感谢支持
我们一直在努力

Linux入门教程:Linux内核内存分配函数

Linux内核中内存的管理不像在内核外这么简单。和用户空间最大的不同是内核的内从空间不像用户空间那么容易得到,并不是总能轻易的得到想要的内存。

页:

内核最基本的内存管理单元就是页(page),因为MMU管理的内存基本单位是page,其维护着提供虚拟地址到物理地址转换的页表。

内核使用如下数据结构表述page:

 

struct page { 

    unsigned long         flags; //the status of the page,eg if the page is dirty,is locked

 

    atomic_t              _count; //the references to this page 

    atomic_t              _mapcount; 

    unsigned long         private; 

    struct address_space  *mapping; 

    pgoff_t               index; 

    struct list_head      lru; 

    void                  *virtual;//the page’s virtual space

};

需要注意的page数据结构是对机器中所有物理内存的描述并关心页中具体的数据,它只关心诸如page是否空闲,谁在占用这个page等等。

区:

内核将整个内存分为三个区(zone)。

ZONE_DMA–可进行DMA的内存空间,在x86中是低于16M的地址空间

ZONE_NOMAL–在x86中是16M-896M的空间,这部分空间是永久直接映射到内核地址空间的

ZONE_HIGHMEM–动态映射的地址空间

为什么要分区呢?因为在一些体系(如x86)中只有部分内存是可以直接映射的,对x86在896M以上的内存都是动态映射的,所以内核对内存分区描述显得必要,DMA分区是能够进行DMA操作的内存空间。

内核页分配接口:

内核提供若干以page为分配单元的内存分配函数。

struct page * alloc_pages(gfp_t gfp_mask, unsigned int order);

分配2的order次方个连续的页面,返回指向第一个page的指针。当然在实际中我们一般针对分配的内存的虚拟地址进行操作,下面这个

void * page_address(struct page *page);

返回page的逻辑地址指针。

当然为了方便内核也提供直接返回虚拟地址指针的函数,实际就是包装了这两个函数:

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

还有直接分配单个page的函数,实际就是order参数为0的版本:

 

struct page * alloc_page(gfp_t gfp_mask) ;

unsigned long __get_free_page(gfp_t gfp_mask);

如果你想获得的页面全部被初始化为0,内核提供这个调用:

get_zeroed_page(gfp_mask);

工作方式和__get_free_pages相同,除了该函数会将内核空间初始化为0。

这些函数都有相对应的页面释放函数,原型如下:

 

void __free_pages(struct page *page, unsigned int order) ;

void free_pages(unsigned long addr, unsigned int order) ;

void free_page(unsigned long addr);

更常用的分配接口:

除了原始的以页为单元的内存分配函数,内核还提供更为方便的和C语言中接口类似的分配接口,最常用的就是kmalloc函数:

void * kmalloc(size_t size, gfp_t flags);

该接口返回至少size字节的物理连续的内存空间。用法和c语言malloc函数一样。

当然还有类似free的内存释放函数

void kfree(const void *ptr);

内核还提供vmalloc函数,用法和kmalloc一样,唯一的不同的vmalloc不保证分配的内存是物理连续的。

怎么选择用哪个分配函数呢?大多数情况下,我们需要分配的内存并没有需要是物理连续的,除了外设操作的内存需要内存连续,因为对很多外设来说并没有逻辑内存的概念,他只能看到和操作物理内存。其他情况下,我们并没有一定要使用连续物理内存的需要。然而事实是,大多是情况下,内核中还是使用kmalloc来分配内存,这基本上是基于效率考虑。因为vmalloc调用为了使不连续物理内存的逻辑地址连续会有很多附加对页表的操作,所以除了必须(需要使用相当大的内存空间),一般情况直接使用kmalloc函数。

vmalloc也有对应的vfree函数。


gfp_mask参数:


gfp_mask参数可以设置很多值,一下是各个取值的用处(直接引用至LKD):




GFP_ATOMIC  The allocation is high priority and must not sleep. This is the flag to use in interrupt handlers, in bottom halves, while holding a spinlock, and in other situations where you cannot sleep.


GFP_NOWAIT  Like GFP_ATOMIC, except that the call will not fallback on emergency memory pools. This increases the liklihood of the memory allocation failing.


GFP_NOIO  This allocation can block, but must not initiate disk I/O. This is the flag to use in block I/O code when you cannot cause more disk I/O, which might lead to some unpleasant recursion.


GFP_NOFS  This allocation can block and can initiate disk I/O, if it must, but it will not initiate a filesystem operation. This is the flag to use in filesystem code when you cannot start another filesystem operation.


GFP_KERNEL  This is a normal allocation and might block. This is the flag to use in process context code when it is safe to sleep. The kernel will do whatever it has to do to obtain the memory requested by the caller. This flag should be your default choice.


GFP_USER  This is a normal allocation and might block. This flag is used to allocate memory for user-space processes.


GFP_HIGHUSER  This is an allocation from ZONE_HIGHMEM  and might block. This flag is used to allocate memory for user-space processes.


GFP_DMA  This is an allocation from ZONE_DMA. Device drivers that need DMA-able memory use this flag, usually in combination with one of the preceding flags.


使用SLAB缓存分配:


当需要频繁的针对特定数据结构分配和释放内存时,为了避免反复内存分配和释放的开销,Linux内核提供了强大的内存缓存分配策略,即SLAB缓存分配。(实际上kmalloc函数就是建立在slab分配器上的!)


每个slab包含一定数量的对象,即需要被缓存的数据结构,每个slab都有三个状态:full,partial,empty。当试图从slab中分配一个object时,内核优先从partial的slab中获取对象,不行则从empty slab中,如果全部是full则创建一个新的slab。内核中管理slab缓存的组成主要有cache,slab,object,他们的关系可以用下图描述。




cache在内核中使用 kmem_cache结构来描述,包含三个链表–—slabs_full, slabs_partial, 以及slabs_empty。这些list中就包含相应状态的slab,slab在内核中的描述如下:



struct slab { 


    struct list_head  list;       /* full, partial, or empty list */ 


    unsigned long     colouroff;  /* offset for the slab coloring */ 


    void              *s_mem;     /* first object in the slab */ 


    unsigned int      inuse;      /* allocated objects in the slab */


    kmem_bufctl_t     free;       /* first free object, if any */ 



};


下列函数可以创建一个新的cache:



struct kmem_cache * kmem_cache_create(const char *name, size_t size, 


                                                                   size_t align, unsigned long flags, void (*ctor)(void *));


各个参数说明如下:


const char *name:cache的名称。


size_t size:cache中需要缓存的object的大小。


size_t align:slab中第一个object的偏移,一般0即可,使用标准对齐方式。


unsigned long flags:可以为缓存分配执行一些附加操作,没有的话直接0即可。


void (*ctor)(void *):cache的构造函数,你可以赋值一个函数地址,当新的page加入cache后一定会执行该构造函数。可以复赋值为NULL。


销毁一个cache时使用


int kmem_cache_destroy(struct kmem_cache *cachep);


内核提供两个函数负责从指定的cache获取和交还内存:


void * kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);


void kmem_cache_free(struct kmem_cache *cachep, void *objp);

赞(0) 打赏
转载请注明出处:服务器评测 » Linux入门教程:Linux内核内存分配函数
分享到: 更多 (0)

听说打赏我的人,都进福布斯排行榜啦!

支付宝扫一扫打赏

微信扫一扫打赏