内存映射与访问机制

经过参考内存布局及访问机制的相关文章,本文试着整合一下相关知识点,但愿能对有须要的朋友提供一点参考。但因为所参考文章皆为网友所做,相关知识并无造成系统的认识,因此有些知识点仍然不够清楚,也不免有谬误之处。若是你们发现错误,敬请指出,另外假若有关于“系统启动及内存布局”方面的可靠的资料或书籍,恳请大神留言告知。html

参考文章(只列出几个重要的):http://www.cnblogs.com/clover-toeic/p/3754433.htmllinux

                   http://blog.chinaunix.net/uid-26126915-id-2981205.html程序员

                   http://blog.csdn.net/yeruby/article/details/39718119编程

                   http://www.cnblogs.com/qintangtao/p/3325985.html数组

                   http://www.cnblogs.com/wangccc/p/5342300.htmlruby

 

1、内存空间布局数据结构

1.1 虚拟地址dom

  随着图形界面的兴起和用户需求的增大,内存空间变得容纳不下程序了。所以人们将程序分割成许多片断,并由操做系统将这些片断调入内存运行,因而便产生一个虚拟地址的概念,其思想是:操做系统把程序当前使用的部分保留在内存中,而把其余未被使用的部分保存在磁盘上,并在须要切换时将外设中的数据调入内存空间。而程序运行在虚拟地址空间中,并经过相关机制将虚拟地址映射到物理内存。所以内存空间就分为物理存储空间和虚拟存储空间。函数

  引入虚拟地址的好处在于:布局

  一、程序可使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。

  二、程序可使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(一般大小为 4 KB)保存到磁盘文件。数据或代码页会根据须要在物理内存与磁盘之间移动。

  三、不一样进程使用的物理地址由系统进行映射管理,从而避免直接操做内存,防止恶意程序或bug破坏其它内存空间。

1.2 物理空间布局

         物理存储空间布局与处理器相关,详细状况能够从处理器用户手册的存储空间分布表(memory map)相关章节中查到。

1.3 虚拟空间布局

         在多任务操做系统中,每一个进程都运行在虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的内存地址块。在Linux系统中, 内核进程和用户进程所占的虚拟内存比例是1:3,而Windows系统为2:2(经过设置Large-Address-Aware Executables标志也可为1:3)。

         虚拟地址经过页表(Page Table)映射到物理内存,页表由操做系统维护并被处理器引用。内核空间在页表中拥有较高特权级,所以用户态程序试图访问这些页时会致使一个页错误(page fault)。在Linux中,内核空间是持续存在的,而且在全部进程中都映射到一样的物理内存。内核代码和数据老是可寻址,随时准备处理中断和系统调用。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化。

 Linux进程在虚拟内存中的标准内存段布局以下图所示:

 

  其中,用户地址空间中的蓝色条带对应于映射到物理内存的不一样内存段,灰白区域表示未映射的部分。这些段只是简单的内存地址范围,与Intel处理器的段没有关系。

  上图中Random stack offset和Random mmap offset等随机值意在防止恶意程序。Linux经过对栈、内存映射段、堆的起始地址加上随机偏移量来打乱布局,以避免恶意程序经过计算访问栈、库函数等 地址。execve(2)负责为进程代码段和数据段创建映射,真正将代码段和数据段的内容读入内存是由系统的缺页异常处理程序按需完成的。另外,execve(2)还会将BSS段清零。

 

2、内存访问机制

         在80X86CPU的发展过程当中,存储器的管理机制发生了较大的变化。8086/8088CPU对存储器的管理采用分段的实方式;80286CPU除了可在实方式下工做外,还能够在保护模式下工做;而80386CPU以后的处理器则具备三种工做方式:实方式、保护方式和虚拟8086方式。

2.1 分段机制

         段的引入:8086为了用16位寄存器实现1MB(20位)的寻址内存空间,引入了段的概念。在没有采用分页管理时,逻辑地址计算而得的线性地址是直接映射物理地址(Physical Address)的,因而能够直接用线性地址访问内存;不然,还要经过X86的分页转换,将线性地址转换为物理地址。

         2.1.1 实模式

         在Real Mode下,咱们对一个内存地址的访问是经过Segment:Offset的方式来进行的,其中Segment是一个段的Base Address,一个Segment的最大长度是64 KB,这是16-bit系统所能表示的最大长度。而Offset则是相对于此Segment Base Address的偏移量。Base Address+Offset就是一个内存绝对地址。在实际编程的时候,使用16-bit段寄存器CS(Code Segment),DS(Data Segment),SS(Stack Segment)来指定Segment,CPU将段寄存器中的数值向左偏移4-bit,放到20-bit的地址线上就成为20-bit的Base Address。

         2.1.2 保护模式

         到了Protected Mode,内存的管理模式分为两种,段模式和页模式。

         因为 Protected Mode运行在32-bit系统上,那么Segment的两个因素:Base Address和Limit也都是32位的。IA-32容许将一个段的Base Address设为32-bit所能表示的任何值(Limit则能够被设为32-bit所能表示的,以2^12为倍数的任何指),而不像Real Mode下,一个段的Base Address只能是16的倍数(由于其低4-bit是经过左移运算得来的,只能为0,从而达到使用16-bit段寄存器表示20-bit Base Address的目的),而一个段的Limit只能为固定值64 KB。另外,Protected Mode,顾名思义,又为段模式提供了保护机制,也就说一个段的描述符须要规定对自身的访问权限(Access)。因此,在Protected Mode下,对一个段的描述则包括3方面因素:【Base Address, Limit, Access】,它们加在一块儿被放在一个64-bit长的数据结构中,被称为段描述符。

  (1)GDT

  若是咱们直接经过一个64-bit段描述符来引用一个段的时候,就必须使用一个64-bit长的段寄存器装入这个段描述符。但Intel为了保持向后兼容,将段寄存器仍然规定为16-bit(尽管每一个段寄存器事实上有一个64-bit长的不可见部分,但对于程序员来讲,段寄存器就是16-bit的),那么很明显,咱们没法经过16-bit长度的段寄存器来直接引用64-bit的段描述符。解决的方法就是把这些长度为64-bit的段描述符放入一个数组中,而将段寄存器中的值做为下标索引来间接引用(事实上,是将段寄存器中的高13 -bit的内容做为索引)。这个全局的数组就是GDT。事实上,在GDT中存放的不只仅是段描述符,还有其它描述符,它们都是64-bit长。GDT是Protected Mode所必须的数据结构,也是惟一的。

  全局描述符表GDT含有每个任务均可能或能够访问的段的描述符,一般包含操做系统所使用的代码段,数据段和堆栈段的描述符,也包含多种特殊数据段的描述符,如各个LDT的描述符等.每一个GDT最多含有8192个描述符.注意,GDT的第0个描述符总不被处理,一般它置成全0.

GDT结构图以下:

 

说明以下:

G:

(1)、G=0时,段限长的20位为实际段限长,最大限长为2^20=1MB

(2)、G=1时,则实际段限长为20位段限长乘以2^12=4KB,最大限长达到4GB

D/B:

当描述符指向的是可执行代码段时,这一位叫作D位,D=1使用32位地址和32/8位操做数,D=0使用16位地址和16/8位操做数。若是指向的是向下扩展的数据段,这一位叫作B位,B=1时段的上界为4GB,B=0时段的上界为64KB。若是指向的是堆栈段,这一位叫作B位,B=1使用32位操做数,堆栈指针用ESP,B=0时使用16位操做数,堆栈指针用SP。

DPL:特权级,0为最高特权级,3为最低,表示访问该段时CPU所需处于的最低特权级

type : 类型

(1)、type<8时:数据段

 

(2)、type>=8时:代码段

 

         GDT能够被放在内存的任何位置,那么当程序员经过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,因此Intel的设计者门提供了一个寄存器GDTR(GDTR是一个48位的全局描述符寄存器,高32位存放GDT的基址,低16位存放GDT限长。)用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置以后,能够经过LGDT指令将GDT的入口地址装入此寄存器,今后之后,CPU就根据此寄存器中的内容做为GDT的入口来访问GDT了。

         (2)LDT

         除了GDT以外,IA-32还容许程序员构建与GDT相似的数据结构,它们被称做LDT(Local Descriptor Table,局部描述符表),但与GDT不一样的是,LDT在系统中能够存在多个,而且从LDT的名字能够得知,LDT不是全局可见的,它们只对引用它们的任务可见,保护模式支持多任务,每一个任务都有本身的局部描述符表LDT,且每一个任务最多只有一个LDT,每一个任务的LDT含有该任务本身的代码段,数据段和堆栈段的描述符。另外,每个LDT自身做为一个段存在,它们的段描述符被放在GDT中。每一个LDT最多含有8192个描述符。

         IA-32为LDT的入口地址也提供了一个寄存器LDTR(LDTR是一个16位的局部描述符寄存器,高13位存放LDT在GDT中的索引值。),由于在任什么时候刻只能有一个任务在运行,因此LDT寄存器全局也只须要有一个。若是一个任务拥有自身的LDT,那么当它须要引用自身的LDT时,它须要经过lldt指令将其LDT的段描述符装入此寄存器。lldt指令与lgdt指令不一样的时,lgdt指令的操做数是一个32-bit的内存地址,这个内存地址处存放的是一个32-bit GDT的入口地址,以及16-bit的GDT Limit。而lldt指令的操做数是一个16-bit的选择子,这个选择子主要内容是:被装入的LDT的段描述符在GDT中的索引值。

         因此咱们能够这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。如图:

 

         其中,selector选择器(一个16位的数据结构)被装入段寄存器,它的高13位做为被引用的段描述符在GDT/LDT中的下标索引,bit 2用来指定被引用段描述符被放在GDT中仍是到LDT中,bit 0和bit 1是RPL——请求特权等级,被用来作保护目的。如图所示:

 

         (3)IDT

         中断描述符表(Interrupt Descriptor Table,IDT)将每一个异常或中断向量分别与它们的处理过程联系起来。与GDT和LDT表相似,IDT也是由8字节长描述符组成的一个数组。

         IDT表能够驻留在线性地址空间的任何地方,处理器使用IDTR寄存器来定位IDT表的位置。这个寄存器中含有IDT表32位的基地址和16位的长度(限长)值。

         在实地址模式中,CPU把内存中从0开始的1K字节做为一个中断向量表。表中的每一个表项占四个字节,由两个字节的段地址和两个字节的偏移量组成,这样构成的地址即是相应中断处理程序的入口地址。可是,在保护模式下,由四字节的表项构成的中断向量表显然知足不了要求。这是由于,除了两个字节的段描述符,偏移量必用四字节来表示;要有反映模式切换的信息。在保护模式下,中断向量表中的表项由8个字节组成,中断向量表也改叫作中断描述符表IDT(InterruptDescriptor Table)。其中的每一个表项叫作一个门描述符(gate descriptor),“门”的含义是当中断发生时必须先经过这些门,而后才能进入相应的处理程序。

主要门描述符是:

· 中断门(Interrupt gate)

其类型码为110,中断门包含了一个中断或异常处理程序所在段的选择符和段内偏移量。当控制权经过中断门进入中断处理程序时,处理器清IF标志,即关中断,以免嵌套中断的发生。中断门中的DPL(Descriptor Privilege Level)为0,所以,用户态的进程不能访问Intel的中断门。全部的中断处理程序都由中断门激活,并所有限制在内核态。

· 陷阱门(Trap gate)

其类型码为111,与中断门相似,其惟一的区别是,控制权经过陷阱门进入处理程序时维持IF标志位不变,也就是说,不关中断。

· 系统门(System gate)

这是Linux内核特别设置的,用来让用户态的进程访问Intel的陷阱门,所以,门描述符的DPL为3。经过系统门来激活4个Linux异常处理程序,它们的向量是三、四、5及128,也就是说,在用户态下,可使用int三、into、bound 及int0x80四条汇编指令。

最后,在保护模式下,中断描述符表在内存的位置再也不限于从地址0开始的地方,而是能够放在内存的任何地方。为此,CPU中增设了一个中断描述符表寄存器IDTR,用来存放中断描述符表在内存的起始地址。中断描述符表寄存器IDTR是一个48位的寄存器,其低16位保存中断描述符表的大小,高32位保存IDT的基址.

2.2 分页机制

         2.2.1 MMU

         在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具备相同地址的物理存储器被读写。而在使用了虚拟存储器的状况下,虚拟地址不是被直接送到内存地址总线上,而是送到内存管理单元——MMU。他由一个或一组芯片组成,通常存在于协处理器中,其功能是把虚拟地址映射为物理地址,这是MMU的基本做用之一,除了硬件的支持外,软件上实际就是维护一张表,表中的内容是VA到PA的转换法则;另外一做用是能够实现不一样的访问权限。

         2.2.2 地址映射

         分页的最大做用就在于:使得进程的物理地址空间能够是非连续的。在分段的方法中,每次程序运行时老是把程序所有装入内存,而分页的方法按照程序运行的局部性原理只将部分程序装入内存。

         当建立一个进程时,操做系统会为该进程分配一个4GB(32位系统中)大小的虚拟进程地址空间。建立4GB虚拟地址空间其实并非要真的建立空间,只是要建立那种映射机制所须要的数据结构而已,这种数据结构就是页目和页表。

         一个页表的大小为4K字节,放在一个物理页中。由1024个4字节的页表项组成。页表项的大小为4个字节(32bit),因此一个页表中有1024 个页表项。页表中的每一项的内容(每项4个字节,32bit)高20bit用来放一个物理页的物理地址,低12bit放着一些标志。

页目录,一个页目录大小为4K字节,放在一个物理页中。由1024个4字节的页目录项组成。页目录项的大小为4个字节(32bit),因此一个页目录中有 1024个页目录项。页目录中的每一项的内容(每项4个字节)高20bit用来放一个页表(页表放在一个物理页中)的物理地址,低12bit放着一些标志。

对于x86系统,页目录的物理地址放在CPU的CR3寄存器中。

页表中包含物理页面基地址和页的属性。对于页表有两级页表和三级页表之分。linux为了保证可移植行,采用了三级分页机制,固然其在某些状况下能够返回到二级分页。

 

3、系统启动过程的内存变化

         参考http://blog.csdn.net/huangzhipeng/article/details/6159169

4、小结

         一、在实模式逻辑地址是直接映射到物理地址的,因此能够直接使用逻辑地址访问物理存储单元;在分段模式下,逻辑地址对应线性地址(虚拟地址),须要转换为对应的物理地址;在分页模式下,内存被划分为一系列较段小的页,从而减小了内存交换的开销,提升了内存使用效率,其使用的虚拟地址与物理地址并不是一一对应,因此须要进行转换。

         二、实模式下经过段寄存器:偏移量肯定逻辑地址;段模式下经过GDT(R)、LDT(R)和selector(就是段寄存器)肯定虚拟地址;分页模式下经过MMU和多级页表肯定存储单元,而CPU使用的是虚拟地址。

相关文章
相关标签/搜索