嵌入式Linux系统开发(基于Yocto Project)

第1章 用于嵌入式系统的Linux

  • Yocto是一个提供构建定制化嵌入式Linux操作系统栈的项目,是把引导加载程序、内核、根文件系统(包括应用软件和库)构建成为一个可以使用的Linux系统镜像。
  • 主要框架为OpenEmbedded,提供元数据(包括配置文件、菜谱等),配置文件说明机器硬件情况、要制作的系统镜像需要哪些软件,菜谱说明如何对软件进行下载、配置、编译、安装、打包。OpenEmbedded在上层视角详细说明了硬件平台是什么样、定制化Linux操作系统栈需要哪些软件、如何把需要的软件放到系统镜像里。OpenEmbedded完成了一系列理论说明:我是谁、我想要什么、你怎么去做。
  • BitBake是一个可执行的二进制,负责按照OpenEmbedded提供的理论说明进行具体实现,实际去做任务。bitbake解析OpenEmbedded提供的元数据,自动完成制作镜像的任务。
  • 使用Yocto定制一个嵌入式Linux,其中BitBake作为构建引擎已经非常稳定和强大,因此定制工作几乎全部集中在元数据上,也就是编写和修改元数据(配置文件、菜谱等)。

第2章 Yocto项目

1 设置DL_DIR变量指向构建环境之外的目录路径(18页)

第一,DL_DIR变量告诉BitBake把源下载放在什么地方,默认就是构建目录下的downloads目录。
第二,如果BitBake检测到downloads目录下已经有可用的源下载了,那么就不会再下载了,大大节省时间。其实就是第一次构建的时候把所有源下载都下载,耗费很多时间,以后就直接用了。
第三,书上推荐设置DL_DIR变量指向构建环境之外的目录路径,就是将源下载目录置于构建环境之外。如果不再需要某一构建环境时,就可以只删除构建环境而保留了源下载,方便以后直接使用而无须重新下载,大大提高效率。

在构建目录下的conf目录的local.conf文件,有关于DL_DIR的设置是构建目录的上层目录的downloads目录。
DL_DIR ?= "${TOPDIR}/../downloads"

对于SSTATE_DIR也是这样,包含到共享状态缓存的路径,同样可以重用,因此也放在构建目录之外的路径。

2 BitBake先下载所有源而不构建(18页)

我觉得这个思路很好。默认的思路是下载、配置、编译、安装等混杂在一起,而且这个过程需要一直联网。如果先下载再构建,那就相当于把之前的一个大任务分解成两个步骤。
先做第一步,源下载,一般没有问题,除非遇到访问不稳定的源。我就遇到了这个问题,最后一个linux-firmware源就是下载不完,进度条走着走着,甚至到了90%多,突然断开连接,进度从0重新开始,反复几次,让人无法接受。
解决方法是,用git将源下载到本地,修改相应的菜谱文件。
当把源都下载完成了,那就是把原材料都准备好了,接下来就是配置、编译、安装了,都可以在本地操作,不再依赖联网。

3 遇到错误继续构建(19页)

一般情况,BitBake遇到了无法修改的错误,就会停止构建过程。而-k选项可以命令BitBake跳过错误,继续构建不依赖错误软件包的其他软件包。
我认为,错误早晚都要解决,早解决比晚解决好。即使使用了-k选项,又能继续构建多久?很有可能遇到依赖之前错误软件包的软件包,那又是一个错误,但由于加了-k选项,BitBake会继续选择其他的进行构建。
到最后,表面上可能全部构建完成,但是,背后隐藏了多少没有构建的错误,是未知的。这些错误,就是之前的自己给现在的自己挖的若干个坑。

4 应用开发工具(21页)

一开始没有明白,应用开发工具是在Linux主机上用的,还是在嵌入式设备的Linux操作系统栈上用的。其实,Yocto项目及其子项目,都是在Linux主机上用的。
在Linux主机上使用应用开发工具,开发在嵌入式设备上使用的用户空间应用程序。
应用开发工具为开发嵌入式设备的用户空间应用程序提供了完整的开发环境,这个开发环境包含交叉开发工具链、QEMU模拟器、Linux内核和根文件系统镜像,所有这些组件都运行在Linux开发主机上,用于开发和测试最终要在嵌入式设备上运行的程序。
这里的Linux内核和根文件系统镜像,都是Yocto提供的现成的,用于开发嵌入式应用的。与使用Yocto定制构建出来的Linux系统镜像,是不一样的。

5 AutoBuilder(21页)

一般来说,个人都是在自己的机器上配置环境,手动使用bitbake进行构建。在团队合作中,每个人的软硬件环境可能不同,从而引起不一致的情况。一样的操作或修改,经常出现在我这里是好的,在别人那里就报错,这应该就是软硬件环境不统一导致的。个人的机器各有特色,千差万别,会出现各种奇怪的问题。
AutoBuilder适合团队协作使用,实现Yocto构建的自动化、持续化、标准化。无须再手动配置环境、使用bitbake命令,24小时一直在线提供构建服务,软硬件环境、构建命令保持统一标准。
AutoBuilder主要组成部分:
(1)持续集成的服务器,书上写的是Buildbot,即构建机器人。之前项目开发用了Jenkins,当在Gerrit平台提交patch时,会触发Jenkins,自动开始编译、测试,返回成功或报错信息。
(2)专门用于构建的服务器,这是真正干活的地方。
(3)一系列自动化脚本,包括环境配置、构建、测试、部署等环节。
自动化提升了效率,统一构建标准、测试标准提升了质量。

6 交叉预先链接(21页)

当运行依赖共享库的程序时,需要链接共享库。
(1)在构建阶段进行预先链接,就是将依赖的所有库加载到一段地址空间,各自有各自的地址。然后计算应用程序中的符号和变量的绝对内存地址(库的基地址+符号/变量的偏移地址),用绝对内存地址替换应用程序二进制中的相对地址。
(2)等到了运行阶段,将库加载到在构建阶段计划好的地址,理想情况下直接100%地址匹配正确,即完成链接。如果某一后来加载的库的计划地址被占用,那么会发生轻微重定位,也就是在应用程序二进制的依赖该库的地址加上偏移量(新的实际的库的基地址 - 旧的计划的库的基地址),这比标准的动态链接的重定位要快几个数量级。

7 Yocto与BitBake

Yocto项目中,元数据应该是属于OpenEmbedded,只有BitBake二进制而没有相关源码。BitBake项目有自己独立维护的python源码,可以生成BitBake二进制,而Yocto项目只是包含使用已有的BitBake二进制。

8 Yocto与Poky

Yocto是一个开源协作项目,一个伞形组织。它制定标准、提供协作框架、整合资源、推动生态发展。它本身不直接构建系统。
Poky是Yocto项目官方维护的参考实现,它是Yocto项目理念的具体化身和“示范田”。
Yocto提供理论指导,Poky提供实践参考,在Poky的基础上进行修改或添加元数据,实现定制化构建。

9 Poky与Poky Linux

  • Poky是Yocto的参考发行版,相当于参考的标准答案,基于Yocto标准规范的一个基准实现和官方示范,具体包括菜谱是怎么写等内容,用于参考入门。
    当不确定一个Recipe该怎么写、一个Layer该如何组织、一个配置该如何设置时,就去看Poky是怎么做的,它定义了Yocto生态中的“最佳实践”。任何新的特性或功能,首先要在Poky这个“标准环境”中进行测试和验证,如果它在Poky中能正常工作,就认为它符合Yocto标准。Poky里meta/recipes-core/busybox下的busybox.bb等基础菜谱,就是编写Recipe的范本。
    Poky是用作学习,一般会在此基础上做修改和增加,定制构建自己的Linux操作系统栈。
  • 当使用Poky这个“工厂”,不进行任何定制,直接按照它的默认菜谱去构建时,它会产生一个具体的、可以运行的Linux系统,是一个完整的、可启动的Linux操作系统栈。这个系统就叫“Poky Linux发行版”。
    Poky Linux包含Bootloader,Linux 内核,根文件系统,一套完整的、相互兼容的软件堆栈。
    在这个层面,Poky Linux是Poky“工厂”按照标准流程生产出来的第一款“样板车”,是一个具体的、可以烧录到设备上运行的嵌入式Linux操作系统栈。

10 Yocto项目术语

  • 交叉开发工具链
  • 上游,包管理系统,包
  • Poky
  • OpenEmbedded核心,BitBake
  • BitBake术语:层,元数据,配置文件,类,菜谱,追加文件
  • 板支持包
  • 任务
  • 镜像

11 Yocto项目系列中的最重要的成员:OpenEmbedded构建系统

OpenEmbedded构建系统包括:

  • BitBake(构建引擎)
  • OpenEmbedded核心(核心的元数据集合,包括BitBake层、类、集成和实用脚本)
  • Poky(Yocto的参考发行版)

12 BitBake 项目,OpenEmbedded项目,OpenEmbedded核心,OpenEmbedded构建系统,Yocto项目

  • BitBake 项目
    这是一个独立的开源项目,它专门负责维护 BitBake 工具本身。
    核心职责:开发和维护 BitBake 这个任务执行引擎。它关注的是如何解析元数据、管理任务依赖关系、并行执行任务、提供签名机制等核心功能。
    与其他人关系:它是 OpenEmbedded 构建系统 的心脏;它被 OpenEmbedded 项目 和 Yocto 项目 共同依赖和使用;它有自己的发布周期和源代码仓库。
    关键点:BitBake 是一个通用工具,它本身并不局限于构建嵌入式Linux。BitBake与OpenEmbedded 元数据组合,成为了一个强大的Linux构建系统。
  • OpenEmbedded 项目
    这是一个历史悠久的开源社区项目。
    核心贡献:它孕育和维护了构建嵌入式Linux系统的两大基石:BitBake:任务执行引擎;OpenEmbedded Core:核心元数据集。
    定位:专注于提供构建嵌入式Linux的核心工具和基础材料。它更像一个“技术研发中心”。
  • OpenEmbedded 核心
    这是由 OpenEmbedded 项目维护的核心元数据集合,它是任何基于OE的构建系统都离不开的基础。
    内容:包含构建一个基本嵌入式Linux系统所必需的菜谱、配置文件和类。
    角色:它是“基础菜谱库”,定义了如何编译 bash、glibc、busybox 等成百上千个基础软件包。
    关键点:它不包含针对特定显卡、复杂桌面环境或非常专门的硬件的菜谱。
  • OpenEmbedded 构建系统
    这是一个通用术语,指任何一个基于 OpenEmbedded Core 和 BitBake 的、可以工作的完整构建环境。
    构成:任何 BitBake + OE-Core + 自定义/第三方层 的组合,都可以被称为一个“OpenEmbedded构建系统”。
    例子:(1)Poky是Yocto项目提供的、最著名的一个OpenEmbedded构建系统。(2)在Yocto项目之前,其他公司或个人搭建的基于OE的构建环境,也都是OpenEmbedded构建系统。
    定位:它是一个具体的、可运行的实体,是开发者直接与之交互的“厨房”。
  • Yocto 项目
    这是一个由Linux基金会托管的下游项目,也是一个开源协作项目。它旨在为嵌入式Linux开发提供完整的解决方案。
    核心目标:提供模板、工具和方法来创建定制的Linux系统,无论硬件架构如何。
    与OpenEmbedded项目的关系:不是竞争,而是协作与继承。Yocto项目不重复发明轮子,它直接采用并依赖由OpenEmbedded项目维护的 BitBake 和 OE-Core。它在这些强大的核心组件之上,构建了一个更完整、更易用、更适合工业化生产的框架和生态系统。
    提供的额外价值:(1)Poky:一个稳定、集成、开箱即用的参考构建系统。(2)全面的文档。(3)测试框架。(4)IDE 插件。(5)硬件兼容性项目,推动芯片厂商提供高质量的BSP层。
  • BitBake项目专注引擎, OpenEmbedded项目专注基础规范,Yocto项目专注整合与用户体验。OpenEmbedded 项目 是这一切的基石和源头,而 Yocto 项目 是使其变得普及和易用的成功典范。

第3章 OpenEmbedded构建系统

1 定制化构建一个Linux操作系统栈,包括软件hello。

(1)经过获取、解压、打补丁、配置、编译链接、安装之后,还需要打包吗,怎么打包?用opkg打包?
(2)最后生成的Linux系统镜像包括引导加载程序、内核、根文件系统,如何把软件hello放到最后的Linux系统镜像里,hello是在根文件系统这部分?
(3)用Yocto将glibc放到了最后的定制化Linux系统栈里,生成镜像,安装镜像之后进入系统操作,使用opkg安装了gcc。有哪些软件需要提前定制化构建到Linux操作系统栈里?有哪些软件是进入系统操作后用包管理器opkg安装的?


这些问题涉及Yocto工作流程:构建时与运行时、系统定制与软件管理的界限

1. 软件打包与OPKG

问:软件hello还需要打包吗?用opkg打包?

答:是的,需要打包,并且默认就是使用opkg格式。

在Yocto中,打包是自动完成的,你不需要手动操作。这个过程是BitBake任务链的最后一环:

  1. do_install:你的食谱将文件安装到 ${D} 目录(目标目录)。这相当于“ staged ”安装,还不是最终的文件系统。
  2. do_package:BitBake自动运行。它会分析 {D} 目录中的内容,根据文件类型(可执行文件、库、配置文件、文档等)自动拆分到不同的子包中(如 hello, hello-dev, hello-dbg),并生成包依赖关系。
  3. do_package_write_ipk:这个任务将上一步准备好的文件实际打包成 .ipk 文件(opkg使用的包格式)。

所以,你的工作只是在 do_install 中正确“安装”文件,Yocto会自动为你完成打包成opkg包的过程。

2. 软件如何集成到最终镜像中

问:如何把软件hello放到最后的linux系统镜像里?是在根文件系统这部分吗?

答:完全正确。Hello软件位于根文件系统中。

最终镜像的构成和集成方式如下:

flowchart TD A[你的Recipe<br>hello.bb] --> B[执行任务链<br>fetch, configure, compile] B --> C["do_install<br>将文件安装到构建目录"] C --> D[自动打包<br>生成hello_x.y-rz.armv7ahf-vfp-neon.ipk] E[你的镜像Recipe<br>my-image.bb] --> F["IMAGE_INSTALL +=<br>&quot;hello&quot;"] D --> G[根文件系统<br>构建过程] F --> G subgraph H[最终系统镜像] H1[引导加载程序] H2[Linux内核] H3[根文件系统<br>内含你的hello软件] end G --> H3

具体流程:

  1. 在你的镜像配方文件(如 my-image.bb)中,通过 IMAGE_INSTALL 变量指定要包含的软件包。
    IMAGE_INSTALL += "hello"
    
  2. 当你构建这个镜像(bitbake my-image)时,BitBake在构建根文件系统的阶段,会解析 IMAGE_INSTALL 中的所有依赖。
  3. 构建系统会找到你已构建好的 hello 包(那个 .ipk 文件),然后将其内容解压并合并到正在构建的根文件系统目录树中
  4. 最后,这个包含了 hello 的完整目录树会被打包成根文件系统镜像(如 .ext4.wic 文件),并与内核、引导程序一起组成最终的可烧录镜像。

3. 构建时集成 vs 运行时安装的决策

问:哪些软件需要提前定制化构建到Linux操作系统栈里?哪些用opkg安装?

这是嵌入式Linux系统设计的一个关键决策。其决策逻辑和考量因素如下图所示:

flowchart LR A[软件集成决策] --> B{系统能否运行<br>依赖于该软件?} B -- 是 --> C[构建时集成] B -- 否 --> D{是否需要严格的<br>版本控制与一致性?} D -- 是 --> C D -- 否 --> E{软件是否很大<br>或很少使用?} E -- 是 --> F[运行时安装] E -- 否 --> G{是否需要极高的<br>启动速度?} G -- 是 --> C G -- 否 --> F subgraph H[构建时集成] H1[“<b>优点</b><br>一致性、可靠性、快速启动”] H2[“<b>缺点</b><br>镜像大、更新需全量”] end subgraph I[运行时安装] I1[“<b>优点</b><br>灵活性、节省空间、易更新”] I2[“<b>缺点</b><br>依赖网络、有版本冲突风险”] end C --> H F --> I

必须/强烈建议在构建时集成的软件:

  • 系统启动和运行的基础glibcinit 系统(systemdsysvinit)、内核模块(尤其是启动关键硬件所需的)。
  • 核心系统工具busyboxcoreutilssyslogdnetwork 配置工具。
  • 你的核心应用:产品主要功能的二进制文件和库。比如你为智能家居设备编写的控制程序。
  • 关键依赖库:如果你的应用依赖 qt,那么Qt库应该集成在镜像中,而不是运行时安装。
  • 设备启动后必须立即运行的服务

适合在运行时通过opkg安装的软件:

  • 调试和诊断工具gcc(正如你的例子!)、gdbstracevim。这些工具在开发阶段有用,但不应出现在最终量产镜像中。
  • 可选的或用户级的应用:比如一个可选的文本编辑器、媒体播放器。
  • 需要频繁更新/测试的组件:在开发迭代期间,可以先将应用打包成ipk,通过opkg安装测试,待稳定后再集成到镜像中。
  • 体积巨大但不常用的软件

针对你的例子分析:

  • glibc必须在构建时集成。没有它,系统根本无法启动。
  • gcc完美的运行时安装候选者。它是一个庞大的开发工具,绝大多数嵌入式产品在运行时不需要它。你在系统启动后通过opkg安装它是非常正确的做法。

最佳实践:
量产镜像应该只包含系统运行和核心功能所必需的最少软件集合(遵循最小权限原则,提高安全性和稳定性)。所有非必需的、用于维护或可选的软件,都应该通过运行时包管理器来安装。

2 赋值符号:= / := / ?= / ??=

正确的优先级顺序

从高到低的正确优先级是:

  1. =:= (同级,后出现的覆盖先出现的)
  2. ?=
  3. ??=

验证和解释

通过实验来验证这个顺序:

实验 1: = vs := (同级竞争)

# 情况 A: = 在后
VAR := "immediate-assign"
VAR = "immediate-expand"
# 结果: VAR = "immediate-expand" (= 胜出)

# 情况 B: := 在后  
VAR = "immediate-expand"
VAR := "immediate-assign"
# 结果: VAR = "immediate-assign" (:= 胜出)

结论=:=同级的,后出现的会覆盖先出现的。

实验 2: = 覆盖 ?=

VAR ?= "weak-default"
VAR = "force-value"
# 结果: VAR = "force-value" (= 覆盖了 ?=)

实验 3: ?= 不覆盖 =

VAR = "force-value"
VAR ?= "weak-default"
# 结果: VAR = "force-value" (?= 无法覆盖 =)

实验 4: ??= 优先级最低

VAR ?= "weak-default"
VAR ??= "weakest-default"
# 结果: VAR = "weak-default" (??= 被 ?= 覆盖)

VAR = "force-value"
VAR ??= "weakest-default"  
# 结果: VAR = "force-value" (??= 被 = 覆盖)

详细优先级规则

第一优先级:强制赋值 (=, :=)

  • 这两个操作符都会强制设置变量的值
  • 它们之间的胜负取决于在解析文件中出现的顺序,后出现的胜出
  • 它们都会覆盖低优先级的 ?=??=

第二优先级:条件默认值 (?=)

  • 只在变量当前没有值时设置
  • 会被 =:= 覆盖
  • 会覆盖 ??=

第三优先级:最终后备值 (??=)

  • 所有解析完成后,如果变量仍然没有值才设置
  • 优先级最低,会被所有其他操作符覆盖

实际应用示例

# 在 meta-layer1/conf/layer.conf 中:
MY_VAR ??= "layer1-fallback"

# 在 meta-layer2/conf/layer.conf 中:
MY_VAR ?= "layer2-default"

# 在 build/conf/local.conf 中:
MY_VAR = "user-setting"

# 最终结果: MY_VAR = "user-setting"
# 解析过程:
# 1. ??= 设置 "layer1-fallback" (临时)
# 2. ?= 覆盖为 "layer2-default"  
# 3. = 最终覆盖为 "user-setting"

总结记忆口诀

  • = / := - "我说了算"(强制设置,同级竞争)
  • ?= - "如果你还空着,我先占个位"(条件默认)
  • ??= - "如果大家都不要,最后给我"(最终后备)

正确的优先级从高到低是:

= / :=  >  ?=  >  ??=

:= 的优先级与 = 是同级的,区别在于求值方式(立即展开 vs 延迟展开),而不是优先级。

特性 := (立即展开) = (延迟展开)
求值时机 赋值时 使用时
值稳定性 固定不变 随依赖变量变化
性能 一次性计算 每次使用都可能重新计算
典型用途 时间戳、路径计算、固定配置 递归配置、可覆盖默认值、模板

3 追加符号:+= / .= / =.

+=

加个空格再追加,解析时立即求值并追加(最常用)

.=

不带空格,直接粘上去,解析时立即求值并追加(用于路径)

=.

不带空格,等等再看怎么粘,延迟展开追加,使用时才求值(用于动态引用)

4 OpenEmbedded构建系统架构包含3个基础组件:构建系统、构建环境、元数据层

  • 构建系统:包括bitbake目录、元数据层目录、脚本等
  • 构建环境:一般通过构建系统中的oe-init-build-env初始化而来,默认初始状态包含两个重要配置文件,conf/local.conf和conf/bblayers.conf
  • 元数据层:包括conf/layer.conf(该层配置文件),classes,recipes-abc/xyz.bb

4 BitBake Build Engine

4.4 Metadata Syntax

4.4.2 Variables

Appending (+=) and Prepending (=+) with Space
最后的VAR3的值好像错了,应该是67 89 5 12 34
Appending (.=) and Prepending (=.) without SpaceAppending and Prepending Using the _append and _prepend Operators,VAR3都是类似错误。

Conditional Variable Setting 条件覆盖机制

OVERRIDES = “sun:rain:snow”
PROTECTION = “unknown”
PROTECTION_sun = “lotion”
PROTECTION是一个独立变量,OVERRIDES是另一个独立变量,定义条件列表。
二者本来没有关系,但是在PROTECTION后面加上_sun,PROTECTION的值就和OVERRIDES有关系了。
按照优先级从高到低扫描OVERRIDES,即从右到左扫描OVERRIDES,依次是snow、rain、sun,发现sun在OVERRIDES中存在,因此PROTECTION的值由unknown变为lotion。

OVERRIDES = “sun:rain:snow”
PROTECTION_rain = “umbrella”
PROTECTION_snow = “sweater”
按照优先级从高到低扫描OVERRIDES,即从右到左扫描OVERRIDES,依次是snow、rain、sun。
PROTECTION_rain发现rain在OVERRIDES中存在,因此PROTECTION的值为umbrella。
PROTECTION_snow发现snow在OVERRIDES中存在,且优先级最高,比rain高,因此PROTECTION的值为sweater。

OVERRIDES = “sun:rain:snow”
PROTECTION_rain = “umbrella”
PROTECTION_hail = “duck”
按照优先级从高到低扫描OVERRIDES,即从右到左扫描OVERRIDES,依次是snow、rain、sun。
PROTECTION_rain发现rain在OVERRIDES中存在,因此PROTECTION的值为umbrella。
PROTECTION_hail发现rain在OVERRIDES中不存在,因此PROTECTION的值不会改为duck,仍然为umbrella。

4.4.5 Executable Metadata

Tasks
Shell函数、Python函数、全局Python函数、匿名Python函数、task,等等。
定义了一些Shell、Python函数,如何让BitBake执行?需要用到task。
这里提到的task,是不是就是执行bitbake命令时打印日志里面的task?是的。BitBake在解析recipe之后,就会执行其中的匿名函数,也就是下载源码、解压、编译之类的功能函数。而这一类特殊的函数,就会被BitBake识别为task。
task具有两个特性:可以直接被BitBake命令行调用;作为构建过程的一部分,可以被BitBake自动执行。

Listing 4-10中展示了部分task。定义一个函数,一般函数名就是do_xxx,都以do_为前缀,后面跟着要做的事情。在函数定义后面,会紧跟着addtask xxx之类的,表示增加该任务进入任务队列。
addtask关键字也提供了插队的机制,也就是自定义该任务的位置,比如addtask unpack before do_build after do_download,其实任务的顺序也确实很明了,下载、解压、构建。
不过有一个奇怪的地方,为什么不直接按顺序将各个任务从上到下写好?在函数定义后面就可以直接只用addtask xxx,而无须额外单独考虑该任务应该位于什么位置,也不用加before/after之类的限定位置。
我猜测这里只是为了展示有before/after这种用法,偶尔可以用用,但实际情况中使用应该不多。
按照Listing 4-10给定的顺序,各任务依次为:clean、download、uppack、compile、build、install。这里有个问题,compilebuild有什么不一样?编译和构建有什么区别?两个词好像是混用的,而且前文中只是用了build,没有出现compile
在普通的Shell或Python函数名前面加上do_前缀,将其定义为任务,除了命名上的特定区别,任务和普通函数几乎一样。 addtask将任务加入BitBake将要执行的任务列表,配合before/after创建出一个任务执行链条。
假设4-10的代码在名为myrecipe.bb的recipe中,则可以使用BitBake执行:bitbake myrecipe -c clean,共分为4部分,第一指定bitbake命令,第二指定recipe名,第三参数选项-c,第四指定要执行的任务是什么。

Accessing BitBake Variables from Functions
Listing 4-11中,首先定义了一个BitBake变量BPN,后面有个 do_install任务,其内容是Shell脚本,在Shell脚本中可以直接对变量进行读写,因此在第一行就直接修改了变量BPN,修改后的值仅在本函数作用域内部有效。
在Shell函数中可以很方便地对BitBake变量直接进行读写,但在Python函数中不可以直接读写,需要使用函数在BitBake数据词典中检索,然后操纵BitBake变量。
Listing 4-12展示了Python函数对BitBake变量的读写。首先定义变量HELLO,并赋值,定义变量DATE,初始为空。
在函数printhello中,想要打印hello值,则需要先获取该值,在数据词典d中,使用检索变量的函数getVar,即d.getVar('HELLO', True)的形式,获取到BitBake变量HELLO的值,再将其赋值给函数内部变量hello
在函数setdate中,要设置BitBake变量DATE的值,先获取当前时间并赋值给函数内部变量date,再通过数据词典d和数据操纵函数setVar,即d.setVar('DATE', date)的形式,将存储当前时间的变量date的值传递给BitBake变量DATE
数据词典用全局Python变量d表示,作为一个Python类的形式来实现,可以调用类里面的函数,包括getVarsetVar

Creating a Local Data Dictionary Copy
BitBake数据词典使用COWCopy On Write,当实际的写操作来临时,才会复制一份变量出来,在此复制变量上做写入。可是这样就造成了两份数据的同一变量的数据不一致。而且,这里提到的写操作,是通过setVar函数的吗?
setVar对全局数据词典操作,修改全局变量,对后续所有操作都会有影响。setVar函数会直接原地修改数据词典吗?如果是原地修改数据词典,那么后续访问直接访问到数据词典本身,自然就会使用修改的新值。
如果不需要对全局的变量都进行修改,那就可以调用数据词典的拷贝函数createCopy,拷贝一份数据词典到本地,应该就是一份实际的深拷贝,而不是指针。
修改BitBake数据词典的变量时,会产生该变量的实际拷贝,那会产生很多个拷贝?使用setVar会把全部的原始变量和拷贝变量都进行修改。如果不需要修改全部的变量,那就使用createCopy函数拷贝一份数据词典到本地,这时该用什么函数做写操作?这时做写操作,只是改变本地数据词典,而不会影响全局的所有变量。

Accessing Variables Containing Value Lists
有些BitBake变量存储值的列表,这些值通过分隔符分开。在Python函数中,同样可以使用d.getVar检索数据词典,获取BitBake变量,赋值给Python的数组。在d.getVar后面加上or "",是为了应对在数据词典中找不到这一情况。找到BitBake变量后,再使用split函数,以空格为分隔符对其进行分隔,并赋值给Python数组uris

Appending and Prepending Functions
一般在同一文件中,不会用到appendprepend
使用场景主要有两个:(1)recipe继承自class,想要向class定义的函数增加内容。(2)append文件想要扩展它附加的recipe中的函数。
Shell和Python函数,都可以使用appendprepend

4.4.6 Metadata Attributes

BitBake元数据(包括变量和函数)都可以拥有attribute,也称为flag。使用BitBake语法进行初始设置比较简单,比如func[run]=1,就是将函数funcrun``attribute的值设置为1。不知道后续能不能多次用同样简单的方式设置。无法使用BitBake语法进行读。
在Python函数中,使用BitBake数据词典的方法getVarFlagsetVarFlag,可以对attribute的值进行读和写。

4.4.7 Metadata Name (Key) Expansion

元数据的名称也可以展开,在解析完最后的数据之后,才进行展开。

A${B} = "foo"
B = "2"
A2 = "bar"

第1行,得到A${B} = "foo"
第2行,得到B = "2"
第3行,得到A2 = "bar"
至此全部解析完,开始展开元数据的名称,得到A2 = "foo"

4.5 Source Download

Linux操作系统栈包含数百个软件包,构建系统需要去找到这些软件包的源代码,也被称为upstream repositories。源代码的地址可能在本地、远程服务器、版本管理系统等,之前linux-firmware软件包默认从远程服务器下载,但一直无法正常下载,只好用git下载到本地,并在元数据中将source地址改为本地地址。一般下载的软件包都是压缩打包的形式,需要解压。
构建系统为用户提供一致性和透明性的访问仓库的方式,应该就是在recipe中指定仓库的地址以及相关信息,用户无须了解仓库的具体细节以及协议实现。什么协议?应该是传输协议,就是从仓库地址下载到本地的协议。仓库可能在远程文件服务器(比如ftp),可能在版本控制平台(比如git)。
BitBake提供了fetch的框架,相关接口路径在sources/poky/bitbake/lib/bb/fetch2,源地址的格式为scheme://user:pwd@host:port/path; key=value;

4.5.1 Using the Fetch Class

4.5.2

posted @ 2025-11-14 18:05  三七373737  阅读(23)  评论(0)    收藏  举报