linux 页表

原文网址:

http://blog.chinaunix.net/uid-26817832-id-3146395.html


 简单来说,讨论linux页表就是讨论linux进程的的页表:linux页表的创建与更新都包含于进程的创建与更新中。当前的linux内核采用的是写时复制方法,在创建一个linux进程时,完全复制父进程的页表,并且将父子进程的页表均置为写保护(即写地址的时候会产生缺页异常等)。那么父子进程谁向地址空间写数据时,产生缺页异常,分配新的页,并将两个页均置为可写,按照这种方式父子进程的地址空间渐渐变得不同(也即页表变得不同)。

按照上面的分析, 只需要讨论第一个进程页表初始化,进程创建时页表的拷贝,以及缺页异常时页表的更新即可。
1.init_task进程页表的初始化
init_task的地址空间是init_mm, init_mm在内核初始化的时候就赋值给了current->active_mm. init_mm的初始化页表是swapper_pg_dir,在mips架构中swapper_pg_dir初始化在函数pagetable_init中,初始化关系是
swapper_pg_dir -> invalide_pmd_table -> invalide_pte_table 或
swapper_pg_dir -> invalide_pte_table.
即在init_mm中,页表指向的全部是invalide_pte_table。

2.创建进程时页表的拷贝
进程创建一般调用的是do_fork函数,按照如下调用关系:
do_fork->copy_process->copy_mm->dup_mm->dup_mmap->copy_page_range
找到copy_page_range函数,这个函数便是负责页表的拷贝,函数核心代码如下:
874 do {
875 next = pgd_addr_end(addr, end);
876 if (pgd_none_or_clear_bad(src_pgd))
877 continue;
878 if (unlikely(copy_pud_range(dst_mm, src_mm, dst_pgd, src_pgd,
879 vma, addr, next))) {
880 ret = -ENOMEM;
881 break;
882 }
883 } while (dst_pgd++, src_pgd++, addr = next, addr != end);
copy_pud_range便是拷贝pud表,copy_pud_range调用copy_pmd_range, copy_pmd_range调用copy_pte_range,以此完成对三级页表的复制。需要注意的是在copy_pte_range调用的copy_one_pte中有如下代码:
694 if (is_cow_mapping(vm_flags)) {
695 ptep_set_wrprotect(src_mm, addr, src_pte);
696 pte = pte_wrprotect(pte);
697 }
这里便是判断如果采用的是写时复制,便将父子页均置为写保护,即会产生如下所示的缺页异常。

3. 缺页异常时页表的更新
由页表的初始化可以看到,init_mm的页表全指向无效页表,然而普通的进程中不可能页表均指向无效项,因此肯定拥有一个不断扩充页表的机制,这个机制是通过缺页异常实现的。
以mips为例,mips的缺页异常最终会调用do_page_fault,do_page_fault调用handle_mm_fault,handle_mm_fault是公共代码,一般所有的缺页异常均会调用handle_mm_fault的核心代码如下:
3217 pud = pud_alloc(mm, pgd, address);
3218 if (!pud)
3219 return VM_FAULT_OOM;
3220 pmd = pmd_alloc(mm, pud, address);
3221 if (!pmd)
3222 return VM_FAULT_OOM;
3223 pte = pte_alloc_map(mm, pmd, address);
3224 if (!pte)
3225 return VM_FAULT_OOM;
其中 pud_alloc代码如下:
1056 static inline pud_t *pud_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long address)
1057 {
1058 return (unlikely(pgd_none(*pgd)) && __pud_alloc(mm, pgd, address))?
1059 NULL: pud_offset(pgd, address);
1060 }
其中 pgd_none用于判断pgd是否为invalide,如果是可调用 __pud_alloc,如果不是获得其地址继续查。
pmd_alloc函数和 pte_alloc_map函数类似。

因此可以看出,在缺页异常中,会按照地址一次查三张页表,如果页表为invalide,比如invalide_pmd_table或invalide_pte_table,则会分配一个新的页表项取代invalide的页表项。这便是页表扩充的机制。

需要注意的是handle_mm_fault最终会调用handle_pte_fault,在handle_pte_fault函数中有如下代码:
3171 if (flags & FAULT_FLAG_WRITE) {
3172 if (!pte_write(entry))
3173 return do_wp_page(mm, vma, address,
3174 pte, pmd, ptl, entry);
3175 entry = pte_mkdirty(entry);
3176 }
即在缺页异常中如果遇到写保护会调用 do_wp_page,这里面会处理上面所说的写时复制中父子进程区分的问题。

如上三个部分便是linux页表的大体处理框架

自己的理解:
1、swapper_pg_dir : Global Page Directory (全局页目录,即最顶层页目录,PGD) 的地址
2、几个名词:页全局目录(PGD);页上级目录(PUD);页中级目录(PMD);页表(PTE)
3、mips 虚拟地址内存空间映射如下:
0x0000 0000 ~ 0x7fff ffff
用户级空间,2GB,要经MMU(TLB)地址翻译。kuseg。可以控制要不要经过缓冲。

0x8000 0000 ~ 0x9fff ffff
kseg0. 这块区域为操作系统内核所占的区域,共512M。使用时,不经过地址翻译,将最高位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。但要经过缓冲区过渡。

0xa000 0000 ~ 0xbfff ffff
kseg1. 这块区域为系统初始化所占区域,共512M。使用时,不经过地址翻译,也不经过缓冲区。将最高3位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。

0xc000 0000 ~ 0xffff ffff
kseg2. 这块区域也为内核级区域。要经过地址翻译。可以控制要不要经过缓冲。

核代码运行在0x8000000000 ~ x9fffffff的地址空间范围,共0.5G。对内核而言,显然它应该具备访问整个物理RAM的功能,这里即存在如下问题:
  • 内核应首先建立自己的页表,以便内核本身能寻址物理RAM(mips 处理器不存在这个问题,0x8000000000 ~ x9fffffff 转换到0x0000000000 ~ x1fffffff的物理空间是由处理器内部的地址译码部件自动完成的)
  • 内核本身的地址空间只有0.5G,对于RAM大于0.5G时,内核如何寻址?
4、关于第一个问题的进一步解释
在内核arch/mips/kernel/vmlinux.lds
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)
PHDRS {
text PT_LOAD FLAGS(7);
note PT_NOTE FLAGS(4);
}
jiffies = jiffies_64;
SECTIONS
{
. = 0xffffffff80200000;

_text = .;
.text : {
. = ALIGN(8); *(.text.hot) *(.text) *(.ref.text) *(.devinit.text) *(.devexit.text) *(.text.unlikely)
. = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
. = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
. = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;

*(.text.*)
*(.fixup)
*(.gnu.warning)
....
这个文件最终会以参数 -Xlinker --script -Xlinker vmlinux.lds 的形式传给 gcc,并最终传给链接器 ld 来控制其行为。ld 会将 .text 节的地址链接到 0xFFFFFFFF80200000 处。而pmon会将内核elf文件移到物理地址0x00200000处,代码调用关键如下:
load命令 -> load_elf -> bootread

pc 寄存器中显示的是虚拟地址,cpu根据pc寄存器的值来取指,取值要访存,经过地址译码部件时,自动将0x8000000000 ~ x9fffffff 转换为0x0000000000 ~ x1fffffff(物理地址)。

5、第二个问题的处理

在前面的讨论中,一直存在一个为难题,即内核本身的地址空间只有0.5G(0x800000000-0x9fffffff),对于RAM大于0.5G时,内核如何寻址? 因此显然RAM等于0.5G是一个分界点;另外,对32位的机器而言,通产其线性地址空间为0~4G范围,当RAM大于4G时如何寻址?因此4G又是另一个分界点。

Linux的实现采用固定,永久映射+动态映射的方式来解决上述问题。
X86具体做法是,内核建立0~896MB的内核映射,即将0xc0000000-0xf0000000映射到物理内存前896MB;而对后128MB的线性地址空间(0xf000000~0xffffffff)采用动态映射方法,即根据需要映射到内存896MB以后的任意地址。具体实现机制请参考<<深入理解linux内核>>。

6、pagetable_init 主要完成固定映射和永久映射,将动态映射的页表无效,动态映射(一般是用户态的地址,进程相关的)如上面台运方所说,需要进一步看一下pagetable_init的代码,看看mips固定映射以及永久映射的具体实现。

7、线性地址空间被分成固定长度为单位的组,称为页,页内部连续的线性地址映射到连续的物理地址,这样,内核可以指定一个页的物理地址和其存取权限,而不用指定页所包含的全部线性地址的存取权限。注意:页只是一个数据块,可以存放在任何页框或磁盘中。X86处理器中有一个分页单元完成线性地址到物理地址的解释,当然在启用分页单元之前,先由内核对页表进行适当的初始化。

8、每个活动的进程拥有一个页表集和全局页目录,必须分配给它一个全局页目录,不过没有必要为进程的所有页表分配RAM,只有当进程实际需要一个页表时才给该页表分配RAM会更又效率。

9、进程共享的是虚拟地址空间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
linux内核调试分析指南 linux内核调试分析指南--上篇 本文档已经转到下面的网址,位于zh-kernel.org的文档停止更新,请访问新网址 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择和安装 安装交叉编译工具 bin工具集的使用 qemu的使用 skyeye的使用 UML的使用 vmware的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环境的建立 gdb基础 基本命令 gdb之gui gdb技巧 gdb宏 汇编基础--X86篇 用户手册 AT&T汇编格式 内联汇编 汇编与C函数的相互调用 调用链形成和参数传递 C难点的汇编解释 优化级别的影响 汇编基础--ARM篇 用户手册 调用链形成和参数传递 源码浏览工具 调用图生成工具 find + grep wine + SI global Source-Navigator vim + cscope/ctags kscope lxr SI等与gdb的特点 调用链、调用树和调用图 理想调用链 函数指针调用 调用链的层次 非理想调用链 调用树与调用图 穿越盲区 穿越gdb的盲区 穿越交叉索引工具的盲区 工程方法 bug 与 OOPS linux内核调试分析指南--下篇 ***第二部分:内核分析*** 内核组织层次和复杂度 内核层次 内核复杂度 复杂度隔离 gdb在内核分析中的用途 数据验证 界面剥离 参数记忆 路径快照 长程跟踪 整理思路 内核编码的艺术 信息聚集 数据聚集 关系聚集 操作聚集 松散聚集 顺序聚集 链聚集 哈希聚集 树形聚集 分层聚集 分块聚集 对象聚集 设施客户 设备驱动模型分析 linux设备子系统的组成 设备驱动模型 usb子系统分析 如何阅读分析大型子系统 btrfs文件系统分析 区间树核心代码分析 B树核心代码分析 调试相关子系统 kgdb源码分析 sysrq oprofile kprobes 驱动分析 载入模块符号 ***第三部分:其他工具*** kexec strace ltrace SystemTap MEMWATCH YAMD Magic SysRq 附录:社区交流相关 补丁提交相关文档 补丁制作与提交示范 多补丁发送工具 git使用 Git公共库创建及使用 附录:内核参考书籍文章 内核git库 书籍 子系统官方网站 必看网站 参考文章 私人备忘

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值