背景
本篇文章企图通过linux内核源码剖析linux的显存管理机制,但是对比内核提供的几个分配显存的插口函数。之后聊下slab层的用法以及插口函数。
内核分配显存与用户态分配显存
内核分配显存与用户态分配显存其实是不同的,内核不可以像用户态那样奢华的使用显存,内核使用显存一定是谨小自警的。而且,在用户态若果出现显存溢出由于有显存保护机制,可能只是一个报错或警告,而在内核态若出现显存溢出后果都会严重的多(虽然再没有管理者了)。
页
我们晓得处理器处理数据的基本单位是字。而内核把页作为显存管理的基本单位。这么,页在显存中是怎样描述的?
内核用structpage结构体表示系统中的每一个数学页:
flags储存页的状态,如该页是不是脏页。
_count域表示该页的使用计数鸟哥的linux私房菜,假如该页未被使用,就可以在新的分配中使用它。
要注意的是,page结构体描述的是化学页而非逻辑页,描述的是显存页的信息而不是页中数据。
实际上每位化学页面都由一个page结构体来描述,有的人可能会吃惊说那这得须要多少显存呢?我们可以来算一下,若一个structpage占用40字节显存,一个页有8KB,显存大小为4G的话,共有524288个页面,须要恰好20MB的大小来储存结构体。这相对于4G的显存根本九牛一毛。
区
有些页是有特定用途的。例如显存中有些页是专门用于DMA的。
内核使用区的概念将具有相像特点的页进行分组。区是一种逻辑上的分组的概念,而没有数学上的意义。
区的实际使用和分布是与体系结构相关的。在x86体系结构中主要分为3个区:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。
ZONE_DMA区中的页拿来进行DMA时使用。ZONE_HIGHMEM是高档显存,其中的页不能永久的映射到内核地址空间,也就是说,没有虚拟地址。剩余的显存就属于ZONE_NORMAL区。
我们可以看一下描述区的结构体structzone(在linux/mmzone.h中定义)。
这个结构体比较长,我只截取了一部份下来。
实际上不是所有的体系结构都定义了全部区,有些64位的体系结构,例如Intel的x86-64体系结构可以映射和处理64位的显存空间,所以其没有ZONE_HIGHMEM区。而有些体系结构中的所有地址都可用于DMA,所以这种体系结构就没有ZONE_DMA区。
内核中显存分配插口
我们如今早已大体了解了内核中的页与区的概念及描述。接出来我们就可以来瞧瞧内核中有什么显存分配与释放的插口。在内核中,我们正是通过这种插口来分配与释放显存的。首先我们来瞧瞧以页为单位进行分配的插口函数。
获得页与释放页获得页
获得页使用的插口是alloc_pages函数,我们来看下它的源码(坐落linux/gfp.h中)
可以看见,该函数返回值是指向page结构体的表针,参数gfp_mask是一个标志,简单来讲就是获得页所使用的行为方法。order参数规定分配多少页面,该函数分配2的order次方个连续的数学页面。返回的表针指向的是第一page页面。
获得页的方法不只一种,我们还可以使用__get_free_pages函数来获得页,该函数和alloc_pages的参数一样,但是它会返回一个虚拟地址。源码如下:
可以听到,这个函数虽然也是调用了alloc_pages函数,只不过在获得了structpage结构体后使用page_address函数获得了虚拟地址。
另外还有alloc_page函数与__get_free_page函数,都是获得一个页,虽然就是将上面两个函数的order分别置为了0而已。这儿不赘言了。
我们在使用这种插口获取页的时侯可能会面对一个问题,我们获得的那些页若是给用户态用,即使那些页中的数据都是随机形成的垃圾数据,不过,即使机率很低,而且也有可能会包含个别敏感信息。所以,更慎重些linux内核24版源代码分析大全(清晰版),我们可以将获得的页都填充为0。这会用到get_zeroed_page函数。看下它的源码:
这个函数也用到了__get_free_pages函数。只是加了一种称作__GFP_ZERO的gfp_mask形式。所以linux计划任务,这种获得页的函数最终调用的都是alloc_pages函数。alloc_pages函数是获得页的核心函数。
释放页
当我们不再须要个别页时可以使用下边的函数释放它们:
__free_pages(structpage*page,unsignedintorder)
__free_page
free_pages
free_page(unsignedlongaddr,unsignedintorder)
这种插口都在linux/gfp.h中。
释放页的时侯一定要当心慎重,内核中操作不同于在用户态,若是将地址弄错,或是order弄错,这么都可能会造成系统的崩溃。若是在用户态进行非法操作,内核作为管理者都会制止并发出警告,而内核是完全信赖自己的,若是在内核态中有非法操作,这么内核可能会死掉的。
kmalloc与vmalloc
后面讲的这些插口都是以页为单位进行显存分配与释放的。而在实际中内核须要的显存不一定是整个页,可能只是以字节为单位的一片区域。这两个函数就是实现这样的目的。不同之处在于,kmalloc分配的是虚拟地址连续,化学地址也连续的一片区域,vmalloc分配的是虚拟地址连续,化学地址不一定连续的一片区域。这儿仍旧须要非常注意的就是使用释放显存的函数kfree与vfree时一定要注意确切释放,否则会发生不可预测的严重后果。
slab层
分配和释放数据结构是内核中的基本操作。有些多次会用到的数据结构假如频繁分配显存必然引起效率低下。slab层就是用于解决频繁分配和释放数据结构的问题。为易于理解slab层的层次结构,请看右图
简单的说,数学显存中有多个高速缓存,每位高速缓存都是一个结构体类型,一个高速缓存中会有一个或多个slab,slab一般为一页,其中储存着数据结构类型的实例化对象。
分配高速缓存的插口是structkmem_cachekmem_cache_create(constchar*name,size_tsize,size_talign,unsignedlongflags,void(*ctor)(void))。
它返回的是kmem_cache结构体。第一个参数是缓存的名子,第二个参数是高速缓存中每位对象的大小,第三个参数是slab内第一个对象的偏斜量。剩下的就不细说。
其实,这个插口函数为一个结构体分配了高速缓存,这么高速缓存有了,是不是就要为缓存中分配实例化的对象呢?这个插口是
void*kmem_cache_alloc(structkmem_cache*cachep,gfp_tflags)
参数是kmem_cache结构体,也就是分配好的高速缓存,flags是标志位。
具象的介绍看着不直观,我们看个具体的反例。之前我写过一个关于jbd2日志系统的博客linux内核24版源代码分析大全(清晰版),介绍过jbd2的模块初始化过程。其中就提及过jbd2在进行模块初始化的时侯是会创建几个高速缓冲区的。如下:
我们瞧瞧第一个创建缓冲区的函数。
首先是断定缓冲区一定为空的。之后用kmem_cache_create创建了两个缓冲区。两个高速缓冲区就如此创建好了。看右图
这儿用kmem_cache结构体,也就是jbd2_revoke_record_cache高速缓存实例化了一个对象。
总结
显存管理的linux内核源码我只剖析了一小部份,主要是总结了一下内核分配与回收显存的插口函数及其用法。
最后给你们【文章福利】小编自己整理了一些个人认为比较好的学习书籍、视频资料有须要的可以私信回复【内核】自行发放哦!!
原文链接: