查看操作系统发行版本

1
cat /etc/os-release

对于apt源(Ubuntu)

安装qemu工具

我们要安装这些工具

1
sudo apt install qemu qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager -y
  • qemu: 模拟器核心
  • qemu-kvm: 支持硬件加速(Intel VT-x 或 AMD-V)
  • virt-manager: 图形化管理工具(可选)
  • libvirt-*: 虚拟机管理守护进程和客户端工具

获取基础启动镜像

对于Ubuntu虚拟机,我们可以使用wget从网络下载并改名

1
2
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
mv jammy-server-cloudimg-amd64.img ubuntu-linux5.15.img

编写启动脚本

针对本项目我们的启动脚本文件start_vm.sh的内容为

1
2
3
4
5
6
7
qemu-system-x86_64 -gdb tcp::8440 -m 64G -smp 16 --enable-kvm -drive "file=ubuntu-linux5.15.img",format=qcow2,if=virtio \
-cdrom seed.iso \
-netdev tap,id=mynet0,ifname=tapmintp_1,script=no,downscript=no -device e1000,netdev=mynet0,mac=52:55:00:d1:55:04 \
-netdev tap,id=mynet1,ifname=tap_inmintp_1,script=no,downscript=no -device e1000,netdev=mynet1,mac=52:55:00:d1:56:04 \
-virtfs local,path=./mintp_vm,mount_tag=host0,security_model=passthrough,id=host0 \
-netdev user,id=mynet2 -device e1000,netdev=mynet2,mac=52:55:00:d1:57:04 \
-nographic -monitor none -serial stdio

生成配置文件

我们创建一个user-data文件,并写入如下内容

1
2
3
4
5
6
7
8
9
10
#cloud-config
hostname: ubuntu-qemu
users:
- name: root
plain_text_passwd: root123s
lock_passwd: false
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_pwauth: true
disable_root: false

再创建一个空文件meta-data

然后执行命令创建配置文件

1

初始化网桥和网关的脚本

我们创建文件setup_env.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/bin/bash
set -e

# ===============================================
# QEMU 虚拟网络自动配置脚本(支持 NAT + DHCP)
# 自动清理旧桥接与 TAP 接口,避免 "Device busy" 错误
# 适用于:openEuler / CentOS / Ubuntu
# ===============================================

# ---- 用户可配置参数 ----
BR_MAIN="br0" # 外部桥接网桥
BR_INNER="br4" # 内部桥接网桥
TAP_OUT="tapgayu_1" # 外部 TAP
TAP_IN="tap_ingayu_1" # 内部 TAP
HOST_IP_MAIN="100.0.0.1/24" # 宿主机外部桥 IP
HOST_IP_INNER="192.168.56.1/24" # 宿主机内部桥 IP
DHCP_RANGE_MAIN="100.0.0.10,100.0.0.10,12h"
DHCP_RANGE_INNER="192.168.56.10,192.168.56.100,12h"
WAN_IF=$(ip route get 8.8.8.8 | awk '/dev/ {print $5; exit}') # 自动检测外网接口

# ---- 工具检测 ----
for cmd in ip sysctl dnsmasq; do
if ! command -v $cmd &>/dev/null; then
echo "[-] 缺少命令: $cmd,请先安装"
exit 1
fi
done

# ---- 自动清理旧网络 ----
echo "[*] 清理旧网络配置..."
for iface in $TAP_OUT $TAP_IN $BR_MAIN $BR_INNER; do
if ip link show "$iface" &>/dev/null; then
echo " - 删除接口 $iface"
ip link set "$iface" down || true
ip link delete "$iface" type bridge 2>/dev/null || ip tuntap del dev "$iface" mode tap 2>/dev/null || true
fi
done
killall dnsmasq 2>/dev/null || true
sleep 1

# ---- 加载 tun 模块 ----
echo "[+] 加载 tun 模块"
modprobe tun || true

# ---- 创建外部桥接网络 ----
echo "[+] 创建外部桥接网络 $BR_MAIN"
ip link add name $BR_MAIN type bridge
ip addr add $HOST_IP_MAIN dev $BR_MAIN
ip link set $BR_MAIN up

echo "[+] 创建 TAP 接口 $TAP_OUT"
ip tuntap add dev $TAP_OUT mode tap
ip link set $TAP_OUT master $BR_MAIN
ip link set $TAP_OUT up

# ---- 创建内部桥接网络 ----
echo "[+] 创建内部桥接网络 $BR_INNER"
ip link add name $BR_INNER type bridge
ip addr add $HOST_IP_INNER dev $BR_INNER
ip link set $BR_INNER up

echo "[+] 创建 TAP 接口 $TAP_IN"
ip tuntap add dev $TAP_IN mode tap
ip link set $TAP_IN master $BR_INNER
ip link set $TAP_IN up

# ---- 启用 IP 转发和 NAT ----
echo "[+] 启用 IP 转发和 NAT"
sysctl -w net.ipv4.ip_forward=1 >/dev/null
iptables -t nat -C POSTROUTING -o $WAN_IF -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -o $WAN_IF -j MASQUERADE

# ---- 启动 DHCP 服务 (dnsmasq) ----
echo "[+] 启动 DHCP 服务 (dnsmasq)"
mkdir -p /var/run/qemu-dhcp
dnsmasq --interface=$BR_MAIN \
--bind-interfaces \
--except-interface=lo \
--dhcp-range=$DHCP_RANGE_MAIN \
--pid-file=/var/run/qemu-dhcp/dnsmasq-$BR_MAIN.pid \
--dhcp-option=3,100.0.0.1 \
--log-facility=/var/run/qemu-dhcp/dnsmasq-$BR_MAIN.log \
--conf-file= &

sleep 1

# ---- 检查启动状态 ----
if ! pgrep -f "dnsmasq.*$BR_MAIN" >/dev/null; then
echo "[-] DHCP 服务启动失败,请检查日志 /var/run/qemu-dhcp/dnsmasq-$BR_MAIN.log"
exit 1
fi

# ---- 打印网络状态 ----
echo "[+] 网络配置完成"
ip addr show $BR_MAIN
ip addr show $BR_INNER
echo
echo "[+] dnsmasq 已启动,日志目录:/var/run/qemu-dhcp/"
echo "[+] 虚拟机连接 tap 接口后,将自动通过 DHCP 获得 IP"

获取要替换的内核

  1. 首先我们获取Linux内核源码,由于部分内核版本会出现编译环境的兼容性问题,这里我依然选择验证可用的Linux5.15.77,来源是阿里云的镜像站🔗
1
2
3
4
wget https://mirrors.aliyun.com/linux-kernel/v5.x/linux-5.15.77.tar.gz

tar -zxvf ./linux-5.15.77.tar.gz
cd ./linux-5.15.77
  1. 然后我们配置一下该内核,使用它自带的图形化配置工具
    1
    make menuconfig
    操作方式很像遥控器遥控老式电视机的菜单,具体方法如图

首先我们开启GCOV功能

  • 按菜单路径General architecture options > GCOV-based kernel profiling > Enable gcov-based kernel profiling进入三级菜单并选中Enable gcov-based kernel profiling
  • 空格让它的选中状态变成[*]
  • 菜单选中Save并回车执行
  • 菜单选中Exit回车执行退出编辑

如果出现了模块签名问题,我们可以尝试编辑.config文件找到如下选项,如果内容不一样,则修改成一致的

1
2
3
4
5
# CONFIG_MODULE_SIG is not set
# CONFIG_MODULE_SIG_ALL is not set
# CONFIG_MODULE_SIG_FORCE is not set
CONFIG_SYSTEM_TRUSTED_KEYS=""
CONFIG_SYSTEM_REVOCATION_KEYS=""

尤其是下面的

1
CONFIG_MODULE_SIG=y

要修改为

1
# CONFIG_MODULE_SIG is not set

其次我们编辑.config开启9p文件系统,这样能够支持文件夹的挂载

1
2
3
4
5
6
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y
CONFIG_9P_FS=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_PCI=y
# CONFIG_NET_9P_DEBUG is not set

如果是直接修改的.config文件,则应该执行下面的指令应用修改,而不是make menuconfig,防止修改被覆盖

1
make menuconfig

配置完成后编译和内核

1
2
3
make -j$(nproc)	# 编译内核

make bindeb-pkg -j$(nproc)

然后我们获得如下文件

  • linux-headers-.deb
  • linux-image-.deb
  • linux-libc-dev-.deb
  • 如果有个带dbg的,可以省略不安装

为qemu虚拟机替换内核

我们要进入虚拟机启动挂载,然后利用挂载把打包好的内核文件传入虚拟机

1
2
3
chmod 700 ./setup_env.sh ./start_vm.sh
./setup_env.sh
./start_vm.sh

然后登录虚拟机的终端,并在虚拟机内执行下面的命令

1
mount -t 9p trans=virtio host0 /home/host_dir -oversion=9p2000.L

这样就在/home/host_dir目录创建挂载点,与宿主机的/<path>/<to>/mintp_vm目录互通

然后在宿主机将打包好的*.deb文件放入/<path>/<to>/mintp_vm/

并进入虚拟机执行dpkg -i /home/host_dir/mintp_vm/*.deb

安装好内核后,我们通过修改grub配置为虚拟机替换启动内核

在虚拟机内执行如下指令,查看已安装的内核版本,找到并复制我们安装的linux-image-5.15.77(或者你自定义的版本名

1
dpkg --list | grep linux-image

然后在虚拟机内vi /etc/default/grub修改其中的GRUB_DEFAULT条目,剩下的不动

1
GRUB_DEFAULT="linux-image-5.15.77"

保存修改后执行更新命令,并重启虚拟机

1
2
update-grub
reboot

uname -r可以查看当前内核版本

对于yum源(EulerOS/CentOS)

安装qemu工具

安装工具

1
sudo yum install -y qemu-kvm qemu-img libvirt virt-install bridge-utils

启动相关服务

1
2
sudo systemctl enable libvirtd
sudo systemctl start libvirtd

检查状态

1
systemctl status libvirtd

在 openEuler/EulerOS 上,QEMU 有时被安装在:

1
/usr/libexec/qemu-kvm

因此我们需要创建符号链接到usr/bin/目录下,方便直接用 qemu-system-x86_64

1
sudo ln -s /usr/libexec/qemu-kvm /usr/bin/qemu-system-x86_64

获取基础启动镜像

这次我们用openEuler,我们下载压缩包并解压

1
2
3
4
5
wget https://repo.openeuler.org/openEuler-22.03-LTS/virtual_machine_img/x86_64/openEuler-22.03-LTS-x86_64.qcow2.xz  
xz -d openEuler-22.03-LTS-x86_64.qcow2.xz

mv openEuler-22.03-LTS-x86_64.qcow2 openEuler_linux5.15.qcow2

配置虚拟机登录参数

  1. ssh登录参数

我们创建文件user-data,并写入如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#cloud-config
hostname: ubuntu-qemu
users:
- name: san
plain_text_passwd: 20050101@h
lock_passwd: false
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
- name: root
plain_text_passwd: 20050101@h
lock_passwd: false
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_pwauth: true
disable_root: false
  1. 网络参数
    对于openEuler,更建议单独一个文件写网络参数,我们创建文件network-config

    1
    2
    3
    4
    5
    6
    7
    8
    version: 2
    ethernets:
    eth0:
    dhcp4: no
    addresses: [100.0.0.10/24]
    gateway4: 100.0.0.1
    nameservers:
    addresses: [8.8.8.8, 1.1.1.1]
  2. 生成配置文件

    1
    cloud-localds cloud-init.iso user-data meta-data --network-config=network-config

然而不知道哪里出了问题,死活不读入配置文件

所以我们用点暴力手段,直接修改root密码

挂载qcow2镜像文件内的文件夹到宿主机并修改

1
2
modprobe nbd max_part=8
qemu-nbd -c /dev/nbd0 openEuler_linux5.15.qcow2

查看哪个才是系统根目录

1
fdisk -l /dev/nbd0

输出

1
2
3
4
Device        Start      End  Sectors  Size Type
/dev/nbd0p1 227328 21389278 21161951 10.1G Linux filesystem
/dev/nbd0p14 2048 10239 8192 4M BIOS boot
/dev/nbd0p15 10240 227327 217088 106M EFI System

我们找Linux filesystem那一栏

1
2
3
4
5
6
mount /dev/nbd0p1 /mnt			# 开始挂载
rm -rf /mnt/var/lib/cloud/* # 清除cloud-init的缓存
# 启用 root 登录
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /mnt/etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /mnt/etc/ssh/sshd_config
chroot /mnt bash -c "echo 'root:00221155@h' | chpasswd"

最后我们取消挂载

1
2
umount /mnt
qemu-nbd -d /dev/nbd0

初始化网桥和网关

我们写一个脚本自动化初始化网桥和网关,让虚拟机能够被ssh远程登录

文件为setup_env.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#!/bin/bash
set -e

# ===============================================
# QEMU 虚拟网络自动配置脚本(支持 NAT + DHCP)
# 自动清理旧桥接与 TAP 接口,避免 "Device busy" 错误
# 适用于:openEuler / CentOS / Ubuntu
# ===============================================

# ---- 用户可配置参数 ----
BR_MAIN="br0" # 外部桥接网桥
BR_INNER="br4" # 内部桥接网桥
TAP_OUT="tapgayu_1" # 外部 TAP
TAP_IN="tap_ingayu_1" # 内部 TAP
HOST_IP_MAIN="100.0.0.1/24" # 宿主机外部桥 IP
HOST_IP_INNER="192.168.56.1/24" # 宿主机内部桥 IP
DHCP_RANGE_MAIN="100.0.0.10,100.0.0.10,12h"
DHCP_RANGE_INNER="192.168.56.10,192.168.56.100,12h"
WAN_IF=$(ip route get 8.8.8.8 | awk '/dev/ {print $5; exit}') # 自动检测外网接口

# ---- 工具检测 ----
for cmd in ip sysctl dnsmasq; do
if ! command -v $cmd &>/dev/null; then
echo "[-] 缺少命令: $cmd,请先安装"
exit 1
fi
done

# ---- 自动清理旧网络 ----
echo "[*] 清理旧网络配置..."
for iface in $TAP_OUT $TAP_IN $BR_MAIN $BR_INNER; do
if ip link show "$iface" &>/dev/null; then
echo " - 删除接口 $iface"
ip link set "$iface" down || true
ip link delete "$iface" type bridge 2>/dev/null || ip tuntap del dev "$iface" mode tap 2>/dev/null || true
fi
done
killall dnsmasq 2>/dev/null || true
sleep 1

# ---- 加载 tun 模块 ----
echo "[+] 加载 tun 模块"
modprobe tun || true

# ---- 创建外部桥接网络 ----
echo "[+] 创建外部桥接网络 $BR_MAIN"
ip link add name $BR_MAIN type bridge
ip addr add $HOST_IP_MAIN dev $BR_MAIN
ip link set $BR_MAIN up

echo "[+] 创建 TAP 接口 $TAP_OUT"
ip tuntap add dev $TAP_OUT mode tap
ip link set $TAP_OUT master $BR_MAIN
ip link set $TAP_OUT up

# ---- 创建内部桥接网络 ----
echo "[+] 创建内部桥接网络 $BR_INNER"
ip link add name $BR_INNER type bridge
ip addr add $HOST_IP_INNER dev $BR_INNER
ip link set $BR_INNER up

echo "[+] 创建 TAP 接口 $TAP_IN"
ip tuntap add dev $TAP_IN mode tap
ip link set $TAP_IN master $BR_INNER
ip link set $TAP_IN up

# ---- 启用 IP 转发和 NAT ----
echo "[+] 启用 IP 转发和 NAT"
sysctl -w net.ipv4.ip_forward=1 >/dev/null
iptables -t nat -C POSTROUTING -o $WAN_IF -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -o $WAN_IF -j MASQUERADE

# ---- 启动 DHCP 服务 (dnsmasq) ----
echo "[+] 启动 DHCP 服务 (dnsmasq)"
mkdir -p /var/run/qemu-dhcp
dnsmasq --interface=$BR_MAIN \
--bind-interfaces \
--except-interface=lo \
--dhcp-range=$DHCP_RANGE_MAIN \
--pid-file=/var/run/qemu-dhcp/dnsmasq-$BR_MAIN.pid \
--dhcp-option=3,100.0.0.1 \
--log-facility=/var/run/qemu-dhcp/dnsmasq-$BR_MAIN.log \
--conf-file= &

sleep 1

# ---- 检查启动状态 ----
if ! pgrep -f "dnsmasq.*$BR_MAIN" >/dev/null; then
echo "[-] DHCP 服务启动失败,请检查日志 /var/run/qemu-dhcp/dnsmasq-$BR_MAIN.log"
exit 1
fi

# ---- 打印网络状态 ----
echo "[+] 网络配置完成"
ip addr show $BR_MAIN
ip addr show $BR_INNER
echo
echo "[+] dnsmasq 已启动,日志目录:/var/run/qemu-dhcp/"
echo "[+] 虚拟机连接 tap 接口后,将自动通过 DHCP 获得 IP"


编写启动脚本

因为我这次的宿主机本身就是虚拟机且已经占用了kvm加速,因此我们本次创建的虚拟机不启用kvm加速

文件为start_vm.sh

1
2
3
4
5
6
qemu-system-x86_64 -gdb tcp::8440 -m 8G -smp 16 -drive "file=openEuler_linux5.15.qcow2",format=qcow2,if=virtio \
-cdrom cloud-init.iso \
-netdev tap,id=mynet0,ifname=tapgayu_1,script=no,downscript=no -device e1000,netdev=mynet0,mac=52:55:00:d1:55:04 \
-netdev tap,id=mynet1,ifname=tap_ingayu_1,script=no,downscript=no -device e1000,netdev=mynet1,mac=52:55:00:d1:56:04 \
-virtfs local,path=./gayu_vm,mount_tag=host0,security_model=passthrough,id=host0 \
-nographic -monitor none -serial stdio

获取要替换的内核

  1. 首先我们获取Linux内核源码,由于部分内核版本会出现编译环境的兼容性问题,这里我依然选择验证可用的Linux5.15.77,来源是阿里云的镜像站🔗
1
2
3
4
wget https://mirrors.aliyun.com/linux-kernel/v5.x/linux-5.15.77.tar.gz

tar -zxvf ./linux-5.15.77.tar.gz
cd ./linux-5.15.77
  1. 然后我们配置一下该内核,使用它自带的图形化配置工具
    1
    make menuconfig
    操作方式很像遥控器遥控老式电视机的菜单,具体方法如图

如果出现了模块签名问题,我们可以尝试编辑.config文件关闭如下选项

1

  1. 接着我们使用多线程并发编译

    1
    make -j$(nproc)
  2. 完成后打包内核

    1
    make rpm-build -j$(nproc)
  3. 将打包文件发送到虚拟机
    一般打包出的文件在~/rpmbuild/RPMS/x86_64/*.rpm,这里我们用scp远程发送

1
scp ~/rpmbuild/RPMS/x86_64/*.rpm root@100.0.0.10:/home/linux/
  1. 安装新内核
    1
    2
    cd /home/linux/
    rpm -ivh ./*.rpm
  2. 修改启动内核
    我们接下来要修改grub文件并更新配置

首先我们查询可用内核版本

1
grep "menuentry '" /boot/grub2/grub.cfg | cut -d"'" -f2

输出如下

1
2
3
4
[root@pool-100-0-0-10 linux]# grep "menuentry '" /boot/grub2/grub.cfg | cut -d"'" -f2
openEuler (5.15.77K51577) 22.03 LTS
openEuler (5.10.0-60.18.0.50.oe2203.x86_64) 22.03 LTS
UEFI Firmware Settings

接下来我们修改grub文件

1
vi /etc/default/grub

然后我们修改下面的条目值为:

1
GRUB_DEFAULT="openEuler (5.15.77K51577) 22.03 LTS"

然后更新grub配置