日期:2014-05-16  浏览次数:21120 次

Kmalloc内部实现:挖掘Linux内核内存分配(一)

为什么写这篇文章:

最近我在几个linux设备上发现整数溢出,以致我开始用kmalloc重写内存分配。

注意:
理解这篇文章需要知道点linux内核。有时候我肯能用熟知的一些函数名。如果你不知道他们的意义,可以快速google。我提到的所有关于cache的函数都能在/usr/src/linux/mm/slab.c找到。一些数字是在IA-32架构上给出的。我努力加粗函数名和数据类型。在一些代码中我删除了调试代码。我忽略了SMP(多核)代码,因为我没有多核系统。我是个偷懒的人。你准备读了吗?

整体浏览:
任何操作系统需要一个方式来管理物理内存。设备驱动和内核的其他部分能够分配内存块去存储数据。这些内存来自系统RAM,当我们谈到系统RAM,它常指代Page_size块。在一些架构上是4k。然而,当一些驱动或者其他内核函数需要动态分配内存,他们经常不需要整页。因此,需要一些实体来管理RAM,处理不同大小内存卡的请求。这样一个分配器需要仔细平衡两个目标:速度和最小化碎片大小,在块上分配但没用到的内存。后者尤为重要,因为任何碎片化内存将会浪费RAM。这个工作有kmalloc函数来执行,它的相对函数是kfree。

Kmalloc 做的不多:

事实上,kmalloc只是一个实际内存管理器的接口。因此为了理解kmalloc,非常需要首先理解缓存分配机制。这将使这次讨论的焦点。理解之后,你将会发现kmalloc只是缓存分配服务的一个使用者。

缓存概述:

     缓存分配器背后的想法很简单:将内存划分为对象池,每个对象池包含同样大小的对象,并且不同的池包含不同的大小。因此,当有人请求一个给定大小的内存块,找到第一个能够足够大的块的内存池,分配给他完事。这就是所有要做的事情。下面内容将会复杂点。需要考虑一些事情:每个池多大为好?如果我们用完一个池的对象,我们如何创建更多的池?我们如何追踪池子的对象是free的或者在使用中?我们应该有多少池?池大小之间的gap多大为好? 
正如之前所说,内存被分为包含对象的池。每个池被认为是一个缓存,内核中有许多这样的缓存池。这些池的关键数据结构是:kmem_cache_t。它用来处理池用到的所有信息,比如每个对象的大小以及每个池中对象数目。驱动能够通过kmem_cache_create函数创建它选定大小的内存池。这个函数创建调用者给定大小的内存,然后将它插入已创建的全局内存池cache_cache。cache_cache是一个静态kem_cache_t对象,用来管理所有系统级缓存。你可以通过/proc/slabinfo看到所有缓存大小。除了全局缓存池,我们也有通用缓存池。通用缓存池是一个缓存数组,每个元素后一个是前一个的2指数幂倍。在IA-32中以32开始,一直到131072*2.对于每个大小,有两个cache,一个为普通内存,一个为DMA。在IA-32,DMA内存必须是16位可寻址的。这就是kmalloc如何工作的。当kmalloc被调用,他搜索通用缓存知道找到一个合适的缓存块。然后调用__kem_cache_alloc来从缓存获取对象并返回给调用者。同样的,kfree只是通过调用__kem_cache_free来返回对象到他的缓存池。

Caches 和 slabs:

     关键的数据结构是kem_cache_t。它有多个成员,下面是一些重要的:
        

     struct list_head    slabs_full;
    struct list_head    slabs_partial;
    struct list_head    slabs_free;

       这些链表是为这个缓存的slab的。解释在下面。

    unsigned int        objsize;
缓存中每个对象的大小。

    unsigned int        flags;  
缓存的标志位。决定某些操作,并且存储对象追踪信息。

    unsigned int        num;    
        在每个slab上存储的对象大小。

    spinlock_t      spinlock;   

       多CPU中保护并发访问的结构。

 &n