《深入Linux内核架构》第4章 进程虚拟内存(6)

本专栏文章将有70篇左右,欢迎+关注,查看后续文章。

4.9 堆管理

malloc函数:分配堆。

问:malloc如何实现?

答:由brk()系统调用实现,而brk()基于匿名映射实现。

struct mm_struct {

        unsigned long         start_brk;         //堆的开始地址

        unsigned long         brk;                  //堆的结束地址

}

brk系统调用:只需一个参数,即堆结束地址。

brk最小分配单位:一页(按页对齐)

find_vma_intersection():

        寻找插入VMA位置,会检查扩大堆后是否和现存VMA重叠,是否可合并。

收缩堆时:调用do_munmap。

brk系统调用:最终建立一个新的匿名映射,生成一个新的VMA。

4.10 缺页异常处理

缺页异常触发情景:

        1. malloc/mmap会新建一个vma,但没有分配和映射到物理页。

                等到随后的写操作,触发缺页异常,异常处理中再分配页,并写数据到页。

        2. fork刚创建子进程时,父子进程共享物理页,并设页属性为只读。

                当父子进程任一方写物理页时,触发缺页异常,执行写时复制COW。即分配新页,并写数据。

缺页异常处理大致流程:

        1. 触发缺页异常。

                访问虚拟地址,找不到对应物理页(PTE中的Present位为0),则触发缺页异常。

        2. 保存当前进程上下文。

        3. 判断缺页原因。

                3.1 访问无效虚拟地址。

                3.2 页也被换出到交换空间。

                3.3 文件页映射,未读入文件到内存。

        4. 分别处理。

                4.1 终止进程或返回错误。

                4.2 从交换空间中读取数据到内存。

                4.3 从磁盘读取文件数据到内存。

        5. 更新页表。

                设置Present位。还可能设置Dirty位、Accessed位等。

        6. 更新TLB。

                以缓存新页表项。

        7. 恢复进程执行。

不同体系架构实现的缺页异常处理不同。

ARM为例:

        缺页异常入口:entry.S,然后调用do_page_fault ():

int do_page_fault(addr, unsigned int fsr, struct pt_regs *regs)内容为:

1. 参数检查

        地址是否合法,内核/用户空间的缺页异常,访问权限,是否中断上下文。

2. 获取task_struct,mm_struct。

3. 通过find_vma找到包含引起缺页的虚拟地址addr的VMA。

4. 根据VMA的类型,调用不同的处理函数。

        文件页:调用do_fault,最终调用vma->vm_ops->fault();

        匿名页:调用do_anonymous_page。

        页面被交换出去:调用do_swap_page。

5. 处理缺页后,并将页面映射到物理内存中,修改页表。

如果缺页处理失败。如物理页不足,则调用do_sigbus向进程发送SIGBUS信号。

4.11 用户空间缺页异常

do_page_fault () -> handle_mm_fault () -> handle_pte_fault () :

handle_pte_fault内容:

如果页表项显示对应页不在物理内存中(即!pte_present),有4种情况:

        1. 没有对应页表项(page_none())。则:

                按需分配(匿名映射)

                按需调页(文件映射)

        2. 有页表项(!page_none())。

                说明页已换出,则调用do_swap_page,从交换空间读数据到到内存页。

        3. 如果是非线性映射(pte_file()为真)

                调用do_nonlinear_fault。

                pte_file()作用:判断是否为非线性映射。

        4. 如果想写,而页表项不支持写该页,则:

                调用do_wp_page,进行COW创建副本页,写数据到副本。

                场景场景:fork子进程。

上述四种情况,分节详细说明。

4.11.1 按需调页

按需分配页调用:do_linear_fault -> _do_fault,最后调用vm->vm_ops->fault();

        (大多数文件映射:vm->vm_ops->fault = filemap_fault函数)

以映射ext4文件为例。映射时调用ext4_file_mmap函数。

struct file_operations     ext4_file_operations = {

        .mmap  =  ext4_file_mmap,

}

int    ext4_file_mmap(struct file *file, struct vm_area_struct *vma)

{

        vma->vm_ops = &ext4_file_vm_ops;

        return 0;

}

struct vm_operations_struct     ext4_file_vm_ops = {

        .fault   =  filemap_fault,

};

而filemap_fault会调用:

        struct address_space mapping->a_ops->readpage(filp, page);

                即使用address_space从磁盘文件读数据到内存。

指定一个VMA,如何读取数据到页(按需调页)?

        1. struct vm_area_struct -> struct file vm_file

        2. struct file -> struct address_space *f_mapping

        3. struct address_space -> struct address_space_operations *a_ops

        4. a_ops->readpage(struct file *, struct page *)

                readpage工作有:

                        分配页,并读入数据后。

                        更新进程页表。

                        再将页加入到逆向映射数据结构中。

写数据需要区分共享映射和私有映射。

        若为私有映射,则创建副本页,并写副本。

4.11.2 匿名页

匿名页:没有后备存储器的页,即没有映射文件。如堆栈。

匿名页的缺页异常处理:

        handle_pte_fault -> do_anonymous_page:

匿名页除了不需要文件页的数据读入,其他大致相同。

do_anonymous_page工作内容:

        1. 在高端内存域中分配一个新页。

        2. 将页加入进程页表。

        3. 更新CPU cache和MMU(TLB)。

拓展

创建匿名映射:

        mmap2(NULL, 0x100000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)

mmap只完成了初始化工作,并没有分配内存页,也没有创建PTE。

当真正访问虚拟地址时,触发缺页异常,执行handle_pte_fault,再调用do_anonymous_page,完成上述工作。

4.11.3 写时复制

当以写访问进行缺页异常时,而PTE是只读,此时进行写时复制(COW)

        即分配副本内存页,写数据到副页。

处理流程:

        handle_pte_fault -> do_wp_page

4.11.4 非线性映射

非线性映射的缺页异常处理流程:

        handle_pte_fault -> do_nonlinear_fault

4.12 内核缺页异常

4.11讲的是用户空间的缺页异常。而本节讲内核缺页异常。

触发场景:

        1. 内核访问错误地址(在不稳定的内核版本中容易出现)。

        2. 用户空间给内核传递了错误参数,导致访问无效地址。

        3. 访问vmalloc分配地址,触发缺页异常,用于分配页。

#1 #2 是真正错误,而#3是正常的,应缺页异常处理。

在内核镜像文件中有一个异常表:

        位于__start___ex_table和__stop___ex_table之间。

        表中包含很多表项,每个表项的内容:

        struct         exception_table_entry

        {

                unsigned long         insn;         //缺页异常的内核地址,即(regs)->ARM_pc

                unsigned long         fixup;         //异常处理代码的地址。

};

问:内核如何处理缺页异常?

答:__do_kernel_fault

                -> fixup_exception(struct pt_regs *regs)

int   fixup_exception(struct pt_regs *regs)

{

        struct exception_table_entry      *fixup;

        fixup   =   search_exception_tables((regs)->ARM_pc);

        if (fixup)

                regs->ARM_pc = fixup->fixup;

}

如果没找到内核异常的修正函数fixup,调用do_page_fault,进入oops(可能重启)

4.13 内核和用户空间传递数据

系统调用需要在内核和用户空间中传递数据。

为什么能直接传递数据的地址?

        1. 用户空间不能访问内核地址。

        2. 用户空间虚拟地址可能没有关联到物理页。

所以不能传递地址,只能传递真实数据。

标准函数

long copy_from_user(void *to, const void __user * from, unsigned long n)

long copy_to_user(void __user *to, const void *from, unsigned long n)

        __user作用:提醒编译器检查指针。

get_user(x, ptr)

put_user(x, ptr)

内核中处理用户空间字符串的标准函数:

        clear_user(to, n):用0填充用户空间to处。

        strlen_user(s):计算用户空间s的strlen。

4.14 小结

  • 37
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山下小童

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值