第2章 存储管理
LINUX页式管理
PGD PMD PT PTE
页表目标 中间目录 页表 页表项
LINUX在32位地址下采取二层映射
#define PGDIR_SHIFT 22
#define PTRS_PER_PGD 1024
#define PMD_SHIFT 22
#define PTRS_PER_PMD 1
#define PTRS_PER_PTE 1024
根据以上宏定义,PMD被完美的架空了,而相当于采取了二层映射
其中PGD用了线性地址的最高10位 与 MMU 对应
线性地址的中间10位是所对应的PTE在PT中的索引
剩下的最低12位则是页中的偏移量
虚拟地址 = 段基地址:段偏移量
16位 32位
更准确的讲是段选择子了吧
在LINUX中段基地址 = 0(下面的____KERNEL_CS等),所以可以认为线性地址与虚拟地址总是相等的,但其本质不是一个东西
0xC0000000-0xFFFFFFFF为内核占用
0x0-0xBFFFFFFF为用户控件
内核的虚拟内存为简单的线性映射
#__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)
__pa是从虚拟地址转换成物理地址
__va是从物理地址转换成虚拟地址
在GDT中有4个段描述符
其索引是2-5
分别是
__KERNEL_CS 内核代码段
__KERNEL_DS 内核数据段
__USER_CS 用户代码段
__USER_DS 用户数据段
#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)
通过这段宏可以看出,LINUX没用段式存储,虽然它也走了这个流程
MMU的流程 MMU使用物理地址
页式映射
从REG CR3拿PGD的地址
找到页面目录,线性地址中的高10位为索引,找到页面目录项,从中拿高20位作为页面表的索引,页面表与4k字节边界对齐,CPU自动补充前12位为0得到页面表地址。
然后拿线性地址的中间10位,得到页面表中的索引,拿到页面表项,页面表项的高20位在低位补充12个0,再加上线性地址的低12位组成物理地址。
mm_struct 任务相关的虚拟内存
vm_area_struct 一段虚拟内存的抽象,也可以理解为段
mm_struct中拥有vm_area_struct的指针
在vm_area_struct多的时候使用avl树来存储
mem_map_t 物理页表
zone_struct 物理内存的区结构,zone_struct把物理内存分成了几个部分
ZONE_DMA 0 供DMA使用
ZONE_NORMAL 普通使用
ZONE_HIGHMEN 高段内存,内核映射不到
物理内存之间区的划分并不是强制的,如果某一个区已经没有内存可用,是可以去别的区拿内存的
其实一直对内核的寻址有些疑问
不过刚刚似乎想通了
内核会做预映射,把PGD第768项以后的都做映射,也就是1G的空间
而这种映射应该是满足__pa()宏,即线性地址与物理地址是线性映射的。
所以最终__pa()宏被用作在内核代码中显性的获得某个线性地址所对应的物理地址
而MMU负责把一个线性地址隐式的转成了物理地址,而这已转换与内核代码无关。
不知这样理解是否正确?
今天只看到了这里
待续……
说起来把这么个东西放到首页很不好意思,主要目的是希望有看到的人帮我指正一下我所认知的错误或者解惑。谢谢啦:)