在之前的博文中,我们学习了直接通过 Makefile 手动来进行的构建,其实,目前存在多种嵌入式 Linux 环境的构建工具,其中,Yocto 就是被广泛应用的一种。由于之前更多的是使用 Buildroot,于是开始恶补 Yocto 相关知识,以下就是学习记录。
嵌入式 Linux 环境的搭建是从源代码开始的,可以手动构建每一部分,也可以选择使用自动化构建工具。如果选择纯手工搭建就要熟悉每一部分的源码的构建细节(好消息是,Linux 系统下的源码基本都是 configure + make 的处理流程),使用自动化构建工具则需要学习对应工具的使用方法。
现代嵌入式构建工具往往是构建出一套完整的嵌入式 Linux 环境。更详细说明参见之前的博文 Linux Kernel 之一 完整嵌入式 Linux 环境、构建工具、编译工具链、CPU 体系架构。
Yocto 全称是 Yocto Project(官方简称 YP) 是 Linux 基金会在 2010 年推出的一个开源的协作项目。提供模板、工具和方法以创建定制的 Linux 系统和配套工具,而无需关心硬件体系。主要由 Poky 和 其他一些工具组成。
从历史上看,Yocto Project 是从 OpenEmbedded 项目发展而来的。他们本是两个不同的项目(左侧分离视图),然而,目前的 OpenEmbedded 与 Yocto Project 已经融合为一体了(右侧合并视图),因为目前已经很少见单独使用 OpenEmbedded 了。
在很多文献中都是右侧这种结构,我个人认为左侧结构更好理解一些!
虽然 Yocto Project 和 OpenEmbedded 的代码仓库是分开的(https://git.yoctoproject.org/ 和 https://git.openembedded.org/),但是其中的内容都是相互关联的,很多概念也不再区分是由 Yocto Project 引入的还是属于 OpenEmbedded。即 Yocto Project 构建过程 = OpenEmbedded 构建过程。
Poky 官方定义为 Yocto Project 的参考嵌入式发行版(Reference Embedded Distribution),它才是我们真正使用的构建系统工具(更确切的说这就是一个可以构建出嵌入式 Linux 的 DEMO)。而 Yocto Project 这个名字(或者我们常简称的 Yocto)是指的这个项目本身或者这个项目组织机构。
Yocto 每六个月(分别在每年的四月和十月)发布一次大版本,每个大版本都有个代号,例如 Mickledore、Kirkstone 等。为了保持稳定性,Yocto 有长期支持版本,因此,每个大版本的生命周期为 Initial Release -> Stable -> Community -> EOL
或 Initial Release -> LTS -> Community -> EOL
。如下是当前版本列表
每个大版本包含:Bitbake、OE-Core、Meta-yocto、yocto-docs 这个四部分内容。当存在 BUG 需要修复时,BUG 的补丁必须优先进入 Master 分支,然后才会合并到稳定分支(与 Linux Kernel 策略相同)。
我们从 Yocto Project 网站下载的发行版的 Yocto (官方命名为 YP CORE-xxxx)就是 Poky 源码包。下面是一个 Poky 源码包的简单说明及与 OpenEmbedded-Core 的对比,从中不难看出 Poky = BitBake + OpenEmbedded-Core(稍作改动) + Yocto 自定义 Layer。
- Poky 不包含 Yocto Project 提供的其他工具,需要单独下载
- 除了版本号,每个Yocto Project 的发行版 Poky 源码包都会有个代号!例如,目前最新的 HONISTER、上一版 HARDKNOTT 等。
Poky 的文档(源码目录/documentation/*
)使用的是 Sphinx 搭建的文档系统。Sphinx 也是基于 Python 的,使用的是 reStructuredText 语言格式,文件扩展名通常是 .rst。这个目录下包含了一系列的文档,用于描述当前 Poky 的各种特性。在线文档地址:https://docs.yoctoproject.org/。
Sphinx 文档系统使用 make
命令来生成发布的文档,可以生成 html、pdf 等格式。例如,在 源码目录/documentation/
下执行 make html
命令,就会生成一个 _build
的目录,其中就包含了生成的文档。
- 依赖工具:
sudo apt install python3-pip
、sudo pip install -U Sphinx sphinx_rtd_theme
- 在 Poky 源码根目录也有个 Makefile,也是用来生成这些文档的,只不过使用时必须先指出一些环境变量。感觉有些多余。
- 在 Yocto Project 官方仓库中,有个叫 yocto-docs 的单独的仓库,这个仓库其实就是整个文档的开发仓库,没有问题之后会被合并到 Poky 下,然后随着 Poky 进行发布。
实际使用中,我们可以直接使用 Poky 外加一些自己的 Layer 作为自己的环境,也可以参考 Poky 来搭建出一个自己的 Poky。下图所示的是我在用的一个参考 Poky,纯手工打造出来的(我有很多个自定义的 meta-xxx ,这里使用 meta-custom 代替),用于构建自己的嵌入式 Linux 系统的构建环境。
vendor-setup-env
是自己添加的用于设置当前目录结构,然后调用source/openembedded-core/oe-init-build-env
这个文件。- 自定义的基本思路就是定义并导出 BitBake 要的各种变量,其他 Layer 中会使用这些环境变量
- Poky 只是一个示例,并不能直接拿来通过配置就用于自己的环境中。
整个构建系统的入口就是 OpenEmbedded-Core 中的 oe-init-build-env 这个文件。它是一个 Bash 脚本文件,用来初始化 OpenEmbedded 构建环境。在开启构建之前,必须先执行一次命令 source oe-init-build-env
这个命令。除了初始化构建环境,这个命令还会建立一个 build 目录(不指定
时的默认名),用来存放构建中产生的所有内容。执行命令后,会自动跳转到
目录下,后续就可以使用 bitbake
启动指定的目标的构建了。
关于 source 命令:
- source 命令是一个内置的 shell 命令,用于从当前 shell 会话中的文件读取和执行命令。source 命令通常用于保留、更改当前 shell 中的环境变量。主要有以下四个用途:
- 刷新当前的 shell 环境
- 在当前环境使用 source 执行 Shell 脚本
- 从脚本中导入环境中一个 Shell 函数
- 从另一个 Shell 脚本中读取变量
- source 也可以用一个英语的
.
表示。source 命令从 C Shell 而来,是 bash shell 的内置命令;.
从 Bourne Shell 而来,是 source 的另一名称。
当我们输入 source oe-init-build-env
并执行执行时,会依次调用 oe-init-build-env
➔ scripts/oe-buildenv-internal
➔ scripts/oe-setup-builddir
➔ .templateconf
这四个文件,以下是每个文件中的一些具体操作。
在初始化 Poky 工作环境(source oe-init-build-env
)时,如果没有检测到配置文件,就会自动进行创建。创建时是根据变量 TEMPLATECONF
所指定的目录下的各种配置文件的模板来生成我们使用的配置文件(Poky 修了 .templateconf
文件指向了自己的 meta-poky/conf
)。
构建输出目录中,我们最为关心的是 conf
下的 local.conf
、bblayers.conf
、templateconf.cfg
这三个配置文件(后续详细介绍),以及 tmp
目录下的 deploy
文件夹包含构建的最终输出文件,例如,deploy/images/
下就包含编译好的 Linux 镜像、Bootloader 等。
以上构建输出目录是 OpenEmbedded 默认的目录结构,它是支持我们自己进行自定义的!
Poky 采用的是被称为影子构建的构建模式。即将所有在构建过程中输出的文件都会放到一个单独的顶级文件夹(默认目录名 build
)中,从而不会对源码有任何影响。执行 source oe-init-build-env
就时为了准备好这个构建环境。
当我们成功执行 source oe-init-build-env
命令之后,就会在 Poky 的根目录下自动生成我们指定的
目录(不指定时默认名为 build
),其中还有其他一系列目录。官方文档对于每个目录的作用都有介绍,具体见 https://docs.yoctoproject.org/ref-manual/structure.html#the-build-directory-build。
除了 Poky 这个构建工具系统之外,Yocto Project 还维护其他一些单独的组件。有些是 Yocto 项目内部使用的,有些则是可以单独在开发过程中使用的。我所接触过的主要就是下面这几个,其他还有一些参见官网介绍。
Yocto Project 引入了 Layer Model 这一机制,这也是它区别于其他构建系统的一点。其中,BSP 和 DISTRO 是其 Layer Model 中最具有代表性的两个 Layer。他俩除了有一般 Layer 的文件结构及配置文件外,还额外多了一些自己特有的配置文件。
BSP Layer:Board Support Packages(BSP)主要包含 Machine Configuration 相关的内容(通常,相比于其他 Layer,BSP Layer 会有 conf/machine/*
配置文件)。它定义了如何支持一个特定的硬件设备、一组设备或硬件平台。BSP 包含关于设备上出现的硬件特性的信息、内核配置信息以及所需的任何其他硬件驱动程序。BSP 还列出了必要的和可选的平台特性所需的通用 Linux 软件堆栈之外的任何其他软件组件。
- 一般只有一个 BSP Layer
- Poky 源码中的
meta-yocto-bsp
中就是就是一个 BSP Layer- OpenEmbedded-Core 就一个 BSP Layer(仅支持模拟器) + DISTRO Layer(基本没啥功能) 集合。
DISTRO Layer:DISTRO 是 Distribution 的缩写,主要包含 Distro Policy Configuration 相关的内容(通常,相比于其他 Layer,DISTRO Layer 会有 conf/distro/*
配置文件)。它包含的系统特性的相关配置。这是通过添加到 DISTRO FEATURES 变量来完成的。
- 一般只有一个 DISTRO Layer
- Poky 源码中的
meta-poky
就是一个 DISTRO Layer- 构建出来的 Linux 系统通常被称为 Linux Distribution。我们常用的 Ubuntu 就是一个有名的 Linux Distribution。
Software Layer:软件层为构建过程中使用的其他软件包提供元数据,不包括特定于 DISTRO 或机器的元数据。通常,Software Layer 会有很多个。
OpenEmbedded (简称 OE)是一个自动化构建框架和交叉编译环境,用于为嵌入式设备创建 Linux 发行版。OpenEmbedded 由成立于 2003 年的 OpenEmbedded 社区开发,其诞生远早于 Yocto Project。2011 年 3 月,它与 Yocto Project 开始合作(实际就是合并了)。
从与 Yocto Project 合作开始,OpenEmbedded 便以 OpenEmbedded-Core 项目作为项目发展的名称(简称 OE-Core),之前的称为 OpenEmbedded-Classic(简称 OE-Classic 或 oe-dev),OpenEmbedded 这个名字就用来代指整个 OpenEmbedded 项目。现在的 OpenEmbedded 也可以理解为基于 OE-Core 的一个实现(BitBake + OpenEmbedded-Core + 一组元数据)。
目前,OpenEmbedded 的文档大都直接引用 Yocto Project 上的对应文档了。注意,在一些老文档中,OpenEmbedded 这个名字有可能还是指的旧的 OpenEmbedded 。例如,OpenEmbedded 的原始代码仓库并没有改名为 OpenEmbedded-Classic。旧版 OpenEmbedded 项目的代码仓库见 https://git.openembedded.org/openembedded。
OpenEmbedded-Core 之前的 OpenEmbedded 将所有自动构建的 Recipes 都放在一起(参见旧版 OpenEmbedded 代码仓库),随着发展越来越难以维护。OpenEmbedded 项目组一直想要改进,直到与 Yocto Project 合作后,Yocto Project 开始派人处理这个事。
Yocto Project 的人参与到 OpenEmbedded 后,将原来的 OpenEmbedded 中的大部分 Recipes 使用 Layer 的概念拆分了出去(单独建立源码仓库 meta-openembedded 来进行维护),并重新组织了剩余 OpenEmbedded 的源代码的结构,新的代码源码被命名为 OpenEmbedded-Core(简称 OE-Core)。
OpenEmbedded-Core 是一个比较特殊的 Layer
调整后的 OpenEmbedded-Core 是原来 OpenEmbedded 的子集,只包含了大多数人需要用来构建小型的嵌入式设备的一些 Recipes 以及一些共享类及相关的文件等。目前由 OpenEmbedded 项目组和 Yocto 项目组共同维护。而旧的 OpenEmbedded 源代码(现在称为 OpenEmbedded-Classic)不再维护,也基本没有使用了!
OpenEmbedded-Core 可以独立使用。实际情况是,更多的是被集成在 Angstrom、SHR、Yocto Project 等系统中来使用,很少见单独使用 OE-Core 的情况。OpenEmbedded 基本就成了 Yocto Project 中的一部分(其他基本死的差不多了)。
OpenEmbedded-Core 是无发行版的(没有明确的 DISTRO 定义),并且只包含模拟机器支持。具体见
openembedded-core/meta/conf
下相关代码。
meta-openembedded 是从原来的 OpenEmbedded 中的 Recipes 中拆分出来的 Recipes 的一个集合。这些拆出来的 Recipes 也被组织为一个个的 Layer,并作为对 OpenEmbedded-Core 的扩展。
meta-openembedded 无法单独使用,因为它里面的各个 Layer 依赖于 OpenEmbedded-Core。因此,如果要使用它,就必须同时使用 OpenEmbedded-Core。但是,OpenEmbedded-Core 可以独立使用!
我们可以选择不使用 meta-openembedded,Poky 就没有使用 meta-openembedded。
meta-openembedded 中的每个 Layer 都有专门的人负责维护。此外,OpenEmbedded 官方还维护了一个可以在 OpenEmbedded-Core 中使用的 Layer 列表,其中包含了很多第三方提供的 Layer。
还有一个需要注意的问题是使用时的路径问题。meta-openembedded 本身不是一个 Layer,它里面的内容才是一个个的 Layer。因此在使用时,需要注意他比其他 Layer 多个一级目录。例如 ../meta-openembedded/meta-oe
。
BitBake 是一个任务调度和执行引擎,用来解析指令(Recipes)和配置数据。它允许 shell 和 Python 脚本高效并行运行,同时在复杂的任务间依赖关系约束下工作。基本就是一个类似于 GNU Make 的东西(make 使用 Makefile,BitBake 使用 Recipe)。
BitBake 最初是 OpenEmbedded 项目的一部分。它的灵感来自 Gentoo Linux 发行版使用的 Portage 包管理系统。2004 年 12 月 7 日,OpenEmbedded 项目团队成员 Chris Larson 将项目分为 BitBake 和 OpenEmbedded 两个独立的部分,其中,前者作为一个通用的任务执行器,后者则包含 BitBake 使用的元数据集。
BitBake 是一个用 Python 语言编写的程序,目前由 Yocto Project 与 OpenEmbedded 项目成员共同维护。BitBake 的源代码结构其实并不复杂,代码量也不是很多。如下是一个目录的基本说明:
BitBake 的文档(源码目录/doc/*
)使用的也是 Sphinx 搭建的文档系统。Sphinx 也是基于 Python 的,使用的是 reStructuredText 语言格式,文件扩展名通常是 .rst
。在线文档则位于 Yocto Project 网站上:https://docs.yoctoproject.org/bitbake/index.html。
严格来说,BitBake 是一个可以独立使用的任务处理引擎。我们可以选择用在其他方面,但是一般没有人会选择这样做。目前,也没有见过在其他方面有使用 BitBake 的。下面是 BitBake 的简单使用目录结构及使用示例:
- 示例代码:
git clone https://gitee.com/itexp/bitbake-demo.git
init-env.sh
用于配置 BitBake 的工作环境,而helloworld/classes/base.bbclass
、helloworld/conf/*
、meta-*
是 BitBake 工作的基本文件
Metadata 是构建系统(BitBake)在构建过程中解析并使用的基本数据的统称。这些数据描述了如何构建一个 Linux 发行版。通常,Recipes、配置文件和其他引用构建指令本身的信息,以及用于控制构建内容和影响构建方式的数据都属于 Metadata。 OE-Core 和 meta-openembedded 就是一些 Metadata 的集合。
除了 BitBake 预定义的一些 Metadata 之外,Yocto Project 还额外扩展了一些。
Metadata 使用 BitBake DSL (Domain Specific Language) 来编写,其中包含变量和可执行的 shell 或 python 代码。该语法与其他几种语言具有相似之处,但也具有一些独特的功能。
配置文件主要用来控制构建过程,使用 .conf
作为扩展名,主要保存全局变量定义、用户定义变量和硬件配置信息等数据。这些文件大体可以分为 用户配置、发行版配置、机器配置、可能的编译器优化、通用配置 等这几大类。
- 配文件也可以有
xxx.inc
文件,然后在yyy.conf
中使用require xxx.inc
- 配置文件可以使用
require
关键字引用其他配置文件- 基本配置元数据是全局的,会影响所执行的所有 Recipes 和 Task。
- 配置文件中只允许定义变量以及使用 include 或 require 指令包含其他配置相关的文件
用户配置(User Configuration)主要是告诉 BitBake 要构建的镜像的目标架构,在哪里存储下载的源代码,以及其他构建属性。当我们执行 source oe-init-build-env
命令时,oe-init-build-env
会调用 scripts/oe-setup-builddir
这个脚本文件生成用户配置文件(
下的文件)。
/meta/conf/local.conf.sample
这个模板文件生成的;在 Poky 中,该文件是由 meta-poky/conf/local.conf.sample
这个模板文件生成的。 该文件主要包含以下内容:
MACHINE
variable.DL_DIR
variable.SSTATE_DIR
variable.TMPDIR
variable.DISTRO
variable.PACKAGE_CLASSES
variable.SDKMACHINE
variable.EXTRA_IMAGE_FEATURES
variable./meta/conf/site.conf.sample
这个模板创建该文件。autobuilder
创建和写入的,里面的内容与 local.conf 或者 site.conf 相同。bitbake
命令时,BitBake 在当前工作目录下寻找的第一个文件就是 conf/bblayers.conf
。 这个文件中有以下几个很重要的变量:
BBPATH
的值。conf/bblayers.conf
中 BBFILES
为空。conf/layer.conf
,其中配置一些当前路径、Recipes 文件路径等bblayers.conf
之后,BitBake 会在用户指定的 BBPATH
中找 conf/bitbake.conf
文件。该配置文件通常包含用于导入任何其他元数据的指令,比如特定于体系结构、计算机、本地环境等的文件。下图是个示例
- 通常这个文件就是
meta/conf/bitbake.conf
(Poky 和 OpenEmbedded-Core 一样)。- PN:Recipe 的名字。例如,对于
something_1.2.3.bb
,PN 就是something
。- PV:Recipe 的版本号。例如,对于
something_1.2.3.bb
,PN 就是1.2.3
。
这部分配置主要是指 BSP Layer 中的配置。提供特定于机器的配置。这种类型的信息是特定于特定目标体系结构的。例如,在 Poky 中的 meta-yocto-bsp/conf
下的配置文件。
这部分配置主要指的是 DISTRO Layer 中为特定分发版构建的镜像或 SDK 提供的顶级或通用的策略。例如,在 Poky 中的 meta-poky/conf/
下的配置文件。
通常,DISTRO Layer 下的
conf/distro/distro.conf
会覆盖 BitBake 源码目录下的conf/local.conf
中的相同配置。
Recipes 是最基本的 Metadata 形式,描述如何处理给定的应用程序,通常的文件命名规则为
。Recipes 包含了一系列的指令,描述了如何对给定的应用进行获取、打补丁、编译、安装和生成二进制包,它还定义了构建或者运行时所需要的依赖。
一个 Recipe 文件通常包含:name、license、dependencies、获取源码的地址以及真正可以被执行的函数(通常被称为任务)。
在解析配置文件时,BitBake 会拿到 BBFILES
这个变量的值。BitBake 使用它来构造要解析的 Recipes 列表,以及要应用的任何附加文件(.bbappend)。BitBake 解析每个 Recipe 和匹配的附加文件,并将各种变量的值存储到数据存储中。
对于每个 Recipe 文件,生成一个新的基本配置副本,然后逐行解析。如果文件中有 inherit xxxx
,则 BitBake 就会使用BBPATH
作为搜索路径来查找并解析 Class 文件(.bbclass)。最后,BitBake 按 BBFILES 中列出的追加文件(.bbappend)的顺序查找并解析相关追加文件。
在源码组织上,Recipe 就是那些 recipes-*
开头的文件夹中的内容。很多应用程序会有多个 Recipe 以支持不同的版本。在这种情况下,公共部分通常放在
文件中,而将专有部分放到
文件中(通过 require
引用)。
也可以使用
include
,区别在于 如果引用的文件不存在,include 指令不会报错,而 require 会报错。.inc
Class 文件包含在 Metadata 文件之间可以共用的信息,文件扩展名为 .bbclass
。Class 文件中的内容并没有特殊要求,所有可以用在 Recipe 中的内容都可以放到 Class 文件中。只要你自己觉得它不违背 Class 设计的目的就好。它基本就和我们的 C 库似的,提供一些公共功能,不需要我们每次自己重新写。
BitBake 工作必须要一个名为 ./classes/base.bbclass
的文件。这个 Class 文件比较特殊,它会默认被包含在所有的 Recipes 和 其他 Class 文件中,不需要显示的引用。这个类包含标准基本任务的定义,比如抓取、解包、配置(默认为空)、编译(运行当前的任何Makefile)、安装(默认为空)和打包(默认为空)。这些任务通常由项目开发过程中添加的其他类重写或扩展。
BitBake 源码目录下有这个文件
bitbake源码/conf/bitbake.conf
,我们可以拿过来使用
在源码组织上,Class 通常会单独放在所在 Layer下的一个名为 classes
的文件夹中,名字一般为 xxxx.bbclass
,其他 Recipe 文件中可以使用 inherit xxxx
(注意不需要扩展名 .bbclass
) 来引用其中的内容。
不同的 Layer 可以有自己的 Class,OpenEmbedded 的公共 Class 位于
.\openembedded-core\meta\classes
目录下
Layer 主要就是用来组织众多 Recipes 的。随着 Recipes 的增多,BitBake 引入了 Layer 的概念,以将 Recipes 按照不同的分类组织起来,不同的 Layer 之间相互独立。Layer 就是一些 Recipes 的集合。
Layer 可以在任何时候包含对先前指令或设置的更改。
常见的 Layer 有 BSP、GUI、distro configuration、middleware、application
在源码组织上,Layer 就是那些 meta-*
开头的文件夹(它有一个约定俗成的目录结构),其中包号一系列的 Recipes。在实际使用中,我们需要根据自己的需求,添加自己的 Layer。OpenEmbedded 官方维护的 meta-openembedded 就是一些 Layer 的集合。它主要用来扩展 OpenEmbedded-Core 的功能。
OpenEmbedded 官方还提供了一个列表,除了官方的之外里面还包括了很多第三方提供的 Layer。通常,meta-openembedded 是要与 Yocto 的固定版本绑定来一起使用的,否则可能会导致出错!例如,列表的官方仓库中除了主线分支,还有对应于 Yocto 每个版本的分支。使用是注意版本对应!
Yocto Project 官方维护了 meta-poky 和 meta-yocto-bsp 这两个 Layer(代码仓库是 https://git.yoctoproject.org/meta-yocto)。此外,他们也提供了一个包含一些第三方 Layer 的列表,不过,里面的内容相对来说少了太多了!
Append Files 用于向现有的 Recipe 添加或者扩展构建信息。通常文件使用扩展名 .bbappend
来表示。BitBake 要求每个 Append Files 都有一个相应的 Recipe 文件(文件名必须相同.bb
)。注意,Append Files 和相应的 Recipe 文件必须使用相同的根文件名。Append Files 文件中的内容会覆盖相应的 Recipe 文件中的同名的内容。
根据官方文档说明,文件名字可以使用 %
来作为通配符。例如,busybox_1.21.%.bbappend 将匹配 busybox_1.21.1.bb、 busybox_1.21.2.bb 等。但是,busybox_1.%.bbappend 与 busybox_1.3.0.bb 这种不能匹配。
用于创建最终镜像的构建系统的输出,通常就是由 Recipes 生成的编译后的二进制文件。唯一需要注意的是,在很久之前,Recipes 也被称为 Packages(变量 PR、PV、PE 中的 P 就是 Package 的首字母),但是这个概念现在已经不用了。
随 Image 同步产生的交叉编译工具链等被称为 SDK