虚拟化技术介绍
虚拟化技术介绍
极简介绍
对于我们想要虚拟化的对象,提供实际资源之上的抽象。应用不同抽象级别将会呈现不同的虚拟化技术。
基于抽象级别的虚拟化技术有两类最为常用:
- 基于虚拟机(VM):虚拟化整个操作系统,换句话说是虚拟化完整的ISA
- 基于容器(Container)
[toc]
Hypervisors
用于虚拟化的特殊软件通称,其本身有两个部分:
- Virtual Machine Monitor(VMM):捕获和模拟特权指令集(仅操作系统内内核可以运行)
- Device model:设备模型,用于虚拟化I/O设备
VMM
硬件在绝大多数情况下不能直接在虚拟机上使用,因此需要通过VMM捕获硬件(磁盘、网卡…)的特权指令,并代表虚拟机执行这些指令。其需满足三个属性:
- 隔离性:将guest(VMs虚拟机)彼此进行隔离
- 等效性:有没有使用虚拟化,对于硬件角度来说执行表现应当相同;即没有在翻译的情况下在硬件执行(几乎所有的)指令
- 性能:有与没有虚拟机的情况下性能应做到(几乎)一致,即虚拟机的开销应尽可能的小
功能
- 不能访问特权级,
VMM
应截获这些请求并模拟特权级调用 - 异常处理和中断
- 大部分cpu指令直接在物理机CPU上运行,仅在陷入某些特权指令才使用虚拟化CPU(运行效率基本与在物理机一致)
- I/O的地址映射
内存虚拟化
CPU虚拟化
见这篇文章,对虚拟化特权级进行了全面解释。
代码定位见此
全虚拟化与半虚拟化
全虚拟化中,客户OS(guest OS)“不知道”自己运行在虚拟环境中,它的运行状态和在物理机中相同;而半虚拟化状态中,guest OS对虚拟环境做了特化,I/O的系统调用会被hypercalls替换掉。
它们的结构区别如下图所示:
半虚拟化状态中,guest os的驱动程序被称为前端驱动程序
,主机的被称为后端驱动程序
,这种前后端的实现方式很大的提高了交互效率,具体见virtio
对于I/O虚拟化,参见sriov
Hypervisors介绍
INTEL有两种虚拟化风格:Vt-x
和Vt-i
- Vt-x:适用于Intel x86 32或64位架构
- Vt-i:适用于安腾处理器系列
本节将更为熟悉的Vt-x指令集
VT-x指令集
物理机中,执行顺序是程序指令->CPU指令;
而在虚拟机中,执行顺序是程序指令->虚拟CPU指令->CPU指令。
这将导致指令翻译两次,会造成巨大开销(详见这篇文章)
通过抽象CPU指令到虚拟指令,即对CPU指令扩充一些虚拟专用指令,可以在虚拟机完成:程序指令->CPU指令
对于Vt-x来说,它还做了一些权限分割上的工作,具体可见这篇关于ring -1的回答
细节了解和代码定位,参考这篇博客
KVM创建虚拟机
在用户态中创建虚拟机的过程如下:
- 创建一个裸VM
- 映射VM的用户空间内存
- 创建VCPU/IRQCHIP,并映射vt-x指令集
- 其他硬件配置:寄存器、FPU、LAPIC等
- 启动VM
具体信息和代码定位详见KVM官网相关文档
基于vhost的数据交互
这里以一个vhost-net设备的生命周期为例。
该设备初始化时,会为QEMU对应进程创建一个内核线程,专门处理guest I/O,该线程侦听主机端virtqueues
上的事件。
事件为传输时,guest上的TX(传输)队列排出(virtio术语叫kick)数据包,线程将该包放到抽头设备(tap),后继续使用正常底层网络设备(路由、交换机)等进行常规网络包传输工作;接收(RX)同理。
事件注册采用eventfd
机制,这是文件描述符,具体功能和机制参见这篇博客。
其他虚拟化尝试
Unikernel
作为虚拟化技术的一种尝试,基础思想就是;仅将应用所需的环境包装到虚拟环境中,即一个虚拟机完成单一的应用功能。好处显然易见——轻量、安全、快速。但是单一的应用和复杂的打包机制也成为制约unikernel不利条件。
Project-Dune
斯坦福大学设计的一种机制,可以通过虚拟化技术让用户程序跑在ring 0
中,即在非特权的上下文中执行特权指令。
原理可用一张图表示:
代码被分为受信任部分和非受信任部分,它们都运行在同一个物理地址上,但受信任的代码所在页被hypervisor bits
位标记保护。受信任的代码通过Dune进程(由libdune库提供
)运行在ring 0,非受信任的代码运行在ring 3。
详细信息可查阅project-dune官网
novm
在VM中提供了一个文件系统,而非块设备。是一种虚拟机的轻量化实现。
具体可见github项目
容器
Linux容器有三个组成基础:
- namesapce:Linux资源的可见性
- cgroups:资源配额控制
- 文件分层系统:容器的文件系统,实现内存和磁盘的共享
Namespace
命名空间是Linux中的一种进程的逻辑隔离技术,即缩小进程的可见性。对于namespace的进程来说,它看不到除自己namespace外的其他资源。
namespace有很多类型:
- UTS
- PID
- Mount
- Network
- IPC
- cgroup
- time
其详情介绍可以参考wiki
原理分析和用法可以参考这篇博客,这大哥的博客写的是真好,从实践到原理到源码介绍的非常细致。如果有时间推荐两部分都看一下。
Cgroup
cgroups(Control Group)是一项 Linux 内核功能,用于限制、说明和隔离进程集合的资源使用情况(CPU、内存、磁盘 I/O 等 )
有两个版本,v1(2.6.24)和v2(4.5)。
v2的版本介绍可以参考wiki百科和kernel官方文档
v1的版本介绍可以参考kernel官方文档
分层文件系统
文件系统
Linux的设计理念是“万物均文件”,即系统中所有对象均使用文件进行抽象,访问这些对象和访问文件的过程是一致的(包括块文件和非块文件)。
Linux使用虚拟文件系统(VFS)层抽象所有文件系统。所有文件系统均在VFS中注册。其具有以下重要的数据结构:
- 文件
- INODE
- 目录项
- 超级块
详细内容可以参考这篇文章,和这个专栏系列中文件系统部分
伪文件系统
对于一些非块文件如procfs
,它们可能通过文件接口公开内核的的一些资源,这些文件系统被称为伪文件系统
。
可参考论坛回答
分层文件系统简介
在上述Linux文件系统中,有两件事是容器需要但其无法满足的:
- 内存共享
- 磁盘空间共享
在分层文件系统中,文件系统被分成多层,每层都是只读文件系统。
这些层在同意主机上的容器之间共享,且inode是相同的,它们将会引用相同操作系统页面进行缓存。
下面将介绍两种Linux中正在使用的分层文件系统。
unionfs(UFS)
unionfs允许管理员将文件在物理上分开,但在逻辑上合并到单个视图中。
合并目录的集合称为联合(黄色部分),每个物理目录称为分支(蓝色部分)。
每个分支都分配有一个优先级。具有较高优先级的分支会覆盖具有较低优先级的分支。 Unionfs 对目录进行操作。如果一个目录存在于两个底层分支中,则Unionfs目录的内容和属性是两个较低目录的组合。 Unionfs 自动删除任何重复的目录条目,因此用户不会因重复的文件名或目录而感到困惑。如果一个文件存在于两个分支中,则Unionfs文件的内容和属性与高优先级分支中的文件相同,而低优先级分支中的文件被忽略。
需要注意的是,当多个同名文件挂载到同联合中时,默认复制第一个文件中的内容。
测试:
$ ls /Fruits
Tomato hi/
$ ls /Veg
Tomato
$ cat /Fruits/Tomato
I am botanically a fruit.
$ cat /Veg/Tomato
I am horticulturally a veg.
# 联合目录挂载到 Eat
$ mount -t overlay -o dirs=/Fruits:/Veg overlay /Eat
# 查看Tomato的内容
$ cat /Eat/Tomato
I am botanically a fruit.
# 查看目录合并结果
$ ls /Eat
unionfs包括aufs均已过时,现在基本都用overlay
或overlay2
作为容器的文件系统。
参考如下链接:
祖宗:2004年的Linux期刊
原理剖析+源码解读
@knoldus/unionfs-a-file-system-of-a-container-2136cd11a779">直观图片+完整实验
Overlayfs
Linux 3.18内核版本之后,overlayfs被集成进内核。
其作用是将一个目录地内容覆盖到另一个目录上
当前有两个版本:v1和v2
v1有两层:
- lower:只读层
- upper:读写层
规则:
- 当进程“读取”文件时,overlayfs文件系统驱动将优先在上层目录upperdir中查找并从该目录中读取文件,找不到则在下层目录lowerdir中查找。
- 当进程”写入”文件时,overlayfs会将其写入上层目录upperdir。
v2有三层:
- base: 基础层,创建时初始化,是只读层
- overlay: 提供从基本层的可见性,当基础层文件发生更改,则存储到其下一层
- diff: 在overlay做的更改存储于此。基础层的任何文件/目录更改都会将文件从基础层复制到diff层,然后将更改写入diff层
v2示例:
初始化创建
root@instance-1: mkdir base diff overlay workdir
root@instance-1: echo "test data" > base/test1
root@instance-1: sudo mount \ > -t overlay \
> -o lowerdir=base,upperdir=diff,workdir=workdir \
> overlay \
> overlay
过程和上一节一致,不做赘述。
修改overlay
root@instance-1:/overlay# touch test2
root@instance-1:/overlay# ls test1
test2
root@instance-1:-/overlay# cd ../diff
root@instance-1:-/diff# ls
test2
可见修改的结果在diff中可见
可以动手试试修改文件内容,看在overlay和base中如何变化
参考这篇博客
发表评论