2、Yocto核心概念:Recipe、Layer、Metadata、Machine、Distro、Image、Package

好,咱们今天来啃几个硬骨头。Yocto 里有一堆术语,Recipe、Layer、Metadata…… 刚接触时很容易被绕晕。我当年刚入坑时,看着这些词也是一脸懵。其实说白了,它们就是 Yocto 世界的「基本零件」。搞懂了它们,整个构建系统在你眼里就会变得透亮。

2.1 Recipe(配方)—— 构建的最小单元

Recipe 是 Yocto 里最基础的东西。你可以把它想象成一张「菜谱」。菜谱告诉你:要做一道菜,需要什么原料、怎么处理、先放什么后放什么。

在 Yocto 里,一个 Recipe 通常对应一个软件包。比如你想编译 openssl,那就需要一个 openssl.bb 文件。这个文件里写了:从哪里下载源码、需要哪些依赖、怎么配置、怎么编译、最终安装到哪个目录。

我个人的习惯是,拿到一个新 BSP 包,先翻翻 recipes-* 目录下的 .bb 文件。这能最快了解这个平台都集成了哪些软件。

一个典型的 Recipe 长这样:

SUMMARY = "A simple hello world application"
LICENSE = "MIT"
SRC_URI = "file://helloworld.c"

do_compile() {
    ${CC} ${CFLAGS} ${LDFLAGS} helloworld.c -o helloworld
}

do_install() {
    install -d ${D}${bindir}
    install -m 0755 helloworld ${D}${bindir}
}

你看,SRC_URI 指定了源码位置,do_compiledo_install 就是具体的构建步骤。嗯,这里要注意:Recipe 里的变量和函数都是 BitBake 能识别的,大小写敏感,别写错了。

2.2 Layer(层)—— 组织的艺术

一个项目有成百上千个 Recipe,全扔一个目录里?那不乱套了。Layer 就是用来解决这个问题的。

Layer 说白了就是一个「分类文件夹」。比如:

  • meta-arm:放 ARM 架构相关的 Recipe
  • meta-qt5:放 Qt5 相关的 Recipe
  • meta-myboard:你自己写的板级支持包

每个 Layer 都有自己的 conf/layer.conf 文件,告诉 BitBake 这个 Layer 的优先级和依赖关系。

我在项目中遇到过最头疼的事,就是两个 Layer 里定义了同名的 Recipe。BitBake 会根据优先级选择其中一个。如果你不小心把优先级搞反了,编译出来的东西可能完全不是你想要的。所以,我建议你给自定义 Layer 设置一个较高的优先级(比如 BBFILE_PRIORITY = "6"),确保它能覆盖掉公共 Layer 里的内容。

2.3 Metadata(元数据)—— 一切皆数据

Metadata 这个词听起来很玄乎。其实它就是指 Yocto 项目里所有 非代码 的描述性信息。包括 Recipe 文件、配置文件、类文件(.bbclass)、附加文件(.bbappend)等等。

你想想看,整个 Yocto 构建系统,本质上就是一个「元数据解释器」。BitBake 读取这些 .bb、.conf、.bbclass 文件,然后根据里面的指令去下载源码、打补丁、编译、打包。

我个人理解:Metadata 就是 Yocto 的「灵魂」。代码是死的,但 Metadata 告诉系统怎么处理这些代码。所以,写 Metadata 比写代码更需要细心。我曾经因为一个 .bbappend 文件里的路径写错了,导致整个文件系统里多了一个空目录,排查了整整一下午。

2.4 Machine(机器)—— 为硬件量身定制

Machine 定义了目标硬件平台的信息。比如:

  • CPU 架构(ARM、x86、RISC-V)
  • 内核配置
  • Bootloader 类型
  • 串口、网口等外设参数

这些信息都放在 conf/machine/ 目录下的 .conf 文件里。比如 qemuarm.confbeaglebone-yocto.conf

当你执行 bitbake core-image-minimal 时,BitBake 会先读取你指定的 Machine 配置,然后根据这个配置去选择对应的内核、驱动、设备树等。

小技巧: 如果你在移植一个新的开发板,第一步就是创建一个新的 Machine 配置文件。把 CPU 类型、内存大小、存储设备这些基本信息填对。剩下的工作就是不断调整 Recipe 和配置,直到系统能跑起来。

2.5 Distro(发行版)—— 系统的「性格」

Distro 定义了整个系统的「风格」。比如:

  • 使用什么 init 系统(systemd 还是 busybox)
  • 默认的 C 库(glibc 还是 musl)
  • 包管理方式(opkg、rpm 还是 deb)
  • 调试符号是否保留

Yocto 自带了一个 poky 发行版配置。你可以基于它修改,也可以完全自己写一个。我建议初学者先用 poky,等熟悉了再定制自己的 Distro。

为什么?因为 Distro 的配置会影响全局。比如你改了 PREFERRED_PROVIDER_virtual/libc,那整个系统里所有依赖 libc 的软件包都会受影响。我曾经在 Distro 配置里误把 DISTRO_FEATURES 中的 ipv6 去掉了,结果编译出来的网络工具全都不支持 IPv6,测试时才发现。

2.6 Image(镜像)—— 最终产物

Image 就是你要生成的最终文件系统镜像。比如 core-image-minimal 是一个最小的可启动镜像,core-image-sato 带了一个图形界面。

Image 本质上也是一个 Recipe,只不过它不编译软件,而是把多个软件包组合在一起。你可以在 Image Recipe 里指定要包含哪些包:

IMAGE_INSTALL = "packagegroup-core-boot \
                 packagegroup-base-extended \
                 openssh \
                 vim \
                 htop"

我个人的经验是:不要在一个 Image 里塞太多东西。保持 Image 的「最小可用」原则。需要什么功能,再加什么包。这样既能减少编译时间,也能降低镜像体积。

2.7 Package(包)—— 软件的分发形式

Package 是 Yocto 构建过程中的中间产物。每个 Recipe 编译完成后,会生成一个或多个 Package。比如 openssl 这个 Recipe,可能会生成:

  • openssl(主程序)
  • openssl-dev(头文件和静态库)
  • openssl-dbg(调试符号)
  • openssl-doc(文档)

这种拆分的好处是:你可以只安装你需要的部分。比如在嵌入式设备上,你只需要 openssl 主程序,不需要开发头文件。这样能节省大量空间。

避坑指南: 我曾经在制作镜像时,发现某个功能总是报错,提示缺少动态库。查了半天,原来是 openssl-dev 没有被包含进去。但 openssl-dev 里只有头文件,没有动态库啊?后来才意识到,那个动态库被分到了 openssl 包里,而 openssl 包默认被安装了。真正的问题是另一个包的依赖关系写错了。所以,遇到包相关的问题,先检查 PACKAGESFILES 变量的定义。

小结一下

这七个概念,就像 Yocto 世界的「七巧板」:

  • Recipe 是零件
  • Layer 是收纳盒
  • Metadata 是说明书
  • Machine 是工作台
  • Distro 是工艺标准
  • Image 是成品
  • Package 是半成品

搞懂了它们之间的关系,Yocto 就不再是一个黑盒子了。下一章,我会带你深入 BitBake 的解析流程,看看这些概念是如何被串联起来的。