ASOR-基于x86架构的虚拟机实现

1. 概述操作系统作为计算机硬件的管理软件直接运行在物理硬件之上,完全占有全部的硬件资源。自1965年以来,半导体硬件一直遵循摩尔定律快速发展。 硬件的计算能力不断增强,一套硬件上运行一个操作系统就显得有些浪费,这种现象在数据中心尤为明显。于是如何共享和整合硬件资源,提升硬件利用效率就成了一个需要解决的问题,虚拟化技术便应运而生。最近十几年,随着云计算的快速发展,虚拟化技术广泛应用于数据中心,已经成为云计算的核心技术之一。 虚拟化需要解决三个基本问题,共享,隔离和性能。在一套硬件环境上运行多个系统来共享硬件资源提升硬件利用效率是虚拟化的出发点,系统之间相互隔离保证安全是一种基本需求,操作系统运行在虚拟化的环境之上性能会有所下降,如何提升虚拟化的性能,一直伴随着虚拟化的发展。虚拟化经历了二进制翻译,半虚拟化,硬件辅助虚拟化的发展。现代CPU基本都已经支持硬件辅助虚拟化。那么,如何实现一个简单的硬件辅助虚拟化系统呢? 图1:虚拟化要解决的问题 ASOR, 是一个简单的虚拟化管理软件(VMM),用来帮助人们了解硬件辅助虚拟化的基本概念以及虚拟化管理软件的实现。虚拟化管理软件又被称作Hypervisor,有些文献将Hypervisor分成TYPE 1和TYPE 2两种类型,如图2所示。所谓TYPE 1类型也就是Hypervisor直接运行在硬件上,虚拟操作系统运行在Hypervisor之上,而TYPE 1类型的Hypervisor则运行在一个被称之为HOST的操作系统之上,其上再运行虚拟操作系统。其实,如果把TYPE 2中的HOST操作系统与hypervisor并成一个整体来看,这个整体也可以认为是一个TYPE 1的 Hypervisor。实现一个简单的TYPE 1类型的Hypervisor和实现一个简单的操作系统类似,前者比后者多了虚拟化的软件支持。从这个意义上,ASOR作为一个TYPE 1的Hypervisor也可以用来学习研究如何实现一个简单的操作系统。 图2:虚拟机类型 TYPE 1 vs TYPE 2 支持虚拟化的硬件架构用户态,内核态,特权指令,敏感指令 x86架构提供四个特权级别给操作系统和应用程序来访问硬件,从Ring 0 到 Ring 3。 其中Ring 0为最高级别,Ring 3为最低级别。计算机指令可以分成特权指令和非特权指令。操作系统内核代码在最高级别Ring 0上运行,可以执行特权指令直接访问硬件,而应用程序代码运则行在Ring 3上,只能执行非特权指令,这些指令不能做受控操作。当一个应用程序需要访问硬件时,需要通过执行系统调用,CPU的运行级别从Ring 3 转换到 Ring 0,并跳转执行内核代码来完成硬件访问,访问完成后再从Ring 3切回Ring 0。这个过程被称作用户态和内核态切换。计算机指令还可以划分成敏感指令和非敏感指令。所谓敏感指令,指的是那些会改变系统硬件资源配置的指令或其执行的行为依赖硬件资源配置的指令。一个可以虚拟化的硬件架构需要满足的条件是,敏感指令集必须是特权指令集的子集,如图3 所示。如果存在敏感指令是非特权指令,这样的硬件就存在虚拟化空洞,是不可虚拟化的硬件架构。这种硬件虽然不能完整支持辅助虚拟化,但仍然可以通过软件方法来实现虚拟化。 图3:可虚拟化架构 早期的硬件没有辅助虚拟化的功能。虚拟化通过修改操作系统来实现,也就是对执行的非特权敏感指令进行修改,来填补虚拟化空洞来实现虚拟化,即所谓的半虚拟化。随着硬件的发展,硬件辅助虚拟化已经成为主流。 2. ASOR的启动流程计算机上电启动后开始执行BIOS的代码,BIOS程序首先进行硬件自检。如果硬件出问题,主板会根据具体问题发出不同类型的蜂鸣声。如果没有问题,BIOS会找到启动顺序排位第一的存储设备,读取该设备的起始扇区的512个字节。如果这512个字节的最后两个字节是_0x55_和_0xAA_,表示这个设备可以用于启动,如果不是,表示设备不能用于启动,则从下一个存储设备查找。在找到可启动的设备后,控制权交给bootloader, 例如我们这里用的GRUB。通过GRUB的界面选择ASOR启动。之后程序开始执行_x86/cstart.c_(对于32位系统)或_x86/cstart64.c_(对于64位系统)。asor.img是一个虚拟硬盘,用于方便在在qemu中调试ASOR。在系统中安装qemu-kvm后,运行make qemu便可看到如 图4所示的ASOR启动界面。 图4:ASOR启动流程 以x86/cstart.c为例,其中start标记的地址是AOSR启动代码的入口地址。从start启动代码的入口地址到ASOR C语言的main函数asor_entry的流程如图5所示。 图5:从start入口到asor_entry 3. 分段与分页x86体系结构中CPU看到的地址是逻辑地址,经过分段单元后逻辑地址被转换为线性地址,又叫虚拟地址,最后分页单元再将虚拟地址转换为物理地址,如图6。操作系统为了隔离内核空间与用户空间,通常都会定义四个段,这些段相互重叠都从地址0起始,覆盖整个线性地址空间。在特权级0上定义内核空间代码段和内核空间数据段,在特权级3上定义用户空间代码段和用户空间数据段。以此来将操作系统与用户程序隔离开来,然后再通过为每一个进程创建不同的页表来将不同的进程彼此隔离开来。图7展示了分段与分页是如何结合起来运行的。 图6:逻辑地址,线性地址(虚拟地址),物理地址 图7:分段与分页机制 多级页表在早期32位时代广泛使用的是两级页表模式如图8图9所示,而现代64位 CPU多采用四级页表模式如图10,图11,图12所示。为什么需要多级页表呢?主要是为了节约内存。我们可以做一个简单计算。假设我们在一个32位的系统使用一级页表,每页占_4K_大小,那么为了寻址_4G_空间,我们需要_1M_个页表项,如果每个页表项占4个字节,那么我们需要一共_4M_的大小来存储页表。假如我们有100个程序,每个程序使用不同的页表,那么则一共需要_400M_来存储页表。而如果使用二级页表,第一级页表需要_4K_大小,可以存储_1K_个页目录项,每个页目录项指向一个_4K_大小第二级页表,则我们一共需要4K+4M的大小。这岂不是比一级页表使用的_4M_要多了吗?事实上,得益于计算机理论中的局部性原理,第二级页表并不需要全部都存在,可以按需分配,4K+4M只是最坏的情况,最少的情况下我们只需要4K+4K内存,这样100个程序在最少的情况下只需要_800KB_,这比一级页表的_400M_要小得多。对于100个程序而言,两级页表需要的内存介于800KB ~ 400K+400M之间,大多数情况下远小于_400M_。 此外,使用多级页表的好处还在于,两级及其以后的页表可以不存放在内存而存放于磁盘,需要的时候再交换回内存。 ...

June 1, 2020 · 1 min · jiezi