您的位置:首页 > 运维架构 > Linux

深入理解Linux内存管理机制(一)

2013-04-11 14:03 661 查看
一、内存组织

计算机内存属于随机存储器(RAM),目前PC机广泛使用的是DDR

SDRAM,即“双倍速率同步动态随机存储器”,其本质上仍然是由n bits*m KB个内存芯片组成的,比如如果我们需要8位64KB的内存,则我们就需要2*8=16块4bits*8KB的内存块。由于计算机通常是以字节(Byte)进行数据交换的,所以对内存的地址编码一般使用字节,如上我们有64KB内存,则其地址编码为0×0000~0xFFFF,称为物理地址。对于32位机来说,由于其“地址寄存器(AR)”是32位,也就限制了其内存的最大寻址范围是2^32=4GB。

Linux将物理地址按4KB的大小划分成“帧(Frame)”。为什么是4KB?因为每一个帧都需要用一个C结构体来描述,称之为“帧描述单元(Frame Discriptor)”,如果太小,帧描述单元显然太多了,如果太大,那么在内存分配时又会造成“内碎片(InnerFragments)”。早些时候,计算机的内存址都是直接映射的,由于程序里的地址是写死的,这就意味着每段程序每次都只能映射对应的地址空间。这无论对程序设计者与系统都是相当大的负担。Linux使用“分段”加“分页”来解决此问题。由于它们的存在,内存地址进入了逻辑地址时代。Linux有三种地址:逻辑地址(LogicAddress)、线性地址(Linear
Address)与物理地址(Physics Address)。其关系如下:



另外,Linux支持众多CPU架构,这里只研究X86的,对应的源代码为:…/X86/… 路径。

Linux中的分段

Linux并不使用太多的分段,原因是某些RISC机器对分段的支持不好。为此Linux的分段都存在“全局描述表(GDT)”中,GDT是一个全局desc_struct数组(位于linux-2.6.32.59\arch\x86\include\asm),其结构如下:

#define GDT_ENTRIES 16

struct desc_struct gdt[GDT_ENTRIES];

struct desc_struct {

union {

struct {

unsigned int a;

unsigned int b;

};

struct {

u16 limit0; // 段大小

u16 base0; // 段起始位置

unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1; // type表示段类型,占4位;dpl指的段运行权限,占2位

unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8; //d 表示内存地址位宽,占1位

};

};

} __attribute__((packed));

所以我们可以看出,段描述结构体占8个字节,至于里面的a,b,那是老的方式,后来使用C++ Struts的Bit
Fields后更方便了。type类型由以下几种:

enum {

DESC_TSS = 0×9,

DESC_LDT = 0×2,

DESCTYPE_S = 0×10, /* !system */

};

Linux主要使用以下几种段:

内核代码段(Kernel Code Segment):type=10,dpl=0
内核数据段(Kernel Data Segment):type=2,dpl=0
用户代码段(User Code Segment):type=10,dpl=3
用户数据段(User Data Segment):type=2,dpl=3
任务状态段(Task State Segment),每进程一个:type=9,dpl=3

其它类型可以参见linux-2.6.32.59\arch\x86\include\asm\segment.h,里面有非常详细的说明。

它们都存储在“全局描述符表(GDT)”。Linux本身并不使用“局部描述符表(LDT)”,当一个进程被创建时,其指向的是一个默认的LDT,不过系统并不阻止进程创建它。也就是说一个进程最多两个段描述符:TSS与LDT。由于Segment Selector为16位(为什么只有16位,这个就是历史原因了,由于X86在Real Mode下段地址只有20位,其中有效的就是16位,详见:x86

memory segmentation,但Linux段内偏移地址高达32位,所以线性地址总共是48位),其中有效的索引位仅有13位,所以GDT的最大长度为213-1=8192,除去系统保留的12个,留给进程的只有8180个入口,那么就意味Linux进程的最大数为8180/2=4090。需要注意的是,进程在创建的时候并不会马上创建自己的LDT,其指向的是GDT一个默认的LDT,里面的SD为null。只有在需要的时候进程才创建自己的LDT并把它放入GDT中。所以不管是LDT也好,TSS也好,它们都存放在GDT里面。而对于UCS与UDS,所有的进程共享一个。这样地址空间不会重复吗?不会,因为线性不是最终的物理地址,每个进程还有自己的页表,所以最终映射到物理地址是不同的。

下面我们来看看段中地址是如何转换的。假设我们需要访问内核数据段的0×00124部分,由代码知其GDT的入口为13,那么其对应的内存地址=gdtr+13*8+0×00124,假设gptr为0×02000,则最终的结果为0×02228。gdtr是一个寄存器,其为48位,用来保存GDT的第一个字节线性地址与表限。其过程如图所示:



分页

相对于分段来说,分页更主流更流行一些。原因是其更灵活,其能把不同的线性地址映射到同一个物理地址上,缺点是内存必须以页大小的整数倍分配。按现在主流的4KB一页来说,如果程序只申请100B的数据,那内存浪费还是相当的大。为此,Linux使用了一种称为Slab的方法来解决这个问题,后面的文章会讲到。

因为页表本身也需要存储空间,按每页32B来算,对于4GB内存,每页4KB,共有1M页,则页表的大小为32MB,这显然不可以接受,所以后来出现了多级页表这个概念。2004年后Linux版本使用的是四级页表:第一级叫“全局目录(Page

Global Directory)“、第二级叫“页上级目录(Page

Upper Directory)”、第三级叫”页中间目录(Page

Middle Derectory)”、第四级叫”页面表(Page

Table Entry)”,最后页内偏移量“offset”,如下图:

进程通过数据结构mm_struct访问PGD,最终找到物理页面:





逻辑上把虚拟地址从高到低分成5部分,分别作为PGD、PUD、PMD、PTE的索引,以及页面内偏移。对于CPU发出的虚拟地址,linux内存管理单元分如下五步完成从虚拟地址到物理地址的映射:

1) 用虚拟地址的最高一个位段作为索引在PGD表中找到对应的表项,该表项指向一个PUD表的基地址。

2) 用虚拟地址中的第二个位段作为索引在PUD表中找到对应的表项,该表项指向一个PMD表的基地址。

3) 用虚拟地址中的第三个位段作为索引在PMD表中找到对应的表项,该表项指向一个PTE表的基地址。

4) 用虚拟地址中的第四个位段作为索引在PTE表中找到对应的表项,该表项中存放的就是指向物理页面的指针(实际上是页帧号PFN和一些标志位的组合体)。

5) 虚拟地址中的最后一个位段是物理页面的页内偏移,将此偏移与物理页面的起始地址相加便得到虚拟地址对应的物理地址。





后面分析内核代码时,会用到下面列出的宏:

n PGDIR_SHIFT:虚拟地址中PGD索引位段的起始地址。

n PTRS_PER_PGD:PGD表中表项的数目。

n PGDIR_SIZE:PGD中每个表项所代表的空间大小。

n USER_PTRS_PER_PGD:需要使用多少个PGD表项才能代表用户空间。

n PUD_SHIFT:虚拟地址中PUD索引位段的起始地址。

n PTRS_PER_PUD:PUD表中表项的数目。

n PUD_SIZE:每个PUD表项所代表的空间大小。对于二级映射的架构而言,PUD_SIZE即PGDIR_SIZE。

n PMD_SHIFT:虚拟地址中PMD索引位段的起始地址。

n PTRS_PER_PMD:PMD表中表项的数目。

n PMD_SIZE:每个PMD表项所代表的空间大小。对于二级映射的架构而言,PMD_SIZE即PGDIR_SIZE。

n PAGE_SHIFT:虚拟地址中PTE索引位段的起始地址。决定了页面的大小。

n PAGE_SIZE:页面大小,1 << PAGE_SHIFT。通常为4KB。

n PTRS_PER_PTE:PTE表中表项的数目。

例如线性地址为:0x91220B01,如下图,如果PGD、PUD、PMD以及PTE均5位。页内偏移12位,即页大小4KB。



那么这段内存的解析步骤是:

PGD号为24,查PGD[24]得到PUD入口;
PUD号为4,再查PUD[4];
PMD号为36,再查PMD[36];
PTE号为2,再查PTE[2];
如果最终帧地址为a:那么最后的物理地址就是a+0×0301

需要补充的是,并不是所有的内存都是使用“分页”,在内核初始化的时候,有100MB内存的样子是使用直接映射的,这是因为总是要先装入分页的初始化代码才能进行页表初始化。

——————一些资源与参考——————-

Linux SLUB 分配器详解:http://www.ibm.com/developerworks/cn/linux/l-cn-slub/

Page Frame Management:http://www.makelinux.net/books/ulk3/understandlk-CHP-8-SECT-1

Linux memory management:http://www.cse.psu.edu/~anand/spring01/linux/memory.ppt

linux内存管理浅析:http://hi.baidu.com/_kouu/blog/item/f72e707ffa8478310cd7da28.html

Linux内存之页表:http://biancheng.dnbcw.info/linux/335152.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: