打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
linux内核内存管理学习
userphoto

2013.04.02

关注

最近重看了《深入理解linux内核》部分章节,有了更多的体会,以前未理解如今更加清晰了,这篇算是一篇读书笔记,主要希望把一些点串联起来,希望能让我们对linux内存管理有一个整体轮廓的认识,因此并没有深入到细节,如伙伴算法如何实现、slab如何实现,实质上这些内容网上也随处可见。

术语解析

页: 主要指一组数据,可能保存在ram中也可能保存在磁盘里,主要强调一页数据;通常用固定大小(4K或4M)的页来描述逻辑地址空间
页框:限指RAM中的页,跟页大小一致;通常用页框来描述物理内存空间;
页表:页表由页表项组成,每一个页表项指向一个页框

内存寻址

X86提供了分段和分页的内存寻址方式,见下图:


分段逻辑地址到线性地址转换图


二级页表转换图

逻辑地址到物理地址转换图
具体参考http://blog.csdn.net/p0303230/archive/2007/12/24/1965094.aspx

现在操作系统依靠特殊的硬件特性来禁止用户程序直接与底层硬件部分进行交互、或者禁止直接访问任意的物理地址。对于x86而言,这也是实模式和保护模式所完成的功能。

在x86架构中,逻辑地址需要经过分段、分页过程转为实际的物理地址,这是一个映射的过程,是由硬件规定的。但是,如何映射,映射规则是什么,则是软件决定的,实际上这也是linux 内核的一个重要功能。可以说映射操作由cpu和MMU共同完成,但映射规则由内核制定。
Linux 内核内存映射规则的实现主要通过建立合适的内核页表来完成。

内核页表

内核代码运行在0xc00000000-0xffffffff 的地址空间范围,供1G。对内核而言,显然它应该具备访问整个物理RAM的功能,这里即存在如下问题:

  • 内核应首先建立自己的页表,以便内核本身能寻址物理RAM
  • 内核本身的地址空间只有1G(0xc00000000-0xffffffff),对于RAM大于1G时,内核如何寻址?

内核页表的建立分为两个阶段,即临时内核页表和最终内核页表:

临时内核页表:

临时内核页表是在内核编译过程静态初始化的,这就是说内核编译时就先指定了映射规则,这样保证内核被加载后cpu就能正确找到内核代码并执行。内核引导加载程序将内核代码加载到0×100000(1MB)处(前1MB的部分物理内存通常被BIOS使用,为避免把内核装入一组不连续的页框里,linux干脆将内核加载到1MB后)。通常编译后内核是小于7MB的,因此临时内核页表的目的就是要保证能对这前8MB的物理RAM寻址,即将0×00000000~0x007fffff的物理内存映射到线性地址0xc00000000~0xc07fffff的线性地址。为建立这前8MB的映射,需要两个页表、同时在页目录表中占用2项。

临时页全局目录存放在swapper_pg_dir变量中,内核把swapper_pg_dir所有项都填充为0,除0、1、0×300(十进制768)和0×301(十进制769)除外,后两项包含了从0xc0000000到0xc07fffff间的所有线性地址。

最终内核页表

在前面的讨论中,一直存在一个为难题,即内核本身的地址空间只有1G(0xc00000000-0xffffffff),对于RAM大于1G时,内核如何寻址? 因此显然RAM等于1G是一个分界点;另外,对32位的机器而言,通产其线性地址空间为0~4G范围,当RAM大于4G时如何寻址(主要针对cpu物理模型支持地址扩展PAE时)? 因此4G又是另一个分界点。

Linux的实现采用固定+动态映射的方式来解决上述问题。 具体做法是,内核建立0~896MB的内核映射,即将0xc0000000-0xf0000000映射到物理内存前896MB;而对后128MB的线性地址空间(0xf000000~0xffffffff)采用动态映射方法,即根据需要映射到内存896MB以后的任意地址。具体实现机制请参考<<深入理解linux内核>>。

内核地址空间布局


上图是linux内核地址空间布局图。内核地址从PAGE_OFFSET(通常定义为3G,即通常说的0-3G为用户地址空间,3G-4G为内核地址空间)开始,接下来是内核映像(.text等可执行代码区);再接下来是mem_map数组,属于内核分配的动态内存区,占用约为整个RAM的1%;接下来是隔离区;接下来就是vmalloc区域;接着又是隔离区;接着为从PKMAP_BASE到FIXADDR_START的由kmap()函数映射高端内存区;接着是从FIXADDR_START到FIXADDR_TOP是一个固定大小的线形地址空间,属于专用页面映射区;最后128K为隔离区。最后说一下隔离带的作用,主要用于监测内存越界等。

内存管理

本节主要介绍页框管理和内存区管理。页框管理关注以页为单位的管理;内存区管理是运行在页框管理之上的RAM管理,但重点关注具有连续的物理地址和任意长度的内存但与序列的管理。这两个管理重点需要解决的问题分别是,页框管理重点在于解决内存外碎片问题,而内存区管理需要解决的是内存内碎片问题。

页框管理

页框管理,顾名思义,就是以页为单位,kernel管理内存的一种机制。内核必须能区分哪些页框属于进程,而哪些页框属于内核代码或内核数据;以及哪些页框是空闲的等等。页框的状态信息保存在page数据结构中,page结构称之为页描述符。而所有的页描述符存放在mem_map数组中,每个页描述符长度为32字节,所以mem_map所需要的空间略小于整个RAM的1%.

受硬件历史的约束,不同地址的内存页框曾经被硬件用于一些特定目的,即不同地址内的页框可能在某个时期被某个硬件特别使用,因此,不能把所有的页框当做同一类型来处理。Linux内核采用管理区的方式来实现页框管理,即将不同类型的页框划归到不同管理区。具体存在如下三类管理区:

  • ZONE_DMA 包含低于16MB的内存页框
  • ZONE_NORMAL 包含16~896MB之间的页框
  • ZONE_HIGHMEM 896MB以上的内存页框

ZONE_DMA和ZONE_NORMAL管理区内的页框被直接映射到0xc0000000-0xf0000000的线性地址空间内,内核可以直接访问;而ZONE_HIGHMEM尽管也被映射到内核地址空间内,但内核不能直接访问。

在采用alloc_pages(gfp_mask)等请求分配页框的函数中,gfp_mask 是一组标志,包括__GFP_DMA,__GFP_HIGHMEM等,用于请求在某个特定管理区内分配页框。

内核采用伙伴系统算法作为页框的分配、释放算法。 内核应该为分配一组连续的页框而建立一种健壮、高效的分配策略。伙伴算法能有效的控制外碎片的出现。其原理如下:

把所有的空闲页面分为10个块组,每组中块的大小是2的幂次方个页面,例如,第0组中块的大小都为20(1个页面),第1组中块的大小为都为21(2个页面),第9组中块的大小都为29(512个页面)。也就是说,每一组中块的大小是相同的,且这同样大小的块形成一个链表。可以通过查看/proc/buddyinfo 来了解内核伙伴算法的信息。

内存区管理

内存区管理主要关注具有连续的物理地址和任意长度的内存但与序列的管理。伙伴系统采用页框作为管理单元,但如何处理小内存区的请求呢?例如几十或几百个字节。如果为了分配一小片内存而分配一个整页框显然是浪费。对于内存区的管理主要需要解决的是内碎片问题,内碎片的产生主要由于请求的大小跟分配给它的大小不匹配导致的。

内核采用slab分配器来解决上内碎片问题。

进程地址空间

进程地址空间是一个非负整数地址的有序集合:{0,1,2….}.程序员在编写应用程序,显然需要关心它的程序所运行的机器具有多大的RAM,哪些RAM又是空闲的。实质上,对于x86 (32bit)机器来说,系统假设它独立运行在0-4G的地址空间里;即链接器将代码按照0-3G(最后1G留给内核)的地址来分配其逻辑地址,例如通常0×08048000用于.text段,这可以通过修改ld的链接脚本来设置新数值。 但这带来了一个新的问题,就是内核如何管理进程的地址空间呢? 显然内核需至少要知道如下信息:

  • 当前哪个区域的地址空间是未使用的?
  • 如何将某个文件跟进程的具体地址空间关联,即添加使用地址空间;
  • 缺页异常时,内核如何加载新的数据到RAM中?

这些都通过内核的VMA的数据结构表述某段地址空间,内核称之为线性区。线性区代表了一个范围内的连续的进程地址空间的集合,这些地址空间逻辑上具有共同的特征,例如被映射到了同一个文件等。所有的线性区通过链表连接起来,最终就构成了进程的地址空间。

每个进程拥有的线性区数量有进程本身决定的,可能很多,也可能很少。因此如何管理线性区,如何高效的增加、删除、查找线性区是内核的重要内容。当前,内核采用红黑树的结构来管理线性区。对线性区的操作,如查到、增加、删除等实质也都牵涉到红黑树的操作,这些事线性区管理最核心的部分,关于红黑树的具体内容请参考书籍《深入理解linux内核》
线性区实现的另一个重要内容是访问权限控制,例如可读、可写、可执行的等等。

缺页异常处理

在linux中,对内核申请内存和应用程序申请内存采用不同的策略:

  • 内核申请内存被认为是紧迫的,具有最高优先级,应该立即处理,即分配物理RAM;
  • 内核信任自己。即假设内核函数都是没有错误的,因此无需插入任何保护措施代码

而对用户程序而言,情况完全不同:

  • 不紧迫的,假设进程并不会立即使用这段内存,因此会推迟分配真实的物理RAM,例如加载某个exe文件,进程并不会对所有的代码页都访问
  • 用户进程是不可信任的,因此内核应能随时捕获用户态进程引起的内存错误

上述策略的核心不同就在于内存分配的时机,对内核而言是立即分配;而对用户进程而言,是推迟分配,准确说是按需分配,即真正需要时才分配。   实现按需分配的核心就是缺页异常处理。

当cpu访问的页不在RAM主存中时,就会导致缺页异常中断。在linue 内核中,对缺页中断处理的大致逻辑是:

  • 判断访问的地址是否属于当前进程的地址空间,即在当前的线性区里面是否包含了该地址
  • 如果未包含并且异常发生在用户态则产生SIGSEGV 段错误信号;如果未包含且发生在内核态,则内核错误并杀死进程;
  • 如果包含,检查访问类型以及权限等;一切都匹配后,找出匹配的线性区,并根据该线性区信息将实质数据(例如在磁盘上,映射了某个文件)copy到主存中(或者其它处理,根据具体的数据需求),并设置好相关页表。

这个过程牵涉到两个重要的技术,请求调页和写时复制。

  • 请求调页指的是一种动态内存分配技术,它把页框分配推迟到不能再推迟为止,即推迟到了缺页异常;例如访问的页目前还没放到任何页框中,内核不得不分配一个页框并适当初始化;
  • 写时复制,主要用于请求的页框存在,但是标志为只读,而当前进程需要写这个页框,这时内核分配新的页框并复制原来页框中的内容。写时复制技术主要是采用一种共享页框的概念来减少页框的使用。例如通过fork产生子进程时,没有必要把父进程的所有页框(包括页表结构占用的页框)都复制到一片全新的页框中;linux采用的策略是共享,即所有页框被共享,但为只读状态;只有在写的操作发生时才会导致copy、分配新的页框。在内核实质实现过程中,通过页描述符中的_count计数字段来跟踪共享相应页框的进程数目。

页框回收

在linux中,随着系统的运行,页框逐渐被分配,为防止内存被耗尽,系统需要“定期”运行页框回收算法。其主要原理就是将一些暂时不用的页框,例如未运行的进程占用的页框,交换到swap空间中,以回收页框;下一次使用这些页框时再从swap空间将数据拷入新的页框中。这也是为什么你的应用程序所实际需要的内存可以超过整个RAM的原因,因为你的磁盘也可以当做“RAM”来使用,存放临时数据。

Linux的页框回收算法(PFRA)不回收下列类型的页框:

  • 空闲页框
  • 保留页(PG_reserved), 主要包括含有内核代码和内核数据的页框
  • 内核动态分配页, 例如在驱动中通过kmalloc申请的内存
  • 进程内核态堆栈页
  • 临时锁定页(PG_locked )
  • 内存锁定页(VM_LOCKED)

这些页框不会被回收,也就是说这些页不会被交换出去,即他们是常驻RAM中,系统启动一旦分配后就常驻内存,这也是我们常说的系统占用内存。

能被回收的页框主要是用户态进程申请的页框和内核高速缓存。

总结

对于一个进程而言,用户程序跟内核协作共同完成某项功能。只不过内核是系统启动加载后常驻内存,用户程序通过系统调用(int 0×80)来执行内核代码;而且内核代码是被多个进程共享使用的,这也是处处要求内核代码必须是可重入的原因。

参考资料

Linux的分段分页机制,http://blog.csdn.net/p0303230/archive/2007/12/24/1965094.aspx

内存观点,http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=290225&page=&view=&sb=&o=&vc=1
《深入理解linux内核》
《linux设备驱动程序》
《深入理解计算机系统》
《深入了解Linux虚拟内存管理》

后记

这篇文章也算纪念我的大学生活吧,曾记得大三那年我整日逃课,躲在图书馆看《linux内核源代码情景分析》,看懂了多少如今也早已忘了,只记得我整整几个月基本都在图书馆度过;那时的我总以为内核是最高深的,所以要努力学习。 大四的毕业设计也是关于嵌入式设备驱动的,而且还在一家公司实习做了一个实际应用的设备驱动,前后性能提高很大,为此还被公司老总特别表扬过,在那对一个实习生而言已属相当难得了。 后来的生活却是世事难料,研究生经历了很多故事;毕业后也没有找到内核相关的工作,很多公司给出的理由是内核开发需要有经验的人,尤其是我当时孤身一人从南方一所学校来到北京,而我们学校在这里没有了竞争力,尽管在南方还算受欢迎。

如今的我对内核扔怀抱敬畏之心,但不再膜拜了。工程更多的是改变我们当下的生活,而真正的改变未来、改变世界的是研究,是认识哪些枯燥数字后面的规律。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Linux内存管理图解
Windows/Linux内核地址空间管理的异同
关于Linux 虚拟内存
linux内存管理
Linux高端内存映射
linux内核
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服