LEC16 操作系统架构
主题: 内核应该做什么?
- 内核应该提供哪些系统调用?
- 内核应该提供哪些抽象?
取决于上层应用的需求和程序员的品味
- 没有一个标准的完美答案.
- 关于答案,有很多选择和讨论.这方面的论文也很多.
- 本章节主要介绍这方面的思想,而不是具体的实现机制.
传统方法
- 大的抽象概念
- 宏内核实现,比如Unix, Linux, xv6.
栗子: CPU的传统处理方式
- 内核赋予每个进程独立的"虚拟"CPU,各进程间互不影响.
- 影响: 2.1 为了对进程透明,中断必须保存/恢复所有寄存器. 2.2 定时器中断强制执行透明地上下文切换
- 优点: 简单,将许多底层细节抽象化.
- 缺点: 过度抽象,比如进程调度等.可能导致性能下降.
栗子: 内存的传统处理方式
- 每个进程有自己私有的独立地址空间.
- 优点: 2.1 进程不需要关心其他进程如何使用内存. 2.2 因为私有,所以进程不需要关心安全问题 2.3 内核有很大的自由空间来通过虚拟内存机制实现各种tricks
- 缺点: 实现用户空间层面的tricks,变得困难.
传统内核巧妙实现的内存技巧
- 延迟物理内存分配--使得分配大块内存快速完成,同时节约了实际物理内存.
fork时,写时拷贝,快速创建进程.- 内存页面交换:
3.1 进程要求分配的内存大于实际的物理内存?
3.2 将暂时不用的内存页写入到磁盘中,同时将PTE标记为invalid.
3.3 当进程需要访问已经被换出的内存时,MMU将会触发
page fault. 3.4 内核执行中断处理程序,将被换出的内存再次载入物理内存,并将PTE标记为valid. 3.5 最后,返回用户程序执行.整个过程对于进程而言是透明的. 3.6 这套机制能够生效的原因在于,进程在一段时间内,只会访问一段内存. - 在可执行文件和共享函数库之间,共享物理内存.
- 通过共享内存,在内核和用户空间之间,实现零拷贝IO.
传统内核背后的哲学要点: 抽象
- 可移植的接口 1.1 文件file抽象,隐藏了磁盘硬件的所有寄存器细节. 1.2 内存地址空间,隐藏了MMU的细节.
- 简单的接口,将复杂性隐藏
2.1 通过文件描述符fd和
read()/write()来执行IO操作,而不是操作具体的设备. 2.2 访问地址空间时,对进程而言,磁盘换页是透明的. - 抽象帮助内核管理资源 3.1 通过进程的抽象,内核才可以实现进程调度. 3.2 通过目录和文件的抽象,内核才可以管理硬件磁盘布局.
- 抽象可以增强内核的安全性 4.1 文件访问的权限 4.2 进程及其独立内存地址空间
- 很多抽象,比如文件描述符,虚拟地址空间,文件名,进程号等.帮助内核实现了虚拟化,隐藏,撤回,调度等等功能.
抽象对于应用程序开发人员来说是一个胜利
应用程序开发人员希望花时间构建新的应用程序功能,他们想让操作系统处理所有其他事情,所以他们想要强大,可移植和足够快的内核.
传统内核通常是宏内核
- 整个内核就是一个大的可执行文件.
- 对于各子系统而言,互相调用非常方便.因为没有明显的边界.比如分页和文件系统缓存等.
- 所有内核代码均运行在特权级,并没有内部安全限制.
传统内核的缺点
- 大: 复杂,有缺陷,容易不可靠(原则上,实际上不太可靠)
- 过于抽象,导致性能下降.比如上下文切换时,并不需要保存全部寄存器.
- 抽象可能不正确.比如需要等待一个并非子进程的进程执行.
- 抽象可能妨碍用户程序的优化.数据库可能比内核文件系统更擅长在磁盘上布局
B-Tree文件.
另一个可选的架构: 微内核
- 主要思想: 将大多数操作系统功能转移到用户空间服务进程
- 内核可以尽量小,主要是IPC进程间通信的实现.
- 目标: 3.1 简单的内核实现应该更快和更可靠 3.2 服务进程易于替换和修改
- 实现示例: Mach3, Minix
- JOS是微内核micro-kernel和外内核exo-kernel的混合.
微内核的优点
- IPC进程间通信会更快
- 独立的服务迫使内核开发人员考虑模块化
- 好的IPC非常适合新的用户态服务,例如X server
微内核的缺点
- 内核不会太小: 需要实现进程和内存管理
- 可能需要很多次IPC,总的来说很慢
- 很难将内核分成许多服务进程! 这使得跨服务优化进程变得更加困难,所以服务进程往往很大,这不是一个巨大的优点.
微内核已经取得了一些成功
- IPC/Service理念广泛应用于OSX,Android等,但是对于传统的内核服务来说并不多,主要用于(大量)新服务,架构设计为客户端/服务端.
- 一些嵌入式操作系统,有强烈的微内核风格.
外内核(1995)
论文
- 操作系统社区投入了巨大的关注
- 有很多有趣的想法
- 描述了一个早期的研究原型
- 1997年,
SOSP的论文实现了更多的想法
外内核概览
- 哲学理念: 取消所有的抽象,尽量暴露硬件细节,让应用程序按需定制.
- 外内核不会提供地址空间,管道,文件系统,TCP等抽象.
- 应用程序需要直接使用外内核暴露的 MMU, 物理内存, 网卡, 时钟中断
- 外内核的会导致应用程序的可移植性很差,但是对硬件的控制力会增强.
- 每个App的libOS实现抽象,可能是POSIX的内存地址空间,fork(), 文件系统,TCP等.每个应用程序有自己定制的libOS,也有自己的抽象.
- 为什么?由于精简和简单,内核可能会更快.由于应用程序可以根据自己的实际情况定制libOS,所有应用程序也可能更快.
外内核面临的挑战
- 外内核将什么资源暴露给libOS?
- 如果要在用户空间实现写时拷贝fork(),内核需要实现哪些API?
- libOS可以共享吗?安全性怎么保证?
- 没有宏内核提供的抽象,我们可以安全地共享lib么?
- 通过定制libOS,应用程序能够得到足够的好处么?
外内核内存接口
暴露的资源
内核将会暴露物理内存页和MMU中VA->PA之间的映射关系.
应用程序到内核的API
pa = AllocPage()
TLBwr(va, pa)
Grant(env, pa) -- to share memory
DeallocPage(pa)
内核到应用程序的API
PageFault(va)
PleaseReleaseMemory()
外内核需要做什么?
- 跟踪保存进程和物理内存之间的对应关系.
- 确保应用程序只为自己拥有的物理内存,创建虚拟内存映射关系.
- 当系统内存耗尽时,决定要求哪个应用程序放弃物理内存页,这个应用程序可以自行决定放弃它的哪个内存页.
虚拟内存相关的典型使用场景
应用程序希望分配100MB的不连续数组,这里延迟分配的实现和之前作业中的实现不同.
PageFault(va):
if va in range:
if va in table:
TLBwr(va, table[va])
else:
pa = AllocPage()
table[va] = pa
TLBwr(va, pa)
jump to faulting PC
通过外内核的内存管理,我们可以完成一些有意思的操作
数据库喜欢在内存中缓存磁盘页面.
在传统内核中会遇到一些困难,假设操作系统支持在磁盘上按需分页.
- 如果数据库缓存了一些数据,而操作系统需要一个物理页面,操作系统可以换出包含缓存磁盘块的数据库页面.
- 但那会浪费时间: 如果数据库知道缓存数据被换出,它会释放相关物理物理,然后从数据库文件中重新读取.
微内核场景
- 微内核需要从某些应用程序取回物理内存.
- 微内核向DB进程发送了一个
PleaseReleaseMemory()上调函数. - DB进程挑选了一个暂时不用的物理页面,执行
DeallocPage(pa). - 或者,DB挑选了一个已被修改过的脏页,将其中的内容保存到DB文件系统,然后再执行
DeallocPage(pa).
外内核CPU接口
不再提供透明的进程切换
- 当内核需要运行app时,内核向上调用app.
- 当内核需要app停止运行时,内核向上调用app.
- 这些向上调用都是会调用到app的固定地址,对app不再是透明的.
当app正在运行,且时间片耗尽,时钟中断触发时
- CPU中断app运行,陷入内核.
- 内核通过
please yield向上调用,跳转回app - app保存当前上下文,即所有寄存器
- app向下调用
yield().
当内核再次运行app
- 内核通过
resume向上调用,跳转到app - app恢复之前保存的上下文.
外内核无需保存用户态的寄存器
- 这会使系统调用/陷阱/上下文切换更快
通过外内核的CPU管理,app可以完成一些有趣的操作
假设时间片在持有锁时,耗尽
- 我们可能并不希望app进程在持有锁时,被调度.因为这样可能会导致其他apps阻塞.
- 那么在app执行
please yield()的时候,可以先完成临界区的工作,从而释放锁.
快速IPC
传统内核中的IPC
- pipe或者socket
- 抽象为一种消息传递
- 实际实现为内核中的两块buffer
- 速度比较慢:
4.1
write+read+read+write,需要8次切换上下文
外内核Aegis中的IPC
yield()可以传入一个目标进程参数- 内核向上调用目标进程
- 几乎相当于直接跳转到目标进程的某条指令处
- 内核只允许跳转到目标进程的特定地址
- 内核不使用寄存器,所以可以使用寄存器来保存参数和返回值.
- 目标进程通过
yield()返回 - 更快: 只有4次切换,更少的寄存器保存/恢复,没有阻塞的
read().
注意
- IPC只有目标进程中执行.
- TODO
底层性能总结
- 主要是关于快速系统调用,陷阱和向上调用.
- 系统调用的速度非常重要!
- 缓慢的系统调用速度,鼓励复杂的系统调用,不鼓励频繁的调用.
- trap处理不会保存大部分寄存器.
- 从内核到app,快速的向上调用.(内核不需要处理上下文恢复)
- 受保护的IPC调用,只能跳转到指定的地址.
- 将一些内核的数据结构映射到用户空间(page table, 寄存器保存)
主要思想--关于抽象
- 自定义抽象对性能是一个提升,app需要底层操作来完成自定义抽象
- 大量内核操作可以在用户空间实现,同样保证安全性和共享
- 保护机制并不需要要内核实现大的抽象.
- 可以保护没有内核管理地址空间的进程页面,1997年的论文对文件系统进行了全面的开发.
- 地址空间抽象可以分解为: 物理页面分配和va->pa映射
外内核的结果
- 目前很少使用
- 以后很难说...
总结预期
- 外内核是一个研究项目.
- 研究的成功,将会带来影响力. 2.1 改变人们的思考方式 2.2 帮助他们看到新的可能性 2.3 人们可能会借鉴部分思想
- 研究成功不代表会有很多用户 3.1 通常研究很少能够转化为产品,即使研究成果很好
外内核的影响
- 相较于1995年,UNIX对用户空间暴露了更多的底层控制接口.这对于某些应用来说,非常重要.
- 人们思考很多关于内核扩展性的问题,比如Linux内核支持动态加载内核模块.
- 库操作系统经常被使用,比如
unikernel.