UML 使用指南¶
简介¶
欢迎使用用户模式 Linux
用户模式 Linux 是第一个开源虚拟化平台(首次发布日期为 1991 年)和 x86 PC 的第二个虚拟化平台。
UML 与使用虚拟化软件包 X 的 VM 有何不同?¶
我们已经开始假设虚拟化也意味着某种程度的硬件仿真。 事实上,并非如此。 只要虚拟化软件包为操作系统提供操作系统可以识别并且具有驱动程序的设备,这些设备就不需要模拟真实硬件。 今天,大多数操作系统都内置了对许多仅在虚拟化下使用的“伪”设备的支持。 用户模式 Linux 将这个概念发挥到了极致 - 眼前没有一个真实的设备。 它是 100% 人工的,或者如果我们使用正确的术语,则是 100% 半虚拟化。 所有 UML 设备都是抽象概念,映射到主机提供的内容 - 文件、套接字、管道等。
UML 与各种虚拟化软件包之间的另一个主要区别是,UML 内核和 UML 程序的操作方式之间存在明显的差异。 UML 内核只是在 Linux 上运行的一个进程 - 与任何其他程序一样。 它可以由非特权用户运行,并且不需要任何特殊的 CPU 功能。 然而,UML 用户空间有点不同。 主机上的 Linux 内核协助 UML 拦截 UML 实例上运行的程序尝试执行的所有操作,并使 UML 内核处理其所有请求。 这与其他不区分客户内核和客户程序的虚拟化软件包不同。 这种差异导致 UML 相对于 QEMU 有许多优点和缺点,我们将在本文档后面介绍。
为什么需要用户模式 Linux?¶
如果用户模式 Linux 内核崩溃,您的主机内核仍然可以正常工作。 它不会以任何方式加速(vhost、kvm 等),并且不会尝试直接访问任何设备。 事实上,它是一个像其他任何进程一样的进程。
您可以以非 root 用户身份运行用户模式内核(您可能需要为某些设备安排适当的权限)。
您可以运行一个非常小的 VM,其占用空间最小,用于特定任务(例如 32M 或更少)。
对于任何“内核特定任务”,例如转发、防火墙等,您可以获得极高的性能,同时仍然与主机内核隔离。
您可以尝试内核概念而不会破坏任何东西。
您不受“模拟”硬件的约束,因此您可以尝试一些奇怪而奇妙的概念,这些概念在模拟真实硬件时很难支持,例如时间旅行并使您的系统时钟依赖于 UML 的操作(对于测试等非常有用)。
它很有趣。
为什么不运行 UML¶
UML 使用的系统调用拦截技术使其在任何用户空间应用程序中都固有地较慢。 虽然它可以执行与其他大多数虚拟化软件包相当的内核任务,但其用户空间是 **慢速的**。 根本原因是 UML 创建新进程和线程的成本非常高(大多数 Unix/Linux 应用程序都认为这是理所当然的)。
UML 目前严格来说是单处理器。 如果您想运行一个需要多个 CPU 才能运行的应用程序,那么它显然是错误的选择。
构建 UML 实例¶
任何发行版中都没有 UML 安装程序。 虽然您可以使用现成的安装介质安装到使用虚拟化软件包的空白 VM 中,但没有 UML 等效项。 您必须使用主机上的适当工具来构建可行的文件系统镜像。
这在 Debian 上非常容易 - 您可以使用 debootstrap 来完成。 在 OpenWRT 上也很容易 - 构建过程可以构建 UML 镜像。 所有其他发行版 - YMMV。
创建镜像¶
创建一个稀疏的原始磁盘镜像
# dd if=/dev/zero of=disk_image_name bs=1 count=1 seek=16G
这将创建一个 16G 磁盘镜像。 操作系统最初只会分配一个块,并且会随着 UML 的写入而分配更多块。 从内核版本 4.19 开始,UML 完全支持 TRIM(通常由闪存驱动器使用)。 通过指定 discard 作为挂载选项或通过运行 tune2fs -o discard /dev/ubdXX
在 UML 镜像内使用 TRIM 将请求 UML 将任何未使用的块返回给操作系统。
在磁盘镜像上创建一个文件系统并挂载它
# mkfs.ext4 ./disk_image_name && mount ./disk_image_name /mnt
此示例使用 ext4,任何其他文件系统(例如 ext3、btrfs、xfs、jfs 等)也可以工作。
在挂载的文件系统上创建最小的操作系统安装
# debootstrap buster /mnt http://deb.debian.org/debian
debootstrap 不设置 root 密码、fstab、主机名或任何与网络相关的内容。 这取决于用户来完成。
设置 root 密码 - 最简单的方法是 chroot 到挂载的镜像中
# chroot /mnt
# passwd
# exit
编辑关键系统文件¶
UML 块设备称为 ubds。 debootstrap 创建的 fstab 将为空,并且它需要 root 文件系统的条目
/dev/ubd0 ext4 discard,errors=remount-ro 0 1
镜像主机名将设置为与您在其上创建镜像的主机相同。 最好更改它,以避免“哦,糟糕,我重新启动了错误的机器”。
UML 支持向量 I/O 高性能网络设备,这些设备支持一些标准虚拟网络封装,例如 Ethernet over GRE 和 Ethernet over L2TPv3。 这些称为 vecX。
当使用向量网络设备时,/etc/network/interfaces
将需要如下条目
# vector UML network devices
auto vec0
iface vec0 inet dhcp
我们现在有一个几乎可以运行的 UML 镜像,我们所需要的只是一个 UML 内核和它的模块。
大多数发行版都有一个 UML 软件包。 即使您打算使用自己的内核,使用库存内核测试镜像始终是一个好的开始。 这些软件包附带一组应复制到目标文件系统的模块。 该位置取决于发行版。 对于 Debian,这些位于 /usr/lib/uml/modules 下。 以递归方式将此目录的内容复制到挂载的 UML 文件系统
# cp -rax /usr/lib/uml/modules /mnt/lib/modules
如果您已编译自己的内核,则需要通过运行来使用常用的“将模块安装到某个位置”过程
# make INSTALL_MOD_PATH=/mnt/lib/modules modules_install
这将把模块安装到 /mnt/lib/modules/$(KERNELRELEASE) 中。 要指定完整的模块安装路径,请使用
# make MODLIB=/mnt/lib/modules modules_install
此时,镜像已准备好启动。
设置 UML 网络¶
UML 网络旨在模拟以太网连接。 此连接可以是点对点连接(类似于使用背靠背电缆的机器之间的连接)或与交换机的连接。 UML 支持多种方式来构建与以下所有内容的连接:本地机器、远程机器、本地和远程 UML 以及其他 VM 实例。
传输 |
类型 |
功能 |
吞吐量 |
---|---|---|---|
tap |
矢量 |
校验和、tso |
> 8Gbit |
混合 |
矢量 |
校验和、tso、多包 rx |
> 6GBit |
原始 |
矢量 |
校验和、tso、多包 rx, tx” |
> 6GBit |
EoGRE |
矢量 |
多包 rx, tx |
> 3Gbit |
Eol2tpv3 |
矢量 |
多包 rx, tx |
> 3Gbit |
bess |
矢量 |
多包 rx, tx |
> 3Gbit |
fd |
矢量 |
取决于 fd 类型 |
各不相同 |
vde |
矢量 |
取决于 VDE VPN:Virt.Net Locator |
各不相同 |
所有具有 tso 和校验和卸载的传输都可以提供接近 10G 的 TCP 流速度。
所有具有多包 rx 和/或 tx 的传输都可以提供高达 1Mps 或更高的 pps 速率。
GRE 和 L2TPv3 允许连接到以下所有内容:本地机器、远程机器、远程网络设备和远程 UML 实例。
网络配置权限¶
大多数受支持的网络模式需要 root
权限。 例如,对于矢量传输,需要 root
权限才能触发 ioctl 来设置 tun 接口和/或在需要时使用原始套接字。
可以通过授予用户特定功能而不是以 root 身份运行 UML 来实现此目的。 对于矢量传输,用户可以将功能 CAP_NET_ADMIN
或 CAP_NET_RAW
添加到 uml 二进制文件。 从此以后,UML 可以使用普通用户权限以及完整的网络运行。
例如
# sudo setcap cap_net_raw,cap_net_admin+ep linux
配置向量传输¶
所有矢量传输都支持类似的语法
如果 X 是接口号,如 vec0、vec1、vec2 等,则选项的通用语法为
vecX:transport="Transport Name",option=value,option=value,...,option=value
常用选项¶
这些选项对于所有传输都是通用的
depth=int
- 设置矢量 IO 的队列深度。 这是 UML 将尝试在单个系统调用中读取或写入的数据包量。 默认数字为 64,通常足以满足大多数需要 2-4 Gbit 范围内的吞吐量的应用程序。 更高的速度可能需要更大的值。mac=XX:XX:XX:XX:XX
- 设置接口 MAC 地址值。gro=[0,1]
- 打开或关闭 GRO。 启用接收/发送卸载。 此选项的效果取决于正在配置的传输中主机端的支持。 在大多数情况下,它将启用 TCP 分段和 RX/TX 校验和卸载。 主机端和 UML 端上的设置必须相同。 如果不是,UML 内核将产生警告。 例如,默认情况下,在本地机器接口(例如 veth 对、桥接等)上启用 GRO,因此应在相应的 UML 传输(原始、tap、混合)中启用 UML,以便网络正常运行。mtu=int
- 设置接口 MTUheadroom=int
- 调整默认预留空间(32 字节),以防数据包需要重新封装到例如 VXLAN 中。vec=0
- 禁用多包 IO 并回退到一次一个数据包模式
tap 传输¶
示例
vecX:transport=tap,ifname=tap0,depth=128,gro=1
这将将 vec0 连接到主机上的 tap0。 Tap0 必须已存在(例如使用 tunctl 创建)并且已启动。
可以将 tap0 配置为点对点接口并分配一个 IP 地址,以便 UML 可以与主机通信。 或者,可以将 UML 连接到连接到网桥的 tap 接口。
虽然 tap 依赖于矢量基础设施,但它此时不是真正的矢量传输,因为 Linux 不支持普通用户空间应用程序(如 UML)的 tap 文件描述符上的多包 IO。 这是仅提供给可以通过专用接口(如 vhost-net)在内核级别连接到它的东西的权限。 计划在将来某个时候为 UML 提供类似 vhost-net 的帮助程序。
所需权限:tap 传输需要以下权限之一
tap 接口存在并且是使用 tunctl 创建的持久性接口,并且归 UML 用户所有。 示例
tunctl -u uml-user -t tap0
二进制文件具有
CAP_NET_ADMIN
权限
混合传输¶
示例
vecX:transport=hybrid,ifname=tap0,depth=128,gro=1
这是一种实验性/演示传输,它将 tap 用于传输,将原始套接字用于接收。 原始套接字允许接收多包,从而产生比普通 tap 高得多的数据包速率。
所需权限:混合传输需要 UML 用户具有 CAP_NET_RAW
功能以及 tap 传输的要求。
原始套接字传输¶
示例
vecX:transport=raw,ifname=p-veth0,depth=128,gro=1
此传输在原始套接字上使用矢量 IO。 虽然您可以绑定到任何接口,包括物理接口,但最常见的用途是绑定到 veth 对的“对等”端,另一端配置在主机上。
Debian 的示例主机配置
/etc/network/interfaces:
auto veth0
iface veth0 inet static
address 192.168.4.1
netmask 255.255.255.252
broadcast 192.168.4.3
pre-up ip link add veth0 type veth peer name p-veth0 && \
ifconfig p-veth0 up
UML 现在可以像这样绑定到 p-veth0
vec0:transport=raw,ifname=p-veth0,depth=128,gro=1
如果 UML 客户机配置为 192.168.4.2,子网掩码为 255.255.255.0,则它可以与主机上的 192.168.4.1 通信
原始传输还提供了一些支持,可以将一些过滤卸载到主机。 用于控制它的两个选项是
bpffile=str
要加载为套接字过滤器的原始 bpf 代码的文件名bpfflash=int
0/1 允许从用户模式 Linux 内部加载 bpf。 此选项允许使用 ethtool load firmware 命令来加载 bpf 代码。
在任何一种情况下,bpf 代码都会加载到主机内核中。 虽然这目前仅限于旧版 bpf 语法(而非 ebpf),但它仍然是一种安全风险。 除非用户模式 Linux 实例被认为是可信的,否则不建议允许这样做。
所需权限:原始套接字传输需要 CAP_NET_RAW 功能。
GRE 套接字传输¶
示例
vecX:transport=gre,src=$src_host,dst=$dst_host
这将配置一个 Ethernet over GRE
(又名 GRETAP
或 GREIRB
)隧道,该隧道会将 UML 实例连接到主机 dst_host 上的 GRE
端点。 GRE
支持以下其他选项
rx_key=int
- rx 数据包的 GRE 32 位整数密钥,如果设置,也必须设置txkey
tx_key=int
- tx 数据包的 GRE 32 位整数密钥,如果设置,也必须设置rx_key
sequence=[0,1]
- 启用 GRE 序列pin_sequence=[0,1]
- 假装序列始终在每个数据包上重置(需要与某些真正损坏的实现互操作)v6=[0,1]
- 分别强制使用 IPv4 或 IPv6 套接字目前不支持 GRE 校验和
GRE 有许多注意事项
每个 IP 地址只能使用一个 GRE 连接。 无法多路复用连接,因为每个 GRE 隧道都直接在 UML 实例上终止。
密钥实际上不是安全功能。 虽然它旨在作为安全功能,但其“安全性”令人啼笑皆非。 然而,这是一个有用的功能,可以确保隧道未配置错误。
将本地地址为 192.168.128.1 的 Linux 主机连接到 192.168.129.1 的 UML 实例的示例配置
/etc/network/interfaces:
auto gt0
iface gt0 inet static
address 10.0.0.1
netmask 255.255.255.0
broadcast 10.0.0.255
mtu 1500
pre-up ip link add gt0 type gretap local 192.168.128.1 \
remote 192.168.129.1 || true
down ip link del gt0 || true
此外,GRE 已经针对各种网络设备进行了测试。
所需权限:GRE 需要 CAP_NET_RAW
l2tpv3 套接字传输¶
_警告_。 L2TPv3 有一个“bug”。 它是被称为“比 GNU ls 具有更多选项”的“bug”。 虽然它有一些优点,但通常有更简单(且更不冗长)的方法来将 UML 实例连接到某些东西。 例如,大多数支持 L2TPv3 的设备也支持 GRE。
示例
vec0:transport=l2tpv3,udp=1,src=$src_host,dst=$dst_host,srcport=$src_port,dstport=$dst_port,depth=128,rx_session=0xffffffff,tx_session=0xffff
这将配置一个 Ethernet over L2TPv3 固定隧道,该隧道会将 UML 实例连接到主机 $dst_host 上的 L2TPv3 端点,使用 L2TPv3 UDP 风格和 UDP 目标端口 $dst_port。
L2TPv3 始终需要以下其他选项
rx_session=int
- rx 数据包的 l2tpv3 32 位整数会话tx_session=int
- tx 数据包的 l2tpv3 32 位整数会话
由于隧道是固定的,因此不会协商这些隧道,并且会在两端预配置它们。
此外,L2TPv3 支持以下可选参数。
rx_cookie=int
- rx 数据包的 l2tpv3 32 位整数 cookie - 与 GRE 密钥相同的功能,更多是为了防止配置错误而不是提供实际安全性tx_cookie=int
- tx 数据包的 l2tpv3 32 位整数 cookiecookie64=[0,1]
- 使用 64 位 cookie 而不是 32 位。counter=[0,1]
- 启用 l2tpv3 计数器pin_counter=[0,1]
- 假装计数器始终在每个数据包上重置(需要与某些真正损坏的实现互操作)v6=[0,1]
- 强制使用 v6 套接字udp=[0,1]
- 使用协议的原始套接字 (0) 或 UDP (1) 版本
L2TPv3 有许多注意事项
在原始模式下,每个IP地址只能使用一个连接。由于每个L2TPv3隧道直接终止于UML实例上,因此无法多路复用连接。UDP模式可以使用不同的端口来实现此目的。
以下是如何配置Linux主机通过L2TPv3连接到UML的示例
/etc/network/interfaces:
auto l2tp1
iface l2tp1 inet static
address 192.168.126.1
netmask 255.255.255.0
broadcast 192.168.126.255
mtu 1500
pre-up ip l2tp add tunnel remote 127.0.0.1 \
local 127.0.0.1 encap udp tunnel_id 2 \
peer_tunnel_id 2 udp_sport 1706 udp_dport 1707 && \
ip l2tp add session name l2tp1 tunnel_id 2 \
session_id 0xffffffff peer_session_id 0xffffffff
down ip l2tp del session tunnel_id 2 session_id 0xffffffff && \
ip l2tp del tunnel tunnel_id 2
所需权限:L2TPv3的原始IP模式需要CAP_NET_RAW
权限,UDP模式不需要特殊权限。
BESS socket传输¶
BESS是一个高性能的模块化网络交换机。
https://github.com/NetSys/bess
它支持一种简单的顺序数据包socket模式,较新的版本使用向量IO来实现高性能。
示例
vecX:transport=bess,src=$unix_src,dst=$unix_dst
这将使用unix_src Unix域socket地址作为源地址,unix_dst socket地址作为目标地址来配置BESS传输。
有关BESS配置以及如何分配BESS Unix域socket端口的信息,请参阅BESS文档。
https://github.com/NetSys/bess/wiki/Built-In-Modules-and-Ports
BESS传输不需要任何特殊权限。
VDE向量传输¶
虚拟分布式以太网(VDE)是一个项目,其主要目标是为虚拟网络提供高度灵活的支持。
http://wiki.virtualsquare.org/#/tutorials/vdebasics
VDE的常见用法包括快速原型设计和教学。
示例
vecX:transport=vde,vnl=tap://tap0
使用 tap0
vecX:transport=vde,vnl=slirp://
使用 slirp
vec0:transport=vde,vnl=vde:///tmp/switch
连接到 VDE 交换机
vecX:transport=\"vde,vnl=cmd://ssh remote.host //tmp/sshlirp\"
连接到远程 slirp(即时 VPN:将 ssh 转换为 VPN,它使用 sshlirp)https://github.com/virtualsquare/sshlirp
vec0:transport=vde,vnl=vxvde://234.0.0.1
连接到局域网云(在同一多播域(LAN)中的主机上运行的使用相同多播地址的所有UML节点将自动连接到虚拟LAN)。
运行UML¶
本节假设已在主机上安装了发行版中的 user-mode-linux 软件包或自定义构建的内核。
这些会向系统中添加一个名为 linux 的可执行文件。 这是 UML 内核。 它可以像任何其他可执行文件一样运行。 它将采用大多数正常的 linux 内核参数作为命令行参数。 此外,它还需要一些特定于 UML 的参数才能做一些有用的事情。
参数¶
强制参数:¶
mem=int[K,M,G]
- 内存量。 默认情况下以字节为单位。 它还将接受 K、M 或 G 限定符。ubdX[s,d,c,t]=
虚拟磁盘规范。 这实际上不是强制性的,但在几乎所有情况下都可能需要它,因此我们可以指定一个根文件系统。 最简单的图像规范是文件系统的图像文件的名称(使用创建映像中描述的方法之一创建)。UBD设备支持写时复制(COW)。 更改保存在一个单独的文件中,该文件可以被丢弃,从而允许回滚到原始的原始映像。 如果需要COW,则UBD映像指定为:
cow_file,master_image
。 示例:ubd0=Filesystem.cow,Filesystem.img
可以将UBD设备设置为使用同步IO。 任何写入都会立即刷新到磁盘。 这是通过在
ubdX
规范后添加s
来完成的。UBD对指定为单个文件名的设备执行一些启发式检查,以确保COW文件未被指定为映像。 要关闭它们,请在
ubdX
后使用d
标志。UBD支持TRIM - 请求宿主OS回收映像中任何未使用的块。 要关闭它,请在
ubdX
后指定t
标志。
root=
根设备 - 最有可能是/dev/ubd0
(这是一个Linux文件系统映像)
重要的可选参数¶
如果UML以“linux”运行且没有额外的参数,它将尝试为映像内配置的每个控制台启动一个xterm(在大多数Linux发行版中最多6个)。 每个控制台都在一个xterm中启动。 这使得在具有GUI的主机上使用UML变得非常容易。 但是,如果要将UML用作测试工具或在纯文本环境中运行,则这是错误的方法。
为了改变这种行为,我们需要指定一个替代控制台并将其连接到支持的“线路”通道之一。 为此,我们需要映射一个控制台以使用与默认xterm不同的东西。
将控制台编号 1 转移到 stdin/stdout 的示例
con1=fd:0,fd:1
UML支持各种串行线路通道,这些通道使用以下语法指定
conX=channel_type:options[,channel_type:options]
如果通道规范包含以逗号分隔的两个部分,则第一部分是输入,第二部分是输出。
空通道 - 丢弃所有输入或输出。 示例
con=null
将默认将所有控制台设置为空。fd通道 - 使用文件描述符编号进行输入/输出。 示例:
con1=fd:0,fd:1.
端口通道 - 在TCP端口号上启动telnet服务器。 示例:
con1=port:4321
。 主机必须具有/usr/sbin/in.telnetd(通常是telnetd软件包的一部分)和来自UML实用程序的port-helper(请参阅下面有关xterm通道的信息)。 在客户端连接之前,UML不会启动。pty和pts通道 - 使用系统pty/pts。
tty通道 - 绑定到现有的系统tty。 示例:
con1=/dev/tty8
将使UML使用主机第8个控制台(通常未使用)。xterm通道 - 这是默认设置 - 在此通道上启动一个xterm并将IO定向到它。 请注意,为了使xterm工作,主机必须安装UML发行版软件包。 这通常包含port-helper和UML与xterm通信所需的其他实用程序。 或者,需要从源代码编译和安装这些实用程序。 适用于控制台的所有选项也适用于UML串行线路,这些线路在UML中显示为ttyS。
启动UML¶
我们现在可以运行UML。
# linux mem=2048M umid=TEST \
ubd0=Filesystem.img \
vec0:transport=tap,ifname=tap0,depth=128,gro=1 \
root=/dev/ubda con=null con0=null,fd:2 con1=fd:0,fd:1
这将运行一个具有2048M RAM
的实例,并尝试使用名为Filesystem.img
的映像文件作为根。 它将使用tap0连接到主机。 除con1
之外的所有控制台都将被禁用,并且控制台1将使用标准输入/输出,使其出现在启动它的同一终端中。
登录¶
如果在生成映像时没有设置密码,则必须关闭UML实例,挂载映像,chroot到其中并设置它 - 如生成映像部分所述。 如果密码已设置,您可以直接登录。
UML管理控制台¶
除了使用普通的系统管理工具从“内部”管理映像之外,还可以使用UML管理控制台执行许多底层操作。 UML管理控制台是运行中的UML实例上内核的底层接口,有点像i386 SysRq接口。 由于UML下有一个完整的操作系统,因此与SysRq机制相比,可能具有更大的灵活性。
您可以使用mconsole接口执行许多操作
获取内核版本
添加和删除设备
停止或重新启动计算机
发送SysRq命令
暂停和恢复UML
检查UML内部运行的进程
检查UML内部/proc状态
您需要mconsole客户端(uml_mconsole),它是大多数Linux发行版中可用的UML工具包的一部分。
您还需要在UML内核中启用CONFIG_MCONSOLE
(在“General Setup”下)。 当您启动UML时,您会看到一行类似
mconsole initialized on /home/jdike/.uml/umlNJ32yL/mconsole
如果在UML命令行上指定唯一的机器ID,例如umid=debian
,您会看到这个
mconsole initialized on /home/jdike/.uml/debian/mconsole
该文件是uml_mconsole将用于与UML通信的socket。 使用umid或完整路径作为其参数运行它
# uml_mconsole debian
或
# uml_mconsole /home/jdike/.uml/debian/mconsole
您将获得一个提示,您可以在其中运行以下命令之一
version
help
halt
reboot
config
remove
sysrq
help
cad
stop
go
proc
stack
version¶
此命令不带任何参数。 它会打印UML版本
(mconsole) version
OK Linux OpenWrt 4.14.106 #0 Tue Mar 19 08:19:41 2019 x86_64
这有几个实际用途。 这是一个简单的空操作,可用于检查UML是否正在运行。 这也是向UML发送设备中断的一种方式。 UML mconsole在内部被视为UML设备。
help¶
此命令不带任何参数。 它会打印一个简短的帮助屏幕,其中包含支持的mconsole命令。
halt和reboot¶
这些命令不带任何参数。 它们会立即关闭机器,而无需同步磁盘,也无需干净地关闭用户空间。 所以,它们非常接近于崩溃机器
(mconsole) halt
OK
config¶
“config”向虚拟机添加一个新设备。 大多数UML设备驱动程序都支持此功能。 它带有一个参数,即要添加的设备,其语法与内核命令行相同
(mconsole) config ubd3=/home/jdike/incoming/roots/root_fs_debian22
remove¶
“remove”从系统中删除设备。 它的参数只是要删除的设备的名称。 设备必须处于驱动程序认为必要的任何意义上的空闲状态。 对于ubd驱动程序,删除的块设备不得挂载、交换或以其他方式打开,对于网络驱动程序,设备必须关闭
(mconsole) remove ubd3
sysrq¶
此命令带有一个参数,即一个字母。 它调用通用内核的SysRq驱动程序,该驱动程序执行该参数所需的操作。 有关有效字母及其功能的说明,请参阅您喜欢的内核树中的Linux Magic System Request Key Hacks中的SysRq文档。
cad¶
这将调用运行中映像中的Ctl-Alt-Del
操作。 最终执行的具体操作取决于init、systemd等。 通常,它会重新启动计算机。
stop¶
这会将UML置于一个循环中,读取mconsole请求,直到收到“go” mconsole命令。 这对于调试/快照工具非常有用。
go¶
这会在被“stop”命令暂停后恢复UML。 请注意,当UML恢复时,TCP连接可能已超时,如果UML暂停很长时间,crond可能会有点疯狂,运行所有它之前未完成的作业。
proc¶
这带有一个参数 - /proc中文件的名称,该文件将打印到mconsole标准输出
stack¶
这带有一个参数 - 进程的pid号。 它的堆栈将打印到标准输出。
高级UML主题¶
主机文件访问¶
如果您想从UML内部访问主机上的文件,您可以将其视为一台单独的机器,并从主机上nfs挂载目录,或者使用scp将文件复制到虚拟机中。 但是,由于UML在主机上运行,它可以像任何其他进程一样访问这些文件,并在虚拟机内部提供这些文件,而无需使用网络。 这可以通过hostfs虚拟文件系统来实现。 使用它,您可以将主机目录挂载到UML文件系统中,并像在主机上一样访问其中包含的文件。
安全警告
没有对UML映像设置任何参数的Hostfs将允许映像挂载主机文件系统的任何部分并写入。 如果运行UML,请始终将hostfs限制在特定的“无害”目录(例如/var/tmp
)。 如果UML以root身份运行,则这一点尤其重要。
使用hostfs¶
首先,确保在虚拟机中使用以下命令提供hostfs
# cat /proc/filesystems
应该列出hostfs
。 如果没有,请重新构建内核,并将hostfs配置到其中,或者确保hostfs构建为模块并且在虚拟机内部可用,并插入它。
现在您需要做的就是运行mount
# mount none /mnt/host -t hostfs
将主机的/
挂载到虚拟机的/mnt/host
上。 如果您不想挂载主机根目录,则可以使用mount的-o开关指定要挂载的子目录
# mount none /mnt/home -t hostfs -o /home
将主机的/home挂载到虚拟机的/mnt/home上。
将hostfs作为根文件系统¶
可以使用hostfs从主机上的目录层次结构启动,而不是使用文件中的标准文件系统。 首先,您需要该层次结构。 最简单的方法是循环挂载现有的root_fs文件
# mount root_fs uml_root_dir -o loop
您需要在etc/fstab
中将/
的文件系统类型更改为“hostfs”,因此该行如下所示
/dev/ubd/0 / hostfs defaults 1 1
然后,您需要将该目录中所有属于root的文件chown给自己。 这对我有用
# find . -uid 0 -exec chown jdike {} \;
接下来,确保您的UML内核已编译hostfs,而不是作为模块。 然后运行UML,并将启动设备指向该目录
ubd0=/path/to/uml/root/directory
然后,UML应像往常一样启动。
Hostfs注意事项¶
Hostfs不支持跟踪主机上主机文件系统的更改(在UML外部)。 因此,如果在UML不知情的情况下更改了文件,则UML将不知道它,并且其自身的文件的内存缓存可能已损坏。 虽然可以修复此问题,但这并不是当前正在处理的问题。
调整UML¶
UML目前是严格的单处理器。 但是,它将启动许多线程来处理各种功能。
UBD驱动程序、SIGIO和MMU仿真就是这样做的。 如果系统空闲,这些线程将被迁移到SMP主机上的其他处理器。 不幸的是,这通常会导致性能*降低*,因为内核之间存在所有缓存/内存同步流量。 因此,UML通常会受益于固定在单个CPU上,尤其是在大型系统上。 这可能会导致某些基准测试的性能差异高达5倍或更高。
类似地,在大型多节点NUMA系统上,如果UML的所有内存都从它将运行的同一NUMA节点分配,UML将会受益。 OS将*NOT*默认执行此操作。 为了做到这一点,系统管理员需要创建一个绑定到特定节点的合适的tmpfs ramdisk,并通过在TMP或TEMP环境变量中指定它来将其用作UML RAM分配的源。 UML将查找TMPDIR
、TMP
或TEMP
的值。 如果失败,它将查找挂载在/dev/shm
下的shmfs。 如果其他一切都失败,则无论用于它的文件系统类型如何,都使用/tmp/
mount -t tmpfs -ompol=bind:X none /mnt/tmpfs-nodeX
TEMP=/mnt/tmpfs-nodeX taskset -cX linux options options options..
贡献UML和使用UML进行开发¶
UML是一个出色的平台,用于开发新的Linux内核概念 - 文件系统、设备、虚拟化等。 它提供了无与伦比的机会来创建和测试它们,而无需受限于模拟特定硬件。
例如 - 想要尝试Linux如何使用4096个“正确的”网络设备?
UML没有问题。 同时,这对于其他虚拟化软件包来说很困难 - 它们受到它们尝试模拟的硬件总线上允许的设备数量的限制(例如,qemu中PCI总线上有16个)。
如果您有任何贡献,例如补丁、错误修复、新功能,请将其发送到linux-um@lists.infradead.org
。
请遵循所有标准的Linux补丁指南,例如cc-ing相关的维护人员并在您的补丁上运行./scripts/checkpatch.pl
。 有关更多详细信息,请参阅Documentation/process/submitting-patches.rst
注意 - 列表不接受HTML或附件,所有电子邮件必须格式化为纯文本。
开发始终与调试密不可分。 首先,您可以始终在gdb下运行UML,稍后将有一整节关于如何执行此操作。 但是,这并不是调试Linux内核的唯一方法。 通常,添加跟踪语句和/或使用UML特定的方法(例如ptrace UML内核进程)会提供更多信息。
跟踪UML¶
运行时,UML由一个主内核线程和许多辅助线程组成。 用于跟踪的那些*不是*UML作为其MMU仿真的一部分已经ptrace的那些。
这些通常是在ps显示中可见的前三个线程。 具有最低PID号并使用最多CPU的线程通常是内核线程。 其他线程是磁盘(ubd)设备辅助线程和SIGIO辅助线程。 在此线程上运行ptrace通常会导致以下图片
host$ strace -p 16566
--- SIGIO {si_signo=SIGIO, si_code=POLL_IN, si_band=65} ---
epoll_wait(4, [{EPOLLIN, {u32=3721159424, u64=3721159424}}], 64, 0) = 1
epoll_wait(4, [], 64, 0) = 0
rt_sigreturn({mask=[PIPE]}) = 16967
ptrace(PTRACE_GETREGS, 16967, NULL, 0xd5f34f38) = 0
ptrace(PTRACE_GETREGSET, 16967, NT_X86_XSTATE, [{iov_base=0xd5f35010, iov_len=832}]) = 0
ptrace(PTRACE_GETSIGINFO, 16967, NULL, {si_signo=SIGTRAP, si_code=0x85, si_pid=16967, si_uid=0}) = 0
ptrace(PTRACE_SETREGS, 16967, NULL, 0xd5f34f38) = 0
ptrace(PTRACE_SETREGSET, 16967, NT_X86_XSTATE, [{iov_base=0xd5f35010, iov_len=2696}]) = 0
ptrace(PTRACE_SYSEMU, 16967, NULL, 0) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=16967, si_uid=0, si_status=SIGTRAP, si_utime=65, si_stime=89} ---
wait4(16967, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGTRAP | 0x80}], WSTOPPED|__WALL, NULL) = 16967
ptrace(PTRACE_GETREGS, 16967, NULL, 0xd5f34f38) = 0
ptrace(PTRACE_GETREGSET, 16967, NT_X86_XSTATE, [{iov_base=0xd5f35010, iov_len=832}]) = 0
ptrace(PTRACE_GETSIGINFO, 16967, NULL, {si_signo=SIGTRAP, si_code=0x85, si_pid=16967, si_uid=0}) = 0
timer_settime(0, 0, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=0, tv_nsec=2830912}}, NULL) = 0
getpid() = 16566
clock_nanosleep(CLOCK_MONOTONIC, 0, {tv_sec=1, tv_nsec=0}, NULL) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGALRM {si_signo=SIGALRM, si_code=SI_TIMER, si_timerid=0, si_overrun=0, si_value={int=1631716592, ptr=0x614204f0}} ---
rt_sigreturn({mask=[PIPE]}) = -1 EINTR (Interrupted system call)
这是来自主要空闲UML实例的典型图片。
UML中断控制器使用epoll - 这是UML等待IO中断
epoll_wait(4, [{EPOLLIN, {u32=3721159424, u64=3721159424}}], 64, 0) = 1
ptrace调用序列是MMU仿真和运行UML用户空间的一部分。
timer_settime
是UML高分辨率计时器子系统的一部分,用于将来自UML内部的计时器请求映射到主机高分辨率计时器。clock_nanosleep
是UML进入空闲状态(类似于PC执行ACPI空闲状态的方式)。
正如你所看到的,即使在空闲状态下,UML也会生成相当多的输出。在观察IO时,这些输出信息量很大。它显示了实际的IO调用、它们的参数和返回值。
内核调试¶
现在你可以在gdb下运行UML,尽管它不一定同意在gdb下启动。如果你试图跟踪一个运行时错误,最好是将gdb附加到一个正在运行的UML实例,并让UML运行。
假设与前一个例子中相同的PID号码,这将是
# gdb -p 16566
这将停止UML实例,因此你必须在GDB命令行输入 cont 来请求它继续。最好将其做成一个gdb脚本,并作为参数传递给gdb。
开发设备驱动程序¶
几乎所有的UML驱动程序都是单片的。虽然可以将UML驱动程序构建为内核模块,但这会将可能的功能限制为仅限内核和非UML特定的。这样做的原因是,为了真正利用UML,需要编写一段用户空间代码,将驱动程序概念映射到实际的用户空间主机调用。
这构成了驱动程序的所谓的“用户”部分。虽然它可以重用许多内核概念,但它通常只是另一段用户空间代码。这一部分需要一些匹配的“内核”代码,这些代码驻留在UML镜像中,并实现Linux内核部分。
注意:在“内核”和“用户”交互方式上几乎没有限制.
UML没有严格定义的内核到主机API。它不试图模拟特定的架构或总线。UML的“内核”和“用户”可以共享内存、代码,并根据需要进行交互,以实现软件开发人员头脑中的任何设计。唯一的限制是纯技术性的。由于许多函数和变量具有相同的名称,因此开发人员应注意他们试图引用哪些包含和库。
因此,许多用户空间代码由简单的包装器组成。例如,os_close_file()
只是一个围绕 close()
的包装器,它确保用户空间函数close不会与内核部分中类似命名的函数冲突。
将UML用作测试平台¶
UML是设备驱动程序开发的绝佳测试平台。与大多数UML的东西一样,“可能需要一些用户组装”。构建仿真环境取决于用户。目前,UML仅提供内核基础设施。
该基础设施的一部分是加载和解析fdt设备树blob的能力,就像在Arm或Open Firmware平台中使用的一样。这些作为内核命令行的可选额外参数提供
dtb=filename
设备树在启动时加载和解析,并且可以由查询它的驱动程序访问。目前,此功能仅用于开发目的。UML自己的设备不查询设备树。
安全注意事项¶
驱动程序或任何新功能应默认不接受任意文件名、bpf代码或其他可能从UML实例内部影响主机的参数。例如,在UML命令行指定用于驱动程序和主机之间IPC通信的套接字在安全方面是可以的。允许它作为可加载模块参数则不然。
如果特定应用程序需要此类功能(例如,为原始套接字网络传输加载BPF“固件”),则应默认关闭,并且应在启动时显式地将其作为命令行参数打开。
即使考虑到这一点,UML和主机之间的隔离级别也相对较弱。如果允许UML用户空间加载任意内核驱动程序,攻击者可以使用它来突破UML。因此,如果在生产应用程序中使用UML,建议在启动时加载所有模块,并在之后禁用内核模块加载。