MIT 6.S081 Lecture 8: Q&A labs

The good seaman is known in bad weather.

6.S081第八课,针对 Lab page table 做一些指导,内容比较简单。

Part 1: USYSCALL

陷入内核是有开销的,所以 OS 一般在进程与内核之间共享一些内存,以加速系统调用。这些可加速的系统调用,需要符合一些条件:

  1. Must have no side-effects. 没有副作用就不会影响OS的状态,简单理解就是只有读一些信息。
  2. Returns constant value while process runs. 系统调用的返回值在进程运行期间是常量,这意味用户进程无法改变这些值。
  3. But value can change after entering kernel (e.g., ticks). 但在进入内核后,这些值可能改变。抽象理解,这些 sped-up 系统调用读取的信息,对用户进程而言是只读的,只有内核态会修改。

一些可加速的例子有,

  • Getpid() – constant value, doesn’t change.
  • Uptime() – constant until next tick. Each tick triggers a kernel interrupt, can update value.
  • Fstat() – maybe possible, not likely worth it, too much state.

Xv6的加速技巧,是通过在用户空间的页表映射一个 USYSCALL 页,这个特殊页的权限位是 PTE_R | PTE_U 并且内核页表中也映射该页。由内核来修改,进程只读 USYSCALL 页获取信息,即可不用陷入内核。

How does Linux use USYSCALL?

Linux 的加速系统调用方案是 VDSO(Virtual dynamic shared object)。首先提供只读的共享内存区,然后内核向用户进程提供代码,这些代码在共享区里获取数据。

VDSO是一种内核机制,用于将精心选择的一组内核空间 routines 导出到用户空间应用程序,这样应用程序就可以在进程内调用这些内核空间 routine ,而不会有通过系统调用接口调用这些相同的内核空间 routines 时的性能开销(固有的从用户模式切换到内核模式的开销)。

VDSO使用标准机制来链接和加载,即标准的可执行和可链接格式(Executable and Linkable Format, ELF)格式。VDSO是在用户空间中分配的一个内存区域,它暴露了一些内核功能。VDSO是动态分配的,通过地址空间布局随机化提高了安全性,并支持4个以上的系统调用。一些C标准库(如glibc)可能提供VDSO链接,如果内核不支持VDSO,就会进行传统的系统调用。VDSO有助于减少简单内核 routines 的调用开销,它也可以作为一种方法来选择一些计算机架构(如IA-32)上的最佳系统调用方法。与其他方法相比,这种导出 routines 的一个优点是可以提供带属性记录格式的调试(Debug With Attributed Record Format, DWARF)。实现通常在动态连接器中使用 hooks 来查找VDSOs。

VDSO是为了提供 vsyscall 特性,同时克服原来 vsyscall 的局限性:

  1. 静态分配的内存很少,只允许4个系统调用;
  2. 每个进程中使用相同的地址应用程序二进制接口(ABI),这降低了安全性。p.s. 这个安全问题已经通过模拟一个虚拟系统调用得到了缓解,但是模拟又引入了额外的延迟。

Linux 的一些 VDSO 方法,

  • clock_gettime()
  • getcpu()
  • getpid()
  • getppid()
  • gettimeofday()
  • set_tid_address()

Part 2: Printing a page table

通过 PTE 的 permission bits 识别 PTE 类型是目录还是叶节点,以打印页表,比较简单。

Part 3: Access bits

任务是为用户空间识别 accessed 页。硬件的 page walker 在搜寻页表时,会根据读写设置权限位,

  • PTE_A: Was the page accessed (read or write)
  • PTE_D: Is the page dirty (only write)

How does Linux use access bits?

一般 access bits 可用于同磁盘换页。磁盘换页有许多策略,以用到 access bits 的CLOCK 算法为例,策略是间隔地扫描页,检查哪些页 PTE_A 被标记,被标记的清空标记,最近没有被标记的页移动到磁盘。这个过程中,位 PTE_D 用于判断是否要写磁盘(磁盘上数据已老)。Linux 将上述过程对用户空间透明化,不暴露任何信息。

How could you detect page access without access bits?

如果不用 PTE_A 可以检测对页的读写吗?可以,通过 PTE_V 触发页错误实现。设置PTE均为 invalid 后,读写页时触发页错误,在 fault handler 中记录读写的页,然后设置 PTE_V 位。这样实现很慢,也没啥额外意义,能用 PTE_A 何乐而不为。

Conclusion

关于 Lab 的问答内容很简单。所以主要记录了一些根据LAB的扩展知识,如 Linux 通过 VDSO 暴露一些系统 routines 给用户以减少开销,Linux用页的 PTE_A 决策向磁盘换页的算法。值得一提的是 generational GC 也会使用 access 信息,这里就不继续展开了。

Reference

  1. MIT 6.S081 2021
  2. Virtual dynamic shared object
  3. Page replacement algorithm: Clock