14 构建
本节介绍简单构建、多配置目标构建、为多台机器生成镜像等各种构建流程。
14 构建简单镜像
在开发环境中,修改硬件支持、添加或修改系统库,以及更改有依赖关系的服务时, 都需要重新构建镜像。Yocto Project 支持多种构建方式,本节介绍在 Linux 构建主机上用 BitBake 构建简单镜像的基本步骤。
备注
通过 Toaster 构建镜像,见 Toaster 用户手册。
通过
devtool构建镜像,见应用程序开发与可扩展 SDK(eSDK)手册的 “在 SDK 工作流中使用 devtool”。OpenEmbedded 构建系统的快速示例见 Yocto Project 快速构建。
也可以使用 Visual Studio Code 的 Yocto Project BitBake 扩展构建镜像。
构建过程从源码创建完整 Linux 发行版,放在 Build Directory 的
tmp/deploy/images 下。详细流程见概述与概念手册的
“镜像”。
下图和列表概述构建过程:
准备开发主机: 设置能够使用 Yocto Project 的构建主机,见 “准备使用 Yocto Project”。
确认配置正确: 使用 bitbake-config-build 定义 MACHINE 或 DISTRO,在 build/conf/site.conf 中设置站点专用选项。
构建镜像: 使用
bitbake:$ bitbake target
备注
BitBake 的更多信息见 BitBake User Manual。
target 是要构建的配方名称。常用目标是 OpenEmbedded-Core (OE-Core) 的
meta/recipes-core/images、meta/recipes-sato/images等目录中的 镜像,也可以是 BusyBox 等特定软件的配方。支持的镜像详见参考手册的 “镜像” 一章。例如,构建
core-image-minimal:$ bitbake core-image-minimal
镜像构建完成后,通常还需安装。生成的镜像和内核位于 Build Directory 的
tmp/deploy/images。运行qemux86、qemuarm等预构建镜像的方法见 Yocto 项目应用开发与软件开发套件(SDK/eSDK)手册;安装方法见 对应开发板或机器的文档。
14 通过多配置为多个目标构建镜像
参阅 Yocto Project 开发任务手册的 使用 Multiconfig 为多个目标构建镜像。
14 构建初始 RAM 文件系统(Initramfs)镜像
初始 RAM 文件系统(Initramfs )提供用于早期系统初始化的临时根文件 系统,通常包含定位并挂载最终根文件系统所需的工具和模块。
创建步骤如下:
创建 Initramfs 镜像配方: 可参考 OpenEmbedded-Core (OE-Core) 的
meta/recipes-core中的core-image-minimal-initramfs.bb。 该配方基于下文介绍的 initramfs-framework。决定是否将 Initramfs 嵌入内核镜像: 如果需要,在机器等 configuration file 中将 INITRAMFS_IMAGE_BUNDLE 设为
"1",并在内核配方中设置 INITRAMFS_IMAGE。设置 INITRAMFS_IMAGE_BUNDLE 后,Initramfs 会解包到
${B}/usr/,再通过 CONFIG_INITRAMFS_SOURCE 传给内核Makefile,按正常方式编入内核。按需通过配方添加内容: 向 Initramfs 添加内容时,应使用 PACKAGE_INSTALL,而不是 IMAGE_INSTALL。前者能更直接地 控制镜像内容,避免引入 image 或 core-image 类设置但未必需要的默认内容。
构建内核和 Initramfs: 用 BitBake 构建内核。由于 Initramfs 配方是内核镜像的依赖,也会同时构建;如果设置了 INITRAMFS_IMAGE_BUNDLE,则会嵌入内核镜像。
14 使用 initramfs-framework 定制 Initramfs
meta/recipes-core/images
中的 core-image-minimal-initramfs.bb 使用
initramfs-framework_1.0.bb
作为基础组件。该配方旨在提供构建自定义 Initramfs 的基本构件。
它使用 meta/recipes-core/initrdscripts/initramfs-framework 中的 Shell 初始化脚本。由于部分脚本并非适合所有场景,配方拆分为多个软件包:
initramfs-framework-base:安装init脚本、/dev/console字符 特殊文件等基础组件。下列模块都运行时依赖它,因此安装任一模块都会自动引入。initramfs-module-exec:支持执行应用程序。initramfs-module-mdev:支持 mdev。initramfs-module-udev:支持 Udev。initramfs-module-e2fs:支持 ext4/ext3/ext2 文件系统。initramfs-module-nfsrootfs:支持通过 NFS 定位并挂载根分区。initramfs-module-rootfs:支持定位并挂载根分区。initramfs-module-debug:提供动态调试支持。initramfs-module-lvm:支持 LVM 根文件系统。initramfs-module-overlayroot:在只读根文件系统上挂载可读写覆盖层。
除上述配方自身定义的软件包外, meta/recipes-core/initrdscripts 中的配方还提供:
initramfs-module-install:在所选块设备上创建并安装分区布局。initramfs-module-install-efi:在所选块设备上创建并安装 EFI 分区布局。initramfs-module-setup-live:如果 内核命令行 传入root=/dev/ram0或没有root=,在 Initramfs 中启动 Shell。
定制时,可为 core-image-minimal-initramfs 创建
bbappend,
从 PACKAGE_INSTALL 添加或移除上述软件包;也可以参考该配方创建
自己的 Initramfs 镜像配方。
通过自编配方可向 Initramfs 添加自定义脚本。惯例名称为
initramfs-module-<module name>,其中 <module name> 是模块名称。
配方的包专用 RDEPENDS 应包含 initramfs-framework-base 及其他
运行时依赖。
配方必须将 Shell 初始化脚本安装到 ${D}/init.d,名称采用 <number>-<script name>:
<number>为 两位数字,影响脚本执行顺序。例如,80-setup-live在01-udev之后执行,因为 80 大于 01。必须使用两位数字,因为脚本按字典顺序执行。
10-script会排在8-script之前,因此后者应命名为08-script。<script name>可自由选择。如果数字相同,则按脚本名称的字典顺序排序。
14 嵌入来自独立 Multiconfig 的 Initramfs 镜像
有时需要 Initramfs 不继承主镜像的发行版策略。例如,主镜像使用
TCLIBC = "glibc",但 Initramfs 使用 TCLIBC = "musl" 以减小体积。
仅按上述步骤操作,Initramfs 会继承 glibc 设置,无法覆盖。
为此,需要额外执行:
为 Initramfs 创建 multiconfig: 按 “通过多配置为多个目标构建镜像” 创建独立配置。假设名为
initramfscfg.conf,内容如下:TMPDIR = "${TOPDIR}/tmp-initramfscfg" TCLIBC = "musl"
在主配置设置额外 Initramfs 变量: 在 configuration file 中添加:
INITRAMFS_MULTICONFIG = "initramfscfg" INITRAMFS_DEPLOY_DIR_IMAGE = "${TOPDIR}/tmp-initramfscfg/deploy/images/${MACHINE}"
INITRAMFS_MULTICONFIG 和 INITRAMFS_DEPLOY_DIR_IMAGE 在内核与
initramfscfg中的 INITRAMFS_IMAGE 之间建立多配置 依赖,并指定该镜像的位置。此配置下,内核仍用主配置构建,但 do_bundle_initramfs 从 INITRAMFS_DEPLOY_DIR_IMAGE 获取指定 INITRAMFS_IMAGE。 最终内核嵌入基于 musl 的 Initramfs,主镜像则基于 glibc。
同一方法也可避免 Initramfs 继承 DISTRO_FEATURES,或为其使用
poky-tiny等不同 DISTRO。
14 构建精简系统
极小发行版具有明显优势:片上或封装内存需求更低、成本更低;缓存利用更有效, 性能更好;内存更少,功耗更低;启动更快,开发开销更小。数码相机、医疗设备和 小型无显示器系统等场景尤其受益。
本节介绍如何将发行版裁剪到比 Yocto Project 开箱可构建、约 5 MB 的
poky-tiny 更小。
14 精简系统概述
为缩小根文件系统、加快启动、保留关键功能并避免初始 RAM 磁盘,需要考虑:
14 目标和指导原则
动手前先明确目标。创建极小发行版时,可参考以下原则:
确定空间目标,例如内核不超过 1 MB,根文件系统不超过 3 MB。
找出占用 90% 空间的部分,集中优化。
不要为达到目标引入难以维护的特殊改造。
利用设备专用选项。
在独立层中工作以隔离变更。创建层的方法见 “理解和创建层”。
14 了解镜像体积的组成
创建发行版时最好有现成起点。可以直接用 Yocto Project 创建 poky-tiny,
最终再以其为参考,在自己的发行版中修改。
备注
使用 poky-tiny 时,按 bitbake-config-build enable-fragment
将 distro/ 片段设为 "poky-tiny",或在
build/conf/local.conf 中将 DISTRO 设为该值。
理解内存概念有助于缩小系统。内存分为静态、动态和临时部分:静态部分包括
TEXT(代码)、DATA(已初始化数据)和 BSS(未初始化数据);动态部分包括
运行时分配的栈、哈希表等;临时部分在启动后回收,包括内核解压缩及
__init__ 函数使用的内存。
OpenEmbedded-Core (OE-Core) 的 scripts/tiny/ 中有两个工具,
用于查看内核和根文件系统当前大小:
ksize.py:报告内核构建对象各组件的大小。dirsize.py:报告根文件系统各组件的大小。
以下工具和命令用于组织配置片段,并直观查看文件依赖:
merge_config.sh:管理内核配置文件和片段,可将独立片段合并,允许 覆盖设置并对缺失选项发出警告。适合迭代配置、创建最小配置,以及为不同 机器生成配置,无需重复整个流程。此脚本位于 Linux Yocto 内核仓库(如
linux-yocto-3.14、linux-yocto-3.10、linux-yocto-3.8)的scripts/kconfig。配置片段详见内核开发手册的 “创建配置片段”。
bitbake -u taskexp -g bitbake_target:打开依赖浏览器,查看文件依赖。 理解依赖有助于合理决定裁剪哪些内核和根文件系统内容。
14 裁剪根文件系统
根文件系统由启动组件、库和应用软件包组成。可以调整打包配置来改变构建方式, 也可以修改文件系统本身或选择另一种文件系统。
先在根目录运行 dirsize.py,查明空间主要被什么占用:
$ cd root-directory-of-image
$ dirsize.py 100000 > dirsize-100k.log
$ cat dirsize-100k.log
脚本可过滤小于指定大小的文件,上例忽略小于 100 KB 的文件。报告的是未压缩 大小,在压缩根文件系统中通常会按相对固定比例缩小。检查日志时,重点关注 占用大量空间的区域。
确保裁剪不会破坏所需功能。可以通过 BitBake 的依赖浏览器观察软件包关系:
$ cd image-directory
$ bitbake -u taskexp -g image
在界面中选择候选删除软件包,查看其依赖关系。
优先移除对功能集影响最小的软件包。例如,可能不需要 VGA 显示,或可以用
devtmpfs 和 mdev 替代 udev。
在发行版 configuration file 中进行此类修改。例如,移除 udev
和 glib:
VIRTUAL-RUNTIME_dev_manager = ""
最后,选择既满足需求又能减小体积的根文件系统,例如 cramfs、squashfs、
ubifs、ext2 或基于 initramfs 的 Initramfs。注意,
ext3 需要 1 MB 日志;如果可以只读运行,就不需要该日志。
备注
每轮裁剪后都需重新构建,再使用工具检查效果。
14 裁剪内核
内核构建包含与硬件无关的策略:启用哪些子系统、面向哪种架构、默认构建哪些驱动?
备注
如果希望改善启动时间,也可以修改内核源码。
在 Linux 构建顶层目录运行 ksize.py,了解内核组成:
$ cd top-level-linux-build-directory
$ ksize.py > ksize.log
$ cat ksize.log
日志显示驱动、网络、内核核心、文件系统、声音等内置 .o 文件的空间占用。
这些值未压缩,压缩内核镜像中通常按相对固定比例缩小。重点处理占用较大、符合
“90% 原则”的区域。
使用 -d 深入查看各区域:
$ ksize.py -d > ksize.log
该选项展开驱动、网络等区域的各个文件信息。
结合可以舍弃的功能和日志决定裁剪内容,例如不需要声音就可移除全部声音驱动。
决定后,需要重新配置内核,使下次构建体现变更。可以用 menuconfig 一次
完成所有修改,但难以判断每项裁剪的效果,也不利于为其他目标复现。更好的方式
是从 allnoconfig 开始,为各项变化创建配置片段,再用 merge_config.sh
合并为单个配置文件。这样便于反复进行配置、构建的迭代。
每次修改配置后,都应重建内核并检查对整体体积的影响。
14 移除软件包管理需求
包管理会增加镜像体积。可以移除包管理器、其独有依赖以及包管理数据来缩小镜像。
确保镜像的 IMAGE_FEATURES 中不包含 "package-management",即可从 根文件系统移除包管理器及其依赖。
14 寻找其他缩小体积的方法
根据具体系统,通常还存在其他可裁剪区域。结合上述工具和方法,不断试验和迭代 才能找到它们。可以尝试:
glibc:一般流程如下:从 DISTRO_FEATURES 移除认为不需要的
glibc功能。构建发行版。
如果某包因缺少符号而失败,判断能否重新配置该包,使其不依赖这些功能。 例如像
ncurses一样禁用宽字符支持。如果仍需要,则查明哪些glibc功能提供支持,并恢复相应配置。
重新构建,重复上述流程。
busybox:流程与glibc类似,但需要启动生成的系统,确认所有预期 操作仍能完成。还必须将配置片段集成到 BusyBox,因为它自行管理核心功能, 再允许在其上添加配置片段。
14 反复迭代
如果尚未达到体积目标,就重复上述流程。用工具找出占根文件系统和内核 90% 的内容,判断哪些可以移除,而不会使设备功能低于实际需要。
BusyBox 常是值得检查的地方,它在单个可执行文件中提供精简版 Unix 工具。 可能还能去掉虚拟终端服务或 IPv6。
14 为多台机器构建镜像
常见需求是为使用相同软件环境的多台机器生成镜像。人们容易为每台硬件选择 最专用的调优和优化标志,但这会显著增加总构建时间和软件包源维护成本。 例如,针对特定 CPU 核心的调优可能启用一些 GCC 微优化,但相比所有构建使用 更通用的设置,实际性能差异未必明显。与其为每台机器分别将 DEFAULTTUNE 等调到极致,不如让构建系统在适合的位置跨机器复用软件。
如果重视构建速度和软件包源维护,可考虑以下做法:
共享 Build Directory : 尽可能让各构建共享 TMPDIR。 Yocto Project 支持在同一临时目录切换 MACHINE,开发者经常这样 进行多机器构建。这样可复用本机配方,通常也能复用交叉配方,从而缩短时间。
选择适当的软件包架构: 构建系统默认提供 "all"、"tune" 或 "package"、 "machine" 三个层次。配方通常选择其中一种。根据输出选择正确架构,会 直接影响构建时间。
只生成脚本的配方可以使用 "all",因为无需构建二进制文件。继承 allarch 即可,它会配置多个变量,使软件包跨架构通用。
如果输出是机器专用的,或构建、运行时依赖已具有机器架构依赖,那么配方 也具有这种依赖,应通过 MACHINE_ARCH 启用 "machine" 架构:
PACKAGE_ARCH = "${MACHINE_ARCH}"
未通过 PACKAGE_ARCH 显式指定时,默认使用 TUNE_PKGARCH:
PACKAGE_ARCH = "${TUNE_PKGARCH}"
尽可能使用通用调优文件: 某些调优适合多个目标,例如
armv5软件包 通常可运行于armv6、armv7,i486二进制可运行于i586及更高处理器,但不会利用新处理器的改进特性。多台机器使用相同调优时,构建系统复用已有软件。即使各机器生成独立系统 根目录,也不会重新编译软件,且只需维护一个软件包源。
管理更细粒度的打包: 有时在上述三层之外增加架构层次很有帮助。例如, NXP(原 Freescale)的 meta-freescale 层 通过 fsl-dynamic-packagearch 为 i.MX53 板共享 AMD GPU 软件包,为 i.MX6 板共享 Vivante GPU 软件包。 该类检查 BitBake 数据存储,判断包是否提供或依赖某个子架构值;如果是, 根据
MACHINE_SUBARCH设置 PACKAGE_ARCH。否则,若命中机器 专用过滤器,则设为 MACHINE_ARCH。通过复用二进制减少构建软件包 数量,节省时间。使用工具排查问题: 有时软件发生了看似不必要的重建,例如机器间没有 如预期共享状态。通常是本应只与调优相关的代码引用了 MACHINE、 SERIAL_CONSOLES、XSERVER、MACHINE_FEATURES 等 机器变量,或者通过 DEPENDS、RDEPENDS、RRECOMMENDS、 RSUGGESTS 等依赖了 PACKAGE_ARCH 为 "${MACHINE_ARCH}" 的其他配方。
备注
此类问题偶尔会出现,欢迎提交修复补丁。
可用工具包括:
state-diff-machines.sh: 位于源码仓库scripts,用法见脚本注释。BitBake 的 "-S printdiff": 尝试寻找最近的签名匹配,例如共享状态 缓存中的匹配,再比较签名,找出两个戳记树发生分歧的位置和差异。
14 从外部源码构建软件
默认情况下,OpenEmbedded 在 Build Directory 构建源码,先获取、 解包并按需应用补丁,然后进行构建。
有时希望直接构建系统之外的源码。例如,新 BSP 使用高度定制内核,希望开发 团队尽量少接触构建系统,专注项目并保持原有流程。可以在开发机器上保留独立 内核源码目录,让配方的 SRC_URI 指向它并直接使用,而不复制。
实际从外部源码构建,只需继承 externalsrc,将 EXTERNALSRC 指向源码。在 build/conf/local.conf 中设置:
INHERIT += "externalsrc"
EXTERNALSRC:pn-myrecipe = "path-to-your-source-tree"
也可在配方或追加文件中设置 EXTERNALSRC:
EXTERNALSRC = "path"
EXTERNALSRC_BUILD = "path"
备注
必须在全局或局部继承 externalsrc,这些设置才会生效。
默认在 EXTERNALSRC 指定源码目录之外的独立目录构建。如果需要在源码 目录本身或其他指定目录构建,设置 EXTERNALSRC_BUILD:
EXTERNALSRC_BUILD:pn-myrecipe = "path-to-your-source-tree"
备注
EXTERNALSRC 和 EXTERNALSRC_BUILD 必须为绝对路径。
14 离线复现构建
可以为构建所用上游源码制作“快照”,随后离线复现构建。先准备下载目录并填充 快照文件,之后就可随时在任何机器上使用它复现。
填充下载目录的步骤如下:
创建干净下载目录: 从空 DL_DIR 开始。可以清空已有目录,或将 变量指向一个空位置或尚不存在的位置。
为源码 Git 仓库生成归档包: 在 build/conf/local.conf 中设置:
DL_DIR = "/home/your-download-dir/" BB_GENERATE_MIRROR_TARBALLS = "1"
下一步获取源码时,BitBake 会收集文件,并在 DL_DIR 中创建归档包。 更多信息见 BB_GENERATE_MIRROR_TARBALLS。
仅填充下载目录,不构建: 用 BitBake 获取源码,但禁止构建:
$ bitbake target --runall=fetch
下载目录
${DL_DIR}现在包含归档包形式的源码快照,可用于构建。按需删除 Git 或其他 SCM 子目录: 可以移除
${DL_DIR}/git2/*等 版本控制子目录,因为归档包已经包含这些内容。
所需源码齐备后,即可创建自己的 "own-mirror" 并构建目标。这些文件可以在 任何机器、任何时间用于离线构建。
使用下载目录中的文件构建:
仅使用本地文件: 在 build/conf/local.conf 或发行版 configuration file 中设置 SOURCE_MIRROR_URL,继承 own-mirrors 并设置 BB_NO_NETWORK:
SOURCE_MIRROR_URL ?= "file:///home/your-download-dir/" INHERIT += "own-mirrors" BB_NO_NETWORK = "1"
SOURCE_MIRROR_URL 和 own-mirrors 将下载目录 配置为自有镜像源;BB_NO_NETWORK 确保第三步的获取过程只使用 本地文件。
从干净构建开始: 删除
${TMPDIR},或使用新的 Build Directory。构建目标: 使用 BitBake:
$ bitbake target
构建使用自有镜像源中的本地源码快照完成。快照归档包位于下载目录。