深入理解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)