目 录CONTENT

文章目录

内存寻址

简中仙
2023-05-25 / 0 评论 / 0 点赞 / 43 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
本文最后更新于2024-02-07,若内容或图片失效,请留言反馈。 本文如有错误或者侵权的地方,欢迎您批评指正!

一、内存地址

逻辑地址:包括在机器语言指令中指定操作数或者一条指令的地址。每个逻辑地址都由一个段选择符和偏移量组成,偏移量指明了从段开始的地方到实际地址的距离。

  线性地址:也称为虚拟地址,32位的无符号整数,程序所能使用空间的大小。

  物理地址:它与内存地址总线上的数目相同,主要用于内存单元的寻址。

  内存控制单元(MMU)通过分段单元的硬件电路把一个逻辑地址转换为线性地址;接着通过分页单元的硬件电路把线性地址转换为一个物理地址。

img

  在多处理器的系统中,由于在RAM芯片上的读或写操作必须是串行地执行,所以内存中有个内存仲裁器,当CPU访问的RAM芯片空闲时,就准予访问,如果该芯片忙于其它CPU的访问,则延迟该CPU的访问。即使在单处理器上也使用内存仲裁器,因为CPU和DMA控制器可能并发访问内存。

二、硬件中的段

1、段描述符

每个段都由一个8字节的段描述符描述,段描述符都放在全局描述符表(Global Desriptor Table,GDT)中或局部描述符表(Local Descriptor Table, LDT)中。一般只定义一个GDT,如果进程还需要创建附加段,则可以有自己的LDT,不过进程单独用LDT比较少,所以一般在GDT中有一个描述LDT的段描述符,进程都共享这个包含LDT的段。GDT在主存中的地址和大小一般存放在gdtr控制寄存器中,LDT存放在ldtr控制寄存器中。

BASE表示段首字节地址;G为粒度标志,如果该位为0,则段大小以字节为单位,否则以4096字节为单位;LIMIT表示段中最后一个内存单元的偏移量;S为系统标志,如果为0,则是系统段,否则为普通段;TYPE描述了段的类型和存取权限;DPL描述了特权级,为0只能内核态访问,为3则都可以访问;P存在标志,为0表示当前段不在内存,否则在内存中。段的类型一般有代码段,数据段,任务状态段(保存寄存器的内容),包含LDT的段。为了能快速获取段描述符,80x86提供一个非编程的寄存器来存放当前被段选择符选中的段描述符。

img

2、段选择符和段寄存器

逻辑地址中的段选择符存放在CPU内的段寄存器cs,ss,ds,es,fs和gs中。段选择符是GDT的一个索引,通过它可以找到对应段的描述符,进而找到段的起始地址,并与逻辑地址中的偏移量形成线性地址。段选择符有个TI字段,用于决定段描述符在GDT还是在LDT中。

img

三、Linux中的分段

用户态的所有Linux进程都使用一对相同的段来对指令和数据寻址,就是所谓的用户代码段和用户数据段,相应的段选择符由__USER_CS,__USER_DS定义。运行在内核态的进程都使用一对相同的段对指令和数据寻址,分别叫内核代码段和内核数据段,相应的段选择符由__USER_CS,__USER_DS定义。每当要访问某个段的数据或执行某个段的代码时,相应的段选择符已经存入ds和cs段寄存器中了,所以访问的数据或者执行的指令地址只需要指定偏移量就可以了。

在Linux中,每个处理器对应一个GDT,所有的GDT都存放在cpu_gdt_table数组中,而GDT的地址和它们的大小存放在cpu_gdt_descr数组中(用于初始化gdtr控制寄存器)。每个GDT包含18个段描述符和14个空的,空的主要是为了经常访问的段描述符能够处于同一个32字节的硬件高速缓存行中。Linux的GDT包含任务状态段(TSS)、局部描述符的段、局部线程存储段、高级电源管理(APM)段和支持即插即用(PNP)的段描述符。

img

大多数用户态下的Linux程序不使用LDT,内核就定义了一个LDT供大多数进程共享。这个LDT放在default_ldt数组中。

四、硬件中的分页

分页单元把线性地址转换为物理地址,其中一个关键的任务是把所请求的访问类型与线性地址的访问权限相比较,如果是无效的,就产生缺页异常。为了效率,线性地址被分成以固定长度为单位的组,称为页。分页单元把所有的RAM分成固定长度的页框,每个页框包含一个页,页框的长度和页的长度是一样的。页只是一个数据块,可以存放在任何页框或磁盘中。

从80386开始,所有的80x86处理器都支持分页,它通常设置cr0寄存器的PG标志启用。当PG为0时,线性地址就被直接解释为物理地址。

1、常规分页

从80386开始,页的大小一般为4KB,32位的线性地址被分为3个域:Directory(目录),高10位;Table(页表),中间10位;Offset(偏移量),最低12位。使用二级页表的原因是因为如果只简单使用一级页表,那么该表项的个数会很大,即使一个进程不使用所有的表项,也会占用很大的内存空间。二级页表通过只为进程实际使用的那些虚拟内存区请求页表来减少内存使用。

每个进程都有一个页目录,不过,不必马上就为进程的所有页表都分配RAM。只有在进程实际需要的一个页表时才给页表分配RAM。正在使用的页目录的物理地址存放在控制寄存器cr3中。线性地址内的Directory字段决定页目录中的目录项,目录项是指向页表的;线性地址内的Table字段又决定页表中的表项,而表项含有页所在页框的物理地址。线性地址的Offset字段决定页框内的相对位置。

img

页目录项和页表项有着相同的结构,都包含:

  • Present,表示页或页表是否在内存中;包含页框物理地址最高20位(页框占了12位)的字段;
  • Accessed标志,对相应页框进行寻址时设置,当页被交换出去时使用;
  • Dirty标志,每当对一个页框进行写操作时设置,当页被交换出去时使用;
  • Read/Write标志,页或页表的存取权限;
  • User/Supervisor标志,访问页或页表所需的特权级;
  • PCD和PWT标志,控制硬件缓存处理页或页表的方式;
  • Page Size标志,只用于目录项,如果设置为1,则目录项指向的是2MB或4MB的页框;
  • Global标志。

2、扩展分页

扩展分页把一大段的连续的线性地址转换为相应的物理地址,页框大小为4MB而不是4KB。通过设置页目录项中的Page Size标志来启用扩展分页功能,在这种情况下,32位的线性地址分成2个字段:Directory,最高10位;Offset,其余22位。页目录项中的20位物理地址只有最高的10位(页框为22位)是有用的。通过设置cr4处理器寄存器的PSE标志来使扩展分页和常规分页共存。

img

3、物理地址扩展(PAE)分页

由于需要大于4G的物理内存,32位的物理地址是表示不了的,intel把处理器地址线增大到36位,并引入了一种叫物理地址扩展(Physical Address Extension,PAE)的机制,通过设置cr4控制寄存器中的物理地址扩展(PAE)标志激活PAE。

由于页表项中的物理地址位由原来的20位变为24位,所以PAE的页表项为64位,所以一个4KB的页表只能包含512个表项。

引入一种叫做页目录项指针表(Page Directory Point Table, PDPT)的页表新级别,它由4个64位的表项组成。cr3控制寄存器包含一个27位的也目录指针表(PDPT)基地址字段。

32位的线性地址按下列方式解释:cr3,指向一个PDPT;位30-31,指向PDPT中4个项中的一个;位21-29,指向页目录中512个项中的一个;位12-20指向页表中512项中的一个;位0-11,4KB页的偏移量。如果线性地址映射到2MB的页时(也目录项中的PS标志为1),位0-20,2MB页中的偏移量。

PAE并没有扩大进程的线性地址空间,因为它只是处理物理地址。此外,只有内核能够修改进程的页表,所以在用户态下运行的进程不能使用大于4GB的物理地址空间。但PAE允许内核使用容量高达64GB的RAM。

4、64位系统中的分页

对于64位的处理器,两级分页显然是不够的,一般要三级或四级,图中显示了一些64位系统的分页级别。

img

五、Linux中的分页

Linux采用了一种同时适用于32位和64位系统的普通分页模型。从2.6.11版本开始,采用了四级分页模型(如图5.1):页全局目录(Page Global Directory),页上级目录(Page Upper Directory),页中间目录(Page Middle Directory),页表(Page Table)。

img

对于没有启用物理地址扩展的32位系统,两级页表已经足够了。使用页全局目录和页表就可以了,内核为页上级目录和页中间目录保留了一个位置,通过把它们的目录项数设置为1,并把这两个目录项映射到页全局目录的一个适当的目录项。这样就兼容了32位的系统和64位的系统。

对于启用了物理扩展的32位系统使用三级页表。Linux的页全局目录对应80x86的页目录指针表(PDPT),取消了页上级目录,页中间目录对应80x86的页目录,Linux的页表对应80x86的页表。

对于64位的系统,使用三级还是四级分页取决于硬件对线性地址的位划分。

每一个进程都有它自己的页全局目录和自己的页表集。当发生进程切换的时候,Linux会把cr3控制寄存器的内容保存在前一个执行进程的描述符中,然后把下一个要执行进程的描述符的值装入cr3寄存器中。

0

评论区