MMU和页表之初探
MMU工作原理
内存管理单元(Memory Management Unit,MMU)是计算机系统中的关键硬件组件,用于管理和控制处理器对内存的访问。MMU的主要功能包括地址转换、内存保护和缓存控制。
1. 地址转换
-
虚拟地址到物理地址的映射: 现代计算机系统使用虚拟内存技术,应用程序运行在自己的虚拟地址空间中。MMU负责将CPU生成的虚拟地址转换为在物理内存中的实际物理地址。
-
页表(Page Table): 地址转换通常通过页表实现。页表是一个数据结构,存储了虚拟页面和物理页面框架之间的映射关系。
-
多级页表: 为了节省内存,页表可能采用多级结构,如两级或三级页表,通过分段方式组织映射关系。
2. 地址转换缓存(TLB)
-
TLB的作用: 为了提高地址转换的效率,MMU使用翻译后备缓冲(Translation Lookaside Buffer,TLB),这是一个高速缓存,存储了最近使用的虚拟地址到物理地址的映射。
-
TLB命中与未命中: 当CPU需要访问内存时,MMU首先在TLB中查找。如果映射存在(TLB命中),则立即完成地址转换;如果不存在(TLB未命中),则需要访问页表,并将新的映射加载到TLB中。
3. 内存保护
-
权限控制: MMU能够设置每个内存区域的访问权限,如只读、读写或可执行。这可以防止程序错误或恶意代码访问未授权的内存区域,保护操作系统和其他应用程序的安全。
-
进程隔离: 通过内存保护,不同的进程运行在各自独立的虚拟地址空间中,互不干扰,提高系统的稳定性和安全性。
4. 虚拟内存管理
-
页置换: 当物理内存不足时,MMU和操作系统协同工作,将不常使用的内存页写入磁盘(换出),并在需要时从磁盘读取(换入)。
-
页面错误(Page Fault): 如果CPU访问的虚拟地址没有对应的物理内存页,MMU会触发页面错误中断,交由操作系统处理。操作系统将所需页面加载到内存,并更新页表和TLB。
5. 工作流程总结
- CPU发出内存访问请求,包含虚拟地址。
- MMU在TLB中查找虚拟地址的映射。
- 如果命中,直接获得物理地址。
- 如果未命中,访问页表获取映射,并更新TLB。
- MMU检查访问权限,确保操作合法。
- 如果权限允许,继续执行内存访问。
- 如果权限不符,触发保护异常,由操作系统处理。
- 物理地址访问,完成读写操作。
6. 总结
MMU通过地址转换和内存保护,实现了虚拟内存的核心功能,使得程序员可以在不知道物理内存布局的情况下编写代码。同时,MMU提高了系统的安全性和稳定性,支持多任务和多进程环境下的内存管理。通过TLB等机制,MMU在提供灵活性的同时,尽可能降低了地址转换带来的性能开销。
每个进程都享有属于自己的页表吗?
是的,在大多数现代操作系统中,每个进程通常都有属于自己的页表。这是因为:
-
独立的虚拟地址空间: 每个进程都运行在其独立的虚拟地址空间中。这意味着相同的虚拟地址在不同的进程中可以映射到不同的物理内存位置。
-
内存保护和隔离: 拥有独立的页表可以确保进程之间的内存隔离。一个进程无法直接访问或修改另一个进程的内存空间,这提高了系统的安全性和稳定性。
-
地址转换的需要: 页表存储了虚拟地址到物理地址的映射关系。由于每个进程的地址空间不同,因此需要各自的页表来管理这种映射。
当进行进程切换时,操作系统会更新MMU中相关的寄存器(例如页表基址寄存器),使其指向当前进程的页表。 这样,CPU在执行地址转换时,会使用当前进程的页表来正确地将虚拟地址映射到物理地址。
值得注意的是:
-
共享内存: 虽然每个进程有自己的页表,但通过操作系统的机制,进程之间可以共享部分内存区域。例如,共享库或通过特定的共享内存机制,多个进程的页表可以映射到相同的物理内存页。
-
线程与页表: 在多线程模型中,同一进程内的多个线程共享同一个地址空间和页表,因为它们属于同一个进程。
-
内核空间: 操作系统内核通常在所有进程的页表中映射到相同的高地址区域,以便在需要时能够快速地从用户态切换到内核态。
总结: 每个进程拥有自己的页表,这是实现虚拟内存、内存保护和进程隔离的基础。通过独立的页表,操作系统能够为每个进程提供独立的虚拟地址空间,同时确保系统的安全和稳定运行。
linux内核会在启动后加载全部的页表到内存吗?
不会,Linux内核在启动后不会将所有的页表一次性全部加载到内存中。
以下是具体原因和工作方式:
1. 内核初始化时的页表加载
-
内核自身的页表: 在启动过程中,Linux内核会初始化自己的页表,用于映射内核空间的虚拟地址到物理地址。这些映射通常是恒定的,并且在系统运行期间不会频繁变化。
-
初始进程的页表: 内核还会为初始进程(如
init
进程或systemd
)创建必要的页表,以启动用户空间的进程。
2. 按需创建和加载页表
-
按需分页(Demand Paging): Linux采用了按需分页机制。这意味着当进程尝试访问某个虚拟地址,但对应的物理内存尚未分配时,会产生一个页面错误(Page Fault)。操作系统捕获这个中断,并为该虚拟地址分配物理内存,同时更新对应的页表项。
-
延迟分配内存: 在进程创建时(例如通过
fork()
或execve()
),内核不会立即分配所有需要的物理内存或完整的页表。相反,它会采用懒加载的方式,只有在实际访问到某个内存区域时,才分配相应的资源。 -
共享页表: 在某些情况下,进程可能会共享部分页表,例如使用写时复制(Copy-On-Write)技术。在
fork()
系统调用中,子进程会共享父进程的内存页和页表,直到发生写操作。这减少了不必要的内存分配。
3. 内存效率和性能的考虑
-
减少内存开销: 如果在启动时就加载所有可能的页表,将会消耗大量的内存,特别是在有大量进程或大型内存映射的系统中。这是不切实际的,也是不必要的。
-
性能优化: 通过按需加载,操作系统可以更有效地管理内存资源,将物理内存用于实际需要的地方,提高系统的整体性能。
4. 页面换入换出
-
页表的换出: 在内存紧张的情况下,不仅普通的内存页可能被交换到磁盘,页表本身也可能被换出。当被换出的页表再次需要时,会被换入内存。
-
内存管理的层次化: 内核通过多级页表结构(如二级、三级或四级页表)和 TLB(Translation Lookaside Buffer)缓存,来高效地管理内存和地址转换,同时尽量减少对物理内存的占用。
5. 总结
-
动态管理: Linux内核以动态、按需的方式管理页表和内存资源,只有在需要时才分配和加载页表。
-
提高效率: 这种机制使得系统能够更高效地利用内存资源,避免不必要的内存占用,同时确保进程在需要时能够正确地访问内存。
因此,Linux内核不会在启动后加载全部的页表到内存,而是通过按需加载和动态分配的方式,高效地管理页表和内存资源。
多个进程页表的虚拟地址会映射到同一个物理地址吗?
是的,多个进程的页表中的虚拟地址可以映射到同一个物理地址。
详细解释:
1. 共享内存(Shared Memory)
-
内存共享: 进程间共享内存是进程通信的一种方式。通过共享内存,不同进程可以访问同一物理内存区域,但在各自的虚拟地址空间中可能映射到相同或不同的虚拟地址。
- 示例: 使用
mmap()
系统调用并设置MAP_SHARED
标志,多个进程可以将同一文件或匿名内存区域映射到各自的地址空间。
- 示例: 使用
-
页表映射: 在每个参与共享的进程的页表中,都有相应的页表项,将各自的虚拟地址映射到相同的物理页帧。
2. 共享库(Shared Libraries)
-
代码段共享: 操作系统通常会将常用的共享库(如 libc)的只读代码段映射到所有需要的进程中,以节省内存。
- 同一物理地址: 这些共享库的代码段在物理内存中只存有一份,不同进程的页表将各自的虚拟地址映射到相同的物理地址。
3. 写时复制(Copy-On-Write,COW)
-
机制描述: 当一个进程通过
fork()
创建子进程时,父子进程最初共享相同的物理内存,实现方式是将他们的页表中的虚拟地址映射到相同的物理页。 -
写操作: 当父或子进程尝试写入共享的内存页时,会触发页错误。操作系统这时会为该进程创建该页的副本,并更新页表,使其指向新的物理页,从而保证进程间的内存独立。
4. 内核映射
-
内核空间共享: 在许多操作系统中,内核空间的某些部分会映射到所有进程的虚拟地址空间中,通常是在高地址区域。
- 相同物理地址: 这些内核空间的虚拟地址在各个进程中虽然可能相同(或者不同,视架构而定),但都映射到相同的物理内存。
5. 内存映射文件
-
文件映射: 通过将文件内容映射到内存(使用
mmap()
),多个进程可以将同一个文件的内容加载到内存中。- 共享页面: 如果映射是共享的(
MAP_SHARED
),那么这些页面可以在物理内存中只存储一份,各进程的页表将各自的虚拟地址映射到相同的物理地址。
- 共享页面: 如果映射是共享的(
小结
-
可能共享: 虽然每个进程有独立的虚拟地址空间和页表,但在需要共享数据时,不同进程的页表可以将虚拟地址映射到相同的物理地址。
-
内存高效: 通过共享物理内存,操作系统可以节省内存资源,提高数据访问的效率。
-
权限控制: 即使映射到相同的物理地址,操作系统仍可通过页表的权限位控制每个进程对该内存区域的访问权限(如只读、读写)。
因此,在操作系统的内存管理中,多个进程的页表中的虚拟地址可以映射到同一个物理地址,这在共享内存、共享库和写时复制等场景中非常常见。
使用多级页表是如何节省内存消耗的?
多级页表通过分层级组织页表结构,只在需要时为特定的页表部分分配内存,从而显著节省了内存消耗。
1. 背景知识:单级页表的内存开销
1.1 单级页表的概念
-
页表的作用: 在虚拟内存系统中,页表用于存储虚拟地址到物理地址的映射关系。每个进程都有自己的页表。
-
平坦的单级页表: 在单级页表中,整个虚拟地址空间的每个页面都对应一个页表项,即页表是一个线性的、一维的数组。
1.2 单级页表的内存消耗
-
示例(32位系统):
-
虚拟地址空间大小: 对于32位地址空间,虚拟地址空间为 2³² 字节(4GB)。
-
页面大小: 假设页面大小为4KB(2¹²字节)。
-
页表项数量: 需要 2³² / 2¹² = 2²⁰ 个页表项。
-
每个页表项大小: 假设为 4 字节。
-
总页表大小: 2²⁰ 页表项 × 4 字节/项 = 4MB。
-
-
问题:
-
对每个进程的内存开销大: 每个进程仅仅为了存储其页表,就需要占用 4MB 的内存。
-
虚拟地址空间的稀疏性: 实际上,大多数进程只使用了虚拟地址空间中的一小部分,大量的页表项对应的页面未被使用,但仍然需要为这些页表项分配内存。
-
2. 多级页表的原理
2.1 分层次的页表结构
-
多级页表: 将页表分为多个层次,例如二级、三级或四级。
-
思路: 将整个页表划分为更小的块(如页表页),只有在需要时才为这些块分配内存。
2.2 二级页表示例(32位系统)
-
一级页表(目录): 包含指向二级页表的指针。
-
二级页表(页表页): 包含实际的页表项。
-
地址分解:
-
虚拟地址: 32位,分为三个部分:
-
目录索引(前10位): 确定一级页表中的哪个项。
-
页表索引(中间10位): 确定二级页表中的哪个项。
-
页面内偏移(后12位): 确定页面内的偏移。
-
-
-
内存消耗计算:
-
一级页表大小: 2¹⁰ 项 × 4 字节/项 = 4KB。
-
二级页表: 仅在对应的地址空间区域被使用时才分配,每个二级页表大小为 4KB。
-
3. 多级页表如何节省内存
3.1 按需分配页表
-
稀疏地址空间: 进程的虚拟地址空间通常是稀疏的,即很多区域未被使用。
-
节省未使用区域的页表内存: 对于未使用的虚拟地址区域,对应的二级页表不需要分配,从而节省内存。
3.2 示例对比
-
单级页表:
- 总是需要分配完整的页表,大小固定(如 4MB)。
-
多级页表:
-
一级页表大小固定(如 4KB)。
-
二级页表按需分配:
-
如果进程只使用了虚拟地址空间的很小一部分,那么实际分配的二级页表页可能只有几个。
-
假设只使用了 4 个二级页表页,总页表大小为:
-
一级页表:4KB
-
二级页表:4KB × 4 = 16KB
-
总页表大小:4KB + 16KB = 20KB
-
-
-
-
节省效果:
- 相比于单级页表的 4MB,使用多级页表仅需 20KB,节省了大量内存。
4. 多级页表的进一步优化
4.1 多级页表的层级
-
更多层级: 对于64位系统,地址空间更大,需要更多的层级来管理页表。
-
示例:四级页表
- 适用于64位系统,分解虚拟地址为多部分,每一级索引一部分。
4.2 大页和超级页
-
大页(Huge Pages): 使用更大的页面大小(如2MB、1GB)来减少页表项数量。
-
优势: 减少页表层级和页表项数量,进一步节省内存。
5. 多级页表的工作流程
5.1 地址转换过程
-
虚拟地址分解: 将虚拟地址分解为多级索引和页面内偏移。
-
逐级查找:
-
从一级页表开始,根据索引找到对应的二级页表指针。
-
在二级页表中,根据下一级索引找到对应的三级页表指针(如果有)。
-
...直到到达最后一级,获取物理页面帧号。
-
-
计算物理地址: 将物理页面帧号与页面内偏移组合,得到物理地址。
5.2 空间效率的体现
-
未分配的页表: 如果某一级的页表不存在,表示对应的地址空间未被使用,直接判定为无效地址,无需继续查找。
-
内存节省: 未分配的页表层级无需占用物理内存。
6. 总结
-
多级页表通过按需分配页表,避免了为未使用的虚拟地址空间分配内存,显著节省了内存消耗。
-
在现代计算机系统中,多级页表是处理大规模虚拟地址空间(如64位系统)的关键技术,有效地解决了单级页表的内存开销问题。
-
同时,多级页表与 TLB(Translation Lookaside Buffer)等技术配合,兼顾了地址转换的效率和内存的空间效率。
简而言之,多级页表通过层次化的结构,仅为实际需要的虚拟地址空间分配页表,避免了对未使用的地址空间分配不必要的页表项,从而有效地节省了内存消耗。
本文来自博客园,作者:闹闹爸爸,转载请注明原文链接:https://www.cnblogs.com/wanglouxiaozi/p/18420612