现在的位置: 首页 > 综合 > 正文

Linux 内存映射与管理

2017年11月11日 ⁄ 综合 ⁄ 共 6020字 ⁄ 字号 评论关闭

本文以Linux 2.6版本内核为例,介绍了内核线性地址空间的布局,并描述了80386架构处理器下的3种内存地址的概念及在分段、分页机制下的相互转换。
  
  通过内存地址访问,我们可以得到存在内存单元里的内容,这很容易理解。但在不同的环境下,会涉及到几种不同的内存地址的概念,初学者很容易混淆。为了方便后面的学习,我们以80X86架构处理器为例,把涉及的3种内存地址的概念分别做一解释。
  逻辑地址(Logical Address):汇编程序中,我们经常会看到段基址加偏移量来表示内存地址的方式,如0100:20,1000:40,其实这就是逻辑地址。目前的MSDOS或Windows程序,利用了80X86架构处理器的分段机制,程序地址空间被分隔成若干个不同的段,如代码段,数据段等。自然地,内存地址就用段地址+段偏移来表示。和Windows不同,Linux只是有限地使用分段机制。Linux启动代码在实模式阶段,会显式地用逻辑地址;当进入保护模式后,内核和进程的代码数据段都被设置成相同的段基址0x00000000,这时,段偏移量就等于线性地址,因而用线性地址表示内存地址更加方便。
  线性地址(Linear Address):线性地址也被称作虚拟地址,在32位CPU架构下,可以表示4G的地址空间,用16进制表示就是0x00000000到0xffffffff。用线性地址寻址内存比逻辑地址更加直观,因此在Linux 中,无论内核地址空间还是进程地址空间,都是用线性地址来表示的。当然,访问内存最终还要利用物理地址的,所以线性地址在寻址时需要转换成物理地址。这种转换是由硬件自动完成的,我们会在后面的分页技术部份详细讨论。
  物理地址 (Physical Address):物理地址顾名思义就是用来访问物理内存的地址表示方式。无论逻辑地址,还是线性地址,最终都要转换成物理地址来完成访存的过程。从电气层来讲,物理地址实际上是CPU地址引脚发往内存总线的电信号。
  在80386处理器中,典型的逻辑地址,线性地址和物理地址的转换过程是这样的。内存管理单元(MMU)通过硬件分段机制把逻辑地址转换成线性地址,然后再通过硬件分页机制把线性地址转换为物理地址。这里,读者可能会感到有些困惑,一次访存操作要涉及到两次地址转换,效率会不会很低。其实,这种转换都是硬件自动完成的,无需软件参与,再加上TLB Cache等技术,实际访存效率还是比较高的。当然,转换的规则是需要软件提前初始化好的。
  
  内存寻址
  1.分段机制
  分段机制是用分段的方法把程序的代码,数据以及堆栈分隔开来,从而保证在同一线性地址空间内,多个程序之间互不干扰的执行。
  Intel从80286开始引入保护模式,因此逻辑地址到物理地址的转换方式和之前的实模式有了很大不同。实模式下的分段机制很简单,地址转换公式是:物理地址=段地址*16+段偏移,这里不赘述了。在保护模式下,逻辑地址是由段选择子和段偏移量两部分构成。其中,段选择子是16位长度,段偏移是32位长度。
  
  图1 段选择子格式
  
  段选择子标识一个段,通常被加载到段寄存器CS,DS,ES,SS,FS和GS来表示,如图1所示。INDEX表示所标识的段描述符在段描述符表(GDT or LDT)中的索引。TI表示所标识的段描述符是在全局还是本地段描述符表中。TI=0表示在全局描述符表(GDT)中,TI=1表示在本地描述符表(LDT)中。RPL表示请求特权级别,分0-3级,0级别最高。在Linux内核中,0代表内核态,3代表用户态。
  80386的段描述符表分全局段描述符表(GDT)和局部描述符表(LDT)两类。其中,全局段描述符表最重要,它其实是一个存放在线性地址空间的结构数组,每个结构单元代表一个段描述符。逻辑地址通过其段选择子中的索引值,就可以定位到对应的段描述符上。
  段描述符顾名思义,描述了线性地址空间的一个段。因为其格式比较复杂,这里就不一一罗列了,其中最重要的字段是段基地址(BASE)和大小(LIMIT),它们会参与到逻辑地址的转换过程。
  事实上,分段机制和后面要讲到的分页机制都可以起到隔离不同进程的地址空间,因此功能上有些重复,再加上考虑和RISC架构的兼容性,因此,Linux只是很有限地使用了段机制。
  在Linux中,4个最主要的段描述符-用户态代码和数据段,内核态代码和数据段都被设置成BASE=0x0000000, LIMIT=0xffffffff,因此,所有的用户进程包括内核都使用同样的线性地址空间,而且逻辑地址中的段偏移就等于线性地址。这样,除了用户态和内核态之间的切换外,在其它绝大多数情况下,段寄存器都不需要改变。所以说,段机制在Linux中的使用被弱化了。
  
  图2 逻辑地址到线性地址转换图
  
  2.分页机制
  分页机制是把线性地址空间转换到物理地址空间,从而达到多任务的隔离目的。从这个意思上讲,分页机制和分段机制有相似的地方,但分页机制在功能上要强大很多。比如,分页机制下所有的进程拥有相同的地址空间,管理更方便。
  分页机制的硬件把物理内存空间看作有一系列的大小相同的页帧组成,通常情况下,页的大小是4K。当然,在较新的80X86架构CPU中,也可以通过设置使页大小达到4M或者2M。以4K页表为例,一个32位的线性地址可以被分成页目录项索引(10位),页表项索引(10位)以及偏移量(12位)。

分页机制下,线性地址的转换需要用到转换表。转换表在80X86中有两级,第一级叫做页目录表,第二级叫页表。这两种表其实都是存放在物理空间中的结构数组,每个结构单元占32位。我们把这种结构单元称作页目录项或者页表项,其格式主要包括两部份,分别为指向页表或页帧的物理地址(对页目录表来说,指向页表的物理地址;对页表来说,指向页帧的物理地址)和控制位(含保留位)。
  线性地址到物理地址的转换过程是这样的。首先根据线性地址中页目录索引在页目录表中找到对应的页目录项,该项中包含有指向下级页表的物理地址;再根据页表索引在页表中找到对应项,取出页帧的物理地址。页帧物理地址加上线性地址中的偏移量就得到最终转换后的物理地址。
  线性地址到物理地址的转换是硬件自动完成的,但考虑到效率的因素,CPU还使用TLB来加速转换速度。
  为了达到更广泛的兼容性, Linux内核采用4级页表模型来使用硬件分页机制 (2.6.11版本之前使用3级页表),分别是Page Global Directory(pgd_t), Page Upper Directory(pud_t), Page Middle Directory(pmd_t) 和Page Table(pte_t)。当硬件分页机制实际是两级页表时,Linux内核在代码上虽然还是4级页表(兼容更多级别页表的CPU架构),但巧妙地把Page Upper Directory 和Page Middle Direcotry跳过了。
  
  Linux线性地址空间布局
  在Linux中,每一个进程可以访问4GB的线性地址空间。其中,0到3GB的线性地址是用户空间,用户态进程可以直接访问。3G之上的空间为内核空间,只有当CPU处于内核态才能访问,用户态进程不能直接访问。当用户态的进程需要申请内核服务时,可以通过中断或者系统调用来触发CPU特权级的转换(3到0),从而访问到内核空间中的代码或数据。
  
  所有进程从3G到4G的线性空间都是相同的,有着同样的页目录项和页表,从而对应同样的物理内存空间。因此,可以说3G到4G的线性空间被所有进程共享,进程之间的差异体现在0到3G空间所对应的页目录项和页表。
  Linux维护了一份主内核空间页目录表及页表(Master Kernel Page Global Directory),在内核代码中用swapper_pg_dir来表示。Swapper_pg_dir 在内核初始化时被设置,虽然它不会被任何进程直接使用,但所有进程在3G以上线性空间对应的页目录项都会引用它。
  
  图3 页目录项及页表项格式图
  
  接下来,我们重点关注一下Linux内核线性地址空间(3G-4G)是如何使用分布的,参见下图
  1.直接内存映射区(Direct Memory Region)
  Linux直接映射从物理地址0开始,最大896M大小的物理空间到3G开始的线性地址区间,该区间我们称作直接内存映射区,其线性地址和物理地址的转换公式为 线性地址=3G + 物理地址。例如,物理地址区间0x100000-0x200000映射到线性空间就是3G+0x100000-3G+0x200000。直接内存映射区是在Linux初始化时静态映射好的。
  2.动态内存映射区(VMalloc Region)
  低端内存映射区上面有8M大小的隔离带,再往上就是动态内存映射区,该区间主要为内核函数Vmalloc服务的,其特点是线性空间连续,但对应的物理空间不连续。在不连续内存管理一章中会详细讨论vmalloc的工作原理。
  3.PKMap区(PKMap Region)
  动态内存分配区上面有8K的隔离带,再往上就是PKMap区,大约有4M空间。我们都知道,3G到4G的线性空间只有1G,但物理内存可能远远不止1G,Linux不可能把所有的物理页面都直接映射到该线性空间。事实上,正如直接映射区所定义的,Linux最大只能直接映射896M物理空间。那如何访问896M以上的物理空间呢?这就是PKMap区产生的由来。该区间是作为一个动态窗口,用来映射896M以上的高端物理页面,通过该窗口,Linux就可以访问到高端内存了。
  4.固定映射区(Fixing Mapping Region)
  PKMap区上面,有大约4M的线性空间,被称作固定映射区,它和4G顶端只有4K的隔离带。固定映射区顾名思义,是用来映射物理页到该区中固定的线性地址项,固定映射区中每个地址项都服务于特定的用途,如ACPI_BASE等。因此,内核不需要使用变量指针,直接用地址项就可以访问被映射的物理页,因而效率更高。
  
  图4 线性地址到物理地址转换图
  
  内存管理
  1.页帧管理——伙伴(Buddy)系统
  Linux管理物理内存是以物理页为单位进行的。对应于每个物理页,Linux在内存中都有一个page类型的对象实例来表示它,称作页描述符。所有的页描述符在Linux初始化阶段被静态设置好,放在以mem_map为头的数组内。
  在一个节点上的物理内存可以分成几个区(Zone)来管理,它们分别是:
  ◆ZONE_DMA区,包含物理地址在16M以下的页帧;
  ◆ZONE_Normal区,包含物理地址在16M到896M之间的页帧;
  ◆ZONE_HIGHMEM区,包含物理地址在896M以上的页帧。

  在每个区内,Linux使用伙伴系统(Buddy)来组织空闲页的管理。伙伴算法的思想是,把空闲页组按连续数目20 , 21, 22,…不同放在不同的链表中,链表上每个节点代表一个空闲页组。比如,2个页连续的空闲页组会作为一个节点放到21链表中,同样的,4个页连续的空闲页组会作为一个节点放到22链表中。当内核需要申请一组连续的空闲页面时,它会首先在大小最接近的伙伴链表中查找并返回目标节点;如果该链表为空,再搜索大小更高一级的伙伴链表,依此往复,直至找到。找到的节点包含的连续页面可能会比请求的大,因此要先拆分,然后返回请求大小的页面组,同时把剩下的连续空闲页面插到相应大小的链表中。比如,内核请求2个连续空闲页面,因为21链表为空,最后在22链表中找到一个节点。伙伴系统把该节点的4个页面拆分成两个连续页面为2的页组,一个返回给内核请求,另一个插到21链表中去。同样道理,当释放页面时,伙伴系统会自动合并相邻的小页面组变成较大的页面组,然后再插入相应大小的链表中。
  2.内存对象管理——Slab系统
  伙伴系统适合分配以页为单位的较大的内存空间,但对任意大小的数据对象的内存分配,尤其较小的对象,如16个字节,就无能为力了。而数据对象的内存分配在Linux内核中需要被大量地使用,如进程描述符,文件描述符等,所以Linux引入Slab系统机制来解决对象的分配和释放问题。
  Slab系统由若干Cache组成,每个Cache管理一个特定类的对象。同时,每个Cache由若干个slab块组成,每个slab块实际上是由若干个页面组成的页组,被管理的对象就被放在页组内。
  Cache有两类,称为通用cache和专用cache。
  通用Cache也分两种。一个是给Cache自己的数据结构对象用的,称作cache_cache。另一种叫kmalloc Cache,包含一系列Cache,每个Cache对应对象的大小是2的幂,如2,4,8,16…;当Linux通过kmalloc请求内存时,直接从大小最接近的那个kmalloc Cache中分配。
  通用Cache都是在系统初始化阶段静态生成好的。
  
  图5 Linux内核线性空间布局图
  
  要使用专用Cache,首先需要显示地为目标类对象创建一个Cache。事实上,可以把这个Cache看作一个特定类的对象池,以后对象的申请和释放都在池中发生。刚开始时,Cache是空的,没有任何对象,也就没有任何slab块。当Linux第一次向该Cache申请目标类对象时,Slab系统会自动为Cache生成一个包含若干页面的slab块,请求的对象就从该slab块中分配。如果该对象被申请的很多导致slab块耗尽,Slab系统会自动为Cache增加新的slab块。相反,如果对象大量释放致使Cache空闲率提高,Slab系统会在适当的时机,通过释放slab块来为Cache瘦身。
  3.不连续内存管理——vmalloc
  在前面的线性地址空间布局中,我们提到过动态内存映射区,这段区域就是vmalloc区。在前面的伙伴系统以及Slab系统中,其所管理的物理内存空间都是连续的。但在vmalloc区,线性地址空间所对应的物理空间地址是不连续的。
  通过vmalloc产生的物理地址和线性地址的映射是动态生成的,按需产生页目录项和页表。例如,如果使用vmalloc分配1M大小的线性空间,其过程是内核将从动态内存分配区找出1M空闲的连续线性地址空间,然后再从页帧管理系统中逐页找出1M物理地址空间(页之间不一定相邻),最后通过产生对应的页目录项及页表把它们映射起来。所以,通过Vmalloc得到的线性空间,其对应的物理空间地址不一定连续,这是和直接映射区内的线性空间最大的不同

 

 

 

 原文地址 http://qkzz.net/magazine/1005-2348/2007/12/851403.htm

抱歉!评论已关闭.