现代开源操作系统的基本组成:
功能 | 链接地址 |
---|---|
文件管理和设备管理 | 虚拟文件系统(无持久存储的文件系统),以proc和sysfs为例 |
内存管理 | 现代操作系统的内存管理原理:以Linux2.6.x.x为例 |
系统启动原理 | Linux 2.6.4.30 Arm Architecture源码深度剖析—基于《ARM Linux内核源码剖析》 |
虚拟地址空间 | 什么是虚拟地址空间?从架构视角来解释 |
动态链接 | 一篇长文带你深析Linux动态链接的全过程 |
从上篇:Intel® 64 and IA-32 Architectures Software Developer’s Manual 读后感可以看到Intel
对于虚拟化技术还是很看重的,因此此篇借助Linux平台来总结一下虚拟化技术。
虚拟化技术与容器技术的差别:KVM内核虚拟化技术以及Docker容器技术的原理浅谈?
下一篇:多核系统原理与解决方案
虚拟化主要分为以下几个部分:CPU
虚拟化,内存虚拟化,中断虚拟化和设备虚拟化。其中内存虚拟化和中断虚拟化属于软件虚拟技术,而CPU
虚拟化和设备虚拟化属于硬件虚拟化技术,特别是设备虚拟化,和真实存在的设备差别不是太大。
结合
Intel
手册中的虚拟化技术来看
陷入和模拟模型(Trap and Emulate
):虚拟机的用户程序和内核都运行在用户模式(一般情况下是用户程序运行在用户模式,内核运行在系统模式),这称为特权级压缩(Ring Compression
),在这种方式下,虚拟机的非特权级治疗直接运行在处理器上,当运行特权级指令时,将触发处理器异常,陷入VMM中,由VMM
代理虚拟机完成系统资源的访问。
随后Intel
开发了VT
技术支持虚拟化,为CPU
增加了VMX
,即Virtual-Machine Extension
。此时CPU
提供了两种运行模式:VMX Root Mode
和VMX non-Root Mode
,每一种都支持ring0-ring3
。VMM
运行在VMX Root Mode
,除了支持VMX
,VMX Root Mode
和普通的模式并无本质区别。VM
运行在VMX non-Root Mode
,Guest kernel
运行在VMX non-Root Mode
的ring0
中 。
VT
技术上处理器上的作用,不必关心其细节。
处于VMX Root Mode
的VMM
可以通过执行CPU
提供的虚拟化指令VMLaunch
切换到VMX non-Root Mode
,因为这个过程相当于进入Guest
,所以通常也被称为VM entry
。当Guest
内部执行力敏感指令,比如某些I/O
操作后,将触发CPU
发生陷入的动作,这个过程相当于退出VM
,因此被称为VM exit
。然后VMM
对Guest
的操作进行模拟。
如同一个CPU
可以分时运行多个任务一样,每个人物有自己的上下文,由调度器质调度时切换上下文,从而实现同一个CPU
同时运行多个任务,在虚拟化场景下,同一个物理CPU
分时运行着Host
和Guest
,在不同的模式间按需切换,因此,不同的模式也需要保存自己的上下文。每个Guest
都有自己的一个VMCS
实例,当物理CPU
加载了不同的VMCS
时,将运行不同的Guest
。
当运行Guest
模式时,Guest
用户空间的系统调用直接陷入Guest
模式的内核空间,而不是陷入Host
模式的内核空间
对于外部中断,因为需要由VMM
控制系统的资源,所以当处于Guest
模式的CPU
收到外部中断后。则触发CPU
从Guest
模式退出到Host
模式,由Host
内核处理外部中断,处理完中断后,再重新切入Guest
模式。(此时不必产生VM exit
)
对于VCPU
,VMM
使用一个线程来代表这个VCPU
实体
KVM
是内核模块。
陷入和模拟:当Guest
访问系统资源时,就需要退出到Host
模式,由Host
作为统一的管理者代为完成资源访问。当虚拟机进行I/O
访问时,首先需要陷入Host
,VMM
中的虚拟磁盘收到I/O
请求后,如果虚拟机磁盘镜像存储在本地文件,那么就代为读写本地文件。在访问设备I/O
内存映射的地址空间时,将触发页面异常,但是这些地址对应的不是内存,而是模拟设备的I/O
空间,因此需要KVM
介入,调用相应的模拟设备处理I/O
对于被动性触发的陷入,只需要Guest
将CPU
资源让给Host
即可。
常用外设访问方式包括
PIO
和MMIO
实质是创建影子页表或者EPT
等页表进行与物理内存的映射关系。
对于软件虚拟的中断芯片而言,中断信号只是一个变量,从软件模拟的角度就是设置变量的值了。如果KVM
发现虚拟中断芯片有中断请求,则向VMCS
中VM entry control
部分的VM-entry interruption-information filed
字段写入中断信息,在切入Guest
模式的一刻,CPU
将检查这个字段,就如同检查CPU
管脚 ,如果有中断,则进入中断执行过程。
其中中断的逻辑为
PIC
发送中断请求,虚拟PIC
记录虚拟设备的中断信息,与物理的中断过程不同,此时并不会触发虚拟PIC
芯片的中断评估逻辑,而是在VM entry
时进行CPU
处于睡眠状态,则唤醒虚拟CPU
,即使虚拟CPU
对应的线程进入了物理CPU
的就绪任务队列CPU
开始运行时,在其切入Guest
前一刻,KVM
模块将检查虚拟PIC
芯片,查看是否有 中断需要处理。此时,KVM
将触发虚拟PIC
芯片的中断评估逻辑Guest
处理的中断,则将中断信息注入VMCS
中断字段VM-entry interruption-information
Guest
模式后,CPU
检查VMCS
中断中断信息CPU
将调用Guest IDT
中相应的中断服务处理中断
一般对于中断的控制是由中断控制器8259A
可编程中断控制器或者APIC
可编程高级中断控制器来操作的,因此也要对这两个芯片进行虚拟化。
硬件虚拟化的支持
虚拟中断芯片是在用户空间实现的,但是中断芯片密集地参与了整个计算机系统的运转过程,因此,为了减少内核空间与用户空间之间的上下文切换带来的开销,后来,中断芯片的虚拟是在了内核空间。为了进一步提高效率,Intel
从硬件层面对虚拟化的方方面面进行了支持。
virtual-APIC page
:Intel
在CPU
的Guest
模式下实现了一个用于存储中断寄存器的virtual-APIC page
。在Guest
模式下有了此状态,后面Guest
模式下还有了中断逻辑,很多中断行为就无需VMM
介入了,从而大大减少了VM exit
的次数。Guest
模式下的中断评估逻辑。Intel
在Guest
模式中实现了部分中断芯片的逻辑用于中断评估,当有中断发生时,CPU
不必再推出到Host
模式,而是直接在Guest
模式下完成中断评估。posted-interrupt processing
:在虚拟中断芯片收到中断请求后,会将信息保存在虚拟中断芯片中,在VM entry
时,触发虚拟中断芯片的中断评估逻辑,根据记录在虚拟中断芯片中断信息进行中断评估。但是当CPU
支持者Guest
模式下的中断评估逻辑后,虚拟中断芯片可以在收到中断请求后,将中断信息直接传递给处于Guest
模式下的CPU
,由Guest
模式下的中断芯片的逻辑中Guest
模式中进行中断评估,向Guest
模式的CPU
直接递交中断。设备虚拟化:系统虚拟化软件使用软件的方式呈现给Guest
操作系统硬件设备的逻辑。设备虚拟化先后经历了完全虚拟化,半虚拟化,以及后来出现的硬件辅助虚拟化,包括将硬件直接透传给虚拟机,以及将一个硬件从硬件层面虚拟成多个子硬件,每个子硬件分别透传给虚拟机等。
设备透传
设备虚拟化如果采用软件模拟的方式,则需要VMM
参与进来。为了避免这个开销,Intel
尝试从硬件层面对I/O
虚拟化进行支持,即将设备直接透传给Guest
,Guest
绕过VMM
直接访问物理设备,无需VMM
参与I/O
过程,这种方式提供了最佳的I/O
虚拟化性能。
Intel
最初采用的方式是Direct Assignment
,即将整个设备透传给某一台虚拟机,不支持多台VM
共享同一设备。为例使不同设备制造商的设备可以互相兼容,PIC-SIG
制定了一个标准:Single Root I/O Virtualzation and Sharing
,简称SR-IOV
。SR-IOV
引入了两个新的Function
类型,一个是Physical Function
,简称PF
;另一个是Virtual Function
,简称VF
。一个SR-IOV
可以支持多个VF
,每一个VF
可以分别透传给Guest
,如此,将从硬件角度实现了多个Guest
分享同一物理设备。
每个VF都有自己独立的用于数据传输的存储空间、队列、中断及命令处理单元等,但是这些VF的管理仍然在VMM
的控制下,VMM
通过PF
管理这些VF
。虚拟机可以直接访问这些VF
,而无须再通过VMM
中的模拟设备访问物理设备。PF
除了提供管理VF
的途径外,Host
以及其上的应用仍然可以通过PF
无差别地访问物理设备。对于那些没有VF
驱动的Guest
,虚拟机依然可以通过SR-IOV
设备的PF接口共享物理设备。
数据传输方式
(1)MMIO
:Memory-Mapped I/O
:将外设的内存,寄存器映射到CPU
的内存地址空间中,CPU
访问外设如同访问自己的内存一样。
(2)PIO
:Port I/O
:使用IN
和OUT
指令对指定端口进行读取数据。
我不喜欢网络,也没有探究网络虚拟化,跳过了
此处要注意,
VT-d
技术实现的是CPU
的虚拟化和内存的虚拟化,中断和设备并无安全算上一种虚拟化。
概念看多了,略显枯燥,来看实操
QEMU
源码下载:qemu源码官网下载
Linux
源码下载:Linux内核源码官网下载
虚拟机(Virtual Machine,VM
)是进程,进程可以看作是一组资源的集合,有自己独立的进程地址空间以及独立的CPU
和寄存器,执行程序员编写的指令,完成一定的任务。系统虚拟化是在云计算的支持下得到了快速的发展,Intel
和AMD
都相继在CPU
硬件层面增加了虚拟化的支持。
随后,Qumranet
利用Intel
的硬件虚拟化技术在Linux
内核上开发了KVM Kernel Virtual Machine
,KVM
架构精简,与Linux
内核天然融合,得以很快进入内核。KVM
现在已经是一个非常成功的虚拟化VMM
,称为云计算的基石。
QEMU
早期是通过把源架构指令通过TCG Tiny Code Generator
引擎转换成目标架构指令。
Intel
和AMD
都相继在CPU
硬件层面增加了虚拟化的支持,即硬件虚拟化,Intel
在x86
指令集的基础上增加了一套VMX
扩展指令VT-x
,为CPU
增加了新的运行模式,完成了x86
虚拟化漏洞的修补。
通过新的硬件虚拟化指令,可以非常方便的构造VMM
,并且x86
虚拟机中的代码能够原生地运行在物理CPU
上
KVM
本身是一个内核模块,导出来一系列的接口到用户空间,用户空间可以使用这些接口创建虚拟机。最开始KVM
只负责最核心的CPU
虚拟化和内存虚拟化部分,使用QEMU
作为其用户态组件,负责大量外设的模拟,当时的方案被称为QEMU-KVM
。
除此之外,还有VMware
和VirtualbBox
等虚拟化解决方案。
虚拟机的创建:要创建一台KVM
虚拟机,需要用户侧的QEMU
发起请求。
QEMU
侧虚拟机的创建和KVM
虚拟机的创建:QEMU
中使用KVMState
结构体来表示KVM
相关的数据结构,KVM_INIT
函数首先打开/dev/kvm
设备得到一个fd
,调用kvm_ioctl(s, KVM_CREATE_VM)
接口中KVM
层面创建一个虚拟机,kvm_init
也会调用kvm_arch_init
完成一些架构相关的初始化。
// qemu
static int accel_init_machine(AccelClass *acc, MachineState *ms)
{
//
ret = acc->init_machine(ms);
if (ret < 0)
{
...
}
return ret;
}
// kvm
static int kvm_init(MachineState *ms)
{
//
MachineClass *mc = MACHINE_GET_CLASS(ms);
ms = KVM_STATE(ms->accelerator);
ms->vmfd = -1;
ms->fd = qemu_open("/dev/kvm", O_RDWR);
do
{
ret = kvm_ioctl(s, KVM_CREATE_VM, type); // 创建一台KVM虚拟机
} while (ret == -EINTR);
}
VCPU
的创建
VCPU
的类型
struct X86CPUDefinition {
const char *name;
uint32_t level;
uint32_t xlevel;
/* vendor is zero-terminated, 12 character ASCII string */
char vendor[CPUID_VENDOR_SZ + 1];
int family;
int model;
int stepping;
FeatureWordArray features;
char model_id[48];
};
static X86CPUDefinition builtin_x86_defs[] = {
// 各种cpu型号和特性
{
.name = "pentium",
.level = 1,
.vendor = CPUID_VENDOR_INTEL,
.family = 5,
.model = 4,
.stepping = 3,
.features[FEAT_1_EDX] =
PENTIUM_FEATURES,
.xlevel = 0,
},
// ......
}
在调用KVM
的创建虚拟CPU
会调用一个kvm_vcpu_ioctl
函数,此处传入一个参数,用来表示创建cpu
的状态,如下
int kvm_cpu_exec(CPUState *cpu)
{
do
{
...
run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
} while ()
}
传统的虚拟化技术可以通过硬件或者操作系统来实现,如KVM
,为了让虚拟的应用程序达到和物理机相近的结果,使用了Hypervisor/VMM
,它允许多个操作系统共享一个或多个CPU
,但是却带来了很大的开销,由于虚拟机中包括全套的OS
,调度和资源占用都非常重。
容器(Container
)技术是一种更加轻量级的操作系统虚拟化技术,它将应用程序,依赖包,库文件等允许依赖环境打包到标准化的镜像中,通过容器引擎提供进程隔离,资源可限制的允许环境,实现应用与OS
平台及底层硬件的解耦。
此处不得不提到一个命令:chroot
,即change root
,其作用是改变程序执行时所参考的根目录位置,可以增加系统的安全性,限制使用者能做的事。
主要包括守护进程,镜像,驱动和容器管理者几个模块。
Docker Daemon
该进程是一个常驻后台的守护进程,负责监听客户端请求,然后执行后续的对应逻辑,还能管理Docker
对象,主要有三部分:
Server
:负责接收客户端发来的请求,接收请求以后Server
通过路由与分发调度找到相应的Handler
执行请求,然后与容器镜像仓库交互镜像并将结果返回给Docker Client
。Engine
:运行引擎。该模块扮演了Docker container
存储仓库的角色,Engine
的每一项工作,都可以拆解成多个最小动作Job
,这是其最基本的工作执行单元Job
:Docker
内部的每一步操作,都可以抽象为一个Job
,会使用下层的Driver
驱动来完成。Docker Driver
Docker
中的驱动,设计驱动这一层是解耦,将容器管理的镜像,网络和隔离执行逻辑从Docker Daemon
的逻辑中剥离。
中期实现中,可以分为以下三类驱动:
graphdriver
:负责容器镜像的管理,主要是镜像的存储和获取,当镜像下载时,会将镜像持久化存储到本地的制定目录。networkdriver
:负责Docker
容器网络环境的配置,如Docker
运行时进行IP
分配端口映射以及启动时创建网桥和虚拟网卡。execdriver
:执行驱动,通过操作Lxc
或者libcontainer
实现资源隔离。它负责创建管理容器运行命名空间,管理分配资源和容器内部真实进程的运行。libcontainer
提供了访问内核中和容器相关的API
,负责对容器进行具体操作。
主要是利用内核的
namespace
和cgroup
两个技术。
其中namespace
负责进程隔离,cgroup
负责资源限制
可见虚拟文件系统(无持久存储的文件系统),以proc和sysfs为例中的Cgroup
文件系统。
感兴趣的可以看看这个项目,用bash
手写的一百多行简易docker
:bocker