Lec 20: Kernels and HLL
- Ref: https://github.com/huihongxiao/MIT6.S081/tree/master/lec20-kernels-and-hll-frans
- Preparation: The benefits and costs of writing a POSIX kernel in a high-level language (2018)
使用 C 语言实现 OS
优点
- 内存分配和释放的控制能力
- 几乎无隐藏的代码
- 直接访问内存的能力(可以读写 PTE 的标志位或设备寄存器)
- 极少的依赖(不需要运行时)
缺点
- 缓冲区越界
- 使用已释放的内存(use-after-free bugs)
- 线程共享动态内存(很难确定是否可以释放内存)
使用高级编程语言(HLLs)实现 OS
优点
- 提供了内存安全性(memory-safety). 上述 C 语言实现 OS 的内存漏洞不会存在.
- 类型安全
- 通过 GC 实现的自动内存管理
- 并发更友好
- 更好的抽象(接口, 类等面向对象语法更易写出模块化代码)
缺点
- 更差的性能(High Level Language Tax):
- 边界检查, 类型检查, 空指针检查
- 垃圾回收
- 与内核编程不兼容
- 无内存直接访问能力
- 不能集成汇编语言
- 受限的并发/并行(与内核所需的并行不一致, 比如在线程调度时线程的锁会传递, 在用户程序中不常见)
论文背景
- 缺少对内核中高级编程语言的优劣分析的工作
- 使用高级编程语言构建操作系统内核与 Linux 内核比较
选择的高级语言
Golang
- 容易调用汇编代码
- 静态编译语言, 性能更好
- 容易并发
- 容易静态分析
- 带有垃圾回收
Biscuit
使用 Golang 实现的宏内核操作系统
特性
- 支持多核 CPU
- 支持用户程序多线程
- 高性能日志文件系统
- 虚拟内存系统
- 完整的 TCP/IP 栈
- 高性能驱动: 网卡和磁盘驱动
用户程序
- 用户进程有自己的地址空间(页表)
- 用户/内核内存空间由硬件隔离
- 每个用户线程有对应的内核线程
- 内核线程由 go runtime 提供的 goroutine 实现
系统调用
- 用户线程将参数保存在寄存器中
- 用户线程执行
SYSENTER
指令进入到系统内核 - 控制权转移到内核线程
- 内核线程执行系统调用, 通过
SYSEXIT
返回到用户空间
实现的挑战
- 在裸机上运行 Go runtime
- 使用 goroutines 运行不同应用程序
- 在临界区中的设备中断处理
- 最困难: 堆耗尽
- 多数内核: 通过
malloc()
返回错误 - Go 调用
new
分配对象, 总是成功
- 多数内核: 通过
堆耗尽的解决方法
不合适的方法
- 报 panic
- 在内存分配器中等待 -> 可能导致死锁
- 检测并处理无内存时返回的空指针
最终解决方案
保留内存. 在程序执行系统调用前调用 reserve()
函数预先保留足够内存(保留的内存通过算法分析得到)
优点:
- 内核中没有检查
- 不用错误处理代码
- 没有死锁问题
选择 C 语言或 HLL 实现 OS
- 性能至关重要 -> C 语言 (提升至少 15%)
- 最小化内存使用 -> C 语言
- 安全至关重要 -> HLL
- 性能不那么重要 -> HLL