编程高手必学的内存知识
海纳
华为编译器高级专家,原 Huawei JDK 团队负责人
20674 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 33 讲
编程高手必学的内存知识
15
15
1.0x
00:00/00:00
登录|注册

10 | 页中断:fork、mmap背后的保护神

你好,我是海纳。
这节课是对前面所有课程的一次总结和回顾。前面我们介绍了很多内存管理的相关机制,其实都是为了把这节课的故事讲完整。在前面的课程里,我们了解了进程内部的分布,但也留下了三个关键的问题没有讲清楚:
fork 的工作方式非常奇怪,一方面父进程和子进程还可以访问共有的变量,另一方面,它们又可以各自修改这个变量,且这个修改对方都看不见,这是怎么做到的呢?
我们在第 1 节课讲内存映射时,就讲过页表中未映射状态的页表项,并不存在一块具体的物理内存与之对应。但是当我们访问到这一页的时候,页表项可以自动变成已映射的正常状态。谁在背后做了什么事情呢?
mmap 的功能十分强大,这些强大的能力是怎么完成的呢?
这三个问题,虽然看上去相互之间关系不大,但实际上它们背后都依赖页中断机制
页中断和普通的中断一样,它的中断服务程序入口也在 IDT 中(第 2 节课的内容),但它是由 MMU 产生的硬件中断。页中断有两类重要的类型:写保护中断和缺页中断。正是这两类中断在整个系统的后台默默地工作着,就像守护神一样支撑着内存系统正常工作
大多数时候,我们即使不知道它们的存在,程序也能正常地运行。但是有时候,程序写得不好就有可能造成中断频繁发生,从而带来巨大的性能下降。面对这种情况,我们第一时间就应该想到统计页中断。因为除了页中断本身会带来性能下降之外,统计页中断也可以反推程序的运行特点,从而为进一步分析程序瓶颈点,提供数据和思路。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了操作系统内存管理中的重要机制——页中断,包括写保护中断和缺页中断。通过讲解fork的原理,介绍了写时复制机制和写保护中断的作用。文章还提到了mmap的功能和execve系统调用的作用。通过对页中断的讲解,读者能够了解到页中断的类型及其在内存管理中的重要性。在execve的执行步骤中,内核为可执行程序创建一个vma结构体实例,然后将它的vm_file属性设成待加载执行的文件,建立起了内存区域和文件的映射关系。在mmap的功能分析中,文章详细介绍了私有匿名映射、私有文件映射、共享文件映射和共享匿名映射的原理和实现方式。总的来说,本文通过深入的技术分析,帮助读者全面了解了页中断在内存管理中的重要作用,以及execve和mmap等系统调用的原理和实现机制。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《编程高手必学的内存知识》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(11)

  • 最新
  • 精选
  • 🐮
    老师,建议对思考题进行分析下,验证我们分析过程; 父进程使用mmap申请私有匿名内存后,再使用fork创建子进程,这块内存会被子进程共享,同时系统会将这块内存设置为只读,当子进程使用sprintf去写数据时,会触发写时复制机制,拷贝一份内存区域,也就是说创建子进程后父子进程写的数据都不可见,他们都是操作自己特有内存空间;除非是在创建子进程前写入的数据,父子进程才可见;

    作者回复: very good~

    2021-11-20
    5
  • 小时候可鲜啦
    1、吊打面试官中,对于全局变量,映射方式应该是可读可写(而不是只读)吧。 2、对于共享库的映射,在第三课中将其归类到了私有文件映射。我觉得对于只读部分是不是应该归属于共享文件映射,而可读可写的部分才归属于私有文件映射。

    作者回复: 不是这个意思哈。首先,共享库是被私有映射的,一开始设置为可读是为了当有进程对它进行写操作的时候可以触发页中断。页中断里会做写时复制。这是为了保证只有一个进程使用这个共享库时也不会更改全局变量的值。不管哪个进程去改写全局变量,操作系统都会弄一个副本给他,让他一边玩去。操作系统那里必须维护一份完全干净的版本。你先理解了这个机制,这两个问题,应该都能明白了,你再想想?

    2021-12-11
    2
  • qinsi
    参考资料里的《Linux 内核设计与实现》似乎还是旧版的,新版(第三版)里已经提到了page cache从hash改为radix tree的理由,比如hash的冲突解决使用了链表,对于不存在的key需要遍历完整个链表才能知道不存在;又比如radix tree(又名压缩前缀树)可以压缩存储相同前缀的key,比hash更节省空间。

    作者回复: very good~

    2021-11-16
    2
  • Geek_347b1f
    作者能不能讲一下java零拷贝和直接内存用mmap怎么实现的

    作者回复: DirectBuffer和FileChannel这里对吧?好,我先记下来,也许后面的答疑课程里可以写一下这个主题。

    2021-12-03
    1
  • Samaritan.
    老师,请问一下,由于fork复制了父进程的task_struct,那么子进程就可以读取到父进程所有的信息了吧?这样是安全的吗?

    作者回复: task_struct是内核态才能访问的。在内核态里只能执行linux kernel代码。而kernel代码是值得信任的。

    2021-11-24
    2
  • linker
    思考题:后执行的进程应该会发生crash

    作者回复: 哈哈,那你实验过没有?和你的猜想是否相符?

    2021-11-17
  • 费城的二鹏
    老师,私有文件映射,多个进程加载同一个文件,其中一个进程进行写入时会发生写时复制。那么,其它进程能否感知到这个文件的变化?

    作者回复: 不能。主要是不需要。我写的时候,系统给我一个副本让我自己一边玩去了。别人不关心我拿走的副本是怎么样的,我也不关心除了我自己的副本之外的其他人是怎么处理的了。

    2021-11-16
  • 费城的二鹏
    吊打面试官中,共享库加载时设置为只读,代码段与全局变量都是只读的,在写入是会进行复制。系统如何保护代码段不会被写入的?这两种内容的只读标记是是相同的吗?

    作者回复: 是一样的。代码段是用来执行的,运行时不允许修改。这个结论我们在第8节就介绍了,这是因为运行时修改代码段会带来安全风险,我们要避免这么做。再细一点说,编译型的语言就是这么规定的,编译器绝对不能产生运行时修改代码段的代码。

    2021-11-16
    2
  • 大鑫仔Yeah
    作者能不能后续讲解下非自动管理内存的语言,比如 Swift

    作者回复: 这就是引用计数嘛。我会在19,20节稍微提一下。

    2021-11-15
  • 郑童文
    你好老师, 从这一课中所学的内容来看,似乎mmap在分配内存时只是分配了虚拟内存,要等到进程访问这块虚拟内存时 才会被映射到物理内存页, 那既然内存分配都只是分配虚拟内存,虚拟内存非常大,我们为什么还需要考虑虚拟内存的内部碎片和外部碎片的问题呢?虚拟内存那么大浪费一些无所谓啊? 还请教老师一个关于伙伴系统的问题: 操作系统的教材上说伙伴系统被用于分配连续的物理页。我有些不明白我们为什么需要策略来管理物理内存的分配呢? 在我的理解中,物理内存应该是已经被划分成一页一页的了,当虚拟内存被分配以后,将虚拟内存直接映射到空闲的物理页就可以了,进程使用的是虚拟内存地址,它只需要连续的虚拟内存,而连续的虚拟内存完全可以映射到不连续的物理页啊?我们只需要记录哪些物理页已经被映射, 哪些物理页是未被映射的,这些物理页连不连续无所谓啊? 在需要物理页的时候找一个空闲的页来映射就可以了啊? 谢谢!
    2021-12-08
    1
    3
收起评论
显示
设置
留言
11
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部