本文主要讨论Linux对物理地址空间的分页机制的实现。
分页机制说来也简单,借助MMU来实现进程虚拟地址空间到物理地址空间的映射,在x86平台中,PGD的基地址存放在CR3寄存器,对于ARM平台,在TIBRx寄存器,每当切换进程上下文的时候,Linux都会将本进程的PGD基地址写入相应的寄存器,如此便更换了虚拟地址到物理地址的映射关系,其产生的结果的就是不同进程中的同一个虚拟地址,经过PageGlobleDentry->PageUpperDentry->PageMiddleDentry->PageTableEntry的映射,找到的是不同的物理页框pfn,也就找到了不同的物理地址。
上图是比较典型的32bit CPU中3级页表模型,在64bit视物理地址总线宽度的不同(eg ARM64采用48bit寻址)可能会采用更多级的页表,但其基本原来都是一样的。
MMU除了要完成相应地址的映射,还要进行权限检查,由于地址映射都是以页为单位的,所以权限检查也是以页为单位的。具体地,每种CPU的MMU支持的权限标志位可能都不一样,以ARM Linux为例,其使用在权限定义在arch/arm/include/asm/pgtable*.h中,包括标记页面是否在主存中的L_PTE_PRESENT、标记页面是否是脏页的L_PTE_DIRTY、以及页面是否可执行的PAGE_SHARED_EXEC等。当Linux触发Page Fault时,就会准备好平台相关的MMU表项结构:映射关系和权限,放在相应的页目录中以供MMU使用。
PHYS_OFFSET,PAGE_OFFSET
这两个宏是系统对于物理地址空间划分很常用的两个宏,由于内核的虚拟地址空间和物理地址空间存在线性映射的关系,通过这两个宏,就可以很快地完成转换
Macro | 含义 | 备注 |
---|
PHYS_OFFSET | RAM偏移 | 物理地址空间RAM的起始地址 |
PAGE_OFFSET | kernel space偏移 | 虚拟地址空间中内核地址空间的起始地址 |
pgd_t, pud_t, pmd_t
这几个变量的类型分别是PGD,PUD,PMD的基地址,主要注意的是,作为物理地址,它们的本质是unsigned long, 而不是指针。
*_SHIFT, *_MASK
这两类宏在内存管理也很常见,前者是用于计算相应空间的大小的对数,比如,对于页来说
#define PAGE_SHIFT 12
#define PAGE_SIZE (1<<PAGE_SHIFT)
类似的还有PMD_SHIFT,PUD_SHIFT,PGDIR_SHIFT等。
后者用于计算屏蔽低位的掩码。
#define PAGE_MASK (~(PAGE_SIZE-1))
ALIGN()
要解释这个宏,就需要看看x&~(2^n -1 )的计算结果。对于一个2的指数,其二进制假设为0x10000,则0x10000 – 1 = 0x01111, ~0x01111 = 0x1111111…0000,可见任何一个数x与该数做”与”运算,低位都会被掩掉,也就是向下对齐的效果,即
#define ALIGN(x,a) (x&~(a-1))
同理,对于向上对齐,我们只需要的使用(x+(a-1))&~(a-1)即可,这就是ALIGN。
ALLIGN(x,a)用于x以a为标准向上对齐的值,a必须是2的次方,定义如下:
#define ALIGN(x,a) __ALIGN_MASK(x,(typeof(x))(a)-1)
#define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask))