CMU 15-213 CSAPP. Ch9. Virtual Memory

CMU 15-213 CSAPP (Ch1~Ch3)
CMU 15-213 CSAPP (Ch5~Ch7)
CMU 15-213 CSAPP (Ch8)
CMU 15-213 CSAPP (Ch9)
CMU 15-213 CSAPP (Ch10)
视频链接
课件链接
课程补充
该课程使用 64位 编译器!

Ch9. Virtual Memory

9.1 Address spaces

将内存看成数组,物理地址 ( physical address ) 是相对某个存储单元的偏移量;
简单微控制器直接使用物理地址即可完成简单任务,但无法胜任手机、电脑、服务器上的复杂任务,必须将 主存 ( main memory ) “虚拟化” ( virtualization );

虚拟化 的概念在计算机领域中被广泛应用,通过 介入资源访问过程,将资源以不同的视角呈现给资源的使用方,例如:

  • 调试 malloc 使用的 Ch7.2.4 库插桩技术
  • 硬盘控制器 将 柱面、磁轨、扇区 以 “serious logical blocks” 的形式呈现给 kernel Ch6.1
  • CPU 向内存请求数据;
  • MMU ( Memory Management Unit ) “拦截” ( intercept ) 请求,并将 虚拟地址 转换 ( Address Translation ) 为 物理地址 ( Physical Address );
  • 内存收到请求后返回 物理地址对应单元内 的数据;
  • CPU “幻觉” 自己可以访问所有的地址空间,独占了整个内存;
    在这里插入图片描述
  • 概念
    • 【地址空间】地址的集合;
    • 【线性地址空间】连续 非负数 的集合,eg { 0,1,2,3 … };
    • 【虚拟地址空间】 N = 2 n N = 2^{n} N=2n 个虚拟地址的集合,eg { 0,1,2,3 … 2 n − 1 2^{n}-1 2n1 };
      进程内部地址,系统中所有进程相同;
    • 【物理地址空间】 M = 2 m M = 2^{m} M=2m 个物理地址的集合,eg { 0,1,2,3 … 2 m − 1 2^{m}-1 2m1 };
      DRAM 的物理容量;
    • 通常 虚拟地址空间 > 物理地址空间;

9.1.0 为什么需要 虚拟内存

  • “虚拟内存” 技术的优点:
    • 物理内存的使用效率更高,根据 局部性原理,一个进程只需要一小块 DRAM 来缓存硬盘上一小部分常用的数据或者指令;
    • 降低内存管理的复杂程度,每个进程内存的 虚拟地址空间布局 相同,映射到后物理地址空间后,分散各处;
    • 隔离、保护 私有地址空间,一个进程无法干涉、访问其他进程的地址空间,用户程序无法访问内核地址空间;

9.1.1 VM as a Tool for Caching

  • Virtual Memory 可以抽象成 硬盘上 连续 字节组成的数组;
  • 数组中的元素以 为基本单位,缓存在 DRAM 中,被称为 VP ( virtual page );
  • Page Size = 2 p 2^p 2p Bytes,Virtual Memory Size = 2 n 2^n 2n Bytes,Page Number = 2 n 2 p − 1 \frac{2^n}{2^p}-1 2p2n1

在这里插入图片描述

  • 通常,大部分虚拟页都 没有被分配 ( unallocated, 类似 VP 0 ),没有 存储在磁盘上;

DRAM 比 CPU 中的 SRAM 慢 10 倍,磁盘 比 DRAM 慢 10,000 倍;

  • 磁盘 比 DRAM 慢太多,导致 未命中惩罚 ( miss penalty ) 巨大,设计整个 VM 系统的初衷,就是为了减轻 miss penalty;巨大的 miss penalty 导致 DRAM Cache 的结构(organization)与 Ch 6.Cache Memories 存在巨大差异:

    • Page Size 通常 4KB,通过选项 ( x86 ) 可以扩大到 4MB,远大于 CPU Cache Blocks Size ( 64 Bytes ) ,单次未命中的惩罚不可调控,只能通过 更大的 Page ( or Block ) Size 换取 更高的命中率,均摊(amortize)未命中惩罚(时间);
    • 直接映射缓存 拥有 较多的 set,但每个 set 只有一个 Line,发生 conflict miss 的概率相对较大,而 硬盘 与 DRAM 置换页面的代价巨大,因此直接映射缓存 并不适用于 DRAM Cache;
    • 全相联 只有一个 Set,但 Line 越多 ( associativity 更大 ),conflict miss 的概率越小,更适合 DRAM 缓存( 经验结论:在多数情况下,一个 Line 就能满足进程所需的 Locality ):
    • Cache Memory 由硬件对 set 中的 line 并行搜索(parallel search),由于硬件比较快且并行执行,时间能接受;
    • 换页算法(replacement algrithms)高度复杂,由操作系统 软件 实现,以确保极高准确性 — 虽然计算需要花费一定的时间,但也远小于 miss 换页 带来的惩罚;
    • 映射函数 需要能够 随机寻址 ( 记录所有 VA ⇒ PA 的映射关系 );
    • 使用 写回 ( write-back ),尽量少写磁盘;
  • 页表(Page Table)

    • 是由页表项(Page Tables Entries)组成的数组,存放在内存中,由 kernel 维护,属于进程上下文的一部分( 每个进程都有自己的 Page Table );
    • 以下图为例:
      • VP 0、5 为空,尚未分配物理空间,页表项 PTE 0 = null,PTE 6 = null,valid 置 0;
      • VP 1、2、4、7 依次缓存到 PP 0、1、3、2 上,页表项 PTE [1] = & PP0、…、PTE [7] = & PP2,valid 置 1;
      • VP 3、6 尚在硬盘中,页表项 PTE [3] = VP3’s 硬盘位置,PTE [6] = VP6’s 硬盘位置,且 valid 置 0;
        在这里插入图片描述
  • 页命中 ( page hit )

    • 访问 已经 被缓存在 DRAM 物理内存 中的页内数据;
  • 页缺失 ( page fault )

    • 访问 DRAM 当前没有 缓存的页内数据;
    • 请求页缺失后,缺失页 被置换到 DRAM 中,采取这种缺页处理方式的 内存管理系统 称为 “请求分页系统” ( Demand paging );
  • 举例
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 页创建(Page Allocate)

    • malloc 请求分配一大块内存,这些内存原本只存在于 Virtual Address Space,(硬盘上)并没有物理实体,如上图 PTE0 = NULL;
    • malloc () 调用 sbrk () 函数,向 Page Table 增添一条记录(如 PTE5 = VP5 in Swap,Swap 文件也是持久化在硬盘上的),此时 valid 还是置 0;
    • 直到 进程 访问到 malloc 新增页面内的 Virtual Address,才触发 Page Fault,在 DRAM 中寻找合适的 PP 分配给 VP;

关于 sbrk 和 allocation 的关系,详见 Ch11.1.1.Dynamic Memory Allocation

  • Locality works all the time !
    • 直觉上,这种 VM 系统非常低效,需要频繁的在 硬盘 与 内存 间进行拷贝,每条指令的执行都需要经历至少一次 page mapping;
    • 事实上,因为 locality,进程总时倾向于访问 同一部分活跃的 VP,这些 VP 组成的子集 被称为 working set;
    • 如果 内存 能够存下 program 的 working set,program 的性能就会很好 ( 冷启动时除外 ) ;
    • 但实际上,操作系统 通常是多任务的,如果 全部进程的 working set 之和 超过 内存大小,将导致 颠簸 ( thrashing each other ),连续不停的缺页换页,性能崩坏(meltdown);

9.1.2 VM as a Tool for Memory Management

在这里插入图片描述

  • Kernel 为每个进程维护独立的 Page Table,使得每个进程都能拥有 独立的 虚拟地址空间;
  • 进程的 VP 被 “七零八落” 的缓存映射到 DRAM 的 PP 上,但 效率很高;
  • 历史上曾经 为每个进程 提供一小块 “自留地“(“a chunk of the physical address space”),进程只在自己的一小块物理内存中加载运行,但这会导致很多问题:
    • 比如,新增进程就必需从其他进程那 ”抢走一小块“,或者由操作系统保留部分内存,创建新进程时再分割,空间利用率奇低,内存上全是空间碎片 ;
    • 比如,A.bin 文件中的函数 Afunc () 引用了共享库 B.so 中的 Bfunc (),在没有 VM 条件下,Afunc () 并不知道 Bfunc () 的物理地址,就需要 重定位 所有的引用(references);这样,生成 excutable file 的过程,只有编译,没有链接( linking );
  • 虚拟地址 简化 了内存管理的复杂度,most flexible scheduling freedom:
    • 任意 VP 可能被缓存在任意的 PP 上;
    • 同一 VP 可能先后被缓存到不同的 PP上(被从 PP0 换出到硬盘上,又从硬盘被 换回到 PP1);
    • 多个 VP ( 如 共享库文件 libc.so ) 可以被映射到同一个 PP 上;
  • 虚拟地址 简化了 linking 和 loading
    • 所有 program 的虚拟地址空间 布局 ( layout ) 都相同,Linker 以 .text segment 起始于 0x400000 为前提,在编译链接阶段,就能将 0x400000 作为统一的偏移基地址,relocate 多个 obj 文件中的 references(即链接过程),得到可执行文件 a.out;
    • loading 过程,execve () 在 Page Table 中添加 .data section 和 .text sections 对应的 PTE,PTE 指向硬盘位置,置为 invalid,等待程序运行,触发 page fault,再将 PTE 对应的 VP 缓存到内存中;所以 loading 本质上是 demand paging ;

9.1.3 VM as a tool for memory protection

  • x86-64 Intell 的 Virtual Address 仅 48 bit,剩余高位补0(用户空间)或 补1(内核空间);
  • PTE 通过 权限位 控制 program 对页面的访问:
    • 比如 高位全“1” 的 Proc i 的 VP2 和 Proc 的 j 的 VP1,只能在 supervisor mode 下访问;
    • EXEC bit 仅在 x86-64 上实现,x86-32 不存在,能够防止 代码注入;正因如此,骇客们才放弃栈代码注入,转而使用 ROP 攻击,program 会 ret 到没有执行权限的页 发生 crash;
    • 每个访问虚拟地址的请求,都会经过 MMU 的权限位校验;

9.1.4 VM Address Translation

  • PTBR ( 在 x86 架构中对应 CR3, Controll Register 3 ) 指向 页表 的起始物理地址;

  • 将 Virtual Address 拆成高低两部分:

    • 高位部分定义为 VPN(Virtual Page Number),作为页表项 PTE 的索引,mapping 到 PPN(Physical Page Number);
    • 低位部分定义为 VPO(Virtual Page Offset) ,VP 与 PP 的 Page Size 相同,VPO 直接用作 PPO(Physical Page Offset);
  • 经过上述 translation,得到 PA(Physical Address)= PPN + PPO;
    在这里插入图片描述

  • Page Hit

    • 按照 step 1~5 的顺序完成 data fetching;
    • 即便 VP 命中,也需要 step 2 获取 PTE,Step 4 根据 PA 获取数据 ,共计两次内存访问;
      在这里插入图片描述
  • Page Fault

    • step 2,valid = 0,MMU 触发 Page Fault Exception;
    • step 5,handler 挑选 VP,如果在内存中被修改过(被标记为 dirty),则写回硬盘;反之(clean)则直接抛弃;
    • step 6,handler 将缺失的页 缓存 到内存,再到 Cache,同时更新 PTE;
    • step 7,handler 结束返回原进程,控制流 重新执行 Page Fault 的命令,这次 hit;
      在这里插入图片描述
  • Cache 与 VM 的集成(第一次优化):

    • MMU 向 L1 Cache 访问 PTE(应该是 PTBR + PPN 得到的 物理地址),如果 miss 则再由 L1 Cache 从 内存 读取存放有 PTE 的 block(Ch 6.4 Cache Memories);
    • MMU 获得 PTE 中 VPN 对应的 PPN,拼接 PPO,得到 PA,再向 L1 Cache 请求 PA 单元内的数据;
      在这里插入图片描述
  • 硬盘 与 VM 的三种交互

    • .text 或 .data section 内的 Page 只读,不存在 dirty 回写硬盘的情况;
    • C语言 open() 创建一个 user level 的文件,C 库 allocate 一段 虚拟地址 映射到这个文件,Virtual Memory System 分配对应的 PP 映射到 这段 虚拟地址,一旦文件被写入,PP 记为 dirty,带 文件被 close 或 文件映射的 PP 被 evict 时,PP 就被回写到硬盘上,user level 的文件得以保存;
    • 如前所述,为节省 物理内存 资源,非活跃进程的部分 VP ( Virtual Page ) 被 evict 到硬盘上 交换分区(swap area 或 swap file)内;
  • Speed Up Translation with TLB

    • 如果使用原有的 L1 Cache ⇒ L2 Cache ⇒ Memory 体系缓存页表,PTE 可能被 .data 或 .text section 挤到内存中,导致缓存 miss;即便 没有 miss,每次地址转换也会花费至少一个 L1 Cache 级别的延时;
    • 地址转换的缓存 使用独立硬件(第二次优化),Translation Lookaside Buffer(TLB):
      在这里插入图片描述
      • TLB 属于 MMU,专用于缓存 PTE;
      • 与 Ch 6.4 相同,将 VPN 分割为 索引 TLB set 的 index 域,和 索引 TLB line 的 tag 域;
      • TLB 中 set 的数量决定了 index 域的位数,记为 TLBI;其余位作为 tag 域,记为 TLBT;
        在这里插入图片描述
  • 2-Level Page Tables

    • 通常 x86-64 虚拟地址空间 48 bits,4 KB( 2 12 2^{12} 212 B)大小的页面,8 bytes 的 PTE,则页表总大小为: 2 48 2 12 ⋅ 8 = 2 39 b y t e s = 0.1 T B \frac{2^{48}}{2^{12}} \cdot 8 = 2^{39} bytes = 0.1 TB 2122488=239bytes=0.1TB,显然,内存放不下,需要对页表 再次 “分级缓存”第三次优化);
    • 以 x86 为例,4-Byte PTE,4KB 页面 ,代码段 及 数据段 存储在 VP [0] ~ VP [2047] 中;其后 6 KB 未使用、未分配,用作 Gap;其后 1023 个未分配 VP;其后 VP [9215] 用作栈空间;
      在这里插入图片描述
    • 每个 Level 2 级页表均为 1024 × 4 B = 4 K B 1024 \times 4 B = 4 KB 1024×4B=4KB;program 共计 9216 个 VP,理论需要 9216 1024 = 9 \frac{9216}{1024} = 9 10249216=9 张 Level 2 级页表;而 9 张 Level 2 级页表的 物理 基地址,显然可以被一张 Level 1 级 页表 存下;
    • 如果 program 所有 VP 均非空,Level 2 级 页表 将占用 9216 × 4 = 36 K B 9216 \times 4 = 36 KB 9216×4=36KB,此时 多级缓存 退化为 直接缓存(Level 1 级 页表 的存在多浪费了 9216 1024 × 4 B = 36 B \frac{9216}{1024}\times 4 B = 36 B 10249216×4B=36B 的空间);
    • 如果 program 存在 6 K Gap + 1023 unallocated 的 Pages,实际只需要 3个 Level 2 级 页表,占用内存 1024 × 4 × 3 = 12 K B 1024 \times 4 \times 3 = 12 KB 1024×4×3=12KB,算上 Level 1 级 页表 1 K × 4 = 4 K B 1K \times 4 = 4 KB 1K×4=4KB,多级页表共占用 16 KB;
    • 可见,多级页表 主要 利用了 数据稀疏性,Level 1 级 PTE = null,就可以省略大部分未被使用的 Virtual Page 对应的 Level 2 级 页表的空间;
      在这里插入图片描述
  • Multi-Level Page Tables

    • VPN k 是第 k 级页表的 index,检索到的 PTE 指向第 k+1 级页表的物理地址;
    • L1 级 页表 覆盖了全部地址空间,L2 则覆盖了当前 指令 前后的大部分地址空间(spacial locality),这些 上层页表 在 TLB 中常驻,保证了 MMU 地址转换 的高效;

早期,Intell 8086 只有 16 位地址总线,为了加大寻址空间,4 bit 的 Segment Register 诞生了(熟悉《微机原理》的朋友可能知道 Code Segment Register 和 Instruction Pointer Register 和 段间寻址 相关的内容),CS:IP 组合成 20 bit 的物理地址;这也许就是 VM 的原型;

9.2 Systems

9.2.1 Memory System Example

  • 分析一个简单系统:
    • Virtual Address 14 bit,Physical Address 12 bit,Page Size 64 B,TLB Page Table 容纳 16 个 PTE,4-way associative,则 16 / 4 = 4 个 set,TLBI 只需要 2 bit,其余 2 bit 作 TLBT;
      在这里插入图片描述
    • TLB 中缓存的页表如下(这里为方便描述,展示了 Set 列,实际不存在);
      在这里插入图片描述 * 内存中缓存有 program 的全部页表(这里为方便描述,展示了 VPN 列,实际不存在); 在这里插入图片描述
    • L1 Cache,Fully Associative,1 set,16 lines,4-byte block(这里为方便描述,展示了 idx 列,实际不存在),Cache Offset 只需要 2 bit,Cache Index 这里取 4 bit,剩余 6 bit 给 Cache Tag;
      在这里插入图片描述

Cache Tag 并非必须等于 PPN,但 Intell 发现,相等 可以带来 “好处”;

  • 场景一 TLB 与 L1 Cache 均 hit
    在这里插入图片描述

    • step1,CPU 请求 虚拟地址 VA = 0x03D4 上的数据:
    • step2,VA = 0x03D4,PPN = 0x0F,TLBI = set = 0x03;
    • step3,TLBT = tag = 0x03,valid = 1,hit;
    • step4,取得对应 PPN = 0x0D;
    • step5,MMU 向 L1 Cache 请求 物理地址 PA = PPN + VPO = 0x0354 上的值;
    • step6,PA 分解得 CI = set index = 5;
    • step6,PA 分解得 CT = tag = 0x0D,valid = 1,hit,数据所在的 block 缓存在 L1 Cache 中;
    • step7,PA 分解得 CO = 0x00,block 中检索 offset = 0 的内存单元,得到 0x36;
    • step8,L1 Cache 直接将 物理地址 0x0354 上的值 0x36 返回给CPU;
    • 整个过程没有访问内存,效率极高;
  • 场景二 TLB 与 L1 Cache 均 miss
    在这里插入图片描述

    • step1,CPU 请求 虚拟地址 0x0020 上的数据;
    • step2,VA = 0x0020,PPN = 0x00,TLBI = set = 0x00;
    • step3,TLBT = tag = 0x00,但 valid = 0,页表项 miss;
    • step4,根据 PTBR,MMU 向内存请求 物理地址 PA = PTBR + VPN 上的值,valid = 1,VPN 对应的页面无需 从硬盘取出 或 新建页面;得到 PTE = 0x28;
    • step5,刷新 TLB 中 set = 0x00,tag = 0x00 对应的 PTE(0x28,valid=1),PPN = 0x28;
    • step6,MMU 向 L1 Cache 请求 物理地址 PA = PPN + VPO = 0x0A20 上的值;
    • step7,PA 分解得 CI = set index = 8;
    • step8,PA 分解得 CT = tag = 0x28,L1 Cache 上 set = 0x08 但 tag = 0x28 不存在,miss,数据所在的 block 需要从内存中获取;
    • step9,L1 Cache 从内存中取得对应 block,更新 set 内 tag = 0x28 的 block 单元;
    • step9,PA 分解得 CO = 0x00,block 中检索 offset = 0 的内存单元,得到 0x00;
    • step10,L1 Cache 直接将 物理地址 0x0A20 上的值 0x00 返回给CPU;
    • 整个过程共访问 2次 内存;

9.2.2 A Real x86-64 Memory System

来看看真实的 Intell Core i7 VM 是如何实现的;
在这里插入图片描述
整个 Package 就是 一块 封装后的处理器芯片,芯片内封装有 4 个核 ( core ),每个核都有:

  • Register
  • 指令存取解码器
  • L1 级数据缓存 ( L1 d-cache )、L1 级指令缓存 ( L1 i-cache )
  • L2 级 ( 公用 ) 缓存
  • MMU
  • L1 级 TLB 数据缓存 ( L1 d-TLB )、L1 级 TLB 指令缓存 ( L1 i-TLB )
  • L2 级 ( 公用 ) TLB 缓存
  • QuickPath Interconnector

除 core 以外,芯片上还封装有:

  • L3 级 ( 多核公用 ) 缓存
  • DDR3 内存控制器
  • 等…

访问 L1 级缓存 需要约 4个 时钟周期,访问 L2 级缓存 需要约 10个 时钟周期,访问 L3 缓存需要通过 核间 / 片外 ( off-chip ) 链接,因此耗费的时间增加到 30~50 个时钟周期;

把 L2 Cache 所需的晶体管,全堆到 L1 Cache 上,岂不是更好?
视频里,老师解释 “没有 L2 miss 的代价会很大”,我不是很认同,L1 + L2 的总大小没变 ( 事实上还变大了,L1 不再是 L2 某一块的 copy,L1 和 L2 可以全部用来缓存 L3 中的块,而不仅仅 L2 缓存 L3 中的块 ) miss 的概率没有发生变化,但 miss 的代价由 L2 降低到了 L1 的水平,所以理论上把 L2 用到的晶体管全部分配给 L1 会更快?不太确定 …
stackoverflow 查了一下,主流的说法还是 成本 和 工艺 问题,又 “大” 又能 “高速多路读写” 的 L1 不好造;

Intell 系统中,VA 长度 48 位,页面大小 4 K B = 2 2 × 2 10 = 2 12 4 KB = 2^{2} \times 2^{10} = 2^{12} 4KB=22×210=212,故 VPO 长 12 位,VPN 长 48 − 12 = 36 48-12=36 4812=36 位;

之前简单模型中 PPN = CT 只是巧合,但这里 不 是 巧 合 !
Intell 为了提升效率,将 VPO ( = PPO = CI + CO ) 直接给到 Cache 并立即查找 CI 对应 Set Index,CO 对应 Block Offset,等 CT 到达 Cache 后立刻就能确认 Line Tag,地址转换 与 Cache 缓存查找 能够并行了 !!
CI + CO 一共才 12 位,也因此导致 L1 Cache 比较小。。。

  • Level 1~3 Page Table Entries
bitfunction
PPresent ? 页表 是 (1) / 否 (0) 存在于内存当中
如果不在,剩余的位 则表示 页表在磁盘中的位置
R/W页表 ( 包括下级 ) 的访问权限是 只读 还是 读写
U/SUser or Supervisor (kernel) mode
用户程序是否可访问 ( 即是不是内核页 )
操作系统的 安全 不可侵犯 靠的就是这一位
WT采用 “直写” 还是 “回写” 的缓存策略 ( 基本都是 “回写” )
AReference bit
MMU 读/写 时置位,软件复位
PS页面大小 4 KB 还是 4 MB ( 仅1级页表有这一位 ).
PTPBA页表 物理 基地址
下级页表起始物理地址的最高 40 位
( 页表大小 4KB,可以推算出 Core i7 支持的物理地址是 40 + 12 = 52 位 )
XD页表内取出的指令是否可执行
还记得 “注入 攻击” 么?
  • Level 4 Page Table Entries
bitfunction
DDirty,MMU 写时置位,软件复位
PPBA页表 物理 基地址,同前 40 bit

每当进程切换,操作系统 便将 L1 页表的物理地址 写入 CR3 寄存器中,实现地址空间切换

9.2.3 Virtual Address Space of a Linux Process

了解 Linux 如何实现虚拟内存 将帮助我们 彻底弄懂 execve 和 fork 的原理;
前文 Ch 7.2.4 所示

ContentAddress
Kernel ( different region )
0xFFFF xxxx xxxx xxxxx
进程 “上下文”,每个进程都不一样,存放如
ptables、task、mm struct 和 内核栈 等数据结构;
Kernel Physical Memory ( identical region )
0xFFFF xxxx xxxx xxxxx
“都一样”区间,1:1 映射到 DRAM 物理内存
为内核提供了一种 便捷访问物理地址的方式
Kernel Code and Data ( identical region )
0xFFFF xxxx xxxx xxxxx
“都一样”区间,存放 内核 代码 及 数据
Gap内核 与 用户空间 之间的 留白
User stack
(created at runtime)
向下生长
“大堆栈”
( for huge object )
在请求超大块空间时出现
推测 malloc 请求的大块 和 小块时 使用的分配策略 不同
% rsp ⇒ rsp 指向栈顶,入栈 即 rsp 值减小
Memory-mapped region for shared libraries通过 内存映射 共用的 .so 库文件
% brk ⇒ 全局变量 brk 指向 堆顶
由内核维护,用于内核跟踪 堆顶 的位置
Run-time heap
(create by malloc)
向上生长
Read/write data segment
.bss
未初始化的变量
Read/write data segment
.data
symtab 记录值 初始化 的变量
Read-only code segment
.init .text .rodata
原封不动拷贝到内存
Unused0x00~0x3FFFFF
Linux 进程保留 4M 空间
64 位 的 Core i7 虚拟地址只有 48 位,若 48 位最高位 = 1,则 以符号位扩展的方式 填充余下的高 16 位 ( 全=1 ); 因此 Kernel 分布在 0xFFFF xxxx xxxx xxxxx 的虚拟空间内;反之,用户空间虚拟地址的高 17 位均为 0,即 0x0000 xxxx xxxx xxxxx

即便运行内核代码,VA 到 PA 的 “地址转换” 也在进行;访问 Kernel Physical Memory 的第 0 个字节 就是访问 DRAM 内存的第 0 个字节;

Linux 的进程由 多个 Area 组成

  • 每个 Area 由对应的 vm_area_struct 管理;
    • vm_prot 控制该 Area 的读/写 权限;
    • vm_flags 控制页面 进程间共享 还是 私有,默认私有;
  • 多个 vm_area_struct 节点组成链表;
  • mm_struct 中的指针 mmap 指向 链表的表头;
  • task_struct 中的指针 mm 指向 mm_struct;
  • mm_struct 中的指针 pgd ( Page Global Directory Address ) 指向一级页表, 进程被调度时 kernel 将 pgd 的值拷贝到 CR3 中实现不同进程虚拟地址空间的切换;

现实中, vm_area_struct 会组成 Red-Black tree,而非 List;

  • Linux Page Fault Handling,三个 缺页中断 ( segment fault ) 的原因
    • 虚拟地址空间内,没有分配 或 创建过该页,kernel 遍历 vm_area_struct 链表后,发现没有对应节点;
    • ( Linux ) kernel 检查 vm_area_struct 的 vm_flags 标志位,发现 指令 尝试向 只读Area ( 如 .text ) 执行写操作;
    • 内存 没有缓存,需要 从磁盘换页;

9.2.4 Memory Mapping

  • VM 的每个 Area 都拷贝 ( 初始化时只读 ) 自硬盘上的 Regular File 文件的 某一部分 ( 该过程即 内存映射 );
  • 被写入的页面 ( dirtied ),发生换页时被保存到 磁盘上,swap file 中;
  • 通过 Page Fault 申请分配一个 全 “0” 的物理页 ( demand-zero page ),但硬盘上没有其对应的实体文件,这种又被称作 匿名文件 ( Anonymous File ),但本质是一个 dirty 的页面;

以 Apache ( Nginx 类似同理 ) 为例,通常这类服务器进程为了提高并发性能,会 fork 出多个完全相同的 子进程 ( worker ) 同时处理外部请求,这些相同的子进程会用到相同的 库文件 ( libxx.so ),每个 库文件 被 load 到一块物理内存上 ( 只有一个物理实体 ),被映射到多个 子进程 的不同虚拟地址上
就需要开辟出一块 共享缓存 实现

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值