tia-chan's blog

kvm-config

简介

介绍 Linux 上的 KVM 配置、virtio-win 驱动、挂载只读文件、VirtIO-FS 或 Samba 共享文件夹、为 Anki 配置端口转发

检查

检测 CPU 是否支持虚拟化

egrep -c '(vmx|svm)' /proc/cpuinfo

如果返回 0,需进入 BIOS 启用虚拟化(通常名为 Intel VT-x 或 AMD-V),如果是其他数字则支持

检测内核是否加载

lsmod | grep kvm

以下为正常输出

kvm_intel 或 kvm_amd
kvm

若无输出,则需要手动加载

sudo modprobe kvm

Intel CPU 加载:

sudo modprobe kvm_intel

AMD CPU 加载:

sudo modprobe kvm_amd

windows 镜像

win10 镜像可在微软官网下载

https://www.microsoft.com/zh-cn/software-download/windows10ISO

win7 镜像可在第三方存档站点下载

https://hellowindows.cn/

安装

下载

qemu-system-x86:虚拟机

virt-manager:图形化管理工具(GUI)

libvirt-daemon-system: virt-manager 的依赖

libvirt-clients:管理虚拟机的 CLI 工具

sudo apt install qemu-system-x86 libvirt-daemon-system libvirt-clients virt-manager

查看并添加组

查看 libvirt

getent group | grep libvirt

类似输出如下

libvirt:x:125:
libvirt-qemu:x:64055:libvirt-qemu

查看 kvm

getent group | grep kvm

类似输出如下

kvm:x:104:

备注:输出格式为 组名:密码占位符:GID:成员列表

添加当前用户到 kvm 和 libvirt 组(否则每次都需要 sudo 命令)

sudo usermod -aG libvirt $(whoami)
sudo usermod -aG kvm $(whoami)

备注:注销并重新登陆后生效

创建虚拟机

基础

启动虚拟机 GUI

virt-manager

点击 文件 - 新建虚拟机 - 选择本地安装介质

一般 win7 选择 1 核 2GB 内存,win10 选择 2 核 4GB 内存

务必在最后一步勾选在安装前自定义配置

进入自定义配置界面后,选择左侧菜单栏 SATA CDROM1 并点击右下角 remove 移除 windows 镜像,稍后会添加

下载 windows 驱动 virtio-win.iso
https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso

点击添加硬件,点击存储,选择 CDROM 设备,SATA 类型,手动选择刚刚下载的 virtio-win.iso 路径

点击添加硬件,点击存储,选择 CDROM 设备,SATA 类型,手动选择 windows 镜像路径

在引导选项中确保将 SATA CDROM 2(Windows ISO)拖到启动顺序首位
,SATA CDROM 1(VirtIO 驱动)在第二位,SATA 磁盘 1 和 NIC 在末尾

在左侧 SATA 磁盘 1 选项中,磁盘总线选择 virtio

在 NIC 网卡选项中,设备型号选择 virtio

网络

检查 default 网络状态

sudo virsh net-list --all

启动 default 网络

sudo virsh net-start default

设置 default 网络自启动

sudo virsh net-autostart default

检查 libvirtd 服务状态

sudo systemctl status libvirtd

如果 default 网络仍然无法启动,可能是 libvirtd 服务未运行:

sudo systemctl start libvirtd
sudo systemctl enable libvirtd

KVM 默认使用 iptables/nftables 管理 NAT 网络。如果防火墙规则被清空,可能导致网络无法激活。可以尝试重启 libvirtd 重新生成规则:

sudo systemctl restart libvirtd

验证网络是否正常工作

sudo virsh net-info default
# 类似如下输出
# 名称:       default
# UUID:       xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# 活动:       是
# 持久:       是
# 自动启动:   是
# 桥接:       virbr0

检查 virbr0 接口是否存在

ip a show virbr0

如果还无法启动,可以删除并重建 default 网络

sudo virsh net-destroy default
sudo virsh net-undefine default
sudo virsh net-define /usr/share/libvirt/networks/default.xml
sudo virsh net-start default
sudo virsh net-autostart default

启动

点击左上角开始安装

驱动

当提示"您想将 Windows 安装在哪里"时:

点击"加载驱动程序"

浏览选择 virtio-win.iso(这个驱动器里面包含多个 windows 驱动)

viostor 驱动,用来加载 VirtIO 磁盘,如果不安装,安装系统时会提示 “找不到磁盘”

win10 路径通常为:E:\viostor\w10\amd64,win7 路径通常为 E:\viostor\w7\amd64(如果仍然看不到驱动,尝试 取消勾选 "隐藏与此计算机不兼容的驱动器")

备注:格式为 驱动名称\系统版本\系统位数,可根据自己的情况选择

网络驱动:virtio-win.iso\NetKVM[系统版本][系统位数]

如果你虚拟机安装的是 win10 及以上版本,那么以下的驱动可以进入系统后 自动安装;如果是 win7 及以下版本,则需要 手动安装

内存驱动:virtio-win.iso\Balloon[系统版本][系统位数](动态调整虚拟机内存使用)

增强串口通信(某些工具如 SPICE 依赖它):virtio-win.iso\vioserial[系统版本][系统位数]

优化鼠标/键盘输入(减少延迟):virtio-win.iso\vioinput[系统版本][系统位数]

显卡驱动:virtio-win.iso\qxl[系统版本][系统位数]

熵生成设备驱动:virtio-win.iso\viorng[系统版本][系统位数]

系统安装完成后,win10 可以直接运行 virtio-win.iso 根目录下的 virtio-win-guest-tools.exe(自动安装所有驱动,仅 win10 以上有用)

挂载只读目录

创建共享目录

mkdir ~/shared-folder

将共享文件夹的组改为 libvirt-qemu,并确保组有读权限

sudo chown $(whoami):libvirt-qemu ~/shared-folder
sudo chmod 750 ~/shared-folder

自动创建目录(由于 tmp 目录中的文件重启后会自动删除)

echo "d /tmp/virtiofs-upper 0750 root root -" | sudo tee /etc/tmpfiles.d/virtiofs.conf
echo "d /tmp/virtiofs-work 0750 root root -" | sudo tee -a /etc/tmpfiles.d/virtiofs.conf

立即生效

sudo systemd-tmpfiles --create

手动挂载 overlayfs(主机重启后失效,需配置 systemd 自启动)
说明:将 shared-folder 挂载到 readonly-share 是为了防止虚拟机传播病毒到主机,此时虚拟机对共享文件夹的写入操作会存到 virtiofs-upper 内存中,而不是磁盘,重启会自动删除(实际上只有读取权限,并不会写入,配置是为了以防万一)

sudo mkdir -p /mnt/readonly-share
sudo mount -t overlay overlay -o \
lowerdir=/home/<your-user>/shared-folder,\
upperdir=/tmp/virtiofs-upper,\
workdir=/tmp/virtiofs-work,\
nosuid,noexec,ro /mnt/readonly-share

验证挂载属性

mount | grep readonly-share
# 应该会输出:...ro... (read-only)

备注:其他命令(当你不想使用共享文件夹时会用到的命令)

# 查看所有的挂载点
mount | grep overlay

# 卸载挂载点
sudo umount /mnt/readonly-share

# 恢复默认设置
sudo mount -o remount,exec /mnt/readonly-share

设置开机自动挂载

这里以 systemd 为例配置自启动,当然还有其他方式,但不是很推荐,例如 fstab

sudo vim /etc/systemd/system/virtiofs-mount.service

编辑 virtiofs-mount.service 文件并添加以下内容

[Unit]
Description=Mount OverlayFS Share
Requires=systemd-tmpfiles-setup.service
After=systemd-modules-load.service systemd-tmpfiles-setup.service local-fs.target
# 显式依赖systemd-tmpfiles-setup服务,即创建/tmp/virtiofs-upper和/tmp/virtiofs-work目录后再挂载。
# After显式声明则表示在内核模块,基础文件系统服务加载后再挂载

[Service]
Type=oneshot

# 确保目录存在(如果没有配置 tmpfiles.d 的话,可以添加以下命令,但由于我们之前配置过了,这里就注释掉了)
# ExecStartPre=/bin/mkdir -p /tmp/virtiofs-upper /tmp/virtiofs-work /mnt/readonly-share

ExecStart=/bin/mount -t overlay overlay -o lowerdir=/home/<your-user>/shared-folder,upperdir=/tmp/virtiofs-upper,workdir=/tmp/virtiofs-work,nosuid,noexec,ro /mnt/readonly-share
RemainAfterExit=yes

# 卸载命令(可选)
ExecStop=/bin/umount /mnt/readonly-share

# 确保挂载后状态持续
RemainAfterExit=yes

# 超时设置(防止阻塞)
TimeoutStartSec=30s

[Install]
WantedBy=multi-user.target

刷新并启动

# sudo umount /mnt/readonly-share #如果之前手动挂载过的话,需要先解除,不然测试就没有效果了
sudo systemctl daemon-reload
sudo systemctl enable --now virtiofs-mount.service

验证

mount | grep readonly-share
systemctl status virtiofs-mount.service

检查挂载点是否真正只读

touch /mnt/readonly-share/test_file # 应该报错 "Read-only file system"

小插曲

以上是使用 systemd 方式来配置自动挂载,其实还可以通过 fstab,但是呢,尽量不要编辑/etc/fstab 文件(或像如下命令间接编辑),如果配置错误,会无法开机,并进入 emergency mode(紧急模式),况且,就算你配置没问题,也会带来一些小问题(怎么突然押韵上了),比如无法显式控制加载顺序,比如在内核加载后,目录创建后再挂载

echo "overlay /mnt/readonly-share overlay lowerdir=/home/<your-user>/shared-folder,upperdir=/tmp/virtiofs-upper,workdir=/tmp/virtiofs-work,nosuid,noexec,ro 0 0" | sudo tee -a /etc/fstab

如果“不小心”进入了紧急模式,并无法开机进入桌面,会提示输入 root 密码,类似输出如下(因为我是真的进入过 QwQ)

# You are in emergency mode. After logging in, type "journalctl -xb" to view
# system logs, "systemctl reboot" to reboot, "systemctl default" or "exit"
# to boot into default mode.
# Give rootpassuord for maintenance
# (or press: Control-D to continue):

输入密码,进入终端,由于紧急模式下的根文件目录一般是只读挂载,我们可以检查一下(不要忘记字符串里面空格的说)

mount | grep " / "

如果输出为 ro(read-only),需要重新挂载为可写

mount -o remount,rw /

注释掉你之前添加到/etc/fstab 文件的内容

sudo vim /etc/fstab

退出终端会自动重启

exit

VirtIO-FS 共享文件夹 (win10)

检查内核模块是否加载

lsmod | grep virtiofs

如果没有输出,需要加载模块

sudo modprobe virtiofs

让主机开机自动加载 virtiofs 模块

sudo tee /etc/modules-load.d/virtiofs.conf <<< "virtiofs"

重启后验证是否生效

sudo systemctl restart systemd-modules-load
lsmod | grep virtiofs

获取所有虚拟机名称

sudo virsh list --all

关闭虚拟机运行,并编辑虚拟机 xml 配置文件

sudo virsh edit win10 # "win10" 是我的虚拟机名称

在 devices 标签内部添加:

<filesystem type="mount" accessmode="passthrough">
  <driver type="virtiofs"/>
  <source dir="/mnt/readonly-share"/>
  <target dir="shared_mount"/>
</filesystem>

在 vcpu 标签之前添加

<memoryBacking>
    <source type="memfd"/>
    <access mode="shared"/>
</memoryBacking>

修改 cpu 标签为

<cpu mode='host-passthrough' check='none' migratable='on'>
  <numa>
    <cell id='0' cpus='0-1' memory='4194304' unit='KiB' memAccess='shared'/>
  </numa>
</cpu>

VirtIO-FS 依赖 WinFSP 才能在 Windows 上运行,因此需要先在虚拟机安装它:

下载地址 https://winfsp.dev/rel/

安装好后检查 C:\Program Files (x86)\WinFsp\bin 文件夹是否包含 winfsp-x64.dll

win+r 输入 sysdm.cpl,确保系统环境变量包含 C:\Program Files (x86)\WinFsp\bin

win+r 输入 devmgmt.msc,点击系统设备,找到 VirtIO-FS Device,右键选择 "更新驱动程序",指向 virtio-fs/viofs 目录(如 E:\virtio-fs\w10\amd64 或者 E:\viofs\w10\amd64)

win+r 输入 services.msc,找到 VirtIO-FS Service 并启动它(最好在属性里改为自动)

现在可以在 win+e 打开资源管理器中看到 shared_mount 共享文件夹,并且可以发现虚拟机无法直接创建和修改共享文件夹的文件,因此即使虚拟机被病毒感染,也无法通过共享文件夹传播给主机

Sumba 共享文件夹 (win7)

以上的 virtio-fs 的驱动并不支持 win7,而且开发者表示并不打算兼容旧系统,可以从下方的开发者回复中看到

https://github.com/virtio-win/kvm-guest-drivers-windows/issues/728

但我们可以尝试使用 Samba 来共享文件夹,这个比 virtio-fs 简单,而且还不用配置虚拟机的 xml 文件

安装 sumba

sudo apt install samba samba-common

编辑配置文件

sudo vim /etc/samba/smb.conf

文件末尾添加以下内容

[win7_share]
path = /mnt/readonly-share
browsable = yes
writable = no # 不可写
guest ok = no            # 关闭匿名访问
valid users = <your-username> # 自定义你的用户名
read only = yes # 只读(与 writable=no 等效)
force user = root # 强制以 root 身份访问(由于是只读挂载,并没有安全风险)

设置密码

sudo smbpasswd -a <your-username>

其他命令

# 修改密码
sudo smbpasswd <your-username>

# 删除用户
sudo smbpasswd -x <your-username>

每次修改 samba 配置文件时要记得重启

sudo systemctl restart smbd nmbd
sudo systemctl enable smbd nmbd

检查状态

sudo systemctl status smbd nmbd

查看 linux 主机 virbr0 网关地址(一般为 192.168.122.1),建议填写网关地址,而不是主机 ip ,是因为局域网的 ip 会变动,而每次都需要修改很麻烦

ip addr show

在 win7 虚拟机中查看是否可以连接主机

ping <your-ip>

若无法连接,请检查 nat 网络,打开 win7 虚拟机命令提示符,运行:

sc query lanmanworkstation

如果显示 STATE : RUNNING,表示 SMB 服务已启动。

如果没有显示,则需要手动开启,win+r 输入 control,进入控制面板,点击 程序 - 程序和功能 - 打开或关闭 windows 功能 -勾选 SMB/CIFS 文件共享支持,重启虚拟机

在 linux 主机检查状态

smbclient -N -L //127.0.0.1

打开计算机 → 右键添加一个网络位置

填写共享路径

\\<your-ip>\win7_share
# 备注:格式为 \\宿主机网关地址\共享名

之后输入你之前设置的用户名和密码就可以访问啦

其他安全配置

先用虚拟机连接到共享文件夹,然后在主机执行以下命令查询连接的 ip

sudo smbstatus

编辑 samba 配置文件

sudo vim /etc/samba/smb.conf

在[global]字段中,添加你的 ip

hosts allow = <your-ip> 127.0.0.1
hosts deny = ALL

如果你所在的局域网没有其他人使用的话,也可以添加整个局域网 ip 段(这样比较省事,因为虚拟机的自身 ip 可能会变动)

hosts allow = 192.168.0.0/16

重启 sumba

sudo systemctl restart smbd nmbd

配置 Ankiweb 端口转发

由于 Ankiweb 不支持 win7,不方便使用 LunaTranslator 添加卡片来学习单词,但是我们可以使用端口转发来解决这个问题

原理:虚拟机中运行的 LunaTranslator 发送卡片数据到虚拟机 8765 端口,虚拟机 8765 端口转发到宿主机网关 IP 对应的 8765 端口,Linux 主机运行的 Anki 监听到请求并通过原路返回响应

首先需要 Linux 主机安装好 Anki,并安装 AnkiConnect 插件

安装插件步骤:从 AnkiConnect 获取数字代码,打开 Anki - 工具 - 插件 - 获取插件 - 输入数字代码

打开 Anki 的工具栏 - 插件 - AnkiConnect - 双击打开 - 编辑 webBindAddress 的地址为 0.0.0.0

查看 linux 主机的 virbr0 网关地址(一般为 192.168.122.1)

ip addr show

在 win7 虚拟机中测试连接

win+r 输入 control,进入控制面板,点击 程序 - 程序和功能 - 打开或关闭 windows 功能 - 勾选 telnet 客户端

telnet <your-ip> 8765

如果黑屏且光标闪烁代表连接成功(关闭终端窗口即可关闭连接,或者使用 ctrl+] 快捷键+ quit 命令退出,但有时会不起作用)
由于 LunaTranslator 只能修改连接 anki 的端口,无法修改为主机的网关 ip,因此需要配置端口转发

添加端口转发规则(NAT 规则)

netsh interface portproxy add v4tov4 listenport=8765 listenaddress=127.0.0.1 connectport=8765 connectaddress=<your-ip>

验证规则是否生效

netsh interface portproxy show all
# 输出类似如下
# 监听地址     : 127.0.0.1
# 监听端口     : 8765
# 连接地址     : 192.168.122.1
# 连接端口     : 8765

测试连接

telnet 127.0.0.1 8765

如果没问题的话,就可以使用啦 w