跳转至

HW3:从用户视角和内核视角理解内存管理

作业说明

  • 本次作业内容涵盖部分往年期末考试中的典型题型,主要考察大家对课堂内存管理相关知识的理解与掌握。同时,部分题目会结合 NexOS 的具体实现,引导大家将理论知识应用到实际系统分析中。
  • 实验相关的实现要求、代码路径和测试说明会在实验文档中单独介绍。
  • 由于本次作业发布时,理论课可能尚未讲完第七章的全部内容,同学们可以先完成已经学习过的部分。
  • 本次作业完成周期为 3 周,截止时间为 5 月 31 日 23:59 (周日)

作业目标

通过完成本次作业,你需要进一步巩固以下内容:

  • 虚拟内存与分页机制
  • 用户态地址空间与 sbrk() 堆边界变化
  • 用户态堆块管理与空闲链表
  • 页表查找与地址转换
  • TLB 对地址转换性能的影响
  • 请求分页与 page fault 的性能开销
  • 页面置换算法
  • 抖动(thrashing)的产生原因与影响

作业题

题目 1:用户态地址空间与 sbrk()

参考 Nexos 内核中的实现,p->sz 表示当前进程用户地址空间的大小,单位为字节,也就是当前堆顶之后的第一个地址;它不一定正好落在页边界上。sbrk 系统调用(sys_sbrk())会先保存旧的 p->sz 作为返回值,再调用 growproc(n)p->sz 调整为 p->sz + n。实际是否分配或释放用户页,不是直接由 n 对应多少字节决定,而是由新旧 p->sz 经过 PGROUNDUP() 后是否跨过页边界决定。

其中,n > 0 时通过 uvmalloc() 增加用户空间大小,n < 0 时通过 uvmdealloc() 减少用户空间大小。本题中的 60003000 均为十进制字节数,不是十六进制。

页大小为 4KB,即 PGSIZE = 0x1000

假设不考虑执行失败和超过用户地址上限的情况。某进程当前的地址空间上界为 p->sz = 0x5000,随后用户程序依次执行:

char *p = sbrk(6000);
char *q = sbrk(-3000);

请回答下面的问题:

  1. 一个用户进程的地址空间通常包含哪些主要区域?sbrk() 主要影响其中哪一部分?
  2. 两次 sbrk() 的返回值分别是多少?每次调用后新的 p->sz 分别是多少?
  3. 两次调用分别需要新分配或释放哪些用户页?请列出相关用户虚拟页的起始地址,并简要说明计算过程。

题目 2:用户态堆块分裂与空闲链表合并

malloc() / free() 通常在用户态维护堆块和空闲链表。假设堆块大小只包含 header 和 payload(注意没有footer),并采用如下规则:

  • HEADER = 0x10ALIGN = 0x10MIN_FREE_BLOCK = 0x20
  • malloc(n)needed = ALIGN_UP(n + HEADER, ALIGN),采用 first-fit,剩余空间不小于 MIN_FREE_BLOCK 时分裂;
  • malloc() 返回 block_start + HEADERfree(ptr) 释放 ptr - HEADER,释放后按地址顺序插入并合并相邻空闲块。

假设当前堆空间范围为:

[0x1000, 0x1240)

初始堆块布局如下,其中空闲链表为 0x1080(0x60) -> 0x1170(0x70)

块名 起始地址 块大小 状态 说明
A 0x1000 0x80 allocated -
B 0x1080 0x60 free 空闲块 F1
C 0x10e0 0x90 allocated 指针对应 p2 = 0x10f0
D 0x1170 0x70 free 空闲块 F2
E 0x11e0 0x60 allocated -

随后依次执行(注意其中的数字40为十进制字节数,不是十六进制):

void *p = malloc(40);
free(p2);

请回答:

  1. 计算 malloc(40)needed 和返回值,并说明是否发生空闲块分裂。
  2. 写出 free(p2) 后是否发生合并,以及最终空闲链表。

题目 3:基本概念解释

请解释下列内存管理相关概念:

  1. Segmentation fault(段错误)
  2. TLB(Translation Lookaside Buffer,地址转换后备缓冲)
  3. Page fault(缺页异常)
  4. Demand paging(请求调页)
  5. Thrashing(抖动)

题目 4:有效访问时间与缺页率分析

假设某系统使用 请求调页(demand paging) 技术,并采用 3 级页表。页表存放在内存中。

已知:

  • TLB 命中率为 96%
  • 内存访问时间为 80 ns
  • TLB 访问时间为 10 ns(假设 TLB 查找与内存访问串行进行)
  • 当发生 page fault 时:
    • 如果存在空闲页框,或者被替换的页面没有被修改,则缺页异常处理时间为 6 ms
    • 如果被替换的页面已经被修改,则缺页异常处理时间为 18 ms
  • 发生 page fault 时,被替换页面有 40% 的概率是已修改页面

请回答下列问题。

  1. 计算一次 page fault 的平均处理时间。

  2. 假设 TLB 不命中时,有 0.5% 的概率发生 page fault。计算 TLB 不命中时的平均访问时间。

  3. 计算系统的有效访问时间 EAT。

  4. 如果要求系统的有效访问时间不超过 150 ns,那么 TLB 不命中时最大可接受的 page fault rate 是多少?对应的系统总 page fault rate 是多少?

说明

为简化计算,本题中,发生 page fault 时,只计算缺页异常的处理时间,忽略触发异常前查询页表所耗费的时间,以及缺页处理完成后重新执行指令所需的普通访存时间。

题目 5:页面置换算法模拟

考虑如下页面访问序列:

7, 2, 3, 1, 2, 5, 3, 4, 6, 7, 7, 1, 0, 5, 4, 6, 2, 3, 0, 1
假设系统采用请求调页(demand paging),并且只有 3 个页框。请分别计算在以下页面置换算法下会发生多少次 page fault,并列出每次 page fault 时系统持有的页面状况:

  • LRU replacement
  • FIFO replacement
  • Optimal replacement

题目 6:页表查找与地址转换

NexOS 采用 RISC-V Sv39 分页机制,页面大小为 4KB,即 PGSIZE = 4096。在当前代码中,虚拟地址通过 PX(level, va) 拆成三级页表索引和页内偏移:

PX(2) PX(1) PX(0) Offset
9 bits 9 bits 9 bits 12 bits

Sv39 页表地址转换

图:Sv39 页表地址转换及PTE具体内容。

相关宏定义如下:

#define PGSHIFT 12    // 页内偏移位数
#define PXMASK 0x1FF  // 9 bits 掩码
#define PXSHIFT(level) (PGSHIFT + (9 * (level))) 
#define PX(level, va) ((((uint64)(va)) >> PXSHIFT(level)) & PXMASK)
#define PA2PTE(pa) ((((uint64)(pa)) >> 12) << 10)  // PA是物理地址,PTE是页表项,物理页地址转换为 PTE 中的物理页起始地址部分
#define PTE2PA(pte) (((pte) >> 10) << 12)
#define PTE_FLAGS(pte) ((pte) & 0x3FF)
#define PTE_COW (1L << 8)  // Copy-On-Write 标志位

某进程的根页表物理页起始地址为 0x0000000087000000,页表中与本题相关的 PTE 如下,其他未列出的 PTE 均为 0。表中的 PTE 数值按 NexOS 的 PA2PTE(pa) | flags 方式编码。

页表页 索引 PTE 内容 说明
root page table 0x080 0x0000000021c00401 指向下一级页表页 0x0000000087001000,仅设置 PTE_V
level-1 page table 0x0000000087001000 0x091 0x0000000021c00801 指向 level-0 页表页 0x0000000087002000,仅设置 PTE_V
level-0 page table 0x0000000087002000 0x145 0x000000002048d017 叶子 PTE,映射到物理页 0x0000000081234000,权限为 PTE_V \| PTE_R \| PTE_W \| PTE_U
level-0 page table 0x0000000087002000 0x146 0x00000000202af013 叶子 PTE,映射到物理页 0x0000000080abc000,权限为 PTE_V \| PTE_R \| PTE_U
level-0 page table 0x0000000087002000 0x147 0x0000000000000000 无有效映射

请回答下列问题:

  1. 虚拟地址 0x0000002012345678 对应的 PX(2)PX(1)PX(0)Offset 分别是多少?
  2. walk() 的查找顺序,虚拟地址 0x0000002012345678 能否通过 walkaddr() 成功翻译?如果可以,最终物理地址是多少?注意 walkaddr() 返回的是物理页起始地址,页内偏移需要再加回去。
  3. 虚拟地址 0x0000002012346abc 若在用户态执行写操作,NexOS 会如何处理?请说明原因,并指出它与 COW 写 fault 的区别。

提示

NexOS 中 MAXVA = 1L << 38,本题给出的虚拟地址均小于该上界。地址转换时,页内偏移保持不变;叶子 PTE 中的物理页起始地址可由 PTE2PA(pte) 取出,最终物理地址为:

Physical Address = PTE2PA(leaf_pte) | Offset