Linux内核-内存管理(下)

来源:互联网 发布:ant java api 编辑:程序博客网 时间:2024/06/05 11:05
物理内存管理(页管理)

Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数4k(i386体系结构中)大小页,从而分配和回收内存的基本单位便是内存页了。利用分页管理有助于灵活分配内存地址,因为分配时不必要求必须有大块的连续内存[1],系统可以东一页、西一页的凑出所需要的内存供进程使用。虽然如此,但是实际上系统使用内存还是倾向于分配连续的内存块,因为分配连续内存时,页表不需要更改,因此能降低TLB的刷新率(频繁刷新会很大增加访问速度)。

鉴于上述需求,内核分配物理页为了尽量减少不连续情况,采用了“伙伴”关系来管理空闲页框。伙伴关系分配算法大家不应陌生——几乎所有操作系统书都会提到,我们不去详细说它了,如果不明白可以参看有关资料。这里只需要大家明白Linux中空闲页面的组织和管理利用了伙伴关系,因此空闲页面分配时也需要遵循伙伴关系,最小单位只能是2的幂倍页面大小。内核中分配空闲页框的基本函数是get_free_page/get_free_pages,它们或是分配单页或是分配指定的页框(248…512页)。

 注意:get_free_page是在内核中分配内存,不同于malloc在用户空间中分配malloc利用堆动态分配,实际上是调用brk()系统调用,该调用的作用是扩大或缩小进程堆空间(它会修改进程的brk域)。如果现有的内存区域不够容纳堆空间,则会以页面大小的倍数位单位,扩张或收缩对应的内存区域,但brk值并非以页面大小为倍数修改,而是按实际请求修改。因此Malloc在用户空间分配内存可以以字节为单位分配,但内核在内部仍然会是以页为单位分配的。

   另外需要提及的是,物理页在系统中由页框结构struct page描述,系统中所有的页框存储在数组mem_map[ ]中,可以通过该数组找到系统中的每一页(空闲或非空闲)。而其中的空闲页框则可由上述提到的以伙伴关系组织的空闲页链表free_area[MAX_ORDER]索引。

Linux内存管理(下) - 喧闹的寂寞 - 喧闹的寂寞

 

 内核内存使用

Slab

    所谓尺有所长,寸有所短。以页为最小单位分配内存对于内核管理系统物理内存来说的确比较方便,但内核自身最常使用的内存却往往是很小(远远小于一页)的内存块——比如存放文件描述符、进程描述符、虚拟内存区域描述符等行为所需的内存都不足一页。这些用来存放描述符的内存相比页面而言,就好比是面包屑与面包。一个整页中可以聚集多个这种这些小块内存;而且这些小块内存块也和面包屑一样频繁地生成/销毁。

  为了满足内核对这种小内存块的需要,Linux系统采用了一种被称为slab分配器的技术。Slab分配器的实现相当复杂,但原理不难,其核心思想就是“存储池[2]的运用。内存片段(小块内存)被看作对象,当被使用完后,并不直接释放而是被缓存到“存储池”里,留做下次使用,这无疑避免了频繁创建与销毁对象所带来的额外负载。

Slab技术不但避免了内存内部分片(下文将解释)带来的不便(引入Slab分配器的主要目的是为了减少对伙伴系统分配算法的调用次数——频繁分配和回收必然会导致内存碎片——难以找到大块连续的可用内存,而且可以很好利用硬件缓存提高访问速度。

    Slab并非是脱离伙伴关系而独立存在的一种内存分配方式,slab仍然是建立在页面基础之上,换句话说,Slab将页面来自于伙伴关系管理的空闲页框链撕碎成众多小内存块以供分配,slab中的对象分配和销毁使用kmem_cache_alloc()kmem_cache_free() 

 

Kmalloc

Slab分配器不仅仅只用来存放内核专用的结构体,它还被用来处理内核对小块内存的请求。当然鉴于Slab分配器的特点,一般来说内核程序中对小于一页的小块内存的请求才通过Slab分配器提供的接口Kmalloc来完成(虽然它可分配32 131072字节的内存)。内核内存分配角度讲kmalloc可被看成是get_free_pages)的一个有效补充,内存分配粒度更灵活了。

有兴趣的话可以到/proc/slabinfo中找到内核执行现场使用的各种slab信息统计,其中你会看到系统中所有slab的使用信息。从信息中可以看到系统中除了专用结构体使用的slab外,还存在大量为Kmalloc而准备的Slab(其中有些为dma准备的)。

 

 

内核非连续内存分配(Vmalloc

 

伙伴关系也好、slab技术也好,从内存管理理论角度而言目的基本是一致的,它们都是为了防止“分片”,不过分片又分为外部分片和内部分片之说所谓内部分片是说系统为了满足一小段内存区(连续)的需要,不得不分配了一大区域连续内存给它,从而造成了空间浪费外部分片是指系统虽有足够的内存,但却是分散的碎片,无法满足对大块“连续内存”的需求无论何种分片都是系统有效利用内存的障碍slab分配器使得含与一个页面内众多小块内存可独立被分配使用,避免了内部分片,节约了空闲内存。伙伴关系把内存块按大小分组管理,一定程度上减轻了外部分片的危害,因为页框分配不在盲目,而是按照大小依次有序进行,不过伙伴关系只是减轻了外部分片,但并未彻底消除。你自己笔画一下多次分配页框后,空闲内存的剩余情况吧。

所以避免外部分片的最终思路还是落到了如何利用不连续的内存块组合成“看起来很大的内存块”——这里的情况很类似于用户空间分配虚拟内存,内存逻辑上连续,其实影射到并不一定连续的物理内存上。Linux内核借用了这个技术,允许内核程序在内核地址空间中分配虚拟地址,同样也利用页表(内核页表)将虚拟地址映射到分散的内存页上。以此完美地解决了内核内存使用中的外部分片问题。内核提供vmalloc函数分配内核虚拟内存,该函数不同于kmalloc,它可以分配较Kmalloc大得多的内存空间(可远大于128K,但必须是页大小的倍数),但相比Kmalloc来说,Vmalloc需要对内核虚拟地址进行重影射,必须更新内核页表,因此分配效率上要低一些(用空间换时间)。

与用户进程相似内核也有一个名为init_mmmm_strcut结构来描述内核地址空间,其中页表项pdg=swapper_pg_dir包含了系统内核空间(3G-4G)的映射关系。因此vmalloc分配内核虚拟地址必须更新内核页表,kmallocget_free_page由于分配的连续内存,所以不需要更新内核页表

Linux内存管理(下) - 喧闹的寂寞 - 喧闹的寂寞

vmalloc分配的内核虚拟内存与kmalloc/get_free_page分配的内核虚拟内存位于不同的区间,不会重叠。因为内核虚拟空间被分区管理,各司其职。进程空间地址分布从0到3G(其实是到PAGE_OFFSET,0x86中它等于0xC0000000)3Gvmalloc_start这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页框表mem_map等等)。比如我使用的系统内存是64M(可以用free看到),那么(3G——3G+64M)这片内存就应该映射物理内存,而vmalloc_start位置应在3G+64M附近(说附近因为是在物理内存映射区与vmalloc_start期间还回存在一个8M大小的gap来防止跃界),vmalloc_end的位置接近4G(说接近是因为最后位置系统会保留一片128k大小的区域用于专用页面映射,还由可能会由高端内存映射区,这些都是细节,这里我们不做纠缠) 

   

Linux内存管理(下) - 喧闹的寂寞 - 喧闹的寂寞 

                  进程地址空间   物理内存映射区(0~3G)    内核虚拟空间(Vmalloc_start~Vmalloc_end) 

上图是内存分布的模糊轮廓

 

   get_free_pageKmalloc函数所分配的连续内存都陷于物理映射区域,所以它们返回的内核虚拟地址和实际物理地址仅仅是相差一个偏移量(PAGE_OFFSET),你可以很方便的将其转化为物理内存地址。同时内核也提供了virt_to_phys()函数将内核虚拟空间中的物理内存映射区地址转化为物理地址。要知道,物理内存映射区中的地址与内核页表是有序对应,系统中的每个物理页框都可以找到它对应的内核虚拟地址(在物理内存映射区中的)。

vmalloc分配的地址则限于vmalloc_startvmalloc_end之间。每一块vmalloc分配的内核虚拟内存都对应一个vm_struct结构体(可别和vm_area_struct搞混,那可是进程虚拟内存区域的结构),不同的内核虚拟地址被4k打大小空闲区的间隔,以防止越界——见下图)。与进程虚拟地址的特性一样,这些虚拟地址可与物理内存没有简单的位移关系,必须通过内核页表才可转换为物理地址或物理页。它们有可能尚未被映射,在发生缺页时才真正分配物理页框。

Linux内存管理(下) - 喧闹的寂寞 - 喧闹的寂寞

 

这里给出一个小程序帮助大家认请上面几种分配函数所对应的区域。

#include<linux/module.h>

#include<linux/slab.h>

#include<linux/vmalloc.h>

unsigned char *pagemem;

unsigned char *kmallocmem;

unsigned char *vmallocmem;

int init_module(void)

{

 pagemem = get_free_page(0);

 printk("<1>pagemem=%s",pagemem);

 kmallocmem = kmalloc(100,0);

 printk("<1>kmallocmem=%s",kmallocmem);

 vmallocmem = vmalloc(1000000);

 printk("<1>vmallocmem=%s",vmallocmem);

}

void cleanup_module(void)

{

 free_page(pagemem);

 kfree(kmallocmem);

 vfree(vmallocmem);

}

 

内存管理实例

代码功能介绍

我们希望能通过访问用户空间的内存达到读取内核数据的目的,这样便可进行内核空间到用户空间的大规模信息传输。

具体的讲,我们要利用内存映射功能,将系统内核中的一部分虚拟内存映射到用户空间,从而使得用户空间地址等同与被映射的内核内存地址。

代码结构体系介绍

内核空间内存分配介绍

因此我们将试图写一个虚拟字符设备驱动程序,通过它将系统内核空间映射到用户空间——将内核虚拟内存映射到用户虚拟地址。当然映射地址时少不了定位内核空间对应的物理地址,并且还要建立新的用户页表项,以便用户进程寻址时能找到对应的物理内存。

从中应该看出,需要我完成既定目标,我们需要获得:被映射内核空间物理地址  建立对应的用户进程页表

在内核空间中主要存在kmalloc分配的连续物理空间vmalloc分配的非物理连续空间kmalloc分配的空间往往被称为内核逻辑地址,由于它是连续分配(直接处理物理页框),而且分配首地址一定,所以其分配的内核虚拟地址对应的实际物理地址很容易获得:内核虚拟地址-PAGE_OFFSET(0xC0000000)(内核有对应例程virt_to_phys)=物理地址,而且其对应的页表属于内核页表(swapper_pg_dir——在系统初始化时就已建立,因此省去了建立页表的工作。

vmalloc分配的空间被称为内核虚拟地址,它的问题相对要复杂些,这是因为其分配的内核虚拟内存空间并非直接操作页框,而是分配的是vm_struct结构。该结构逻辑上连续但对应的物理内存并非连续,也就是说它vamlloc分配的内核空间地址所对应的物理地址并非可通过简单线性运算获得。从这个意义上讲,它的物理地址在分配前是不确定的,因此虽然vmalloc分配的空间与kmalloc一样都是由内核页表来映射的,vmalloc分配内核虚拟地址时必须更新内核页表。 

实例蓝图

为了近可能丰富我们的例子程序的场景,我们选择映射vmalloc分配的内核虚拟空间(下面我们简称为vk地址)到用户空间。

要知道用户进程操作的是虚拟内存区域vm_area_struct,我们此刻需要将用户vma区间利用用户页表映射到vk对应的物理内存上去(如下图所示)。这里主要工作便是建立用户也表项完成映射工作,而这个工作完全落在了vma->nopage[3]操作上,该方法会帮助我们在发生“缺页”时,动态构造映射所需物理内存的页表项。

Linux内存管理(下) - 喧闹的寂寞 - 喧闹的寂寞

 

 

    我们需要实现nopage方法,动态建立对应页表,而在该方法中核心任务是寻找到vk地址对应的内核逻辑地址[4]。这必然需要我们做以下工作:

a)         找到vmalloc虚拟内存对应的内核页表,并寻找到对应的内核页表项。

b)        获取内核页表项对应的物理页框指针。

c)  通过页框得到对应的内核逻辑地址。

Linux sys_exec中可执行文件映射的建立及读取
     1. 创建一个vm_area_struct;
      2. 圈定一个虚用户空间,将其起始结束地址(elf段中已设置好)保存到vm_start和vm_end中;
      3. 将磁盘file句柄保存在vm_file中;
      4. 将对应段在磁盘file中的偏移值(elf段中已设置好)保存在vm_pgoff中;
      5. 将操作该磁盘file的磁盘操作函数保存在vm_ops中(vma->nopage保存在该处);
      6. 注意这里没有为对应的页目录表项创建页表,更不存在设置页表项了;

 

基本函数

我们实例将利用一个虚拟字符驱动程序,驱动负责将一定长的内核虚拟地址(vmalloc分配的)映射到设备文件上,以便可以通过访问文件内容来达到访问内存的目的。这样做的最大好处是提高了内存访问速度,并且可以利用文件系统的接口编程(设备在Linux中作为特殊文件处理)访问内存,降低了开发难度。 

       Map_driver.c就是我们的虚拟字符驱动程序。不用说,它要实现文件操作表(file_operations——字符驱动程序主要做的工作便是实现该结构中的为了要完成内存映射除了常规的open/release操作外必须自己实现mmap操作该函数将给定的文件映射到指定的地址空间上,也就是说它将负责把vmalloc分配的内核地址映射到我们的设备文件上。

我们下面就谈谈mmap操作的实现细节:

文件操作的mmap操作是在用户进行系统调用mmap[5]时被执行的,而且在调用前内核已经给用户找到并分配了合适的虚拟内存区域vm_area_struct,这个区域将代表文件内容,所以剩下要做的便是如何把虚拟区域和物理内存挂接到一起了,即构造页表。由于我门前面所说的原因,我们系统中页表需要动态分配,因此不可使用remap_page_range函数一次分配完成,而必须使用虚拟内存区域自带的nopage方法,在现场构造页表。这样以来,文件操作的mmap的方法只要完成“为它得到的虚拟内存区域绑定对应的操作表vm_operations”即可。于是主要的构造工作就落在了vm_operations中的nopage方法上了。

Nopage方法中核心内容上面已经提到了是“寻找到vk地址对应的内核逻辑地址”,这个解析内核页表的工作是需要自己编写辅助函数vaddr_to_kaddr来完成的,它所作的工作概括来讲就是上文提到的a/b/c三条

整个任务执行路径请看下图。

 

 

Linux内存管理(下) - 喧闹的寂寞 - 喧闹的寂寞

 

STEP BY STEP

编译map_driver.cmap_driver.o模块,具体参数见Makefile

加载模块 insmod map_driver.o

生成对应的设备文件

/proc/devices下找到map_driver对应的设备命和设备号:grep mapdrv /proc/devices

建立设备文件mknod  mapfile c 254 0  (在我系统里设备号为254

    利用maptest读取mapfile文件,将取自内核的信息(”ok”——我们在内核中在vmalloc分配的空间中填放的信息)打印到用户屏幕。

 

全部程序下载 mmap.tar (感谢Martin Frey,该程序主体出自他的灵感) 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 面对喜欢上网又判逆的儿子怎么办 五年级了计算题老是出错怎么办 三年级的孩子老是计算题出错怎么办 写在表上的字写错了怎么办不能涂改 我的孩子做作业很马虎怎么办 幼儿园老师把学生名字写错怎么办 孩子的手写字磨了疙瘩怎么办 我家孩子上一年级算题特慢怎么办 脚注太多导致与正文距离太大怎么办 搜狗输入法打字不显示选字框怎么办 粘贴文字时带自动添加了序号怎么办 电脑上是表字打印出来是坔怎么办 不同颜色的衣服一起洗染色了怎么办 两岁宝宝不爱吃饭怎么办且消瘦 一岁两个月宝宝不爱吃饭怎么办 罗汉鱼头座大颜色不红怎么办 为什么孩子的牙都是龋齿怎么办呀 蓝迪儿童墙膜出现气泡了怎么办 空sd卡或文件系统不受支持怎么办 8个月宝宝吃了纸怎么办 宝宝出生两天了不吃不喝怎么办 八个月宝宝阴唇边红肿痛怎么办 儿童五周岁九个月比同龄矮怎么办 8个月宝宝长牙母乳喂养咬人怎么办 6周的孩子视力低常怎么办 宝宝吃了甜食生痰咳嗽怎么办 两个月的边牧抵抗力差怎么办 阴茎勃起后向上翘的厉害怎么办 5个月宝宝发烧38.5度怎么办 9个月宝宝发烧38.5度怎么办 八个月宝宝只吃母乳不吃奶粉怎么办 八个月母乳不够宝宝不吃奶粉怎么办 八个月宝宝吃母乳不吃奶粉怎么办 八个月宝宝戒奶不吃奶粉怎么办 刚满月的宝宝发烧38度怎么办 未满月的宝宝发烧38度怎么办 半月大的婴儿吃奶就漾奶怎么办 上司交给你不能完成的任务怎么办 电脑光驱里放入光碟放不出来怎么办 黑暗之魂3太难了怎么办 苹果手机下载的游戏闪退怎么办