Linux存储管理(一)
一、内存管理基础
Linux为符合多种CPU的架构,采用了不完全同于i386结构页面目录(PGD)和页面表(PT)的两层结构,而是在他们之间加了一项中间目录(PMD)。
线性地址:
PGD中下标 PMD中下标 PT中下标 物理偏移
其中:PGD的基址是在某个特定的寄存器中的。PGD中的内容指出PMD的基址。PMD中内容指出PT基址。PT中内容指出物理页面基址。
由于i386 CPU按两层映射,所以内核需将三层映射转换为两层。我们先来看内核中PGD和PMD的基本结构:
pgtable-2level.h
/*
* traditional i386 two-level paging structure:
*/
#define PGDIR_SHIFT 22
#define PTRS_PER_PGD 1024
/*
* the i386 is two-level, so we don't really have any PMD directory physically.
*/
#define PMD_SHIFT 22
#define PTRS_PER_PMD 1
#define PTRS_PER_PT 1024 PGDIR_SHIFT为22,为10位页表+12位偏移量。表示PGD下标段其实位置在22bit处。也就是0000 0000 0000 0000 0000 0000 0000 0000中下划线的地方开始的高10位。因此PGD下标的范围是:0x00400000~0xFFB00000。
PTRS_PER_PGD表示每张PGD表中指针的个数。
PMD中各个宏定义的意思和PGD相同。只是它的PTRS_PER_PMD为1,表示每张PMD表只有一个指针。这样,Linux的三层映射,就因为PMD表指针个数为1而在实际上转变为两层映射。所以,实际的线性地址中是没有PMD下标的项存在的,它只在逻辑上存在。(当然,这些都是以CPU为32位为前提的。如果CPU不是32为,PMD在物理上也是存在的。 )
32位地址,则虚拟存储空间为4GB。Linux内核将这4GB分为系统空间和用户空间。逻辑上,最高的1GB(0xC0000000~0xFFFFFFFF)为内核空间;低的3GB(0x0~0xBFFFFFFF)为用户空间。实际上,在物理内存中是从最低的地址开始的。所以内核在虚拟存储和在物理存储之间的偏移是0xC0000000。所以,对于系统空间,虚拟地址和物理地址的转换就是加减0xC0000000。
二、地址映射过程
i386 CPU为向下兼容,所以采用段页式存储管理。程序运行时,先进行段式映射,后进行页式映射。Linux为完成映射也需进行段式映射和页式映射,但弱化了段式映射。i386 CPU使用CS来作为段式映射可见部分的选择码。(不可见部分为GDT,LDT和SST)。
保护模式下寄存器格式;
Index(15~3) Table Indicator(2) Requested Privilege Level(1~0)
TI = 0, 使用GDT;TI = 1, 使用LDT。RPL为特权级别。
内核在建立一个进程时都要将其段寄存器设置好。
processor.h
#define start_thread(regs, new_eip, new_esp) do { \
__asm__("movl %0, %%fs; movl %0, %%gs": :"r"(0)); \
set_fs(USER_DS); \
regs->xds = __USER_DS; \
regs->xes = __USER_DS; \
regs->xss = __USER_DS; \
regs->xcs = __USER_CS; \
regs->edp = new_eip; \
regs->esp = new_esp; \
} while(0)
可以看出,DS,ES,SS都划分为数据段;CS为代码段。再来看看USER_CS和USER_DS是什么。
segment.h
#define __KERNEL_CS 0x10
#define __KERNEL_DS 0x18
#define __USER_CS 0x23
#define __USER_DS 0x2B
__KERNEL_CS 0x10 0000 0000 0001 0|0|00 index=2, TI=0, RPL=0
__KERNEL_DS 0x18 0000 0000 0001 1|0|00 index=3, TI=0, RPL=0
__USER_CS 0x23 0000 0000 0010 0|0|11 index=4, TI=0, RPL=3
__USER_DS 0x2B 0000 0000 0010 1|0|11 index=5, TI=0, RPL=3
因为TI=0,所以在Linux中全都使用GDT,不之用LDT。只有在VM86模式中运行wine等模拟程序时才使用LDT。
GDT的内容: head.S
/*
* This contains typically 140 quadwords, depending on NR_CPUS.
*
* NOTE! Make sure the gdt descriptor in head.S matches this if you change anything.
*/
ENTRY(gdt_table)
.quad 0x0000000000000000 /* NULL descriptor */
.quad 0x0000000000000000 /* not used */
.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff /* 0x23 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff /* 0x2b user 4GB data at 0x00000000 */
.quad 0x0000000000000000 /* not used */
.quad 0x0000000000000000 /* not used */
base(31-24) G D/B O AVL limit(19-16) P=1 DPL S=1 type base(32-16) base(15-0) limit(15-0)
K_CS 0000 0000 1 1 0 0 1111 1 00 1 1010 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
K_DS 0000 0000 1 1 0 0 1111 1 00 1 0010 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
U_CS 0000 0000 1 1 0 0 1111 1 11 1 1010 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
U_DS 0000 0000 1 1 0 0 1111 1 11 1 0010 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
给个例子说明地址转换过程:有一个程序的开始地址为0x08048568,转换为二进制为:0000 1000 0000 0100 1000 0101 0110 1000
对照线性地址的格式, 最高10位为00 0010 0000,即32。这是PGD中的下标。以这个为下标,在PGD中找到相应的目录项,目录项的高20位,并在其后12位全补为0,称为一个指向页面表的指针,指向某一个PT的基址。(因为页面大小为4KB,页面基址应为4KB的整数倍,所以低12位全位0。)
线性地址的中间10位为00 0100 1000,即72。这是PT的下标。 页表项的高20位,并在其后12位全补为0,得到物理内存页面的起始地址。
得到物理页面的起始地址,加上线性地址的低12位,得到程序的入口地址。


^^上学期课比较多,实验也多,现在放假了没太多事情做~~~