6.内存管理

内存管理

在内核中分配内存不像在其他地方分配内存那么容易。造成这种局面的因素很多,根本原因是内核本身不能像用户空间那样奢侈地使用内存。

1.页

内核把物理页作为内存管理的基本单位。内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单位。体系结构不同,支持的页大小也不同。内核用struct page结构表示每个物理页:

struct page {
         unsigned long flags;                                                      
         atomic_t count;                
         unsigned int mapcount;          
         unsigned long private;          
         struct address_space *mapping;  
         pgoff_t index;                  
         struct list_head lru;  
         void *virtual;                  
};

对上面重要变量说明:

  • flag的每一位单独表示一个状态,标志定义在<linux/page-flags.h>
  • count存放页的引用次数,为0则是空闲页
  • virtual是页的虚拟地址

2.区

由于硬件限制,内核对页不能一视同仁。有些页位于内存特定的物理地址上,不能用于一些特定的任务,因此内核把页划分为不同的区(zone)。Linux必须处理如下两种由于硬件缺陷而引起的内存寻址问题:

  • 一些硬件只能用某些特定内存来执行DMA(直接内存访问)
  • 一些体系结构的内存物理寻址范围比虚拟寻址范围大的多,因此部分内存永远无法映射到内核空间

因此Linux主要存在四种区:

  • ZONE_DMA,包含的页可以执行DMA
  • ZONE_DMA32,和ZONE_DMA不同在于,这些页面只能被32位设备访问,某些体系下该区比ZONE_DMA更大
  • ZONE_NORMAL,能够正常映射的页
  • ZONE_HIGHMEM,不能永久被映射到内核空间地址的区

每个区都用struct zone表示,定义在<linux/mmzone.h>

struct zone {
         spinlock_t              lock;
         unsigned long           free_pages;
         unsigned long           pages_min, pages_low, pages_high;
         unsigned long           protection[MAX_NR_ZONES];
         spinlock_t              lru_lock;       
         struct list_head        active_list;
         struct list_head        inactive_list;
         unsigned long           nr_scan_active;
         unsigned long           nr_scan_inactive;
         unsigned long           nr_active;
         unsigned long           nr_inactive;
         int                     all_unreclaimable; 
         unsigned long           pages_scanned;    
         struct free_area        free_area[MAX_ORDER];
         wait_queue_head_t       * wait_table;
         unsigned long           wait_table_size;
         unsigned long           wait_table_bits;
         struct per_cpu_pageset  pageset[NR_CPUS];
         struct pglist_data      *zone_pgdat;
         struct page             *zone_mem_map;
         unsigned long           zone_start_pfn;
 
         char                    *name;
         unsigned long           spanned_pages;  
         unsigned long           present_pages;  
};

其中,lock是自旋锁防止该结构被并发访问;watermark数组持有该区的最小值、最低和最高水位值;name是以NULL结尾的区名字,三个区名字为DMANormalHighMem

3.获得页

前面了解了页和区的概念,下面讲述如何请求和释放页。

请求页

标志 描述
alloc_page(gfp_mask) 只分配一页,返回指向页结构的指针
alloc_pages(gfp_mask, order) 分配2^order页,返回指向第一页页结构的指针
__get_free_page(gfp_mask) 只分配一页,返回指向其逻辑地址的指针
__get_free_pages(gfp_mask, order) 分配2^order页,返回指向第一页逻辑地址的指针
get_zeroed_page(gfp_mask) 只分配一页,让其内容填充0,返回指向逻辑地址的指针

释放页

释放页需要谨慎,只能释放属于你的页。传递了错误的struct page或地址,,用了错误的order值都可能导致系统崩溃。

例如释放8个页:

free_pages(page, 3)

可以看到释放过程与C语言的释放内存很相似的。

4.kmalloc()

上述的方法是对以页为单位的连续物理页,而以字节为单位的分配,内核提供的函数是kmalloc()。使用方法和malloc()类似,只是多了一个flags参数,其在<linux/slab.h>中声明:

void * kmalloc(size_t size, gfp_t flags)

kmalloc()对应的函数就是kfree()kfree()声明于<linux/slab.h>中:

void kfree(const void *ptr)

5.vmalloc()

vmalloc()kmalloc()工作方式类似,但是kmalloc()使用的连续的物理地址。vmalloc()使用非连续的物理地址,该函数为了把物理上不连续的页转换为虚拟地址空间上连续的页,必须专门建立页表项。

大多数情况下,一般硬件设备需要使用连续的物理地址,而软件可以使用非连续的物理地址,但是大多数情况,为了性能提升,内核往往用kmalloc()更多。

vmalloc()函数声明在<linux/vmalloc.h>中,定义在<mm/vmalloc.c>中。用法和用户空间的malloc()相同:

void * vmalloc(unsigned long size)

释放通过vmalloc()所获得的内存,使用下面函数:

void vfree(const void *addr)

6.slab层

分配和释放数据结构是所有内核中最常用操作之一。为了便于数据的频繁分配和回收,编程人员常常会用到空闲链表空闲链表包含可供使用的、已经分配好的数据结构块。当代名需要一个新的数据结构实例时,就可以从空闲链表中抓取一个,而不需要分配内存,再把数据存放进去。不需要这个数据结构的实例时,就放回空闲链表,而不是释放它。空闲链表相对于对象的高速缓存——快速存储频繁使用的对象类型(这个策略简直是awesome!)。

没有免费的蛋糕,对于空闲链表存在的主要问题是无法全局控制。当内存紧缺时,内核无法通知每个空闲链表,让其收缩缓存的大小,以便释放部分内存。实际上,内核根本就不知道任何空闲链表。因此未来弥补这个缺陷,Linux内核提供了slab层(也就是所谓的slab分配器)。slab分配器扮演了通用数据结构缓存层的角色。对于slab分配器设计需要考虑一下几个原则:

  • 频繁使用的数据结构也会频繁分配和释放,因此应当缓存它们。
  • 频繁分配和回收必然会导致内存碎片。为了避免这种情况,空闲链表的缓存会连续地存放。因为已释放的数据结构又会放回空闲链表,不会导致碎片。
  • 回收的对象可以立即投入下一次分配,因此,对于频繁的分配和释放,空闲链表能够提高其性能。
  • 如果让部分缓存专属于单个处理器,那么,分配和释放就可以在不加SMP锁的情况下进行。
  • 对存放的对象进行着色,以防止多个对象映射到相同的高速缓存行。

slab层把不同的对象划分为所谓的高速缓存组,其中每个高速缓存都存放不同类型的对象,每种对象类型对应一个高速缓存,例如一个高速缓存用于task_struct,一个用于struct inode。kmalloc()接口建立在slab层上,使用了一组通用高速缓存。这些缓存又被分为slabs,slab由一个或多个物理上连续的页组成,一般情况下,slab也就仅仅由一页组成。每个高速缓存可以由多个slab组成。每个slab都包含一些对象成员,这里的对象指的是被缓存的数据结构,每个slab处于三种状态之一:满,部分满,空。当内核的某一部分需要一个新的对象时,先从部分满的slab中进行分配。如果没有部分满的slab,就从空的slab中进行分配。如果没有空的slab,就要创建一个slab了。下图给出高速缓存,slab及对象之间的关系:

高速缓存、slab和对象关系

每个缓存都使用kmem_catche结构表示,结构中包含3个链表。这些链表包含高速缓存所有的slab。slab描述符struct slab用来描述每个slab:

struct slab {
        struct list_head  list;       /*满,部分满或空链表*/
        unsigned long     colouroff;  /*slab着色的偏移量*/
        void              *s_mem;     /*在slab中的第一个对象*/
        unsigned int      inuse;      /*已分配的对象数*/
        kmem_bufctl_t     free;       /*第一个空闲对象*/
};

slab层负责内存紧缺情况下所有底层的对齐、着色、分配、释放和回收等。

7.栈上的静态分配

在前面讨论的分配例子,不少可以分配到栈上。用户空间可以奢侈地负担很大的栈,而且栈空间还可以动态增长,相反内核空间不能——栈小而固定。给每个进程分配一个固定小栈,可以减小内存消耗和栈管理任务负担。

进程的内核栈大小既依赖体系结构,也和编译时的选项有关。在任何一个函数中,都必须尽量节省栈资源。让函数所有局部变量之后不要超过几百字节(栈上分配大量的静态分配是不理智的),栈溢出就会覆盖掉临近堆栈末端的数据。首先就是前面讲的thread_info

8.每个CPU使用数据

支持SMP的操作系统使用每个CPU上的数据,对于给定的处理器其数据是唯一的。一般而言,每个CPU的数据存放在一个数组内,数组中的每一项对应着系统上一个存在的处理器,安装当前处理器号就能确定这个数组的当前元素。

在Linux中引入了新的操作接口称为percpu,头文件<linux/percpu.h>声明了所有接口操作例程,可以在文件mm/slab.c<asm/percpu.h>找到定义。

使用每个CPU数据的好处是:

  • 减少了数据锁定
  • 大大减少了缓存失效,一个CPU操作另一个CPU的数据时,必须清理另一个CPU的缓存并刷新,存在不断的缓存失效。持续不断的缓存失效称为缓存抖动

这种方式的唯一安全要求就是禁止内核抢占,同时注意进程在访问每个CPU数据过程中不能睡眠——否则,唤醒之后可能已经到其他处理器上了。

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

推荐阅读更多精彩内容