Osv

Posted by Maxul's Technical Notes on April 28, 2018

题目: OSv - Optimizing the Operating System for Virtual Machines

作者: Avi Kivity, Dor Laor, Glauber Costa, Pekka Enberg, Nadav Har’El, Don Marti, Vlad Zolotarov

单位: Cloudius Systems

出版: USENIX ATC 2014

解决问题:

  1. 本文旨在解决云计算中IaaS中因为多余的抽象和保护带来的性能问题。
  2. 多年的云计算发展,VM的系统设计者意识到,系统一定要小、快且容易配置/管理,适合大规模部署。(除了最后一点,简直是嵌入式系统)
  3. 云上系统N宗罪:进程模型带来切换开销;HPC应用无法更快地处理数据;Guest的HAL(硬件抽象层)为鸡肋。

本文探索了未来云OS的发展可能,目标如下:

1. 能兼容运行Linux可执行文件;
2. 比Linux上运行快;
3. 启动快;
4. 提供更高效的API抽象;
5. 允许原生Java程序直接运行(移植了JVM);
6. 成为VM操作系统持续研究的平台,已经开源,社区鼓励实验和创新。

当前支持KVM、Xen、VMWare和VirtualBox;支持x86和ARM。

设计与实现:

遵循Exokernel中关于Library OS的设计原则:Hypervisor作为Exokernel,VM作为应用程序;每个VM有自己的LibOS,主要解决性能问题;将资源管理交给程序,并硬件直接暴露给应用,减小了软件层次,并用虚拟化隔离保证安全。

  • OSv中,如果程序互不可信,那么请运行在不同VM中。
  • 用户线程和内核使用同一个页表,避免模式切换。
  • 使用ELF动态链接器,支持Linux ABI,使系统调用作为普通的函数调用,同时避免用户到内核的参数拷贝。
  • 由于面向单个应用模型,因此不支持fork和exec语义。
  • 内核core完全是用c++11新写的,包括加载/链接器、内存管理、线程调度和同步机制,以及虚拟设备的驱动。
  • 虚拟设备包括:键盘、VGA、串口、SATA、IDE和HPET时钟。另支持半虚拟化设备如kvm-clock、virtio网卡和vmxnet3、virtio块设备和pvscsi。(注:半虚拟化本质上没有虚拟化,这些驱动是以用户态线程工作的,VM程序直接把半虚化的数据交给host驱动处理,抽象层次较低,性能一般较好)
  • 文件系统:使用ZFS,强调数据完整性,支持快照和容量管理,使用Adaptative Replacement Cache来管理,对recency和frequency有较好平衡。支持内存文件系统:ramfs和devfs、procfs。
  • stdio和math库来自musl libc,VFS来自Prex,ZFS来自FreeBSD,ACPI驱动来自ACPICA。

内存管理:

  • 使用虚拟内存管理,因为可以支持long模式寻址,同时现有POSIX应用喜欢用mmap语义来修改页权限。
  • 支持按需分页和mmap API,允许JNI直接管理物理内存。
  • 对于大块内存映射,使用巨页(2MB)满足需求,减小TLB misses。
  • 学习Linux,支持将小而离散的页组成巨页(即物理不连续),构成Transparent Huge Pages机制。
  • 因为面向单个应用,不提供换页(page eviction),直接使用host内存管理。

向Spinlock说不:

  • 对单处理器平台而言,在并发访问条件下,保护数据的方法是在该上下文时段禁止中断或屏蔽调度(注:单核上的锁就是防止调度的方案)。但多核下完全不行。因此对SMP平台,大家爱用自旋锁。
  • 在虚拟化环境下,自旋锁引入了“lock-holder preemption”问题:vCPU的停滞时间不固定,因为VM是必须退出到HV来调度CPU的,而VM自身持有spinlock(注:因为这里有两层调度,而互斥都利用了硬件锁,内层嵌套获得锁后,外层调度发生时就只能等待。本质上是锁的不可见性导致的。OS的mutex对于任何进程都可见,因为调度和互斥都是OS全局的,而虚拟化中的调度分为两级,内部对外不可见,外部对内透明,互斥锁的不当使用简直就是灾难!)有文献表明,极端情况下,会导致7%~99%的性能下降。
  • 现有的方案通常要修改HV和Guest,使锁跨层可见。但本文认为根本办法就是避免Spinlock的引入:放弃基于锁的算法。使用机器的原子操作(如compare-exchange、fetch-and-add)来实现lock-free操作,同时使用RCU技术(注:lockfree不是无锁,无锁是lockless,Stack Overflow上说,lockfree是waitfree,而lockless通常实现为消息传递,安全但未必高效)。考虑OSv复用了大量代码,要想实现lockfree算法难度很大,因此:1. 将中断处理设计为线程,使用mutex让没有得到IO的IRQ Handler休眠,而非spinlock busy-wait;2. mutex不使用spinlock;3. 调度器使用per-cpu结构来避免锁的引入(注:任何SMP系统都将per-cpu设置为私有变量,防止共享带来的锁操作,同时加速了调度过程,为实时系统所采纳!
  • 笔者注:之所以中断可以这么处理,应考虑QEMU/KVM的中断注入过程,KVM得到中断后注入VMCS,QEMU有个线程得到通知(eventfd),被唤醒再去通知Guest。所以都是异步的,同时为了提高CPU利用率,QEMU使用了coroutine来实现IO事件驱动模型。

网络通道:

  • Van Jacobson对TCP/IP协议栈做了流控优化,本文使用该设计来实现网络栈。主要问题在于,高负载下lock and cacheline contention。
  • 本文对锁做了merge,同时期待进一步的简化和改善。

调度:

  • 每个CPU有个run queue,每个任务有个fair criteria。scheduler根据这个公平值来调度。
  • 每个CPU还有个负载均衡线程,10秒唤醒一次,检查别的CPU上的队列长度,如果自己的队列比别人短,就将那个任务“偷”过来。告知对方任务“被偷”的方法是IPI处理器间中断,每个CPU维护一张bitmap(2维,和CPU总个数和队列长度有关),检查重调度的情况。
  • 支持抢占:用户甚至可以抢占内核线程
  • Tick-less:时钟中断浪费CPU周期,因为1) 虚拟中断比物理中断慢(软件模拟);2) VM-Exit。因此不依赖时钟源来决定调度,直接高精度时钟(文章没说,我猜是TSC);使用hysteresis(迟滞)和timer机制防止过频切换。
  • 公平:使用exponentially-decaying计算最近运行时间,不考虑更早的过去(注:0阶马尔科夫模型)。由于计算涉及浮点数,因此OSv允许在内核中使用浮点计算,和常规的内核设计指导完全相反
  • 扩展性:调度复杂度O(lgN),N为CPU数。
  • 效率:单地址空间,无模式切换;线程调度时不保存FPU寄存器,调用者自己保存。核间IPI开销太大,同时有VMExit,因此使用idle线程poll一段时间防止睡眠。

Beyond POSIX:

  • POSIX的抽象被公认为对性能有害。因此本文提供了新的API,并支持JVM,从而使OSv的实用性进一步增强。

评估:

实验环境:4-CPU 3.4GHz Intel Core i7,16GB RAM,SSD,Fedora 20,KVM,no iptables firewall

  • Memcached吞吐量是Linux的3.9倍

微观: 见表3-6。

相关工作:

  • Container使用OS级别的虚拟化,更为高效。但不适合IaaS平台,因为1) 隔离效果差;2) 不能使用Linux之外的OS。
  • Picoprocesses和本文工作很像,但Picoprocesses更多使用了host支持,如调度。OSv有自己的调度器。
  • MirageOS运行在Xen上,支持语言单一(OCaml)。
  • Libra运行在IBM的VM之上,使用Linux作为HV提供网络和文件系统。
  • ClickOS需要对Xen做大量修改,不支持SMP和TCP栈。

体会:

  • 本文的大量优化适合于实时系统。
  • OSv是我目前参与的体验最好的开源社区。
  • 关于移植OpenVPN到OSv中,我收到的答复是:OSv目前还没有tap设备的抽象,需要自己移植;但是BSD和GPL的许可证不同,后者的代码最好少复用。