资讯动态

源码编译GCC 9.2、Python 3.8、QEMU 4.2与Libvirt 6.0虚拟化工具链实战

发布时间:2026/10/13 13:45:44 来源:尧图企业网站定制
1. 为什么要在源码层面折腾这套虚拟化工具链把 GCC 9.2、Python 3.8、Libvirt 6.0、QEMU 4.2 这四个东西凑在一起从源码编译安装听起来像是自找麻烦。系统自带的包管理器一条命令就能搞定的事为什么要花几个小时甚至一整天去编译这个问题我在动手之前也反复问过自己。答案其实很直接版本锁定与功能可控。很多生产环境或者实验环境对版本有严格要求比如某个内核模块只兼容特定版本的 QEMU或者某个管理平台依赖 Libvirt 6.0 的特定 API而系统仓库里提供的版本要么太新要么太旧。更常见的情况是你需要开启一些默认编译选项里没有的功能比如特定的网络后端、存储驱动、或者调试符号。这些需求在二进制包面前基本无解只能自己动手。另一个现实原因是依赖链的连锁反应。Libvirt 6.0 对 QEMU 的版本有最低要求QEMU 4.2 的编译又依赖较新的 GCC 和 Python。如果你单独升级其中一个系统里其他组件可能因为动态库版本冲突而崩溃。所以最稳妥的做法是把这一整套工具链装到一个独立的前缀目录下比如/opt/stack或者/usr/local/stack让它们自成一体不污染系统原有的环境。这套组合的典型应用场景包括搭建私有云底层的计算节点、做虚拟化相关的开发调试、复现某些特定版本下的 bug、以及构建定制化的虚拟机管理平台。适合有一定 Linux 基础、熟悉源码编译流程、并且愿意花时间读文档和排错的读者。如果你只是想快速跑个虚拟机那直接用发行版仓库里的包就行没必要走这条路。我这次的操作环境是一个干净的 Linux 发行版内核版本 5.x系统自带的 GCC 是 7.xPython 是 3.6Libvirt 和 QEMU 都没有安装。目标是把这四个组件全部编译安装到/opt/stack下并且保证它们之间能正常协作。下面我把整个过程的思路、命令、踩过的坑和验证方法完整记录下来。2. 编译前的环境准备与依赖梳理2.1 系统基础依赖的安装清单在动手编译任何东西之前先把系统层面的构建工具和库装齐。这一步偷懒的话后面会在各种configure报错里反复挣扎。我习惯一次性把可能用到的都装上省得来回折腾。对于基于 RPM 的发行版基础命令如下sudo yum groupinstall Development Tools sudo yum install -y wget curl git tar xz bzip2 \ gmp-devel mpfr-devel libmpc-devel \ zlib-devel openssl-devel libffi-devel \ readline-devel sqlite-devel bzip2-devel \ xz-devel ncurses-devel libuuid-devel \ numactl-devel libaio-devel libcap-ng-devel \ glib2-devel gnutls-devel libxml2-devel \ libnl3-devel cyrus-sasl-devel \ device-mapper-devel parted-devel \ libpciaccess-devel yajl-devel \ gettext-devel autoconf automake libtool对于基于 DEB 的发行版对应的包名会有所不同核心是build-essential、libgmp-dev、libmpfr-dev、libmpc-dev、zlib1g-dev、libssl-dev、libffi-dev、libreadline-dev、libsqlite3-dev、libbz2-dev、liblzma-dev、libncurses-dev、uuid-dev、libnuma-dev、libaio-dev、libcap-ng-dev、libglib2.0-dev、libgnutls28-dev、libxml2-dev、libnl-3-dev、libsasl2-dev、libdevmapper-dev、libpciaccess-dev、libyajl-dev、gettext、autoconf、automake、libtool。注意libnl3-devel和libnl-3-dev这两个包在编译 Libvirt 时是必须的缺少的话 configure 阶段会直接报错说找不到 netlink 库。很多人第一次编译 Libvirt 就卡在这里。2.2 目录规划与多版本共存策略我强烈建议把所有自编译的组件装到一个独立前缀下比如/opt/stack。这样做的好处是卸载时直接删目录就行不会留下散落在/usr/local各处的文件。同时通过设置PKG_CONFIG_PATH和LD_LIBRARY_PATH可以让后续编译的组件优先找到这个前缀下的库。export STACK_PREFIX/opt/stack export PATH$STACK_PREFIX/bin:$PATH export LD_LIBRARY_PATH$STACK_PREFIX/lib:$STACK_PREFIX/lib64:$LD_LIBRARY_PATH export PKG_CONFIG_PATH$STACK_PREFIX/lib/pkgconfig:$STACK_PREFIX/lib64/pkgconfig:$PKG_CONFIG_PATH export C_INCLUDE_PATH$STACK_PREFIX/include:$C_INCLUDE_PATH export CPLUS_INCLUDE_PATH$STACK_PREFIX/include:$CPLUS_INCLUDE_PATH把这几个 export 写进~/.bashrc或者一个单独的env.sh里每次编译前 source 一下。这里有个细节LD_LIBRARY_PATH的顺序很重要新前缀必须放在系统路径前面否则运行时可能加载到系统旧版本的库导致莫名其妙的段错误。2.3 编译顺序的依赖逻辑这四个组件的编译顺序不能乱必须按照 GCC → Python → QEMU → Libvirt 的顺序来。原因很简单Python 的编译需要 GCCQEMU 的编译需要 Python用于生成一些代码和运行测试脚本Libvirt 的编译需要 QEMU 提供的某些头文件和工具同时也需要 Python 来生成绑定。组件编译依赖运行依赖预计编译时间GCC 9.2系统 GCC、GMP、MPFR、MPC无1-3 小时Python 3.8GCC 9.2、zlib、openssl、libffi无10-20 分钟QEMU 4.2GCC 9.2、Python 3.8、glib2、pixmanPython 3.820-40 分钟Libvirt 6.0GCC 9.2、Python 3.8、QEMU 4.2、libxml2QEMU 4.2、Python 3.810-20 分钟编译时间取决于机器性能我用的是一台 8 核 16G 的机器GCC 编译花了将近两个小时。如果你机器核数少时间会更长。建议在开始之前确认磁盘空间充足整个编译过程加上源码包大概需要 15-20GB 的空间。3. GCC 9.2 源码编译从引导到安装3.1 下载与依赖库准备GCC 的源码包可以从官方镜像站获取。下载gcc-9.2.0.tar.xz之后解压到一个工作目录。GCC 的编译需要三个数学库GMP、MPFR、MPC。这三个库可以单独编译安装也可以让 GCC 的编译脚本自动下载。我倾向于手动下载并放到源码目录下这样编译过程更可控。cd /opt/build wget https://ftp.gnu.org/gnu/gcc/gcc-9.2.0/gcc-9.2.0.tar.xz tar xf gcc-9.2.0.tar.xz cd gcc-9.2.0 ./contrib/download_prerequisitesdownload_prerequisites脚本会自动下载 GMP、MPFR、MPC 和 ISL 的源码包并创建符号链接。这一步需要网络畅通如果下载失败可以手动下载对应的 tar 包放到源码根目录下脚本会识别到已存在的文件并跳过下载。3.2 configure 参数的选择与解释GCC 的 configure 参数比较多但核心的就那么几个。我用的配置如下mkdir build cd build ../configure \ --prefix$STACK_PREFIX \ --enable-languagesc,c,fortran \ --disable-multilib \ --with-system-zlib \ --enable-shared \ --enable-threadsposix \ --program-suffix-9.2逐个解释一下这些参数的含义。--prefix指定安装路径这个不用多说。--enable-languages指定要编译哪些前端我只需要 C、C 和 Fortran所以只选了这三个。如果你需要 Go 或者 Ada可以加上但编译时间会显著增加。--disable-multilib表示只编译 64 位版本不生成 32 位兼容库这能节省大量时间。--with-system-zlib使用系统自带的 zlib避免重复编译。--enable-shared生成动态链接库这样其他程序链接 GCC 的库时更方便。--program-suffix-9.2给所有生成的可执行文件加上后缀这样系统里可以同时存在多个版本的 GCC通过gcc-9.2来调用。提示如果你打算用这个 GCC 替换系统默认编译器就不要加--program-suffix但那样做风险较大可能导致系统工具编译失败。我建议保留后缀在需要时显式指定。3.3 编译过程中的资源控制与常见报错GCC 的编译非常吃内存每个编译进程大概需要 1-2GB 内存。如果你用make -j8开 8 个并行任务内存不足时会被 OOM Killer 杀掉表现为编译进程突然消失没有任何错误信息。我的经验是内存小于 16GB 的机器并行数不要超过核数的一半。可以用make -j4先试试如果内存充足再增加。make -j4 make install-stripinstall-strip会在安装时去掉调试符号节省磁盘空间。如果你需要调试 GCC 本身就用make install。常见的报错之一是configure: error: C compiler missing or inoperational。这通常是因为系统自带的 GCC 版本太老或者 C 编译器没有安装。解决方法是先安装系统仓库里的gcc-c包用它来引导编译新的 GCC。另一个坑是fatal error: gmp.h: No such file or directory。这说明 GMP 的头文件没有被正确找到。如果你用的是download_prerequisites自动下载的方式检查一下gmp目录是否存在以及configure时是否在build目录下执行。有时候需要手动指定--with-gmp-include和--with-gmp-lib。编译完成后验证一下$STACK_PREFIX/bin/gcc-9.2 --version应该输出gcc (GCC) 9.2.0。如果报错说找不到共享库检查LD_LIBRARY_PATH是否包含了$STACK_PREFIX/lib64。4. Python 3.8 编译让解释器认识新编译器4.1 用新 GCC 引导 Python 编译Python 3.8 的编译相对简单但有一个关键点必须确保用的是刚编译好的 GCC 9.2而不是系统自带的旧版本。否则 Python 会用旧编译器编译可能无法利用新 GCC 的优化特性甚至在某些平台上出现兼容性问题。cd /opt/build wget https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tar.xz tar xf Python-3.8.0.tar.xz cd Python-3.8.0 export CC$STACK_PREFIX/bin/gcc-9.2 export CXX$STACK_PREFIX/bin/g-9.2 ./configure \ --prefix$STACK_PREFIX \ --enable-shared \ --enable-optimizations \ --with-ensurepipinstall \ --with-openssl/usr \ --enable-loadable-sqlite-extensions--enable-optimizations会启用 PGOProfile Guided Optimization编译时间会变长但生成的 Python 解释器性能更好。--enable-shared生成libpython3.8.so这样其他程序可以动态链接 Python 库。--with-ensurepipinstall确保 pip 被安装。--enable-loadable-sqlite-extensions允许 SQLite 加载扩展模块某些 Python 库会用到。4.2 共享库路径与 pip 配置编译安装完成后需要把 Python 的共享库路径加入到系统配置中否则运行python3.8时会报error while loading shared libraries: libpython3.8.so.1.0。echo $STACK_PREFIX/lib | sudo tee /etc/ld.so.conf.d/stack-python.conf sudo ldconfig然后验证$STACK_PREFIX/bin/python3.8 --version $STACK_PREFIX/bin/pip3 --versionpip 默认会从官方源下载包如果网络环境需要配置镜像源可以修改~/.pip/pip.conf或者$STACK_PREFIX/pip.conf。我一般会设置一个国内镜像源加快下载速度。注意Python 3.8 的--enable-optimizations会导致编译时间增加大约 30%-50%但运行性能提升大概 10%-20%。如果你只是用来跑 Libvirt 的生成脚本可以不加这个参数节省时间。4.3 验证 Python 与 GCC 的协作编译完 Python 之后写一个简单的测试脚本确认 Python 能正常调用 GCC 编译扩展模块# test_compile.py import sysconfig import subprocess cc sysconfig.get_config_var(CC) print(fPython was built with: {cc}) subprocess.run([cc, --version])运行这个脚本应该输出gcc-9.2的版本信息。如果输出的是系统旧版 GCC说明CC环境变量没有生效需要重新编译 Python。5. QEMU 4.2 编译虚拟化核心的定制化构建5.1 configure 选项的取舍与功能裁剪QEMU 的 configure 选项非常多全部启用会编译很久而且很多功能你根本用不到。我的策略是只启用需要的目标架构和功能禁用无关的组件。cd /opt/build wget https://download.qemu.org/qemu-4.2.0.tar.xz tar xf qemu-4.2.0.tar.xz cd qemu-4.2.0 ./configure \ --prefix$STACK_PREFIX \ --target-listx86_64-softmmu,x86_64-linux-user \ --enable-kvm \ --enable-vhost-net \ --enable-spice \ --enable-usb-redir \ --enable-libssh \ --enable-numa \ --disable-werror \ --python$STACK_PREFIX/bin/python3.8--target-list指定要编译的目标架构。x86_64-softmmu是系统模拟用于跑完整的虚拟机x86_64-linux-user是用户态模拟用于跑单个 Linux 程序。如果你不需要用户态模拟可以去掉后者节省编译时间。--enable-kvm启用硬件加速这是必须的否则虚拟机性能会差很多。--enable-vhost-net启用内核态的虚拟网络加速。--enable-spice启用 SPICE 协议支持用于远程桌面。--disable-werror关闭“警告视为错误”避免因为新 GCC 的严格检查导致编译失败。提示--python参数指定使用的 Python 解释器。如果不指定QEMU 会从 PATH 里找python3可能找到系统旧版本。显式指定可以避免很多奇怪的问题。5.2 编译加速与错误处理QEMU 的编译可以用make -j$(nproc)全速跑因为它对内存的需求没有 GCC 那么大。但如果你同时开了其他编译任务还是要注意内存。make -j8 make install常见的编译错误包括ERROR: glib-2.0 required说明glib2-devel没装ERROR: pixman 0.21.8 not present说明pixman-devel没装。这些依赖在之前的系统准备阶段应该已经装好了如果还报错检查PKG_CONFIG_PATH是否包含了系统库的 pkgconfig 目录。另一个坑是--enable-spice需要spice-protocol和spice-server的开发包。如果不需要 SPICE直接去掉这个选项。我遇到过因为 SPICE 版本不匹配导致编译失败的情况后来干脆禁用了用 VNC 代替。5.3 验证 QEMU 的 KVM 加速与网络后端编译安装完成后验证 QEMU 是否能正常工作$STACK_PREFIX/bin/qemu-system-x86_64 --version $STACK_PREFIX/bin/qemu-system-x86_64 -accel kvm -machine q35 -cpu host -m 1024 -nographic如果 KVM 可用会输出 QEMU 的版本信息和 KVM 加速已启用的提示。如果报错Could not access KVM kernel module说明当前用户没有权限访问/dev/kvm需要把用户加入kvm组或者用sudo运行。网络后端方面检查vhost-net是否可用$STACK_PREFIX/bin/qemu-system-x86_64 -netdev help应该能看到tap、user、vhost-user等选项。如果vhost-net没有编译进去这里不会显示。6. Libvirt 6.0 编译管理层的最后一块拼图6.1 依赖 QEMU 路径的指定技巧Libvirt 的 configure 脚本会自动探测 QEMU 的位置。如果 QEMU 安装在非标准路径下需要显式指定cd /opt/build wget https://libvirt.org/sources/libvirt-6.0.0.tar.xz tar xf libvirt-6.0.0.tar.xz cd libvirt-6.0.0 ./configure \ --prefix$STACK_PREFIX \ --with-qemu \ --with-qemu-user$STACK_PREFIX/bin/qemu-system-x86_64 \ --with-qemu-groupkvm \ --without-xen \ --without-vbox \ --without-lxc \ --without-openvz \ --without-vmware \ --without-phyp \ --without-esx \ --with-python \ --enable-debugno--with-qemu-user指定 QEMU 系统模拟器的完整路径。--without-xen等选项禁用不需要的虚拟化后端减少编译时间和依赖。--with-python启用 Python 绑定这样可以用 Python 脚本管理 Libvirt。注意Libvirt 默认会尝试探测所有支持的虚拟化后端如果某个后端的开发包没装configure 会报错。用--without-xxx显式禁用可以避免这个问题。6.2 编译安装与权限配置make -j8 make install安装完成后需要配置 Libvirt 的运行环境。首先是创建必要的目录和用户sudo mkdir -p /var/run/libvirt sudo mkdir -p /var/lib/libvirt/images sudo mkdir -p /var/lib/libvirt/qemu sudo groupadd -r kvm 2/dev/null || true sudo usermod -aG kvm $USER然后是配置文件。Libvirt 的主配置文件在$STACK_PREFIX/etc/libvirt/libvirtd.conf。需要修改几个关键项unix_sock_group kvm unix_sock_ro_perms 0777 unix_sock_rw_perms 0770 auth_unix_ro none auth_unix_rw none这些配置允许kvm组的用户无需密码即可连接 Libvirt 守护进程。生产环境应该启用更严格的认证但实验环境这样配置比较方便。6.3 启动守护进程与连接测试启动 Libvirt 守护进程$STACK_PREFIX/sbin/libvirtd -d然后用virsh连接$STACK_PREFIX/bin/virsh -c qemu:///system version如果输出包含 Libvirt 版本、QEMU 版本和运行状态说明连接成功。如果报错Failed to connect socket to /var/run/libvirt/libvirt-sock检查libvirtd是否真的在运行以及 socket 文件的权限。$STACK_PREFIX/bin/virsh -c qemu:///system capabilities | head -20这个命令会输出 Libvirt 探测到的虚拟化能力包括支持的 CPU 模型、迁移特性、安全驱动等。如果 QEMU 路径配置正确这里应该能看到 QEMU 4.2 的相关信息。7. 四件套联调验证与典型故障排查7.1 从零创建一台虚拟机的完整链路验证整套工具链是否正常工作的最好方法就是实际创建一台虚拟机。我准备了一个最小的 Linux 镜像用virt-install来创建$STACK_PREFIX/bin/virt-install \ --name test-vm \ --ram 1024 \ --vcpus 1 \ --disk path/var/lib/libvirt/images/test-vm.qcow2,size5 \ --cdrom /opt/images/minimal.iso \ --os-variant generic \ --network networkdefault \ --graphics vnc,listen0.0.0.0 \ --noautoconsole这条命令会调用 QEMU 创建虚拟机Libvirt 负责管理生命周期。如果一切正常virsh list应该能看到test-vm处于 running 状态。$STACK_PREFIX/bin/virsh list --all如果虚拟机启动失败查看日志sudo tail -f /var/log/libvirt/qemu/test-vm.log日志里会显示 QEMU 的完整命令行和错误输出是排查问题的第一手资料。7.2 动态库加载失败的排查思路这套自编译工具链最常见的问题就是动态库加载失败。表现是运行某个命令时提示error while loading shared libraries: libxxx.so.x: cannot open shared object file。排查步骤如下用ldd检查可执行文件依赖哪些库ldd $STACK_PREFIX/bin/virsh | grep not found如果某个库显示not found确认它是否存在于$STACK_PREFIX/lib或$STACK_PREFIX/lib64下。检查/etc/ld.so.conf.d/下是否有包含$STACK_PREFIX/lib的配置文件。如果没有添加一个并运行sudo ldconfig。如果库存在但仍然找不到可能是LD_LIBRARY_PATH没有生效。用echo $LD_LIBRARY_PATH确认。提示ldconfig缓存的是系统级库路径LD_LIBRARY_PATH是进程级环境变量。两者优先级不同LD_LIBRARY_PATH优先于ldconfig缓存。如果两者冲突以LD_LIBRARY_PATH为准。7.3 版本不匹配导致的运行时错误另一个常见问题是版本不匹配。比如 Libvirt 编译时链接的是 QEMU 4.2 的库但运行时加载了系统里的 QEMU 2.x 库导致 API 调用失败。排查方法是检查 Libvirt 实际加载的 QEMU 库路径ldd $STACK_PREFIX/sbin/libvirtd | grep qemu如果输出指向/usr/lib而不是$STACK_PREFIX/lib说明LD_LIBRARY_PATH没有正确设置或者ldconfig缓存了旧路径。解决方法是确保LD_LIBRARY_PATH在启动libvirtd之前已经设置并且$STACK_PREFIX/lib在路径的最前面。故障现象可能原因排查命令解决方案启动 libvirtd 报库找不到LD_LIBRARY_PATH 未设置ldd libvirtd | grep not found设置环境变量并重启virsh 连接失败socket 权限不对ls -l /var/run/libvirt/修改 libvirtd.conf 权限QEMU 启动报 KVM 不可用用户不在 kvm 组groupsusermod -aG kvm $USER虚拟机创建失败存储池未定义virsh pool-list定义并启动 default 池Python 绑定导入失败PYTHONPATH 未设置python3.8 -c import libvirt设置 PYTHONPATH 到 site-packages7.4 Python 绑定与自动化脚本的衔接Libvirt 的 Python 绑定安装后需要确保 Python 能找到它。通常绑定会被安装到$STACK_PREFIX/lib/python3.8/site-packages下。验证方法$STACK_PREFIX/bin/python3.8 -c import libvirt; print(libvirt.getVersion())如果报ModuleNotFoundError检查PYTHONPATH是否包含了 site-packages 目录。可以在~/.bashrc里加上export PYTHONPATH$STACK_PREFIX/lib/python3.8/site-packages:$PYTHONPATH写一个简单的 Python 脚本测试连接import libvirt conn libvirt.open(qemu:///system) if conn is None: print(Failed to open connection) exit(1) print(fLibvirt version: {conn.getLibVersion()}) print(fQEMU version: {conn.getVersion()}) print(fHostname: {conn.getHostname()}) for domain in conn.listAllDomains(): print(fDomain: {domain.name()}, State: {domain.state()}) conn.close()这个脚本能跑通说明 Libvirt 的 Python 绑定和 QEMU 的协作没有问题。8. 把这套工具链用顺手之后的几点体会整套编译安装流程走下来最耗时的其实是 GCC其他三个组件加起来可能都没有 GCC 一半的时间长。但 GCC 又是最不能省的一步因为 Python 和 QEMU 的编译质量直接受编译器影响。我试过用系统自带的 GCC 7 编译 QEMU 4.2结果在链接阶段报了一堆关于std::filesystem的未定义引用换成 GCC 9.2 之后问题消失。所以如果你的系统 GCC 版本低于 8强烈建议先升级 GCC。另一个体会是环境变量的持久化。编译过程中设置的PATH、LD_LIBRARY_PATH、PKG_CONFIG_PATH如果只在当前终端生效下次开新终端就没了会导致各种“昨天还好好的今天就不行了”的诡异问题。我的做法是写一个env.sh放在项目根目录每次操作前source env.sh并且把这个 source 命令加到~/.bashrc里。这样无论什么时候打开终端环境都是对的。还有一点关于磁盘空间GCC 的源码目录加上编译产物轻松超过 5GB。QEMU 的编译目录也有 2-3GB。如果/opt分区空间不够可以把编译目录放到一个大容量的数据盘上安装时再指定--prefix到/opt/stack。我就是这么干的源码放在/data/build安装到/opt/stack两不耽误。最后分享一个排查编译错误的通用技巧看 config.log。configure 阶段报错时终端输出的信息往往很简略真正的错误原因在config.log里。用grep -i error config.log快速定位比一行行翻终端输出高效得多。这个习惯帮我省了很多时间尤其是在处理 Libvirt 这种依赖复杂的项目时。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑