arm32 一个 vma 中 有 两个 index
L1(一级页表,即页目录表)index 为 12位,所以一级页表的大小为 2^12*4B=16KB
L2(二级页表,页表)index为8位,所以二级页表的大小为2^8*4B=1KB
arm32 硬件上 有 TTBR0 TTBR1 寄存器,但是linux不使用 Linux内核只使用TTBR1寄存器
CP15 C2 用于 存储 页目录表的物理地址(基址)
-
X 可为0
-
一级页表和二级页表的位置
我们把 一套 (一级页表和二级页表) 称为 一套表,一套表对应一个pgd
如果有N个用户进程,那么有多少套表呢?
答案是N+1套表
N个表
对应N个用户进程,每个用户分别有一套表,其中包括(内核表+用户表)
1个表
对应M个内核进程(包括idle),只对应一套表,其中包括(内核表,基址为swapper_pg_dir)
这一套表的大小远远小于 4MB+16KB
arch/arm/kernel/head.S
48 .globl swapper_pg_dir
49 .equ swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE
内核页目录表基址在swapper_pg_dir , 大小为16K
具体物理地址范围 为 0x50004000 - 0x50007FFF
二级页表的地址范围就比较随意了, alloc获取的地址是哪里,就在哪里
本质是 对 写内存 和 CP15 C2 寄存器的改变
写 CP15 C2 寄存器的本质来说
1. 将一级页表(有N+1套)的基址写入该寄存器
写内存详细一点来说是
1. 确定写哪个 一级页表项 (即确定即将写入的一级页表项 的地址),并写入值
2. 确定写哪个 二级页表项 (即确定即将写入的二级页表项 的地址),并写入值
内核页表是什么时候创建的
内核初始化的时候(从_stext到start_kernel),创建了
一级页表和二级页表
包括 Image atags mmu_turn_on附近代码的映射
从start_kernel 到 运行时,创建了
1. 设备映射
2. 内存管理相关映射等
为什么要复制
ARM32位处理器,由于无法通过TTBR0、TTBR1同时设置内核页表项地址和用户空间页表项地址,所以采用创建进程时拷贝内核空间页表的方式来实现共享内核空间的操作。
参考代码 kernel/fork.c 中的 copy_mm
idle进程进程创建的时候有没有复制内核页表?
没有,页目录表基址还是为swapper_pg_dir ,即0x50007000)
其他(kthreadd)内核进程的时候有没有赋值内核页表
没有,页目录表基址还是为swapper_pg_dir ,即0x50007000)
init 内核进程 转换为 用户进程 的时候有没有赋值内核页表
有,创建了一套页表,并复制了内核页表中的内容到新的一套页表中
用户进程创建的
有,创建了一套页表,并复制了内核页表中的内容到新的一套页表中
复制之后怎么实时同步多套页表中的"内核页表内容"?
Linux arm进程内核空间页表同步机制 https:
性能分析
内核空间的虚拟地址也是在不停的变换的,
如果映射一段地址就去主动更新所有进程页表的内核部分,显然是相当不划算的。
一个进程映射的地址,其他进程很大概率是不会用到的。
所以采用缺页中断的方式,在需要使用时,去拷贝页表。
init_mm的pgd,则保存了完整的内核空间页表。
实际情况
在vmalloc、vmap亦或者ioremap时,内核空间页表会进程调整
会修改 init_mm.pgd(swapper_pg_dir)中的一套页表内容 (即N+1中的那1套表),
并没有修改用户进程中的那套表中的该内容
在用户进程访问时,会产生缺页异常,在异常中,"拷贝内核对应页表到进程中"
进程陷入内核空间时,不进行页表切换
进程退出内核空间时,如果要切换到与之前不同的进程,则要切换,否则,不需要切换
当进程切换时,有可能进行 页表的切换
switch_mm
有几种情况
内核进程->内核进程 不切换
内核进程->用户进程 切换
用户进程->内核进程 不切换
用户进程->用户进程 切换
arch/arm/mm/mmu.c
early_pte_alloc
arm_pte_alloc
pte_t *pte = alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);
__pmd_populate(pmd, __pa(pte), prot);
return pte_offset_kernel(pmd, addr);
- linux 页表 与 arm32硬件页表是如何兼容的
https:
arm页表和"linux对页表的要求"有矛盾
ARM32现状:Most of the bits in the second level entry are used by hardware, and there aren't any "accessed" and "dirty" bits.
Linux要求:However, Linux also expects one "PTE" table per page, and at least a "dirty" bit.
解决方案:
针对第一级页表的改变
1.Therefore, we tweak the implementation slightly - we tell Linux that we have 2048 entries in the first level, each of which is 8 bytes (iow, two hardware pointers to the second level.)
针对第二级页表的改变
2.The second level contains two hardware PTE tables arranged contiguously, preceded by Linux versions which contain the state information Linux needs. We, therefore, end up with 512 entries in the "PTE" level.
现状:
See L_PTE_xxx below for definitions of bits in the "Linux pt",
See PTE_xxx for definitions of bits appearing in the "h/w pt".
PMD_xxx definitions refer to bits in the first level page table.
dirty bit
The "dirty" bit is emulated by only granting hardware write permission iff the page is marked "writable" and "dirty" in the Linux PTE.
This means that a write to a clean page will cause a permission fault, and the Linux MM layer will mark the page dirty via handle_pte_fault().
access bit
For the hardware to notice the permission change, the TLB entry must be flushed, and ptep_set_access_flags() does that for us.
The "accessed" or "young" bit is emulated by a similar method; we only allow accesses to the page if the "young" bit is set.
Accesses to the page will cause a fault, and handle_pte_fault() will set the young bit for us as long as the page is marked present in the corresponding Linux PTE entry.
Again, ptep_set_access_flags() will ensure that the TLB is up to date.
However, when the "young" bit is cleared, we deny access to the page by clearing the hardware PTE.
Currently Linux does not flush the TLB for us in this case, which means the TLB will retain the transation until either the TLB entry is evicted under pressure, or a context switch which changes the user space mapping occurs.