Overview
本文是学习Yocto的学习笔记,但是Yocto是一个很大的概念,本文侧重于Yocto工作机制的学习,与Bitbake最小系统的搭建不同,这篇文章的讲解将会更全面,而不仅仅是为了搭建一个项目出来。是为了更加了解工作的机制。
涉及到autotools和bitbake的内容有单独的笔记。尤其是bitbake的配方语法部分,不在这个笔记中,在bitbake的笔记里面。但是如果是一些bitbake的机制问题,还是在这个文档里面。
本文编写时在开发Qualcomm的项目,因此很多都和标准poky不同,比如local.conf文件的模板位置,正常是poky/meta-poky/conf/templates/default/local.conf.sample
但是高通有自己的模板 apps_proc/poky/qti-conf/local.conf 。这些都不影响对yocto的理解。
需要知道一些yocto机制的原理就是在本文查找,如果需要这些原理的具体到配方中如何使用并且写出来一个实际的案例就在bitbake文档中查找。
参考文档:
https://www.yoctoproject.org/
对于这个网站来说,包含yocto和bitbake的,这是两种东西,要先明白自己搜索的东西是yocto的内容还是bitbake内容。
yocto架构
yocto解释
yocto是一套构建嵌入式的方法和工具。
poky yocto官方提供的标准工程。其他非标准工程有 适用于服务器的openBMC、汽车的AGL、精简系统Poky-tiny等等。
Yocto 是整体框架,OE 是底层构建逻辑,BitBake 是执行构建的工具,三者协作完成嵌入式 Linux 镜像的定制化构建。
以盖房子做比,yocto就是一套盖房子体系,包括规范、工具、施工方法等;bitbake是施工队长,决定顺序,比如先打地基;oe-core,是基础材料和施工图纸;poky就是官方盖出来的样板房。
Poky 是 Yocto 的参考套件,内置了 BitBake/OE-Core,是新手入门的基础
BitBake 和 OE-Core(OpenEmbedded 的核心子集)是 Poky 的核心组成部分,但 Poky 并不包含完整的 OpenEmbedded (OE) 项目。
OE-Core:OpenEmbedded 项目的核心子集(而非完整 OE),放在 Poky 的 meta/ 目录下,包含所有基础的元数据(如 base.bbclass、核心软件的 recipe 等);
oe的扩展都放在meta-openembedded(正常的yocto也都会是完整的oe)
meta-poky 负责配方镜像的核心组件层,但是对于一个项目来说,这个文件夹只是一个参考,很多时候没用,比如Qualcomm真正的核心镜像是放在meta-qti-bsp。
oe-init-build-env 编译脚本
qti-conf配置文件工具
参考配置层:meta-poky(这个也是poky,真正的poky包含了 bitbake + oe核心层(meta)+ meta-poky +meta-yocto-bsp(yocto维护) + 工具/脚本)
yocto架构
从产品定义角度上分析,影响产物的是revision、machine、distro、target image。
但是就从对产物的影响来说,还是有很多指标。包括但是不限于架构(32/64)、组件版本、文件系统格式、包格式、layer组合、local.conf增量定制等等
客户代码管理
对于实际落地项目来说,yocto基础上往往要加上客户的私有修改管理。
最初,我是考虑revision中加入客户基线,进行维护。但是有个问题,revision只是时间和版本的变换,没有涉及到空间的变换。
目前公司使用的客户修改单独存放,每次编译copy客户修改到主基线开发。这样有弊端,首先就是修改不明显,因为是文件copy,所有根本看不出来diff。然后就是客户多了以后,极其难管理。
我认为标准的流程,应该遵循yocto架构,将客户的私有代码新建layers存放。
如果涉及到源代码,小体量存放到layers下,大体量就src下单独建仓库。
因为考虑到客户修改,大多是一些驱动适配、设备树修改。驱动修改完全可以提交到主干分支,设备树完全可以引用设备树追加。
如果是公司开发的代码,可以新建公司layer存放。至于大体量代码完全可以直接存放src。
这样看不同客户的修改,观看的是修改的patch,容易比对差异。
但是这样有个问题还是没有解决,就是基线修改,可能会影响客户的patch。并且是无法知晓影响到哪些客户私有定制化patch。客户的修改一般不会有大体量,一般都是patch的方式。
产品架构
machine不是按照soc型号区分的,同一个soc也常常有多个machine。按"构建产物层面的硬件形态"分的。
同一个 SoC,开新 machine 的常见理由:

文件解释
主要是为了解释各种的文件作用目的。
tmp
有很多命名方式,但都是一个意思,存放的是一些运行过程文件和结果文件,包含了cooker 各种日志等等,查bug和系统运行状态都离不开这个文件,它是在build目录下面的。
cooker
这个文件里面存放着日志文件,日志文件中写明了构建使用了什么设置,调试过程等。
这个cooker文件夹下存放了控制台的日志,是所有的控制台的日志,每次编译的控制台日志都会在这里备份,按日期备份。
recipes下的temp
存放各种的任务日志文件。这个文件是{T}指出的,在每个配方的文件夹里面,存放的log和run开头的各种任务文件。
build
bitbake.lock
这个文件位置在build目录下,这个文件就是一个锁文件,是为了保证终端只会进行一个构建。有的时候项目不能正常退出,导致了锁文件锁死了整个项目,表现为:当前还有没有完成构建的项目。这时,删除锁文件即可。
buildhistory
这个文件是否生成是conf/local.conf指定的
INHERIT += "buildhistory"
BUILDHISTORY_COMMIT = "1"
buildhistory/images下面有一个文件是放log的,里面有一个image-info.txt 这里面有关于项目的基本信息,里面需要注意的是指明了项目到底编译了哪些群组。(这个就是实际上编译的东西,而不是用脚本把所有群组,不管是否编译的,名称都拿出来)
比如说build-id.txt文件,就是把所有的群组,用不用的上的都列出来。
其他文件都是很有价值的。
但是需要注意的是,这个文件是在整编过后,最后才生成的。
depends.dot文件:这个里面放的是所有构建的信息,包括了群组里面有什么配方,每一个配方依赖什么配方都会在这个里面能查询到。而且,这个文件放的这些依赖关系都是实际运行的依赖关系。
这个里面还会列出来依赖的库(但是不会全部列出来,我添加的第三方的库就没有显示出来,这个被列出的库应该是一种包的形式)

还有这些去掉kernel libc 等等的列表文件。
build还有一个比较git历史的脚本文件,poky/scripts/buildhistory-diff
使用方法:
~/poky/poky/scripts/buildhistory-diff . HEAD^
不要去使用,因为这个需要安装一个python3.6版本的工具,python3.5版本装上工具是没用的。
yocto文件
poky/meta-poky/conf/distro/poky.conf 这个文件里面能够看到yocto的版本信息。
yocto工作机制
查看yocto版本
yocto/poky/meta-poky/conf/distro/poky.conf
DISTRO_VERSION = "4.0.17"
DISTRO_CODENAME = "kirkstone"
对应关系可以通过官网查询
Releases - The Yocto Project
recipes工作目录
每一个配方都会有一个工作目录。
在工作目录中都会有两个sysroot目录。
一个用于目标构建recipe-sysroot 一个用于构建主机的本地文件recipe-sysroot-native 。
如果发现里面的文件有缺失,只能是通过do_install任务来进行填充。
编译核心数
poky/qti-conf/local.conf
BB_NUMBER_THREADS ?= "${@oe.utils.less_or_equal('CPU_COUNT', '20', '${CPU_COUNT}', '20', d)}"
#
# The second option controls how many processes make should run in parallel when
# running compile tasks:
#
PARALLEL_MAKE ?= "${@oe.utils.less_or_equal('CPU_COUNT', '20', '-j ${CPU_COUNT}', '-j 20', d)}"
BB_NUMBER_THREADS 指定同时运行的编译线程数,这个指的是同时跑的task数量,这是配方级别的。可以是一个配方下多个task同时跑或者是不同配方下的task,
PARALLEL_MAKE 控制make工具在编译过程中使用并行任务数,这个控制的是代码编译 -j 设置的线程数量
(这里的cpu核心数量指的就是字面意义上的核心数量,一个实际cpu一般都会有多个核心)
如果核心数小于等于20,则等于核心数,否则就是20.
所以最大就是20个任务数量。
有的yocto版本是检测电脑核心数量,也可以通过这样的方式查看(这个是获取逻辑核心数量也就是线程数量,不是物理核心数量,所以如果这么设置,电脑会被吃满)
BB_NUMBER_THREADS ?= "${@oe.utils.cpu_count()}"
bitbake -e | grep ^BB_NUMBER_THREADS=
features
绕不开一个很关键的参数:DISTRO_FEATURES
distro features和 machine features和COMBINED_FEATURES等等有很多features参数
distro features这个参数是独立于kernel配置的,所以这个参数依赖的特性也依赖kernel,也需要去kernel中检查参数配置。
但是只使用DISTRO_FEATURES参数是没有办法启用包的,有些时候还要使用PACKAGECONFIG参数来启用包。
DISTRO_FEATURES: 这个变量定义了要包含在发行版中的功能列表。你可以根据需要添加或删除不同的功能
MACHINE_FEATURES: 这个变量定义了特定硬件平台上的功能列表。你可以根据目标硬件平台的特性添加或删除功能。
MACHINE_FEATURES 声明平台具备的硬件能力,比如wifi bt
DISTRO_FEATURES 声明发行版本具备的软件能力 比如wayland wifi
但是绝大多数在决定编不编译一个软件包,直接检查的是DISTRO_FEATURES ,和MACHINE_FEATURES没有关系,只要前者制定了,后者有没有,都会参与编译打包。
具体参考backfill机制。
COMBINED_FEATURES是自动生成的,为了就是反应上面两个feature交集的地方,不建议手动改,风险很高。
配方检测feature参数
具体是怎么去检测这个参数的?
poky/meta/classes/features_check.bbclass配方中的python函数来实现的。这个python函数会在里面检测DISTRO_FEATURES、COMBINED_FEATURES等等很多features参数。
bb.warn(" feature value'%s'" % distro_features)
这个bbclass是很重要的,因为在编译的时候,无论是什么的配方都要去运行这个class,这个class就是负责检测features value的,每一个配方都有可能使用通配符的方式来利用features来决定一些包之类的是否要加入,所以每一个配方都会在运行之前先运行一次这个class。
想要在这个class中打印log可以使用bb.warn函数,因为这个是写在python函数中,一般的print是打印不出来东西的,(猜测是设置了log等级)
同理每次运行bitbake命令应该都会检测环境,就会检测feature的值,所以也可以通过
bitbake -e | grep ^DISTRO_FEATURES=
来拿到feature的值。
移除功能
可以通过修改这两个feature值来移除某些功能。
比如移除wifi功能
这个一般加在local.conf文件中,然后构建环境生效即可。
DISTRO_FEATURES:remove = "wifi"
backfill
apps_proc/poky/meta/conf/bitbake.conf中会定义
DISTRO_FEATURES_BACKFILL
MACHINE_FEATURES_BACKFILL
首先backfill中的参数都是默认参数,默认加入对应的feature,除非专门设置了backfill排除项。
distro定义了一个值,但是machine没有定义,这个backfill就没有任何意义。
machine定义了一个值,但是distro没有定义,如果这个值在DISTRO_FEATURES_BACKFILL 中有,就会自动补充到distro中。
COMBINED_FEATURES 是distro和machine取交集的结果,自动生成的,一般不手动改动。
配方
poky/meta/classes这个里面的所有的bbclass文件是不是被所有的配方继承
每一个运行的配方都会去运行这里面的一些bbclass
download目录链接
apps_proc/poky/meta/classes/own-mirrors.bbclass
这个文件里面就是替换哪些需要拉取的文件,把路径替换成本地downloads的路径
SOURCE_MIRROR_URL这个就是download的路径地址
poky/meta
class下有很多bbclass文件。
官方描述:Class files are used to abstract common functionality and share it amongst multiple recipe
所以很多配方都回运行这里的文件任务。
download
一般是通过DL_DIR参数来确定的
在这个下面的git2文件夹中存放远程拉下来的仓库备份,一般都是以镜像仓库的方式存放,并且一式两份,其中一个名字多后缀.done表示这个仓库已经下载完成的意思,是标志文件。
如果后续拉取代码仓库,发现这个done表示已经下载,就不需要从网络上再下载。
variables
这里只是给出一些yocto项目中常用的变量。
BP
基础配方的名字和版本 ${BPN}-${PV}
S
S = "${WORKDIR}/${BP}"
B
和S目录是一样的。
T
里面放的全是配方的日志文件。
T="......../tmp-glibc/work/aarch64-oe-linux/init-audio/git-r5/temp"
CONFLICT_DISTRO_FEATURES
只有在继承了features_check class,才会在解析时,CONFLICT_DISTRO_FEATURES列出的变量列表如果DISTRO_FEATURES也有,那就会跳过这个变量对应的配方。如果强行编译这个配方,会给出错误。
PROVIDES
这个参数就是给配方起一个别名,所起的别名和配方本身的名字具有同等的效力。
==为什么需要起别名?==
PREFERRED_PROVIDER
在 Yocto Project 中,PREFERRED_PROVIDER 是一个变量,用于指定首选的软件包提供者。通过设置 PREFERRED_PROVIDER,你可以选择特定软件包的提供者,或者使用替代的软件包提供者。
这个变量通常在配置文件(如 local.conf 或 conf/distro.conf)中使用,用于指定特定软件包的首选提供者。它的语法如下:
goCopy code
PREFERRED_PROVIDER_<package> = "<provider>"
其中 <package> 是要指定首选提供者的软件包名称,<provider> 是该软件包的首选提供者的名称。
例如,如果你希望将 openssl 软件包的首选提供者设置为 openssl-1.1.1,你可以这样定义:
makefileCopy code
PREFERRED_PROVIDER_openssl = "openssl-1.1.1"
这将告诉 Yocto Project 构建系统优先选择名为 openssl-1.1.1 的软件包作为 openssl 的提供者。
通过使用 PREFERRED_PROVIDER,你可以控制 Yocto 构建系统在存在多个软件包提供者时选择特定的提供者
==需要注意的是,PREFERRED_PROVIDER 只是指定软件包提供者的首选顺序,并不会强制使用指定的提供者。如果指定的提供者不可用或不满足依赖关系,Yocto 构建系统仍会尝试使用其他可用的提供者。==
PREFERRED_PROVIDERS
这个参数和PREFERRED_PROVIDER差不多,只不过这个能一次性指定多个首选项。
比如:
PREFERRED_PROVIDERS = "xxx:yyy aaa:bbb ..."
PREFERRED_PROVIDER_xxx = "yyy"
PREFERRED_PROVIDER_aaa = "bbb"
这两种方式是等价的。
PREFERRED_VERSION
这个指定一般就是在.conf文件中。
上面能够选定配方了,但是问题还是有?有很多配方是有多个版本的,问题是,选择哪个版本?
这个参数就能指定到底选择哪个具体的版本。
定好PN名称作为变量后缀,赋值成想要选择的版本。
PREFERRED_VERSION_python = "2.7.3"
PREFERRED_VERSION_linux-yocto = "4.12%"
使用 % 来通配,一般是选择高版本。
==kernel版本选择==

可以看到有很多kernel的recipes,但是具体执行的是5.4版本,为什么?如何指定的是这个版本而不是其他的?
poky/meta-qti-bsp/conf/machine/qrbx210-rbx.conf
poky/meta-qti-bsp/conf/machine/include/qrbx210.inc
PREFERRED_PROVIDER_virtual/kernel = "linux-msm"
PREFERRED_VERSION_linux-msm ?= "5.4"
PREFERRED_VERSION_linux-msm-headers ?= "5.4"
就是在这个地方指定的5.4版本。
==问题是,应该不止这一个地方指定版本的,因为我修改了这个地方的版本,编译过程中检测到我的修改,但是认为我的修改不合理,所以又去编译5.4版本的kernel==
SRC_URI
az://: Fetches files from an Azure Storage account using HTTPS.
bzr://: Fetches files from a Bazaar revision control repository.
ccrc://: Fetches files from a ClearCase repository.
cvs://: Fetches files from a CVS revision control repository.
file://: Fetches files, which are usually files shipped with the Metadata, from the local machine. The path is relative to the FILESPATH variable. Thus, the build system searches, in order, from the following directories, which are assumed to be a subdirectories of the directory in which the recipe file (.bb) or append file (.bbappend) resides:
${BPN}: the base recipe name without any special suffix or version numbers.
${BP} - ${BPN}-${PV}: the base recipe name and version but without any special package name suffix.
files: files within a directory, which is named files and is also alongside the recipe or append file.
ftp://: Fetches files from the Internet using FTP.
git://: Fetches files from a Git revision control repository.
gitsm://: Fetches submodules from a Git revision control repository.
hg://: Fetches files from a Mercurial (hg) revision control repository.
http://: Fetches files from the Internet using HTTP.
https://: Fetches files from the Internet using HTTPS.
npm://: Fetches JavaScript modules from a registry.
osc://: Fetches files from an OSC (OpenSUSE Build service) revision control repository.
p4://: Fetches files from a Perforce (p4) revision control repository.
repo://: Fetches files from a repo (Git) repository.
ssh://: Fetches files from a secure shell.
svn://: Fetches files from a Subversion (svn) revision control repository.
DEPENDS
事实上,依赖在运行时是没有任何关系的。
即便是有预编译组件,DEPENDS也是很必要的,因为DEPENDS是将依赖项放在sysroot,如果libfoo依赖libbar,这个项目依赖libfoo。这样,调用libfoo尽管libfoo会放在sysroot中,但是libbar不在,还是会失败。
SYSROOT_DIRS
是一个目录,由任务do_populate_sysroot安装的目录,这是一个临时性的目录。
默认的有:
SYSROOT_DIRS = " \
${includedir} \
${libdir} \
${base_libdir} \
${nonarch_base_libdir} \
${datadir} \
/sysroot-only \
"
IMAGE_FEATURES
EXTRA_IMAGE_FEATURES
无论是EXTRA_IMAGE_FEATURES还是IMAGE_FEATURES都是给yocto添加特性的。
他们的区别是:如果你是添加在recipes层次就用IMAGE_FEATURES,如果你是修改local.conf文件就用EXTRA_IMAGE_FEATURES。
这两个值本身设置就可以了,但是一个项目中各个地方都可能写这些值,会很混乱,为了便于管理这些值,引入了==白名单工作机制==。
以poky/meta/classes/image.bbclass为例子:
IMAGE_FEATURES[validitems] += "debug-tweaks read-only-rootfs stateless-rootfs empty-root-password allow-empty-password allow-root-login post-install-logging"
只有添加在IMAGE_FEATURES[validitems]中的特性,被启用才会生效。
IMAGE_FEATURES[validitems]本身只是个白名单,不会启用这些特性。
dependencies
.bb file
addtask 变量可以将任务与当前配方或者人物与任务之间构成关系。
addtask printdate after do_fetch before do_build
添加printdate任务在do_fetch 之后,在do_build之前
对于一个任务想要运行,必须直接或者间接的依赖其他的任务,加入到运行任务的列表中。
还有另外一种写法
bb.build.addtask(name, after=None, before=None, d)
配方运行机制
运行配方的时候,会对配方中的任务进行排序,先准备好运行的顺序,每当运行完成一个任务,就会对这个任务创建一个时间戳,后续如果用到这个任务就会先去检查这个时间戳,如果时间戳能够匹配上,保证整个任务没有改变,就不需要再次运行这个任务了,直接使用。
需要注意的是,有一些任务是没有时间戳的概念的,他们是必定会运行的。
packagegroup
==需要明确概念,软件包,软件包组,配方,核心镜像的关系和区别==
==recipes:==
配方指的就是一个bb文件,只有bb文件结合源码,conf配置文件结合起来才能构成一个简单的软件包。
==软件包:==
就是一个配方带上源码等等,,,,
example-package/
├── example-package.bb
├── files/
│ └── example.conf
└── src/
└── example.c
事实上,我们很少见到这样的,因为项目中的关系都是很复杂的,一般都是要放在软件包中,这样bb文件就不止一个了,而且源码一般是和配方分开的。
==软件包组:==
example-packagegroup/
├── example-packagegroup.bb
└── recipes-example/
├── package1/
│ └── package1.bb
├── package2/
│ └── package2.bb
└── package3/
└── package3.bb
所以一个软件包组是有很多个软件包组成。(也可以是一个软件包就是一个软件包组)
==核心镜像==
这个就是一个bb文件,但是他是控制整个系统的bb文件。
CORE_IMAGE_EXTRA_INSTALL
CORE_IMAGE_EXTRA_INSTALL参数更是一个全局变量,指定软件包组的列表等。
查找当项目所有包组
一般这个就用
bitbake-layers show-recipes | grep packagegroup
命令就能够看到,里面也会有不用的。
查找当前项目使用的包组
这种只能查看项目运行生成的中间文件或者统计信息。
其中installed-package-names.txt里面就能看到实际使用的那些软件包组。
build-qcom-wayland/buildhistory/images/qcm6490/glibc/qcom-multimedia-image/installed-package-names.txt
制作软件安装包
opkg在yocto的机制
/etc/opkg/opkg.conf是配置文件
制作软件包是apps_proc/poky/qti-conf/local.conf文件中
PACKAGE_CLASSES ?= "package_ipk"
来指定了要制作ipk的软件包,同理,package_rpm package_deb都是可以制作的,如果同时指定多个,那么生效的应该是第一个。
yocto就是通过这个生成ipk,方便板子更新软件包的,不同于开源的linux系统(比如ubuntu),yocto需要经过交叉编译生成新的ipk来更新软件包,ubuntu因为有人维护所以会有直接交叉编译完成的软件包下载安装。
制作软件包一般是编译阶段最后一步,
名字:log.do_package_write_ipk
目的:将这个配方的生成打包成ipk包的方式。
一般会存放在工作目录下1.0-r0/deploy-ipks里面,同时每次编译(单编就可以更新)也会将deploy/ipk/aarch64$文件下面的对应的ipk更新。
==这两个位置存放的ipk包是一模一样的==
多个软件包
一般一个配方会编译出来同名的多个软件包
├──xx_1.0-r0_aarch64.ipk
├── xx-dbg_1.0-r0_aarch64.ipk
└── xx-dev_1.0-r0_aarch64.ipk
xx_1.0-r0_aarch64.ipk主要的应用程序包,包含了运行该应用程序所需的二进制文件和基本文件
xx-dbg_1.0-r0_aarch64.ipk调试包,包含调试符号和信息,用于开发和调试。这些符号可以帮助开发者在调试器中查看更详细的错误信息和程序运行情况
xx-dev_1.0-r0_aarch64.ipk开发包,包含头文件和开发所需的库文件,通常用于开发其他软件时引用该包的功能
一般来说,加载的是xx_1.0-r0_aarch64.ipk。
软件开发调试加载xx-dbg_1.0-r0_aarch64.ipk
更新软件包
opkg list 查看当前有哪些
opkg list-installed 查看当前安装了哪些
opkg info camera-test 能够查到当前安装的ipk具体信息,里面就有一个时间信息,如果成功更新,这个时间信息会变的,靠这个判断是否成功更新。
opkg install --force-reinstall /path/ipk_name 强制安装
opkg install --force-reinstall因为opkg不允许安装同样版本的包,要么就强制安装,要么就编译的时候临时将版本号写大点。
==同样需要注意的是,更新ipk是会更新二进制文件等依赖文件,但是有的系统,可能会显示一些更新失败,要特别留意,否则可能出现只有部分更新的问题==
解析配方
BitBake 刚刚把 .bb 文件读进内存、展开变量、计算任务依赖的时候,远早于真正 fetch / unpack / compile 的构建阶段。
这个阶段的时候是在最开始,此时work目录都没创建
这个阶段也会涉及到网络问题
主要是有的需要连接网络拉取仓库,且是要求最新提交的配方,会有下面的一句。
SRCREV = "${AUTOREV}"
这个就要求在解析阶段先访问一下远程仓库获取到基本的仓库提交信息等(还没有拉取仓库)
这个比使用download阶段还早,本地有download缓存也没用,设置不访问网络也是无效的。
解决办法:
把SRCREV设置成需要的commit id,固定住提交,就可以使用download了。
配方识别添加机制
一般流程是:
BBLAYERS 可以找到当前层中配方的路径。
packagegroup 负责控制包组的捆绑,决定该包组内的配方包如何捆绑在一起。
IMAGE_INSTALL 决定了最终哪些包组会被编译并打包进镜像。
set_bb_env
这是yocto的编译脚本。
bsp layer
bsp layer中包含了machine,bootloader、kernelware、底层驱动和内核驱动。
bsp layer一般就是原厂、板卡厂商或者开源社区。
recipes
不介绍具体配方的语法规则,主要介绍配方在整个系统中运行的规则和配方的运行方法。
task
YOCto中的任务只有两种shell语言和python的。
每个配方都是有函数的,如果没写任何函数,那就是按照default。
如果不想执行某个函数,不要去删除!!!!这可能会破坏隐式规则,方法应该是写这个函数在recipes中,这样就会覆盖默认的,但是这个函数是空的 Nothing to do 实现删除效果。
deltask do_install
删除默认的do_install(强烈不建议)
do_populate_lic
写入一些配方的许可证信息
非编译主链
do_populate_sysroot
将当前配方提供的编译资源导出,使其他依赖该配方的recipe能够编译。
do_package
通过解析${D}区域下的文件,将内容划分子集。用到的变量有PACKAGES FILES
这个任务最关键的还是它和do_packagedata任务一起将一些重要的包的元数据保存起来。
do_packagedata
通过do_package任务将元数据加入到PKGDATA_DIR变量中,这个变量是全局可用的。(这样就使得元数据是全局可用的了)
do_package 和 do_packagedata两个任务,一个把文件解析的元数据都拿出来,一个将元数据加入到环境变量中。
rm_work
这个任务的目的就是减少项目所占空间,每一个配方编译完成之后如果这个任务是开启的,会在最后执行这个任务,会删除配方临时掉工作空间下除了temp所有的文件。
这个任务是写在poky/meta/classes/rm_work.bbclass文件中,
开关在poky/qti-conf/local.conf中有一句
INHERIT += "rm_work"
把这句注释掉。一般正式项目都会把这一句注释掉不开启rm_work任务。
如果不想注释这句,就是希望减少所占空间,但是又不希望有一些配方的临时空间被删除,可以在这个文件中加入:(还没有验证过效果)
RM_WORK_EXCLUDE += "busybox glibc"
do_symlink_kernsrc
/home/wangdong/workspace/QRB2210_LE10_AP/apps_proc/poky/meta/classes/kernel.bbclass
kernsrc = /home/wangdong/workspace/QRB2210_LE10_AP/apps_proc/build-qti-distro-fullstack-debug/tmp-glibc/work-shared/qrbx210-rbx/kernel-source
packagegroup
引入包组的概念,通过群组的方式将类似的==一些==配方封装成一个群组,可以在全编配方中选择哪些群组需要去编译,也可以在每个群组里面决定这个群组里哪些配方会被编译。
就是说在群组中,你选择加入哪些本群组内的配方,哪些配方在编译到这个群组中就会被编译并且被打包进rootfs中(除非你特别的设置了打包规则)。
PS:并不是说不把配方写进群组中就不会去编译,需要知道的是yocto还有别的机制,如果一个配方依赖了别的配方,当编译这个配方的时候也会去校验依赖的配方是否需要编译。尽管群组中没有加入被依赖的配方,这个被依赖的配方被依赖用到,也会被编译。
为什么能找到没加入群组的配方呢?
那是因为yocto的群组仅仅是为了表明哪些需要编译。那些不需要编译加载进系统的群组和配方就不代表yocto不知道他们的位置。在初始化环境变量运行set_bb_env.sh脚本的时候,会生成很多文件保存配置信息,其中有一个文件build-qti-distro-fullstack-debug/conf/bblayers.conf这个里面就是保存的所有的群组(无论这个群组有没有被加入到编译列表里面,因为这个保存的群组列表是脚本poky/qti-conf/get_bblayers.py根据名字去匹配的),不包含一些群组
ignoreList = [ "meta-selftest", "meta-skeleton", \
"meta-poky", "meta-yocto", "meta-yocto-bsp", \
]
因为这些群组是被添加在排除的列表里面,所以这里就能找到出排除列表外的所有的群组。
能找到所有的群组,找到群组里面的配方就简单很多,在群组机制下,所有的配方必定会在一个群组文件夹里面。
recipe-products
这个应该是和recipe文件夹同级的,里面放的是.bbappend文件,这个文件里面的参数决定了哪些会被放在系统里面。但是现在问题是,这个机制和packagegroup机制一起出现的时候,会出现什么情况?
已经知道的是packagegroup是一定会起作用的。
==事实上一旦使用了群组,这个就完全没作用了。==只有没有群组机制的时候,这个机制才会有用。
ps:应该是1版本的没使用群组机制,2版本后面的应该都有群组机制,所以这个东西并没有那么重要。
预编译 qprebuilt
inherit qprebuilt
预编译简单点说就是直接把编译结果放到rootfs中,不会去编译代码,这种一般就是为了缩减编译时间,提前把编译结果打包,编译的时候解包直接放到rootfs中。
至于从哪里找到预编译需要的包,可以查看temp/log.do_install_qprebuilt
配方层
查看已经激活的配方层:
后面的数字表示优先级,数字越大,优先级越高,优先级高的会覆盖优先级低的。
bitbake-layers show-layers
优先级是写在conf/layer.conf里面
BBFILE_PRIORITY_meta-qti-audio-external = "0"
配方主包和子包
以ntfs-3g-ntfsprogs,有的时候想要添加配方,
IMAGE_INSTALL:append = " ntfs-3g-ntfsprogs"
发现这样失败了,整编的时候的确编译了这个配方,但是实际的rootfs里面并没有相关的文件。
那是因为这样只是把主包打包到rootfs里面,但是这个配方中,主包是空包,真正的生成文件都放在子包里面。
# 1. 主包(ntfs-3g-ntfsprogs)被设置为空包
ALLOW_EMPTY:${PN} = "1" # ${PN}就是ntfs-3g-ntfsprogs,这个包本身无任何文件
# 2. 实际文件都被拆分到3个子包中
PACKAGES =+ "ntfs-3g ntfsprogs libntfs-3g"
# 3. 子包的文件归属(关键)
# - ntfs-3g包:包含mount.ntfs、ntfs-3g等核心可执行文件
FILES:ntfs-3g = "${base_sbindir}/*.ntfs-3g ${bindir}/ntfs-3g* ${base_sbindir}/mount.ntfs"
# - ntfsprogs包:包含ntfsfix等工具
FILES:ntfsprogs = "${base_sbindir}/* ${bindir}/* ${sbindir}/*"
# - libntfs-3g包:包含动态库
FILES:libntfs-3g = "${libdir}/*${SOLIBS}"
所以真正的做法是所有的包都放进去。
IMAGE_INSTALL:append = " ntfs-3g ntfsprogs libntfs-3g"
task
构建开源软件包
- parse
- fetch
- unpack
- patch
- do_prepare_recipe_sysroot
- configure
- build
- install
- do_populate_sysroot
- package
- rootfs
- images
sysroot
一般每个配方都会有 recipe-sysroot-native 和 recipe-sysroot
recipe-sysroot 中都是编译给目标板使用的,里面文件是arm架构
recipe-sysroot-native 中都是在主机环境使用的工具,一般都是x86架构的。
do_populate_sysroot 把当前配方文件导出到其他文件
do_prepare_recipe_sysroot 把其他配方文件导入当前配方
rootfs
yocto官方手册有专门的章节介绍。
主要介绍构建的系统权限问题,比如如何构建只读系统,如何去开辟一块写区域等等。
3 Common Tasks — The Yocto Project ® 3.1.25 documentation
构建只读系统
两种方法:
IMAGE_FEATURES += "read-only-rootfs"
EXTRA_IMAGE_FEATURES = "read-only-rootfs"
postinst
这个步骤一般都是在核心镜像编译rootfs的配方中,的do_rootfs任务中。
这是一个脚本,不属于yocto,自然也不是任务。
这是rpm的一部分:
.rpm 包
├── 二进制文件 /usr/bin/foo
├── 库文件 /usr/lib/libfoo.so
├── 配置文件 /etc/foo.conf
├── preinst ← 安装前脚本
├── postinst ← 安装后脚本(就是它!)
├── prerm ← 卸载前脚本
└── postrm ← 卸载后脚本
一般在打包阶段,rpm包里面嵌入了这个脚本,在do_rootfs阶段,所有的包都安装好之后,执行到安装后脚本的时候,就会执行这个脚本。但是postinst执行,只是报道。
这里需要引入一个包管理的方法:intercept(批处理收尾)
postinst_intercept需要将postinst脚本都记录报道
run_intercepts在最后记录完成之后一次将所有遍历,执行一遍。
遍历这个文件夹下面:yocto/poky/scripts/postinst-intercepts
yocto/poky/meta/lib/oe/package_manager/init.py脚本去遍历检查文件是否具有可执行权限
if script == "postinst_intercept" or not os.access(script_full, os.X_OK):
continue
没有可执行权限就跳过。
这个下面的文件一般都不会有可执行权限,都会跳过,postinst_intercept脚本倒是可以有权限,但是这个脚本在代码中点名跳过
总结就是根本没有收尾,这些收尾的脚本实际上都不执行。
问题
之所以特别介绍,在开发过程中这个地方遇到了问题。
rk平台编译到这里出现了一个/var/tmp目录的访问失败,原因是我使用schroot环境,这个环境本身就对/var目录没有操作权限,因为没有挂载到主机环境上。
高通的yocto没有出现这个错误是因为高通使用的是ipk+opkg这种方式安装
rk使用的是rpm+dnf
ipk+opkg
解压文件 + 简单脚本
没有数据库事务(或者很弱)
没有复杂依赖校验
postinst 很少做复杂操作
❗ 基本不依赖系统目录(比如 /var/tmp)
rpm+dnf
事务系统 + 数据库 + 脚本执行引擎
创建系统文件
生成缓存(ldconfig)
更新 alternatives
初始化目录
甚至可能启动/操作服务
rpm 在执行 scriptlet 时,不会直接执行脚本,而是:
1. 把脚本写到临时文件
2. 再执行这个文件
解决方案:
考虑过给这个中间目录换个位置,不用/var/改成/tmp,这样应该也可以,但是有一个问题就是/tmp是很临时的文件,如果重启服务器,数据信息就会丢失,主要丢失的就是编译过程中的debug信息,对开发板上镜像没有任何影响。
语法
变量赋值
= 直接赋值完全执行完recipes才能决定变量(makefile && recipes)。意思就是
x = "foo"
y = "${x} bar"
x = "xyz"
y的结果是xyz bar
:=立即赋值
?=默认赋值如果之前没有赋值,那么运行到这里就会进行一次赋值。
??=弱默认赋值,如果一直没有给变量赋值,运行结束之后选择最后一个弱默认赋值进行赋值,但是如果中间出现了 = ?= 等赋值符号,直接使用这些赋值符号。
+=空格后增赋值 将值加在变量现有值后面,中间有一个空格。
=+ 空格前增赋值 将值加在变量现有值的前面,中间有一个空格
.=后增式 将值加在变量现有值的后面,这种方式是没有空格的。
=.前增式 将值加在变量现有值的前面,这种方式是没有空格的
操作符
_append 追加操作符
SRC_URI_append = " file://fix-makefile.patch"
这种是按照追加的方式追加在SRC_URI的后面,而且这种是没有空格的,所以在file前面才会有一个空格的。
如果遇见了变量赋值 +=等 这些变量赋值都结束了才会执行操作符。
还有一种更常见的用法:
CPPFLAGS_append_qrbx210 += "-DCOMPILE_DRM"
采用这样的方法,就是覆盖的追加方法,指定了特定的machine :qrbx210 只给这一个追加上去。
_prepend
sstate
shared state cache
sstate 机制以 task 为最小粒度,给每个 task 算一个反映其全部输入(自身代码、依赖签名、相关变量)的 hash,构建前先按 hash 去本地/远端缓存查产物,命中就直接搬运结果跳过执行(setscene),未命中才真正跑。团队共享缓存服务器是它在协作场景下最大的价值所在。
缓存的粒子是task不是recipe,每个task单独计算签名、单独判断是否命中缓存。
sstate包存放在 ${TMPDIR}/sstate-cache ,存放的不是源码,而是task之后产生的输出,按签名hash值的前两位做子目录区分。
正常在配方中打回车没用,bitbake在解析文件之前就去掉无用的内容了。认得是解析内容,不是文本内容。
缓存工作流
BitBake 准备执行某个 task
│
├─ 1. 根据 task 内容 + 依赖签名 + 相关变量,计算出这个 task 的 signature (hash)
│
├─ 2. 拿这个 signature 去查:
│ - 本地 sstate-cache 目录
│ - 配置的远端 sstate mirror (SSTATE_MIRRORS 变量指定,公司通常会有内部服务器)
│
├─ 3a. 命中 (缓存里有对应 signature 的包)
│ └─→ 下载/复制这个包,解压还原到对应位置 (sysroot / build 目录等)
│ 标记这个 task 为 "setscene" 完成,跳过真正执行
│
└─ 3b. 未命中
└─→ 真正执行这个 task 的实际逻辑 (跑编译器/脚本等)
执行完毕后,把产物打包,存进本地 sstate-cache
(如果配置了可写的远端 mirror,也可能上传上去供团队共享)
Sstate主要缓存具有明确、可复用最终产物的任务,不是所有任务都会有sstate
setscene
正式执行任务之前,会进入setscene阶段。
这个对应于每个task的do_XXX ,专门负责"尝试从缓存恢复"
可以看到前13行是第一次编译,执行了完整的编译流程。
14行以后是第二次编译,这次发现没有修改,直接执行了setscene恢复了。
然后执行17行真实任务,再次导出。

缓存失效
绝对路径:如果涉及不同用户之类的操作,绝对路径就不一样。
时间戳:每次编译都不一样。
native/cross 工具链的变化:所有配方都依赖工具链,工具链出现变化会引起大面积变化。
sstate判断
任务哈希 =
任务代码
+ 任务使用的变量
+ 源码和补丁信息
+ 配置
+ 依赖任务哈希
hash值
不止 sstate 任务会计算哈希,所有任务都会计算。哈希计算由 BitBake 完成,sstate 只是使用计算结果。
sstate失效
这种就是一个任务实际输出没有变,但是yocto保守的认为这个任务发生了变化,变化就沿着依赖链往下传播,导致大量sstate无法命中。
1. 修改公共依赖的配方或者bbclass
2. 修改全局配置
3. 修改工具链,比如-native之类
4. 强制执行重编 -f
5. sstate本身失效了
Hash Equivalence
Hash Equivalence专门用于缓解:任务输入不同,但最终输出相同。
Task Hash:任务输入的哈希
Output Hash:实际任务输出的哈希
UniHash:相同输出使用的统一标识
比如
旧Task Hash = H1
新Task Hash = H2
但:
Output(H1) == Output(H2)
这种就不会把变化往下传递了。
配置方法:
BB_HASHSERVE = "auto"
BB_SIGNATURE_HANDLER = "OEEquivHash"
确认变化的任务hash
这个命令不会重新执行编译,只是查找task的hash变化。
bitbake -S printdiff recipeName
devtool
Yocto 提供的增量开发工具,核心目的是让你不用每次改代码都走完整的 recipe/build 流程,尤其适合调试单个组件或做二次开发。
把由 bitbake 全流程管理"的 recipe,切换成"源码在本地可直接编辑、增量编译"的模式
devtool modify pulseaudio
如果第一次执行,会在build/workspace建立对应目录。
具体每个配方存放位置。
workspace/sources/<recipe-name>/
工作原理是将原本的${WORKDIR}/git 目录换到了 workspace/sources/<recipe-name>/
bbapend位置:
build/workspace/appends/pulseaudio_git.bbappend
具体流程是:
1. 解析原始配方,拿到源代码
2. 解压,打补丁
3. 把这个目录初始化成一个完整的仓库,如果本来就是个git仓库,会保留原本的git历史,
4. 生成.bbapend,接管后续构建行为
5.
externalsrc.bbclass
交叉编译
需要在项目外不使用bitbake来构建一个测试程序,就需要用到交叉编译。
需要先生成交叉编译工具链,使用生成的交叉编译的工具链才能去交叉编译。
运行配方meta-toolchain.
然后执行安装交叉编译工具链的脚本oecore-x86_64-armv7ahf-noon-toolchain-nodistro.0.sh
脚本的安装的目录结构:
<install_dir>/
├── bin/
│ ├── arm-oe-linux-gnueabi-gcc
│ ├── arm-oe-linux-gnueabi-g++
│ ├── ...
│
├── sysroots/
│ ├── x86_64-oe-linux/
│ │ ├── usr/
│ │ │ ├── include/
│ │ │ │ ├── ...
│ │ │ ├── lib/
│ │ │ │ ├── ...
│ │ │ ├── libexec/
│ │ │ │ ├── ...
│ │ │ ├── ...
│ │ │
│ ├── arm-oe-linux-gnueabi/
│ │ ├── usr/
│ │ │ ├── include/
│ │ │ │ ├── ...
│ │ │ ├── lib/
│ │ │ │ ├── ...
│ │ │ ├── libexec/
│ │ │ │ ├── ...
│ │ │ ├── ...
│ │ │
│ ├── ...
│
├── ...
└── licenses/
├── COPYING
├── ...
bin包含了交叉编译工具。
sysroot包含了不同目标架构的根文件系统的内容。
x86_64-oe-linux: 这是针对 x86_64 架构(64 位 Intel 和 AMD 处理器)的交叉编译工具链。它用于生成在 x86_64 架构上运行的应用程序和库。arm-oe-linux-gnueabi: 这是针对 ARM 架构的交叉编译工具链。具体来说,arm-oe-linux-gnueabi针对 ARMv7 架构的 ARM 处理器。它用于生成在 ARM 架构上运行的应用程序和库。
licenses 目录包含了工具链和相关软件的许可协议文件
/home/wangdong/workspace/apq8009-le-2-1/apps_proc/build-qti-distro-fullstack-debug/tmp-glibc/deploy/sdk/toolchain/sysroots/x86_64-oesdk-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi-gcc test.c -o test --sysroot=/home/wangdong/workspace/apq8009-le-2-1/apps_proc/build-qti-distro-fullstack-debug/tmp-glibc/deploy/sdk/toolchain/sysroots/armv7ahf-neon-oe-linux-gnueabi -mfpu=neon -mfloat-abi=hard
多平台构建适配
Yocto 的“多平台构建”,指的是:使用同一套 Yocto 工程和源码,为多个不同的目标硬件平台分别生成系统镜像。
配置
配方配置
前者表明了配方是什么平台(如果设定了平台就会存放在 build-qti-distro-fullstack-debug/tmp-glibc/work 对应的平台文件夹),是给./configure传参,configure会根据此决定一些宏控开关及后续操作。
后者表明产物的归属,如果不表明归属一般根据项目,比如默认为 aarch64,yocto就认为该配方和cpu架构有关,和平台无关,任何平台都可以适配。(这在一个项目单次只编译一个平台没有问题,但是如果一个项目多个平台轮流编译,就可能导致这个配方属于b平台,但是a平台先编译过,b平台编译的时候,认为这个配方不需要重编,直接用a平台编译产物)
EXTRA_OECONF += "--enable-target=${BASEMACHINE}"
PACKAGE_ARCH = "${MACHINE_ARCH}"
编译脚本配置
configure.ac
前者将命令行中的--enable-target转化成变量。
后者对变量进行判断转化成对应的宏定义。
AC_ARG_ENABLE(target,
[AS_HELP_STRING([--enable-target=TARGET], [Specify the target product to build])],
[TARGET=$enableval],
[TARGET=none]
)
if test "x$TARGET" = "xsim8965"; then
CPPFLAGS="${CPPFLAGS} -DSIMCOM_PLATFORM_SIM8965"
CFLAGS="${CFLAGS} -DSIMCOM_PLATFORM_SIM8965"
fi
test中x是一种防御写法,很老的shell中,空参数的时候,会恒定相等。
test "" = "sim8965"
会被转化成
test = "sim8965"
编译优化
首先应该记录基线编译时间,以这个为基准来进行优化。
时间
体积
启动时间
构建可复性
poky源码解析
poky是官方提供的具体的一个标准实例项目。
https://github.com/yoctoproject/poky/tree/scarthgap
标准的poky只有不到300M(单分支)。但是如果编译完成,整个项目大概在60G左右。
目录架构
| 文件或目录 | 作用 |
|---|---|
bitbake/ |
BitBake 构建工具本身 |
meta/ |
OpenEmbedded-Core,也就是 Yocto 的核心层 |
meta-poky/ |
Poky 参考发行版配置 |
meta-yocto-bsp/ |
官方参考 BSP 层 |
scripts/ |
Yocto 提供的开发、调试和维护工具 |
oe-init-build-env |
初始化 Yocto 编译环境 |
documentation/ |
Yocto 官方文档源码 |
meta-skeleton/ |
编写配方、内核模块等内容的示例模板 |
meta-selftest/ |
Yocto 自测试使用的层 |
contrib/ |
社区贡献的辅助脚本 |
文档说明
poky/meta-poky/README.poky.md
https://docs.yoctoproject.org/scarthgap/brief-yoctoprojectqs/index.html
meta
meta layer是所有的根基,提供了完整的Linux系统完整的底层机制。
包括不限于:
1. 构建引擎的规则:classes/、classes-global/、classes-recipe/ 里的 bbclass(base、image、kernel、cmake、autotools 等)定义了"配方怎么执行"——下载、解压、编译、安装、打包的全套流程。没有它们,任何 .bb 配方都无法被解析执行。
2. 基础配置与变量体系:conf/bitbake.conf 等定义了所有核心变量(TMPDIR、WORKDIR、S、D、交叉编译变量等)的默认值,是整个变量体系的根。
3. 最底层的软件配方:recipes-* 提供构建一切所需的最小集合——
- 工具链:gcc、binutils、glibc(recipes-devtools)
- 内核与引导:recipes-kernel
- 基础用户态:busybox、base-files、initscripts(recipes-core)
- 包管理与构建工具:opkg、rpm、python、perl(recipes-devtools)
4. 通用支持设施:lib/(OE 的 Python 库,供 bbclass 和配方函数调用)、files/、site/(autoconf 缓存)。
meta保证了最基本的功能,能够产出一个最小镜像。
meta-poky
配置描述发行版本的样子,不是代码,发行版的代码是放在meta中的。只是负责定义发行版的,不是构建发行版。
meta-yocto-bsp
bsp推荐参考,里面配置了真实pc和qemu等板卡配置。
正常这个层都是作为参考,新建自己板卡配置layer后,从 BBLAYERS 中移除这个即可。
环境配置和源码拉取
自行参考 https://docs.yoctoproject.org/scarthgap/brief-yoctoprojectqs/index.html
Ubuntu 环境更加推荐 jammy22.04
poky编译
在poky目录执行 oe-init-build-env 脚本
source oe-init-build-env
bitbake core-image-minimal
qemu测试
确认machine
bitbake-getvar MACHINE
MACHINE="qemux86-64"
运行测试,一般账号root ,无密码
runqemu \
/home/wangdong/workspace/scarthgap/poky/build/tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.rootfs.qemuboot.conf \
nographic slirp
常用命令
查看所有配方任务情况
bitbake core-image-minimal -g
一般会生成两个文件在build中
pn-buildlist
task-depends.dot
pn-buildlist是bitbake依赖推导之后,表示了会参与构建调度的配方,但是不表示依赖关系,也不表示编译顺序。因为实际依赖关系是很复杂的,编译也是并行编译。
task-depends.dot
label表示名字,task阶段,版本号,配方的文件路径等信息
A->B表示依赖关系,B必须在A之前完成,A依赖B
"acl-native.do_compile"
"acl-native.do_compile" -> "acl-native.do_configure"