Linux 内存管理的基本框架
内核中的3层映射
为了同时兼顾32位,64位CPU,内核的映射机制设计成3层。
PGD, PMD, PT(PTE)
内核中3层映射是如何在i386CPU转换成了2层映射?
#if CONFIG_X86_PAE # include <asm/pgtable-3level.h> #else # include <asm/pgtable-2level.h> #endif
在i386中直接include了pgtable-2level.h。
架构相关的代码集中在h文件中,然后根据设置的宏进行选择。
另外,
#define PGDIR_SHIFT 22 //PGDIR 从22位到32位
#define PTRS_PER_PGD 1024 //一个PGDIR 的空间是1024个PMD
#define PMD_SHIFT 22 // PMD 也是从22位开始 [PMD, PGDIR) 正好是0个bit位。
#define PTRS_PER_PMD 1
虚拟地址划分
32位的虚拟地址,空间大小是4G。 内核把最高的1G分配给内核使用 [0xCFFFFFFF ,0xFFFFFFFF) 用户空间的虚拟地址[0, 0xBFFFFFFF)
内核空间的地址映射
内核的虚拟地址占据最高的1G,但是物理地址是固定的从0开始。所以,内核的虚拟地址到物理地址的映射很简单减去3G (0xC0000000) #define __PAGE_OFFSET (0xC0000000) #define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET) #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET) #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET)) 可以看到: 内核虚拟地址到物理的翻译直接减去PAGE_OFFSET _pa(x) 而反向映射的宏__va(x) 则加上PAGE_OFFSET 同时,PAGE_OFFSET 也代表用户空间的上限(processor.h)。 /* * User space process size: 3GB (default). */ #define TASK_SIZE (PAGE_OFFSET)
地址映射的全过程
段式映射
由于i386同时支持段和页,所以地址映射必须走一遍段映射。
linux是如何饶过段式映射的?
1) 创建一个进程的时候给所有进程的段寄存器的赋值一样
#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->eip = new_eip; \ regs->esp = new_esp; \ } while (0)
2) 再看__USER_DS和__USER_CS的定义
#define __KERNEL_CS 0x10 #define __KERNEL_DS 0x18 #define __USER_CS 0x23 #define __USER_DS 0x2B 这4个值就是段寄存器的值,而他们的TI位都是0,都用GDT表。 而高13位依次是2,3,4,5. 所以当前进程所用的段描述符都是在全局的段描述符的2,3,4,5项。
3) 再看全局段描述符表的初始化
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 */ 从注释可以看到2,3为kernel的段描述符;4,5为用户进程的段描述符。 这些描述的base都是0,至此解释了代码中的虚拟地址经过段式映射之后还是自身。
页式映射
CR3里保存当前进程的PGD值。进程切换要初始化CR3寄存器。 asm volatile ("movl %0 %%cr3": : "r"(__pa(next->pgd)))