当前位置: 首页 > news >正文

快速上手QEMU:创建你的第一个虚拟机实例

在计算机技术的广阔天地中,虚拟化技术无疑是一颗璀璨的明星,为我们的计算环境带来了前所未有的灵活性和效率。而在虚拟化的众多工具和技术中,Qemu 虚拟机管理器以其独特的魅力和强大的功能,吸引着无数技术爱好者和专业人士的目光;Qemu,全称 Quick Emulator,是一款开源的虚拟机监视器。它诞生于 2003 年,由 Fabrice Bellard 发起开发 ,最初的目标是提供一个通用的模拟器和虚拟化工具。经过多年的发展和开源社区的积极贡献,Qemu 已经从一个简单的项目,成长为一款功能强大且备受欢迎的虚拟机软件,被广泛应用于开发、测试、实验以及云计算等众多领域。

从本质上讲,Qemu是一种高效的硬件模拟器,它能够在软件中模拟完整的机器,包括各种硬件设备,如CPU、内存、硬盘、网卡等 。这意味着,通过Qemu,我们可以在一台物理计算机上创建多个虚拟的计算机环境,每个环境都可以运行不同的操作系统和应用程序,就像它们运行在真实的物理机器上一样。

而且,Qemu 支持多种处理器架构的模拟和虚拟化,如 x86、ARM、PowerPC、SPARC 等 。这种跨架构的模拟能力,使得 Qemu 在不同的硬件平台和应用场景中都能发挥重要作用,无论是进行 x86 架构的服务器虚拟化,还是进行 ARM 架构的嵌入式系统开发,Qemu 都能提供有力的支持。

一、Qemu简介

1.1 Qemu概述

Qemu 是一款开源的硬件模拟器和虚拟机监控器,其全称为 Quick Emulator,即快速模拟器 。它允许用户在一台物理计算机上创建和运行多个虚拟机,每个虚拟机都可以运行不同的操作系统,就像它们在独立的物理计算机上运行一样。Qemu 的特别之处在于,它不仅能够模拟常见的 x86 架构,还对 ARM、PowerPC、MIPS、RISC-V 等多种硬件架构提供了出色的支持。

这使得开发者可以在 x86 架构的主机上,轻松模拟运行 ARM 架构的操作系统和应用程序,为跨平台开发和测试提供了极大的便利。例如,在开发基于 ARM 架构的嵌入式系统时,开发者可以利用 Qemu 在普通的 PC 上搭建模拟环境,进行代码编写、调试和测试,大大降低了开发成本和时间。

从功能实现角度来看,Qemu 通过软件的方式模拟硬件设备的行为。当虚拟机中的操作系统执行指令时,Qemu 会将这些指令转换为宿主机能够理解的指令并执行,同时模拟硬件设备的响应,使得虚拟机中的操作系统感觉像是在真实的硬件上运行。这种模拟机制使得 Qemu 具有极高的通用性和灵活性,几乎可以运行任何操作系统和应用程序。

用户可以通过不同Linux发行版所带有的软件包管理器来安装QEMU。如在Debian系列的发行版上可以使用下面的命令来安装:

sudo apt-get install qemu

除此之外,也可以选择从源码安装。

1.2Qemu源码安装流程

(1)获取QEMU源码,可以从QEMU下载官网上下载QEMU源码的tar包,以命令行下载2.0版本的QEMU为例:

$wget http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2
$tar xjvf qemu-2.0.0.tar.bz2

(2)编译及安装,获取源码后,可以根据需求来配置和编译QEMU:

$cd qemu-2.0.0 //如果使用的是git下载的源码,执行cd qemu
$./configure --enable-kvm --enable-debug --enable-vnc --enable-werror  --target-list="x86_64-softmmu"
或者用户模式(使能TCI)$./configure --target-list=arm-linux-user --enable-tcg-interpreter
$make -j8
$sudo make install

configure脚本用于生成Makefile,其选项可以用./configure --help查看。这里使用到的选项含义如下:

--enable-kvm:编译KVM模块,使QEMU可以利用KVM来访问硬件提供的虚拟化服务。
--enable-vnc:启用VNC。
--enalbe-werror:编译时,将所有的警告当作错误处理。
--target-list:选择目标机器的架构。默认是将所有的架构都编译,但为了更快的完成编译,指定需要的架构即可。

安装好之后,会生成如下应用程序:

图片

  • ivshmem-client/server:这是一个 guest 和 host 共享内存的应用程序,遵循 C/S 的架构。

  • qemu-ga:这是一个不利用网络实现 guest 和 host 之间交互的应用程序(使用 virtio-serial),运行在 guest 中。

  • qemu-io:这是一个执行 Qemu I/O 操作的命令行工具。

  • qemu-system-x86_64:Qemu 的核心应用程序,虚拟机就由它创建的。

  • qemu-img:创建虚拟机镜像文件的工具,下面有例子说明。

  • qemu-nbd:磁盘挂载工具。

1.3常用命令与操作示例

⑴创建虚拟机磁盘镜像:在使用 Qemu 创建虚拟机之前,需要先创建一个虚拟磁盘镜像,用于存储虚拟机的操作系统和数据 。可以使用qemu-img工具来创建磁盘镜像 。例如,要创建一个大小为 20GB,格式为 qcow2 的磁盘镜像文件 “myvm.qcow2”,可以在命令行中输入以下命令:

qemu-img create -f qcow2 myvm.qcow2 20G

其中,-f参数指定磁盘镜像的格式,qcow2是一种常用的磁盘镜像格式,具有写时复制等特性,可以有效节省磁盘空间 。myvm.qcow2是磁盘镜像文件的名称,20G表示磁盘镜像的大小为 20GB 。

⑵启动虚拟机:创建好磁盘镜像后,就可以使用qemu-system-x86_64命令来启动虚拟机 。假设要启动刚才创建的虚拟机,并指定内存大小为 2GB,使用 Windows 10 的 ISO 镜像文件进行安装,可以使用以下命令:

qemu-system-x86_64 -m 2048 -cdrom /path/to/windows10.iso -drive file=myvm.qcow2,format=qcow2

其中,-m参数指定虚拟机的内存大小,单位为 MB,这里设置为 2048MB,即 2GB 。-cdrom参数指定用于安装操作系统的 ISO 镜像文件的路径,/path/to/windows10.iso需要替换为实际的 Windows 10 ISO 镜像文件的路径 。-drive参数用于指定虚拟机的磁盘驱动器,file=myvm.qcow2指定使用前面创建的磁盘镜像文件,format=qcow2指定磁盘镜像的格式为 qcow2 。执行该命令后,会弹出一个窗口,显示虚拟机的启动界面,用户可以按照提示进行操作系统的安装 。

⑶管理虚拟机:在虚拟机运行过程中,可以使用一些命令来管理虚拟机 。例如,要暂停虚拟机的运行,可以在 Qemu 的控制台中按下Ctrl + Alt + 2组合键,进入 Qemu 的监视器模式,然后输入stop命令 。要恢复虚拟机的运行,在监视器模式下输入cont命令即可 。如果要关闭虚拟机,可以在监视器模式下输入system_powerdown命令 。

此外,还可以使用savevm命令来保存虚拟机的当前状态,使用loadvm命令来加载保存的状态 。例如,要保存虚拟机的当前状态为 “snapshot1”,可以在监视器模式下输入savevm snapshot1命令;要加载名为 “snapshot1” 的状态,可以输入loadvm snapshot1命令 。

1.4Qemu独特功能

全系统仿真:Qemu 的全系统仿真功能堪称一绝,它能够模拟完整的计算机系统,包括 CPU、内存、存储设备、网络设备等 。这意味着用户可以在 Qemu 中运行各种操作系统,无论是 Windows、Linux、macOS,还是一些小众的嵌入式操作系统,都不在话下。例如,安全研究人员可以利用 Qemu 搭建一个包含特定操作系统和应用程序的模拟环境,用于进行漏洞挖掘和安全测试,不用担心对真实系统造成损害。在开发操作系统时,开发者也可以借助 Qemu 的全系统仿真功能,在虚拟环境中进行开发和调试,方便快捷。

  • 动态二进制翻译:这是 Qemu 的核心技术之一,动态二进制翻译技术允许 Qemu 在运行时将客户机的指令集动态转换为宿主机的指令集 。通过这种方式,Qemu 能够在不同架构的宿主机上运行不同架构的客户机操作系统,实现了跨架构的虚拟化。例如,在 x86 架构的主机上运行 ARM 架构的操作系统时,Qemu 会将 ARM 指令动态翻译成 x86 指令,让程序能够在 x86 主机上顺利运行。这种技术不仅提高了 Qemu 的兼容性,还在一定程度上提升了性能,因为它可以根据实际运行情况对指令进行优化。

  • 丰富的设备模拟:Qemu 提供了丰富的设备模拟功能,支持多种常见的硬件设备,如虚拟网卡、虚拟磁盘、虚拟显卡、USB 设备等 。这些设备模拟使得虚拟机可以与外部环境进行交互,满足各种应用场景的需求。比如,在进行网络应用开发和测试时,用户可以在 Qemu 虚拟机中模拟不同类型的网络设备,测试应用程序在不同网络环境下的性能和稳定性。同时,Qemu 还支持对一些特殊设备的模拟,为特定领域的开发和研究提供了便利。

  • 插件扩展功能:Qemu 具备良好的插件扩展机制,用户可以通过编写插件来扩展 Qemu 的功能 。这使得 Qemu 能够适应各种复杂的应用场景和用户需求。例如,一些开发者编写了自定义的设备插件,用于模拟特定的硬件设备,满足特定行业的开发和测试需求。还有一些插件用于优化 Qemu 的性能、增强安全性或者提供新的管理功能,使得 Qemu 的功能不断丰富和完善。

二、Qemu 的工作原理

QEMU作为系统模拟器时,会模拟出一台能够独立运行操作系统的虚拟机。如下图所示,每个虚拟机对应主机(Host)中的一个QEMU进程,而虚拟机的vCPU对应QEMU进程的一个线程。

系统虚拟化最主要是虚拟出CPU、内存及I/O设备。虚拟出的CPU称之为vCPU,QEMU为了提升效率,借用KVM、XEN等虚拟化技术,直接利用硬件对虚拟化的支持,在主机上安全地运行虚拟机代码(需要硬件支持)。虚拟机vCPU调用KVM的接口来执行任务的流程如下:

open("/dev/kvm")
ioctl(KVM_CREATE_VM)
ioctl(KVM_CREATE_VCPU)
for (;;) {ioctl(KVM_RUN)switch (exit_reason) {case KVM_EXIT_IO:  /* ... */case KVM_EXIT_HLT: /* ... */}
}

QEMU发起ioctrl来调用KVM接口,KVM则利用硬件扩展直接将虚拟机代码运行于主机之上,一旦vCPU需要操作设备寄存器,vCPU将会停止并退回到QEMU,QEMU去模拟出操作结果。

虚拟机内存会被映射到QEMU的进程地址空间,在启动时分配。在虚拟机看来,QEMU所分配的主机上的虚拟地址空间为虚拟机的物理地址空间。

QEMU在主机用户态模拟虚拟机的硬件设备,vCPU对硬件的操作结果会在用户态进行模拟,如虚拟机需要将数据写入硬盘,实际结果是将数据写入到了主机中的一个镜像文件中。

2.1系统架构解读

Qemu 的系统架构可以分为用户态和内核态两大部分,这种分层设计使得 Qemu 能够高效地实现硬件模拟和虚拟化功能,各部分组件相互协作,共同为虚拟机提供完整的运行环境。

在用户态,Qemu 包含了丰富的组件。用户接口是用户与 Qemu 交互的桥梁,用户可以通过命令行、图形界面或者 API 等方式,向 Qemu 发送各种指令,如创建虚拟机、启动虚拟机、配置虚拟机参数等 。设备模型是用户态的重要组成部分,它负责模拟各种硬件设备的行为。Qemu 通过设备模型模拟出虚拟的 CPU、内存、硬盘、网卡、显卡等设备,使得虚拟机中的操作系统能够像在真实硬件上一样访问这些设备。以虚拟网卡为例,设备模型会模拟网卡的接收和发送数据的功能,当虚拟机中的操作系统发送网络数据包时,设备模型会将这些数据包进行处理,并通过宿主机的网络接口发送出去;反之,当宿主机接收到网络数据包时,设备模型会将其转发给虚拟机中的操作系统。

虚拟设备驱动程序也是用户态的关键组件之一,它为虚拟机中的操作系统提供了访问虚拟设备的接口。这些驱动程序模拟了真实设备驱动程序的功能,使得操作系统能够正常识别和使用虚拟设备。例如,虚拟显卡的驱动程序会模拟真实显卡的功能,将虚拟机中的图形数据进行处理和渲染,然后通过宿主机的显示设备展示出来。

在内核态,Qemu 主要包含虚拟机监控程序(VMM)和虚拟机管理器(VM Manager) 。VMM 是 Qemu 的核心组件之一,它负责管理虚拟机的运行状态,监控虚拟机的指令执行,实现硬件虚拟化的核心功能。VMM 通过与宿主机内核的交互,实现对 CPU、内存等硬件资源的虚拟化管理。例如,在处理 CPU 虚拟化时,VMM 会负责模拟目标架构的指令集、寄存器等关键组件,使得虚拟机能够运行不同架构的操作系统。当虚拟机执行指令时,VMM 会捕获这些指令,并根据需要进行处理和转换,然后将其发送给宿主机 CPU 执行。

VM Manager 则主要负责虚拟机的创建、管理和调度。它根据用户的请求,创建新的虚拟机,并为其分配必要的资源,如内存、CPU 时间片等 。同时,VM Manager 还负责监控虚拟机的运行状态,在多个虚拟机之间进行资源调度,确保每个虚拟机都能够获得合理的资源分配,从而保证整个系统的性能和稳定性。当一个虚拟机需要更多的 CPU 时间片时,VM Manager 会根据预设的调度策略,调整各个虚拟机的 CPU 分配,使得系统资源得到合理利用。

用户态和内核态的组件之间通过特定的接口进行交互,以实现高效的协作。例如,用户态的设备模型通过与内核态的 VMM 进行交互,将虚拟机对硬件设备的访问请求传递给 VMM,VMM 再根据请求进行相应的处理,并将结果返回给设备模型,设备模型最后将结果返回给虚拟机中的操作系统 。这种交互机制确保了虚拟机能够在 Qemu 提供的模拟环境中正常运行,同时也保证了系统的性能和稳定性。

2.2指令翻译与虚拟化实现

(1)动态二进制翻译技术:动态二进制翻译是 Qemu 实现虚拟化的核心技术之一,它允许 Qemu 在运行时将客户机的指令集动态转换为宿主机的指令集 。简单来说,当虚拟机中的操作系统执行指令时,Qemu 会将这些指令实时翻译成宿主机能够理解和执行的指令。例如,在 x86 架构的宿主机上运行 ARM 架构的虚拟机时,Qemu 会将 ARM 指令动态翻译成 x86 指令,使得 ARM 架构的操作系统和应用程序能够在 x86 主机上顺利运行。

Qemu 使用了一种称为 “Tiny Code Generator(TCG)” 的技术来实现动态二进制翻译 。TCG 是一个轻量级的代码生成器,它能够将客户机的指令动态地转换为宿主机的指令,并生成高效的机器码。具体过程如下:首先,Qemu 从虚拟机中读取客户机的指令,然后将这些指令翻译成 TCG 中间表示形式(IR) 。TCG 中间表示是一种抽象的指令表示,它不依赖于具体的硬件架构,具有较高的通用性。接下来,TCG 会根据宿主机的架构,将中间表示进一步翻译成宿主机的目标指令 。在翻译过程中,TCG 会对指令进行优化,以提高执行效率。例如,它会合并一些可以合并的指令,减少指令的执行次数;还会根据宿主机的硬件特性,选择最合适的指令序列来实现相同的功能。通过这种动态二进制翻译技术,Qemu 能够在不同架构的宿主机上运行各种不同架构的客户机操作系统,大大提高了虚拟化的灵活性和兼容性。

(2)中断与模拟设备操作实现虚拟化:中断是计算机系统中一种重要的机制,用于处理异步事件。在 Qemu 的虚拟化环境中,中断的模拟和处理对于实现完整的虚拟化至关重要 。当虚拟机中的设备产生中断请求时,Qemu 需要准确地模拟这个中断过程,将中断信号传递给虚拟机中的操作系统,并确保操作系统能够正确地响应和处理这个中断。

以虚拟网卡为例,当宿主机接收到网络数据包时,虚拟网卡设备模型会产生一个中断请求,通知虚拟机中的操作系统有新的网络数据到达 。Qemu 会捕获这个中断请求,并将其转换为虚拟机能够识别的中断信号,然后传递给虚拟机中的操作系统。操作系统接收到中断信号后,会调用相应的中断处理程序,从虚拟网卡中读取网络数据包,并进行后续的处理。在这个过程中,Qemu 需要模拟中断控制器的行为,确保中断信号的正确传递和处理。

同时,Qemu 对设备的模拟操作也是实现虚拟化的关键。Qemu 通过软件模拟各种硬件设备的功能和行为,使得虚拟机中的操作系统能够像操作真实硬件设备一样操作这些虚拟设备 。例如,在模拟硬盘设备时,Qemu 会模拟硬盘的读写操作。当虚拟机中的操作系统向虚拟硬盘写入数据时,Qemu 会将数据存储在宿主机的文件系统中,以模拟硬盘的存储功能;当操作系统从虚拟硬盘读取数据时,Qemu 会从宿主机的文件系统中读取相应的数据,并返回给操作系统。通过这种方式,Qemu 实现了对硬件设备的完整模拟,为虚拟机提供了一个与真实硬件环境相似的运行环境,使得各种操作系统和应用程序能够在虚拟机中正常运行,实现了高效的硬件虚拟化。

2.3创建及使用虚拟机

使用qemu-img创建虚拟机镜像,虚拟机镜像用来模拟虚拟机的硬盘,在启动虚拟机之前需要创建镜像文件。

qemu-img create -f qcow2 test-vm-1.qcow2 10G

-f 选项用于指定镜像的格式,qcow2 格式是 Qemu 最常用的镜像格式,采用来写时复制技术来优化性能。test-vm-1.qcow2 是镜像文件的名字,10G是镜像文件大小。镜像文件创建完成后,可使用 qemu-system-x86 来启动x86 架构的虚拟机:

使用 qemu-system-x86 来启动 x86 架构的虚拟机

qemu-system-x86_64 test-vm-1.qcow2

因为 test-vm-1.qcow2 中并未给虚拟机安装操作系统,所以会提示 “No bootable device”,无可启动设备。

启动 VM 安装操作系统镜像

qemu-system-x86_64 -m 2048 -enable-kvm test-vm-1.qcow2 -cdrom ./Centos-Desktop-x86_64-20-1.iso

-m 指定虚拟机内存大小,默认单位是 MB, -enable-kvm 使用 KVM 进行加速,-cdrom 添加 fedora 的安装镜像。可在弹出的窗口中操作虚拟机,安装操作系统,安装完成后重起虚拟机便会从硬盘 ( test-vm-1.qcow2 ) 启动。之后再启动虚拟机只需要执行:

qemu-system-x86_64 -m 2048 -enable-kvm test-vm-1.qcow2

qemu-img 支持非常多种的文件格式,可以通过 qemu-img -h 查看,其中 raw 和 qcow2 是比较常用的两种,raw 是 qemu-img 命令默认的,qcow2 是 qemu 目前推荐的镜像格式,是功能最多的格式。

三、Qemu应用场景

3.1开发与测试领域

在软件开发过程中,开发人员常常需要在不同的操作系统和硬件平台上对软件进行测试,以确保软件的兼容性和稳定性 。Qemu 提供了一个理想的测试环境,它可以模拟多种硬件架构和操作系统,让开发人员在一台物理计算机上就能完成对软件在不同环境下的测试工作。例如,开发一款跨平台的应用程序时,开发人员可以利用 Qemu 创建 Windows、Linux、macOS 等多种操作系统的虚拟机,在这些虚拟机中分别运行应用程序,测试其在不同系统下的功能完整性、界面显示、性能表现等 。这样可以提前发现并解决软件在不同平台上可能出现的兼容性问题,大大提高软件的质量和可靠性。

对于硬件开发,Qemu 同样发挥着重要作用。在硬件设计阶段,工程师可以使用 Qemu 模拟目标硬件平台,进行硬件相关软件的开发和调试 。比如,开发一款基于 ARM 架构的嵌入式系统时,在实际硬件还未生产出来之前,工程师就可以利用 Qemu 模拟 ARM 硬件环境,编写和调试驱动程序、操作系统内核等软件部分。通过在 Qemu 模拟环境中进行充分的测试和优化,可以减少硬件开发过程中的错误和风险,缩短硬件开发周期,降低开发成本 。而且,Qemu 支持对硬件设备的详细模拟,工程师可以模拟各种硬件故障和异常情况,测试软件在这些情况下的应对能力,提高硬件系统的稳定性和可靠性。

3.2云计算与数据中心

在云计算和数据中心领域,Qemu 是实现资源虚拟化的关键技术之一 。它与 KVM(Kernel-based Virtual Machine)相结合,为云计算提供了强大的支持。KVM 是 Linux 内核中的一个模块,它利用硬件虚拟化扩展(如 Intel VT 或 AMD-V)来实现高效的虚拟化 。

Qemu 则作为 KVM 的用户空间工具,负责模拟硬件设备,为虚拟机提供完整的运行环境。通过这种组合,云计算提供商可以在一台物理服务器上创建多个虚拟机,将服务器的计算、存储和网络资源进行虚拟化分割,为不同的用户提供独立的虚拟机服务 。

这种虚拟化技术在云计算和数据中心中有诸多优势。首先,它提高了服务器的资源利用率。传统的数据中心中,服务器的资源往往不能得到充分利用,很多服务器在大部分时间内处于低负载运行状态 。通过虚拟化技术,多个虚拟机可以共享一台物理服务器的资源,根据实际需求动态分配资源,大大提高了服务器的利用率,降低了硬件成本和能源消耗 。

其次,虚拟化技术增强了系统的灵活性和可扩展性。云计算提供商可以根据用户的需求,快速创建、删除或调整虚拟机的配置,实现资源的动态分配和回收 。用户也可以根据自己的业务需求,灵活选择虚拟机的规格和配置,无需担心硬件资源的限制。此外,Qemu和KVM的结合还提供了良好的隔离性和安全性,每个虚拟机都运行在独立的隔离环境中,相互之间不会干扰,保证了用户数据的安全和隐私 。

3.3教育与科研用途

在教育领域,Qemu 为学生和教师提供了一个便捷的实验环境 。在计算机相关课程的教学中,学生常常需要实践操作不同的操作系统和硬件环境,但由于实际硬件设备的限制,很难为每个学生提供多样化的实验环境 。Qemu 的出现解决了这个问题,教师可以利用 Qemu 创建多个虚拟机,每个虚拟机运行不同的操作系统和实验软件,学生可以在虚拟机中进行各种实验操作,如操作系统安装、配置,网络实验,软件开发等 。这样不仅提高了教学效果,还能让学生在实践中更好地理解计算机系统的原理和机制。

在科研方面,Qemu 也有着广泛的应用 。科研人员在进行计算机体系结构、操作系统、网络协议等领域的研究时,常常需要搭建复杂的实验环境 。Qemu 可以模拟各种硬件平台和操作系统,为科研人员提供了一个灵活、可定制的实验平台 。例如,在研究新型的计算机体系结构时,科研人员可以利用 Qemu 模拟新的处理器架构和硬件设备,验证设计的可行性和性能优势 。

在研究操作系统的性能优化和安全机制时,科研人员可以在 Qemu 虚拟机中运行不同版本的操作系统,进行各种性能测试和安全实验 。Qemu 还支持对网络设备和网络协议的模拟,方便科研人员进行网络相关的研究和实验,推动科研工作的顺利开展。

四、Qemu源码结构

Qemu软件虚拟化实现的思路是采用二进制指令翻译技术,主要是提取 guest 代码,然后将其翻译成 TCG 中间代码,最后再将中间代码翻译成 host 指定架构的代码,如 x86 体系就翻译成其支持的代码形式,ARM 架构同理。

图片

所以,从宏观上看,源码结构主要包含以下几个部分:

  • /vl.c:最主要的模拟循环,虚拟机环境初始化,和 CPU 的执行。

  • /target-arch/translate.c:将 guest 代码翻译成不同架构的 TCG 操作码。

  • /tcg/tcg.c:主要的 TCG 代码。

  • /tcg/arch/tcg-target.c:将 TCG 代码转化生成主机代码。

  • /cpu-exec.c:主要寻找下一个二进制翻译代码块,如果没有找到就请求得到下一个代码块,并且操作生成的代码块。

其中,涉及的主要几个函数如下:

图片

知道了这个总体的代码结构,再去具体了解每一个模块可能会相对容易一点。

(1)开始执行

主要比较重要的c文件有:/vl.c,/cpus.c, /exec-all.c, /exec.c, /cpu-exec.c。

QEMU的main函数定义在/vl.c中,它也是执行的起点,这个函数的功能主要是建立一个虚拟的硬件环境。它通过参数的解析,将初始化内存,需要的模拟的设备初始化,CPU参数,初始化KVM等等。接着程序就跳转到其他的执行分支文件如:/cpus.c, /exec-all.c, /exec.c, /cpu-exec.c。

(2)硬件模拟

所有的硬件设备都在/hw/ 目录下面,所有的设备都有独自的文件,包括总线,串口,网卡,鼠标等等。它们通过设备模块串在一起,在vl.c中的machine _init中初始化。这里就不讲每种设备是怎么实现的了。

(3)目标机器

现在QEMU模拟的CPU架构有:Alpha, ARM, Cris, i386, M68K, PPC, Sparc, Mips, MicroBlaze, S390X and SH4。

我们在QEMU中使用./configure 可以配置运行的架构,这个脚本会自动读取本机真实机器的CPU架构,并且编译的时候就编译对应架构的代码。对于不同的QEMU做的事情都不同,所以不同架 构下的代码在不同的目录下面。/target-arch/目录就对应了相应架构的代码,如/target-i386/就对应了x86系列的代码部分。虽然 不同架构做法不同,但是都是为了实现将对应客户机CPU架构的TBs转化成TCG的中间代码。这个就是TCG的前半部分。

(4)主机

这个部分就是使用TCG代码生成主机的代码,这部分代码在/tcg/里面,在这个目录里面也对应了不同的架构,分别在不同的子目录里面,如i386就在/tcg/i386中。整个生成主机代码的过程也可以教TCG的后半部分。

(5)文件总结

/vl.c:                      最主要的模拟循环,虚拟机机器环境初始化,和CPU的执行。
/target-arch/translate.c    将客户机代码转化成不同架构的TCG操作码。
/tcg/tcg.c                  主要的TCG代码。
/tcg/arch/tcg-target.c      将TCG代码转化生成主机代码
/cpu-exec.c  其中的cpu-exec()函数主要寻找下一个TB(翻译代码块),如果没找到就请求得到下一个TB,并且操作生成的代码块。

4.1TCG动态翻译

QEMU在 0.9.1版本之前使用DynGen翻译c代码.当我们需要的时候TCG会动态的转变代码,这个想法的目的是用更多的时间去执行我们生成的代码。当新的代 码从TB中生成以后, 将会被保存到一个cache中,因为很多相同的TB会被反复的进行操作,所以这样类似于内存的cache,能够提高使用效率。而 cache的刷新使用LRU算法。

图片

编译器在执行器会从源代码中产生目标代码,像GCC这种编译器,它为了产生像函数调用目标代码会产生一些特殊的汇编目标代码,他们能够让编译器需要知道在调用函数。需要什么,以及函数调用以后需要返回什么,这些特殊的汇编代码产生过程就叫做函数的Prologue和Epilogue,这里就叫前端和后段吧。我在其他文章中也分析过汇编调用函数的过程,至于汇编里面函数调用过程中寄存器是如何变化的,在本文中就不再描述了。

函数的后端会恢复前端的状态,主要做下面2点:

  1. 恢复堆栈的指针,包括栈顶和基地址。

  2. 修改cs和ip,程序回到之前的前端记录点。

TCG就如编译器一样可以产生目标代码,代码会保存在缓冲区中,当进入前端和后端的时候就会将TCG生成的缓冲代码插入到目标代码中。

接下来我们就来看下如何翻译代码的:

①客户机代码

图片

②TCG中间代码

图片

③主机代码

图片

4.2TB链

在QEMU中,从代码cache到静态代码再回到代码cache,这个过程比较耗时,所以在QEMU中涉及了一个TB链将所有TB连在一起,可以让一个TB执行完以后直接跳到下一个TB,而不用每次都返回到静态代码部分。具体过程如下图:

图片

4.3TCG代码分析

接下来来看看QEMU代码中中到底怎么来执行这个TCG的,看看它是如何生成主机代码的。

main_loop(...){/vl.c} :

函数main_loop 初始化qemu_main_loop_start()然后进入无限循环cpu_exec_all() , 这个是QEMU的一个主要循环,在里面会不断的判断一些条件,如虚拟机的关机断电之类的。

qemu_main_loop_start(...){/cpus.c} :

函数设置系统变量 qemu_system_ready = 1并且重启所有的线程并且等待一个条件变量。

cpu_exec_all(...){/cpus.c} :

它是cpu循环,QEMU能够启动256个cpu核,但是这些核将会分时运行,然后执行qemu_cpu_exec() 。

struct CPUState{/target-xyz/cpu.h} :

它是CPU状态结构体,关于cpu的各种状态,不同架构下面还有不同。

cpu_exec(...){/cpu-exec.c}:

这个函数是主要的执行循环,这里第一次翻译之前说道德TB,TB被初始化为(TranslationBlock *tb) ,然后不停的执行异常处理。其中嵌套了两个无限循环 find tb_find_fast() 和tcg_qemu_tb_exec();
cantb_find_fast()为客户机初始化查询下一个TB,并且生成主机代码。
tcg_qemu_tb_exec()执行生成的主机代码

struct TranslationBlock {/exec-all.h}:

结构体TranslationBlock包含下面的成员:PC, CS_BASE, Flags (表明TB), tc_ptr (指向这个TB翻译代码的指针), tb_next_offset[2], tb_jmp_offset[2] (接下去的Tb), *jmp_next[2], *jmp_first (之前的TB).

tb_find_fast(...){/cpu-exec.c} :

函数通过调用获得程序指针计数器,然后传到一个哈希函数从 tb_jmp_cache[] (一个哈希表)得到TB的所以,所以使用tb_jmp_cache可以找到下一个TB。如果没有找到下一个TB,则使用tb_find_slow。

tb_find_slow(...){/cpu-exec.c}:

这个是在快速查找失败以后试图去访问物理内存,寻找TB。

tb_gen_code(...){/exec.c}:

开始分配一个新的TB,TB的PC是刚刚从CPUstate里面通过using get_page_addr_code()找到的phys_pc = get_page_addr_code(env, pc);tb = tb_alloc(pc);ph当调用cpu_gen_code() 以后,接着会调用tb_link_page(),它将增加一个新的TB,并且指向它的物理页表。

cpu_gen_code(...){translate-all.c}:

函数初始化真正的代码生成,在这个函数里面有下面的函数调用:

gen_intermediate_code(){
/target-arch/translate.c}->gen_intermediate_code_internal(){
/target-arch/translate.c }->disas_insn(){/target-arch/translate.c}

disas_insn(){/target-arch/translate.c}:

函数disas_insn() 真正的实现将客户机代码翻译成TCG代码,它通过一长串的switch case,将不同的指令做不同的翻译,最后调用tcg_gen_code。

tcg_gen_code(...){/tcg/tcg.c}:

这个函数将TCG的代码转化成主机代码,这个就不细细说明了,和前面类似。

#define tcg_qemu_tb_exec(...){/tcg/tcg.g}:
通过上面的步骤,当TB生成以后就通过这个函数进行执行

next_tb = tcg_qemu_tb_exec(tc_ptr) :
extern uint8_t code_gen_prologue[];
#define tcg_qemu_tb_exec(tb_ptr) ((long REGPARM(*)(void *)) code_gen_prologue)(tb_ptr)

通过上面的步骤我们就解析了QEMU是如何将客户机代码翻译成主机代码的,了解了TCG的工作原理。接下来看看QEMU与KVM是怎么联系的。

4.4IOCTL使用流程

在QEMU-KVM中,用户空间的QEMU是通过IOCTL与内核空间的KVM模块进行通讯的。

(1)创建KVM

在/vl.c中通过kvm_init()将会创建各种KVM的结构体变量,并且通过IOCTL与已经初始化好的KVM模块进行通讯,创建虚拟机。然后创建VCPU,等等。

(2)KVM_RUN

这个IOCTL是使用最频繁的,整个KVM运行就不停在执行这个IOCTL,当KVM需要QEMU处理一些指令和IO等等的时候就会退出通过这个IOCTL退回到QEMU进行处理,不然就会一直在KVM中执行。

它的初始化过程:

vl.c中调用machine->init初始化硬件设备接着调用pc_init_pci,然后再调用pc_init1;接着通过下面的调用初始化KVM的主循环,以及CPU循环。在CPU循环的过程中不断的执行KVM_RUN与KVM进行交互。

pc_init1->pc_cpus_init->pc_new_cpu->cpu_x86_init->qemu_init_vcpu->kvm_init_vcpu->ap_main_loop->kvm_main_loop_cpu->kvm_cpu_exec->kvm_run

(3)KVM_IRQ_LINE

这个IOCTL和KVM_RUN是不同步的,它也是个频率非常高的调用,它就是一般中断设备的中断注入入口。当设备有中断就通过这个IOCTL最终 调用KVM里面的kvm_set_irq将中断注入到虚拟的中断控制器。在kvm中会进一步判断属于什么中断类型,然后在合适的时机写入vmcs。当然在 KVM_RUN中会不断的同步虚拟中断控制器,来获取需要注入的中断,这些中断包括QEMU和KVM本身的,并在重新进入客户机之前注入中断。

五、Qemu中内存管理

5.1相关配置参数

QEMU的命令行中有参数:

-m [size=]megs[,slots=n,maxmem=size]

用于指定客户机初始运行时的内存大小以及客户机最大内存大小,以及内存芯片槽的数量(DIMM)。

之所以QEMU可以指定最大内存、槽等参数,是因为QEMU可以模拟DIMM的热插拔,客户机操作系统可以和在真实的系统上一样,检测新内存被插入或者拔出。也就是说,内存热插拔的粒度是DIMM槽(或者说DIMM集合),而不是最小的byte。

与内存相关的数据结构:

图片

PCDIMMDevice和HostMemoryBackend对象都是在QEMU中用户可见的客户机内存。它们能通过QEMU命令行或者QMP监控器接口来管理。

PCDIMMDevice数据结构是使用QEMU中的面向对象编程模型QOM定义的,对应的对象和类的数据结构如下。通过在QEMU进程中创建一个新的PCDIMMDevice对象,就可以实现内存的热插拔。

值得注意的是,客户机启动时的初始化内存,可能不会被模拟成PCDIMMDevice设备,也就是说,这部分初始化内存不能进行热插拔。PCDIMMDevice的定义在include/hw/mem/pc-dimm.h中。

typedef struct PCDIMMDevice {/* private */DeviceState parent_obj;/* public */uint64_t addr;uint32_t node; //numa nodeint32_t slot;  //slot编号HostMemoryBackend *hostmem;
} PCDIMMDevice;typedef struct PCDIMMDeviceClass {/* private */DeviceClass parent_class;/* public */MemoryRegion *(*get_memory_region)(PCDIMMDevice *dimm);
} PCDIMMDeviceClass;

每个PCDIMMDevice对象都与 HostMemoryBackend对象相关联。HostMemoryBackend也是使用QEMU中的面向对象编程模型QOM定义的。HostMemoryBackend定义在include/sysemu/hostmem.h中。HostMemoryBackend对象包含了客户机内存对应的真正的主机内存,这些内存可以是匿名映射的内存,也可以是文件映射内存。文件映射的客户机内存允许Linux在物理主机上透明大页机制的使用(hugetlbfs),并且能够共享内存,从而使其他进程可以访问客户机内存。

struct HostMemoryBackend {/* private */Object parent;/* protected */uint64_t size;bool merge, dump;bool prealloc, force_prealloc;DECLARE_BITMAP(host_nodes, MAX_NODES + 1);HostMemPolicy policy;MemoryRegion mr;
};struct HostMemoryBackendClass {ObjectClass parent_class;void (*alloc)(HostMemoryBackend *backend, Error **errp);
};

HostMemoryBackend对象中的内存被实际映射到通过qemu_ram_alloc()函数(代码定义在exec.c中)RAMBlock数据结构中。每个RAMBlock都有一个指针指向被映射内存的起始位置,同时包含一个ram_addr_t的位移变量。ram_addr_t位于全局的命名空间中,因此RAMBlock能够通过offset来查找。

RAMBlock定义在include/exec/ram_addr.h中。RAMBlock受RCU机制保护,所谓RCU,即Read-COPY-Update。

typedef uint64_t ram_addr_t;struct RAMBlock {struct rcu_head rcu; //该数据结构受rcu机制保护struct MemoryRegion *mr; uint8_t *host;          //RAMBlock的内存起始位置ram_addr_t offset;  //在所有的RAMBlock中offsetram_addr_t used_length;  //已使用长度ram_addr_t max_length;  //最大分配内存void (*resized)(const char*, uint64_t length, void *host);uint32_t flags;/* Protected by iothread lock.  */char idstr[256];      //RAMBlock的ID/* RCU-enabled, writes protected by the ramlist lock */QLIST_ENTRY(RAMBlock) next;int fd;  //映射文件的描述符
};

所有的RAMBlock保存在全局的RAMBlock的链表中,名为RAMList,它有专门的数据结构定义。RAMList数据结构定义在include/exec/ram_addr.h中,而全局的ram_list变量则定义在exec.c中。因此这个链表保存了客户机的内存对应的所有的物理机的实际内存信息。

5.2跟踪脏页

当客户机CPU或者DMA将数据保存到客户机内存时,需要通知下列一些用户:

  1. 热迁移特性依赖于跟踪脏页,因此他们能够在被改变之后重新传输。

  2. 图形卡模拟依赖于跟踪脏的视频内存,用于重画某些界面。

所有的CPU架构都有内存地址空间、有些CPU架构又有一个IO地址空间。它们在QEMU中被表示为AddressSpace数据结构,它定义在include/exec/memory.h中。而每个地址空间都包含一个MemoryRegion的树状结构,所谓树状结构,指的是每个MemoryRegion的内部可以含有MemoryRegion,这样的包含所形成的树状结构。

MemoryRegion是联系客户机内存和包含这一部分内存的RAMBlock。每个MemoryRegion都包含一个在RAMBlock中ram_addr_t类型的offset,每个RAMBlock也有一个MemoryRegion的指针。

MemoryRegion不仅可以表示RAM,也可以表示I/O映射内存,在访问时可以调用read/write回调函数。这也是硬件从客户机CPU注册的访问被分派到相应的模拟设备的方法。

struct AddressSpace {/* All fields are private. */struct rcu_head rcu;char *name;MemoryRegion *root;/* Accessed via RCU.  */struct FlatView *current_map; //AddressSpace的一张平面视图,它是AddressSpace所有正在使用的MemoryRegion的集合,这是从CPU的视角来看到的。int ioeventfd_nb;struct MemoryRegionIoeventfd *ioeventfds;struct AddressSpaceDispatch *dispatch;struct AddressSpaceDispatch *next_dispatch;MemoryListener dispatch_listener;QTAILQ_ENTRY(AddressSpace) address_spaces_link;
};struct MemoryRegion {Object parent_obj;/* All fields are private - violators will be prosecuted */const MemoryRegionOps *ops;  //与MemoryRegion相关的操作const MemoryRegionIOMMUOps *iommu_ops;void *opaque;MemoryRegion *container;  Int128 size;hwaddr addr;         //在AddressSpace中的地址void (*destructor)(MemoryRegion *mr);ram_addr_t ram_addr;  //MemoryRegion的起始地址uint64_t align;   //big-endian or little-endianbool subpage;bool terminates;bool romd_mode;bool ram;bool skip_dump;bool readonly; /* For RAM regions */bool enabled;     //如果为true,表示已经通知kvm使用这段内存bool rom_device;  //是否是只读内存bool warning_printed; /* For reservations */bool flush_coalesced_mmio;bool global_locking;uint8_t vga_logging_count;MemoryRegion *alias;hwaddr alias_offset;int32_t priority;bool may_overlap;QTAILQ_HEAD(subregions, MemoryRegion) subregions;QTAILQ_ENTRY(MemoryRegion) subregions_link;QTAILQ_HEAD(coalesced_ranges, CoalescedMemoryRange) coalesced;const char *name;  //MemoryRegion的名字,调试时使用uint8_t dirty_log_mask;  //表示哪种dirty map被使用,共有三种//IOevent文件描述符的管理unsigned ioeventfd_nb;MemoryRegionIoeventfd *ioeventfds;NotifierList iommu_notify;
};

六、Qemu与其他虚拟机管理器对比

在虚拟化的广袤天地里,Qemu 并非独自闪耀,还有其他一些虚拟机管理器同样备受关注,它们各自有着独特的特点和优势。下面,我们就来深入对比一下 Qemu 与其他常见虚拟机管理器的异同。

6.1 Qemu与 KVM的协作与差异

KVM,即 Kernel-based Virtual Machine,是基于 Linux 内核的虚拟化技术 。它利用硬件虚拟化扩展(如 Intel VT 或 AMD-V)来实现高效的硬件辅助虚拟化,与 Qemu 有着紧密的联系,同时也存在一些明显的差异。

从协作的角度来看,KVM 和 Qemu 常常携手合作,共同为用户提供强大的虚拟化解决方案 。KVM 作为 Linux 内核的一部分,提供了虚拟化的核心框架,负责管理 CPU 和内存等关键资源的虚拟化 。而 Qemu 则作为用户空间的工具,承担起模拟各种硬件设备的重任,为虚拟机提供完整的硬件环境,如模拟网卡、硬盘、显卡等设备,使得虚拟机能够像运行在真实硬件上一样与外部环境进行交互 。通过这种协作,KVM 和 Qemu 充分发挥各自的优势,既利用了硬件虚拟化的高性能,又实现了丰富的硬件模拟功能,为用户带来了高效、灵活的虚拟化体验。

在性能方面,KVM 具有显著的优势 。由于 KVM 依赖于硬件辅助虚拟化,它可以直接利用 CPU 的虚拟化扩展,大大降低了虚拟化的开销,使得虚拟机能够以接近原生系统的性能运行 。对于那些对性能要求极高的应用场景,如大规模数据处理、高性能计算等,KVM 的优势尤为明显 。相比之下,Qemu 在纯软件模拟模式下,由于所有的硬件模拟都通过软件实现,会产生较高的开销,性能相对较低 。特别是在处理计算密集型任务时,Qemu 可能会出现性能瓶颈 。然而,当 Qemu 与 KVM 结合使用时,借助 KVM 的硬件加速功能,其性能会得到大幅提升 。

设备支持方面,Qemu 凭借其全系统仿真的特性,展现出了强大的兼容性 。它能够模拟多种不同架构的硬件设备,支持 x86、ARM、PowerPC、MIPS 等多种处理器架构,这使得 Qemu 在跨平台开发和测试中具有独特的优势 。无论是开发基于 x86 架构的服务器应用,还是基于 ARM 架构的嵌入式系统,Qemu 都能提供相应的模拟环境 。而 KVM 的设备支持则相对局限于具有特定硬件虚拟化扩展的系统,主要适用于基于 x86 架构的服务器场景 。虽然 KVM 也在不断扩展其设备支持范围,但在跨平台兼容性方面,与 Qemu 相比仍有一定差距 。

易用性上,Qemu 的使用相对较为简单直观 。它提供了丰富的命令行参数和图形化界面工具,用户可以通过简单的配置和操作,快速创建和管理虚拟机 。而且,Qemu 对硬件的要求相对较低,即使在不支持硬件虚拟化的设备上也能运行 。KVM 的使用则相对复杂一些,由于它与 Linux 内核紧密集成,用户需要对 Linux 系统有一定的了解,并且需要确保硬件支持虚拟化扩展 。不过,随着管理工具的不断发展,如 virt-manager 等图形化管理工具的出现,KVM 的管理也变得越来越方便 。

6.2 Qemu与 libvirt 的功能区分

libvirt 是一个用于管理和控制虚拟化技术的工具集,它提供了统一的 API 和命令行工具,支持多种虚拟化技术,包括 Qemu/KVM、Xen、LXC 等 。与 Qemu 相比,libvirt 在功能和定位上有着明显的区别。

在虚拟机管理方式上,Qemu 主要通过命令行或者脚本来直接启动和管理虚拟机 。用户需要熟悉 Qemu 的各种命令行参数,手动配置虚拟机的各项参数,如内存大小、CPU 核心数、磁盘映像文件等 。这种方式虽然灵活,但对于初学者来说可能具有一定的难度 。而 libvirt 则提供了更为高级和抽象的管理方式 。它通过 XML 配置文件来描述虚拟机的各种属性和配置,用户可以通过编辑 XML 文件或者使用 libvirt 提供的命令行工具(如 virsh)、图形化界面工具(如 virt-manager)来管理虚拟机 。这种方式使得虚拟机的管理更加规范化和易于操作,尤其适合在生产环境中进行大规模的虚拟机管理 。

从功能特点来看,Qemu 专注于硬件模拟和虚拟机的运行,它提供了强大的设备模拟功能,能够为虚拟机提供逼真的硬件环境 。Qemu 还支持动态二进制翻译等技术,实现了跨架构的虚拟化 。而 libvirt 则更侧重于提供统一的管理接口和高级的管理功能 。它不仅可以管理虚拟机的生命周期,如创建、启动、暂停、关闭、删除等操作,还提供了诸如虚拟机快照、迁移、资源监控等高级功能 。通过 libvirt,用户可以方便地对多个虚拟机进行集中管理,实现资源的动态分配和优化 。

举个例子,在一个云计算数据中心中,管理员可能会使用 libvirt 来统一管理大量的虚拟机,通过 virsh 命令或者 virt-manager 界面,快速创建、部署和监控虚拟机 。而在每个虚拟机内部,Qemu 则负责模拟硬件设备,为虚拟机提供运行环境,确保虚拟机中的操作系统和应用程序能够正常运行 。

七、使用Qemu虚拟机管理器的优势

7.1灵活性与可定制性

Qemu 虚拟机管理器赋予用户高度的自由,让用户能够根据自己的需求随心所欲地定制虚拟机的各种参数 。无论是设置虚拟机的 CPU 核心数、内存大小,还是配置虚拟硬盘的容量和类型,甚至是调整虚拟网卡的网络配置,用户都可以通过简单的命令行参数或者配置文件轻松实现 。例如,在进行大数据处理的性能测试时,用户可以根据测试需求,将虚拟机的 CPU 核心数设置为 8 个,内存设置为 16GB,以模拟真实的大数据处理环境 。

这种高度的灵活性和可定制性,使得 Qemu 能够满足各种复杂的硬件虚拟化需求,无论是进行简单的开发测试,还是搭建复杂的云计算环境,Qemu 都能应对自如 。而且,Qemu 还支持对虚拟机进行动态调整,用户可以在虚拟机运行过程中,根据实际需求动态增加或减少 CPU 核心数、内存大小等资源,无需重启虚拟机,大大提高了资源的利用率和使用效率 。

7.2广泛的硬件和系统支持

Qemu 对硬件和操作系统的兼容性堪称一绝 。它支持 x86、ARM、PowerPC、MIPS、RISC-V 等多种硬件架构,这使得开发者可以在同一台物理计算机上模拟不同架构的硬件环境,进行跨平台的开发和测试 。无论是开发基于 x86 架构的桌面应用,还是基于 ARM 架构的嵌入式系统,Qemu 都能提供相应的模拟环境 。

在操作系统支持方面,Qemu 同样表现出色,它几乎支持所有主流的操作系统,如 Windows、Linux、macOS、FreeBSD、Solaris 等 。用户可以在 Qemu 虚拟机中轻松安装和运行这些操作系统,无需担心兼容性问题 。例如,安全研究人员可以利用 Qemu 创建一个包含 Windows 操作系统和各种应用程序的虚拟机,用于进行恶意软件分析和漏洞挖掘 。这种广泛的硬件和系统支持,使得 Qemu 成为了开发者和技术爱好者的得力助手,为他们的工作和研究提供了极大的便利 。

7.3强大的社区与生态

Qemu 拥有一个庞大且活跃的开源社区 。在这个社区中,来自世界各地的开发者和技术爱好者们积极参与 Qemu 的开发和维护,不断为 Qemu 贡献新的功能和特性 。社区成员们还会在社区论坛、邮件列表等平台上分享自己的使用经验、技术心得和解决方案,当用户在使用 Qemu 过程中遇到问题时,可以在社区中寻求帮助,往往能得到其他成员的热心解答和支持 。

同时,社区还提供了丰富的文档资源,包括官方文档、用户手册、教程等,这些文档详细介绍了 Qemu 的使用方法、工作原理和开发指南,无论是初学者还是有经验的用户,都能从中获取到有用的信息 。此外,Qemu 还有着丰富的生态系统,它与许多其他开源项目和工具紧密集成,如 KVM、libvirt、OpenStack 等 。这些集成使得Qemu的功能得到了进一步扩展和增强,用户可以根据自己的需求,选择合适的工具和技术与Qemu搭配使用,构建出更加完善的虚拟化解决方案 。

http://www.xdnf.cn/news/188317.html

相关文章:

  • 深入浅出限流算法(一):简单但有“坑”的固定窗口计数器
  • 大数据应用开发和项目实战
  • 第五章、SpringBoot与消息通信(三)
  • 线性代数——行列式⭐
  • 任意波形发生器——2路同步DA模拟量输出卡
  • 项目管理 - 1.Maven
  • [特殊字符] SpringCloud项目中使用OpenFeign进行微服务远程调用详解(含连接池与日志配置)
  • stm32week13
  • Swiper 在 Vue 中的使用指南
  • 02《小地图实时》Unity
  • 榕壹云信用租赁系统:基于ThinkPHP+MySQL+UniApp的全链路免押租赁解决方案
  • [ACTF2020 新生赛]Include [ACTF2020 新生赛]Exec
  • 基于ffmpeg的音视频编码
  • 电路研究9.3.2——合宙Air780EP中的AT开发指南:HTTP(S)-PDP的研究
  • 【图论 拓扑排序 bfs】P6037 Ryoku 的探索|普及+
  • SpeedyAutoLoot
  • DeepSeek+Dify之五工作流引用API案例
  • 在自动驾驶数据闭环中的特征工程应用
  • VSCode 查看文件的本地修改历史
  • 大模型(LLMs)加速篇
  • Ubuntu 20.04 上安装 最新版CMake 3.31.7 的详细步骤
  • MongoDB的增删改查操作
  • 如何搭建spark yarn模式的集群
  • vite项目tailwindcss4的使用
  • 检查IBM MQ SSL配置是否成功
  • 代码片段存储解决方案ByteStash
  • 每日算法-250428
  • 跨境电商店铺矩阵布局:多账号运营理论到实操全解析
  • JVM 内存分配策略
  • 深海科技服务博客简介