Linux4.0,运行环境qemu,arm64平台
void __init early_fixmap_init(void) // 为FIXADDR_START建立页表映射
{
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
unsigned long addr = FIXADDR_START; // 0xffff7ffffabfe000
pgd = pgd_offset_k(addr); // addr = 0xffff7ffffabfe000 pgd = 0xffff800000eec7f8
// 此处 bm_pud 为静态定义的 pud_t 变量,此处定义的未初始化的全局变量,存放在bss段中,
// pgd 为 FIXADDR = 0xffff7ffffabfe000 虚拟地址,在init进程的pgd全局页表项中的页表项的虚拟地址
// 访问pgd虚拟地址,即可得到pud的物理基地址
// 此函数即将bm_pud变量的虚拟地址对应的物理基地址,然后此物理基地址或上PUD_TYPE_TABLE,将此结果
// 存放到 FIXADDR_START所对应的pgd页表中,即 pgd_val(pgd) = 此物理地址
pgd_populate(&init_mm, pgd, bm_pud);
pud = pud_offset(pgd, addr);
pud_populate(&init_mm, pud, bm_pmd);
pmd = pmd_offset(pud, addr);
pmd_populate_kernel(&init_mm, pmd, bm_pte);
/*
* The boot-ioremap range spans multiple pmds, for which
* we are not preparted:
*/
BUILD_BUG_ON((__fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT)
¦ != (__fix_to_virt(FIX_BTMAP_END) >> PMD_SHIFT));
if ((pmd != fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)))
¦|| pmd != fixmap_pmd(fix_to_virt(FIX_BTMAP_END))) {
WARN_ON(1);
pr_warn("pmd %p != %p, %p\n",
pmd, fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)),
fixmap_pmd(fix_to_virt(FIX_BTMAP_END)));
pr_warn("fix_to_virt(FIX_BTMAP_BEGIN): %08lx\n",
fix_to_virt(FIX_BTMAP_BEGIN));
pr_warn("fix_to_virt(FIX_BTMAP_END): %08lx\n",
fix_to_virt(FIX_BTMAP_END));
pr_warn("FIX_BTMAP_END: %d\n", FIX_BTMAP_END);
pr_warn("FIX_BTMAP_BEGIN: %d\n", FIX_BTMAP_BEGIN);
}
}
val 页表项 页表项物理地址
FIXADDR_START = 0xffff7ffffabfe000
swapper_pg_dir = 0xffff800000ef0000
pgd = 0xffff800000ef07f8 0x40ebd003 bm_pud 0x40ebd000
pud = 0xffff800000ebdff8 0x40ebc003 bm_pmd 0x40ebc000
pmd = 0xffff800000ebcea8 0x40ebb003 bm_pte 0x40ebb000
虚拟地址 0xffff800000ef07f8 对应的物理地址 0x40ef07f8 处,存放的就是 bm_pud 的物理基地址
可以执行命令:
/ # devmem 0x40ef07f8 64
0x0000000040EBD003
可以访问到物理内存 0x40ef07f8 ,内容为0x0000000040EBD003
/ # devmem 0x40ebdff8 64
0x0000000040EBC003
/ # devmem 0x40ebcea8 64
0x0000000040EBB003
/* pgd_offset */
1.首先,对于pgd页表,会通过目标虚拟地址addr(此处为addr = FIXADDR_START= 0xffff7ffffabfe000),计算出此addr在pgd页表项中的偏移offset,然后通过进程的pgd的基地址加上offset*8,注(为什么乘以8?因为arm64 一个pgd页表项包含8个字节),即可得到此addr在全局页表项中的那个pgd页表的虚拟地址 pgd_vaddr
2.得到pgd_vaddr后,可以在此pgd_vaddr处填充 pud的物理基地址
注:内核中静态定义了 bm_pud[512] __page_aligned_bss,bm_pmd[512] __page_aligned_bss,
bm_pte[512] __page_aligned_bss,因为静态定义的全局未初始化的变量,存在于bss段中,所以定义后,便在bss段中有地址
静态数组 物理地址 虚拟地址
bm_pud 0x40ebd000 0xffff800000ebd000
bm_pmd 0x40ebc000 0xffff800000ebc000
bm_pte 0x40ebb000 0xffff800000ebb000
3.代码讲解:
3.1 start_kernele -> setup_arch -> early_fixmap_init -> pgd = pgd_offset_k(addr)
pgd_offset_k(addr)函数,以init进程中的pgd页表基地址为pgd的基地址,init进程中的pgd即为 swapper_pg_dir=0xffff800000ef0000
知道了pgd基地址,那么需要知道addr在pgd页表中的哪一项,需要跟进addr计算在pgd页表中的offset,跟进虚拟地址的组成,可知,pgd的索引位为: addr[47:39],占据9位,所以计算方式为:(addr >> 39) & (2^9 - 1) = (0xffff7ffffabfe000>>39)&0x1ff = 0xff,则在pgd中的偏移为0xff,因为一个pgd页表项有8个字节,所以0xff个页表项的实际偏移为:0xff*8 = 0x7f8
则addr在gpd页表中的pgd的虚拟地址为:0xffff800000ef0000 + 0x7f8 = 0xffff800000ef07f8
3.2 start_kernele -> setup_arch -> early_fixmap_init -> pgd_populate(&init_mm, pgd, bm_pud);
pgd_populate(&init_mm,pgd,bm_pud)函数,将pud页表的物理基地址放到 addr对应的pgd页表项对应的地址中去,由于bm_pud的虚拟地址为0xffff800000ebd000,物理地址为0x40ebd000,所以将 0x40ebd000 (另外还需要或上PUD_TABLE的标记)值,放到0xffff800000ef07f8这个虚拟地址对应的物理地址上去,即pgd页表项中,存放了pud页表的物理基地址
此函数调用了set_pgd(pgd, __pgd(__pa(pud) | PUD_TYPE_TABLE));
__pa(pud)即为 __pa(bm_pud),即将0xffff800000ebd000虚拟地址转换为物理地址,由于是线性映射的,所以为:
0xffff800000ebd000 - 0xffff800000000000 + 0x40000000 = 0x40ebd000
PUD_TYPE_TABLE = 3,所以__pa(pud) | PUD_TYPE_TABLE = 0x40ebd000 | 0x3 = 0x40ebd003
__pgd(x)宏是构造一个pgd变量,set_pgd函数将新构造的pgd变量,指向pgd_populate函数传进来的gpd并返回,此时调用pgd_populate(&init_mm, pgd, bm_pud)函数后,pgd_val便为pud的物理基地址0x40ebd003
同理,简单记录PUD,PMD的过程
pud = pud_offset(pgd, addr); // pgd = ffff800000ef07f8 //addr = 0xffff7ffffabfe000
接着调用:(pud_t *)pgd_page_vaddr(*pgd) + pud_index(addr)
pud_index(addr) = (0xffff7ffffabfe000 >> 30)& 0x1ff = 0x1ff
pgd_page_vaddr(*pgd) = pgd_page_vaddr(*pgd) = __va(pgd_val(pgd) & PHYS_MASK & (s32)PAGE_MASK);
pgd_val(pgd) = 0x40ebd003
PHYS_MASK = 0xffffffffffff
(s32)PAGE_MASK = 0xfffff000
所以__va(0x40ebd003 & 0xffffffffffff & 0xfffff000) = __va(0x40ebd000) = 0xffff800000ebd000
所以最终 (pud_t *)pgd_page_vaddr(*pgd) + pud_index(addr)
= 0xffff800000ebd000 + 0x1ff*8 = 0xffff800000ebdff8
所以pud = 0xffff800000ebdff8
pud_populate函数和pgd_populate完全类似,执行完后,pud页表项中存放的是bm_pmd的物理基地址
同理不再介绍
最后,没有建立PTE页表项,此处放在使用的地方进行映射