当你思考 “虚拟化” 这个话题,你会被乱飞过来的层出不穷的技术名词击晕:docker、podman、VirtualBox、VMWare、KVM、…。而从第一性原理出发,进程是虚拟化,虚拟内存也是虚拟化。概览各种虚拟化技术,你会发现三条线索:资源共享、权限控制和模拟执行。
资源共享
我所看到的虚拟化最主要的线索是资源共享。
- “虚拟内存” 就是最典型的资源共享机制:它把一块内存地址空间分给数个不同的程序去用,每个程序都能获得一个完整的,好比独占的内存地址空间。
- “多任务处理” 同样经典,它把 CPU 的计算时间划分为若干时间片分配给不同的线程,但每个线程获得的是同样的东西——不可能说 A 线程获得了一个 x86_64 指令集的 CPU,而 B 线程获得了一个 RISCV 指令集的 CPU:虚拟化之后资源本身的性质没有变。
- 如果上面的东西让你头晕没关系,下面的转换头能把一个 USB-B 母口扩展到 4 个 USB-B 母口,这就是资源共享。扩展之后的口是一个完整可用的 Type-B 口,且它没有变成 Type-C、HDMI(废话)。

理解了 “完整独占” 和 “资源性质不变” 这两个特性,我们可以即刻分析一堆虚拟化技术了
虚拟机技术(Hypervisor)
虚拟机共享硬件资源(不妨简单理解为 CPU),供给多种多样、并非宿主(Host)的操作系统使用。调研了半天,还是按照 Robert P. Goldberg 在 Architectural Principles for Virtual Computer Systems 提出的分类法来梳理虚拟机技术最清晰明了。请看从 Wikipedia 搬来的图:

简言之,Type 1 Hypervisor 利用硬件(CPU 指令集)对虚拟化的原生支持,向下直接和 CPU 交互,而向上支持多种 OS。
- 比如说,在一个 x86_64 的 CPU 上跑一个 Type 1 Hypervisor,那么原则上你可以在 Hypervisor 上跑一个 Windows(for x64) 和一个 Arch Linux(for x64)。但你想在上面跑 Windows for ARM 就行不通了,被共享的资源里,找不到一个 ARM 指令集的 CPU。
- Type-1 Hypervisor 的例子:Hyper-V,Xen 以及 VMware ESXi。
Type 2 Hypervisor 向上与 Type 1 Hypervisor 提供相同的服务/资源,向下却不直接和硬件交互,它运行在一个宿主操作系统上。
- Type-2 Hypervisor 的例子: VirtualBox 和 VMware Workstation。
- 举个例子,在一个 Windows(x86_64)的宿主机上安装运行一个 VirtualBox,你可以在 VirtualBox 中创建一个 Windows(for x64)虚拟机和一个 Arch Linux(for x64)虚拟机。
- 在指令集的层面,与 Type-1 Hypervisor 相似地,想创建一个 Windows for ARM 是行不通的
- 在操作系统的层面,之所以能成功创建 Linux 操作系统的虚拟机,是因为共享的是 CPU,而非的宿主机 Windows 内核。
这个二分并非全面的。KVM 作为一个 Kernel Module,某种程度上把 Host OS 变成了一个 Type-1 Hypervisor,两者都不完全是。
最后关于用语:Virtualization 通常指代 Hardware Virtualization,即像这里把 CPU 硬件资源共享给多个操作系统使用。virtual as in virtual memory。这个过程通常需要 CPU 指令集的支持;相应地,Guest OS 也能获得接近 native 的性能。
权限与用量控制
理解资源共享是理解虚拟化之根本。然而通常只有虚拟化机制是不够的。在其基础上,我们引入一条新机制,权限控制。资源共享+权限控制,事情变得复杂起来。
比如说
- 操作系统配置页表,可以限制虚拟内存的用户(进程)访问虚拟内存的合法范围和权限;它也可以可选地为某个进程使用的内存设置上限
- 操作系统的调度器可以控制何时给线程分配 CPU 时间片,从而控制线程对 CPU 的用量
在这些意义上,进程就是操作系统内置的虚拟化技术,它将宿主操作系统作为资源共享(自然附带更下面的硬件),供给多样的应用使用。
- 从这个描述看,它比起 “虚拟机技术” 的描述向上平移了一个层级,”向下”从硬件变成了操作系统,”向上”从操作系统变成了应用。
- 简言之,我们在用户空间(User mode)
然而 “进程” 并不属于那些飞过来将你装晕的虚拟化技术名词。后者是 docker、podman、LXC、… – 对于以这三个玩意儿为代表的虚拟化技术,我们暂且称之为 Container,容器化技术。
容器化技术(Container)
Acknowledgment:Jacky 在讨论中的发言对本节内容贡献良多,本节很多部分是对 Jacky 讨论中发言的转述
在进程之外之所以还要容器,是因为在一些操作系统中(具体而言,Linux),进程这一抽象的 “完整独占” 的幻觉还不够逼真,权限控制的工具还不够方便。
为了弥补,Linux 内核提供了诸多底层 API:
- 命名空间(Namespaces)——进一步实现 “完整独占”/隔离
- 所谓命名空间,其核心特质就是:命名空间之间相互独立,每个命名空间内部麻雀虽小五脏俱全
- 网络命名空间(Network Namespaces, netns)提供了网络沙盒,原则上你在一个命名空间中无论怎么折腾网络配置都不用担心把宿主的网络搞坏
- PID Namespaces 提供了相互独立的 PID 组
- Filesystem Namespace
- …
- 控制组(Control Group)—— 权限与用量控制
- 限制内存、CPU 时间、I/O 的用量
从 Linux 操作系统的演化历史来看,这些 API 是逐步加入 Linux 内核的,没有一个统一的抽象。这时人们希望有一个统一的工具来协调这些底层 API,提供一个名为 “容器” 的抽象。
既然 “Docker container” 是 Container,那让我们看看 Docker 发布 runC 的博文:
Because “containers” are actually an array of complicated, sometimes arcane system features*, we have integrated them into a unified low-level component which we simply call runC**.
*: control groups, namespaces, etc
**: runC is the low-level container runtime behind Docker
如果你能够且愿意手操这些 Linux API,你也可以手动构造所谓的 Container。
模仿我们对虚拟机技术的举例,在此再举几例
- 不考虑容器的场景下,可以在一个指令集架构为 x86_64 的 Linux 的系统上运行一个 win32-x64 程序吗?一个 arm64 程序呢?都不可以
- 既然如此,即使我们在这个系统中想象出了 container 这个抽象,该不行还是不行。
- 那为什么现实中,我在一个指令集架构为 x86_64 的 Window 系统上安装 Docker,就可以运行一个 linux-x64 的程序了呢?原因是你在 Windows 上安装 Docker 的同时也安装了 WSL2,linux-x64 程序能运行是 WSL2 的功劳,而非 Container 的功劳。
在 Linux PC/server 之外
如果我们把视线转向移动端,以 iOS 和 iOS 应用为例,它某种程度上就是主机/服务器 Container 的对应物(尽管通常不被称为 Container)。它们在用户空间,拥有资源共享/隔离机制,受到访问控制(尤其是当你考虑 iOS 的权限系统),都是比 “进程” 更有用的更高层抽象。
如果我们进一步不局限于操作系统,一个 “平台” 应用也可以将自己的资源带有访问控制地共享给 “租户” 应用。如今很多 AI Agent Framework 作为 “平台”,创造沙盒(Sandbox)供 AI Agent 在里面自主发挥。
最后关于用语:Container 和 Containerization 通常就指代这一类比 “进程” 更适用于应用场景的用户空间资源隔离和访问控制技术,尤其是 Linux World 中与宿主共享 Kernel 但受控的轻量级容器。
模拟执行
当用户需要系统内不即已存在的资源和接口,作为下策,可以让系统使用它既有的资源模仿另一个系统(用户需要的)的行为,这就是 Emulation。Emulation 技术就好比下面的转换头,把一个 USB-C 母口扩展到 USB-C, HDMI 和 USB-B 母口,后两个接口都不是既有的。

仿真器(Emulator)
我们暂且将 Emulation 翻译为仿真 – 它与 Simulation 不同,Simulation 是模仿和重建现实系统的实验方法,它重在提供实验结果而非提供资源。
以 QEMU(Quick Emulator)为例:
- 你可以在一个 x86_64 的 Window pc 上面,通过 QEMU 运行为 linux-riscv 编译的应用。你的系统原本提供的资源类型不再成为否决应用的条件了。
- 你既可以模拟一个 Hareware+OS,为应用提供运行环境,也可以仅模拟 Hardware,为应用提供运行环境。
- 然而,默认情况下,作为一个 Emulator,”轻量级” 是不存在的,near native performace 通常也是不存在的。
模拟执行+权限控制的例子
一个这样糅合了虚拟化两条线索的例子是 iOS 系统上的 iSH Shell 应用。根据项目网站的简介:
iSH is a project to get a Linux shell environment running locally on your iOS device, using a usermode x86 emulator.
根据 iSH wiki,iSH 自身在 ARM 指令集的 iOS 系统上运行,却需要支持被编译到 x86_32 指令集Linux 系统上运行的应用。这里似乎有两个层级上的模拟或者说转化要做:
- 把 x86_32 指令转换为 ARM 指令
- 把 Linux 系统调用转换为 Darwin (iOS) 系统调用
实际上发现 iSH 内的 ssh 命令提供的 ssh client 功能是可用的,说明这个 Shell 还是实现了很多相关的 System Call 的,具体没有深究。如果我们试图运行更多命令,安装 iproute2 包,借此更改 kernel 的网络配置,就无法成功了:
localhost:~# apk add iproute2
localhost:~# ip a
Cannot open netlink socket: Invalid argument
根本原因很好推测,即 iSH 作为一个 App Store 上的第三方应用不具有更改 iOS kernel 网络配置的权限。
Leave a Reply