概述
本文主要描述的是基于X86平台的分段管理机制,本参考Linux-2.6.11进行说明。其中的基本概念可以参考《深入理解Linux内核》第二章。
分段的概念
在了解清楚分段之前,我们需要知道以下三种地址之间的关系:逻辑地址,线性地址,物理地址。
- 逻辑地址:
包含在机器语言指令中用来指定一个操作数或一条指令的地址,它由段部分和偏移部分组成。 - 线性地址 :
我理解的是在X86平台中因为使能分页管理之前需要先使能分段管理,所以才需要转换成线性地址再进行分页管理,我们在后面会发现在Linux中,逻辑地址的偏移部分与线性地址是一样的。 - 物理地址:
用于内存芯片单元的寻址。
逻辑地址到线性地址的转换过程
其实转换过程中主要是由MMU的分段单元来负责的,但是对于操作系统来说,其需要分配好段描述表,以及设置好段选择符(其为段寄存器的2-15位)。
转换操作主要由以下过程完成:
- 先检查段选择符的TI字段,以决定段描述符在哪一个描述符表中,TI=0为全局描述符表(gdt), TI=1为局部描述符表(LDT).
- 以段描述符的index字段为索引,从GDT/LDT中获取对应的描述符,由于一个描述符的大小为8bytes,所以index*8 + GDT/LDT基地址就得到了描述符的地址。
- 把逻辑地址的偏移量与段描述符Base字段的值相加就得到了线性地址。
Linux中的分段
分段机制主要是由80x86引入的,而且大多数的平台并不支持分段机制,所以在Linux内核中是以非常有限的方式在使用分段。2.6版的Linux只有在80x86结构下才使用了分段。
在Linux中,所有用户态进程都使用相同的数据段和代码端,并且所有进程的内核态代码段和数据段也相同,这四个主要的段描述符如表1所示。
表1:
段名 | Base | G | Limiit | 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段寄存器即可。
从表1中可知,所有段的基地址都为0x00000000,并且寻址范围为(0-0xfffff)*4kbytes, 乘以4kbytes是因为G=1。所以虚拟地址(逻辑地址的偏移量)与线性地址相同。
Linux GDT
在单处理器系统中只有一个GDT,而在多处理器中每个CPU对应一个GDT。所有的GDT都存放在cpu_gdt_table数组中,而所有GDT的地址和它们的大小(初始化dgtr寄存器时使用)都被存放在cpu_gdt_descr数组中。这些符号定义在arch/i386/kernel/head.S中。
cpu_gdt_descr:
.word GDT_ENTRIES*8-1
.long cpu_gdt_table
.fill NR_CPUS-1,8,0 # space for the other GDT descriptors
/*
* The boot_gdt_table must mirror the equivalent in setup.S and is
* used only for booting.
*/
.align L1_CACHE_BYTES
ENTRY(boot_gdt_table)
.fill GDT_ENTRY_BOOT_CS,8,0
.quad 0x00cf9a000000ffff /* kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff /* kernel 4GB data at 0x00000000 */
/*
* The Global Descriptor Table contains 28 quadwords, per-CPU.
*/
.align PAGE_SIZE_asm
ENTRY(cpu_gdt_table)
.quad 0x0000000000000000 /* NULL descriptor */
.quad 0x0000000000000000 /* 0x0b reserved */
.quad 0x0000000000000000 /* 0x13 reserved */
.quad 0x0000000000000000 /* 0x1b reserved */
.quad 0x0000000000000000 /* 0x20 unused */
.quad 0x0000000000000000 /* 0x28 unused */
.quad 0x0000000000000000 /* 0x33 TLS entry 1 */
.quad 0x0000000000000000 /* 0x3b TLS entry 2 */
.quad 0x0000000000000000 /* 0x43 TLS entry 3 */
.quad 0x0000000000000000 /* 0x4b reserved */
.quad 0x0000000000000000 /* 0x53 reserved */
.quad 0x0000000000000000 /* 0x5b reserved */
.quad 0x00cf9a000000ffff /* 0x60 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff /* 0x68 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff /* 0x73 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff /* 0x7b user 4GB data at 0x00000000 */
.quad 0x0000000000000000 /* 0x80 TSS descriptor */
.quad 0x0000000000000000 /* 0x88 LDT descriptor */
/* Segments used for calling PnP BIOS */
.quad 0x00c09a0000000000 /* 0x90 32-bit code */
.quad 0x00809a0000000000 /* 0x98 16-bit code */
.quad 0x0080920000000000 /* 0xa0 16-bit data */
.quad 0x0080920000000000 /* 0xa8 16-bit data */
.quad 0x0080920000000000 /* 0xb0 16-bit data */
/*
* The APM segments have byte granularity and their bases
* and limits are set at run time.
*/
.quad 0x00409a0000000000 /* 0xb8 APM CS code */
.quad 0x00009a0000000000 /* 0xc0 APM CS 16 code (16 bit) */
.quad 0x0040920000000000 /* 0xc8 APM DS data */
.quad 0x0000000000000000 /* 0xd0 - unused */
.quad 0x0000000000000000 /* 0xd8 - unused */
.quad 0x0000000000000000 /* 0xe0 - unused */
.quad 0x0000000000000000 /* 0xe8 - unused */
.quad 0x0000000000000000 /* 0xf0 - unused */
.quad 0x0000000000000000 /* 0xf8 - GDT entry 31: double-fault TSS */
对于表1所对应的段选择符定义在include/asm-i386/segment.h,我们可以参照cpu_gdt_table来求出对应的索引值。
#define GDT_ENTRY_DEFAULT_USER_CS 14
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
#define GDT_ENTRY_DEFAULT_USER_DS 15
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
#define GDT_ENTRY_KERNEL_BASE 12
#define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
#define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE + 1)
#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)