原文:annas-archive.org/md5/973dcf79f7ad34e0b3ec1efedb2f8b67

译者:飞龙

协议:CC BY-NC-SA 4.0

第三章:软件层

在本章中,我们将介绍以下配方:

  • 探索镜像的内容

  • 添加新的软件层

  • 选择特定的包版本和提供者

  • 添加支持的包

  • 添加新包

  • 添加数据、脚本或配置文件

  • 管理用户和组

  • 使用 sysvinit 初始化系统

  • 使用 systemd 初始化系统

  • 安装包安装脚本

  • 减小 Linux 内核镜像大小

  • 减小根文件系统镜像大小

  • 发布软件

  • 分析系统合规性

  • 使用开源和专有代码

介绍

随着硬件特定更改的到来,下一步是定制目标根文件系统;即在 Linux 内核下运行的软件,也称为 Linux 用户空间。

通常的做法是从一个可用的核心镜像开始,然后根据嵌入式项目的需求进行优化和定制。通常选择的起始镜像是 core-image-minimalcore-image-sato,但任何一个都可以。

本章将展示如何添加软件层以包含这些更改,并解释一些常见的定制,如大小优化。还将展示如何向根文件系统添加新包,包括许可考虑事项。

探索镜像的内容

我们已经看到如何使用构建历史功能来获取包含在镜像中的包和文件列表。在这个配方中,我们将解释根文件系统是如何构建的,以便能够跟踪其组件。

准备工作

当包构建完成后,它们会根据架构在项目的工作目录(tmp/work)中进行分类。例如,在 wandboard-quad 构建中,我们会看到以下目录:

  • all-poky-linux:用于架构无关的包

  • cortexa9hf-vfp-neon-poky-linux-gnueabi:用于 cortexa9 硬浮动点包

  • wandboard_quad-poky-linux-gnueabi:用于机器特定包;在此情况下,是 wandboard-quad

  • x86_64-linux:用于构成主机 sysroot 的包

BitBake 将在其自己的目录中构建其依赖列表中的所有包。

如何做到…

要查找给定包的 build 目录,可以执行以下命令:

$ bitbake -e <package> | grep ^WORKDIR=

build 目录中,我们可以找到一些子目录(假设未使用 rm_work),这些子目录是构建系统在打包任务中使用的。这些子目录包括以下内容:

  • deploy-rpms:这是存储最终包的目录。我们可以在这里找到可以本地复制到目标并安装的单个包。这些包被复制到 tmp/deploy 目录,在 Yocto 构建根文件系统镜像时也会使用这些包。

  • image:这是默认的目标目录,do_install任务会将组件安装到该目录。它可以通过配方中的D配置变量进行修改。

  • package:这个包含了实际的包内容。

  • package-split:这里是根据最终的包名将内容分类到各个子目录中。配方可以根据PACKAGES变量指定将包内容拆分为多个最终包。除了默认包名称外,默认的包有:

    • dbg:安装用于调试的组件

    • dev:安装用于开发的组件,如头文件和库

    • staticdev:安装用于静态编译的库和头文件

    • doc:这是文档所在的目录

    • locale:安装本地化组件

每个包中要安装的组件由FILES变量选择。例如,要添加到默认包,可以执行以下命令:

FILES_${PN} += "${bindir}/file.bin"

如果要添加到开发包,可以使用以下命令:

FILES_${PN}-dev += "${libdir}/lib.so"

它是如何工作的……

一旦 Yocto 构建系统完成其依赖包列表中的所有单独包的构建,它会运行do_rootfs任务,填充sysroot并构建根文件系统,然后创建最终的包镜像。你可以通过执行以下命令找到根文件系统的位置:

$ bitbake -e core-image-minimal | grep ^IMAGE_ROOTFS=

请注意,IMAGE_ROOTFS变量不可配置,且不应更改。

该目录中的内容稍后将根据IMAGE_FSTYPES配置变量中配置的镜像类型准备成一个镜像。如果某些内容已安装在此目录中,它将被安装到最终镜像中。

添加新的软件层

根文件系统的定制涉及向基础镜像中添加或修改内容。与这些内容相关的元数据将进入一个或多个软件层,具体取决于定制的需求量。

一个典型的嵌入式项目通常只有一个包含所有非硬件特定定制的软件层。但也可以为图形框架或系统范围的元素添加额外的层。

准备工作

在开始工作之前,最好检查是否有其他人提供了类似的层。此外,如果你试图集成一个开源项目,检查是否已有现成的层。可以在layers.openembedded.org/找到可用层的索引。

如何操作……

然后,我们可以通过yocto-layer命令创建一个新的meta-custom层,正如我们在第二章的创建自定义 BSP 层配方中所学到的。从sources目录中执行以下命令:

$ yocto-layer create custom

别忘了将该层添加到项目的conf/bblayers.conf文件以及模板的conf目录中,以便在所有新项目中使用。

默认的conf/layer.conf配置文件如下所示:

# We have a conf and classes directory, add to BBPATH
BBPATH .= ":${LAYERDIR}"

# We have recipes-* directories, add to BBFILES
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
        ${LAYERDIR}/recipes-*/*/*.bbappend"

BBFILE_COLLECTIONS += "custom"
BBFILE_PATTERN_custom = "^${LAYERDIR}/"
BBFILE_PRIORITY_custom = "6"

我们在第二章的创建自定义 BSP 层配方中讨论了所有相关变量,BSP 层

它是如何工作的…

在向新软件层添加内容时,我们需要记住我们的层需要与 Yocto 项目中的其他层良好兼容。因此,在定制配方时,我们将始终使用附加文件,并且仅在完全确定无法通过附加文件添加所需定制时,才会重载现有配方。

为了帮助我们管理多个层中的内容,我们可以使用以下bitbake-layers命令行工具:

  • $ bitbake-layers show-layers:此命令将显示配置的层,按 BitBake 的视角展示。这有助于检测conf/bblayer.conf文件中的错误。

  • $ bitbake-layers show-recipes:此命令将显示所有可用的配方及提供它们的层。它可以用来验证 BitBake 是否识别了你新创建的配方。如果没有显示,请验证文件系统层级是否与层的BBFILES变量在conf/layer.conf中的定义一致。

  • $ bitbake-layers show-overlayed:此命令将显示所有被另一个具有相同名称但优先级更高层中的配方所覆盖的配方。它有助于检测配方冲突。

  • $ bitbake-layers show-appends:此命令将列出所有可用的附加文件及其应用到的配方文件。它可以用来验证 BitBake 是否识别了你的附加文件。此外,和配方一样,如果它们没有显示,你需要检查文件系统层级和你层中的BBFILES变量。

  • $ bitbake-layers flatten <output_dir>:此命令将创建一个目录,其中包含所有配置层的内容,且没有覆盖的配方,并应用所有附加文件。这是 BitBake 将看到的元数据。这个扁平化目录有助于发现与层的元数据冲突。

还有更多…

我们有时会添加特定于某个开发板或机器的定制内容。这些内容不总是与硬件相关,因此它们可能出现在 BSP 或软件层中。

在这样做时,我们会尽量保持我们的定制尽可能具体。一个典型的例子是针对特定机器或机器系列进行定制。如果你需要为wandboard-quad机器添加补丁,可以使用以下代码行:

SRC_URI_append_wandboard-quad = " file://mypatch.patch"

如果补丁适用于所有基于 i.MX6 的开发板,你可以使用以下命令:

SRC_URI_append_mx6 = " file://mypatch.patch"

要使用机器系列重载,机器配置文件需要包含一个SOC_FAMILY变量,例如在meta-fsl-arm-extra中用于wandboard-quad的配置。参考以下代码行:

conf/machine/wandboard-quad.conf:SOC_FAMILY = "mx6:mx6q:wandboard"

为了让它出现在MACHINEOVERRIDES变量中,需要包含soc-family.inc文件,正如在meta-fsl-arm中的配置。以下是conf/machine/include/imx-base.inc文件中的相关代码摘录:

include conf/machine/include/soc-family.inc
MACHINEOVERRIDES =. "${@['', '${SOC_FAMILY}:']['${SOC_FAMILY}' != '']}"

BitBake 将搜索预定义的路径,查找包工作目录内的文件,该目录由FILESPATH变量定义,作为以冒号分隔的列表。具体来说:

${PN}-${PV}/${DISTRO}
${PN}/${DISTRO}
files/${DISTRO}

${PN}-${PV}/${MACHINE}
${PN}/${MACHINE}
files/${MACHINE}

${PN}-${PV}/${SOC_FAMILY}
${PN}/${SOC_FAMILY}
files/${SOC_FAMILY}

${PN}-${PV}/${TARGET_ARCH}
${PN}/${TARGET_ARCH}
files/${TARGET_ARCH}

${PN}-${PV}/
${PN}/
files/

wandboard-quad的具体情况下,这将转换为以下内容:

${PN}-${PV}/poky
${PN}/poky
files/poky
${PN}-${PV}/wandboard-quad
${PN}/wandboard-quad
files/wandboard-quad
${PN}-${PV}/wandboard
${PN}/wandboard
files/wandboard
${PN}-${PV}/mx6q
${PN}/mx6q
files/mx6q
${PN}-${PV}/mx6
${PN}/mx6
files/mx6
${PN}-${PV}/armv7a
${PN}/armv7a
files/armv7a
${PN}-${PV}/arm
${PN}/arm
files/arm
${PN}-${PV}/
${PN}/
files/

在这里,PN是包名,PV是包版本。

最好将补丁放在这些层中最具体的地方,比如wandboard-quad,然后是wandboardmx6qmx6armv7aarm,最后是通用的PN-PVPNfiles

注意,搜索路径指的是 BitBake 配方的位置,因此在添加内容时,追加文件始终需要添加路径。如果需要,我们的追加文件可以通过以下方式在FILESEXTRAPATHS变量中追加或前置,来添加额外的文件夹到该搜索路径:

FILESEXTRAPATHS_prepend := "${THISDIR}/folder:"

注意

注意立即操作符(:=),它会立即扩展THISDIR,以及前置操作符,它将你添加的路径放在其他路径之前,这样你的补丁和文件可以首先在搜索中被找到。

此外,我们也看到在配置文件中使用+==+风格的操作符,但应避免在配方文件中使用,应该优先使用追加和前置操作符,如之前的示例代码所解释,以避免顺序问题。

选择特定的包版本和提供者

我们的层可以为同一包的不同版本提供配方。例如,meta-fsl-arm层包含多个不同类型的 Linux 源:

如前所述,所有配方默认提供包名(例如,linux-imxlinux-fslc),但所有 Linux 配方还必须提供virtual/kernel虚拟包。构建系统将根据构建的要求,如目标机器,解析virtual/kernel到最合适的 Linux 配方名称。

而在这些配方中,linux-imx例如,包含了 2.6.35.3 和 3.10.17 的配方版本。

在这个配方中,我们将展示如何告诉 Yocto 构建系统构建哪个特定的包和版本。

如何操作…

要指定我们要构建的精确包,构建系统允许我们指定要使用的提供者和版本。

我们如何选择使用哪个提供者?

我们可以通过使用PREFERRED_PROVIDER变量来告诉 BitBake 使用哪个配方。为了在我们的 Wandboard 机器上为virtual/kernel虚拟包设置首选提供者,我们需要在其机器配置文件中添加以下内容:

PREFERRED_PROVIDER_virtual/kernel = "linux-imx"

我们如何选择使用哪个版本?

在特定的提供者中,我们还可以告诉 BitBake 使用哪个版本,方法是使用 PREFERRED_VERSION 变量。例如,要为所有基于 i.MX6 的机器设置特定的 linux-imx 版本,我们将在 conf/local.conf 文件中添加以下内容:

PREFERRED_VERSION_linux-imx_mx6 = "3.10.17"

% 通配符被接受来匹配任何字符,正如我们在这里看到的:

PREFERRED_VERSION_linux-imx_mx6 = "3.10%"

然而,通常情况下,这种类型的配置会在机器配置文件中完成,在这种情况下,我们就不会使用 _mx6 的附加操作符了。

我们如何选择不使用哪个版本?

我们可以使用将 DEFAULT_PREFERENCE 变量设置为 -1 的方式来指定除非通过 PREFERRED_VERSION 变量显式设置,否则不使用某个版本。这在软件包的开发版本中很常见。

DEFAULT_PREFERENCE = "-1"

添加支持的包

通常,我们希望向已经在包含的 Yocto 层中有现成配方的镜像中添加新软件包。

当目标镜像与提供的核心镜像差异较大时,建议定义一个新的镜像,而不是定制现有的镜像。

这个配方将展示如何通过向现有镜像中添加支持的包来定制它,但如果需要,也可以创建一个全新的镜像配方。

准备工作

为了发现我们需要的软件包是否包含在我们配置的层中,以及支持哪些特定版本,我们可以使用来自构建目录的 bitbake-layers,正如我们之前所见:

$ bitbake-layers show-recipes | grep -A 1 htop
htop:
 meta-oe              1.0.3

或者,我们也可以像下面这样使用 BitBake:

$ bitbake -s | grep htop
htop                                                :1.0.3-r0

或者,我们可以在 sources 目录中使用 find Linux 命令:

$ find . -type f -name "htop*.bb"
./meta-openembedded/meta-oe/recipes-support/htop/htop_1.0.3.bb

一旦我们知道要包含哪些软件包在最终的镜像中,接下来我们来看看如何将它们添加到镜像中。

如何做到这一点…

在开发过程中,我们将使用我们项目的 conf/local.conf 文件来添加自定义内容。要向所有镜像中添加软件包,我们可以使用以下代码行:

IMAGE_INSTALL_append = " htop"

注意

请注意,第一个引号后有一个空格,用于将新包与现有包分开,因为附加操作符不会自动添加空格。

我们还可以通过以下方式将添加限制到特定镜像:

IMAGE_INSTALL_append_pn-core-image-minimal = " htop"

另一种简单的定制方式是通过利用 特性。特性是软件包的逻辑分组。例如,我们可以创建一个新的特性叫做 debug-utils,它将添加一整套调试工具。我们可以在配置文件或类中按如下方式定义我们的特性:

FEATURE_PACKAGES_debug-utils = "strace perf"

然后我们可以通过在 conf/local.conf 文件中添加 EXTRA_IMAGE_FEATURES 变量来将此特性添加到我们的镜像中,如下所示:

EXTRA_IMAGE_FEATURES += "debug-utils"

如果你将其添加到镜像配方中,你将使用 IMAGE_FEATURES 变量。

通常,特性作为 packagegroup 配方被添加,而不是单独列出作为软件包。让我们展示如何在 recipes-core/packagegroups/packagegroup-debug-utils.bb 文件中定义一个 packagegroup 配方:

SUMMARY = "Debug applications packagegroup"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://${COREBASE}/LICENSE;md5=3f40d7994397109285ec7b81fdeb3b58"

inherit packagegroup

RDEPENDS_${PN} = "\
    strace \
    perf \
"

然后你将它添加到 FEATURE_PACKAGES 变量中,如下所示:

FEATURE_PACKAGES_debug-utils = "packagegroup-debug-utils"

我们可以使用packagegroups来创建更复杂的示例。有关详细信息,请参考Yocto 项目开发手册www.yoctoproject.org/docs/1.7.1/dev-manual/dev-manual.html

它是如何工作的…

自定义镜像的最佳方法是使用现有镜像作为模板来创建我们自己的镜像。我们可以使用core-image-minimal.bb,它包含以下代码:

SUMMARY = "A small image just capable of allowing a device to boot."

IMAGE_INSTALL = "packagegroup-core-boot ${ROOTFS_PKGMANAGE_BOOTSTRAP} ${CORE_IMAGE_EXTRA_INSTALL}"

IMAGE_LINGUAS = " "

LICENSE = "MIT"

inherit core-image

IMAGE_ROOTFS_SIZE ?= "8192"

然后扩展为我们自己的版本,以允许通过添加以下meta-custom/recipes-core/images/custom-image.bb镜像文件来自定义IMAGE_FEATURES

require recipes-core/images/core-image-minimal.bb
IMAGE_FEATURES += "ssh-server-dropbear package-management"

当然,我们也可以使用现有的镜像作为模板,从头定义一个新的镜像。

还有更多…

自定义镜像的最终方法是通过添加 shell 函数,这些函数在镜像创建后执行。你可以通过在镜像配方或conf/local.conf文件中添加以下内容来实现:

ROOTFS_POSTPROCESS_COMMAND += "function1;...;functionN"

你可以在命令中使用IMAGE_ROOTFS变量来指定根文件系统的路径。

类别将使用IMAGE_POSTPROCESS_COMMAND变量,而不是ROOTFS_POSTPROCESS_COMMAND

一个用例示例可以在image.bbclass中的debug-tweaks功能中找到,当镜像被调整以允许无密码 root 登录时。这个方法也通常用于自定义目标镜像的 root 密码。

配置包

正如我们在第二章的配置 Linux 内核一节中看到的那样,BSP 层,一些包(比如 Linux 内核)提供了一个配置菜单,可以通过menuconfig BitBake 命令进行配置。

另一个值得提到的、带有配置界面的包是 BusyBox。我们将展示如何配置 BusyBox,例如添加pgrep,这是一个通过名称查找进程 ID 的工具。具体步骤如下:

  1. 配置 BusyBox:

    $ bitbake -c menuconfig busybox
    
    
  2. 进程工具中选择pgrep

  3. 编译 BusyBox:

    $ bitbake -C compile busybox
    
    
  4. 将 RPM 包复制到目标:

    $ bitbake -e busybox | grep ^WORKDIR=
    $ scp ${WORKDIR}/deploy-rpms/cortexa9hf_vfp_neon/busybox- 1.22.1-r32.cortexa9hf_vfp_neon.rpm root@<target_ip>:/tmp
    
    
  5. 在目标上安装 RPM 包:

    # rpm --force -U /tmp/busybox-1.22.1- r32.cortexa9hf_vfp_neon.rpm
    
    

    请注意,由于配置更改后包的版本没有增加,我们强制更新。

添加新包

我们已经看到如何自定义我们的镜像,以便我们可以向其中添加支持的包。当我们找不到现有的配方,或者需要集成我们自己开发的新软件时,我们将需要创建一个新的 Yocto 配方。

准备就绪

在开始编写新配方之前,我们需要问自己一些问题:

  • 源代码存储在哪里?

  • 它是源代码控制的,还是以 tarball 形式发布的?

  • 源代码许可证是什么?

  • 它使用什么构建系统?

  • 它需要配置吗?

  • 我们能否按原样交叉编译,还是需要打补丁?

  • 需要部署到根文件系统的文件有哪些,它们应该放在哪里?

  • 是否需要进行任何系统更改,比如新增用户或init脚本?

  • 是否有任何依赖项需要提前安装到sysroot中?

一旦我们知道了这些问题的答案,就可以开始编写我们的食谱了。

如何操作…

最好从如下的空白模板开始,而不是从一个类似的食谱开始并修改它,因为这样做的结果会更加干净,并且仅包含严格需要的指令。

添加最小食谱的一个良好起点是:

SUMMARY = "The package description for the package management system"

LICENSE = "The package's licenses typically from meta/files/common-licenses/"
LIC_FILES_CHKSUM = "License checksum used to track open license changes"
DEPENDS = "Package list of build time dependencies"

SRC_URI = "Local or remote file or repository to fetch"
SRC_URI[md5sum] = "md5 checksums for all remote fetched files (not for repositories)"
SRC_URI[sha256sum] = "sha256 checksum for all remote fetched files (not for repositories)"

S = "Location of the source in the working directory, by default ${WORKDIR}/${PN}-${PV}."

inherit <class needed for some functionality>

# Task overrides, like do_configure, do_compile and do_install, or nothing.

# Package splitting (if needed).

# Machine selection variables (if needed).

它是如何工作的…

我们将在接下来的部分中更详细地解释每个食谱部分。

包许可证

每个食谱都需要包含LICENSE变量。LICENSE变量允许你指定多个、替代的以及按包类型的许可证,如下例所示:

  • 对于 MIT 或 GPLv2 替代许可证,我们将使用:

    LICENSE = "GPL-2.0 | MIT"
    
  • 对于 ISC 和 MIT 许可证,我们将使用:

    LICENSE = "ISC & MIT"
    
  • 对于拆分包,所有包都是 GPLv2,除了文档部分,它是由知识共享协议保护的,我们将使用:

    LICENSE_${PN} = "GPLv2"
    LICENSE_${PN}-dev = "GPLv2"
    LICENSE_${PN}-dbg = "GPLv2"
    LICENSE_${PN}-doc = "CC-BY-2.0"
    

开源包通常会在源代码中包含许可证,如READMECOPYINGLICENSE文件,甚至在源代码头文件中。

对于开源许可证,我们还需要为所有许可证指定LIC_FILES_CHECKSUM,以便构建系统在许可证发生变化时通知我们。要添加它,我们定位包含许可证的文件或文件部分,并提供其相对于源目录的路径以及其 MD5 校验和。例如:

LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common- licenses/GPL-2.0;md5=801f80980d171dd6425610833a22dbe6"
LIC_FILES_CHKSUM = "file://COPYING;md5=f7bdc0c63080175d1667091b864cb12c"
LIC_FILES_CHKSUM = "file://usr/include/head.h;endline=7;md5=861ebad4adc7236f8d1905338 abd7eb2"
LIC_FILES_CHKSUM = "file://src/file.c;beginline=5;endline=13;md5=6c7486b21a8524b1879f a159578da31e"

专有代码的许可证应设置为CLOSED,不需要为其提供LIC_FILES_CHECKSUM

获取包内容

SRC_URI变量列出了要获取的文件。构建系统将根据文件前缀使用不同的获取器。这些获取器可以是:

  • 与元数据一起包含的本地文件(file://)。如果本地文件是补丁,SRC_URI变量可以通过补丁特定的参数扩展,如下所示:

    • striplevel:默认的补丁去除级别是 1,但可以通过此参数进行修改

    • patchdir:此参数指定应用补丁的目录位置,默认值为源目录

    • apply:此参数控制是否应用补丁,默认情况下会应用补丁

  • 存储在远程服务器上的文件(通常为http(s)://ftp://ssh://)。

  • 存储在远程仓库中的文件(通常为git://svn://hg://bzr://)。这些文件也需要一个SRCREV变量来指定修订版本。

存储在远程服务器上的文件(不是本地文件或远程仓库)需要指定两个校验和。如果有多个文件,可以通过name参数来区分;例如:

SRCREV = "04024dea2674861fcf13582a77b58130c67fccd8"
SRC_URI = "git://repo.com/git/ \
           file://fix.patch;name=patch \
           http://example.org/archive.data;name=archive"
SRC_URI[archive.md5sum] = "aaf32bde135cf3815aa3221726bad71e"
SRC_URI[archive.sha256sum] = "65be91591546ef6fdfec93a71979b2b108eee25edbc20c53190caafc9a92d4e7"

源目录文件夹S指定了源文件的位置。仓库将在此处检出,或者 tarball 会解压到此位置。如果 tarball 解压到标准的${PN}-${PV}位置,则可以省略,因为这是默认值。对于仓库,必须始终指定;例如:

S = "${WORKDIR}/git"

指定任务覆盖

所有食谱都继承了base.bbclass类,该类定义了以下任务:

  • do_fetch:该方法通过SRC_URI变量选择获取器,获取源代码。

  • do_unpack:该方法将代码解包到由S变量指定的位置。

  • do_configure:该方法根据需要配置源代码。默认情况下不执行任何操作。

  • do_compile:该方法默认编译源代码并运行 GNU make 目标。

  • do_install:该方法将构建结果从build目录B复制到目标目录D。默认情况下不执行任何操作。

  • do_package:该方法将交付物拆分成多个包。默认情况下不执行任何操作。

通常,仅重写配置、编译和安装任务,并且大多数情况下是通过继承autotools类隐式完成的。

对于不使用构建系统的自定义配方,您需要在相应的do_configuredo_compiledo_install重写方法中提供所需的配置(如果有)、编译和安装指令。以下是这种类型配方的示例,位于meta-custom/recipes-example/helloworld/helloworld_1.0.bb

DESCRIPTION = "Simple helloworld application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://helloworld.c"

S = "${WORKDIR}"

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

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

meta-custom/recipes-example/helloworld/helloworld-1.0/helloworld.c源文件如下:

#include <stdio.h>

int main(void)
{
    return printf("Hello World");
}

我们将在下一章中看到使用最常见构建系统的示例配方。

配置包

Yocto 构建系统提供了PACKAGECONFIG变量,用于通过定义多个特性来帮助配置包。您的配方通过以下方式定义单个特性:

PACKAGECONFIG ??= "feature"
PACKAGECONFIG[feature] = "--with-feature,--without-feature,build- deps-feature,rt-deps-feature"

PACKAGECONFIG变量包含一个以空格分隔的特性名称列表,并且可以在bbappend文件中进行扩展或重写;请查看以下示例:

PACKAGECONFIG_append = " feature1 feature2"

要从发行版或本地配置文件扩展或重写它,您需要使用以下语法:

PACKAGECONFIG_pn-<package_name> = "feature1 feature2"
PACKAGECONFIG_append_pn-<package_name> = " feature1 feature2"

接下来,我们使用四个有序参数来描述每个特性:

  • 启用特性时的额外配置参数(对于EXTRA_OECONF

  • 启用特性时的额外配置参数(对于EXTRA_OECONF

  • 启用特性时的额外构建依赖(对于DEPENDS

  • 启用特性时的额外运行时依赖(对于RDEPENDS

这四个参数是可选的,但必须保持其顺序,并且需要保留分隔的逗号。

例如,wpa-supplicant配方定义了两个特性,gnutlsopenssl,但默认仅启用gnutls,如下所示:

PACKAGECONFIG ??= "gnutls"
PACKAGECONFIG[gnutls] = ",,gnutls"
PACKAGECONFIG[openssl] = ",,openssl"

拆分成多个包

将配方内容分成不同的包以满足不同的需求是常见做法。典型的例子是将文档包含在doc包中,将头文件和/或库包含在dev包中。我们可以通过使用FILES变量来实现这一点,示例如下:

FILES_${PN} += "List of files to include in the main package"
FILES_${PN}-dbg += "Optional list of files to include in the debug package"
FILES_${PN}-dev += "Optional list of files to include in the development package"
FILES_${PN}-doc += "Optional list of files to include in the documentation package"

设置特定于机器的变量

每个配方都有一个PACKAGE_ARCH变量,用于将配方分类到一个软件包源中,正如我们在探索镜像内容的配方中看到的那样。大多数时候,它们由 Yocto 构建系统自动分类。例如,如果配方是内核、内核模块配方、镜像配方,或者是交叉编译或构建本地应用程序,Yocto 构建系统将相应地设置软件包架构。

BitBake 还会查看SRC_URI的机器覆盖并调整软件包架构,如果你的配方使用allarch类,它将把软件包架构设置为all

因此,在处理仅适用于特定机器或机器系列的配方时,或者涉及特定机器或机器系列的更改时,我们需要检查软件包是否被分类到适当的软件包源中。如果没有,则需要通过在配方中明确指定以下代码行来指定软件包架构:

PACKAGE_ARCH = "${MACHINE_ARCH}"

此外,当一个配方仅用于特定的机器类型时,我们使用COMPATIBLE_MACHINE变量来指定。例如,为了让它仅兼容mxs、mx5 和 mx6 SoC 系列,我们会使用以下内容:

COMPATIBLE_MACHINE = "(mxs|mx5|mx6)"

添加数据、脚本或配置文件

所有配方都继承了基类,并执行默认的任务集合。继承基类后,配方知道如何执行诸如获取和编译之类的任务。

由于大多数配方旨在安装某种可执行文件,基类知道如何构建它。但有时我们所需要的仅仅是将数据、脚本或配置文件安装到文件系统中。

如果数据或配置与应用程序相关,最合逻辑的做法是将其与应用程序的配方本身打包在一起,如果我们认为分开安装更好,甚至可以将其拆分成单独的包。

但有时,数据或配置与应用程序无关,可能适用于整个系统,或者我们只想为其提供一个单独的配方。根据需要,我们甚至可能希望安装一些不需要编译的 Perl 或 Python 脚本。

如何操作…

在这些情况下,我们的配方应该继承allarch类,该类被那些不产生特定架构输出的配方继承。

这种类型的配方示例meta-custom/recipes-example/example-data/example-data_1.0.bb可以在这里查看:

DESCRIPTION = "Example of data or configuration recipe"
SECTION = "examples"

LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0;md5=801f80980d171dd6425610833a22dbe6"

SRCREV = "${AUTOREV}"
SRC_URI = "git://github.com/yoctocookbook/examples.git \
           file://example.data"

S = "${WORKDIR}/git"

inherit allarch

do_compile() {
}

do_install() {
        install -d ${D}${sysconfdir}
        install -d ${D}${sbindir}
        install -m 0755 ${WORKDIR}/example.data ${D}/${sysconfdir}/
        install -m 0755 ${S}/python-scripts/* ${D}/${sbindir}
}

它假设虚构的examples.git仓库包含一个我们想要包括在根文件系统中的python-scripts文件夹。

一个可用的配方示例可以在与本书附带的源代码中找到。

管理用户和组

在很多情况下,我们还需要向文件系统中添加或修改用户和组。这个配方解释了如何做到这一点。

准备工作

用户信息存储在/etc/passwd文件中,这是一个文本文件,用作系统用户信息的数据库。passwd文件是人类可读的。

每一行对应系统中的一个用户,格式如下:

<username>:<password>:<uid>:<gid>:<comment>:<home directory>:<login shell>

让我们看看这种格式的每个参数:

  • username:一个唯一的字符串,用于标识用户在登录时的身份

  • uid:用户 ID,Linux 用来识别用户的数字

  • gid:组 ID,Linux 用来识别用户主组的数字

  • comment:以逗号分隔的值,用来描述账户,通常是用户的联系方式

  • home directory:用户主目录的路径

  • login shell:为交互式登录启动的 Shell

默认的passwd文件存储在base-passwd包中,格式如下:

root::0:0:root:/root:/bin/sh
daemon:*:1:1:daemon:/usr/sbin:/bin/sh
bin:*:2:2:bin:/bin:/bin/sh
sys:*:3:3:sys:/dev:/bin/sh
sync:*:4:65534:sync:/bin:/bin/sync
games:*:5:60:games:/usr/games:/bin/sh
man:*:6:12:man:/var/cache/man:/bin/sh
lp:*:7:7:lp:/var/spool/lpd:/bin/sh
mail:*:8:8:mail:/var/mail:/bin/sh
news:*:9:9:news:/var/spool/news:/bin/sh
uucp:*:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:*:13:13:proxy:/bin:/bin/sh
www-data:*:33:33:www-data:/var/www:/bin/sh
backup:*:34:34:backup:/var/backups:/bin/sh
list:*:38:38:Mailing List Manager:/var/list:/bin/sh
irc:*:39:39:ircd:/var/run/ircd:/bin/sh
gnats:*:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:*:65534:65534:nobody:/nonexistent:/bin/sh

所有账户的直接登录功能都被禁用,密码字段上会显示一个星号,除 root 用户外,root 没有密码。这是因为默认情况下,镜像是通过启用debug-tweaks功能构建的,该功能启用了 root 用户的无密码登录功能。若启用 root 密码,则会显示加密后的 root 密码。

注意

不要忘记从生产镜像中移除debug-tweaks功能。

在相同时间安装的/etc/group文件包含系统组的信息。

core-image-minimal镜像不包括影子密码保护,但其他镜像,如core-image-full-cmdline,是包括的。启用时,所有密码字段会包含一个* x *,加密后的密码存储在/etc/shadow文件中,该文件只有超级用户才能访问。

系统所需的任何用户,如果不在我们之前看到的列表中,也需要被创建。

如何操作……

食谱添加或修改系统用户或组的标准方法是使用useradd类,该类使用以下变量:

  • USERADD_PACKAGES:此变量指定食谱中需要添加用户或组的独立包。对于主包,您可以使用以下内容:

    USERADD_PACKAGES = "${PN}"
    
  • USERADD_PARAM:此变量对应传递给 Linux useradd命令的参数,用于向系统添加新用户。

  • GROUPADD_PARAM:此变量对应传递给 Linux groupadd命令的参数,用于向系统添加新组。

  • GROUPMEMS_PARAM:此变量对应传递给 Linux groupmems命令的参数,用于管理用户主组的成员。

以下是使用useradd类的食谱示例片段:

inherit useradd

PASSWORD ?= "miDBHFo2hJSAA"
USERADD_PACKAGES = "${PN}"
USERADD_PARAM_${PN} = "--system --create-home \
                       --groups tty \
                       --password ${PASSWORD} \
                       --user-group ${PN}"

密码可以通过在主机上使用mkpasswd Linux 命令行工具生成,该工具随着whois Ubuntu 包一起安装。

还有更多……

在使用useradd类生成用户和组时,uidgid值会在包安装过程中动态分配。如果不希望这样,可以通过提供自己的passwd和组文件来分配全局静态的uidgid值。

要做到这一点,你需要在conf/local.conf文件中定义USERADDEXTENSION变量,如下所示:

USERADDEXTENSION = "useradd-staticids"

构建系统将搜索 BBPATH 变量以获取 files/passwdfiles/group 文件,以获取 uidgid 值。这些文件具有先前定义的标准 passwd 布局,密码字段被忽略。

可以使用 USERADD_UID_TABLESUSERADD_GID_TABLES 变量重写默认文件名。

您还需要定义以下内容:

USERADD_ERROR_DYNAMIC = "1"

这样做是为了当提供的文件中找不到所需的 uidgid 值时,构建系统产生错误。

注意

请注意,如果在已构建的项目中使用 useradd 类,您需要删除 tmp 目录并从 sstate-cache 目录重新构建,否则将出现构建错误。

还有一种方法可以添加与特定配方无关但与映像相关的用户和组信息 - 使用 extrausers 类。它通过映像配方中的 EXTRA_USERS_PARAMS 变量配置,并如下使用:

inherit extrausers

EXTRA_USERS_PARAMS = "\
  useradd -P password root; \
  "

这将 root 密码设置为 password

使用 sysvinit 初始化管理器

初始化管理器是根文件系统的重要部分。这是内核执行的第一件事,并且有责任启动系统的其余部分。

该配方将介绍 sysvinit 初始化管理器。

准备工作

这是 Yocto 中的默认初始化管理器,并且自操作系统起源以来一直在 Linux 中使用。内核传递一个 init 命令行参数,通常为 /sbin/init,然后启动它。此 init 进程具有 PID 1 并且是所有进程的父进程。init 进程可以由 BusyBox 实现,也可以是与 sysvinit 包独立安装的独立程序。它们都以相同的方式工作,基于 运行级别 的概念,即定义要运行哪些进程的机器状态。

init 进程将读取一个 inittab 文件,并查找默认运行级别。默认的 inittab 文件安装在 sysvinit-inittab 包中,并且如下所示:

# /etc/inittab: init(8) configuration.
# $Id: inittab,v 1.91 2002/01/25 13:35:21 miquels Exp $

# The default runlevel.
id:5:initdefault:

# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS

# What to do in single-user mode.
~~:S:wait:/sbin/sulogin

# /etc/init.d executes the S and K scripts upon change
# of runlevel.
#
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.

l0:0:wait:/etc/init.d/rc 0
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5
l6:6:wait:/etc/init.d/rc 6
# Normally not reached, but fallthrough in case of emergency.
z6:6:respawn:/sbin/sulogin

然后,init 运行 /etc/rcS.d 目录中以 S 开头的所有脚本,然后运行 /etc/rcN.d 目录中以 S 开头的所有脚本,其中 N 是运行级别值。

因此,init 进程只执行初始化并忽略进程。如果发生故障并且进程被终止,没有人会关心。如果系统不响应,系统看门狗将重新启动系统,但通常需要具有某种类型进程监视器以响应系统健康的应用程序,但 sysvinit 不提供这些类型的机制。

然而,sysvinit 是一个被充分理解和可靠的初始化管理器,建议保留,除非需要一些额外的功能。

如何做到…

在使用 sysvinit 作为初始化管理器时,Yocto 提供 update-rc.d 类作为辅助工具,安装初始化脚本,以便在需要时启动和停止它们。

使用此类时,你需要指定INITSCRIPT_NAME变量,该变量指定要安装的脚本名称,INITSCRIPT_PARAMS指定传递给update-rc.d工具的选项。你可以选择性地使用INITSCRIPT_PACKAGES变量列出包含初始化脚本的包。默认情况下,这只包含主包,如果提供多个包,则需要为每个包指定INITSCRIPT_NAMEINITSCRIPT_PARAMS,并使用覆盖方式。以下是一个示例片段:

INITSCRIPT_PACKAGES = "${PN}-httpd ${PN}-ftpd"
INITSCRIPT_NAME_${PN}-httpd = "httpd.sh"
INITSCRIPT_NAME_${PN}-ftpd = "ftpd.sh"
INITSCRIPT_PARAMS_${PN}-httpd = "defaults"
INITSCRIPT_PARAMS_${PN}-ftpd = "start 99 5 2 . stop 20 0 1 6 ."

当初始化脚本未绑定到特定的食谱时,我们可以为其添加一个特定的食谱。例如,以下食谱将在recipes-example/sysvinit-mount/sysvinit-mount_1.0.bb文件中运行mount.sh脚本。

DESCRIPTION = "Initscripts for mounting filesystems"
LICENSE = "MIT"

LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "file://mount.sh"

INITSCRIPT_NAME = "mount.sh"
INITSCRIPT_PARAMS = "start 09 S ."

inherit update-rc.d

S = "${WORKDIR}"

do_install () {
    install -d ${D}${sysconfdir}/init.d/
    install -c -m 755 ${WORKDIR}/${INITSCRIPT_NAME} ${D}${sysconfdir}/init.d/${INITSCRIPT_NAME}
}

使用systemd初始化管理器

作为sysvinit的替代方案,你可以配置项目使用systemd作为初始化管理器,尽管systemd包含更多功能。

准备工作

systemd初始化管理器正在取代大多数 Linux 发行版中的sysvinit和其他初始化管理器。它基于单元的概念,单元是所有与系统启动和维护相关的元素的抽象,而目标是将单元分组,并且可以视为运行级别的等价物。systemd定义的一些单元包括:

  • 服务

  • 套接字

  • 设备

  • 挂载点

  • 快照

  • 定时器

  • 路径

默认目标及其对应的运行级别在下表中定义:

Sysvinit 运行级别 Systemd 目标 备注
0 runlevel0.target poweroff.target 关闭系统。
1, s, single runlevel1.target rescue.target 单用户模式。
2, 4 runlevel2.target, runlevel4.target multi-user.target 用户定义的/特定站点的运行级别。默认情况下,与3相同。
3 runlevel3.target multi-user.target 多用户、非图形模式。用户通常可以通过多个控制台或网络登录。
5 runlevel5.target graphical.target 多用户、图形模式。通常包括运行级别 3 的所有服务以及图形登录。
6 runlevel6.target reboot.target 重启系统。

systemd初始化管理器设计上与sysvinit兼容,包括使用sysvinit init脚本。

systemd的一些特点包括:

  • 允许更快启动时间的并行化功能

  • 通过套接字和 D-Bus 进行服务初始化,以便仅在需要时启动服务

  • 允许进程失败恢复的进程监控

  • 系统状态快照和恢复

  • 挂载点管理

  • 基于事务依赖的单元控制,其中单元之间建立了依赖关系

如何操作…

要配置你的系统使用systemd,你需要通过将以下内容添加到分发配置文件中,在poky分发的默认配置文件 sources/poky/meta-yocto/conf/distro/poky.conf中,或者在你项目的conf/local.conf文件中,来将systemd分发功能添加到项目中:

DISTRO_FEATURES_append = " systemd"

注意

注意开始引号后的空格。

VIRTUAL-RUNTIME_init_manager = "systemd"

此配置示例允许你定义一个带有systemd的主镜像和一个带有sysvinit的救援镜像,前提是它不使用VIRTUAL-RUNTIME_init_manager变量。因此,救援镜像不能使用packagegroup-core-bootpackagegroup-core-full-cmdline食谱。例如,本章稍后将介绍的减少根文件系统镜像大小食谱中的镜像大小已被减小,可以作为救援镜像的基础。

要完全从系统中删除sysvinit,你需要执行以下操作:

DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

功能回填是自动扩展机器和分发功能,以保持向后兼容性。sysvinit分发功能会自动回填,因此,要删除它,我们需要将其添加到DISTRO_FEATURES_BACKFILL_CONSIDERED变量中,如前所述。

提示

请注意,如果你正在使用现有项目,并且按照前文所述更改了DISTRO_FEATURES变量,你需要删除tmp目录并使用 sstate-cache重新构建,否则构建将失败。

还有更多…

不仅根文件系统需要配置,Linux 内核也需要特别配置,以满足systemd所需的所有功能。systemd源代码 README 文件中有一份详细的内核配置变量列表。举例来说,要扩展我们将在本章稍后介绍的减少 Linux 内核镜像大小食谱中的最小内核配置,以支持 Wandboard 的systemd,我们需要在arch/arm/configs/wandboard-quad_minimal_defconfig文件中添加以下配置更改:

+CONFIG_FHANDLE=y
+CONFIG_CGROUPS=y
+CONFIG_SECCOMP=y
+CONFIG_NET=y
+CONFIG_UNIX=y
+CONFIG_INET=y
+CONFIG_AUTOFS4_FS=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_SCHEDSTATS=y

为 Wandboard 提供的默认内核配置将正常启动一个带有systemdcore-image-minimal镜像。

安装 systemd 单元文件

Yocto 提供了systemd类作为辅助工具来安装单元文件。默认情况下,单元文件会安装在目标目录的${systemd_unitdir}/system路径下。

使用此类时,需要指定SYSTEMD_SERVICE_${PN}变量,值为要安装的单元文件的名称。你也可以选择使用SYSTEMD_PACKAGES变量来列出包含单元文件的包。默认情况下,这只包含主包,如果提供多个包,则需要使用覆盖方式指定SYSTEMD_SERVICE变量。

服务默认配置为在启动时自动启动,但你可以通过SYSTEMD_AUTO_ENABLE变量来更改此设置。

以下是一个示例代码片段:

SYSTEMD_PACKAGES = "${PN}-syslog"
SYSTEMD_SERVICE_${PN}-syslog = "busybox-syslog.service"
SYSTEMD_AUTO_ENABLE = "disabled"

安装包安装脚本

支持的包格式(RPM、ipk 和 deb)支持在包安装过程中添加安装脚本,这些脚本可以在不同的时间运行。在本配方中,我们将看到如何安装这些脚本。

准备工作

有不同类型的安装脚本:

  • 预安装脚本 (pkg_preinst):这些脚本在软件包解包之前调用。

  • 后安装脚本 (pkg_postinst):这些脚本在软件包解包后调用,并且会配置依赖关系。

  • 卸载前脚本 (pkg_prerm):这些脚本在已安装或至少部分安装的软件包时调用。

  • 卸载后脚本 (pkg_postrm):这些脚本在软件包的文件被移除或替换之后调用。

如何实现……

以下是一个在配方中安装预安装脚本的示例片段:

     pkg_preinst_${PN} () {
         # Shell commands
     }

所有安装脚本的工作方式相同,不同之处在于,后安装脚本可以在主机上创建根文件系统镜像时、在目标设备上(对于无法在主机上执行的操作),或者在软件包直接安装到目标设备时运行。请查看以下代码:

 pkg_postinst_${PN} () {
     if [ x"$D" = "x" ]; then
          # Commands to execute on device
     else
          # Commands to execute on host
     fi
 }

如果后安装脚本成功,软件包将被标记为已安装。如果脚本失败,软件包将被标记为未解包,脚本将在镜像再次启动时执行。

它是如何工作的……

一旦配方定义了安装脚本,特定包类型的类将在遵循特定格式的打包规则的同时安装该脚本。

对于后安装脚本,当在主机上运行时,D 被设置为目标目录,因此比较测试将失败。但当在目标设备上运行时,D 将为空。

注意

如果可能,建议在主机上执行后安装脚本,因为我们需要考虑到某些根文件系统将是只读的,因此无法在目标设备上执行某些操作。

减少 Linux 内核镜像大小

在根文件系统定制之前或与之并行,嵌入式项目通常需要进行镜像大小优化,以减少启动时间和内存使用。

更小的镜像意味着更少的存储空间、传输时间和编程时间,从而在制造和现场更新中节省成本。

默认情况下,wandboard-quad 的压缩 Linux 内核镜像(zImage)大约为 5.2 MB。这个配方将展示我们如何减少该镜像的大小。

如何实现……

一个能够从 microSD 卡根文件系统启动的 Wandboard 的最小内核配置示例是以下的 arch/arm/configs/wandboard-quad_minimal_defconfig 文件:

CONFIG_KERNEL_XZ=y
CONFIG_NO_HZ=y
CONFIG_HIGH_RES_TIMERS=y
CONFIG_BLK_DEV_INITRD=y
CONFIG_CC_OPTIMIZE_FOR_SIZE=y
CONFIG_EMBEDDED=y
CONFIG_SLOB=y
CONFIG_ARCH_MXC=y
CONFIG_SOC_IMX6Q=y
CONFIG_SOC_IMX6SL=y
CONFIG_SMP=y
CONFIG_VMSPLIT_2G=y
CONFIG_AEABI=y
CONFIG_CPU_FREQ=y
CONFIG_ARM_IMX6_CPUFREQ=y
CONFIG_CPU_IDLE=y
CONFIG_VFP=y
CONFIG_NEON=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_PROC_DEVICETREE=y
CONFIG_SERIAL_IMX=y
CONFIG_SERIAL_IMX_CONSOLE=y
CONFIG_REGULATOR=y
CONFIG_REGULATOR_ANATOP=y
CONFIG_MMC=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_MMC_SDHCI_ESDHC_IMX=y
CONFIG_DMADEVICES=y
CONFIG_IMX_SDMA=y
CONFIG_EXT3_FS=y

该配置生成一个 886 KB 压缩的 Linux 内核镜像(zImage)。

它是如何工作的……

除了硬件设计考虑因素(如从 NOR 闪存运行 Linux 内核,并使用就地执行 (XIP) 来避免将镜像加载到内存中),内核大小优化的第一步是审查内核配置并移除所有多余的功能。

要分析内核块的大小,我们可以使用:

$ size vmlinux */built-in.o
text    data     bss     dec     hex filename
8746205  356560  394484 9497249  90eaa1 vmlinux
117253    2418    1224  120895   1d83f block/built-in.o
243859   11158      20  255037   3e43d crypto/built-in.o
2541356  163465   34404 2739225  29cc19 drivers/built-in.o
1956       0       0    1956     7a4 firmware/built-in.o
1728762   18672   10544 1757978  1ad31a fs/built-in.o
20361   14701     100   35162    895a init/built-in.o
29628     760       8   30396    76bc ipc/built-in.o
576593   20644  285052  882289   d7671 kernel/built-in.o
106256   24847    2344  133447   20947 lib/built-in.o
291768   14901    3736  310405   4bc85 mm/built-in.o
1722683   39947   50928 1813558  1bac36 net/built-in.o
34638     848     316   35802    8bda security/built-in.o
276979   19748    1332  298059   48c4b sound/built-in.o
138       0       0     138      8a usr/built-in.o

在这里,vmlinux是 Linux 内核 ELF 镜像,可以在 Linux 的build目录中找到。

一些通常需要排除的内容包括:

  • 移除 IPv6(CONFIG_IPV6)和其他多余的网络功能

  • 如果不需要,请移除块设备(CONFIG_BLOCK

  • 如果未使用,请移除加密特性(CONFIG_CRYPTO

  • 审查支持的文件系统类型并移除不需要的文件系统,例如在没有闪存设备上使用的闪存文件系统

  • 避免模块,如果可能,移除内核中的模块支持(CONFIG_MODULES)。

一个好的策略是从一个最小的内核开始,逐步添加必要的功能直到你得到一个工作系统。从allnoconfig GNU make 目标开始,并审查CONFIG_EXPERTCONFIG_EMBEDDED下的配置项,因为这些配置项不包含在allnoconfig设置中。

以下是一些可能不太明显,但在不移除功能的情况下显著减少镜像大小的配置更改:

  • 将默认压缩方法从Lempel–Ziv–Oberhumer (LZO)改为 XZ(CONFIG_KERNEL_XZ)。不过,解压速度可能稍微慢一些。

  • 将分配器从 SLUB 更改为简单块列表 (SLOB)(CONFIG_SLOB),适用于内存较小的小型嵌入式系统。

  • 除非你有 4GB 或更多内存,否则请不要使用高内存(CONFIG_HIGHMEM)。

你可能还希望为生产和开发系统设置不同的配置,因此你可以从生产镜像中移除以下内容:

  • printk支持(CONFIG_PRINTK

  • tracing支持(CONFIG_FTRACE

在编译方面,使用CONFIG_CC_OPTIMIZE_FOR_SIZE进行大小优化。

一旦基本配置完成,我们需要分析内核功能,找出进一步的优化空间。你可以使用以下命令打印一个已排序的内核符号列表:

$ nm --size-sort --print-size -r vmlinux | head
 808bde04 00040000 B __log_buf
 8060f1c0 00004f15 r kernel_config_data
 80454190 000041f0 T hidinput_connect
 80642510 00003d40 r drm_dmt_modes
 8065cbbc 00003414 R v4l2_dv_timings_presets
 800fbe44 000032c0 T __blockdev_direct_IO
 80646290 00003100 r edid_cea_modes
 80835970 00003058 t imx6q_clocks_init
 8016458c 00002e74 t ext4_fill_super
 8056a814 00002aa4 T hci_event_packet

然后,你需要查看内核源码以找到优化点。

可以通过运行中的 Wandboard 内核日志获取未压缩内核在内存中实际使用的空间,如下所示:

$ dmesg | grep -A 3 "text"
 .text : 0x80008000 - 0x80a20538   (10338 kB)
 .init : 0x80a21000 - 0x80aae240   ( 565 kB)
 .data : 0x80ab0000 - 0x80b13644   ( 398 kB)
 .bss  : 0x80b13644 - 0x80b973fc   ( 528 kB)

从这里开始,.text部分包含代码和常量数据,.data部分包含变量的初始化数据,.bss部分包含所有未初始化的数据。.init部分仅包含在 Linux 初始化期间使用的全局变量,这些变量在之后会被释放,如下所示的 Linux 内核启动信息所示:

Freeing unused kernel memory: 564K (80a21000 - 80aae000)

当前正在进行的工作旨在减少 Linux 内核的大小,因此预计新版本的内核将更小,并且能够更好地定制以用于嵌入式系统。

减小根文件系统镜像大小

默认情况下,wandboard-quad 解包后的 core-image-minimal 大约为 45 MB,而 core-image-sato 大约为 150 MB。本文将探讨减小它们大小的方法。

如何操作…

下面显示了一个小映像示例 core-image-small,不包括 packagegroup-core-boot 配方,并且可用作根文件系统映像的基础,recipes-core/images/core-image-small.bb

DESCRIPTION = "Minimal console image."

IMAGE_INSTALL= "\
        base-files \
        base-passwd \
        busybox \
        sysvinit \
        initscripts \
        ${ROOTFS_PKGMANAGE_BOOTSTRAP} \
        ${CORE_IMAGE_EXTRA_INSTALL} \
"

IMAGE_LINGUAS = " "

LICENSE = "MIT"

inherit core-image

IMAGE_ROOTFS_SIZE ?= "8192"

该配方生成约 6.4 MB 的映像。如果使用 poky-tiny 发行版,可以通过在 conf/local.conf 文件中添加以下内容进一步减小。

DISTRO = "poky-tiny"

poky-tiny 发行版进行了一系列大小优化,可能会限制您可以包含在映像中的软件包集。要成功构建此映像,您必须跳过 Yocto 构建系统执行的某个完整性检查之一,方法是添加以下内容:

INSANE_SKIP_glibc-locale = "installed-vs-shipped"

使用 poky-tiny,映像的大小进一步减小至约 4 MB。

可以进一步减小映像的大小;例如,我们可以将 sysvinit 替换为 tiny-init,但这留给读者作为练习。

大小缩小的映像还与生产映像一起用于救援系统和制造测试流程。它们还非常适合作为 initramfs 映像,即 Linux 内核从内存中挂载的映像,甚至可以打包成单个 Linux 内核映像二进制文件。

工作原理…

从适当的映像(如 core-image-minimal)开始,并分析依赖项,如 第一章 中的 调试构建系统 配方所示,决定哪些不需要。您还可以使用映像构建历史中列出的文件大小,如 使用构建历史 配方中所见,也在 第一章 中的 构建系统 中,对文件大小按照 files-in-image.txt 文件的第四列进行逆序排序。

$ sort -r -g  -k 4,4 files-in-image.txt -o sorted-files-in-image.txt
sorted-files-in-image.txt:
-rwxr-xr-x root       root          1238640 ./lib/libc-2.19.so
-rwxr-xr-x root       root           613804 ./sbin/ldconfig
-rwxr-xr-x root       root           539860 ./bin/busybox.nosuid
-rwxr-xr-x root       root           427556 ./lib/libm-2.19.so
-rwxr-xr-x root       root           130304 ./lib/ld-2.19.so
-rwxr-xr-x root       root            88548 ./lib/libpthread-2.19.so
-rwxr-xr-x root       root            71572 ./lib/libnsl-2.19.so
-rwxr-xr-x root       root            71488 ./lib/libresolv-2.19.so
-rwsr-xr-x root       root            51944 ./bin/busybox.suid
-rwxr-xr-x root       root            42668 ./lib/libnss_files- 2.19.so
-rwxr-xr-x root       root            30536 ./lib/libnss_compat- 2.19.so
-rwxr-xr-x root       root            30244 ./lib/libcrypt-2.19.so
-rwxr-xr-x root       root            28664 ./sbin/init.sysvinit
-rwxr-xr-x root       root            26624 ./lib/librt-2.19.so

从中我们可以观察到 glic 是文件系统大小的最大贡献者。在仅限控制台系统上可以节省空间的其他地方包括:

  • 使用 IPK 软件包管理器,因为它是最轻量的,或者更好的是,完全从生产根文件系统中删除 package-management 功能。

  • conf/local.conf 文件中指定 BusyBox 的 mdev 设备管理器,而不是 udev,如下所示:

    VIRTUAL-RUNTIME_dev_manager = "mdev"
    

    请注意,这仅适用于包括 packagegroup-core-boot 的核心映像。

  • 如果我们在块设备上运行根文件系统,请在没有日志的情况下使用 ext2 而不是 ext3 或 ext4。

  • 通过在 bbappend 中提供自己的配置文件,仅配置 BusyBox 的基本应用程序:

  • 审查glibc配置,可以通过DISTRO_FEATURES_LIBC分发配置变量进行更改。它的使用示例可以在poky-tiny分发中找到,该分发包含在poky源代码中。poky-tiny分发可以作为定制小型系统分发的模板。

  • 考虑切换到比默认的glibc更轻量的C库。曾经,uclibc被用作替代方案,但该库似乎在过去几年未再维护,并且当前的 Wandboard 的core-image-minimal镜像无法使用它进行构建。

    注意

    最近,muslwww.musl-libc.org/)有了一些新的动向,它是一个新的 MIT 许可证的C库。要启用它,你需要将以下内容添加到你的conf/local.conf文件中:

    TCLIBC = “musl”

    你需要将meta-musl层(github.com/kraj/meta-musl)添加到你的conf/bblayers.conf文件中。

    它目前为 QEMU 目标构建core-image-minimal,但仍然需要一些工作才能在真实硬件(如 Wandboard)上使用它。

  • 使用-Os编译你的应用程序,以优化大小。

发布软件

当发布基于 Yocto 项目的产品时,我们必须考虑到我们是建立在许多不同的开源项目之上的,每个项目都有不同的许可要求。

至少,你的嵌入式产品将包含一个引导加载程序(可能是 U-Boot)、Linux 内核以及一个包含一个或多个应用程序的根文件系统。U-Boot 和 Linux 内核都使用通用公共许可证第 2 版GPLv2)授权。根文件系统可能包含具有不同许可证的各种程序。

所有开源许可证都允许你销售一个商业产品,该产品可以同时包含专有和开源许可证的混合,只要它们是独立的,并且产品符合所有开源许可证的要求。我们将在稍后的与开源和专有代码共存食谱中讨论开源与专有代码的共存。

在将你的产品发布到公众之前,理解所有许可的影响非常重要。Yocto 项目提供了工具,使得处理许可要求变得更加简单。

准备工作

我们首先需要明确我们在使用 Yocto 项目构建的产品中需要遵守的要求。对于最严格的开源许可证,这通常意味着:

  • 源代码分发,包括修改

  • 许可文本分发

  • 分发构建和运行软件所用的工具

如何操作…

我们可以使用archiver类来提供需要分发的成果物,以遵守许可要求。我们可以将其配置为:

  • 提供原始未修补源代码作为 tar 包

  • 提供应用于原始源代码的补丁

  • 提供用于构建源代码的配方

  • 提供有时必须附带的许可证文本(根据某些许可证的要求)

为了使用前面提到的archiver类,我们需要在conf/local.conf文件中添加以下内容:

INHERIT += "archiver"
ARCHIVER_MODE[src] = "original"
ARCHIVER_MODE[diff] = "1"
ARCHIVER_MODE[recipe] = "1"
COPY_LIC_MANIFEST = "1"
COPY_LIC_DIRS = "1"

源代码将在tmp/deploy/sources目录下的一个许可证子目录层次中提供。

对于wandboard-quad,我们可以在tmp/deploy/sources下找到以下目录:

  • allarch-poky-linux

  • arm-poky-linux-gnueabi

在查看分发的 Linux 内核源代码时,作为 GPLv2 软件包,我们可以在tmp/deploy/sources/arm-poky-linux-gnueabi/linux-wandboard-3.10.17-r0下找到以下内容:

  • defconfig

  • github.com.wandboard-org.linux.git.tar.gz

  • linux-wandboard-3.10.17-r0-recipe.tar.gz

所以我们有了内核配置、源代码压缩包,以及用于构建它的配方,其中包括:

  • linux-wandboard_3.10.17.bb

  • linux-dtb.inc

  • linux-wandboard.inc

根文件系统软件包的许可证文本也将包含在根文件系统中的/usr/share/common-licenses目录下,并按照软件包的目录层次进行存放。

该配置将为所有构建软件包提供交付物,但我们真正想做的是仅为那些许可证要求我们提供交付物的软件包提供它们。

当然,我们不希望盲目地分发source目录中的所有内容,因为它还会包含我们的专有源代码,而我们很可能不希望分发这些代码。

我们可以配置archiver类,仅为 GPL 和 LGPL 软件包提供源代码,方法如下:

COPYLEFT_LICENSE_INCLUDE = "GPL* LGPL*"
COPYLEFT_LICENSE_EXCLUDE = "CLOSED Proprietary"

此外,对于嵌入式产品,我们通常只关心实际出现在产品中的软件,因此我们可以通过以下方式限制要归档的配方类型,仅限于目标镜像:

COPYLEFT_RECIPE_TYPES = "target"

我们应当获得法律建议,以决定哪些软件包的许可证要求我们进行源代码分发。

还有其他配置选项,例如提供补丁或配置过的源代码,而不是分开的原始源代码和补丁,或者提供源rpm包而不是源代码压缩包。有关更多详情,请参见archiver类。

还有更多内容…

我们还可以选择分发整个构建环境。通常做到这一点的最佳方法是将我们的 BSP 和软件层发布到公共 Git 仓库中。我们的软件层可以提供bblayers.conf.samplelocal.conf.sample,用于设置即用型build目录。

另请参见

  • 这里没有讨论的其他要求包括选择的分发机制。建议在发布产品之前获得法律建议,以确保所有许可证义务都已履行。

分析您的系统以确保合规性

Yocto 构建系统使得向我们的法律顾问提供审计信息变得更加容易。此配方将解释如何操作。

如何做…

tmp/deploy/licenses下,我们可以找到一个包含软件包(及其相应许可证)的目录列表,以及一个包含软件包和许可证清单的image文件夹。

对于之前提供的示例镜像core-image-small,我们有以下内容:

tmp/deploy/licenses/core-image-small-wandboard-quad-<timestamp>/package.manifest
base-files
base-passwd
busybox
busybox-syslog
busybox-udhcpc
initscripts
initscripts-functions
libc6
run-postinsts
sysvinit
sysvinit-inittab
sysvinit-pidof
update-alternatives-opkg
update-rc.d

相应的tmp/deploy/licenses/core-image-small-wandboard-quad-<timestamp>/license.manifest文件摘录如下:

PACKAGE NAME: base-files
PACKAGE VERSION: 3.0.14
RECIPE NAME: base-files
LICENSE: GPLv2

PACKAGE NAME: base-passwd
PACKAGE VERSION: 3.5.29
RECIPE NAME: base-passwd
LICENSE: GPLv2+

这些文件可以用来分析构成我们根文件系统的所有不同包。我们还可以对它们进行审核,以确保在公开发布产品时遵守许可证。

还有更多内容

你可以通过使用INCOMPATIBLE_LICENSE配置变量来指示 Yocto 构建系统特意避免某些许可证。通常使用它的方法是通过在conf/local.conf文件中添加以下内容来避免使用 GPLv3 类型的许可证:

INCOMPATIBLE_LICENSE = "GPL-3.0 LGPL-3.0 AGPL-3.0"

这将构建core-image-minimalcore-image-base镜像,只要没有包含额外的镜像特性。

使用开源和专有代码

嵌入式产品通常是基于像 Yocto 这样的开源系统构建的,并且包含增加价值和专门化产品的专有软件。这个专有部分通常是知识产权,需要得到保护,了解它如何与开源软件共存非常重要。

本食谱将讨论一些常见的开源包示例,这些包通常出现在嵌入式产品中,并简要解释如何将专有软件与它们一起使用。

如何做到这一点…

开源许可证可以大致分为两类,取决于它们是否:

  • 宽松性:这些许可证类似于互联网软件协会ISC)、MIT 和 BSD 许可证。它们附带的要求较少,只要求我们保留版权声明。

  • 限制性:这些许可证类似于 GPL,它们要求我们不仅要分发源代码和修改版本,无论是与二进制文件一起还是在后期分发,还需要分发构建、安装和运行源代码的工具。

然而,某些许可证可能会“污染”修改和衍生作品的条件,这些通常被称为病毒性许可证,而其他一些则不会。例如,如果你将应用程序链接到 GPL 许可的代码,那么你的应用程序也将受到 GPL 的约束。

GPL 的病毒性特性让一些人对使用 GPL 许可的软件保持警惕,但需要注意的是,专有软件可以与 GPL 软件共存,只要理解并遵守许可证条款。

例如,违反 GPLv2 许可证将意味着失去将来分发 GPLv2 代码的权利,即使进一步的分发符合 GPLv2 规定。在这种情况下,能够再次分发代码的唯一方法是请求版权持有者的许可。

它是如何工作的…

接下来,我们将提供有关一些常见用于嵌入式产品的开源包的许可要求的指南。这并不构成法律建议,如前所述,在公开发布之前,应该对你的产品进行适当的法律审查。

U-Boot 引导程序

U-Boot 采用 GPLv2 许可证,但由它启动的任何程序不会继承其许可证。因此,你可以自由使用 U-Boot 启动专有操作系统。例如,你可以使用 U-Boot 启动一个专有操作系统。然而,你的最终产品必须遵守关于 U-Boot 的 GPLv2 条款,因此必须提供 U-Boot 的源代码和修改。

Linux 内核

Linux 内核也采用 GPLv2 许可证。任何在 Linux 内核用户空间运行的应用程序都不会继承其许可证,因此你可以在 Linux 上自由运行你的专有软件。然而,Linux 内核模块是 Linux 内核的一部分,因此必须遵守 GPLv2 许可证。此外,你的最终产品必须发布 Linux 内核源代码和修改,包括在你的产品中运行的外部模块。

Glibc

GNU C库采用较宽松通用公共许可证LGPL),允许动态链接而无需继承许可证。因此,你的专有代码可以与glibc动态链接,但当然你仍然需要遵守关于glibc的 LGPL。不过,值得注意的是,静态链接你的应用程序会使其与 LGPL 绑定。

BusyBox

BusyBox 也采用 GPLv2 许可证。该许可证允许不相关的软件与它一起运行,因此你的专有软件可以与 BusyBox 自由运行。如前所述,关于 BusyBox,你需要遵守 GPLv2 并分发其源代码和修改。

Qt 框架

Qt 有三种不同的许可证,这对于开源项目来说是很常见的。你可以选择是否需要商业许可证(在这种情况下,你的专有应用程序将受到保护),LGPL 许可证(如前所述,通过允许你的应用程序与 Qt 框架动态链接,只要你遵守 LGPL 条款,也能保护你的专有软件),或者 GPLv3 许可证(它将被继承到你的应用程序中)。

X Windows 系统

X.Org源代码采用宽松的 MIT 风格许可证。因此,只要声明使用情况并保留版权声明,你的专有软件可以自由使用它。

还有更多内容…

让我们看看如何将我们的专有许可证代码集成到 Yocto 构建系统中。在为我们的应用程序准备配方时,我们可以采取几种许可证方式:

  • LICENSE标记为封闭。这是专有应用程序的常见做法。我们使用以下方式:

    LICENSE = "CLOSED"
    
  • LICENSE标记为专有,并包括某种类型的许可协议。这通常在发布二进制文件时使用,附带某种终端用户协议,并在配方中引用。例如,meta-fsl-arm使用这种类型的许可证来遵守 Freescale 的终端用户许可协议。以下是一个示例:

    LICENSE = "Proprietary"
    LIC_FILES_CHKSUM = "file://EULA.txt;md5=93b784b1c11b3fffb1638498a8dde3f6"
    
  • 提供多种许可选项,例如开源许可证和商业许可证。在这种情况下,LICENSE 变量用于指定开源许可证,而 LICENSE_FLAGS 变量用于商业许可证。一个典型的例子是 Poky 中的gst-plugins-ugly包:

    LICENSE = "GPLv2+ & LGPLv2.1+ & LGPLv2+"
    LICENSE_FLAGS = "commercial"
    LIC_FILES_CHKSUM = "file://COPYING;md5=a6f89e2100d9b6cdffcea4f398e37343 \ file://gst/synaesthesia/synaescope.h;beginline=1;endline=20 ;md5=99f301df7b80490c6ff8305fcc712838 \  file://tests/check/elements/xingmux.c;beginline=1;endline=2 1;md5=4c771b8af188724855cb99cadd390068 \  file://gst/mpegstream/gstmpegparse.h;beginline=1;endline=18 ;md5=ff65467b0c53cdfa98d0684c1bc240a9"
    

当在配方中设置了 LICENSE_FLAGS 变量时,除非许可证也出现在 LICENSE_FLAGS_WHITELIST 变量中,否则包将不会被构建,通常该变量在你的 conf/local.conf 文件中定义。对于前面的例子,我们需要添加:

LICENSE_FLAGS_WHITELIST = "commercial"

LICENSELICENSE_FLAGS_WHITELIST 变量可以精确匹配以进行非常狭窄的匹配,也可以像前面例子那样广泛匹配,匹配所有以 commercial 开头的许可证。对于狭窄匹配,包名必须附加到许可证名称上;例如,如果我们只希望将前面例子中的 gst-plugins-ugly 包加入白名单,而不包括其他包,我们可以使用如下方式:

LICENSE_FLAGS_WHITELIST = "commercial_gst-plugins-ugly"

另见

  • 你应该参考具体的许可证,以完整理解它们所施加的要求。你可以在spdx.org/licenses/找到完整的开源许可证列表及其文档。

第四章:应用程序开发

在本章中,我们将覆盖以下配方:

  • 介绍工具链

  • 准备和使用 SDK

  • 使用应用程序开发工具包

  • 使用 Eclipse IDE

  • 开发 GTK+ 应用程序

  • 使用 Qt Creator IDE

  • 开发 Qt 应用程序

  • 描述应用程序开发的工作流

  • 使用 GNU make

  • 使用 GNU 构建系统

  • 使用 CMake 构建系统

  • 使用 SCons 构建工具

  • 使用库进行开发

  • 使用 Linux 帧缓冲区

  • 使用 X Windows 系统

  • 使用 Wayland

  • 添加 Python 应用程序

  • 集成 Oracle Java 运行时环境

  • 集成 Open Java 开发工具包

  • 集成 Java 应用程序

介绍

专用应用程序是定义嵌入式产品的关键,Yocto 提供了有用的应用程序开发工具,以及与流行的 集成开发环境IDE)如 Eclipse 和 Qt Creator 集成的功能。它还提供了广泛的工具类,帮助将完成的应用程序集成到构建系统和目标映像中。

本章将介绍 IDE,并向我们展示如何在真实硬件上使用它们构建和调试 C 和 C++ 应用程序,同时探索应用程序开发,包括图形框架和 Yocto 集成,不仅适用于 C 和 C++,还包括 Python 和 Java 应用程序。

介绍工具链

工具链是一组用于构建应用程序以在计算机平台上运行的工具、二进制文件和库。在 Yocto 中,工具链基于 GNU 组件。

准备就绪

一个 GNU 工具链包含以下组件:

  • 汇编器(GNU as):这是 binutils 包的一部分

  • 链接器(GNU ld):这也是 binutils 包的一部分

  • 编译器(GNU gcc):支持 C、C++、Java、Ada、Fortran 和 Objective C

  • 调试器(GNU gdb):这是 GNU 调试器

  • 二进制文件工具(objdump、nm、objcopy、readelf、strip 等):这些是 binutils 包的一部分。

这些组件足以构建裸机应用程序、引导程序(如 U-Boot)或操作系统(如 Linux 内核),因为它们不需要 C 库,并且实现了所需的 C 库函数。然而,对于 Linux 用户空间应用程序,则需要一个符合 POSIX 标准的 C 库。

GNU C 库,glibc,是 Yocto 项目中使用的默认 C 库。Yocto 正在引入对 musl(一个较小的 C 库)的支持,但正如我们之前提到的,仍需进行一些工作,才能在 FSL 社区层支持的硬件平台上使用。

但是在嵌入式系统中,我们不仅需要一个工具链,还需要一个交叉编译工具链。这是因为我们在主机计算机上构建,但将生成的二进制文件在目标机器上运行,而目标机器通常具有不同的架构。实际上,工具链有几种类型,基于构建工具链的机器架构(构建机器)、运行工具链的机器架构(主机机器)和运行工具链构建的二进制文件的机器架构(目标机器)。最常见的组合有:

  • 本地:一个例子是运行在 x86 机器上的工具链,该工具链也是在 x86 机器上构建的,并生成用于在 x86 机器上运行的二进制文件。这在桌面计算机中很常见。

  • 交叉编译:这是嵌入式系统中最常见的情况;例如,一台 x86 机器运行着在 x86 机器上构建的工具链,但生成用于在不同架构上运行的二进制文件,如 ARM。

  • 交叉本地:这通常是运行在目标上的工具链。例如,工具链是在 x86 机器上构建的,但它在 ARM 上运行并生成 ARM 用的二进制文件。

  • 加拿大:很少见,这里构建、主机和目标机器都是不同的。

构建交叉编译工具链的过程复杂且容易出错,因此自动化的工具链构建工具应运而生,如buildrootcrosstool-NG。Yocto 构建系统在每次构建时都会编译自己的工具链,正如我们将看到的,你也可以使用这个工具链进行应用程序开发。

但是交叉编译工具链和 C 库并不是构建应用程序所需的唯一内容;我们还需要一个sysroot,即在主机上具有与目标根文件系统相同的库和头文件的根文件系统。

交叉编译工具链、sysroot,以及有时的其他开发工具(如 IDE)的组合被称为 SDK,即软件开发工具包。

如何做……

使用 Yocto 项目获取 SDK 的方式有几种:

  • 使用应用程序开发工具包ADT)。

    如果你使用的是 Poky 支持的硬件平台(即虚拟化的 QEMU 机器或其中一块参考板),建议使用 ADT,它会为你安装所有所需的 SDK 组件。

  • 下载预编译的工具链。

    获取支持平台的交叉编译工具链最简单的方法是下载一个预编译的版本;例如,可以从 Yocto 项目下载站点获取:downloads.yoctoproject.org/releases/yocto/yocto-1.7.1/toolchain/。Yocto 项目为 32 位和 64 位 i686 主机提供预构建的工具链,并为armv5armv7架构提供预构建的 ARM 工具链。这些工具链包含与core-image-sato目标映像匹配的sysroot。然而,预构建的sysroot是软浮点的,因此无法与 FSL 社区层为基于 i.MX6 的平台构建的目标映像一起使用,因为它们是硬浮点的。要为 x86_64 主机安装预构建的 armv7 工具链,请运行以下命令:

    $ wget http://downloads.yoctoproject.org/releases/yocto/yocto- 1.7.1/toolchain/x86_64/poky-glibc-x86_64-core-image-sato- armv7a-vfp-neon-toolchain-1.7.1.sh
    $ chmod a+x poky-glibc-x86_64-core-image-sato-armv7a-vfp-neon- toolchain-1.7.1.sh
    $ ./poky-glibc-x86_64-core-image-sato-armv7a-vfp-neon- toolchain-1.7.1.sh
    
    
  • 构建你自己的工具链安装程序。

    在大多数嵌入式 Linux 项目中,你的机器将由外部层支持,并且你将拥有一个定制的根文件系统,你的sysroot需要与其匹配。因此,当你有一个定制的根文件系统时,推荐构建你自己的工具链安装程序。例如,适合与 Wandboard 配合使用的理想工具链应该是Cortex-A9特定的,并且针对生成硬浮点二进制文件。

  • 使用 Yocto 项目构建系统。

    最后,如果你的主机上已经安装了 Yocto 构建系统,你也可以用它来进行应用程序开发。通常,应用程序开发人员不需要 Yocto 构建系统安装的复杂性,因此一个针对目标系统的工具链安装程序就足够了。

准备和使用 SDK

Yocto 构建系统可以用来为目标系统生成交叉编译工具链和匹配的sysroot

准备就绪

我们将使用之前使用过的wandboard-quad构建目录,并按照以下方式加载setup-environment脚本:

$ cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad

如何操作…

使用 Yocto 构建系统构建 SDK 有几种方式:

  • meta-toolchain目标。

    这种方法将构建一个与目标平台匹配的工具链,以及一个基本的sysroot,但它不会与目标根文件系统匹配。然而,这个工具链可以用来构建裸机软件,比如 U-Boot 引导加载程序或 Linux 内核,它们不需要sysroot。Yocto 项目为支持的硬件平台提供可下载的sysroot。你也可以使用以下方法自行构建这个工具链:

    $ bitbake meta-toolchain
    
    

    构建完成后,可以使用以下命令安装:

    $ cd tmp/deploy/sdk
    $ ./poky-glibc-x86_64-meta-toolchain-cortexa9hf-vfp-neon- toolchain-1.7.1.sh
    
    
  • populate_sdk任务。

    这是构建与目标平台匹配的工具链,并且sysroot匹配目标根文件系统的推荐方式。你可以使用以下命令进行构建:

    $ bitbake core-image-sato -c populate_sdk
    
    

    你应该将core-image-sato替换为你希望sysroot匹配的目标根文件系统映像。构建完成的工具链可以使用以下命令安装:

    $ cd tmp/deploy/sdk
    $ ./poky-glibc-x86_64-core-image-sato-cortexa9hf-vfp-neon- toolchain-1.7.1.sh
    
    

    此外,如果你希望工具链能够构建静态应用程序,则需要向其中添加静态库。你可以通过将特定的静态库添加到目标镜像来实现,静态库也可以用于本地编译。例如,要添加静态的glibc库,可以在conf/local.conf文件中添加以下内容:

    IMAGE_INSTALL_append =  " glibc-staticdev"
    

    然后,按照之前的说明构建工具链,以匹配你的根文件系统。

    通常,你不会希望将静态库添加到镜像中,但如果你希望能够交叉编译静态应用程序,则可以通过添加以下内容将所有静态库添加到工具链中:

    SDKIMAGE_FEATURES_append = " staticdev-pkgs"
    
  • meta-toolchain-qt目标。

    该方法将扩展meta-toolchain以构建 Qt 应用程序。稍后我们将看到如何构建 Qt 应用程序。要构建此工具链,请执行以下命令:

    $ bitbake meta-toolchain-qt
    
    

    一旦构建完成,可以使用以下命令进行安装:

    $ cd tmp/deploy/sdk
    $ ./poky-glibc-x86_64-meta-toolchain-qt-cortexa9hf-vfp-neon- toolchain-qt-1.7.1.sh
    
    

    生成的工具链安装程序将位于tmp/deploy/sdk目录下,适用于此处提到的所有情况。

  • meta-ide-support目标。

    该方法不会生成工具链安装程序,但它会准备当前的构建项目以使用其自己的工具链。它将在 tmp 目录中生成一个environment-setup脚本。

    $ bitbake meta-ide-support
    
    

    要使用捆绑的工具链,你现在可以按如下方式引入该脚本:

    $ source tmp/environment-setup-cortexa9hf-vfp-neon-poky-linux- gnueabi
    
    

使用应用程序开发工具包

ADT 是一个 SDK 安装脚本,用于在 Poky 支持的硬件平台上安装以下内容:

  • 一个预构建的交叉编译工具链,如前所述

  • core-image-sato目标镜像匹配的sysroot

  • QEMU 模拟器

  • 用于系统分析的其他开发用户空间工具(将在后续章节中讨论)

准备就绪

要安装 ADT,可以选择以下两种方式之一:

  • 使用以下命令从 Yocto 项目的下载站点下载预编译的 tarball:

    $ wget http://downloads.yoctoproject.org/releases/yocto/yocto- 1.7.1/adt-installer/adt_installer.tar.bz2
    
    
  • 使用你的 Yoctobuild目录构建一个。

ADT 安装程序是一个自动化脚本,用于安装预编译的 Yocto SDK 组件,因此无论你是下载预构建版本还是自己构建,都将是相同的。

然后,你可以在运行之前配置它,以定制安装。

请注意,ADT 仅适用于 Poky 支持的平台。例如,除非提供自己的组件,否则它对于像wandboard-quad这样的外部硬件并没有多大用处。

如何操作…

要从你的 Yoctobuild目录构建 ADT,请打开一个新的 shell 并执行以下命令:

$ cd /opt/yocto/poky
$ source oe-init-build-env qemuarm
$ bitbake adt-installer

ADT 的 tarball 将位于tmp/deploy/sdk目录下。

它是如何工作的…

要安装它,请按照以下步骤操作:

  1. 将 tarball 提取到你选择的位置:

    $ cd /opt/yocto
    $ cp /opt/yocto/poky/qemuarm/tmp/deploy/sdk/adt_installer.tar.bz2 /opt/yocto
    $ tar xvf adt_installer.tar.bz2
    $ cd /opt/yocto/adt-installer
    
    
  2. 通过编辑adt_installer.conf文件来配置安装。一些选项包括:

    • YOCTOADT_REPO:这是一个包含软件包和根文件系统的仓库。默认情况下,它使用 Yocto 项目网站上的仓库,adtrepo.yoctoproject.org/1.7.1/,但是你也可以自己设置一个,包含自定义的软件包和根文件系统。

    • YOCTOADT_TARGETS:定义 SDK 的机器目标。默认情况下,这是 ARM 和 x86。

    • YOCTOADT_QEMU:此选项控制是否安装 QEMU 模拟器。默认情况下会安装它。

    • YOCTOADT_NFS_UTIL:此选项控制是否安装用户模式 NFS。如果你打算在基于 QEMU 的机器上使用 Eclipse IDE,建议启用此选项。默认情况下会安装它。

    然后针对特定目标架构(仅针对 ARM 显示):

    • YOCTOADT_ROOTFS_arm:定义从 ADT 仓库下载的特定根文件系统镜像。默认情况下,安装 minimalsato-sdk 镜像。

    • YOCTOADT_TARGET_SYSROOT_IMAGE_arm:这是用于创建 sysroot 的根文件系统。此选项必须包含在之前说明过的 YOCTOADT_ROOTFS_arm 选择中。默认情况下,这是 sato-sdk 镜像。

    • YOCTOADT_TARGET_MACHINE_arm:这是下载镜像的机器类型。默认情况下,这是 qemuarm

    • YOCTOADT_TARGET_SYSROOT_LOC_arm:这是主机上安装目标 sysroot 的路径。默认情况下是 $HOME/test-yocto/

  3. 按照以下方式运行 ADT 安装程序:

    $ ./adt_installer
    
    

    安装过程中将提示你选择安装位置(默认是 /opt/poky/1.7.1),并询问是否以交互模式或静默模式运行。

使用 Eclipse IDE

Eclipse 是一个开源集成开发环境(IDE),主要使用 Java 编写,并根据 Eclipse 公共许可证EPL)发布。它可以通过插件进行扩展,Yocto 项目发布了一个 Yocto 插件,允许我们使用 Eclipse 进行应用开发。

准备工作

Yocto 1.7 为两个不同版本的 Eclipse 提供了 Yocto 插件,分别是 Juno 和 Kepler。它们可以从 downloads.yoctoproject.org/releases/yocto/yocto-1.7.1/eclipse-plugin/ 下载。我们将使用 Kepler 4.3,因为它是最新的。我们将从 Eclipse Kepler 标准版开始,并安装所需的所有插件。

推荐在 Oracle Java 1.7 下运行 Eclipse,尽管其他 Java 提供商也受支持。你可以从 Oracle 网站安装 Oracle Java 1.7,www.java.com/en/,或者使用 Ubuntu Java 安装程序 PPA,launchpad.net/~webupd8team/+archive/ubuntu/java。后者将 Java 与你的包管理系统集成,因此更为推荐。安装步骤如下:

$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java7-set-default

要下载并安装适用于 x86_64 主机的 Eclipse Kepler 标准版,请按以下步骤操作:

  1. 从 Eclipse 下载站点获取 tar 包,eclipse.org/downloads/packages/release/Kepler/SR2。例如:

     $ wget http://download.eclipse.org/technology/epp/downloads/release/kepler/SR2/eclipse-standard-kepler-SR2-linux-gtk-x86_64.tar.gz
    
    
  2. 将其解压到你选择的位置,如下所示:

    $ tar xvf eclipse-standard-kepler-SR2-linux-gtk-x86_64.tar.gz
    
    
  3. 启动 Eclipse IDE,使用以下命令:

    $ nohup eclipse/eclipse &
    
    
  4. 帮助下拉菜单中选择安装新软件。然后选择Kepler - http://download.eclipse.org/releases/kepler源。

  5. 安装以下 Eclipse 组件:

    • Linux 工具:

      LTTng - Linux 跟踪工具包

    • 移动和设备开发:

      C/C++ 远程启动

      远程系统浏览器最终用户运行时

      远程系统浏览器用户操作

      目标管理终端

      TCF 远程系统浏览器插件

      TCF 目标浏览器

    • 编程语言:

      C/C++ 自动工具支持

      C/C++ 开发工具

  6. 通过添加此仓库源来安装 Eclipse Yocto 插件:downloads.yoctoproject.org/releases/eclipse-plugin/1.7.1/kepler,如以下截图所示:https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_01.jpg

  7. 选择Yocto 项目 ADT 插件并忽略未签名的内容警告。我们不会覆盖其他插件扩展。

如何操作…

要配置 Eclipse 使用 Yocto 工具链,请转到 窗口 | 首选项 | Yocto 项目 ADT

ADT 配置提供了两种交叉编译器选项:

  1. 独立预构建工具链:当你通过工具链安装程序或 ADT 安装程序安装了工具链时,请选择此项。

  2. 基于构建系统的工具链:当使用之前提到的通过 meta-ide-support 准备的 Yocto build 目录时,请选择此项。

它还提供了两个目标选项:

  1. QEMU 模拟器:如果你使用的是虚拟化机器上的 Poky,并且已经使用 ADT 安装程序安装了 qemuarm Linux 内核和根文件系统,请选择此项。

  2. 外部硬件:如果你使用的是像 wandboard-quad 这样的真实硬件,请选择此选项。此选项对于嵌入式开发最为有用。

使用 ADT 安装程序及其默认配置时的示例配置是选择独立预构建的工具链选项,并配合 QEMU 模拟器,如下所示:

对于使用 wandboard-quad 参考板的基于构建系统的工具链,你将需要以下内容:

还有更多…

为了在远程目标上进行调试,目标需要运行 tcf-agent 守护进程。它默认包含在 SDK 镜像中,但你也可以通过在 conf/local.conf 文件中添加以下内容将其包含到其他镜像中:

EXTRA_IMAGE_FEATURES += "eclipse-debug"

另见

开发 GTK+ 应用程序

本教程将展示如何使用 Eclipse IDE 构建、运行和调试图形化 GTK+ 应用程序。

准备工作

  1. 如下所示,将 eclipse-debug 功能添加到项目的 conf/local.conf 文件中:

    EXTRA_IMAGE_FEATURES += "eclipse-debug"
    
  2. 如下所示,构建一个 core-image-sato 目标镜像:

    $ cd /opt/yocto/fsl-community-bsp/
    $ source setup-environment wandboard-quad
    $ bitbake core-image-sato
    
    
  3. 如下所示构建一个 core-image-sato 工具链:

    $ bitbake -c populate_sdk core-image-sato
    
    
  4. 如下所示安装工具链:

    $ cd tmp/deploy/sdk
    $ ./poky-glibc-x86_64-core-image-sato-cortexa9hf-vfp-neon- toolchain-1.7.1.sh
    
    

在启动 Eclipse IDE 之前,我们可以检查是否能够手动构建并启动一个 GTK 应用程序。我们将构建以下 GTK+ Hello World 应用程序:

以下是 gtk_hello_world.c 的代码:

#include <gtk/gtk.h>

int main(int argc, char *argv[])
{
  GtkWidget *window;
  gtk_init (&argc, &argv);
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_show (window);
  gtk_main ();
  return 0;
}

要构建它,我们使用之前描述的安装好的 core-image-sato 工具链:

$ source /opt/poky/1.7.1/environment-setup-cortexa9hf-vfp-neon-poky- linux-gnueabi
$ ${CC} gtk_hello_world.c -o helloworld `pkg-config --cflags --libs gtk+-2.0`

该命令使用 pkg-config 辅助工具读取与 GTK 库一起安装在 sysroot 中的 .pc 文件,以确定编译使用 GTK 的程序所需的编译器选项(--cflags 用于 include 目录,--libs 用于要链接的库)。

我们可以手动将生成的二进制文件复制到 Wandboard,并在通过 NFS 启动 core-image-sato 时,从目标控制台运行:

# DISPLAY=:0 helloworld

这将打开一个 GTK+ 窗口,显示在 SATO 桌面上。

如何操作…

我们现在可以使用前面描述的独立工具链配置 Eclipse ADT 插件,或者我们也可以决定使用派生自构建系统的工具链。

https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_04.jpg

按照以下步骤构建并运行一个示例 Hello World 应用程序:

  1. 创建一个新的 Hello World GTK Autotools 项目。在项目创建向导中接受所有默认选项。浏览到 文件 | 新建 | 项目 | C/C++ | C 项目 | Yocto 项目 ADT Autotools 项目 | Hello World GTK C Autotools 项目

    提示

    在为项目命名时,请避免使用破折号等特殊字符,因为它们可能会导致构建工具出现问题。

  2. 通过访问 项目 | 构建项目 来构建项目。https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_05.jpg

  3. 即使项目成功构建,您也可能会看到源代码和 问题 标签中标记的错误。这是因为 Eclipse 的代码分析功能无法解析项目的所有符号。为了解决这个问题,您需要通过以下路径将所需的 include 头文件添加到项目属性中:项目 | 属性 | C/C++ 常规 | 路径和符号 | 包含https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_06.jpg

  4. 运行 | 运行配置下,你应该有一个名为<project_name>_gdb_arm-poky-linux-gnueabiC/C++远程应用程序与 TCF 目标。如果没有,创建一个。https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_07.jpg

  5. 使用新建…按钮在选项卡中创建一个到目标 IP 地址的新 TCF 连接。https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_08.jpg

  6. C/C++应用程序的远程绝对文件路径字段中填写二进制文件的路径,并包括二进制文件名;例如,/gtk_hello_world

  7. 应用程序执行前的命令字段中,输入export DISPLAY=:0https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_09.jpg

  8. 运行应用程序并以root用户登录,密码为空。你应该能够在 SATO 桌面上看到 GTK 应用程序,并在控制台标签中看到以下输出:https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_23.jpg

提示

如果你在连接目标时遇到问题,可以通过在目标的控制台输入以下命令来验证它是否正在运行 tcf-agent

# ps w | grep tcf
735 root     11428 S    /usr/sbin/tcf-agent -d -L- -l0

如果你遇到登录问题,可以使用 Eclipse 的远程系统资源管理器RSE)视图来清除密码并调试与目标的连接。一旦能够建立连接,并且通过 RSE 浏览目标的文件系统,你可以返回到运行配置。

还有更多…

要调试应用程序,请按照以下步骤进行:

  1. 进入运行 | 调试配置

  2. 调试器标签下,验证 GDB 调试器路径是否指向正确的工具链调试器位置。

    /opt/poky/1.7.1/sysroots/x86_64-pokysdk-linux/usr/bin/arm- poky-linux-gnueabi/arm-poky-linux-gnueabi-gdb
    
    

    如果没有,指向正确的位置。

    https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_10.jpg

  3. 双击源文件中的main函数以添加断点。侧边栏将显示一个蓝点。

  4. 点击调试按钮,调试视图出现,并且应用程序在远程 Wandboard 硬件上执行。https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_11.jpg

    提示

    如果遇到文本文件正忙错误,请记得关闭我们在前一点运行的应用程序。

使用 Qt Creator IDE

Qt Creator 是一个多平台的 IDE,属于 Qt 应用程序开发框架 SDK 的一部分。它是 Qt 应用程序开发的首选 IDE,并提供多种许可证,包括 GPLv3、LGPLv2 和商业许可证。

准备就绪

  1. 从 Qt 项目下载网站下载并安装适合你主机的 Qt Creator 3.3.0 版本。对于 x86_64 Linux 主机的下载和安装,你可以使用以下命令:

    $ wget http://download.qt.io/official_releases/qtcreator/3.3/3.3.0/qt -creator-opensource-linux-x86_64-3.3.0.run
    $ chmod u+x qt-creator-opensource-linux-x86_64-3.3.0.run
    $ ./qt-creator-opensource-linux-x86_64-3.3.0.run
    
    
  2. 构建一个准备好开发 Qt 应用程序的工具链,使用以下命令:

    $ cd /opt/yocto/fsl-community-bsp/
    $ source setup-environment wandboard-quad
    $ bitbake meta-toolchain-qt
    
    
  3. 按以下方式安装:

    $ cd tmp/deploy/sdk
    $ ./poky-glibc-x86_64-meta-toolchain-qt-cortexa9hf-vfp-neon- toolchain-qt-1.7.1.sh
    
    

如何操作…

在启动 Qt Creator 之前,我们需要设置开发环境。为了在启动 Qt Creator 时自动完成这一过程,我们可以通过在bin/qtcreator.sh文件的开头添加以下行来修补其初始化脚本:

source /opt/poky/1.7.1/environment-setup-cortexa9hf-vfp-neon-poky- linux-gnueabi
#! /bin/sh

注意

请注意,环境初始化脚本位于哈希标记之前。

现在我们可以按照以下方式运行 Qt Creator:

$ ./bin/qtcreator.sh &

并通过转到工具 | 选项并按照以下步骤进行配置:

  1. 首先,我们为我们的 Wandboard 配置一个新设备。在设备 | 添加下,选择通用 Linux 设备https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_12.jpg

    通过在目标的根控制台使用passwd命令设置根密码,并将其输入密码字段。

  2. 构建与运行下,我们配置一个新的编译器,指向我们刚刚安装的 Yocto meta-toolchain-qt编译器路径。下面的截图显示了该路径:

    /opt/poky/1.7.1/sysroots/x86_64-pokysdk-linux/usr/bin/arm- poky-linux-gnueabi/arm-poky-linux-gnueabi-g++
    
    

    https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_13.jpg

  3. 对于交叉调试器,下面是路径,截图中也提到过:

    /opt/poky/1.7.1/sysroots/x86_64-pokysdk-linux/usr/bin/arm- poky-linux-gnueabi/arm-poky-linux-gnueabi-gdb
    
    

    https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_14.jpg

  4. 然后我们通过从工具链中选择qmake构建器来配置 Qt。以下是该路径,截图中也提到过:

    /opt/poky/1.7.1/sysroots/x86_64-pokysdk-linux/usr/bin/qmake
    
    

    https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_15.jpg

  5. 最后,我们按照以下步骤配置一个新的开发套件:

    1. 选择通用 Linux 设备并将其sysroot配置为:

      /opt/poky/1.7.1/sysroots/cortexa9hf-vfp-neon-poky-linux- gnueabi/
      
      
    2. 选择我们刚刚定义的编译器、调试器和 Qt 版本。https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_16.jpg

      提示

      在 Ubuntu 中,Qt Creator 将其配置存储在用户的主目录下的.config/QtProject/目录中。

开发 Qt 应用程序

本教程将展示如何使用 Qt Creator 构建、运行和调试一个图形化的 Qt 应用程序。

准备就绪

在启动 Qt Creator 之前,我们检查是否能够手动构建并启动一个 Qt 应用程序。我们将构建一个 Qt hello world 应用程序。

这里是qt_hello_world.cpp的代码:

#include <QApplication>
#include <QPushButton>

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);

     QPushButton hello("Hello world!");

     hello.show();
     return app.exec();
 }

要构建它,我们使用之前描述的meta-toolchain-qt

$ source /opt/poky/1.7.1/environment-setup-cortexa9hf-vfp-neon-poky- linux-gnueabi
$ qmake -project
$ qmake
$ make

这使用qmake来创建一个项目文件和一个Makefile文件,包含文件夹中的所有相关代码文件。

为了运行它,我们首先需要构建一个支持 Qt 的文件系统。我们首先按照以下步骤准备环境:

$ cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad

然后我们通过在conf/local.conf中添加以下内容来配置我们的项目,启用qt4-pkgs额外功能:

EXTRA_IMAGE_FEATURES += "qt4-pkgs"

对于 Qt 应用程序,我们还需要国际化组件 UnicodeICU)库,因为 Qt 库是以支持该库进行编译的。

IMAGE_INSTALL_append = " icu"

然后通过以下命令构建:

$ bitbake core-image-sato

完成后,我们可以编程 microSD 卡镜像并启动 Wandboard。将qt_hello_world二进制文件复制到目标设备并运行:

# DISPLAY=:0 qt_hello_world

你应该能在 X11 桌面上看到 Qt hello world 窗口。

如何操作…

按照这些步骤构建并运行一个示例 hello world 应用程序:

  1. 通过转到文件 | 新建文件或项目 | 其他项目 | 空 qmake 项目来创建一个新的空项目。

  2. 仅选择我们刚创建的wandboard-quad开发板套件。https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_17.jpg

  3. 通过转到文件 | 新建文件或项目 | C++ | C++源文件,添加一个新的 C++文件,qt_hello_world.cpp

  4. qt_hello_world.cpp文件的内容粘贴到 Qt Creator 中,如下图所示:https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_18.jpg

  5. 通过将以下内容添加到 hw.pro 文件中,配置你的项目与目标安装的详细信息:

    SOURCES += \
       qt_hello_world.cpp
    
    TARGET =  qt_hello_world
       target.files =  qt_hello_world
       target.path = /
    
    INSTALLS += target
    

    qt_hello_world 替换为你的项目名称。

  6. 构建项目。如果遇到构建错误,检查 Yocto 构建环境是否正确设置。

    提示

    在启动 Qt Creator 之前,你可以尝试手动运行工具链的 environment-setup 脚本。

  7. 转到 项目 | 运行,检查你的项目设置。https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_19.jpg

  8. 如屏幕截图所示,Qt Creator 将使用 SFTP 协议将文件传输到目标设备。默认情况下,core-image-sato 上运行的 dropbear SSH 服务器不支持 SFTP。我们需要通过将 openssh-sftp-server 软件包添加到项目的 conf/local.conf 文件中,将其添加到镜像中,以使 Qt Creator 能够正常工作。

    IMAGE_INSTALL_append =  " openssh-sftp-server"
    

    然而,我们还需要其他工具,例如 gdbserver,如果我们希望调试我们的应用程序,那么添加 eclipse-debug 功能会更方便,它将把所有需要的应用程序添加到目标镜像中。

    EXTRA_IMAGE_FEATURES += "eclipse-debug"
    
  9. 现在,你可以运行项目了。https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_20.jpg

    提示

    如果应用程序在部署时出现登录错误,检查是否已按照之前的配方在目标设备中设置了 root 密码,或者是否正在使用 SSH 密钥认证。

现在,你应该能看到示例 Qt hello world 应用程序在你的 SATO 桌面上运行。

还有更多…

为了调试应用程序,可以在源代码上设置断点并点击 调试 按钮。

https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_21.jpg

描述应用程序开发的工作流

应用程序开发的工作流类似于我们之前在第二章,BSP 层,看到的 U-Boot 和 Linux 内核的工作流。

如何操作…

我们将看到以下开发工作流是如何应用于应用程序开发的:

  • 外部开发

  • 工作目录开发

  • 外部源开发

它是如何工作的…

外部开发

这就是我们在之前通过命令行使用独立工具链构建时,看到的配方中的使用方式,也适用于使用 Eclipse 和 Qt Creator IDE 时的情况。该工作流会生成需要单独复制到硬件上才能运行和调试的二进制文件。它可以与其他工作流结合使用。

工作目录开发

当应用程序通过 Yocto 构建系统构建时,我们使用这个工作流来调试偶发性问题。然而,它并不是长期开发的推荐工作流。但请注意,它通常是调试第三方软件包时的第一步。

我们将使用在第三章,软件层,中的 添加新软件包 配方中看到的 helloworld_1.0.bb 自定义配方 meta-custom/recipes-example/helloworld/helloworld_1.0.bb 作为示例。

DESCRIPTION = "Simple helloworld application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "file://helloworld.c"

S = "${WORKDIR}"

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

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

这里,helloworld.c 源文件如下:

#include <stdio.h>

int main(void)
{
   return printf("Hello World");
}

工作流步骤如下:

  1. 从头开始启动软件包编译。

    $ cd /opt/yocto/fsl-community-bsp/
    $ source setup-environment wandboard-quad
    $ bitbake -c cleanall helloworld
    
    

    这将删除软件包的构建文件夹、共享状态缓存和下载的软件包源。

  2. 启动开发环境:

    $ bitbake -c devshell helloworld
    
    

    这将获取、解压并修补helloworld源代码,并启动一个新的 shell,环境已准备好进行编译。新 shell 将切换到软件包的build目录。

  3. 根据SRC_URI变量,软件包的build目录可能已经受版本控制。如果没有(如本示例所示),我们将按如下方式创建一个本地 Git 版本库:

    $ git init
    $ git add helloworld.c
    $ git commit -s -m "Original revision"
    
    
  4. 执行我们需要的修改;例如,将helloworld.c更改为如下内容,以打印Howdy world

    #include <stdio.h>
    
    int main(void)
    {
     return printf("Howdy World");
    }
    
    
  5. 退出devshell并构建软件包,而不删除我们的修改。

    $ bitbake -C compile helloworld
    
    

    提示

    注意大写的C(它触发编译任务),以及所有跟随它的任务。

  6. 在硬件上测试您的更改,方法是复制生成的软件包并安装它。因为您只修改了一个软件包,其他依赖项应该已经安装在运行中的根文件系统中。运行以下命令:

    $ bitbake -e helloworld | grep ^WORKDIR=
    WORKDIR="/opt/yocto/fsl-community-bsp/wandboard- quad/tmp/work/cortexa9hf-vfp-neon-poky-linux- gnueabi/helloworld/1.0-r0"
    $ scp ${WORKDIR_PATH}/deploy-rpms/deploy- rpms/cortexa9hf_vfp_neon/helloworld-1.0- r0.cortexa9hf_vfp_neon.rpm root@<target_ip_address>:/
    $ rpm -i /helloworld-1.0-r0.cortexa9hf_vfp_neon.rpm
    
    

    这假设目标的根文件系统已通过package-management功能构建,并且在使用rm_work类时,helloworld软件包已添加到RM_WORK_EXCLUDE变量中。

  7. 返回devshell并将更改提交到本地 Git 版本库,如下所示:

    $ bitbake -c devshell helloworld
    $ git add  helloworld.c
    $ git commit -s -m "Change greeting message"
    
    
  8. 生成补丁到配方的补丁目录中:

    $ git format-patch -1 -o /opt/yocto/fsl-community- bsp/sources/meta-custom/recipes- example/helloworld/helloworld-1.0
    
    
  9. 最后,将补丁添加到配方的SRC_URI变量中,如下所示:

    SRC_URI  =  "file://helloworld.c \
               file://0001-Change-greeting-message.patch"
    

外部源开发

这种工作流建议在将应用程序集成到 Yocto 构建系统后用于开发工作。例如,可以与使用 IDE 进行的外部开发结合使用。

在我们之前看到的示例配方中,源文件与元数据一起放置在meta-custom层中。

更常见的是让配方直接从版本控制系统(如 Git)中获取,因此我们将修改meta-custom/recipes-example/helloworld/helloworld_1.0.bb文件,以从 Git 目录中获取,如下所示:

DESCRIPTION = "Simple helloworld application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "git://github.com/yoctocookbook/helloworld"

S = "${WORKDIR}/git"

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

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

然后我们可以将其克隆到本地目录,如下所示:

$ cd /opt/yocto/
$ git clone git://github.com/yoctocookbook/helloworld

使用本地版本控制库是使用远程版本控制库的替代方法。为此,请按照以下步骤操作:

  1. 创建一个本地 Git 版本库,用于存放源代码:

    $ mkdir -p /opt/yocto/helloworld
    $ cd /opt/yocto/helloworld
    $ git init
    
    
  2. 将我们的helloworld.c文件复制到这里,并将其添加到版本库中:

    $ git add helloworld.c
    
    
  3. 最后,使用签名和消息提交:

    $ git commit -s -m "Original revision"
    
    

无论如何,我们都将版本控制的源代码放在本地目录中。然后我们将配置我们的conf/local.conf文件以从中工作,如下所示:

INHERIT += "externalsrc"
EXTERNALSRC_pn-helloworld = "/opt/yocto/helloworld"
EXTERNALSRC_BUILD_pn-helloworld = "/opt/yocto/helloworld"

然后使用以下命令进行构建:

$ cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad
$ bitbake helloworld

然后我们可以直接在本地文件夹中工作,而无需担心 BitBake 意外删除我们的代码。开发完成后,conf/local.conf的修改将被删除,配方将从其原始SRC_URI位置获取源代码。

使用 GNU make 工作

GNU make 是 Linux 系统上的一种 make 实现。它被许多开源项目使用,包括 Linux 内核。构建由Makefile管理,Makefile指示 make 如何构建源代码。

如何操作…

Yocto 配方继承base.bbclass,因此它们的默认行为是查找MakefilemakefileGNU Makefile并使用 GNU make 来构建软件包。

如果你的软件包已经包含Makefile,那么你只需要关注需要传递给 make 的参数。make 的参数可以通过EXTRA_OEMAKE变量传递,且需要提供一个调用oe_runmake安装的do_install重写,否则将执行空安装。

例如,logrotate配方基于一个Makefile,如下所示:

SUMMARY = "Rotates, compresses, removes and mails system log files"
SECTION = "console/utils"
HOMEPAGE = "https://fedorahosted.org/logrotate/"
LICENSE = "GPLv2"

DEPENDS="coreutils popt"

LIC_FILES_CHKSUM = "file://COPYING;md5=18810669f13b87348459e611d31ab760"

SRC_URI = "https://fedorahosted.org/releases/l/o/logrotate/logrotate- ${PV}.tar.gz \"
SRC_URI[md5sum] = "99e08503ef24c3e2e3ff74cc5f3be213"
SRC_URI[sha256sum] = "f6ba691f40e30e640efa2752c1f9499a3f9738257660994de70a45fe00d12b64"

EXTRA_OEMAKE = ""

do_install(){
    oe_runmake install DESTDIR=${D} PREFIX=${D} MANDIR=${mandir}
    mkdir -p ${D}${sysconfdir}/logrotate.d
    mkdir -p ${D}${sysconfdir}/cron.daily
    mkdir -p ${D}${localstatedir}/lib
    install -p -m 644 examples/logrotate-default ${D}${sysconfdir}/logrotate.conf
    install -p -m 755 examples/logrotate.cron ${D}${sysconfdir}/cron.daily/logrotate
    touch ${D}${localstatedir}/lib/logrotate.status
}

另见

与 GNU 构建系统的合作

当你总是在相同系统上构建和运行软件时,Makefile是一个不错的解决方案,前提是已知glibcgcc版本以及可用的库版本。然而,大多数软件需要在不同系统上构建和运行。

准备工作

GNU 构建系统或autotools是一组工具,旨在为你的软件创建适用于各种系统的Makefile。它由三个主要工具组成:

  • autoconf:解析configure.ac文件的内容,该文件描述了要构建的源代码,并生成一个configure脚本。然后,这个脚本将用于生成最终的Makefile

  • automake:用于解析Makefile.am文件的内容,并将其转换为Makefile.in文件。然后,早先生成的configure脚本将使用该文件来生成config.status脚本,自动执行该脚本以获得最终的Makefile

  • libtools:用于管理静态和动态库的创建。

如何操作…

Yocto 构建系统包含了构建autotools软件包所需的类。你的配方只需要继承autotools类,并配置要传递给configure脚本的参数,这些参数存储在EXTRA_OECONF变量中。通常,autotools系统知道如何安装软件,因此你不需要重写do_install

有许多开源项目使用autotools作为构建系统。

一个示例,meta-custom/recipes-example/hello/hello_2.9.bb,不需要任何额外的配置选项,如下所示:

DESCRIPTION = "GNU helloworld autotools recipe"
SECTION = "examples"

LICENSE = "GPLv3"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common- licenses/GPL-3.0;md5=c79ff39f19dfec6d293b95dea7b07891"

SRC_URI = "${GNU_MIRROR}/hello/hello-${PV}.tar.gz"
SRC_URI[md5sum] = "67607d2616a0faaf5bc94c59dca7c3cb"
SRC_URI[sha256sum] = "ecbb7a2214196c57ff9340aa71458e1559abd38f6d8d169666846935df191ea7"

inherit autotools gettext

另见

与 CMake 构建系统的合作

GNU make 系统在你仅为 Linux 系统构建时是一个很棒的工具。然而,一些软件包是跨平台的,需要一种方式来管理不同操作系统上的 Makefile 文件。CMake 是一个跨平台的构建系统,它不仅可以与 GNU make 配合使用,还可以与微软 Visual Studio 和苹果的 Xcode 配合使用。

准备工作

CMake 工具解析每个目录中的 CMakeLists.txt 文件,以控制构建过程。以下是编译 hello world 示例的 CMakeLists.txt 文件示例:

cmake_minimum_required(VERSION 2.8.10)
project(helloworld)
add_executable(helloworld helloworld.c)
install(TARGETS helloworld RUNTIME DESTINATION bin)

如何实现…

Yocto 构建系统还包含了用于构建 CMake 包的必需类。你的配方只需继承 cmake 类并配置传递给 configure 脚本的参数,这些参数存储在 EXTRA_OECMAKE 变量中。通常,CMake 系统知道如何安装软件,因此你不需要覆盖 do_install 任务。

一个示例配方,用于构建 helloworld.C 示例应用程序,meta-custom/recipes-example/helloworld-cmake/helloworld-cmake_1.0.bb,如下所示:

DESCRIPTION = "Simple helloworld cmake application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "file://CMakeLists.txt \
           file://helloworld.c"
S = "${WORKDIR}"

inherit cmake

EXTRA_OECMAKE = ""

另见

使用 SCons 构建器

SCons 也是一个跨平台的构建系统,使用 Python 编写,配置文件也是用同样的语言编写。它还支持微软 Visual Studio 等其他功能。

准备工作

SCons 解析 SConstruct 文件,并且默认情况下不会将环境变量传递给构建系统。这是为了避免由于环境差异而引发的构建问题。这对于 Yocto 来说是一个复杂的地方,因为它使用交叉编译工具链设置来配置环境。

SCons 没有定义支持交叉编译的标准方式,因此每个项目都会有所不同。以简单的 hello world 程序为例,我们可以像下面这样从外部环境初始化 CCPATH 变量:

import os
env = Environment(CC = os.environ['CC'],
                  ENV = {'PATH': os.environ['PATH']})
env.Program("helloworld", "helloworld.c")

如何实现…

Yocto 构建系统还包含了用于构建 SCons 包的必需类。你的配方只需继承 SCons 类并配置传递给配置脚本的参数,这些参数存储在 EXTRA_OESCONS 变量中。尽管一些使用 SCons 的软件包可能会通过 SCons 类要求的安装别名处理安装,但你的配方大多需要提供 do_install 任务的覆盖。

一个示例配方,用于构建 helloworld.c 示例应用程序,meta-custom/recipes-example/helloworld-scons/helloworld-scons_1.0.bb,如下所示:

DESCRIPTION = "Simple helloworld scons application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "file://SConstruct \
           file://helloworld.c"

S = "${WORKDIR}"

inherit scons

EXTRA_OESCONS = ""

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

另见

使用库开发

大多数应用程序使用共享库,这可以节省系统内存和磁盘空间,因为它们在不同的应用程序之间共享。将代码模块化成库还可以更容易地进行版本控制和代码管理。

本教程将解释如何在 Linux 和 Yocto 中处理静态库和共享库。

准备工作

按照惯例,库文件以lib前缀开头。

基本上有两种库类型:

  • 静态库.a):当目标代码被链接并成为应用程序的一部分时

  • 动态库.so):在编译时链接,但不包含在应用程序中,因此它们需要在运行时可用。多个应用程序可以共享动态库,从而减少磁盘空间占用。

库文件放置在以下标准根文件系统位置:

  • /lib:启动时所需的库文件

  • /usr/lib:大多数系统库

  • /usr/local/lib:非系统库

动态库遵循运行系统中的某些命名约定,以便多个版本可以共存,因此库可以通过不同的名称引用。以下是一些解释:

  • 链接器的名称以.so结尾;例如,libexample.so

  • 完全限定名或soname,是指向库名称的符号链接。例如,libexample.so.x,其中x是版本号。增加版本号意味着该库与之前的版本不兼容。

  • 真正的名称。例如,libexample.so.x.y[.z],其中x是主版本号,y是次版本号,z(可选)是发布号。增加次版本号或发布号将保持兼容性。

在 GNU glibc中,启动 ELF 二进制文件时会调用程序加载器/lib/ld-linux-X。其中,X是版本号,加载器会找到所有需要的共享库。这个过程使用了几个有趣的文件:

  • /etc/ld.so.conf:存储加载器搜索的目录

  • /etc/ld.so.preload:用于覆盖库文件

ldconfig工具读取ld.so.conf文件并创建一个缓存文件(/etc/ld.so.cache)以提高访问速度。

以下环境变量也可能有帮助:

  • LD_LIBRARY_PATH:这是一个冒号分隔的目录列表,用于搜索库文件。它在调试或使用非标准库位置时非常有用。

  • LD_PRELOAD:用于覆盖共享库。

构建静态库

我们将从两个源文件hello.cworld.c构建一个静态库libhelloworld,并用它来构建一个 Hello World 应用程序。库的源文件如下所示。

以下是hello.c文件的代码:

char * hello (void)
{
  return "Hello";
}

这是world.c文件的代码:

char * world (void)
{
  return "World";
}

构建库时,按照以下步骤操作:

  1. 配置构建环境:

    $ source /opt/poky/1.7.1/environment-setup-cortexa9hf-vfp- neon-poky-linux-gnueabi
    
    
  2. 编译并链接库:

    ${CC} -c hello.c world.c
    ${AR} -cvq libhelloworld.a hello.o world.o
    
    
  3. 验证库的内容:

    ${AR} -t libhelloworld.a
    
    

接下来展示应用程序源代码。

  • 对于helloworld.c文件,以下是代码:

    #include <stdio.h>
    int main (void)
    {
      return printf("%s %s\n",hello(),world());
    }
    
  • 要构建它,我们运行:

    ${CC} -o helloworld helloworld.c libhelloworld.a
    
    
  • 我们可以使用readelf检查它链接的库:

    $ readelf -d helloworld
    Dynamic section at offset 0x534 contains 24 entries:
     Tag        Type                         Name/Value
     0x00000001 (NEEDED)                     Shared library: [libc.so.6]
    
    

构建共享动态库

要从相同的源构建动态库,我们可以运行:

${CC} -fPIC -g -c hello.c world.c
${CC} -shared -Wl,-soname,libhelloworld.so.1 -o libhelloworld.so.1.0 hello.o world.o

我们可以用它来构建我们的helloworld C应用程序,具体如下:

${CC} helloworld.c libhelloworld.so.1.0 -o helloworld

再次,我们可以使用readelf检查动态库,如下所示:

$ readelf -d helloworld
Dynamic section at offset 0x6ec contains 25 entries:
 Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libhelloworld.so.1]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

如何操作…

以下是我们刚才看到的静态库示例的配方,meta-custom/recipes-example/libhelloworld-static/libhelloworldstatic_1.0.bb

DESCRIPTION = "Simple helloworld example static library"
SECTION = "libs"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "file://hello.c \
           file://world.c \
           file://helloworld.pc"
S = "${WORKDIR}"

do_compile() {
        ${CC} -c hello.c world.c
        ${AR} -cvq libhelloworld.a hello.o world.o
}

do_install() {
        install -d ${D}${libdir}
        install -m 0755 libhelloworld.a ${D}${libdir}
}

默认情况下,meta/conf/bitbake.conf中的配置将所有静态库放入-staticdev包中。它也被放置在sysroot中,以便可以使用。

对于动态库,我们将使用以下配方,meta-custom/recipes-example/libhelloworld-dyn/libhelloworlddyn_1.0.bb

meta-custom/recipes-example/libhelloworld-dyn/libhelloworlddyn_1.0.bb
DESCRIPTION = "Simple helloworld example dynamic library"
SECTION = "libs"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "file://hello.c \
           file://world.c \
           file://helloworld.pc"

S = "${WORKDIR}"

do_compile() {
       ${CC} -fPIC -g -c hello.c world.c
       ${CC} -shared -Wl,-soname,libhelloworld.so.1 -o libhelloworld.so.1.0 hello.o world.o
}

do_install() {
       install -d ${D}${libdir}
       install -m 0755 libhelloworld.so.1.0 ${D}${libdir}
       ln -s libhelloworld.so.1.0 ${D}/${libdir}/libhelloworld.so.1
       ln -s libhelloworld.so.1 ${D}/${libdir}/libhelloworld.so
}

通常我们会在RDEPENDS变量中列出库的依赖项(如果有的话),但这并不总是必要的,因为构建系统会通过检查库文件和pkg-config文件自动执行一些依赖检查,并将其发现的依赖项自动添加到RDEPENDS中。

同一库的多个版本可以在运行系统中共存。为此,您需要提供具有相同包名但不同包修订版的不同配方。例如,我们将有libhelloworld-1.0.bblibhelloworld-1.1.bb

为了使用静态库构建应用程序,我们将创建一个配方meta-custom/recipes-example/helloworld-static/helloworldstatic_1.0.bb,如下所示:

DESCRIPTION = "Simple helloworld example"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

DEPENDS = "libhelloworld-static"

SRC_URI = "file://helloworld.c"

S = "${WORKDIR}"

do_compile() {
        ${CC} -o helloworld helloworld.c ${STAGING_LIBDIR}/libhelloworld.a
}

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

若要使用动态库进行构建,我们只需要在meta-custom/recipes-example/helloworld-shared/helloworldshared_1.0.bb中更改配方,改为meta-custom/recipes-example/helloworld-shared/helloworldshared_1.0.bb

meta-custom/recipes-example/helloworld-shared/helloworldshared_1.0.bb
DESCRIPTION = "Simple helloworld example"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

DEPENDS = "libhelloworld-dyn"

SRC_URI = "file://helloworld.c"

S = "${WORKDIR}"

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

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

如何工作…

库应该提供使用它们所需的信息,如include头文件和library依赖项。Yocto 项目为库提供构建设置提供了两种方式:

  • binconfig类。这是一个遗留类,用于那些提供-config脚本来提供构建设置的库。

  • pkgconfig类。这是库提供构建设置的推荐方法。

一个pkg-config构建设置文件具有.pc后缀,随库一起分发,并安装在pkg-config工具已知的常见位置。

动态库的helloworld.pc文件如下所示:

prefix=/usr/local
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib

Name: helloworld
Description: The helloworld library
Version: 1.0.0
Cflags: -I${includedir}/helloworld
Libs: -L${libdir} -lhelloworld

然而,对于静态库,我们需要将最后一行更改为:

Libs: -L${libdir} libhelloworld.a

一个想要使用这个.pc文件的包将继承pkgconfig类。

还有更多…

对于既构建库又构建可执行文件但不希望它们一起安装的包,提供了相应的解决方案。通过继承lib_package类,包将创建一个独立的-bin包来包含可执行文件。

另请参见

与 Linux 帧缓冲区一起工作

Linux 内核提供了一个图形硬件抽象层,以帧缓冲设备的形式呈现。它们允许应用程序通过一个明确定义的 API 访问图形硬件。帧缓冲区还被用来为 Linux 内核提供一个图形控制台,例如,它可以显示颜色和徽标。

在本食谱中,我们将探讨应用程序如何使用 Linux 帧缓冲区来显示图形和视频。

准备工作

一些应用程序,特别是在嵌入式设备中,能够通过映射内存并直接访问帧缓冲区来访问它。例如,gstreamer框架能够直接在帧缓冲区上工作,Qt 图形框架也是如此。

Qt 是一个跨平台的应用程序框架,使用 C++编写,由 Digia 公司(以 Qt 公司名义)和开源 Qt 项目社区共同开发。

对于 Qt 应用程序,Poky 提供了qt4e-demo-image,而 FSL 社区 BSP 提供了qte-in-use-image,这两个镜像都包括对 Qt4 扩展在帧缓冲区上的支持。提供的框架还包括硬件加速支持——不仅支持视频,还支持通过 OpenGL 和 OpenVG API 提供的 2D 和 3D 图形加速。

如何操作…

要编译我们之前在开发 Qt 应用程序食谱中看到的 Qt Hello World 应用程序,我们可以使用以下meta-custom/recipes-qt/qt-helloworld/qt-helloworld_1.0.bb Yocto 食谱:

DESCRIPTION = "Simple QT helloworld example"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

RDEPENDS_${PN} += "icu"

SRC_URI = "file://qt_hello_world.cpp \
           file://qt_hello_world.pro"

S = "${WORKDIR}"

inherit qt4e

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

这里是meta-custom/recipes-qt/qt-helloworld/qt-helloworld-1.0/qt_hello_world.cpp源文件:

#include <QApplication>
#include <QPushButton>

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);

     QPushButton hello("Hello world!");

     hello.show();
     return app.exec();
 }

另外,meta-custom/recipes-qt/qt-helloworld/qt-helloworld-1.0/qt_hello_world.pro项目文件如下:

SOURCES += \
   qt_hello_world.cpp

然后我们通过在项目的conf/local.conf文件中使用以下内容将其添加到镜像中:

IMAGE_INSTALL_append = " qt-helloworld"

然后我们使用以下命令构建镜像:

$ bitbake qt4e-demo-image

然后我们可以将 SD 卡镜像编程,启动它,登录到 Wandboard,并通过运行以下命令启动应用程序:

# qt_hello_world -qws

运行服务器应用程序时需要-qws命令行选项。

它是如何工作的…

帧缓冲设备位于/dev目录下。默认的帧缓冲设备是/dev/fb0,如果图形硬件提供多个帧缓冲设备,它们将按顺序编号。

默认情况下,Wandboard 启动时会有两个帧缓冲设备,fb0fb1。第一个是默认的视频显示,第二个是叠加平面,可用于在显示器上组合内容。

然而,i.MX6 SoC 支持最多四个显示器,因此它除了两个叠加帧缓冲区外,还可以有最多四个帧缓冲设备。

你可以通过FRAMEBUFFER环境变量更改应用程序使用的默认帧缓冲区。例如,如果你的硬件支持多个帧缓冲区,你可以通过运行以下命令使用第二个帧缓冲区:

# export FRAMEBUFFER=/dev/fb1

帧缓冲设备是内存映射的,你可以在它们上执行文件操作。例如,你可以通过运行以下命令清除屏幕内容:

# cat /dev/zero > /dev/fb0

或者使用以下命令复制它:

# cat /dev/fb0 > fb.raw

你甚至可以通过以下命令恢复内容:

# cat fb.raw > /dev/fb0

用户空间程序也可以通过 ioctls 查询帧缓冲区或以编程方式修改其配置,或者通过控制台使用 fbset 应用程序,这个程序包含在 Yocto 的核心镜像中,作为 BusyBox 小程序。

# fbset -fb /dev/fb0
mode "1920x1080-60"
 # D: 148.500 MHz, H: 67.500 kHz, V: 60.000 Hz
 geometry 1920 1080 1920 1080 24
 timings 6734 148 88 36 4 44 5
 accel false
 rgba 8/16,8/8,8/0,0/0
endmode

你可以通过从 U-Boot 引导加载程序传递 video 命令行选项到 Linux 内核,来配置帧缓冲 HDMI 设备的特定分辨率、每像素位数和刷新率。具体格式取决于设备的帧缓冲驱动,对于 Wandboard,格式如下:

video=mxcfbn:dev=hdmi,<xres>x<yres>M[@rate]

其中:

  • n 是帧缓冲区的编号

  • xres 是水平分辨率

  • yres 是垂直分辨率

  • M 表示要使用 VESA 协调视频时序来计算时序,而不是从查找表中获取。

  • rate 是刷新率

例如,对于 fb0 帧缓冲区,你可以使用:

video=mxcfb0:dev=hdmi,1920x1080M@60

提示

请注意,在一段时间的不活动后,虚拟控制台将会消失。要恢复显示,可以使用:

# echo 0 > /sys/class/graphics/fb0/blank

还有更多…

FSL 社区 BSP 层还提供了一个 fsl-image-multimedia 目标镜像,包含 gstreamer 框架和利用 i.MX6 SoC 内硬件加速特性的插件。此外,还提供了一个 fsl-image-multimedia-full 镜像,扩展了对更多 gstreamer 插件的支持。

要构建带有帧缓冲区支持的 fsl-image-multimedia 镜像,你需要通过将以下内容添加到 conf/local.conf 文件来移除图形分发特性:

DISTRO_FEATURES_remove = "x11 directfb wayland"

然后用以下命令构建镜像:

$ bitbake fsl-image-multimedia

生成的 fsl-image-multimedia-wandboard-quad.sdcard 镜像位于 tmp/deploy/images,可以将其写入 microSD 卡并启动。

默认的 Wandboard 设备树定义了一个 mxcfb1 节点,如下所示:

       mxcfb1: fb@0 {
                compatible = "fsl,mxc_sdc_fb";
                disp_dev = "hdmi";
                interface_pix_fmt = "RGB24";
                mode_str ="1920x1080M@60";
                default_bpp = <24>;
                int_clk = <0>;
                late_init = <0>;
        };

因此,连接一个 1920x1080 的 HDMI 显示器应该会显示一个带有 Poky 登录提示符的虚拟终端。

然后,我们可以使用 gstreamer 命令行工具 gst-launch 来构建 gstreamer 管道。例如,要在帧缓冲区上查看硬件加速的视频,你可以下载 Big Bunny 预告片的全高清文件,并通过 gstreamer 框架的 gst-launch 命令行工具播放该视频,命令如下:

# cd /home/root
# wget http://video.blendertestbuilds.de/download.blender.org/peach/trailer_ 1080p.mov
# gst-launch playbin2 uri=file:///home/root/trailer_1080p.mov

视频将使用 Freescale 的 h.264 视频解码插件 vpudec,它利用 i.MX6 SoC 中的硬件视频处理单元来解码 h.264 视频。

你可以通过运行以下命令来查看可用的 i.MX6 特定插件列表:

# gst-inspect | grep imx
h264.imx:  mfw_h264decoder: h264 video decoder
audiopeq.imx:  mfw_audio_pp: audio post equalizer
aiur.imx: webm: webm
aiur.imx:  aiurdemux: aiur universal demuxer
mpeg2dec.imx:  mfw_mpeg2decoder: mpeg2 video decoder
tvsrc.imx:  tvsrc: v4l2 based tv src
ipucsc.imx:  mfw_ipucsc: IPU-based video converter
mpeg4dec.imx:  mfw_mpeg4aspdecoder: mpeg4 video decoder
vpu.imx:  vpudec: VPU-based video decoder
vpu.imx:  vpuenc: VPU-based video encoder
mp3enc.imx:  mfw_mp3encoder: mp3 audio encoder
beep.imx: ac3: ac3
beep.imx: 3ca: ac3
beep.imx:  beepdec: beep audio decoder
beep.imx:  beepdec.vorbis: Vorbis decoder
beep.imx:  beepdec.mp3: MP3 decoder
beep.imx:  beepdec.aac: AAC LC decoder
isink.imx:  mfw_isink: IPU-based video sink
v4lsink.imx:  mfw_v4lsink: v4l2 video sink
v4lsrc.imx:  mfw_v4lsrc: v4l2 based camera src
amrdec.imx:  mfw_amrdecoder: amr audio decoder

参见

使用 X Windows 系统

X Windows 系统为 GUI 环境提供了框架——例如在显示器上绘制和移动窗口,以及与鼠标、键盘和触摸屏等输入设备进行交互。其协议版本已经有二十多年历史,因此也被称为 X11。

准备工作

X Windows 系统的参考实现是X.Org服务器,发布采用 MIT 等宽松许可协议。它使用客户端/服务器模型,服务器与多个客户端程序进行通信,处理用户输入并接受图形输出。X11 协议具有网络透明性,意味着客户端和服务器可以运行在不同的机器上,拥有不同的架构和操作系统。然而,大多数情况下,它们都运行在同一台机器上,并通过本地套接字进行通信。

X11 中未定义用户界面规范(如按钮或菜单样式),这将由其他窗口管理器应用程序定义,这些窗口管理器通常是桌面环境的一部分,如 Gnome 或 KDE。

X11 具有输入和视频驱动程序来处理硬件。例如,它有一个帧缓冲驱动程序fbdev,可以输出到非加速的 Linux 帧缓冲区;还有evdev,一个通用的 Linux 输入设备驱动程序,支持鼠标、键盘、平板和触摸屏。

X11 Windows 系统的设计使其对嵌入式设备来说较为繁重,尽管像四核 i.MX6 这样强大的设备使用时没有问题,许多嵌入式设备还是选择其他图形替代方案。然而,许多图形应用程序,主要来自桌面环境,仍然运行在 X11 Windows 系统上。

FSL 社区的 BSP 层为 i.MX6 SoC 提供了一个硬件加速的 X 视频驱动程序xf86-video-imxfb-vivante,该驱动程序包含在基于 X11 的core-image-sato目标镜像和其他图形镜像中。

X 服务器通过/etc/X11/xorg.conf文件进行配置,该文件将加速设备配置如下:

Section "Device"
    Identifier  "i.MX Accelerated Framebuffer Device"
    Driver      "vivante"
    Option      "fbdev"     "/dev/fb0"
    Option      "vivante_fbdev" "/dev/fb0"
    Option      "HWcursor"  "false"
EndSection

图形加速由 i.MX6 SoC 中包含的 Vivante GPU 提供。

不建议进行低级 X11 开发,推荐使用 GTK+和 Qt 等工具包。我们将看到如何将这两种类型的图形应用程序集成到我们的 Yocto 目标镜像中。

如何做……

SATO 是基于Gnome Mobile and EmbeddedGMAE)的 Poky 发行版的默认视觉样式。它是一个基于 GTK+的桌面环境,使用 matchbox-window-manager。其特点是一次只显示一个全屏窗口。

要构建我们之前介绍的 GTK Hello World 应用程序meta-custom/recipes-graphics/gtk-helloworld/gtk-helloworld-1.0/gtk_hello_world.c,可以按照如下方式进行:

#include <gtk/gtk.h>

int main(int argc, char *argv[])
{
    GtkWidget *window;
    gtk_init (&argc, &argv);
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_widget_show (window);
    gtk_main ();
    return 0;
}

我们可以使用以下meta-custom/recipes-graphics/gtk-helloworld/gtk-helloworld_1.0.bb配方:

DESCRIPTION = "Simple GTK helloworld application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "file://gtk_hello_world.c"

S = "${WORKDIR}"

DEPENDS = "gtk+"

inherit pkgconfig

do_compile() {
    ${CC} gtk_hello_world.c -o helloworld `pkg-config --cflags -- libs gtk+-2.0`
}

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

然后,我们可以通过以下命令将该软件包添加到core-image-sato镜像中:

IMAGE_INSTALL_append = " gtk-helloworld"

我们可以通过以下命令从串口终端构建、编程并运行应用程序:

# export DISPLAY=:0
# helloworld

还有更多……

Qt 框架也支持加速图形输出,可以直接在帧缓冲区上实现(就像我们之前看到的qt4e-demo-image目标中那样),或者使用core-image-sato中提供的 X11 服务器。

要在前一个示例中介绍的 Qt hello world 源码上构建 X11,我们可以使用以下 Yocto 配方meta-custom/recipes-qt/qtx11-helloworld/qtx11-helloworld_1.0.bb

DESCRIPTION = "Simple QT over X11 helloworld example"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

RDEPENDS_${PN} += "icu"

SRC_URI = "file://qt_hello_world.cpp \
           file://qt_hello_world.pro"

S = "${WORKDIR}"

inherit qt4x11

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

然后我们还需要将 Qt4 框架添加到目标图像以及应用程序。

EXTRA_IMAGE_FEATURES += "qt4-pkgs"
IMAGE_INSTALL_append = " qtx11-helloworld"

然后我们可以使用以下命令构建core-image-sato

$ bitbake core-image-sato

程序和启动我们的目标。然后使用以下命令运行应用程序:

# export DISPLAY=:0
# qt_hello_world

另请参阅

  • 更多关于 X.Org 服务器的信息,请访问www.x.org

  • Qt 应用程序框架的文档可以在qt-project.org/找到

  • 更多关于 GTK+的信息和文档,请访问www.gtk.org/

使用 Wayland

Wayland 是一个显示服务器协议,旨在取代 X Window 系统,其采用 MIT 许可证。

本篇将概述 Wayland,包括与 X Window 系统的一些关键区别,并展示如何在 Yocto 中使用它。

准备工作

Wayland 协议采用客户端/服务器模型,其中客户端是请求在屏幕上显示像素缓冲区的图形应用程序,服务器或合成器是控制这些缓冲区显示的服务提供者。

Wayland 合成器可以是 Linux 显示服务器、X 应用程序或特殊的 Wayland 客户端。Weston 是 Wayland 项目中的参考合成器。它用 C 语言编写,并与 Linux 内核 API 一起工作。它依赖于evdev来处理输入事件。

Wayland 在 Linux 内核中使用Direct Rendering Manager (DRM),不需要像 X 服务器那样的东西。客户端通过自身的渲染库或类似 Qt 或 GTK+的引擎将窗口内容渲染到与合成器共享的缓冲区中。

Wayland 缺乏 X 的网络透明特性,但未来可能会添加类似功能。

它还比 X 具有更好的安全功能,并设计为提供保密性和完整性。Wayland 不允许应用程序查看其他程序的输入、捕获其他输入事件或生成虚假输入事件。它还更好地保护窗口输出。然而,这也意味着它目前无法提供桌面 X 系统中我们习惯的某些功能,如屏幕捕获或辅助功能程序中常见的功能。

比 X.Org 更轻量且更安全,Wayland 更适合在嵌入式系统中使用。如果需要,X.Org 可以作为 Wayland 的客户端以实现向后兼容性。

然而,Wayland 并没有像 X11 那样成熟,而且 Poky 中基于 Wayland 的图像没有像基于 X11 的那些得到那么多社区关注。

如何操作…

Poky 提供了一个包含 Weston 合成器的core-image-weston镜像。

将我们的 GTK Hello World 示例从使用 X Windows 系统的配方修改为使用 GTK3 并用 Weston 运行是直接的。

DESCRIPTION = "Simple GTK3 helloworld application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "file://gtk_hello_world.c"

S = "${WORKDIR}"

DEPENDS = "gtk+3"

inherit pkgconfig

do_compile() {
    ${CC} gtk_hello_world.c -o helloworld `pkg-config --cflags -- libs gtk+-3.0`
}

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

为了构建它,配置您的conf/local.conf文件,通过如下方式删除x11发行版特性:

DISTRO_FEATURES_remove = "x11"

注意

当更改DISTRO_FEATURES变量时,您需要从头开始构建,删除tmpsstate-cache目录。

使用以下命令将应用程序添加到镜像中:

IMAGE_INSTALL_append = " gtk3-helloworld"

然后使用以下命令构建镜像:

$ cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad
$ bitbake core-image-weston

一旦构建完成,您将在tmp/deploy/images/wandboard-quad下找到准备好编程的 microSD 卡镜像。

然后,您可以通过运行以下命令启动该应用程序:

# export XDG_RUNTIME_DIR=/var/run/user/root
# helloworld

还有更多内容…

FSL 社区的 BSP 发布版支持在 Wayland 中使用 i.MX6 SoC 中包含的 Vivante GPU 进行硬件加速图形处理。

这意味着像gstreamer这样的应用程序在与 Weston 合成器一起运行时,将能够提供硬件加速的输出。

Wayland 支持也可以在像 Clutter 和 GTK3+这样的图形工具包中找到。

另见

添加 Python 应用程序

在 Yocto 1.7 中,Poky 支持构建 Python 2 和 Python 3 应用程序,并在meta/recipes-devtools/python目录中包含了一小套 Python 开发工具。

meta-python层中提供了更多种类的 Python 应用程序,该层作为meta-openembedded的一部分,您可以将其添加到conf/bblayers.conf文件中。

准备就绪

打包 Python 模块的标准工具是distutils,它同时适用于 Python 2 和 Python 3。Poky 包括distutils类(Python 3 中的distutils3),用于构建使用distutils的 Python 包。meta-python中有一个使用distutils类的示例配方:meta-python/recipes-devtools/python/python-pyusb_1.0.0a2.bb

SUMMARY = "PyUSB provides USB access on the Python language"
HOMEPAGE = "http://pyusb.sourceforge.net/"
SECTION = "devel/python"
LICENSE = "BSD"
LIC_FILES_CHKSUM = "file://LICENSE;md5=a53a9c39efcfb812e2464af14afab013"
DEPENDS = "libusb1"
PR = "r1"

SRC_URI = "\
    ${SOURCEFORGE_MIRROR}/pyusb/${SRCNAME}-${PV}.tar.gz \
"
SRC_URI[md5sum] = "9136b3dc019272c62a5b6d4eb624f89f"
SRC_URI[sha256sum] = "dacbf7d568c0bb09a974d56da66d165351f1ba3c4d5169ab5b734266623e1736"

SRCNAME = "pyusb"
S = "${WORKDIR}/${SRCNAME}-${PV}"

inherit distutils

然而,distutils不安装包依赖项,不允许卸载包,也不允许安装同一包的多个版本,因此仅推荐用于简单需求。因此,setuptools被开发出来以扩展distutils。它不包含在标准的 Python 库中,但在 Poky 中是可用的。Poky 中也有一个setuptools类(Python 3 中是setuptools3),用于构建与setuptools一起分发的 Python 包。

如何操作…

为了使用setuptools构建 Python Hello World 示例应用程序,我们可以使用以下 Yocto meta-custom/recipes-python/python-helloworld/pythonhelloworld_1.0.bb配方:

DESCRIPTION = "Simple Python setuptools hello world application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

SRC_URI = "file://setup.py \
      file://python-helloworld.py \
      file://helloworld/__init__.py \
              file://helloworld/main.py"

S = "${WORKDIR}"

inherit setuptools

do_install_append () {
    install -d ${D}${bindir}
    install -m 0755 python-helloworld.py ${D}${bindir}
}

为了创建一个示例的 Hello World 包,我们创建如下截图所示的目录结构:

https://github.com/OpenDocCN/freelearn-linux-pt4-zh/raw/master/docs/emb-linux-proj-yocto-proj-cb/img/5186OS_04_22.jpg

这是相同目录结构的代码:

$ mkdir -p meta-custom/recipes-python/python-helloworld/python- helloworld-1.0/helloworld/
$ touch meta-custom/recipes-python/python-helloworld/python- helloworld-1.0/helloworld/__init__.py

然后创建以下meta-custom/recipes-python/python-helloworld/python-helloworld-1.0/setup.py Python 设置文件:

import sys
from setuptools import setup

setup(
    name = "helloworld",
    version = "0.1",
    packages=["helloworld"],
    author="Alex Gonzalez",
    author_email = "alex@example.com",
    description = "Hello World packaging example",
    license = "MIT",
    keywords= "example",
    url = "",
)

以及meta-custom/recipes-python/python-helloworld/python-helloworld-1.0/helloworld/main.py Python 文件:

import sys

def main(argv=None):
    if argv is None:
        argv = sys.argv
    print "Hello world!"
    return 0

以及一个meta-custom/recipes-python/python-helloworld/python-helloworld-1.0/python-helloworld.py测试脚本,利用该模块:

#!/usr/bin/env python
import sys
import helloworld.main

if __name__ == '__main__':
       sys.exit(helloworld.main.main())

然后我们可以通过以下命令将其添加到我们的镜像中:

IMAGE_INSTALL_append = " python-helloworld"

然后使用以下命令构建:

$ cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad
$ bitbake core-image-minimal

编程并启动后,我们可以通过运行示例脚本来测试该模块:

# /usr/bin/python-helloworld.py
Hello world!

还有更多……

meta-python中,你还可以找到python-pip食谱,它会将pip工具添加到目标镜像中。pip可以用于从Python 包索引PyPI)安装软件包。

你可以使用以下命令将其添加到镜像中:

IMAGE_INSTALL_append  = " python-pip python-distribute"

你需要将meta-openembedded/meta-python层添加到conf/bblayers.conf文件中,以便构建镜像,还需要python-distribute依赖项,它是python-pip所必需的。然后,你可以使用以下命令为core-image-minimal镜像构建:

$ cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad
$ bitbake core-image-minimal

安装完成后,你可以按照以下方式从目标设备使用它:

# pip search <package_name>
# pip install <package_name>

集成 Oracle Java 运行时环境

Oracle 提供了两种专门用于嵌入式开发的 Java 版本:

  • Java SE 嵌入式:这是标准 Java SE 桌面版的一个大型子集。与标准版本相比,它包含了针对大小和内存使用的优化,以适应中型嵌入式设备的需求。

  • Java 微型版ME):该版本面向无头的低端和中端设备,是 Java SE 的一个子集,符合连接受限设备配置CLDC),并为嵌入式市场提供一些额外的功能和工具。Oracle 提供了几个参考实现,但 Java ME 必须从源代码中单独集成到特定平台。

我们将专注于 Java SE 嵌入式,它可以从 Oracle 的下载站点以二进制格式下载。

Java SE 嵌入式是商业授权的,并且在嵌入式部署中需要支付版税。

准备就绪

Yocto 有一个meta-oracle-java层,旨在帮助集成官方 Oracle Java 运行时环境JRE)版本 7。然而,由于 Oracle 的网页需要登录并接受其许可协议,因此无法实现无人干预的安装。

在 Java SE 嵌入式版本 7 中,Oracle 提供了软浮点和硬浮点版本的无头和有头 JRE,用于 ARMv6/v7,以及为 ARMv5 提供的软浮点用户空间的无头版本 JRE。Java SE 嵌入式版本 7 为 ARM Linux 提供了两种不同的Java 虚拟机JVM):

  • 一个针对响应性的客户端 JVM

  • 一个与客户端 JVM 相同的服务器 JVM,但优化了长时间运行的应用程序

截至本文撰写时,meta-oracle-java 层仅有一个针对带客户端 JVM 的无头硬浮动点版本的配方。我们将为最新的 Java 7 SE 嵌入式版本(更新 75)添加配方,涵盖无头和带头部的硬浮动点 JRE,这些版本适用于像 wandboard-quad 这样的基于 i.MX6 的板。

如何操作…

要安装 Java SE 嵌入式运行时环境,首先需要将 meta-oracle-java 层克隆到我们的源目录中,并将其添加到 conf/bblayers.conf 文件中,方法如下:

$ cd /opt/yocto/fsl-community-bsp/sources
$ git clone git://git.yoctoproject.org/meta-oracle-java

然后我们需要通过在 conf/local.conf 文件中添加以下内容,明确接受 Oracle Java 许可证:

LICENSE_FLAGS_WHITELIST += "oracle_java"

我们希望构建最新的更新,因此我们将以下 meta-custom/recipes-devtools/oracle-java/oracle-jse-ejre-arm-vfphflt-client-headless_1.7.0.bb 配方添加到我们的 meta-custom 层中:

SUMMARY = "Oracle Java SE runtime environment binaries"

JDK_JRE = "ejre"
require recipes-devtools/oracle-java/oracle-jse.inc

PV_UPDATE = "75"
BUILD_NUMBER = "13"

LIC_FILES_CHKSUM = "\
       file://${WORKDIR}/${JDK_JRE}${PV}_${PV_UPDATE}/COPYRIGHT;md5=0b204 bd2921accd6ef4a02f9c0001823 \
       file://${WORKDIR}/${JDK_JRE}${PV}_${PV_UPDATE}/THIRDPARTYLICENSERE ADME.txt;md5=f3a388961d24b8b72d412a079a878cdb \
       "

SRC_URI = "http://download.oracle.com/otn/java/ejre/7u${PV_UPDATE}- b${BUILD_NUMBER}/ejre-7u${PV_UPDATE}-fcs-b${BUILD_NUMBER}-linux- arm-vfp-hflt-client_headless-18_dec_2014.tar.gz"
SRC_URI[md5sum] = "759ca6735d77778a573465b1e84b16ec"
SRC_URI[sha256sum] = "ebb6499c62fc12e1471cff7431fec5407ace59477abd0f48347bf6e89c6bff3b"

RPROVIDES_${PN} += "java2-runtime"

尝试使用以下命令构建配方:

$ bitbake oracle-jse-ejre-arm-vfp-hflt-client-headless

你会看到我们遇到了校验和不匹配的错误。这是由于在 Oracle 网站上的许可证接受步骤所致。为了解决这个问题,我们需要手动下载文件到 downloads 目录中,路径由我们项目的 DL_DIR 配置变量指定。

然后我们可以将 JRE 添加到目标镜像中:

IMAGE_INSTALL_append = " oracle-jse-ejre-arm-vfp-hflt-client- headless"

然后使用以下命令构建:

$ cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad
$ bitbake core-image-minimal

现在我们可以登录目标设备并运行它,使用命令:

# /usr/bin/java -version
java version "1.7.0_75"
Java(TM) SE Embedded Runtime Environment (build 1.7.0_75-b13, headless)
Java HotSpot(TM) Embedded Client VM (build 24.75-b04, mixed mode)

我们还可以使用以下 meta-custom/recipes-devtools/oracle-java/oracle-jse-ejre-arm-vfphflt-client-headful_1.7.0.bb 配方构建带头部版本:

SUMMARY = "Oracle Java SE runtime environment binaries"

JDK_JRE = "ejre"
require recipes-devtools/oracle-java/oracle-jse.inc

PV_UPDATE = "75"
BUILD_NUMBER = "13"

LIC_FILES_CHKSUM = "\
       file://${WORKDIR}/${JDK_JRE}${PV}_${PV_UPDATE}/COPYRIGHT;md5=0b204 bd2921accd6ef4a02f9c0001823 \
       file://${WORKDIR}/${JDK_JRE}${PV}_${PV_UPDATE}/THIRDPARTYLICENSERE ADME.txt;md5=f3a388961d24b8b72d412a079a878cdb \
       "

SRC_URI = "http://download.oracle.com/otn/java/ejre/7u${PV_UPDATE}- b${BUILD_NUMBER}/ejre-7u${PV_UPDATE}-fcs-b${BUILD_NUMBER}-linux- arm-vfp-hflt-client_headful-18_dec_2014.tar.gz"

SRC_URI[md5sum] = "84dba4ffb47285b18e6382de2991edfc"
SRC_URI[sha256sum] = "5738ffb8ce2582b6d7b39a3cbe16137d205961224899f8380eebe3922bae5c61"

RPROVIDES_${PN} += "java2-runtime"

然后使用以下命令将其添加到目标镜像中:

IMAGE_INSTALL_append =  " oracle-jse-ejre-arm-vfp-hflt-client- headful"

然后使用以下命令构建 core-image-sato

$ cd cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad
$ bitbake core-image-sato

在这种情况下,报告的 Java 版本是:

# /usr/bin/java -version
java version "1.7.0_75"
Java(TM) SE Embedded Runtime Environment (build 1.7.0_75-b13)
Java HotSpot(TM) Embedded Client VM (build 24.75-b04, mixed mode)

还有更多内容…

截至本文撰写时,最新版本是 Java SE 嵌入式版本 8 更新 33(8u33)。

Oracle 仅提供 JDK 下载,且需要使用一个主机工具 jrecreate 来配置并创建适当的 JRE。该工具允许我们在不同的 JVM(最小版、客户端版和服务器版)以及软硬浮动点 ABI、JavaFX 扩展、本地化和 JVM 的其他一些调优选项之间进行选择。

Oracle Java SE 嵌入式版本 8 提供对仅适用于 ARMv7 硬浮动点用户空间的 X11 开发的支持,支持使用 Swing、AWT 和 JavaFX,且包括对 Freescale i.MX6 处理器上 JavaFX(旨在替代 Swing 和 AWT 的图形框架)的支持。

截至本文撰写时,还没有 Yocto 配方可以集成 Java 版本 8。

集成开放 Java 开发工具包

Oracle Java SE 嵌入式的开源替代品是 开放 Java 开发工具包OpenJDK),它是 Java SE 的开源实现,基于 GPLv2 许可证并附带类路径例外,这意味着应用程序可以链接而不受 GPL 许可证的约束。

这个配方将展示如何使用 Yocto 构建 OpenJDK,并将 JRE 集成到我们的目标镜像中。

准备工作

OpenJDK 的主要组件包括:

  • 热点 Java 虚拟机

  • Java 类库JCL

  • Java 编译器,javac

最初,OpenJDK 需要使用专有的 JDK 进行构建。然而,IcedTea项目使我们能够使用 GNU 类库、GNU 的 Java 编译器(GCJ)来构建 OpenJDK,并引导 JDK 来构建 OpenJDK。它还通过补充一些 Java SE 中缺失的组件(如网页浏览器插件和 Web Start 实现)来完善 OpenJDK。

Yocto 可以使用meta-java层构建 OpenJDK,其中包括使用 IcedTea 进行交叉编译 OpenJDK 的配方。

你可以从其 Git 仓库下载 OpenJDK,地址为git.yoctoproject.org/cgit/cgit.cgi/meta-java/

开发讨论可以通过访问开发邮件列表lists.openembedded.org/mailman/listinfo/openembedded-devel进行跟踪和参与。

meta-java层还包括多种 Java 库和虚拟机的配方,以及应用开发工具,如antfastjar

如何实现…

要构建 OpenJDK 7,你需要按如下方式克隆meta-java层:

$ cd /opt/yocto/fsl-community-bsp/sources/
$ git clone http://git.yoctoproject.org/cgit/cgit.cgi/meta-java/

在写这篇文章时,还没有 1.7 版本的 Dizzy 分支,所以我们将直接使用主分支。

将该层添加到你的conf/bblayers.conf文件中:

+ ${BSPDIR}/sources/meta-java \
 "

然后通过将以下内容添加到你的conf/local.conf文件中来配置项目:

PREFERRED_PROVIDER_virtual/java-initial = "cacao-initial"
PREFERRED_PROVIDER_virtual/java-native = "jamvm-native"
PREFERRED_PROVIDER_virtual/javac-native = "ecj-bootstrap-native"
PREFERRED_VERSION_openjdk-7-jre = "25b30-2.3.12"
PREFERRED_VERSION_icedtea7-native = "2.1.3"

然后你可以通过以下方式将 OpenJDK 包添加到你的镜像中:

IMAGE_INSTALL_append = " openjdk-7-jre"

并构建你选择的镜像:

$ cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad
$ bitbake core-image-sato

当你运行目标镜像时,你将看到以下 Java 版本:

# java -version
java version "1.7.0_25"
OpenJDK Runtime Environment (IcedTea 2.3.12) (25b30-2.3.12)
OpenJDK Zero VM (build 23.7-b01, mixed mode)

它是如何工作的…

为了测试 JVM,我们可以在主机上字节编译一个 Java 类并将其复制到目标系统上执行。例如,我们可以使用以下简单的HelloWorld.java示例:

class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}

为了在主机上进行字节编译,我们需要安装 Java SDK。在 Ubuntu 中安装 Java SDK,只需运行:

$ sudo apt-get install openjdk-7-jdk

要字节编译示例,我们执行:

$ javac HelloWorld.java

要运行它,我们将HelloWorld.class复制到目标系统,并在同一文件夹中运行:

# java HelloWorld

还有更多…

在生产系统上使用 OpenJDK 时,建议始终使用最新的发布版本,其中包含了错误修复和安全更新。在写这篇文章时,最新的 OpenJDK 7 发布版本是更新 71(jdk7u71b14),可以使用 IcedTea 2.5.3 构建,因此meta-java配方应进行更新。

另见

集成 Java 应用程序

meta-java层还提供了帮助类,以便简化将 Java 库和应用程序集成到 Yocto 中的过程。在本配方中,我们将看到一个使用提供的类构建 Java 库的示例。

准备工作

meta-java层提供了两个主要的类,帮助集成 Java 应用程序和库:

  • Java bbclass:这提供了默认的目标目录和一些辅助功能,即:

    • oe_jarinstall:这将安装并创建 JAR 文件的符号链接

    • oe_makeclasspath:这是一个根据 JAR 文件名生成类路径字符串的工具。

    • oe_java_simple_wrapper:这是一个将 Java 应用程序封装在 shell 脚本中的工具。

  • java-library bbclass:该类继承了 Java 的 bbclass,并扩展其功能以创建和安装 JAR 文件。

如何实现……

我们将使用以下的meta-custom/recipes-java/java-helloworld/java-helloworld-1.0/HelloWorldSwing.java图形化 Swing Hello World 作为示例:

import javax.swing.JFrame;
import javax.swing.JLabel;

public class HelloWorldSwing {
    private static void createAndShowGUI() {
        JFrame frame = new JFrame("Hello World!");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JLabel label = new JLabel("Hello World!");
        frame.getContentPane().add(label);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

要集成这个HelloWorldSwing应用程序,我们可以使用一个 Yocto meta-custom/recipes-java/java-helloworld/java-helloworld_1.0.bb食谱,如下所示:

DESCRIPTION = "Simple Java Swing hello world application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4 f302"

RDEPENDS_${PN} = "java2-runtime"

SRC_URI = "file://HelloWorldSwing.java"

S = "${WORKDIR}"

inherit java-library

do_compile() {
        mkdir -p build
        javac -d build `find . -name "*.java"`
        fastjar cf ${JARFILENAME} -C build .
}

BBCLASSEXTEND = "native"

该食谱也可以为主机本地架构构建。我们可以通过提供一个单独的java-helloworld-native食谱,该食谱继承native类,或者像之前一样使用BBCLASSEXTEND变量来实现。在这两种情况下,我们可以使用_class-native_class-target覆盖选项,以区分本地和目标功能。

即使 Java 是字节编译的,且编译后的类在两者之间是相同的,但显式地添加本地支持仍然是有意义的。

它是如何工作的……

java-library类将创建一个名为lib<package>-java的库包。

要将其添加到目标镜像中,我们将使用:

IMAGE_INSTALL_append = " libjava-helloworld-java"

然后我们可以决定是否使用 Oracle JRE 或 OpenJDK 运行应用程序。对于 OpenJDK,我们将向我们的镜像中添加以下软件包:

IMAGE_INSTALL_append = " openjdk-7-jre openjdk-7-common"

对于 Oracle JRE,我们将使用以下内容:

IMAGE_INSTALL_append = " oracle-jse-ejre-arm-vfp-hflt-client- headful"

目前可用的 JRE 无法在帧缓冲或 Wayland 上运行,因此我们将使用基于 X11 的图形镜像,如core-image-sato

$ cd /opt/yocto/fsl-community-bsp/
$ source setup-environment wandboard-quad
$ bitbake core-image-sato

然后我们可以启动目标系统,登录并通过运行以下命令使用 OpenJDK 执行示例:

# export DISPLAY=:0
# java -cp /usr/share/java/java-helloworld.jar HelloWorldSwing

还有更多内容……

在写本文时,从meta-java层主分支构建的 OpenJDK 无法运行 X11 应用程序,并将出现以下异常:

Exception in thread "main" java.awt.AWTError: Toolkit not found: sun.awt.X11.XToolkit
        at java.awt.Toolkit$2.run(Toolkit.java:875)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.awt.Toolkit.getDefaultToolkit(Toolkit.java:860)
        at java.awt.Toolkit.getEventQueue(Toolkit.java:1730)
        at java.awt.EventQueue.invokeLater(EventQueue.java:1217)
        at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1287)
        at HelloWorldSwing.main(HelloWorldSwing.java:17)

然而,预编译的 Oracle JRE 能够无问题地运行该应用程序:

# export DISPLAY=:0
# /usr/bin/java -cp /usr/share/java/java-helloworld.jar HelloWorldSwing

注意

如果在使用 Oracle JRE 构建软件包时遇到构建错误,可以尝试使用其他包格式,例如 IPK,通过在conf/local.conf配置文件中添加以下内容:

PACKAGE_CLASSES = "package_ipk"

这是由于meta-oracle-java层中的 RPM 包管理器存在依赖问题,具体情况请参见该层的 README 文件。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐