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_ADMINCAP_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 - 設定介面 MTU

  • headroom=int - 調整預設預留空間(32 位元組),以防資料包需要重新封裝到例如 VXLAN 中。

  • vec=0 - 停用多包 IO 並回退到一次一個資料包模式

共享選項

  • ifname=str 繫結到本地網路介面的傳輸有一個共享選項 - 要繫結的介面的名稱。

  • src, dst, src_port, dst_port - 所有使用具有源和目的地和/或源埠和目標埠概念的套接字的傳輸都使用這些來指定它們。

  • v6=[0,1] 指定是否需要所有透過 IP 執行的傳輸的 v6 連線。 此外,對於在 v4 和 v6 上的執行方式存在一些差異的傳輸(例如 EoL2TPv3),設定正確的操作模式。 如果沒有此選項,套接字型別將根據 src 和 dst 引數解析/分析的內容來確定。

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(又名 GRETAPGREIRB)隧道,該隧道會將 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 位整數 cookie

  • cookie64=[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來共享檔案系統。 這與從共享磁碟啟動兩臺物理機器相同。 這將導致檔案系統損壞。

使用分層塊裝置

在兩個虛擬機器之間共享檔案系統的方法是使用ubd塊驅動程式的寫時複製(COW)分層功能。 任何更改的塊都儲存在私有COW檔案中,而讀取來自任一裝置 - 如果請求的塊在其私有檔案中有效,則來自私有檔案,否則來自共享檔案。 使用此方案,大多數未更改的資料在任意數量的虛擬機器之間共享,每個虛擬機器都有一個包含其所做更改的小得多的檔案。 對於從大型根檔案系統啟動的大量UML,這可以節省大量的磁碟空間。

共享檔案系統資料也有助於提高效能,因為主機能夠使用更少的記憶體來快取共享資料,因此UML磁碟請求將從主機的記憶體而不是其磁碟提供服務。 在多socket NUMA機器上執行此操作時,存在一個主要的警告。 在此類硬體上,執行具有共享主映像和COW更改的許多UML例項可能會導致諸如來自過多socket間流量的NMIs的問題。

如果您在此類高階硬體上執行UML,請確保使用taskset命令將UML繫結到位於同一socket上的一組邏輯CPU,或者檢視“tuning”部分。

要將寫時複製層新增到現有的塊裝置檔案,只需將COW檔案的名稱新增到相應的ubd開關

ubd0=root_fs_cow,root_fs_debian_22

其中root_fs_cow是私有COW檔案,root_fs_debian_22是現有的共享檔案系統。 COW檔案不需要存在。 如果不存在,驅動程式將建立並初始化它。

磁碟使用情況

UML具有TRIM支援,它會將磁碟映像檔案中任何未使用的空間釋放到底層OS。 務必使用ls -ls或du來驗證實際的檔案大小。

COW有效性。

對主映像的任何更改都將使所有COW檔案無效。 如果發生這種情況,UML將*NOT*自動刪除任何COW檔案,並且將拒絕啟動。 在這種情況下,唯一的解決方案是恢復舊映像(包括其上次修改時間戳)或刪除所有COW檔案,這將導致重新建立它們。 COW檔案中的任何更改都將丟失。

Cows can moo - uml_moo:將COW檔案與其後備檔案合併

根據您使用UML和COW裝置的方式,建議不時將COW檔案中的更改合併到後備檔案中。

執行此操作的實用程式是uml_moo。 它的用法是

uml_moo COW_file new_backing_file

無需指定後備檔案,因為該資訊已在COW檔案標頭中。 如果您很偏執,請啟動新的合併檔案,如果您對此感到滿意,請將其移到舊後備檔案上。

uml_moo預設情況下建立一個新的後備檔案作為安全措施。 它還具有破壞性合併選項,該選項會將COW檔案直接合併到其當前的後備檔案中。 這實際上僅在後備檔案僅與一個COW檔案關聯時才可用。 如果有多個COW與後備檔案關聯,則其中一個的-d合併將使所有其他COW檔案無效。 但是,如果您缺少磁碟空間,這將很方便,並且應該比非破壞性合併快得多。

uml_moo與UML發行版軟體包一起安裝,並且作為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將查詢TMPDIRTMPTEMP的值。 如果失敗,它將查詢掛載在/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,建議在啟動時載入所有模組,並在之後停用核心模組載入。