虚拟地址空间、页表、写时拷贝、mm_struct——一篇讲透进程如何“独占”内存文章目录前言一、一个进程一个虚拟地址空间二、页表虚拟地址到物理地址的“翻译官”2.1 页表是什么2.2 映射关系三、写时拷贝Copy-on-Write独立性的实现3.1 什么是写时拷贝3.2 为什么要写时拷贝3.3 写时拷贝流程示意四、虚拟地址空间的本质一个数据结构4.1 要不要管理“大饼”4.2 虚拟地址空间的区域划分五、虚拟地址空间的意义5.1 意义一将地址从“无序”变“有序”5.2 意义二权限保护5.3 意义三解耦合六、深入理解进程的“独立性”七、进程挂起的深刻认识八、再扩展一点vm_area_struct九、澄清几个问题Q1可以不加载代码和数据吗Q2如何深刻理解写时拷贝十、总结速查表前言你有没有想过这些问题为什么每个进程都“以为”自己拥有 4GB 内存两个进程的同一个虚拟地址比如 0x1234怎么会指向不同的物理地址写时拷贝Copy-on-Write是什么为什么要用它为什么字符串常量区写入会崩溃背后谁在拦截如果你对这些问题的答案模模糊糊那这篇文章就是为你准备的。我们把虚拟地址空间、页表映射、写时拷贝等核心概念一次性讲清楚。 一句话操作系统给每个进程画了一张“超级大饼”让进程以为自己独占物理内存实际上背后有页表在偷偷映射。一、一个进程一个虚拟地址空间在 Linux 中每个进程都拥有自己独立的虚拟地址空间通常 32 位系统是 4GB64 位系统更大。这个地址空间是“虚拟”的不是真实的物理内存。概念说明虚拟地址空间进程“认为”自己拥有的内存地址范围物理内存计算机上真实的内存条 比喻每个进程就像一个孩子操作系统像一位爸爸。爸爸给每个孩子画了一张巨大的“大饼”虚拟地址空间告诉他们“这块饼全是你的”但实际上爸爸只有一张真实的饼物理内存只是切成了小块分给孩子们。每个孩子都以为自己拥有整张饼。二、页表虚拟地址到物理地址的“翻译官”2.1 页表是什么页表是操作系统为每个进程维护的一张映射表用来将虚拟地址转换为物理地址。虚拟地址 ──(页表)── 物理地址2.2 映射关系初始情况下父进程和子进程的页表映射关系是一样的。也就是说同一个虚拟地址在父子进程中映射到同一个物理页面。 比喻页表就像一张地图。进程拿着虚拟地址比如“王府井大街1号”通过地图找到真实的物理位置比如“北京市东城区……”。通过下面这张图希望你能更加理解三、写时拷贝Copy-on-Write独立性的实现3.1 什么是写时拷贝当父子进程共享同一块物理内存时如果子进程或父进程要对这块内存进行修改操作系统会在物理内存中开辟一块新空间将原来的内容拷贝到新空间修改子进程的页表映射让子进程的虚拟地址指向新空间父进程的映射保持不变结果父子进程的虚拟地址相同但物理地址不同实现了进程的独立性。3.2 为什么要写时拷贝原因说明减少创建时间fork 时不需要拷贝全部内存只需复制页表减少内存浪费只有修改时才真正拷贝否则一直共享 如果没有写时拷贝fork 一个进程就要把父进程的所有内存拷贝一遍既慢又浪费内存。3.3 写时拷贝流程示意┌─────────────────────────────────────────────────────────┐ │ fork 之后初始状态 │ │ 父进程虚拟地址 V ──页表── 物理页面 P │ │ 子进程虚拟地址 V ──页表── 物理页面 P相同 │ └─────────────────────────────────────────────────────────┘ │ │ 子进程尝试写入 ▼ ┌─────────────────────────────────────────────────────────┐ │ 写时拷贝后 │ │ 父进程虚拟地址 V ──页表── 物理页面 P不变 │ │ 子进程虚拟地址 V ──页表── 物理页面 P新拷贝 │ └─────────────────────────────────────────────────────────┘核心虚拟地址不变但页表映射变了物理地址变了。四、虚拟地址空间的本质一个数据结构4.1 要不要管理“大饼”要管理操作系统用struct mm_struct这个数据结构来描述虚拟地址空间。概念说明虚拟地址空间操作系统给进程画的“饼”mm_struct描述虚拟地址空间的内核数据结构 经典思想先描述再组织。操作系统用mm_struct描述每个进程的地址空间再用链表或红黑树组织起来。4.2 虚拟地址空间的区域划分虚拟地址空间不是铁板一块而是划分成多个区域。每个区域有起始地址和结束地址。区域典型位置内容代码段低地址程序指令数据段代码段上方已初始化的全局变量BSS 段数据段上方未初始化的全局变量堆向上增长动态分配的内存malloc内存映射区堆和栈之间共享库、mmap栈高地址向下增长局部变量、函数调用// 内核中 mm_struct 的简化表示structmm_struct{unsignedlongcode_start,code_end;// 代码段范围unsignedlongdata_start,data_end;// 数据段范围unsignedlongheap_start,heap_end;// 堆范围unsignedlongstack_start,stack_end;// 栈范围// ... 还有页表指针等}; 比喻虚拟地址空间就像一栋大楼的楼层分布图标明了哪一层是办公室代码段、哪一层是仓库数据段、哪一层是食堂堆……五、虚拟地址空间的意义5.1 意义一将地址从“无序”变“有序”物理内存的分配是杂乱的但虚拟地址空间让每个进程看到连续、整齐的地址范围简化了程序开发。5.2 意义二权限保护页表中不仅包含物理地址还包含读写执行权限rwx。当 CPU 访问一个虚拟地址时MMU内存管理单元会检查· 如果操作违反权限比如对只读区域进行写入则触发段错误Segmentation Fault。 这就是为什么字符串常量区写入会崩溃char*strhello world;*strH;// 段错误原因说明字符串常量存储在只读数据段页表中该区域标记为只读写入操作被 MMU 拦截操作系统收到异常发送 SIGSEGV 信号进程崩溃就是我们看到的段错误5.3 意义三解耦合虚拟地址空间让进程管理和内存管理解耦合· 进程只关心自己的虚拟地址空间· 操作系统负责把虚拟地址映射到物理内存· 进程不需要知道物理内存的实际情况 好处进程可以随便用地址操作系统可以在后台搬家比如页面换入换出进程毫不知情。六、深入理解进程的“独立性”进程具有独立性体现在两个方面独立性说明内核数据结构独立每个进程有自己的task_struct、mm_struct、页表加载进入内存的代码和数据独立通过写时拷贝修改时物理内存分离 两个进程可以拥有完全相同的虚拟地址空间布局但映射到不同的物理内存。七、进程挂起的深刻认识进程挂起是指进程的某些部分或全部被换出到磁盘swap 分区以释放物理内存。有了虚拟地址空间挂起变得容易· 进程的 mm_struct 依然存在· 页表中某些条目可以标记为“未映射”或“已换出”· 当进程访问这些地址时操作系统触发缺页异常从磁盘换入 比喻就像你出差时把行李寄存在火车站。你手上只有一张寄存凭证虚拟地址需要时凭凭证取回行李物理内存。凭证还在行李可能不在手上。八、再扩展一点vm_area_structmm_struct 描述整个地址空间而每个具体区域比如堆、栈、代码段由 vm_area_struct 来描述。structvm_area_struct{unsignedlongvm_start;// 区域起始地址unsignedlongvm_end;// 区域结束地址unsignedlongvm_flags;// 权限标志rwxstructvm_area_struct*next;// 链表指针};所有的 vm_area_struct 通过链表或红黑树连接组成了完整的地址空间布局。 比喻mm_struct 是一本书的目录vm_area_struct 是每个章节的详细信息。下图展示了他们与虚拟地址空间的关系九、澄清几个问题Q1可以不加载代码和数据吗可以。 一个进程创建后可以先只有· task_struct进程控制块· mm_struct地址空间描述· 页表代码和数据可以按需加载比如执行到某个函数时才把对应的代码页从磁盘读入。Q2如何深刻理解写时拷贝写时拷贝的核心目的目的说明减少创建时间fork 时只复制页表不复制物理内存减少内存浪费父子进程共享物理内存只有修改时才拷贝 写时拷贝是操作系统的一种惰性优化不到万不得已绝不拷贝。十、总结速查表知识点核心内容虚拟地址空间每个进程认为自己独占一整块内存页表虚拟地址 → 物理地址的映射表写时拷贝修改共享内存时才真正拷贝物理页mm_struct描述虚拟地址空间的数据结构vm_area_struct描述地址空间中一个区域的数据结构虚拟地址空间意义有序化、权限保护、解耦合段错误本质页表权限拦截非法访问进程独立性内核数据结构独立 代码数据独立进程挂起部分内存换出到磁盘通过缺页异常换入写时拷贝优点减少 fork 时间减少内存浪费最后虚拟地址空间是现代操作系统的基石。理解它你就明白了· 为什么 fork 这么快写时拷贝· 为什么字符串常量不能写页表权限· 为什么两个进程的同一地址值不同独立映射· 什么是“进程挂起”换出换入动手试试写一个程序打印父子进程中同一个全局变量的地址和值观察写时拷贝的效果。
【程序地址空间:进程眼中的“超级大饼”】
虚拟地址空间、页表、写时拷贝、mm_struct——一篇讲透进程如何“独占”内存文章目录前言一、一个进程一个虚拟地址空间二、页表虚拟地址到物理地址的“翻译官”2.1 页表是什么2.2 映射关系三、写时拷贝Copy-on-Write独立性的实现3.1 什么是写时拷贝3.2 为什么要写时拷贝3.3 写时拷贝流程示意四、虚拟地址空间的本质一个数据结构4.1 要不要管理“大饼”4.2 虚拟地址空间的区域划分五、虚拟地址空间的意义5.1 意义一将地址从“无序”变“有序”5.2 意义二权限保护5.3 意义三解耦合六、深入理解进程的“独立性”七、进程挂起的深刻认识八、再扩展一点vm_area_struct九、澄清几个问题Q1可以不加载代码和数据吗Q2如何深刻理解写时拷贝十、总结速查表前言你有没有想过这些问题为什么每个进程都“以为”自己拥有 4GB 内存两个进程的同一个虚拟地址比如 0x1234怎么会指向不同的物理地址写时拷贝Copy-on-Write是什么为什么要用它为什么字符串常量区写入会崩溃背后谁在拦截如果你对这些问题的答案模模糊糊那这篇文章就是为你准备的。我们把虚拟地址空间、页表映射、写时拷贝等核心概念一次性讲清楚。 一句话操作系统给每个进程画了一张“超级大饼”让进程以为自己独占物理内存实际上背后有页表在偷偷映射。一、一个进程一个虚拟地址空间在 Linux 中每个进程都拥有自己独立的虚拟地址空间通常 32 位系统是 4GB64 位系统更大。这个地址空间是“虚拟”的不是真实的物理内存。概念说明虚拟地址空间进程“认为”自己拥有的内存地址范围物理内存计算机上真实的内存条 比喻每个进程就像一个孩子操作系统像一位爸爸。爸爸给每个孩子画了一张巨大的“大饼”虚拟地址空间告诉他们“这块饼全是你的”但实际上爸爸只有一张真实的饼物理内存只是切成了小块分给孩子们。每个孩子都以为自己拥有整张饼。二、页表虚拟地址到物理地址的“翻译官”2.1 页表是什么页表是操作系统为每个进程维护的一张映射表用来将虚拟地址转换为物理地址。虚拟地址 ──(页表)── 物理地址2.2 映射关系初始情况下父进程和子进程的页表映射关系是一样的。也就是说同一个虚拟地址在父子进程中映射到同一个物理页面。 比喻页表就像一张地图。进程拿着虚拟地址比如“王府井大街1号”通过地图找到真实的物理位置比如“北京市东城区……”。通过下面这张图希望你能更加理解三、写时拷贝Copy-on-Write独立性的实现3.1 什么是写时拷贝当父子进程共享同一块物理内存时如果子进程或父进程要对这块内存进行修改操作系统会在物理内存中开辟一块新空间将原来的内容拷贝到新空间修改子进程的页表映射让子进程的虚拟地址指向新空间父进程的映射保持不变结果父子进程的虚拟地址相同但物理地址不同实现了进程的独立性。3.2 为什么要写时拷贝原因说明减少创建时间fork 时不需要拷贝全部内存只需复制页表减少内存浪费只有修改时才真正拷贝否则一直共享 如果没有写时拷贝fork 一个进程就要把父进程的所有内存拷贝一遍既慢又浪费内存。3.3 写时拷贝流程示意┌─────────────────────────────────────────────────────────┐ │ fork 之后初始状态 │ │ 父进程虚拟地址 V ──页表── 物理页面 P │ │ 子进程虚拟地址 V ──页表── 物理页面 P相同 │ └─────────────────────────────────────────────────────────┘ │ │ 子进程尝试写入 ▼ ┌─────────────────────────────────────────────────────────┐ │ 写时拷贝后 │ │ 父进程虚拟地址 V ──页表── 物理页面 P不变 │ │ 子进程虚拟地址 V ──页表── 物理页面 P新拷贝 │ └─────────────────────────────────────────────────────────┘核心虚拟地址不变但页表映射变了物理地址变了。四、虚拟地址空间的本质一个数据结构4.1 要不要管理“大饼”要管理操作系统用struct mm_struct这个数据结构来描述虚拟地址空间。概念说明虚拟地址空间操作系统给进程画的“饼”mm_struct描述虚拟地址空间的内核数据结构 经典思想先描述再组织。操作系统用mm_struct描述每个进程的地址空间再用链表或红黑树组织起来。4.2 虚拟地址空间的区域划分虚拟地址空间不是铁板一块而是划分成多个区域。每个区域有起始地址和结束地址。区域典型位置内容代码段低地址程序指令数据段代码段上方已初始化的全局变量BSS 段数据段上方未初始化的全局变量堆向上增长动态分配的内存malloc内存映射区堆和栈之间共享库、mmap栈高地址向下增长局部变量、函数调用// 内核中 mm_struct 的简化表示structmm_struct{unsignedlongcode_start,code_end;// 代码段范围unsignedlongdata_start,data_end;// 数据段范围unsignedlongheap_start,heap_end;// 堆范围unsignedlongstack_start,stack_end;// 栈范围// ... 还有页表指针等}; 比喻虚拟地址空间就像一栋大楼的楼层分布图标明了哪一层是办公室代码段、哪一层是仓库数据段、哪一层是食堂堆……五、虚拟地址空间的意义5.1 意义一将地址从“无序”变“有序”物理内存的分配是杂乱的但虚拟地址空间让每个进程看到连续、整齐的地址范围简化了程序开发。5.2 意义二权限保护页表中不仅包含物理地址还包含读写执行权限rwx。当 CPU 访问一个虚拟地址时MMU内存管理单元会检查· 如果操作违反权限比如对只读区域进行写入则触发段错误Segmentation Fault。 这就是为什么字符串常量区写入会崩溃char*strhello world;*strH;// 段错误原因说明字符串常量存储在只读数据段页表中该区域标记为只读写入操作被 MMU 拦截操作系统收到异常发送 SIGSEGV 信号进程崩溃就是我们看到的段错误5.3 意义三解耦合虚拟地址空间让进程管理和内存管理解耦合· 进程只关心自己的虚拟地址空间· 操作系统负责把虚拟地址映射到物理内存· 进程不需要知道物理内存的实际情况 好处进程可以随便用地址操作系统可以在后台搬家比如页面换入换出进程毫不知情。六、深入理解进程的“独立性”进程具有独立性体现在两个方面独立性说明内核数据结构独立每个进程有自己的task_struct、mm_struct、页表加载进入内存的代码和数据独立通过写时拷贝修改时物理内存分离 两个进程可以拥有完全相同的虚拟地址空间布局但映射到不同的物理内存。七、进程挂起的深刻认识进程挂起是指进程的某些部分或全部被换出到磁盘swap 分区以释放物理内存。有了虚拟地址空间挂起变得容易· 进程的 mm_struct 依然存在· 页表中某些条目可以标记为“未映射”或“已换出”· 当进程访问这些地址时操作系统触发缺页异常从磁盘换入 比喻就像你出差时把行李寄存在火车站。你手上只有一张寄存凭证虚拟地址需要时凭凭证取回行李物理内存。凭证还在行李可能不在手上。八、再扩展一点vm_area_structmm_struct 描述整个地址空间而每个具体区域比如堆、栈、代码段由 vm_area_struct 来描述。structvm_area_struct{unsignedlongvm_start;// 区域起始地址unsignedlongvm_end;// 区域结束地址unsignedlongvm_flags;// 权限标志rwxstructvm_area_struct*next;// 链表指针};所有的 vm_area_struct 通过链表或红黑树连接组成了完整的地址空间布局。 比喻mm_struct 是一本书的目录vm_area_struct 是每个章节的详细信息。下图展示了他们与虚拟地址空间的关系九、澄清几个问题Q1可以不加载代码和数据吗可以。 一个进程创建后可以先只有· task_struct进程控制块· mm_struct地址空间描述· 页表代码和数据可以按需加载比如执行到某个函数时才把对应的代码页从磁盘读入。Q2如何深刻理解写时拷贝写时拷贝的核心目的目的说明减少创建时间fork 时只复制页表不复制物理内存减少内存浪费父子进程共享物理内存只有修改时才拷贝 写时拷贝是操作系统的一种惰性优化不到万不得已绝不拷贝。十、总结速查表知识点核心内容虚拟地址空间每个进程认为自己独占一整块内存页表虚拟地址 → 物理地址的映射表写时拷贝修改共享内存时才真正拷贝物理页mm_struct描述虚拟地址空间的数据结构vm_area_struct描述地址空间中一个区域的数据结构虚拟地址空间意义有序化、权限保护、解耦合段错误本质页表权限拦截非法访问进程独立性内核数据结构独立 代码数据独立进程挂起部分内存换出到磁盘通过缺页异常换入写时拷贝优点减少 fork 时间减少内存浪费最后虚拟地址空间是现代操作系统的基石。理解它你就明白了· 为什么 fork 这么快写时拷贝· 为什么字符串常量不能写页表权限· 为什么两个进程的同一地址值不同独立映射· 什么是“进程挂起”换出换入动手试试写一个程序打印父子进程中同一个全局变量的地址和值观察写时拷贝的效果。