段式管理用以将逻辑地址转化为线性地址。一个逻辑地址由一个16位的段选择子和一个32位的偏移量组成。
|
Index |
TI |
RPL |
- 其中,Index有13位,为访问段描述符数组项的下标;TI只有1位,为0表示使用GDT,1表示使用LDT;RPL有2位,用以表示当前进程的特权级别。
- 2、段描述符
- 每个段的属性在内存中由一个8字节的段描述符表示,段描述符存放在全局描述符表(GDT)或者局部描述符表(LDT)中,其地址和体积分别保存在GDTR和LDTR寄存器中。段描述符的结构如下:
|
Base24-Base31 |
G |
D/B |
L |
AVL |
Limit16-Limit19 | |||
|
P |
DPL |
S |
Type |
Base16-Base23 | ||||
|
Base0-Base15 | ||||||||
|
Limit0-Limit15 | ||||||||
- 其中,Base为32位的段基(线性)地址;Limit为20位的段长度;G只有1位,为0表示段长度以字节为单位,1表示段长度以4KB为单位;D/B只有1位,为0表示为16位段,1表示为32位段;L只有1位,未使用;AVL只有1位,供系统软件使用(Linux从未使用该位);P只有1位,表示该段内容是否在内存中(Linux从不将整个段替换出内存,故该位恒为1);DPL有2位,表示访问该段所需要的特权级别;S只有1位,为0表示段为存放关键数据的系统段,1表示段为普通段;Type有4位,定义了段类型和访问权限。
- 在linux系统中,主要包括代码段描述符、数据段描述符(包含栈段)、任务状态段(TSS,Type字段值为9或11)描述符和局部描述符表(LDT,Type字段值为2)描述符四种段描述符。其中,前两者可以被包含在GDT或LDT中,TSSD和LDTD为系统关键段,仅能包含在GDT中。
- 3、Linux内核中的分段
- 出于效率和兼容性的考虑,Linux内核中对分段的支持有限,仅在i386结构有要求时才使用分段。
- (1)代码段和数据段
- 在linux中,所有用户进程都使用相同的代码段(__USER_CS)和数据段(__USER_DS),所有的内核进程都使用相同的代码段(__KERNEL_CS)和数据段(__KERNEL_DS)。这四个段的段描述符主要字段如下:
|
Segment |
Base |
G |
Limit |
S |
Type |
DPL |
D/B |
P |
|
__USER_CS |
0x00000000 |
1 |
0xFFFFF |
1 |
10 |
3 |
1 |
1 |
|
__USER_DS |
0x00000000 |
1 |
0xFFFFF |
1 |
2 |
3 |
1 |
1 |
|
__KERNEL_CS |
0x00000000 |
1 |
0xFFFFF |
1 |
10 |
0 |
1 |
1 |
|
__KERNEL_DS |
0x00000000 |
1 |
0xFFFFF |
1 |
2 |
0 |
1 |
1 |
- 即,这四个段均为覆盖整个地址空间的32位普通段,仅有Type和DPL存在差异。在发生特权级变化时,内核会自动将由相应宏表示的段载入到段寄存器中。
- (2)Linux中的GDT
- 在linux中,每个处理器需要一个GDT。所有的GDT都保存在arch/i386/kernel/head.S文件中的cpu_gdt_table数组中,其地址和体积则保存在同一文件的cpu_gdt_descr数组中。GDT表共有32项,其中包含了18个段描述符和14个保留项。这18个段描述符如下:
- ①段选择子为0x00的null;
- ②段选择子为0x30、0x38、0x40的三个线程本地存储段(TLS#1-TLS#3),用以辅助多线程程序在本地存储局部数据;
- ③段选择子为0x60的__KERNEL_CS,段选择子为0x68的__KERNEL_DS,段选择子为0x70的__USER_CS,段选择子为0x78的__USER_DS;
- ④段选择子为0x80的任务状态段(TSS),用以保存进程的状态,不同处理器的GDT中的TSS不同;
- ⑤段选择子为0x88的LDT;
- ⑥段选择子为0x90、0x98、0xA0、0xA8、0xB0的五个和Plug and Play(PnP)BIOS服务相关的段;
- ⑦段选择子为0xB8、0xC0、0xC8的三个和高级电源管理(APM)相关的段;
- ⑧段选择子为0xF8的一个特殊的TSS,由内核用以处理“Double fault”异常。
- (3)Linux中的LDT
- 由于Linux的大多数用户进程都不使用LDT,其内核仅在arch/i386/kernel/traps.c文件中定义了一个五元素的数组default_ldt来保存被进程共享的LDT。其中,只有两个可被内核使用:用于iBCS可执行代码的调用门,和用于Solaris /x86可执行代码的调用门。
- 此外,用户进程可以调用modify_ldt()系统调用来管理自身的LDT,但内核不会对其进行严格的管理。








