PS:个人学习笔记,四处抄写,拼凑而成,轻喷~
三种内存地址[1]:
- 逻辑地址:包含在机器语言指令中用来指定一个操作数或一条指令的地址。在80x86中的分段结构中尤为具体,每一个逻辑地址都由一个段(segment)和偏移量(offset)组成,偏移量指明了从段开始的地方到实际地址的距离。这里的内存地址0x80495b0 就是一个逻辑地址,必须加上隐含的DS 数据段的基地址,才能构成线性地址。也就是说 0x80495b0 是当前任务的DS数据段内的偏移。
## 写一个简单的test.c gcc -S test.s test.c得到汇编代码。 mov 0x80495b0, %eax
- 线性地址:也称虚拟地址,一个32位无符号整数,可以用来表示高达4GB的地址,通常用十六进制表示,0x00000000~0xffffffff。
- 物理地址:内存芯片级内存单元寻址。与从微处理器的地址引脚发送到内存总线上的电信号对应。物理地址由32位或36位无符号整数表示。(32位还是36位跟PAE物理地址地址扩展有关)
逻辑地址====>分段单元====>虚拟地址====>物理地址。
内存仲裁器:多处理器系统中,所有CPU共享同一内存(ps: numa CPU了解一波),这意味着 RAM芯片可以由独立CPU并发访问。因为在RAM芯片上的读写操作必须串行执行,因此一种叫做内存仲裁器的硬件电路插在总线和每个RAM之间。若某个RAM空闲,准予一个CPU访问,若繁忙状态,延迟CPU访问。单处理器上也使用内存仲裁器,因为由一个叫做DMA控制器的特殊处理器与CPU并发操作。
硬件中的分段:
实模式下,段寄存器存储的是真实的段地址,保护模式下16位的段寄存器无法放下32位的段地址,因此,它们被称为选择符,即段寄存器的作用是用来选择描述符。
一个逻辑地址由一个段标识符和一个指定段内相对地址的偏移量组成。段标识符(段选择符)是一个16位长的字段,如下图所示。偏移量是一个32位长的字段。段选择符用来表示指向哪个段描述符,即用来在段描述符中寻址,前13位是地址,能寻0到(2^13)-1,因此段描述符表的大小就是 8192,他还牵扯到一些特权级的限制,后三位;段描述符是用来表示这个段的一些性质的,比如段基址和段长之类的。我们在寻址的时候,一般是从段选择符找 到段描述符,然后从段描述符中取出段基址,加上偏移就形成了我们要访问的地址。
索引、TI、RPL具体用处后面会说道。
为了快速找到段选择符,处理器提供了段寄存器用来存放段选择符,这些寄存器称为cs, ss, ds, es, fs和gc。尽管只有6个段寄存器,但程序可以把同一个段寄存器用于不同的目的,方法是先将其值保存在内存中,用完再恢复。
6个寄存器中3个有专门用途:
- cs 代码段寄存器,指向包含程序指令的段。它还有个很重要的用处,它含有一个两位的字段用来指明CPU当前特权级别(Current Privilege Level, CPL)0最高 内核态 3最低 用户态[2]。
- ss 栈段寄存器,指向包含当前程序栈的段。
- ds 数据段寄存器,指向包含静态数据或者全局数据段。
段描述符:每个段由一个8字节的段描述符表示,它描述了段的特征。段描述符放在全局描述符表中(GDT)或局部描述符表(LDT),通常只定义一个GDT,而每个进程除了存放在GDT中的段之外,还要创建附加的段,就酱拥有了自己的LDT。GDT的地址和大小存放在gdtr控制寄存器中,当前正在使用的LDT地址和大小存放在ldtr寄存器中。
为了快速访问段描述符,80x86处理器提供了一种附加的非编程寄存器(程序猿不能设置)供6个段选择寄存器使用。每个非编程寄存器含有8个字节段描述符,由相应的段寄存器中的段选择符来指定。
每当一个段选择符装入段寄存器,对应的段描述符就装入到非编程寄存器中,从此以后,针对这个段的逻辑地址转换就不需要访问主存中的GDT或LDT,只有当段寄存器中的内容改变时,才有必要访问GDT或LDT。
索引:指定了放在GDT或LDT中的相应段描述符的入口。
TI:指明段描述符是在GDT(TI=0)中还是在LDT(TI=1)中。
RPL:请求者特权级:当段选择符装入cs寄存器时指示出CPU当前的特权级。还可以在访问数据段时有选择的削弱处理器的特权级。
一个段描述符8字节长,因此它在GDT或LDT中的相对地址是由段选择符的最高13位(即索引)x 8得到的。例如GDT在0x00020000(存放在gdtr寄存器中),段选择符索引为2,那么相应的段描述符地址时0x00020000 + (2*8),即0x00020010。
GDT的第一项总是为0,用来确保空段选择符的逻辑地址会被认为无效,因此引起一个处理器异常。能够保存在GDT中的段描述符的最大数目时2^13 - 1。
80x86中,一个逻辑地址如何转换成线性地址:
- 检查段选择符的TI字段,1的话说明段描述符在GDT中, 0的话说明在LDT中。从gdtr 或 ldtr寄存器中获得GDT/LDT的线性基地址。
- index * 8(一个描述符8个字节)+ GDT/LDT基地址得到段描述符的地址。
- 逻辑地址偏移量 + 段描述符Base字段(P43各字段含义)的值得到线性地址。
Linux中的分段
分段:每个进程分配不同的线性地址空间
分页:同一线性地址空间映射到不同的物理空间。
运行在用户态的所有进程都使用一对相同的段来对指令和数据寻址,用户代码段和用户数据段,对应的,内核态下的所有进程都使用内核代码段和内核数据段。
相应的段选择符由宏__USER_CS, __USER_DS, __KERNEL_CS和__KERNEL_DS分别定义。例如,为了对内核代码段寻址,只需要把__USER_CS宏的值装入cs段寄存器即可。
所有段相关的线性地址都是从0开始,到达2^32-1。这意味着用户态 内核态下的所有进程可以使用相同的逻辑地址。(页表不同,最终映射的物理地址不同)
所有段都是从0x00000000开始,所以Linux下逻辑地址与虚拟地址时一致的,虚拟地址等于逻辑地址的偏移量。 虚拟地址 = offset + 0x00000000。
CPL由段选择符的RPL指定, 只要当前特权级改变,段寄存器必须更新。
e.g. 当CPL为3时,ds寄存器必须含有用户数据段的段选择符。