内核内存管理

在内核里分配内存可不像在其他地方分配内存那么容易。造成这种局面的因素很多。从根本上讲,是因为内核本身不能像用户空间那样奢侈地使用内存。内核与用户空间不同,它不支持简单便捷的内存分配方式。处理内存错误对内核来说也决非易事。正是由于这些限制,再加上内存分配机制不能太复杂,所以在内核中获取内存要比在用户空间复杂得多。不过,我们会看到,也不是说内核的内存分配就困难得不得了。只是和用户空间中的内存分配不太一样而已。

  • 内核把物理页作为内存管理的基本单位。
  • 从虚拟内存的角度来看,页就是最小单位。
 struct page{
    page_flags_t    flags;//flag域用来存放页的状态。这些状态包括页是不是脏的
    atomic_t    __count;  //__count域存放页的引用计数—也就是这一页被引用了多少次
    atomic_t    __mapcount;
    unsigned long private;
    struct address_space    *mapping;
    pgoff_t    index;
    struct list_head    lru;
    void    *virtual;    //virtual域是页的虚拟地址。通常情况下,它就是页在虚拟内存中的地址
    }

  • 内核把页划分为不同的区(zone)。
  • 内核使用区对具有相似特性的页进行分组
  • Linux使用了三种区:
    ZONE_DMA:这个区包含的页能用来执行DMA操作。
    ZONE_NORMAL:这个区包含的都是能正常映射的页。
    ZONE_HIGHMEM:这个区包含“高端内存”,其中的页能不永久地映射到内核地址空间。
  • 这些区定义于<linux/mmzone.h>中。
  • 每个区都用struct zone表示。该结构描述了page的状况,如该区还有多少个page没有被分配等信息。


获得页

  • 内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口。所有这些接口都以页为单位分配内存,定义于<linux/gfp.h>中。最核心的函数:
    struct page *alloc_pages(unsigned int gfp_mask,unsigned int order)
    该函数分配2的order次方个连续的物理页,并返回一个指针,该指针指向第一个页的page结构体,如果出错,就返回NULL。
  • 把给定的页转换成它的逻辑地址:
    void *page_address(struct page *page);
    该函数返回一个指针,指向给定物理页当前所在的逻辑地址。
  • 如果你无须用到struct page,你可以调用:
    unsigned long __get_free_pages(unsigned int gfp_mask,unsigned int order)
    这个函数与alloc_pages()作用相同,不过它直接返回所请求的第一个页的逻辑地址。

释放页

  • 当你不再需要页时可以用下面的一族函数来释放它们:
    void free_pages(unsigned long addr,unsigned int order)
    void free_page(unsigned long addr)
    释放页时要谨慎,只能释放属于你的页。传递了错误的地址,用了错误的order值,都可能导致系统崩溃。

kmalloc

  • kmalloc()函数用来获取指定大小的内存;函数原型如下:
    void *kmalloc(size, gfp_mask);
    用它可以获得以字节为单位的一块内核内存。
    kmalloc()在<linux/slab.h>中声明;
    这个函数返回一个指向内存块的指针,其内存块至少要有size大小。
    所分配的内存区在物理上是连续的。
    在出错时,它返回NULL。
  • gfp_mask标志
    • Action修饰符
      __GFP_WAIT: 分配器可以睡眠
      __GFP_HIGH: 分配器可以访问紧急事件缓冲池.
      __GFP_IO: 分配器可以启动磁盘I/O.
      __GFP_FS: 分配器能够启动文件系统I/O.
      __GFP_REPEAT: 失败时重新分配.
      __GFP_NOFAIL: 失败时一直重新分配,直到成功.
      __GFP_NORETRY: 分配器不重试.
    • Zone修饰符
      __GFP_DMA
      __GFP_HIGHMEM
    • 行为修饰符与区修饰符的组合
      GFP_ATOMIC: 不能睡眠时使用.
      GFP_NOIO: 可以阻塞,但不启动io.
      GFP_NOFS: 用于文件系统中的部分代码.
      GFP_KERNEL:常规分配,可能阻塞.首选
      GFP_USER: 常规分配,可能阻塞.
      GFP_HIGHUSER: 从高端内存分配,可能阻塞.
      GFP_DMA: 从DMA区分配.


kfree

  • kmalloc() 另一端就是kfree()
  • kfree()声明于<linux/slab.h>中:
    void kfree(const void *ptr);
    kfree()函数释放由kmalloc()分配出来的内存块。
  • 举个栗子
char *buf;
buf = kmalloc(BUF_SZ, GFP_KERNEL);
if (buf == NULL)
        /* deal with error */
/* Do something with buf */
kfree(buf);

vmalloc

  • 另外一个分配内存的函数是vmalloc()函数,如下:
    void *vmalloc(unsigned long size);
    vmalloc()分配的内存虚拟地址是连续的,而物理地址则无需连续。
    kmalloc()函数确保页在物理地址上是连续的(虚拟地址自然也是连续的)。
    vmalloc()函数只确保页在虚拟地址空间内是连续的。它通过分配非连续的物理内存块,再“修正”页表,把内存映射到逻辑地址空间的连续区域中,就能做到这点。
    很多内核代码都用kmalloc()来获得内存,而不是vmalloc()。
  • void *vmalloc(unsigned long size);
    该函数返回一个指针,指向逻辑上连续的一块内存区,其大小至少为size。在发生错误时,函数返回NULL。
    函数可能睡眠,因此,不能从中断上下文中进行调用,也不能从其他不允许阻塞的情况下进行调用。
    要释放通过vmalloc()所获得的内存,使用下面的函数:
    void vfree(void *addr)
    这个函数会释放从addr开始的内存块,其中addr是以前由vmalloc()分配的内存块的地址。这个函数也可以睡眠,因此,不能从中断上下文中调用。
    没有返回值。

kmalloc与vmalloc比较

  • kmalloc分配的内存虚拟地址连续,物理地址也连续;vmalloc分配的虚拟地址是连续的,但是物理地址不一定连续。如果想给DMA分内存,只能用kmalloc(size, GFP_DMA)
  • kmalloc分配的内存不能超过128KB,vmalloc分配的内存是没有限制
  • kmalloc是可以使用在中断上下文中(原子过程),但要使用GFP_ATOMIC分配标志kmalloc(size, GFP_ATOMIC),vmalloc是不可以使用在中断上下文中的。

slab层

  • 为了便于数据的频繁分配和回收,编程者常常会用到一个空闲链表。该空闲链表包含有可供使用的、已经分配好的数据结构块。
    当需要一个新的数据结构时,就从空闲链表中抓取一个
    当不需要这个数据结构时,就把它放回空闲链表。
  • 从这个意义上说,空闲链表相当于对象高速缓存以便快速存储频繁使用的对象类型。
  • 空闲链表作为高速缓冲的缺点是不能全局控制。
    当可用内存变得紧缺时,内核无法通知每个空闲链表,让其收缩缓存的大小以便释放出一些内存来。
  • 为了弥补这一缺陷,Linux内核提供了slab层(也就是所谓的slab分配器)。

slab层的设计

  • 每个slab都包含一些对象成员,这里的对象指的是被缓存的数据结构。
  • 每个slab处于三种状态之一:
    满: 没有空闲的对象(slab的所有对象都已被分配)。
    部分满: 一些对象已分配出去,有些对象还空闲着。
    空: 没有分配出任何对象(slab的所有对象都是空闲的)。
  • 当内核的某一部分需要一个新的对象时,先从部分满的slab中进行分配。
  • 如果没有部分满的slab,就从空的slab中进行分配。
  • 如果没有空的slab,就要创建一个slab了。
  • 这种策略能减少碎片。

slab层设计细节

  • 高速缓冲
    • 每个高速缓存都是用kmem_cache结构来表示。
    • 这个结构包含三个链表:
      slabs_full
      slabs_partial
      slabs_empty
      均存放在kmem_list3结构内,这些链表包含高速缓存中的所有slab。
  • slab描述符:struct slab用来描述每个slab


slab分配器的接口

  • 一个新的高速缓存是通过以下函数创建:
    struct kmem_cache *kmem_cache_create (const char *name,size_t size, size_t align,unsigned long flags, void (*ctor)(void *));
    第一个参数是高速缓存的名字
    第二个参数是高速缓存中每个元素的大小。
    第三个参数是高速缓存内第一个对象的偏移。通常为0,
    flags参数是可选的设置项,用来控制高速缓存的行为。通常为0
    参数ctor是高速缓存的构造函数。只有在新的页追加到高速缓存时,构造函数才被调用。实际上,Linux内核的高速缓存不使用构造函数。
  • kmem_cache_create()在成功时会返回一个指向所创建高速缓存的指针,否则,返回NULL。这个函数不能在中断上下文中调用,因为它可能会睡眠。
  • 要销毁一个高速缓存,则调用:
    void kmem_cache_destroy(struct kmem_cache *cachep);
    调用该函数之前必须确保存在以下两个条件:
    高速缓存中的所有slab都必须为空
    在调用kmem_cache_destroy()期间不再访问这个高速缓存
  • 该函数成功执行,返回0;否则,返回非0值。
  • 创建高速缓存之后,就可以通过下列函数从中获取对象:
    void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
    该函数从给定的高速缓存cachep中返回一个指向对象的指针。
    如果高速缓存的所有slab中都没有空闲的对象,那么slab层必须通过kmern_getpages()获取新的slab.任何在新的slab中获取对象。
  • 最后释放一个对象,并把它返回给原先的slab,可以使用下面这个函数:
    void kmem_cache_free(struct kmem_cache *cachep, void *objp)
  • 这样就能把cachep中的对象objp标记为空闲了。

使用哪种方法在内核中分配内存

  • 频繁分配和释放.
    Slab分配器.
  • 需要以页为单位分配内存.
    alloc_pages()
  • 从高端内存分配.
    alloc_pages().
  • 默认
    kmalloc()
  • 不需要连续的页.
    vmalloc()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容