/*读《深入理解linux内核》第二章 内存寻址 读书笔记*/
以80286处理器为例,因为80286在8086基础上引入存储管理中的虚存管理机制。通过“虚地址”和“保护”两重功能对存储器管理提供了支持, 加强了对多用户/多任务运行的管理能力。以后的80x86基本都是这种方式。(8086为何要分段:16位寄存器形成20位地址空间,使用分段来寻址。)
下图为地址转换过程
1、首先是逻辑地址-->虚拟地址,分为实地址模式(8086方式兼容,让操作系统自举)与虚地址保护模式。
实地址模式:
将存储器逻辑上划分为每64K为一个段
实际访问单元地址(物理地址) = 段基地址*16(左移4位) + 段内偏移量
虚地址保护模式:
段寄存器存放段选择符, 而非段基地址。该模式主要针对在多任务机制中的存储管理。
1. 虚地址保护模式的基本概念
两个方面的含义
(1) 虚地址: 程序设计者可以寻址一个比实际物理地址空间(16M)大得多的虚存空间(1000M)。
(2) 保护:对存储空间的(数据和程序)保护。
*地址空间上的保护避免多任务机制下的越界访问
*特权级的保护,比如防止应用软件修改系统软件或数据
*访问权限的保护,如可读或可读/写、可执行或可读/可执行等
2.虚地址到实地址转换关键:段选择符(作用:刻划存储段的属性(比如一个段的保护属性), 并提供虚地址到实地址转化的信息)
基本过程如下:
i
具体过程:
1)根据逻辑地址的段选择符*8(因为段描述符大小为8字节,所以相对于段描述符表地址的偏移量要*8,跨过一个段描述符,与linux红黑树颜色表示类似),由TI选择是GDTR(0)、LDTR(1),将之前的结果与寄存器相加得到段描述符地址。(段寄存器内容被改变时才执行这一步)
2)将偏移地址与段描述符Base字段相加得到线性地址。
Linux中的分段:
实际上2.6版本的linux只有在80X86结构下才使用分段。(IA32结构必须使用段机制)
Linux的设计人员让段的基地址为0,而段的界限为4GB,这时任意给出一个偏移量,则等式为“0+偏移量=线性地址”,也就是说“偏移量=线性地址”。另外由于段机制规定“偏移量 < 4GB”,所以偏移量的范围为0H~FFFFFFFFH,这恰好是线性地址空间范围,也就是说虚拟地址直接映射到了线性地址,我们以后所提到的虚拟地址和线性地址指的也就是同一地址。看来,Linux在没有回避段机制的情况下巧妙地把段机制给绕过去了。
由于IA32段机制还规定,必须为代码段和数据段创建不同的段,所以Linux必须为代码段和数据段分别创建一个基地址为0,段界限为4GB的段描述符。不仅如此,由于Linux内核运行在特权级0,而用户程序运行在特权级别3,根据IA32的段保护机制规定,特权级3的程序是无法访问特权级为0的段的,所以Linux必须为内核和用户程序分别创建其代码段和数据段。这就意味着Linux必须创建4个段描述符——特权级0的代码段和数据段,特权级3的代码段和数据段。
Linux在启动的过程中设置了段寄存器的值和全局描述符表GDT的内容,段的定义在include/asm-i386/segment.h中,共32个段:
/*内核代码段,index=2,TI=0,RPL=0*/
#define GDT_ENTRY_KERNEL_BASE 12
#define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
/*内核数据段, index=3,TI=0,RPL=0*/
#define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE + 1)
#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)
/*用户代码段, index=4,TI=0,RPL=3*/
#define GDT_ENTRY_DEFAULT_USER_CS 14
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
/*用户数据段, index=5,TI=0,RPL=3*/
#define GDT_ENTRY_DEFAULT_USER_DS 15
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3) //用于DS寄存器
从定义看出,没有定义堆栈段,实际上,Linux内核不区分数据段和堆栈段,这也体现了Linux内核尽量减少段的使用。因为没有使用LDT,因此,TI=0,并把这4个段都放在GDT中, index就是某个段在GDT表中的下标。内核代码段和数据段具有最高特权,因此其RPL为0,而用户代码段和数据段具有最低特权,因此其RPL为3。可以看出,Linux内核再次简化了特权级的使用,使用了两个特权级而不是4个。
2、虚地址-->物理地址,分页
总体过程(摘自深入理解计算机系统)(VA:虚拟地址VPN:虚拟地址页号PTE:页表项PTEA:页表地址PA:物理地址)
具体地址转换过程:
有了以上计算机系统认识,来看linux中的分页
Linux中的分页:
Linux主要采用分页机制来实现虚拟存储器管理。这是因为:
· Linux的分段机制使得所有的进程都使用相同的段寄存器值,这就使得内存管理变得简单,也就是说,所有的进程都使用同样的线性地址空间(0~4G)。
· Linux设计目标之一就是能够把自己移植到绝大多数流行的处理器平台。但是,许多RISC处理器支持的段功能非常有限。为了保持可移植性,Linux采用三级分页模式而不是两级,这是因为许多处理器都采用64位结构的处理器,在这种情况下,两级分页就不适合了,必须采用三级分页。
为此,Linux定义了三种类型的页表:
· 总目录PGD(Page Global Directory)
· 中间目录PMD(Page Middle Derectory)
· 页表PT(Page Table)
Linux所定义的一些主要数据结构,其分布在include/arch_name/目录下的page.h,pgtable.h及pgtable-2level.h三个文件中。
Linux内存初始化启动分页机制参考:Linux内存初始化之一启用分页机制
更多参考: