1、内存地址
逻辑地址(Logical Address):包含在机器语言中用来指定一个操作数或一条指令的地址。每个逻辑地址都由一个端(segment)和偏移量(offset或displacement)组成,偏移量指明了从段开始的地方到实际地址之间的距离。
线性地址(Linear Address)(也称虚拟地址 Virtual Address):是一个32位无符号整数,可以用来表示高达4GB的地址。线性地址通常用十六进制数字来表示,值的范围从0x00000000到0xffffffff。
物理地址(Physical Address):用于内存芯片级内存单元寻址。它们与从微处理器的地址引脚发送到内存总线上的电信号相对应。物理地址由32为或36位无符号整数表示。
内存条硬件图:
内存芯片寻址的基本原理:
在存储器中保存着大量数据。为了方便保存和提取这些数据,必须为这些数据编上号。但是,这个序号不是按系列顺序编址的,而是按行和列的顺序编址的。因此,当提取它们的时侯,只要指明行数(称行址)和列数(称列址)就可以很轻易地寻找到它们。这就是内存芯片寻址的基本原理。内存芯片的内部结构就相当是一张大的矩阵网,由行(Row)线和列(Column)线相互交叉得到一系列单元格(cell),数据都被依序放在单元格中。我们把矩阵中的每个单元格称为单元(Cell),它是内存中存储数据的最小逻辑单位。把这个大矩阵就称为逻辑存储块(Logical
Bank),简写为L-Bank。
内存条抽象图:
内存控制单元(存储器管理单元 MMU)通过分段单元(Segmentation Unit)的硬件电路把一个逻辑地址转换为线性地址,接着,分页单元(Paging Unit)硬件电路把线性地址转换为物理地址。
2、逻辑地址->线性地址(分段:硬件+Linux)
2.1 段选择符与段寄存器
逻辑地址:段选择符(Segment Selector,16位)+段内偏移(Offset,32位)
Index:在GDT或LDT中段描述符的位置。
TI:段描述符在GDT中(TI=0),段描述符在LDT中(TI=1)。
RPL:请求者特权级,当段选择符装入cs寄存器中,指示CPU的当前特权级。
为了方便的找到段选择符,处理器提供六个段寄存器存放段选择符。分别是:
cs ss ds es fs gs
其中,
cs:代码段寄存器,指向包含程序指令的段。
ss:栈段寄存器,指向包含当前程序栈的段。
ds:数据段寄存器,指向包含静态数或者全局数据段。
其他三个段寄存器作一般用途,可以指向任意的数据段。
cs寄存器的RPL字段(两位字段)表示CPU的当前特权级(CPL),内核态0,用户态3。
2.2 段描述符(Segment Descriptor)
每个段由一个8字节的段描述符表示,它描述了段的特征。
全局描述符表(GDT)和局部描述符表(LDT)用于存放段描述符。
通常只定义一个GDT,而每个进程除了存放在GDT中的段之外,如果还需要创建附加的段,就可以有自己的LDT。
GDT在主存中的位置和大小存放在gdtr控制寄存器中,当前正在被使用的LDT地址和大小放在ldtr寄存器中。
段描述符关键字段:
Base: 段基地址
G :粒度标志:清0,段大小以字节为单位,否则以4096字节为单位。
Limit:存放段中最后一个内存单元的偏移量,即段长度。
S:系统标志,0:系统段,存储诸如LDT之类关键数据结构;否则,普通代码段或数据段。
Type:描述了段的类型和它的存取权限。
DPL:描述符特权级(Descriptor Privilege Level)字段,用于存取这个段都要求的特权级,表示为访问这个段而要求的CPU最小的优先级。
段的类型及对应段描述符:
代码段描述符:
表示这个段描述符代表一个代码段,它可以放在GDT或LDT中。该描述符置S标识为1(非系统段)。
数据段描述符:
表示这个段描述符代表一个数据段,它可以放在GDT或LDT中。该描述符置S标识为1,栈段是通过一般的数据段实现的。
任务状态段描述符(TSSD):
表示这个段描述符代表一个任务状态段(Task State Segment,TSS)。也就是说这个段用于保存处理器寄存器的内容。它只能出现在GD中。该描述符置S标志为0(系统段)。
局部描述符表描述符(LDTD)
表示这个段描述符代表一个包含LDT的段,它只出现在GDT中,相应的Type字段的值为2,S标志置为0(系统段)。
结构:
2.3 逻辑地址的转换(分段单元:逻辑地址 —> 线性地址)
具体操作:
*检查段选择符的TI字段,确定段描述符保存在哪一个描述表中。TI字段指明描述符是在GDT中还是在激活的LDT中,分段单元根据TI字段从gdtr或ldtr寄存器中获得GDT或LDT的线性基地址。
*从段选择符的Index字段计算出段描述符的地址,Index字段的值乘以8(一个段描述符的大小),此结果与gdtr或ldtr寄存器中的内容相加。
*把逻辑地址的偏移量与段描述符Base字段的值相加,即得到线性地址。
3、Linux中的分段
3.1 Linux中的分段
Linux以非常有限的方式使用分段。
四个主要的linux段的段描述符:
段 |
Base |
G |
Limit |
S |
Type |
DPL |
D/B |
P |
用户代码段 |
0x00000000 |
1 |
0xfffff |
1 |
10 |
3 |
1 |
1 |
用户数据段 |
0x00000000 |
1 |
0xfffff |
1 |
2 |
3 |
1 |
1 |
内核代码段 |
0x00000000 |
1 |
0xfffff |
1 |
10 |
0 |
1 |
1 |
内核数据段 |
0x00000000 |
1 |
0xfffff |
1 |
2 |
0 |
1 |
1 |
相应的段选择符由宏__USER_CS,__USER_DS,__KERNEL_CS,__KERNEL_DS分别定义。
例如,为了对内核代码段寻址,只需要将__KERNEL_CS宏产生的值装入cs寄存器中即可。
所有段的段基地址都为0x00000000,地址空间都是从0~232-1。
可就是,Linux下逻辑地址与线性地址是一致的,逻辑地址的偏移量字段与相应线性地址的值总是一一对应的。
3.2 Linux GDT
一个CPU对应一个GDT,所有的GDT都存放在cpu_gdt_table数组中。
所有GDT的地址和它们的大小被存放在cpu_gdt_descr数组中。
LinuxGDT:
Linux全局描述符表 |
段选择符 |
Linux全局描述符表 |
段选择符 |
Null |
0x0 |
TSS |
0x80 |
Reserved |
|
LDT |
0x88 |
Reserved |
|
PNPBIOS 32-bit Code |
0x90 |
Reserved |
|
PNPBIOS 16-bit Code |
0x98 |
Reserved |
|
PNPBIOS 16-bit Data |
0xa0 |
Reserved |
|
PNPBIOS 16-bit Data |
0xa8 |
TLS #1 |
0x33 |
PNPBIOS 16-bit Data |
0xb0 |
TLS #2 |
0x3b |
APMBIOS 32-bit Code |
0xb8 |
TLS #3 |
0x43 |
APMBIOS 16-bit Code |
0xc0 |
Reserved |
|
APMBIOS Data |
0xc8 |
Reserved |
|
Not Used |
|
Reserved |
|
Not Used |
|
Kernel Code |
|
Not Used |
|
Kernel Data |
|
Not Used |
|
User Code |
|
Not Used |
|
User Data |
|
Double Fault TSS |
0xf8 |
每个GDT包含18个段描述符和14个空的,未使用的或保留的项。插入未使用的项的目的是为了使经常一起访问的描述符能够处于同一个32字节的硬件高速缓冲行中。
每一个GDT中包含的18个段描述符指向下列的段:
*3个局部线程存储段(Thread-Local Storage,TLS):线程私有数据,系统调用set_thread_area()和get_thread_area()分别为正在执行的进程创建和撤销一个TLS段。
*4个用户态和内核态下的代码段,数据段。
*TSS段:任务状态段,每个CPU一个。所有的任务状态段都顺序存放在init_tss数组中。
#第n个CPU的TSS描述符的Base字段指向init_tss数组的第n个元素。
#G标志清0,Limit为0xeb,也就是段长为236字节。Type字段置为9或11。DPL为0,不允许用户态访问。
#进程切换,进程上下文切换时,这个段用于保存CPU寄存器的内容。
*LDT段:一般指向包含缺省LDT表的段。(大多数用户态下程序都不使用LDT,所以定义一个缺省的LDT供大多数进程共享)。
#缺省的局部描述符表存放在default_ldt数组中,它包含5个项,但内核仅仅有效地使用了其中的两个项(用于iBCS执行文件的调用门和Solaris/x86可执行文件的调用门)。modify_ldt()系统调用允许进程创建自己的局部描述符表(例如Wine程序),此时LDT段相应的被修改。
*Double Fault TSS:处理双重错误异常的特殊的TSS段。
*3个与高级电源管理(AMP)有关的段。
*5个与支持PnP功能的BIOS服务有关的段。