系统DDR的基地址为0x0,内存为1GB,所以TTB的基地址为0x4000。下面要创建虚拟地址0xfe700000到物理地址0xffff0000之间的映射,映射大小为64KB,即16页。由于物理地址不是1MB字节对齐,所以必须创建两级映射。
用户空间/内核空间划分为2G/2G。
create_mapping:pgd = 0x80007f98, addr = 0xfe700000, phys =0xffff0000, next = 0xfe710000
pte =0x3fffd000, pmdval=0x3fffd801 //这里的pte已经转成物理地址了
*pte = 0xffff045f, *(pte+(2048>>2))=0xffff045e
*pte = 0xffff145f, *(pte+(2048>>2))=0xffff145e
*pte = 0xffff245f, *(pte+(2048>>2))=0xffff245e
*pte = 0xffff345f, *(pte+(2048>>2))=0xffff345e
*pte = 0xffff445f, *(pte+(2048>>2))=0xffff445e
*pte = 0xffff545f, *(pte+(2048>>2))=0xffff545e
*pte = 0xffff645f, *(pte+(2048>>2))=0xffff645e
*pte = 0xffff745f, *(pte+(2048>>2))=0xffff745e
*pte = 0xffff845f, *(pte+(2048>>2))=0xffff845e
*pte = 0xffff945f, *(pte+(2048>>2))=0xffff945e
*pte = 0xffffa45f, *(pte+(2048>>2))=0xffffa45e
*pte = 0xffffb45f, *(pte+(2048>>2))=0xffffb45e
*pte = 0xffffc45f, *(pte+(2048>>2))=0xffffc45e
*pte = 0xffffd45f, *(pte+(2048>>2))=0xffffd45e
*pte = 0xffffe45f, *(pte+(2048>>2))=0xffffe45e
*pte = 0xfffff45f, *(pte+(2048>>2))=0xfffff45e
读取:level 1 页表
0x00007F98: 3FFFD801 3FFFDC01
读取: level 2 页表
0x3FFFD400: FFFF045F FFFF145F FFFF245F FFFF345F
0x3FFFD410: FFFF445F FFFF545F FFFF645F FFFF745F
0x3FFFD420: FFFF845F FFFF945F FFFFA45F FFFFB45F
0x3FFFD430: FFFFC45F FFFFD45F FFFFE45F FFFFF45F /* 这个位置是linux 页表,也就是所谓的软件页表 */
0x3FFFDC00: FFFF045E FFFF145E FFFF245E FFFF345E
0x3FFFDC10: FFFF445E FFFF545E FFFF645E FFFF745E
0x3FFFDC20: FFFF845E FFFF945E FFFFA45E FFFFB45E
0x3FFFDC30: FFFFC45E FFFFD45E FFFFE45E FFFFF45E /* 这个位置是硬件页表,是给ARM硬件MMU使用的 */
0x3FFFD000: 00000000 00000000 00000000 00000000
0x3FFFD010: 00000000 00000000 00000000 00000000
0x3FFFD020: 00000000 00000000 00000000 00000000
0x3FFFD030: 00000000 00000000 00000000 00000000 /* 这个位置是linux 页表,也就是所谓的软件页表 */
0x3FFFD800: 00000000 00000000 00000000 00000000
0x3FFFD810: 00000000 00000000 00000000 00000000
0x3FFFD820: 00000000 00000000 00000000 00000000
0x3FFFD830: 00000000 00000000 00000000 00000000 /* 这个位置是硬件页表,是给ARM硬件MMU使用的 */
static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte,
pmdval_t prot)
{
pmdval_t pmdval = (pte + PTE_HWTABLE_OFF) | prot;/* HW page table offset is 512*4 = 2048 */
printk("pte =0x%x, pmdval=0x%x\n", pte, pmdval);
pmdp[0] = __pmd(pmdval); //第1个页表项
#ifndef CONFIG_ARM_LPAE //这个宏没有定义,所以1次要填充PGD的8个字节的页表
pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t)); //第2个页表项
#endif
flush_pmd_entry(pmdp);
}
static pte_t * __init early_pte_alloc(pmd_t *pmd, unsigned long addr, unsigned long prot)
{
if (pmd_none(*pmd)) {
pte_t *pte = early_alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);
__pmd_populate(pmd, __pa(pte), prot);
}
BUG_ON(pmd_bad(*pmd));
return pte_offset_kernel(pmd, addr);
}
static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
unsigned long end, unsigned long pfn,
const struct mem_type *type)
{
pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);
do {
set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
printk("*pte = 0x%x, *(pte+(2048>>2))=0x%x\n", *pte, *(pte + (2048>>2)));
pfn++;
} while (pte++, addr += PAGE_SIZE, addr != end);
}
void __init create_mapping(struct map_desc *md)
{
unsigned long addr, length, end;
phys_addr_t phys;
const struct mem_type *type;
pgd_t *pgd;
if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
printk(KERN_WARNING "BUG: not creating mapping for 0x%08llx"
" at 0x%08lx in user region\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
return;
}
if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
md->virtual >= PAGE_OFFSET &&
(md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {
printk(KERN_WARNING "BUG: mapping for 0x%08llx"
" at 0x%08lx out of vmalloc space\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
}
type = &mem_types[md->type];
#ifndef CONFIG_ARM_LPAE
/*
* Catch 36-bit addresses
*/
if (md->pfn >= 0x100000) {
create_36bit_mapping(md, type);
return;
}
#endif
addr = md->virtual & PAGE_MASK;
phys = __pfn_to_phys(md->pfn);
length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
printk(KERN_WARNING "BUG: map for 0x%08llx at 0x%08lx can not "
"be mapped using pages, ignoring.\n",
(long long)__pfn_to_phys(md->pfn), addr);
return;
}
pgd = pgd_offset_k(addr);
end = addr + length;
do {
unsigned long next = pgd_addr_end(addr, end);
//if(phys == 0xffff0000)
printk("%s:pgd = 0x%x, addr = 0x%x, phys =0x%x, next = 0x%x\n", __func__, (u32)pgd, addr, phys, next);
alloc_init_pud(pgd, addr, next, phys, type);
phys += next - addr;
addr = next;
} while (pgd++, addr != end);
}
一级页表的内容是:3FFFD801 3FFFDC01
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
按照linux的方式映射:
pgd_index = (0xfe7 >> 1) = 0x7f3
所以0xfe600000和0xfe700000组成的2MB空间是放到一个pgd[2]数组里的。0xfe6对应的是pgd[0],0xfe7对应的是pgd[1]。
一级页表的在TTB中的TTB_offset = 0x7f3 * 8 = 0x3f98
所以1级页表的地址为0x4000+0x3f98 = 0x7f98
所以二级页表的地址计算过程为:
第1个表项0x3fffd801 ----> 0xfe600000虚拟地址
0x3fffd| 1|000 0000 0001
hex bin bin bin
bit31 bit11 |bit10 bit9 ...... bit2 bit1 bit0
0x3fffd| 1 0 0 0 0 0 0 0 0 0 0 0 即 0x3fffd800 就是上面的硬件二级物理页表地址。
第2个表项0x3fffdc01 ----> 0xfe700000虚拟地址
0x3fffd| 1|100 0000 0001
hex bin bin bin
bit31 bit11 |bit10 bit9 ...... bit2 bit1 bit0
0x3fffd| 1 1 0 0 0 0 0 0 0 0 0 0 即 0x3fffdc00 就是上面的硬件二级物理页表地址。
所以从linux页表角度来看,bit31-bit11为2级页表的基地址,而bit10-bit2为二级页表的索引(偏移)。而bit10-bit2来自虚拟地址(MVA)的bit20-bit12。
虚拟地址0xfe600000: bit20-bit12 是0b0 0000 0000
虚拟地址0xfe700000: bit20-bit12 是0b1 0000 0000
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
下面是从硬件角度做页表转换:
虚拟地址0xfe700000
pgd_index = 0xfe7
TTB_offset = 0xfe7 * 4 = 0x3f9c
所以1级页表的地址为0x4000+0x3f9c = 0x7f9c,所以硬件找到的1级表项内容是3FFFDC01
下面是找2级页表的过程
0x3fffd| 11|00 0000 0001
hex bin bin bin
bit31 bit10 bit9 ...... bit2 bit1 bit0
0x3fffd| 1 1 |0 0 0 0 0 0 0 0 0 0 即 0x3fffdc00 就是上面的硬件二级物理页表地址。
假定MMU要访问第1个1MB空间,即虚拟地址为0xfe60000,即pgd_index = 0xfe6
TTB_offset = 0xfe6 * 4 = 0x3f98
0x3fffd| 10|00 0000 0001
hex bin bin bin
bit31 bit10 bit9 ...... bit2 bit1 bit0
0x3fffd| 1 0 |0 0 0 0 0 0 0 0 0 0 即 0x3fffd800 就是上面的硬件二级物理页表地址。
bit9...bit2 来自虚拟机地址的bit19~bit12。
虚拟地址0xfe600000: bit19-bit12 是0b0 0000 0000
虚拟地址0xfe700000: bit19-bit12 是0b0 0000 0000
---------------------------------------------------------------------------------------------------------------------------------------------------
总结:
bit21 bit20
pg_index = 0xfe7-->0b 1111 1110 0 1 1 1 xxxx xxxx xxxx xxxx xxxx
对于linux来说实际是以2MB为单位映射的,pgd一定是8个字节对齐的,比如这里的0x7f98而不是0x7f9c。bit21为0则映射到这两2MB第1MB空间,而bit21为1的话则映射到这2MB第1MB空间,我们举的这个例子,是映射到第2个1MB空间。我们知道硬件的二级页表本来是256项,即256*4KB = 1MB空间,现在明显Linux把它改成512项, 512*4KB = 2MB。所以linux是这样处理的,通过定义1级pgd为8个字节,即有两个表项,2级页表pte有512个表项,L1的第1个表项,指向L2的前256个表项,L1的第2个表项,指向L2的后256个表项,
总体来看和硬件是保持一致的。
pmdp[0] = __pmd(pmdval); //第1个页表项, 对应第1个1MB空间 (bit21 = 0)
pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t)); //第2个页表项, 跳过256个表项,即跳过1MB地址空间,进入到第2个1MB空间 (bit21 = 1)
一次性把L1的pgd的2个表项都填充完毕,linux的映射是以2MB空间为单位映射的。
bit21在linux中只是前256个表项还是后256个表项。
#define PTRS_PER_PTE 512
#define pte_index(addr) (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
最后2级页表的布局:
0x3fffd000--> Linux page table offset: 0
256个表项(1st 1MB)
0x3fffd400--> Linux page table
256个表项(2st 1MB)
0x3fffd800--> HW page table offset: 2048
256个表项(1st 1MB)
0x3fffdc00--> HW page table
256个表项(2st 1MB)