|
| 1 | +--- |
| 2 | +tags: |
| 3 | + - theory |
| 4 | +--- |
| 5 | + |
| 6 | +# Main Memory |
| 7 | + |
| 8 | +!!! note "Address Binding" |
| 9 | + - 源代码层面的地址通常用某个符号(变量名)表示 |
| 10 | + - Compiler 将符号绑定到可重定位的地址 |
| 11 | + - Linker 将可重定位的地址绑定到绝对地址 |
| 12 | + |
| 13 | + PIE: Position Independent Executable |
| 14 | + |
| 15 | + - PIE 类型的可执行文件中的地址都是相对地址,由 loader 加载到内存中后才确定绝对地址 |
| 16 | + |
| 17 | +Logical address: 逻辑地址,CPU 在寻址时使用的地址 |
| 18 | + |
| 19 | +Physical address: 物理地址,实际存储单元的地址 |
| 20 | + |
| 21 | +## Memory Management Unit (MMU) |
| 22 | + |
| 23 | +MMU 将逻辑地址转换为物理地址 |
| 24 | + |
| 25 | +最简单的方法是加入一个重定位寄存器,保存 offset. 物理地址 = 逻辑地址 + offset |
| 26 | + |
| 27 | +### Dynamic Loading |
| 28 | + |
| 29 | +所有的 routine 都以可重定位格式存放在磁盘上,只有某个 routine 被调用时,其代码才会被加载进内存中 |
| 30 | + |
| 31 | +可以提高内存空间的利用率 |
| 32 | + |
| 33 | +由应用程序自行设计,OS 可以提供相关库 |
| 34 | + |
| 35 | +### Dynamic Linking |
| 36 | + |
| 37 | +Static Linking: 编译时链接,将所有代码和数据都链接到可执行文件中 |
| 38 | + |
| 39 | +Dynamic Linking: 运行时链接 |
| 40 | + |
| 41 | +- 共享库(Shared Library): 编译时产生,包含多个 routine,可以被多个程序共享 |
| 42 | +- 桩(Stub) |
| 43 | +- 延迟绑定(Lazy Binding) |
| 44 | +- GOT(Global Offset Table) |
| 45 | + |
| 46 | +loader 加载程序时,先将代码加载到内存中,然后检查 metadata 得知该程序依赖哪些外部库,然后在 `LD_PATH` 下查找外部依赖库 |
| 47 | + |
| 48 | +## Memory Allocation |
| 49 | + |
| 50 | +有限的内存资源需要用来支撑 OS 与 Application 的运行,因此需要高效的分配方式. |
| 51 | + |
| 52 | +### Contiguous Allocation |
| 53 | + |
| 54 | +- 目的:限制程序对超出范围的物理地址的访问 |
| 55 | +- Base register: 基址寄存器,保存分配起始地址 |
| 56 | +- Limit register: 限制寄存器,保存分配的内存大小 |
| 57 | +- MMU 动态映射逻辑地址到物理地址 |
| 58 | + |
| 59 | +寻找可分配空间的算法: |
| 60 | + |
| 61 | +- First-fit: 从基址开始,找到第一个大小足够的空闲空间 |
| 62 | +- Best-fit: 从基址开始,找到大小最接近要求的空闲空间 |
| 63 | +- Worst-fit: 从基址开始,找到最大的空闲空间 |
| 64 | + |
| 65 | +Best-fit 和 Worst-fit 都需要扫描整块地址空间. |
| 66 | + |
| 67 | +以上三种方法具有内存碎片(Fragmentation)问题. First-fit 和 Best-fit 的利用率相对 worst-fit 高. |
| 68 | + |
| 69 | +- External Fragmentation: 外部碎片,分配的内存空间不连续 |
| 70 | +- Internal Fragmentation: 内部碎片,分配的内存空间大于程序需求的空间 |
| 71 | + |
| 72 | +### Paging |
| 73 | + |
| 74 | +分配给一个程序的物理地址可以是不连续的,这样可以解决外部碎片问题,但是仍然有内部碎片问题. |
| 75 | + |
| 76 | +将物理内存分成多个固定大小的块,称为 Frame |
| 77 | +将逻辑内存分成与 Frame 大小相同的块,称为 Page |
| 78 | + |
| 79 | +OS 需要跟踪所有的空闲 Frame |
| 80 | + |
| 81 | +通过页表来维护 Page 和 Frame 的映射关系 |
| 82 | + |
| 83 | +逻辑地址被分为页号(page number)与页内偏移(page offset)两部分 |
| 84 | + |
| 85 | +页号是页表的索引,对应某个 Frame 的编号 |
| 86 | + |
| 87 | +假设页表中页号 p 对应的 Frame 为 f, 则逻辑地址 $p + d$ 对应物理地址 $f + d$, 其中 d 为页内偏移 |
| 88 | + |
| 89 | +Frame Table: 标记物理内存中 Frame 的使用情况,通常会维护一个 Free Frame 链表. |
| 90 | + |
| 91 | + |
| 92 | + |
| 93 | +打开 paging 后,程序只能操控 virtual address,而不能直接操控 physical address. 因此,在打开 paging 前,我们需要让页表的虚拟地址与物理地址相等,以便打开 paging 后对页表的访问. |
| 94 | + |
| 95 | +## Page table |
| 96 | + |
| 97 | +Page Table 一般存储在内存中,寄存器 PTBR 指向 Page Table 的起始地址,寄存器 PTLR 存储页表的大小. |
| 98 | + |
| 99 | +我们每条访存指令实际上要访问两次内存: |
| 100 | + |
| 101 | +- 第一次从页表中查找逻辑地址对应的物理地址 |
| 102 | +- 第二次从物理地址读取数据 |
| 103 | + |
| 104 | +为了加快访问,我们引入 TLB(Translation Look-aside Buffer),作为页表的缓存 |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +TLB 需要保证一致性: |
| 109 | + |
| 110 | +- TLB 与内存中的 Page Table 的一致性 |
| 111 | +- 多核 CPU 中每个 CPU 的 TLB 的一致性 |
| 112 | + |
| 113 | +每个进程都有自己的页表,操作系统内核也有自己的页表. 上下文切换时,需要更新 PTBR 寄存器,使其指向新进程的页表. TLB 也需要更新,此时有两种方式: |
| 114 | + |
| 115 | +- 冷启动:进程切换时,将 TLB 全部清空,从页表中重新加载 |
| 116 | +- 将 TLB 中的每个项增加 ASID(Address-Space Identifier)用于区分进程 |
| 117 | + |
| 118 | +!!! definition "Effective Access Time" |
| 119 | + |
| 120 | + 有效访问时间,指考虑页表和 TLB 的情况下,一次访存的平均耗时. |
| 121 | + |
| 122 | + $$ |
| 123 | + \text{EAT} = (\text{TLB Hit Ratio} + \text{TLB Miss Rate} \times 2) \times \text{Memory Access Time} |
| 124 | + $$ |
| 125 | + |
| 126 | +虚拟内存对应的物理内存不一定已经被分配,在页表中不一定能查到。若查不到,就会产生缺页异常(Page Fault Exception),控制权转移到操作系统,操作系统会先对程序请求访问的地址进行合法性校验,若地址合法,则分配物理内存,并更新页表;若地址不合法,则抛出段错误(Segmentation Fault). |
| 127 | + |
| 128 | + |
| 129 | +### Memory Protection |
| 130 | + |
| 131 | +页表项一般包括: |
| 132 | + |
| 133 | +- PFN(Page Frame Number): Page 对应的物理内存中 Frame 的编号 |
| 134 | +- valid bits:表示该页表项存储的映射关系是否存在 |
| 135 | +- permission bits(权限位):用于控制程序对该 page 对应的物理内存的访问权限,取值由操作系统控制. 根据操作类型(Read、Write、Execution)、指令特权态(Kernel,User)等可设置不同的权限位. |
| 136 | + |
| 137 | +当程序访问某个 page 时,若在页表中查到了对应的页表项,则会根据权限位确定程序对该地址的访问是否合法,若合法,则可以访问该地址,否则会产生异常. |
| 138 | + |
| 139 | +### Page Sharing |
| 140 | + |
| 141 | +多个进程可以共享同一块物理内存,只需在进程各自的页表中设置对应的映射关系即可. 可能的应用场景包括: |
| 142 | + |
| 143 | +- 相同程序启动多个进程,代码段可以共享 |
| 144 | +- 进程间通信使用的共享内存 |
| 145 | +- 公共库(如 libc)的代码 |
| 146 | + |
| 147 | +Linux 在 x86-64 架构上的进程在内核态和用户态之间共享页表 |
| 148 | + |
| 149 | +## Structure of page table |
| 150 | + |
| 151 | +如果 Page size 取 4 KB,对于 32 位的系统,每个进程都需要 4 MB 的物理空间来存储页表,并且其中大部分页表项都不会被使用,造成空间浪费. |
| 152 | + |
| 153 | +为了减少页表占用的空间,有以下几种方法: |
| 154 | + |
| 155 | +### Hierarchical Page Tables |
| 156 | + |
| 157 | +将页表分成多个层次,高层次的页表存储低层次的页表所在的 Frame id. |
| 158 | + |
| 159 | + |
| 160 | + |
| 161 | +逻辑地址被分为三段: |
| 162 | + |
| 163 | +- page directory number |
| 164 | +- page table number |
| 165 | +- page offset |
| 166 | + |
| 167 | +32 位系统中,page directory number 占 10 位,page table number 占 10 位,page offset 占 12 位,共 32 位. |
| 168 | + |
| 169 | +一次寻址流程如下: |
| 170 | + |
| 171 | +- 首先从 outer page table 中根据 page directory number 找到 inner page table 对应的 Frame 的地址 |
| 172 | +- 再从 inner page table 中根据 page table number 找到 page 对应的 Frame 的地址 |
| 173 | +- 最后在 Frame 中根据 page offset 找到数据 |
| 174 | + |
| 175 | +### Hashed Page Tables |
| 176 | + |
| 177 | + |
| 178 | + |
| 179 | +### Inverted Page Tables |
| 180 | + |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | +## 64-bit RISC-V Linux Paging |
| 185 | + |
| 186 | +SATP(Supervisor Address Translation and Protection)寄存器用于设置虚拟内存分页模式,其结构如下: |
| 187 | + |
| 188 | + |
| 189 | + |
| 190 | +- MODE:分页模式 |
| 191 | +- ASID:地址空间标识符 |
| 192 | +- PPN:顶层页表的物理页号 |
| 193 | + |
| 194 | +SV39 是 RISCV64 的一种分页模式,采用三级页表 |
| 195 | + |
| 196 | + |
| 197 | +## Demand Paging |
| 198 | + |
| 199 | +Demand Paging 是一种动态内存分配技术,只有当某个 Page 被访问时,系统才会将其加载进内存. |
| 200 | + |
| 201 | +两种 Demand Paging 的策略: |
| 202 | + |
| 203 | +- Lazy swapper |
| 204 | +- Pre-Paging:提前将进程需要的部分或全部 Page 加载进内存 |
| 205 | + |
| 206 | +### Page Fault |
| 207 | + |
| 208 | + |
| 209 | + |
| 210 | +!!! definition "EAT in demand paging" |
| 211 | + |
| 212 | + Page fault rate $p = \dfrac{\text{Page Fault Times}}{\text{Page Access Times}}$ |
| 213 | + |
| 214 | + EAT in demand paging: |
| 215 | + |
| 216 | + $$ |
| 217 | + \begin{align*} |
| 218 | + \text{EAT} &= (1 - p) \times \text{Memory Access Time} + p \times (\text{Page Fault Overhead} + \\ |
| 219 | + &{} \quad \text{Swap Page Out Cost} + \text{Swap Page In Cost} + \text{Instruction Restart Overhead}). |
| 220 | + \end{align*} |
| 221 | + $$ |
| 222 | + |
| 223 | +- Major page fault: 页被访问,但没有在内存中,需要从磁盘加载 |
| 224 | +- Minor page fault: 页被访问,已经在内存中,但页表映射未建立 |
| 225 | + |
| 226 | + !!! example "Shared library for dynamic linking" |
| 227 | + 公共库(例如 `libc.so`)在第一个调用它的进程启动时被加载到内存中,之后其他程序再次调用该公共库时,不需要再次将其加载到内存中,而只需要在新进程的页表中设置映射关系即可. |
| 228 | + |
| 229 | +### Copy-on-Write |
| 230 | + |
| 231 | + |
| 232 | +### Page Fault Handler |
| 233 | + |
| 234 | +当需要将一个 page 加载进内存时: |
| 235 | + |
| 236 | +- 在磁盘上找到该 page |
| 237 | +- 找 Free Frame: |
| 238 | + - 如果存在空闲的 Frame,则分配给进程 |
| 239 | + - 如果不存在,使用页替换策略选择一个 victim frame,如果 victim frame 被修改过,则先将其写回磁盘,然后分配给进程 |
| 240 | +- 将 page 内容加载到新分配的 Frame 中 |
| 241 | +- 重启被中断的进程 |
| 242 | + |
| 243 | +#### Page Replacement |
| 244 | + |
| 245 | +当物理内存不足时,需要将某些 Page 从内存中换出到磁盘,以腾出空间. |
| 246 | + |
| 247 | +Dirty Bit:当 Page 被修改时,设置 dirty bit,表示 Page 内容与磁盘中的内容不一致,需要被优先替换 |
| 248 | + |
| 249 | +替换算法: |
| 250 | + |
| 251 | +- FIFO |
| 252 | +- LRU |
| 253 | +- Optimal |
| 254 | + |
| 255 | +与 Cache 中的 [Block replacement](./chapter2.md#block-replacement) 策略类似. |
| 256 | + |
| 257 | +LRU 实现: |
| 258 | + |
| 259 | +- Counter-based |
| 260 | +- Stack-based |
| 261 | + |
| 262 | +精确实现 LRU 的开销较大,我们可以采用 LRU 的近似实现:Additional-Reference-Bit Algorithm. |
| 263 | + |
| 264 | +Second-chance algorithm |
| 265 | + |
| 266 | +Page-Buffering algorithm |
| 267 | + |
| 268 | +#### Allocation of Frames |
| 269 | + |
| 270 | +Fixed allocation |
| 271 | + |
| 272 | +- equal allocation |
| 273 | +- proportional allocation |
| 274 | + |
| 275 | + |
| 276 | + |
| 277 | +## Non-Uniform Memory Access (NUMA) |
| 278 | + |
| 279 | + |
| 280 | +## Thrashing |
| 281 | + |
| 282 | +!!! definition "Thrashing" |
| 283 | + 如果一个进程分配得到的 Page 过少,导致 page fault 频繁发生,就会导致 CPU 利用率降低, |
| 284 | + |
| 285 | + |
| 286 | +## Kernel Memory Allocation |
| 287 | + |
| 288 | +内核态的内存分配与用户态不同,不能使用 page fault 进行动态内存分配, |
0 commit comments