首页 > 其他分享 >『QEmu』理解QEMU构建系统

『QEmu』理解QEMU构建系统

时间:2024-09-10 20:51:26浏览次数:9  
标签:obj CONFIG Kconfig riscv QEMU SIFIVE 构建 QEmu

QEmu 采用了一套由 Kconfig 发展而来的 Domain-Specific Language (DSL 领域特定语言),和 meson 相结合。其特点是对于模块编译的依赖关系较为严格(QEmu 文档自己说的),在大量不同种类的主板之间也可以对同样的模块采用同样的共享代码。对于开发者来说,一方面添加新的设备较为容易;另一方面也使QEmu易于裁剪,可以把依赖树以外的代码全部去掉。
为了实现以上功能,QEmu设计了一套自己的构建流程,主要由 Target、Kconfig和meson组成。Target指的是QEmu所定义的一系列构建目标,分别对应一套最根本的若干配置项;Kconfig是用于计算配置项依赖的配置工具,用于从Target的根本配置项出发、通过依赖关系得到所有的配置项内容;meson则用于根据配置项生成构建列表,扮演类似make、cmake的角色。

Target - 构建的起点

QEmu的构建以 target 为起点(TARGET_*),每个 target 都定义了一系列的选项子集(CONFIG_*),以此层层向下依赖确定每个模块的编译是否进行。
关于目标和相应顶层选项的配置文件位于 config/ 中。有趣的是,虽然文档里写的是 default-configs ,但是这个目录设置在 QEmu v6.1 版本被改成了 config/ ,它们实际上是同样的东西。
即:

qemu/configs
├── devices
│   ├── ...
│   ├── aarch64-softmmu
│   │   ├── default.mak
│   │   └── minimal.mak
│   ├── ppc-softmmu
│   │   └── default.mak
│   ├── riscv64-softmmu
│   │   └── default.mak
│   ├── x86_64-softmmu
│   │   └── default.mak
│   └── ...
├── meson
│   └── windows.txt
└── targets
    ├── ...
    ├── riscv64-linux-user.mak
    ├── riscv64-softmmu.mak
    ├── x86_64-bsd-user.mak
    ├── x86_64-linux-user.mak
    ├── x86_64-softmmu.mak
    └── ...

这里列出了我们比较关心的几个 target ,除了这些以外还有许多其他不同平台的 targetconfigs/targets 中的内容即为我们可以作为 target 构建的目标,以 configs/targets/riscv64-softmmu.mak 为例,其内容为:

TARGET_ARCH=riscv64
TARGET_BASE_ARCH=riscv
TARGET_SUPPORTS_MTTCG=y
TARGET_XML_FILES= gdb-xml/riscv-64bit-cpu.xml gdb-xml/riscv-32bit-fpu.xml gdb-xml/riscv-64bit-fpu.xml gdb-xml/riscv-64bit-virtual.xml
TARGET_NEED_FDT=y

configs/devices/riscv64-softmmu/default.mak 中的内容则是:

# Default configuration for riscv64-softmmu

# Uncomment the following lines to disable these optional devices:
#
#CONFIG_PCI_DEVICES=n
CONFIG_SEMIHOSTING=y
CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y

# Boards:
#
CONFIG_SPIKE=y
CONFIG_SIFIVE_E=y
CONFIG_SIFIVE_U=y
CONFIG_RISCV_VIRT=y
CONFIG_MICROCHIP_PFSOC=y
CONFIG_SHAKTI_C=y

每个目标分别对应了一套配置依赖树。确定对应的Target也就是确定了配置依赖树的根节点——用于计算后续所依赖配置的最初的几个配置。随后,这些配置项会基于源码树不同目录中Kconfig的内容推断出更细节的、下一层的配置项取值。

Kconfig - 依赖关系计算

这个Kconfig跟Linux内核源码树里面的Kconfig其实差不多是一个东西。对于一个配置项来说,它可能具有某些依赖(依赖项没启用它不能启用)、某些默认数值(如果没有显式说明的话它就取这个默认值),它可以导致其他的一些选项一定被选上,或者让某些选项具有一些默认的内容(如果没有显式说明的话这个配置项应该就得是这个值)。对于多个配置项来说,前述的这些规则对同一个配置项产生的推断结果可能又会产生冲突。Kconfig的作用就是记录开发者所定义的这些依赖关系、处理推断产生的冲突,并根据给定的根本配置项推断得出所有需要的配置项的值。
在Linux内核构建中,用户可以用 menuconfig 这样的工具更深入地调整各种各样的配置项的取值;而在QEmu中则显得局限得多,因为用户基本只需要关心到底选择哪一个 Target 进行构建。对于QEmu的开发者而言,往往是需要通过修改 Kconfigmeson.build 文件去添加新的依赖关系和代码模块,也不需要过于细致地手动选择配置项的值。
在QEmu源码树所有有代码的地方(或者说,源码树的各级结点处)都会见到对应的 Kconfig 文件,其语法和Linux内核树中的 Kconfig 类似,采用和目录结构相同的树形结构构建出一个Kconfig树。
Kconfig中的配置单元称为“元素”,每个“元素”具有不同类型的值,以及赋值的规则和条件。
它们的语法规则如下:

source gua/Kconfig               # 将子目录中 gua/Kconfig 的配置信息包括进来

config GUA_BOARD                 # 该元素的名称是 GUA_BOARD
	bool                         # 该元素的数值类型是 bool
	depends on <expr>            # 若 <expr> 为 n 则为 n
	select <symbol> [if <expr>]  # 若为 y 则令 <symbol> 为 y
	default <value> [if <expr>]  # 默认情况下为 <value>
	imply <symbol> [if <expr>]   # 令 <symbol> 默认情况下为 y

hw/riscv/Kconfig 为例,其内容如下:

...

config IBEX
    bool

config SIFIVE
    bool
    select MSI_NONBROKEN

...

config SIFIVE_U
    bool
    select CADENCE
    select HART
    select SIFIVE
    select UNIMP

config SPIKE
    bool
    select HART
    select HTIF
    select SIFIVE

...

值得注意的是,和Linux内核源码树中的Kconfig一样,这里的每一个元素名(如 SPIKE)实际上代表的就是配置项 CONFIG_* (即 CONFIG_SPIKE)。其中的 select 是一个很强的依赖规则。一般来说,如果当前元素是一个板卡的话,会 select 这个板卡上必然存在的子系统或者特性——因为这种情况下被 select 的目标元素取值通常不会被其他元素左右;否则会更倾向于用 imply 。依据这种默契,我们可以通过Kconfig更好地理解需要打交道的虚拟设备。

meson - 执行构建

按照 QEMU Internals 的说法,添加机器模型需要改动的构建文件是 Makefile.objs 。在 QEmu v5.2版本中也被替换为了 meson.build ,体现了 QEmu 构建体系的变化。因此,我们也可以通过这个更像 Makefile 的表亲来分析 meson.build 所代表的角色。以 QEmu v5.1 中的 hw/riscv/Makefile.objs 为例,其中的主要文件内容如下:

obj-y += boot.o
obj-$(CONFIG_SPIKE) += riscv_htif.o
obj-$(CONFIG_HART) += riscv_hart.o
obj-$(CONFIG_OPENTITAN) += opentitan.o
obj-$(CONFIG_SIFIVE_E) += sifive_e.o
obj-$(CONFIG_SIFIVE_E) += sifive_e_prci.o
obj-$(CONFIG_SIFIVE) += sifive_clint.o
obj-$(CONFIG_SIFIVE) += sifive_gpio.o
obj-$(CONFIG_SIFIVE) += sifive_plic.o
obj-$(CONFIG_SIFIVE) += sifive_test.o
obj-$(CONFIG_SIFIVE_U) += sifive_u.o
obj-$(CONFIG_SIFIVE_U) += sifive_u_otp.o
obj-$(CONFIG_SIFIVE_U) += sifive_u_prci.o
obj-$(CONFIG_SIFIVE) += sifive_uart.o
obj-$(CONFIG_SPIKE) += spike.o
obj-$(CONFIG_RISCV_VIRT) += virt.o

可以看到,这里用了一个比较有意思的做法,把所有对应配置项为 y 的模块都加入了 obj-y 编译列表中,剩下的加入 obj-n 中(以 obj-$(CONFIG_SPIKE) 为例,如果 CONFIG_SPIKE=y 那么这个符号就是 obj-y )。 obj-y 就是稍后会实际进行编译的列表。而在 QEmu v9.1.0 中, hw/riscv/meson.build 的内容如下:

iscv_ss = ss.source_set()
riscv_ss.add(files('boot.c'))
riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c'))
riscv_ss.add(files('riscv_hart.c'))
riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c'))
riscv_ss.add(when: 'CONFIG_RISCV_VIRT', if_true: files('virt.c'))
riscv_ss.add(when: 'CONFIG_SHAKTI_C', if_true: files('shakti_c.c'))
riscv_ss.add(when: 'CONFIG_SIFIVE_E', if_true: files('sifive_e.c'))
riscv_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u.c'))
riscv_ss.add(when: 'CONFIG_SPIKE', if_true: files('spike.c'))
riscv_ss.add(when: 'CONFIG_MICROCHIP_PFSOC', if_true: files('microchip_pfsoc.c'))
riscv_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c'))

hw_arch += {'riscv': riscv_ss}

这边的内容可读性就比较高了,会先根据配置项做一个判断,判断为 y 的加入列表 riscv_ss 中。可以看出,这两个文件都试图完成一个任务:根据配置(CONFIG_*)的值决定要不要在编译中添加对应的叶子节点模块(指的是QEmu源码树的最末端的模块)。叶子结点负责将需要编译的模块收集起来,而最终的构建则交由顶层结点进行。

将自己的模块加入编译

这里可以参考 A deep dive into QEMU: a new machine 的做法——先判断自己的代码是属于什么架构的,如果是一个新的架构,那么在 hw/ 下单开一个子目录,然后照葫芦画瓢加上配套的 Kconfig 和 meson.build 文件(当然, meson.build 比该文写就时的 Makefile.objs 要复杂上不少,可能需要到 configs/ 中修改 Target 配置);如果只是一个架构下的新机器模型(比如说 hw/riscv/ ),那么只需要在 hw/riscv/meson.build 中模仿 riscv_ss.add(files('riscv_hart.c)) 加上 riscv_ss.add(files('Your_File.c')) 就可以加入编译了。
在代码中,则需要根据QEmu编写设备文件的规范,把 TypeInfo 等 QOM 注册新设备所需的数据结构都准备好,这样就可以编译得到包含自己编写的新设备的QEmu。由于 QEmu 采用了一套内置的面向对象模型 QOM ,使用函数指针的方式使用与设备相关的代码,函数甚至可以与其他设备代码中的符号重名,就像内核代码一样,不专门导出的符号是不能在其他文件中使用的——反过来说,单文件如果不做特殊的处理,就几乎是一个独立的命名空间。

标签:obj,CONFIG,Kconfig,riscv,QEMU,SIFIVE,构建,QEmu
From: https://www.cnblogs.com/Chorolop/p/18407147

相关文章

  • 使用Flask框架构建RESTful API:从基础到实践
    随着移动设备和Web应用的普及,API(应用程序接口)的重要性日益凸显。RESTfulAPI因其简洁的设计和广泛的支持成为构建现代Web服务的标准。Flask是一个轻量级且灵活的PythonWeb框架,非常适合用来快速搭建RESTfulAPI。本文将详细介绍如何使用Flask构建一个简单的RESTfulAPI,并提......
  • docker镜像构建libreoffice转换文件
    具体需求: 根据Libreoffice最新版本,创建一个容器环境,用于文件不同类型的转换#使用阿里云的Python镜像FROMdockerpull.com/python:3.9-slim#设置工作目录WORKDIR/app#更新debian系统的APT源列表为阿里ARGDEBIAN_FRONTEND=noninteractiveRUNapt-getclean&&\......
  • SpringBoot环境下的大学生租房系统构建
    第2章开发环境与技术大学生租房平台的编码实现需要搭建一定的环境和使用相应的技术,接下来的内容就是对大学生租房平台用到的技术和工具进行介绍。2.1MYSQL数据库本课题所开发的应用程序在数据操作方面是不可预知的,是经常变动的,没有办法直接把数据写在文档里,这样不仅仅......
  • 低代码门户技术:构建灵活、高效的企业门户解决方案
    正文:在当今快节奏的商业环境中,企业门户网站已经成为连接内部员工、外部客户以及合作伙伴的关键平台。为了应对日益增长的业务需求和技术挑战,低代码门户技术应运而生,它提供了一种更加灵活、高效的方式来构建和管理企业门户。本文将探讨低代码门户技术的优势、应用场景以及如何......
  • 电商API接口开发:构建强大的商品信息检索系统
    在电子商务的快速发展中,商品详情数据的准确性和可访问性对于提升用户体验和增强业务竞争力至关重要。电商API接口作为连接用户、商家和平台的桥梁,其设计与实现的质量直接影响到数据的检索效率和准确性。本文将深入探讨电商API接口在商品详情数据方面的设计与实现。一、商品详情数据......
  • 墙裂推荐:《Transformer自然语言处理实战:使用Hugging-Face-Transformers库构建NLP应用
    大家好,今天给大家推荐一本大模型神书——《Transformer自然语言处理实战:使用Hugging-Face-Transformers库构建NLP应用》。近年来,Transformer模型在NLP领域取得了显著成果。为了让广大开发者更好地掌握这一技术,给大家推荐一本实战教程——《Transformer自然语言处理实战:使用......
  • 深度学习基础案例4--运用动态学习率构建CNN卷积神经网络实现的运动鞋识别(测试集的准
    ......
  • 构建基于Qwen API 的AgentScope 聊天机器人
    环境搭建与库安装首先,我们需要创建一个Python3.10环境。你可以使用conda来创建一个新的虚拟环境,并激活它:condacreate-npy310python==3.10condaactivatepy310接着,安装所需的库agentscope,由于它可能处于预发布阶段,因此我们需要指定--pre标志:pipinstallagentscope--pre获取......
  • 【0326】Postgres内核之 VACUUM (FULL)构建所有要 VACUUM 的 relation(s) list(17)
    上一篇:【0325】Postgres内核之VACUUM(FULL)创建BufferAccessStrategyobject(16)1.构建vacuum关系表(reltaions)List在上一篇文章中讲解了Postgres内核创建缓冲区策略对象,之后初始化给全局指针变量vac_strategy。接下来Postgres将通过vacuum()函数的参数,以确认用户......
  • 基于“SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI
      近年来,国内外学者在生态系统的敏感性、适应能力和潜在影响等方面开展了大量的生态脆弱性研究,他们普遍将生态脆弱性概念与农牧交错带、喀斯特地区、黄土高原区、流域、城市等相结合,评价不同类型研究区的生态脆弱特征,其研究内容主要包括脆弱性的时空演变、动态监测、影响机......