深入理解linux内核第三版(一)内存寻址

1 内存地址

逻辑地址

每个逻辑地址都由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。

物理地址

物理地址和CPU地址引脚发送到内存总线上的电信号相对应

CPU可以实现逻辑地址到物理地址的转换。

2 实模式和保护模式

从80286开始引入保护模式。

实模式体现在内核中用到的地址都是真实物理地址。也就是说段基址:段内偏移地址产生的逻辑地址就是物理地址。也就是说程序员可见的地址就是真实的内存地址。

在内核启动时,开始阶段都是以实模式启动,然后再切换到保护模式。在实模式下,所有的内存区间都是可以随意读写的。

为什么需要保护模式?实模式下,我们可以访问任何内存地址空间,包括内核空间,也可以访问其他进程的地址空间。举个例子,如果我们修改了内核空间的中断向量表,将导致系统不可用。因此需要保护模式,在保护模式下,用户进程不允许访问内核空间。

3 硬件中的分段

3.1 段选择符和段寄存器

一个逻辑地址由两部分组成:段选择符(16位)和段内偏移量(32位)。

cs:代码段寄存器

ds:数据段寄存器

ss:栈段寄存器

cs寄存器还有一个功能,只有2bit,用以指明CPU的当前特权级(current privilege level,CPL)。值为0表示最高优先级,值为3表示最低优先级。Linux只有0和3,分别称之为内核态和用户态。

段寄存器(16位)唯一目的是存放段选择符

段选择符是一个16位长度的字段,用于表示段的位置。段选择符存放在段寄存器中(CS,DS,SS)

 

 

 段选择子包括3部分,Index表示段描述符索引,TI=0表示段描述符在GDT中,TI=1表示段描述符在LDT中。RPL代表选择子的特权级(0123)

也就是说系统中最多有2^13=8192个进程,但是一般我们看到的进程号都比较大,原因是我们可分配的进程号最大65535.最大进程号和最大进程数不要混淆了

3.2 段描述符

 

 

段选择符存放了段描述符的地址。段描述符占8个字节(64位)。

段描述符有64位,用来存放具体段的段基址和段界限。(比如某一进程的代码段在内存中的位置)。或存放LDT的段描述符。

段描述符存放在GDT(全局描述符表)或LDT(局部描述符表)中。GDT和当前正在被使用的LDT在主存中的地址分别放在gdtr和ldtr控制寄存器中。

注意:下图段描述符寄存器其实不是寄存器,是内存

 段描述符结构

3.2.1 GDT(Global Descriptor Table)

一个CPU对应一个GDT,存放了段描述符

3.2.2 GDTR

GDTR是一个寄存器,存储GDT在内存中的起始地址和表长界限

 

 3.2.3 LDT(Local Descriptor Table)

有时,我们根据GDTR找到GDT基址,然后根据段寄存器中的段选择符存储的Index找到偏移量。GDT基址+偏移量找到GDT表中的记录,也就是段描述符。然后根据段描述符中的段基址找到具体段的位置(比如数据段的位置,代码段的位置等)。

但是有些情况GDT表中的记录不是段描述符,而是LDT描述符,我们根据LDT描述符中的基址找到LDT的位置,然后根据LDT再找到具体段的位置。

这里LDT就是局部描述符表

3.2.4 LDTR

LDTR是一个寄存器,存储LDT在内存中的起始地址和表长界限

3.2.5 实例:访问GDT

 

 

假设我们要访问某一代码段

1 CS寄存器中得到段选择符,段选择符TI=0(表示段描述符在GDT中):

2从GDTR寄存器中获得GDT基址。

3得到段选择器的高13位位置索引。

3根据2,3可以确定GDT中的某一记录,即段描述符

4段描述符符包含段的基址、限长、优先级等各种属性,这就得到了具体段的起始地址(基址),再以基址加上偏移地址得到最后的线性地址。

3.2.6 实例:访问LDT

 

 

假设我们要访问某一代码段

1 CS寄存器中得到段选择符,段选择符TI=1(表示段描述符在LDT中):

2 从GDTR寄存器中获得GDT基址。

3 从LDTR寄存器中获取LDT所在段的位置索引(LDTR高13位)。

4 以这个位置索引在GDT中得到LDT段描述符从而得到LDT段基址

5 用段选择器高13位位置索引值从LDT段中得到段描述符。

6 段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址得到最后的线性地址

3.2.7 快速访问段描述符

因为段描述符存储在GDT或者LDT中,也就是存储在内存中。当CPU进行取指操作时,如果从内存获取段描述符,然后定位段(比如代码段)位置,则效率是非常慢的。

         为解决这个问题,CPU提供了一个非编程寄存器,该寄存器存储段描述符。

         具体是,当段寄存器中存储了新的段选择符之后,CPU就会把该段选择符对应的段描述符从GDT或LDT中取出来,放到这个非编程寄存器中。当段寄存器中的段选择符发生了变化,则重新加载段描述符到这个非编程寄存器中。

 

 

3.2.8 逻辑地址到线性地址的转换

分段单元用来进行逻辑地址到线性地址的转换。

下图为转换过程

 

 

1 逻辑地址中取出段选择符Segment Selector

2 Segment Selector中TI字段为0,表明段描述符存储在GDT中

3 Segment Selector中高13位取出段描述符索引index。

4 根据gdtr获取GDT的基址。

5 根据3中获取的index获取GDT中的段描述符,index=0x15,所以是用户代码段。

6 段描述符中获取段起始地址0x00000000+逻辑地址中的偏移量0x80495b0得到线性地址0x80495b0.

真实场景下,不需要以上步骤就能获取到线性地址。因为,在1.3.9中讲过,CPU有个非编程寄存器会存储段描述符。

4 硬件中的分页

从80386开始所有的处理器都支持分页,cr0寄存器的PG标志位置1则开启分页功能。PG=0时,线性地址=物理地址。

4.1 线性地址的组成

在上面所描述的分段机制中,我们知道,逻辑地址已经转换为线性地址了。在分页机制中,线性地址被分成固定长度的单位,叫做页。80x86系列规定,每一页大小为4KB。

以32位地址空间为例,32位线性地址结构如下:

 

 高10位:目录表中的偏移地址,基地址在控制寄存器cr3中

中间10位:页表中的偏移地址,基地址在高10位中

低12位:页中的偏移地址,基地址在中间10位中

页的寻址过程见下图:

 

 cr3中存放目录表的基地址,线性地址高10位为目录项的偏移地址,他们组合定位到这一目录项

目录项中存放的是页表的基地址,线性地址中间10位为页表项的偏移地址,他们组合定位到页表项

页表项中存放页表的基地址,线性地址低12位为页中的偏移地址,他们组合定位到这一页的物理地址,页的大小为4KB。

 

实例

假如我们要找到线性地址0x20021406对应的页,方式如下

 

4.2 页目录项和页表项的组成

页目录项和页表项有相同的格式:

 

高20位:表示[某一页表项]的偏移地址或者[一个存储值在某一页中]的偏移地址。为什么设置为20位?因为物理地址2^32=4GB,一个叶框2^12=4kb,则总共需要2^32/2^12=2^20个叶框,所以要留足够的位数使其能寻址所有叶框,也就是20位。如果叶框大小不是4KB,这个位数可能要调整,见下面的4.4小节PAE

Present(P)标志位为0,说明此页不在主存中,此时会产生一个缺页异常,同时把线性地址放入CR2中,此时就会从磁盘加载数据到内存中,放入到该页中。

Accessed(A)标志,分页单元对相应叶框进行寻址时就设置这个标志。

Dirty(D)标志,只应用于页表项中,每当对一个叶框进行写操作时就设置这个标志。

Read/Write标志:页或页表的存取权限。

User/Supervisor(U/S)标志,访问页或者页表的所需的特权级。当为0时,只有内核态才能对页寻址。当为1时,内核态和用户态都可对页进行寻址。

PCD和PWT标志,控制赢家高速缓存处理页或页表的方式

Page Size标志,只应用于页目录项,如果置位,则页目录项指的是2MB或4MB的叶框

Global标志,只应用于页表项。只有CR4寄存器的Page Global Enable(PGE)标志置位时这个标志才起作用。

 4.3 扩展分页

从Pentium模型开始,引入扩展分页,它允许一页大小是4M而不是4KB。通过设置页目录项的Page Size标志启用扩展分页功能。在这种情况下,线性地址的格式以及到物理地址的转换过程如下

 

 4.4 CPU的物理地址扩展(PAE)分页机制

为了扩大寻址范围从Pentium Pro处理器开始采用36位地址线,这使得处理器寻址能力提升到2^36=64GB。不过,只有采用一种新的分页机制把32位线性地址转换为36位物理地址才能使用所增加的物理地址。因此Pentium Pro引入了PAE机制。通过把CR4寄存器的PAE标志置1来激活PAE功能。

PAE对应的分页机制有两种:

4.4.1 叶框大小为4KB时

1 叶框大小为4KB(2^12次方),64GB(2^36)物理内存空间共有2^36/2^12=2^24个叶框。为了能够寻址这些叶框,页表项的页表基址不再是20位,而应该是24位。而且一个页表项还包含12个标志位(见下图,当然下图画的有误只有10个标志位)。显然,之前设定的页表项大小为32位已经不满足我们的需求(24位基址+12位标志位=36位),所以页表项我们这里应该设置为64位。

页表项大小从32位变为64位大小。所以,一个4KB的页表包含512个页表项而不再是1024个。

2 新增一级表,叫页目录指针表PDPT(Page Dir Point Table),它只有4项

其寻址过程如下:

 

 4.4.2 叶框大小为2MB时

当页目录项中的Page Size置位时,寻址方式如下:

 

 

总结:PAE技术并没有扩大进程的线性地址空间;而且只有内核能够修改进程的页表,用户态下运行的进程不能使用大于4GB的物理地址空间

4.5 64位系统中的分页

注意:我们这里说的32位和64位指的是数据总线的宽度,比如数据总线是64位,则可以一次性向cpu传输64位数据。而支持64位的cpu,其地址总线不一定是64位。

一般:

8086 20位地址线
80286 24位地址线
80386 32位地址线
pentium 36位地址线
pentium4-core 48位地址线

对于64位系统,数据总线64位,线性地址也是64位,但是物理地址引脚不一定是64个(如果64个引脚,则物理地址空间2^64=亿GB级别的地址空间)

64位系统,对其寻址一般会多出几个目录层次,比如我们可以把线性地址这样划分用来寻址(假设采用标准页64KB)

 

 

 

posted @ 2022-02-24 01:03  zhenjingcool  阅读(491)  评论(0编辑  收藏  举报