使用-Yocto-项目学习-Linux-嵌入式编程-全-

使用 Yocto 项目学习 Linux 嵌入式编程(全)

原文:zh.annas-archive.org/md5/6A5B9E508EC2401ECE20C211D2D71910

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

关于当今的 Linux 环境,本书中解释的大多数主题已经可用并且有详细的介绍。本书还涵盖了大量信息,并帮助创建许多观点。当然,本书中还介绍了一些关于各种主题的很好的书籍,并在这里,您将找到对它们的引用。然而,本书的范围并不是再次呈现这些信息,而是在传统的嵌入式开发过程中与 Yocto 项目使用的方法之间进行对比。

本书还介绍了您在嵌入式 Linux 中可能遇到的各种挑战,并为其提出了解决方案。尽管本书旨在面向对其基本 Yocto 和 Linux 技能相当自信并试图改进它们的开发人员,但我相信那些在这个领域没有真正经验的人也可以在这里找到一些有用的信息。

本书围绕您在嵌入式 Linux 之旅中会遇到的各种重要主题构建而成。除此之外,还向您提供了技术信息和许多练习,以确保尽可能多地向您传递信息。在本书结束时,您应该对 Linux 生态系统有一个清晰的认识。

本书涵盖的内容

第一章,“介绍”,试图呈现嵌入式 Linux 软件和硬件架构的样子。它还向您介绍了 Linux 和 Yocto 的好处,并提供了示例。它解释了 Yocto 项目的架构以及它是如何集成到 Linux 环境中的。

第二章,“交叉编译”,为您提供了工具链的定义、其组件以及获取方式。之后,向您提供了有关 Poky 存储库的信息,并与组件进行了比较。

第三章,“引导加载程序”,为您提供了引导顺序、U-Boot 引导加载程序以及如何为特定板构建它的信息。之后,它提供了从 Poky 获取 U-Boot 配方的访问权限,并展示了它的使用方法。

第四章,“Linux 内核”,解释了 Linux 内核和源代码的特性。它为您提供了构建内核源代码和模块的信息,然后继续解释 Yocto 内核的配方,并展示了内核引导后发生的相同事情。

第五章,“Linux 根文件系统”,为您提供了有关根文件系统目录和设备驱动程序的组织的信息。它解释了各种文件系统、BusyBox 以及最小文件系统应包含的内容。它将向您展示如何在 Yocto 项目内外编译 BusyBox,以及如何使用 Poky 获取根文件系统。

第六章,“Yocto 项目的组件”,概述了 Yocto 项目的可用组件,其中大部分在 Poky 之外。它提供了每个组件的简介和简要介绍。在本章之后,这些组件中的一些将被更详细地解释。

第七章,“ADT Eclipse 插件”,展示了如何设置 Yocto 项目 Eclipse IDE,为交叉开发和使用 Qemu 进行调试进行设置,并自定义图像并与不同工具进行交互。

第八章,“Hob,Toaster 和 Autobuilder”,介绍了这些工具的每一个,并解释了它们各自的用途,提到了它们的好处。

第九章, Wic 和其他工具,解释了如何使用另一组工具,这些工具与前一章提到的工具非常不同。

第十章, 实时,展示了 Yocto Project 的实时层,它们的目的和附加值。还提供了有关 Preempt-RT、NoHz、用户空间 RTOS、基准测试和其他实时相关功能的文档信息。

第十一章, 安全,解释了 Yocto Project 的安全相关层,它们的目的以及它们如何为 Poky 增加价值。在这里,您还将获得有关 SELinux 和其他应用程序的信息,例如 bastille、buck-security、nmap 等。

第十二章, 虚拟化,解释了 Yocto Project 的虚拟化层,它们的目的以及它们如何为 Poky 增加价值。您还将获得有关虚拟化相关软件包和倡议的信息。

第十三章, CGL 和 LSB,为您提供了 Carrier Graded Linux (CGL)的规范和要求的信息,以及 Linux Standard Base (LSB)的规范、要求和测试。最后,将与 Yocto Project 提供的支持进行对比。

阅读本书需要什么

在阅读本书之前,对嵌入式 Linux 和 Yocto 的先验知识将会有所帮助,尽管不是强制性的。在本书中,有许多练习可供选择,为了完成这些练习,对 GNU/Linux 环境的基本理解将会很有用。此外,一些练习是针对特定的开发板,另一些涉及使用 Qemu。拥有这样的开发板和对 Qemu 的先验知识是一个加分项,但不是强制性的。

在整本书中,有一些章节包含各种练习,需要读者已经具备 C 语言、Python 和 Shell 脚本的知识。如果读者在这些领域有经验,那将会很有帮助,因为它们是当今大多数 Linux 项目中使用的核心技术。我希望这些信息不会在阅读本书内容时让您感到沮丧,希望您会喜欢它。

这本书是为谁准备的

这本书是针对 Yocto 和 Linux 爱好者的,他们想要构建嵌入式 Linux 系统,也许还想为社区做出贡献。背景知识应该包括 C 编程技能,以及将 Linux 作为开发平台的经验,对软件开发流程有基本的了解。如果您之前阅读过《使用 Yocto Project 进行嵌入式 Linux 开发》,那也是一个加分项。

看一下技术趋势,Linux 是下一个大事件。它提供了访问尖端开源产品的机会,每天都有更多的嵌入式系统投入使用。Yocto Project 是与嵌入式设备交互的任何项目的最佳选择,因为它提供了丰富的工具集,帮助您将大部分精力和资源投入到产品开发中,而不是重新发明。

约定

在本书中,您会发现一些区分不同信息类型的文本样式。以下是一些样式的示例及其含义的解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下: "一个maintainers文件提供了特定板支持的贡献者列表。"

代码块设置如下:

sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
sudo apt-get update
sudo add-apt-repository "deb http://people.linaro.org/~neil.williams/lava jessie main"
sudo apt-get update

sudo apt-get install postgresql
sudo apt-get install lava
sudo a2dissite 000-default
sudo a2ensite lava-server.conf
sudo service apache2 restart

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
sudo apt-get update
sudo add-apt-repository "deb http://people.linaro.org/~neil.williams/lava jessie main"
sudo apt-get update

sudo apt-get install postgresql
sudo apt-get install lava
sudo a2dissite 000-default
sudo a2ensite lava-server.conf
sudo service apache2 restart

任何命令行输入或输出都将按照以下格式编写:

DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.1 LTS"

新术语重要单词以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,会以这种方式出现在文本中:"如果出现此警告消息,请按确定并继续"

注意

警告或重要说明会以这种方式出现在框中。

提示

技巧和窍门会以这种方式出现。

读者反馈

我们的读者的反馈总是受欢迎的。让我们知道您对本书的看法——您喜欢或不喜欢什么。读者的反馈对我们很重要,因为它帮助我们开发您真正能够充分利用的标题。

要向我们发送一般反馈,只需简单地发送电子邮件<feedback@packtpub.com>,并在消息主题中提及书名。

如果您在某个专题上有专业知识,并且有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在您是 Packt 书籍的自豪所有者,我们有一些事情可以帮助您充分利用您的购买。

勘误

尽管我们已经尽一切努力确保内容的准确性,但错误还是会发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——我们将不胜感激,如果您能向我们报告。通过这样做,您可以帮助其他读者避免挫折,并帮助我们改进本书的后续版本。如果您发现任何勘误,请访问www.packtpub.com/submit-errata报告,选择您的书,点击勘误提交表链接,并输入您的勘误详情。一旦您的勘误经过验证,您的提交将被接受,并且勘误将被上传到我们的网站或添加到该书标题的勘误部分下的任何现有勘误列表中。

要查看先前提交的勘误,请转到www.packtpub.com/books/content/support并在搜索字段中输入书名。所需信息将出现在勘误部分下。

盗版

在互联网上盗版受版权保护的材料是所有媒体的持续问题。在 Packt,我们非常重视版权和许可的保护。如果您在互联网上以任何形式发现我们作品的非法副本,请立即向我们提供位置地址或网站名称,以便我们采取补救措施。

请通过<copyright@packtpub.com>与我们联系,并附上涉嫌盗版材料的链接。

我们感谢您帮助保护我们的作者和我们为您提供有价值的内容的能力。

问题

如果您对本书的任何方面有问题,可以通过<questions@packtpub.com>与我们联系,我们将尽力解决问题。

第一章:介绍

在本章中,您将了解 Linux 和开源开发的优势。将介绍运行嵌入式 Linux 的系统的示例,许多嵌入式硬件平台都支持。之后,您将介绍嵌入式 Linux 系统的架构和开发环境,最后介绍 Yocto 项目,总结其 Poky 构建系统的属性和目的。

Linux 和开源系统的优势

本书中大部分可获得的信息和作为练习呈现的示例有一个共同点:它们都是任何人都可以自由访问的。本书试图为您提供如何与现有的和免费可用的软件包进行交互的指导,这些软件包可以帮助像您这样的嵌入式工程师,并且同时也试图激发您的好奇心,让您学到更多。

有关开源的更多信息可以从开源倡议OSIopensource.org/获取。

开源的主要优势在于它允许开发人员更专注于他们的产品和附加值。拥有开源产品可以获得各种新的可能性和机会,比如减少许可成本、增加公司的技能和知识。使用大多数人都可以访问并理解其工作原理的开源产品意味着预算节省。节省下来的资金可以用于其他部门,比如硬件或收购。

通常,人们对开源产品有很少或没有控制权的误解。然而,事实恰恰相反。开源系统一般来说提供了对软件的完全控制,我们将证明这一点。对于任何软件,您的开源项目都驻留在一个允许每个人查看的存储库中。由于您是项目的负责人,也是其管理员,您有权接受他人的贡献,这使他们和您拥有同样的权利,基本上给了您想做任何事情的自由。当然,可能会有人受到您的项目的启发,做出了开源社区更受欢迎的事情。然而,这就是进步的方式,坦率地说,如果您是一家公司,这种情况几乎是无效的。即使在这种情况下,这种情况也并不意味着您的项目的失败,而是一个机会。在这里,我想引用以下引用:

"如果你想建立一个开源项目,你不能让自己的自尊挡住你的路。你不能重写每个人的补丁,你不能对每个人进行第二次猜测,你必须给人们平等的控制权。"
--– Rasmus Lerdorf

允许他人访问、获得外部帮助、对您的开源软件进行修改、调试和优化意味着产品的寿命更长,随着时间的推移,质量也得到了提高。同时,开源环境提供了各种组件的访问,如果需要,这些组件可以轻松地集成到您的产品中。这可以实现快速的开发过程,降低成本,并且还可以将大部分的维护和开发工作从您的产品中转移出去。此外,它还提供了支持特定组件的可能性,以确保它继续满足您的需求。然而,在大多数情况下,您需要花一些时间从零开始为您的产品构建这个组件。

这将我们带到开源的下一个好处,涉及我们产品的测试和质量保证。除了测试所需的工作量较少之外,还可以在决定哪个组件最适合我们的产品之前从多个选项中进行选择。此外,使用开源软件比购买和评估专有产品更便宜。这种接受和回馈的过程,在开源社区中可见,是产生更高质量和更成熟产品的过程。这种质量甚至比其他专有或闭源类似产品的质量更高。当然,这并不是一个普遍有效的断言,只发生在成熟和广泛使用的产品上,但在这里出现了社区和基金会这个术语。

一般来说,开源软件是由开发人员和用户社区共同开发的。这个系统提供了直接从开发人员那里获得更大支持的机会——这在使用闭源工具时是不会发生的。此外,无论您是为公司工作与否,寻找问题答案时都没有限制。成为开源社区的一部分意味着不仅仅是修复错误、报告错误或开发功能。它是开发人员所做的贡献,但同时也为工程师提供了在工作环境之外获得认可的可能性,面对新挑战并尝试新事物。它也可以被视为一个巨大的激励因素和所有参与过程的灵感来源。

作为结论,我还想引用这个过程的核心人物的一句话,他给了我们 Linux 并使其保持开源:

"我认为,从根本上讲,开源软件确实更稳定。这是正确的做事方式。"
--– Linus Torvalds

嵌入式系统

既然开源的好处已经向您介绍了,我相信我们可以通过一些嵌入式系统、硬件、软件及其组件的例子。首先,嵌入式设备随处可见:看看您的智能手机、汽车信息娱乐系统、微波炉甚至您的 MP3 播放器。当然,并非所有这些都符合 Linux 操作系统的要求,但它们都有嵌入式组件,使它们能够实现其设计功能。

一般描述

要在任何设备硬件上运行 Linux,您将需要一些能够将硬件相关组件抽象为硬件无关组件的硬件相关组件。引导加载程序、内核和工具链包含使所有其他组件的工作更容易的硬件相关组件。例如,BusyBox 开发人员只会专注于为他的应用程序开发所需的功能,而不会专注于硬件兼容性。所有这些硬件相关组件都支持 32 位和 64 位的各种硬件架构。例如,U-Boot 实现是最容易作为源代码检查的例子。从中,我们可以很容易地想象如何添加对新设备的支持。

我们现在将尝试做一些之前介绍的小练习,但在继续之前,我必须介绍我将继续进行练习的计算机配置,以确保您尽可能少遇到问题。我正在使用 Ubuntu 14.04,并已从 Ubuntu 网站www.ubuntu.com/download/desktop下载了 64 位镜像。

使用此命令可以收集有关在计算机上运行的 Linux 操作的信息:

uname –srmpio

前面的命令生成了这个输出:

Linux 3.13.0-36-generic x86_64 x86_64 x86_64 GNU/Linux

收集与 Linux 操作相关的信息的下一个命令如下:

cat /etc/lsb-release

前面的命令生成了这个输出:

DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.1 LTS"

例子

现在,转到练习,第一个要求您获取 U-Boot 软件包的git存储库源代码:

sudo apt-get install git-core
git clone http://git.denx.de/u-boot.git

在您的机器上可用源代码之后,您可以尝试查看board目录内部;在这里,将出现许多开发板制造商。让我们看看board/atmel/sama5d3_xplainedboard/faraday/a320evbboard/freescale/imxboard/freescale/b4860qds。通过观察这些目录,可以看到一种模式。几乎所有的板都包含一个Kconfig文件,主要受到内核源的启发,因为它们以更清晰的方式呈现配置依赖关系。一个maintainers文件提供了对特定板支持的贡献者列表。基本的Makefile文件从更高级别的 makefiles 中获取必要的对象文件,这些对象文件是在构建特定板支持后获得的。与board/freescale/imx的区别在于,它只提供了一个配置数据列表,这些数据将在高级别 makefiles 中使用。

在内核级别,硬件相关的支持添加到arch文件中。在这里,除了MakefileKconfig之外,还可以添加各种数量的子目录。这些子目录为内核的不同方面提供支持,例如引导、内核、内存管理或特定应用程序。

通过克隆内核源代码,可以使用以下代码轻松可视化前面的信息:

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

一些可以可视化的目录是arch/arcarch/metag

从工具链的角度来看,硬件相关的组件由 GNU C 库表示,通常由glibc表示。这提供了系统调用接口,连接到内核架构相关的代码,并进一步为用户应用程序提供这两个实体之间的通信机制。如果克隆了glibc源代码,系统调用将显示在glibc源代码的sysdeps目录中,如下所示:

git clone http://sourceware.org/git/glibc.git

可以使用两种方法验证前面的信息:第一种方法涉及打开sysdeps/arm目录,例如,或者阅读ChangeLog.old-ports-arm库。尽管它已经过时,且存在不存在的链接,比如从存储库的新版本中消失的 ports 目录,但后者仍然可以用作参考点。

这些软件包也可以通过 Yocto 项目的poky存储库非常容易地访问。如www.yoctoproject.org/about所述:

“Yocto 项目是一个开源协作项目,提供模板、工具和方法,帮助您创建嵌入式产品的自定义 Linux 系统,无论硬件架构如何。它成立于 2010 年,是许多硬件制造商、开源操作系统供应商和电子公司之间的合作,旨在为嵌入式 Linux 开发的混乱带来一些秩序。”

与 Yocto 项目的大多数交互都是通过 Poky 构建系统完成的,这是其核心组件之一,提供了生成完全可定制的 Linux 软件堆栈所需的功能和功能。确保与存储库源进行交互的第一步是克隆它们:

git clone -b dizzy http://git.yoctoproject.org/git/poky

在您的计算机上存在源代码之后,需要检查一组配方和配置文件。可以检查的第一个位置是 U-Boot 配方,位于meta/recipes-bsp/u-boot/u-boot_2013.07.bb。它包含构建相应选定机器的 U-Boot 软件包所需的指令。下一个要检查的地方是内核中可用的配方。在这里,工作是稀疏的,有更多的软件包版本可用。它还为可用的配方提供了一些bbappends,例如meta/recipes-kernel/linux/linux-yocto_3.14.bbmeta-yocto-bsp/recipes-kernel/linux/linux-yocto_3.10.bbappend。这构成了使用 BitBake 开始新构建时可用的内核软件包版本的一个很好的例子。

工具链的构建对于主机生成的软件包来说是一个重要的步骤。为此,需要一组软件包,如gccbinutilsglibc库和内核头文件,它们起着重要的作用。对应于这些软件包的配方可在meta/recipes-devtools/gcc/meta/recipes-devtools/binutilsmeta/recipes-core/glibc路径中找到。在所有可用的位置,都可以找到大量的配方,每个配方都有特定的目的。这些信息将在下一章中详细介绍。

选择一个软件包版本而不是另一个的配置和选项主要添加在机器配置中。一个这样的例子是 Yocto 1.6 支持的 Freescale MPC8315E-rdb低功耗型号,其机器配置可在meta-yocto-bsp/conf/machine/mpc8315e-rdb.conf文件中找到。

注意

有关此开发板的更多信息,请访问www.freescale.com/webapp/sps/site/prod_summary.jsp?code=MPC8315E

介绍 GNU/Linux

GNU/Linux,或者通常所说的 Linux,代表着一个悠久的传统,是开源软件中最重要的联盟之一。不久,您将会了解到今天为全世界人们提供的历史以及在选择个人计算机操作系统方面的选择。最重要的是,我们将看看硬件开发人员提供的内容以及可用于平台开发的共同基础。

GNU/Linux 由 Linux 内核和一系列用户空间应用程序组成,这些应用程序放在 GNU C 库之上;这充当了计算机操作系统。它可以被认为是最多产的开源和免费软件之一,仍在发展中。它的历史始于 1983 年,当时 Richard Stallman 创立了 GNU 项目,旨在开发一个完整的类 Unix 操作系统,只能使用免费软件组装。到了 1990 年代初,GNU 已经提供了一系列库、类 Unix shell、编译器和文本编辑器。然而,它缺少一个内核。他们在 1990 年开始开发自己的内核 Hurd。该内核基于 Mach 微内核设计,但证明难以使用,并且开发过程缓慢。

与此同时,1991 年,一位芬兰学生在赫尔辛基大学上学时开始了另一个内核的业余工作。他还得到了来自互联网上各种程序员的帮助。那位学生的名字叫 Linus Torvalds,在 1992 年,他的内核与 GNU 系统结合在一起。结果是一个名为 GNU/Linux 的完全功能的操作系统,它是免费和开源的。GNU 系统的最常见形式通常被称为GNU/Linux 系统,甚至是Linux 发行版,是 GNU 的最流行的变体。今天,有许多基于 GNU 和 Linux 内核的发行版,其中最广泛使用的有:Debian、Ubuntu、Red Hat Linux、SuSE、Gentoo、Mandriva 和 Slackware。这张图片向我们展示了 Linux 的两个组件是如何一起工作的:

介绍 GNU/Linux

尽管最初并不是为了在 x86 PC 之外的其他设备上运行,但今天,Linux 操作系统是最广泛和可移植的操作系统。它可以在嵌入式设备或超级计算机上找到,因为它为用户和开发人员提供了自由。拥有生成可定制 Linux 系统的工具是这个工具发展的又一个重大进步。它为新类别的人提供了访问 GNU/Linux 生态系统的途径,通过使用 BitBake 等工具,他们最终会了解更多关于 Linux、其架构差异、根文件系统的构建和配置、工具链以及 Linux 世界中的许多其他内容。

Linux 并不是设计用于微控制器。如果 RAM 小于 32MB,它将无法正常工作,并且至少需要 4MB 的存储空间。然而,如果你看一下这个要求,你会发现它非常宽松。另外,它还支持各种通信外围设备和硬件平台,这清楚地说明了为什么它如此广泛地被采用。

注意

嗯,它可能在 8MB 的 RAM 上运行,但这取决于应用程序的大小。

在嵌入式环境中使用 Linux 架构需要遵循一定的标准。这是一个图形化表示的环境,它是在 free-electrons Linux 课程中提供的:

介绍 GNU/Linux

前面的图像展示了在嵌入式设备世界中使用 Linux 进行开发过程中涉及的两个主要组件:

  • 主机:这是所有开发工具所在的机器。在 Yocto 世界之外,这些工具由为特定目标交叉编译的相应工具链以及其必要的应用程序源代码和补丁表示。然而,对于 Yocto 用户,所有这些软件包和所涉及的准备工作都被简化为在实际工作之前执行的自动化任务。当然,这必须得到适当的优先考虑。

  • 目标机器:这是嵌入式系统,用于进行工作和测试。目标上可用的所有软件通常都是在主机上进行交叉编译的,主机是一个更强大、更高效的环境。通常需要用于嵌入式设备引导 Linux 并运行各种应用程序的组件,包括使用引导加载程序进行基本初始化和加载 Linux 内核。这反过来初始化驱动程序和内存,并通过可用的 C 库的功能为应用程序提供服务。

注意

还有其他与嵌入式设备一起工作的方法,比如交叉加拿大和本地开发,但这里介绍的方法是最常用的,对于开发人员和公司在嵌入式设备的软件开发方面都能够取得最好的结果。

在开发板上拥有一个功能完整的 Linux 操作系统之前,开发人员首先需要确保内核、引导程序和板对应的驱动程序正常工作,然后才能开始开发和集成其他应用程序和库。

Yocto 项目简介

在前一节中,介绍了拥有开源环境的好处。回顾 Yocto 项目出现之前嵌入式开发是如何进行的,可以完整地展现这个项目的好处。它也解释了为什么它被如此迅速地和如此大量地采用。

使用 Yocto 项目,整个过程变得更加自动化,主要是因为工作流程允许这样做。手动操作需要开发人员执行一系列步骤:

  1. 选择并下载必要的软件包和组件。

  2. 配置下载的软件包。

  3. 编译配置好的软件包。

  4. 在开发机上安装生成的二进制文件、库等到rootfs上。

  5. 生成最终可部署的格式。

所有这些步骤在需要引入最终可部署状态的软件包数量增加时会变得更加复杂。考虑到这一点,可以明确地说,手动工作只适用于少量组件;自动化工具通常更适用于大型和复杂的系统。

在过去的十年里,有许多自动化工具可以用来生成嵌入式 Linux 发行版。它们都基于之前描述的相同策略,但它们还需要一些额外的信息来解决依赖性相关的问题。这些工具都建立在一个用于执行任务的引擎周围,并包含描述操作、依赖关系、异常和规则的元数据。

最值得注意的解决方案是 Buildroot、Linux 目标镜像生成器(LTIB)、Scratchbox、OpenEmbedded、Yocto 和 Angstrom。然而,Scratchbox 似乎不再活跃,最后一次提交是在 2012 年 4 月。LTIB 曾是 Freescale 的首选构建工具,最近更多地转向 Yocto;在短时间内,LTIB 也可能被淘汰。

Buildroot

Buildroot 作为一个工具,试图简化使用交叉编译器生成 Linux 系统的方式。Buildroot 能够生成引导程序、内核映像、根文件系统,甚至交叉编译器。它可以独立生成每一个组件,因此它的主要用途被限制在生成相应的自定义根文件系统的交叉编译工具链上。它主要用于嵌入式设备,很少用于 x86 架构;它的主要关注点是 ARM、PowerPC 或 MIPS 等架构。与本书中介绍的每个工具一样,它都是为 Linux 设计的,并且期望主机系统上有一些特定的软件包以便正确使用。有一些强制性的软件包和一些可选的软件包。

在 Buildroot 手册中有一份包含特定软件包的强制性软件包列表,可以在buildroot.org/downloads/manual/manual.html找到。这些软件包如下:

  • which

  • sed

  • make(3.81 版本或更高版本)

  • binutils

  • build-essential(仅适用于基于 Debian 的系统)

  • gcc(2.95 版本或更高版本)

  • g++(2.95 版本或更高版本)

  • bash

  • patch

  • gzip

  • bzip2

  • perl(5.8.7 版本或更高版本)

  • tar

  • cpio

  • python(2.6 或 2.7 版本)

  • unzip

  • rsync

  • wget

除了这些强制性软件包外,还有一些可选的软件包。它们对以下方面非常有用:

  • 源获取工具:在官方树中,大多数软件包的检索都是使用wgethttphttps甚至ftp链接进行的,但也有一些链接需要使用版本控制系统或其他类型的工具。为了确保用户没有获取软件包的限制,可以使用以下工具:

  • bazaar

  • cvs

  • git

  • mercurial

  • rsync

  • scp

  • subversion

  • 接口配置依赖:它们由需要确保内核、BusyBox 和 U-Boot 配置等任务能够顺利执行的软件包表示:

  • ncurses5用于 menuconfig 界面

  • qt4用于xconfig界面

  • glib2gtk2glade2用于gconfig界面

  • 与 Java 相关的软件包交互:这用于确保当用户想要与 Java 类路径组件进行交互时,可以顺利进行:

  • javac:这是指 Java 编译器

  • jar:这是指 Java 存档工具

  • 图形生成工具:以下是图形生成工具:

  • graphviz用于使用graph-depends<pkg>-graph-depends

  • python-matplotlib用于使用graph-build

  • 文档生成工具:以下是文档生成过程中所需的工具:

  • asciidoc,版本 8.6.3 或更高版本

  • w3m

  • pythonargparse模块(在 2.7+和 3.2+版本中自动可用)

  • dblatex(仅用于 pdf 手册生成)

Buildroot 发布每三个月一次,具体在 2 月、5 月、8 月和 11 月,并且发布名称采用buildroot-yyyy-mm格式。对于有兴趣尝试 Buildroot 的人来说,前一节中描述的手册应该是安装和配置的起点。对于有兴趣查看 Buildroot 源代码的开发人员,可以参考git.buildroot.net/buildroot/

注意

在克隆 Buildroot 源代码之前,建议快速查看buildroot.org/download。这可能会帮助那些使用代理服务器的人。

接下来,将介绍一组新的工具,它们为这一领域做出了贡献,并将 Buildroot 项目放在了较低的支持级别上。我相信有必要快速回顾一下这些工具的优势和劣势。我们将从 Scratchbox 开始,考虑到它已经被弃用,关于它的内容并不多;它之所以被提及纯粹是出于历史原因。接下来是 LTIB,它构成了 Freescale 硬件的标准,直到采用 Yocto 为止。它在板支持包BSPs)方面得到了 Freescale 的良好支持,并包含了大量的组件数据库。另一方面,它相当古老,已经被 Yocto 取代。它不包含对新发行版的支持,也不被许多硬件供应商使用,在短时间内,它很可能会像 Scratchbox 一样被弃用。Buildroot 是它们中的最后一个,它易于使用,采用Makefile基本格式,并有一个活跃的社区支持。然而,它仅限于较小和较简单的镜像或设备,并不支持部分构建或软件包。

OpenEmbedded

接下来要介绍的工具非常相关,并且实际上具有相同的灵感和共同的祖先,即 OpenEmbedded 项目。这三个项目都由一个称为 Bitbake 的共同引擎连接,并受到 Gentoo Portage 构建工具的启发。OpenEmbedded 最初是在 2001 年开发的,当时夏普公司推出了基于 ARM 的 PDA 和 SL-5000 Zaurus,运行 Lineo,一个嵌入式 Linux 发行版。在夏普 Zaurus 推出后不久,Chris Larson 发起了 OpenZaurus 项目,旨在取代基于 Buildroot 的 SharpROM。之后,人们开始贡献更多的软件包,甚至支持新设备,最终系统开始显示其局限性。2003 年,开始讨论一个新的构建系统,可以提供一个通用的构建环境,并结合开源社区所需的使用模型;这是用于嵌入式 Linux 发行版的系统。这些讨论在 2003 年开始显示结果,今天出现的就是 Openembedded 项目。它有从 OpenZaurus 移植过来的软件包,如 Chris Larson、Michael Lauer 和 Holger Schurig 等人,根据新构建系统的能力。

Yocto 项目是同一项目的下一个演进阶段,其核心部分是 Poky 构建系统,由 Richard Purdie 创建。该项目最初是 OpenEmbedded 项目的一个稳定分支,只包括 OpenEmbedded 上可用的众多 recipes 的子集;它还具有有限的设备和架构支持。随着时间的推移,它变得更多:它变成了一个软件开发平台,集成了 fakeroot 替代品、Eclipse 插件和基于 QEMU 的镜像。现在 Yocto 项目和 OpenEmbedded 围绕一个称为OpenEmbedded-CoreOE-Core)的核心元数据进行协调。

Yocto 项目由 Linux 基金会赞助,为对开发定制嵌入式产品的 Linux 开发人员提供了一个硬件无关环境的起点。Poky 构建系统代表了其核心组件之一,也非常复杂。在所有这些中心是 Bitbake,它驱动一切的引擎,处理元数据的工具,下载相应的源代码,解决依赖关系,并相应地存储所有必要的库和可执行文件在构建目录中。Poky 结合了 OpenEmbedded 的优点,以分层的方式添加或删除构建环境配置中的额外软件组件,具体取决于开发人员的需求。

Poky 是一个以简单性为理念开发的构建系统。默认情况下,测试构建的配置需要用户很少的交互。基于之前练习中的克隆,我们可以进行一个新的练习来强调这个理念:

cd poky
source oe-init-build-env ../build-test
bitbake core-image-minimal

正如本例所示,很容易获得一个 Linux 镜像,以便在 QEMU 环境中进行测试。有许多可用的镜像足迹,从可以通过 shell 访问的最小镜像到具有 GNOME Mobile 用户界面支持的 LSB 兼容镜像都有。当然,这些基本镜像可以导入到新的镜像中以获得额外的功能。Poky 具有分层结构是一个巨大的优势,因为它增加了扩展功能的可能性,并且包含了错误的影响。层可以用于各种功能,从为新的硬件平台添加支持到扩展工具的支持,从新的软件堆栈到扩展的镜像功能。这里的可能性是无限的,因为几乎任何 recipe 都可以与另一个组合。

所有这些都是可能的,因为 Bitbake 引擎,它在环境设置和满足最小系统要求的测试之后,根据配置文件和接收到的输入,识别任务之间的相互依赖关系,任务的执行顺序,生成一个完全功能的交叉编译环境,并开始构建必要的本地和目标特定的软件包任务,就像它们被开发人员定义的那样。这里有一个示例,列出了一个软件包的可用任务列表:

OpenEmbedded

注意

有关 Bitbake 及其烘烤过程的更多信息,请参阅《使用 Yocto 项目进行嵌入式 Linux 开发》,作者是 Otavio Salvador 和 Daiane Angolini。

元数据模块化基于两个想法——第一个是关于优先考虑层的结构的可能性,第二个是关于当一个配方需要更改时不需要重复工作的可能性。这些层是重叠的。最一般的层是 meta,所有其他层通常都堆叠在其上,比如meta-yocto与 Yocto 特定的配方、机器特定的板支持包,以及其他可选层,取决于开发人员的需求和需求。应该使用位于上层的bbappend来定制配方。这种方法更受青睐,以确保不会重复配方,并且还有助于支持更新和旧版本。

在前面指定软件包的可用任务列表的示例中,可以找到层的组织示例。如果用户有兴趣识别在前面的练习中指定软件包的可用任务列表的test构建设置使用的层,bblayers.conf文件是一个很好的灵感来源。如果在此文件上执行cat命令,将看到以下输出:

# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
LCONF_VERSION = "6"

BBPATH = "${TOPDIR}"
BBFILES ?= ""

BBLAYERS ?= " \
  /home/alex/workspace/book/poky/meta \
  /home/alex/workspace/book/poky/meta-yocto \
  /home/alex/workspace/book/poky/meta-yocto-bsp \
  "
BBLAYERS_NON_REMOVABLE ?= " \
  /home/alex/workspace/book/poky/meta \
  /home/alex/workspace/book/poky/meta-yocto \
  "

执行此操作的完整命令是:

cat build-test/conf/bblayers.conf

这是一个更通用的构建目录的分层结构的可视模式:

OpenEmbedded

Yocto 作为一个项目提供了另一个重要的功能:无论主机机器上发生了什么变化,都可以以相同的方式重新生成镜像。这是一个非常重要的功能,不仅考虑到在开发过程中,一些工具的更改,如autotools交叉编译器Makefileperlbisonpkgconfig等,可能会发生,还考虑到与仓库的交互过程中参数可能会发生变化。简单地克隆一个仓库分支并应用相应的补丁可能无法解决所有问题。Yocto 项目对这些问题的解决方案非常简单。通过在任何安装步骤之前定义变量和配置参数,并确保配置过程也是自动化的,将最小化手动交互的风险。这个过程确保了镜像生成总是像第一次那样进行。

由于主机上的开发工具容易发生变化,Yocto 通常会编译用于软件包和镜像开发过程的必要工具,只有在它们的构建过程完成后,Bitbake 构建引擎才开始构建所请求的软件包。这种与开发人员机器的隔离有助于开发过程,保证了主机机器的更新不会影响或影响生成嵌入式 Linux 发行版的过程。

Yocto 项目优雅解决的另一个关键问题是工具链处理头文件和库的方式;因为这可能不仅会带来编译错误,还会带来非常难以预测的执行错误。 Yocto 通过将所有头文件和库移动到相应的sysroots目录中,并使用sysroot选项,构建过程确保不会与本地组件发生污染来解决这些问题。一个例子将更好地强调这一信息:

ls -l build-test/tmp/sysroots/
total 12K
drwxr-xr-x 8 alex alex 4,0K sep 28 04:17 qemux86/
drwxr-xr-x 5 alex alex 4,0K sep 28 00:48 qemux86-tcbootstrap/
drwxr-xr-x 9 alex alex 4,0K sep 28 04:21 x86_64-linux/

ls -l build-test/tmp/sysroots/qemux86/ 
total 24K
drwxr-xr-x 2 alex alex 4,0K sep 28 01:52 etc/
drwxr-xr-x 5 alex alex 4,0K sep 28 04:15 lib/
drwxr-xr-x 6 alex alex 4,0K sep 28 03:51 pkgdata/
drwxr-xr-x 2 alex alex 4,0K sep 28 04:17 sysroot-providers/
drwxr-xr-x 7 alex alex 4,0K sep 28 04:16 usr/
drwxr-xr-x 3 alex alex 4,0K sep 28 01:52 var/

Yocto 项目有助于实现可靠的嵌入式 Linux 开发,由于其规模,它被用于许多事情,从硬件公司的板支持包到软件开发公司的新软件解决方案。 Yocto 并不是一个完美的工具,它有一定的缺点:

  • 磁盘空间和机器使用要求相当高

  • 缺乏高级用法的文档

  • 工具,如 Autobuilder 和 Eclipse 插件,现在存在功能问题

还有其他一些困扰开发人员的事情,比如ptest集成和 SDK sysroot 的缺乏可扩展性,但其中一部分问题已经被项目背后的大社区解决,直到项目显示出其局限性,新的问题仍然需要等待来取代它。在此之前,Yocto 是开发基于 Linux 的自定义嵌入式 Linux 发行版或产品的框架。

总结

在本章中,您将了解开源的优势,以及开源如何帮助 Linux 内核、Yocto 项目、OpenEmbedded 和 Buildroot 等项目的发展和增长,例如 LTIB 和 Scratchbox;缺乏开源贡献意味着它们随着时间的推移被淘汰和消失。向您呈现的信息将以示例的形式呈现,这将让您更清楚地了解本书中的概念。

在下一章中,将会有更多关于工具链及其组成部分的信息。使用手动和自动方法生成让您更好地了解工具链的练习。

第二章:交叉编译

在本章中,您将了解工具链,如何使用和自定义它们,以及代码标准如何适用于它们。工具链包含了许多工具,如编译器、链接器、汇编器、调试器和各种杂项实用程序,帮助操纵生成的应用程序二进制文件。在本章中,您将学习如何使用 GNU 工具链,并熟悉其特性。您将看到涉及手动配置的示例,并同时将这些示例移至 Yocto 项目环境。在本章结束时,将进行分析,以确定手动部署工具链和自动部署工具链之间的相似性和差异,以及可用于它的各种使用场景。

介绍工具链

工具链代表了一个编译器及其相关实用程序,用于生成特定目标所需的内核、驱动程序和应用程序。工具链通常包含一组通常相互链接的工具。它包括gccglibcbinutils或其他可选工具,如用于特定编程语言(如 C++、Ada、Java、Fortran 或 Objective-C)的调试器可选编译器。

通常,一个可用于传统桌面或服务器的工具链在这些机器上执行,并生成可在同一系统上运行的可执行文件和库。通常用于嵌入式开发环境的工具链称为交叉工具链。在这种情况下,诸如 gcc 之类的程序在主机系统上运行,用于特定目标架构生成二进制代码。整个过程称为交叉编译,这是构建嵌入式开发源代码的最常见方式。

介绍工具链

在工具链环境中,有三台不同的机器:

  • 代表创建工具链的机器的构建机器

  • 代表执行工具链的主机机器

  • 代表工具链生成二进制代码的目标机器

这三台机器用于生成四种不同的工具链构建过程:

  • 本地工具链:这通常在普通 Linux 发行版或您的普通桌面系统上可用。通常编译和运行,并为相同的架构生成代码。

  • 交叉本地工具链:这代表了在一个系统上构建的工具链,尽管在目标系统上运行并生成二进制代码。一个常见的用例是在目标平台上需要本地gcc而无需在目标平台上构建它。

  • 交叉编译工具链:这是用于嵌入式开发的最常见的工具链类型。它在一个架构类型上编译和运行,通常是 x86,并为目标架构生成二进制代码。

  • 交叉加拿大构建:这代表了一个涉及在系统 A 上构建工具链的过程。然后在另一个系统上运行该工具链,例如 B,生成第三个系统 C 的二进制代码。这是最不常用的构建过程之一。

生成四种不同的工具链构建过程的三台机器在下图中描述:

介绍工具链

工具链代表了使今天大多数伟大项目的存在成为可能的工具列表。这包括开源项目。没有相应的工具链,这种多样性是不可能的。这也发生在嵌入式世界中,新的可用硬件需要相应工具链的组件和支持板支持包BSP)。

工具链配置并不是一个简单的过程。在寻找预构建的工具链,甚至自己构建工具链之前,最好的解决方案是检查特定目标 BSP;每个开发平台通常都提供一个。

工具链的组成部分

GNU 工具链是 GNU 项目下的一组编程工具的术语。这套工具通常被称为工具链,用于应用程序和操作系统的开发。它在嵌入式系统和 Linux 系统的开发中起着重要作用。

以下项目包含在 GNU 工具链中:

  • GNU make:这代表了用于编译和构建的自动化工具

  • GNU 编译器套件(GCC):这代表了用于多种可用编程语言的编译器套件

  • GNU Binutils:这包含了链接器、汇编器等工具 - 这些工具能够操作二进制文件

  • GNU Bison:这是一个解析器生成器

  • GNU 调试器(GDB):这是一个代码调试工具

  • GNU m4:这是一个 m4 宏处理器

  • GNU 构建系统(autotools):包括以下内容:

  • Autoconf

  • Autoheaders

  • Automake

  • Libtool

工具链中包含的项目如下图所示:

工具链的组成部分

嵌入式开发环境需要的不仅仅是交叉编译工具链。它还需要库,并且应该针对特定系统的软件包,如程序、库和实用程序,以及特定主机的调试器、编辑器和实用程序。在某些情况下,通常是在谈论公司的环境时,一些服务器托管目标设备,并且某些硬件探针通过以太网或其他方法连接到主机。这强调了嵌入式发行版包括大量工具的事实,通常情况下,其中一些工具需要定制。介绍这些工具中的每一个将占用书中的一个章节以上。

然而,在本书中,我们只会涵盖工具链构建组件。这些包括以下内容:

  • binutils

  • gcc

  • glibc(C 库)

  • 内核头文件

我将从介绍列表中的第一项开始,即GNU Binutils 软件包。根据 GNU GPL 许可证开发,它代表了一组工具,用于创建和管理给定架构的二进制文件、目标代码、汇编文件和配置数据。以下是 GNU Binutils 软件包可用工具的功能和名称列表:

  • GNU 链接器,即ld

  • GNU 汇编器,即as

  • 将地址转换为文件名和行号的实用程序,即addr2line

  • 创建、提取和修改存档的实用程序,即ar

  • 用于列出对象文件中可用符号的工具,即nm

  • 复制和翻译对象文件,即objcopy

  • 显示来自对象文件的信息,即objdump

  • 为存档内容生成索引的工具,即ranlib

  • 显示任何 ELF 格式对象文件的信息,即readelf

  • 列出对象或存档文件的段大小,即size

  • 从文件中列出可打印的字符串,即strings

  • 丢弃符号实用程序,即strip

  • 过滤或解码编码的 C++符号,即c++filt

  • 创建使用 DLL 的文件,即dlltool

  • 一种新的、更快的、仅支持 ELF 的链接器,目前仍处于测试阶段,即gold

  • 显示分析信息工具,即gprof

  • 将目标代码转换为 NLM 的实用程序,即nlmconv

  • 一个兼容 Windows 的消息编译器,即windmc

  • 用于 Windows 资源文件的编译器,即windres

这些工具中的大多数使用二进制文件描述符BFD)库进行低级数据操作,而且其中许多使用opcode库来组装和反汇编操作。

注意

有关binutils的有用信息可以在www.gnu.org/software/binutils/找到。

在工具链生成过程中,列表上的下一项是内核头文件,它们被 C 库所需,用于与内核交互。在编译相应的 C 库之前,需要提供内核头文件,以便它们可以访问可用的系统调用、数据结构和常量定义。当然,任何 C 库都定义了针对每个硬件架构特定的规范集;在这里,我指的是应用二进制接口ABI)。

应用二进制接口(ABI)代表两个模块之间的接口。它提供了有关函数调用方式以及应该在组件之间或操作系统之间传递的信息的信息。参考一本书,比如The Linux Kernel Primer,会对你有好处,而且在我看来,它是 ABI 提供的完整指南。我将尝试为你复制这个定义。

ABI 可以被视为类似于协议或协议的一组规则,它提供了链接器将编译模块组合成一个组件的可能性,而无需重新编译的可能性。同时,ABI 描述了这些组件之间的二进制接口。遵守 ABI 的这种约定并符合 ABI 的好处是可以链接使用不同编译器编译的目标文件。

很容易从这两个定义中看出,ABI 取决于平台的类型,这可能包括物理硬件、某种虚拟机等。它也可能取决于所使用的编程语言和编译器,但大部分取决于平台。

ABI 展示了生成的代码如何运行。代码生成过程也必须了解 ABI,但在高级语言中编码时,对 ABI 的关注很少是一个问题。这些信息可以被视为指定一些与 ABI 相关选项的必要知识。

一般规则是,ABI 必须尊重其与外部组件的交互。但是,就其与内部模块的交互而言,用户可以自由做任何他或她想做的事情。基本上,他们能够重新发明 ABI,并形成自己对机器限制的依赖。这里的简单例子与属于自己国家或地区的各种公民有关,因为他们从出生开始就学会并了解该地区的语言。因此,他们能够互相理解并无障碍地交流。对于外部公民来说,要能够交流,他或她需要了解一个地区的语言,并且在这个社区中似乎是很自然的,因此这不会构成问题。编译器也能够设计自己的自定义调用约定,其中他们了解在模块内调用的函数的限制。这通常是出于优化的原因而进行的。然而,这可能被视为 ABI 术语的滥用。

与用户空间 ABI 相关的内核是向后兼容的,并确保使用旧内核头版本生成的二进制文件比在运行内核上可用的版本更好地工作。这样做的缺点在于,使用较新内核头的工具链生成的新系统调用、数据结构和二进制文件可能无法使用较新功能。需要最新内核头的原因可以通过需要访问最新内核功能来证明。

GNU 编译器集合,也称为 GCC,代表了 GNU 工具链的关键组件。尽管最初被命名为 GNU C 编译器,因为它只处理 C 编程语言,但很快开始代表一系列语言,如 C、C++、Objective C、Fortran、Java、Ada 和 Go,以及其他语言的库(如libstdc++libgcj等)。

它最初是作为 GNU 操作系统的编译器编写的,并作为 100%自由软件开发。它在 GNU GPL 下分发。这有助于它在各种体系结构上扩展其功能,并在开源软件的增长中发挥了重要作用。

GCC 的开发始于 Richard Stallman 为引导 GNU 操作系统所付出的努力。这个任务导致 Stallman 从头开始编写自己的编译器。它于 1987 年发布,Stallman 是作者,其他人是贡献者。到 1991 年,它已经达到了稳定阶段,但由于其架构限制,无法包含改进。这意味着开始了对 GCC 版本 2 的工作,但不久之后,对它进行新语言接口开发的需求也开始出现,并且开发人员开始对编译器源代码进行自己的分支。这种分支倡议被证明是非常低效的,由于接受代码程序的困难,对它的工作变得非常沮丧。

这在 1997 年发生了变化,当时一群开发人员聚集在实验/增强 GNU 编译系统EGCS)工作组,开始将几个分支合并为一个项目。他们在这个冒险中取得了巨大成功,并收集了许多功能,以至于他们使自由软件基金会FSF)停止了他们对 GCC 版本 2 的开发,并于 1999 年 4 月任命 EGCS 为官方 GCC 版本和维护者。他们在发布 GCC 2.95 时合并在一起。有关 GNU 编译器集合的历史和发布历史的更多信息,请访问www.gnu.org/software/gcc/releases.htmlen.wikipedia.org/wiki/GNU_Compiler_Collection#Revision_history

GCC 接口类似于 Unix 约定,用户调用特定于语言的驱动程序,解释参数并调用编译器。然后运行汇编程序生成输出,必要时运行链接器以获得最终可执行文件。对于每种语言编译器,都有一个执行源代码读取的单独程序。

从源代码获取可执行文件的过程有一些执行步骤。在第一步之后,生成抽象语法树,在这个阶段,可以应用编译器优化和静态代码分析。优化和静态代码分析可以同时应用于与体系结构无关的GIMPLE或其超集 GENERIC 表示,也可以应用于与体系结构相关的寄存器传输语言RTL)表示,它类似于 LISP 语言。使用由 Jack Davidson 和 Christopher Fraser 编写的模式匹配算法生成机器代码。

GCC 最初几乎完全用 C 语言编写,尽管 Ada 前端主要用 Ada 语言编写。然而,2012 年,GCC 委员会宣布采用 C++作为实现语言。尽管 GCC 库的主要活动包括添加新语言支持、优化、改进的运行时库和增加调试应用程序的速度,但它不能被认为是一个完成的实现语言。

每个可用的前端都从给定的源代码生成一个树。使用这种抽象树形式,不同的语言可以共享相同的后端。最初,GCC 使用由 Bison 生成的Look-Ahead LRLALR)解析器,但随着时间的推移,它在 2006 年转向了递归下降解析器,用于 C、C++和 Objective-C。今天,所有可用的前端都使用手写的递归下降解析器。

直到最近,程序的语法树抽象与目标处理器不独立,因为树的含义在不同的语言前端之间是不同的,每个前端都提供自己的树语法。所有这些都随着 GCC 4.0 版本引入的 GENERIC 和 GIMPLE 架构无关表示的引入而发生了变化。

GENERIC 是一个更复杂的中间表示,而 GIMPLE 是一个简化的 GENERIC,目标是 GCC 的所有前端。诸如 C、C++或 Java 前端的语言直接在前端生成 GENERIC 树表示。其他使用不同的中间表示,然后被解析和转换为 GENERIC 表示。

GIMPLE 转换表示复杂表达式,这些表达式使用临时变量分割成三地址代码。GIMPLE 表示受到了 McCAT 编译器上使用的 SIMPLE 表示的启发,用于简化程序的分析和优化。

GCC 的中间阶段表示涉及代码分析和优化,并且在编译语言和目标架构方面是独立的。它从 GENERIC 表示开始,继续到寄存器传输语言RTL)表示。优化主要涉及跳转线程、指令调度、循环优化、子表达式消除等。RTL 优化不如通过 GIMPLE 表示进行的优化重要。但是,它们包括死代码消除、全局值编号、部分冗余消除、稀疏条件常量传播、聚合标量替换,甚至自动矢量化或自动并行化。

GCC 后端主要由预处理宏和特定目标架构函数表示,例如大小端定义,调用约定或字大小。后端的初始阶段使用这些表示来生成 RTL;这表明,尽管 GCC 的 RTL 表示在名义上是处理器无关的,但抽象指令的初始处理是针对每个特定目标进行调整的。

机器特定的描述文件包含 RTL 模式,还包括最终汇编的代码片段或操作数约束。在 RTL 生成过程中,验证目标架构的约束。要生成一个 RTL 片段,它必须与机器描述文件中的一个或多个 RTL 模式匹配,并且同时满足这些模式的限制。如果不这样做,最终 RTL 转换为机器代码的过程将是不可能的。在编译的最后阶段,RTL 表示变得严格。它的表示包含了真实的机器寄存器对应关系,以及每个指令引用的目标机器描述文件的模板。

因此,通过调用与相应模式相关联的小代码片段来获得机器代码。这样,指令就从目标指令集生成。这个过程涉及从重新加载阶段使用寄存器、偏移和地址。

注意

有关 GCC 编译器的更多信息,请访问gcc.gnu.org/en.wikipedia.org/wiki/GNU_Compiler_Collection

需要在这里介绍的最后一个元素是 C 库。它代表了 Linux 内核和 Linux 系统上使用的应用程序之间的接口。同时,它还为应用程序的更轻松开发提供了帮助。在这个社区中有几个 C 库可用:

  • glibc

  • eglibc

  • Newlib

  • bionic

  • musl

  • uClibc

  • dietlibc

  • Klibc

GCC 编译器使用的 C 库的选择将在工具链生成阶段执行,并且不仅受到库提供的大小和应用程序支持的影响,还受到标准的符合性、完整性和个人偏好的影响。

深入研究 C 库

我们将在这里讨论的第一个库是glibc库,它旨在提高性能、符合标准和可移植性。它是由自由软件基金会为 GNU/Linux 操作系统开发的,至今仍然存在于所有积极维护的 GNU/Linux 主机系统上。它是根据 GNU Lesser General Public License 发布的。

glibc库最初是由 Roland McGrath 在 20 世纪 80 年代编写的,直到 20 世纪 90 年代才继续发展,当时 Linux 内核分叉了glibc,称其为Linux libc。它在 1997 年 1 月之前是分开维护的,当时自由软件基金会发布了glibc 2.0glibc 2.0包含了很多功能,使得继续开发Linux libc毫无意义,因此他们停止了分支并回到了使用glibc。在Linux libc中进行的更改没有合并到glibc中,因为代码的作者身份存在问题。

glibc库在尺寸上相当大,不适合小型嵌入式系统,但它提供了单一 UNIX 规范SUS)、POSIX、ISO C11、ISO C99、伯克利 Unix 接口、System V 接口定义和 X/Open 可移植性指南 4.2 版的功能,以及与 X/Open 系统接口兼容系统以及 X/Open UNIX 扩展的所有扩展。此外,GLIBC 还提供了在开发 GNU 时被认为有用或必要的扩展。

我将在这里讨论的下一个 C 库是 Yocto 项目在 1.7 版本之前使用的主要 C 库。这里,我指的是eglibc库。这是glibc的一个版本,经过优化,用于嵌入式设备的使用,并且同时能够保持兼容性标准。

自 2009 年以来,Debian 及其一些派生版本选择从 GNU C 库转移到eglibc。这可能是因为 GNU LGPL 和eglibc之间的许可证存在差异,这使他们能够接受glibc开发人员可能拒绝的补丁。自 2014 年以来,官方eglibc主页声明eglibc的开发已经停止,因为glibc也已经转移到相同的许可证,而且 Debian Jessie 的发布意味着它已经回到了glibc。在 Yocto 支持的情况下,他们也决定将glibc作为他们的主要库支持选项。

newlib库是另一个旨在用于嵌入式系统的 C 库。它是由 Cygnus Support 开发并由 Red Hat 维护的一组自由软件许可证下的库组件。它是用于非 Linux 嵌入式系统的首选 C 库版本之一。

newlib系统调用描述了 C 库在多个操作系统上的使用,以及在不需要操作系统的嵌入式系统上的使用。它包含在商业 GCC 发行版中,如 Red Hat、CodeSourcery、Attolic、KPIT 等。它还受到包括 ARM、Renesas 在内的架构供应商的支持,或者类 Unix 环境,如 Cygwin,甚至 Amiga 个人电脑的专有操作系统的支持。

到 2007 年,它还得到了任天堂 DS、PlayStation、便携式 SDK Game Boy Advance 系统、Wii 和 GameCube 开发平台的工具链维护者的支持。2013 年,谷歌原生客户端 SDK 将newlib作为其主要 C 库包含在此列表中。

Bionic 是由 Google 为基于 Linux 内核的 Android 开发的 BSD C 库的派生版本。它的开发独立于 Android 代码开发。它的许可证是 3 条款 BSD 许可证,其目标是公开可用的。这些目标包括:

  • 小尺寸:与glibc相比,Bionic 尺寸更小

  • 速度:这些 CPU 设计为在低频率下工作

  • BSD 许可证:谷歌希望将 Android 应用程序与 GPL 和 LGPL 许可证隔离开来,这就是它转向非版权许可证的原因,具体如下:

  • Android 基于 GPLv2 许可证的 Linux 内核

  • glibc基于 LGPL,允许链接动态专有库,但不允许静态链接

glibc相比,它还有一系列限制,如下:

  • 它不包括 C++异常处理,主要是因为 Android 大多数代码都是用 Java 编写的。

  • 它不支持宽字符。

  • 它不包括标准模板库,尽管可以手动包含。

  • 它在 Bionic POSIX 中运行,甚至系统调用头文件都是 Android 特定函数的包装器或存根。这有时可能会导致奇怪的行为。

  • 当 Android 4.2 发布时,它包括对glibc``FORTIFY_SOURCE功能的支持。这些功能在 Yocto 和嵌入式系统中经常使用,但只存在于带有 ARM 处理器的 Android 设备的gcc版本中。

接下来要讨论的 C 库是musl。它是一个用于嵌入式和移动系统的 Linux 操作系统的 C 库。它具有 MIT 许可证,并且是根据从头开始开发的干净、符合标准的libc的想法而开发的。作为一个 C 库,它被优化用于静态库的链接。它与 C99 标准和 POSIX 2008 兼容,并实现了 Linux、glibc和 BSD 非标准函数。

接下来,我们将讨论uClibc,这是为 Linux 嵌入式系统和移动设备设计的 C 标准库。尽管最初是为μClinux 开发并设计用于微控制器,但它获得了追踪,并成为任何在设备上有限空间的人的首选。这是因为它变得受欢迎的原因:

  • 它侧重于尺寸而不是性能

  • 它具有 GNU Lesser General Public License(LGPL)免费许可证

  • 它比 glibc 小得多,减少了编译时间

  • 由于许多功能可以使用类似于 Linux 内核、U-Boot 甚至 BusyBox 等软件包上可用的menuconfig界面进行启用,因此它具有很高的可配置性。

uClibc库还具有另一个使其非常有用的特性。它引入了一种新的思想,因此 C 库不试图支持尽可能多的标准。然而,它专注于嵌入式 Linux,并包括对面临可用空间限制的开发人员必要的功能。出于这个原因,这个库是从头开始编写的,尽管它有其自身的局限性,但uClibcglibc的一个重要替代品。如果我们考虑到大多数 C 库使用的功能都包含在其中,最终尺寸要小四倍,WindRiver、MontaVista 和 TimeSys 都是其活跃的维护者。

dietlibc库是由 Felix von Leitner 开发的标准 C 库,并在 GNU GPL v2 许可下发布。尽管它也包含一些商业许可的组件,但其设计基于与uClibc相同的思想:在尽可能小的尺寸下编译和链接软件。它与uClibc还有另一个相似之处;它是从头开始开发的,并且只实现了最常用和已知的标准函数。它的主要用途主要是在嵌入式设备市场。

C 库列表中的最后一个是klibc标准 C 库。它是由 H. Peter Anvin 开发的,并且被开发用作 Linux 启动过程中早期用户空间的一部分。它被用于运行内核启动过程的组件,但不用于内核模式,因此它们无法访问标准 C 库。

klibc的开发始于 2002 年,旨在将 Linux 初始化代码移出内核。其设计使其适用于嵌入式设备。它还有另一个优势:它针对小尺寸和数据正确性进行了优化。klibc库在 Linux 启动过程中从initramfs(临时 Ram 文件系统)中加载,并且默认情况下使用mkinitramfs脚本将其合并到基于 Debian 和 Ubuntu 的文件系统中。它还可以访问一小组实用程序,如mountmkdirdashmknodfstypenfsmountrun-init等,在早期初始化阶段非常有用。

注意

有关 initramfs 的更多信息可以在内核文档中找到:www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt

klibc库根据 GNU GPL 许可,因为它使用了一些来自 Linux 内核的组件,因此作为整体,它被视为 GPL 许可的软件,限制了其在商业嵌入式软件中的适用性。然而,大多数库的源代码都是根据 BSD 许可编写的。

使用工具链

在生成工具链时,需要做的第一件事是建立用于生成二进制文件的 ABI。这意味着内核需要理解这个 ABI,同时系统中的所有二进制文件都需要使用相同的 ABI 进行编译。

在使用 GNU 工具链时,收集信息并了解使用这些工具的方式的一个很好的来源是查阅 GNU 编码标准。编码标准的目的非常简单:确保在 GNU 生态系统中以清晰、简单和一致的方式执行工作。这是一个需要被有兴趣使用 GNU 工具编写可靠、稳固和可移植软件的人使用的指南。GNU 工具链的主要重点是 C 语言,但这里应用的规则对于任何编程语言也非常有用。通过确保将给定信息背后的逻辑传递给读者来解释每条规则的目的。

我们将主要关注的语言也将是 C 编程语言。关于 GNU 编码标准与 GNU 库、异常或实用程序的兼容性,以及它们与 Berkeley Unix、标准 C 或 POSIX 等标准的比较应该非常好。在兼容性冲突的情况下,为该编程语言拥有兼容模式非常有用。

标准,如 POSIX 和 C,对于支持扩展有许多限制 - 然而,这些扩展仍然可以通过包括—posix—ansi—compatible选项来禁用它们。如果扩展提供了破坏程序或脚本的高概率,因为不兼容,应重新设计其接口以确保兼容性。

大量的 GNU 程序抑制了已知会与 POSIX 冲突的扩展,如果定义了POSIXLY_CORRECT环境变量。用户定义功能的使用为交换 GNU 功能与其他完全不同、更好甚至兼容功能提供了可能性。额外的有用功能总是受欢迎的。

如果我们快速浏览 GNU 标准文档,可以从中学到一些有用的信息:

最好使用int类型,尽管您可能考虑定义一个更窄的数据类型。当然,也有一些特殊情况可能很难使用。一个例子是dev_t系统类型,因为在某些机器上它比int短,在其他机器上则更宽。支持非标准 C 类型的唯一方法是使用Autoconf检查dev_t的宽度,然后相应地选择参数类型。然而,这可能不值得麻烦。

对于 GNU 项目来说,实施组织标准规范是可选的,只有在帮助系统整体变得更好的情况下才能实现。在大多数情况下,遵循已发布的标准符合用户需求,因为他们的程序或脚本可能被认为更具可移植性。一个例子是 GCC,它几乎实现了标准 C 的所有特性,正如标准要求的那样。这为 C 程序的开发人员提供了巨大的优势。这也适用于遵循 POSIX.2 规范的 GNU 实用程序。

还有一些规范中没有遵循的具体要点,但这是为了使 GNU 系统更好地为用户服务。一个例子是标准 C 程序不允许对 C 进行扩展,但是 GCC 实现了其中的许多扩展,其中一些后来被标准所采纳。对于希望按照标准输出错误消息的开发人员,可以使用--pedantic参数。这是为了确保 GCC 完全实现了标准。

POSIX.2 标准提到,诸如dudf之类的命令应该以 512 字节为单位输出大小。然而,用户希望以 1KB 为单位,因此实现了这种默认行为。如果有人希望具有 POSIX 标准要求的行为,他们需要设置POSIXLY_CORRECT环境变量。

另一个例子是 GNU 实用程序,当涉及到长命令行选项的支持或选项与参数的混合时,并不总是遵循 POSIX.2 标准规范。这种与 POSIX 标准的不兼容在实践中对开发人员非常有用。这里的主要思想不是拒绝任何新功能或删除旧功能,尽管某个标准将其视为已弃用或禁止。

注意

有关 GNU 编码标准的更多信息,请参阅www.gnu.org/prep/standards/html_node/

健壮编程的建议

为了确保编写健壮的代码,应该提到一些指导方针。第一个指导方针是不应该对任何数据结构使用限制,包括文件、文件名、行和符号,尤其是任意限制。所有数据结构都应该是动态分配的。其中一个原因是大多数 Unix 实用程序会悄悄地截断长行;GNU 实用程序不会这样做。

用于读取文件的实用程序应避免删除null字符或不可打印字符。这里的例外是,当这些旨在与某些类型的打印机或终端进行接口的实用程序无法处理先前提到的字符时。在这种情况下,我会建议尝试使用 UTF-8 字符集或其他用于表示多字节字符的字节序列使程序正常工作。

确保检查系统调用的错误返回值;例外情况是开发人员希望忽略错误。最好在由系统调用崩溃导致的错误消息中包括strerrorperror或等效错误处理函数的系统错误文本,还要添加源代码文件的名称和实用程序的名称。这样做是为了确保错误消息易于被与源代码或程序交互的任何人阅读和理解。

检查mallocrealloc的返回值,以验证它们是否返回了零。如果在系统中使用realloc使块变小,系统将近似块尺寸为 2 的幂,则realloc可能会有不同的行为并获得不同的块。在 Unix 中,当realloc存在错误时,它会破坏零返回值的存储块。对于 GNU,这个错误不会发生,当它失败时,原始块保持不变。如果要在 Unix 上运行相同的程序并且不想丢失数据,可以检查 Unix 系统上的错误是否已解决,或者使用 GNU 的malloc

释放的块的内容不可访问以进行更改或进行任何其他用户交互。这可以在调用 free 之前完成。

malloc命令在非交互式程序中失败时,我们面临致命错误。如果发生相同的情况,但这次涉及交互式程序,最好中止命令并返回读取循环。这提供了释放虚拟内存、终止其他进程并重试命令的可能性。

要解码参数,可以使用getopt_long选项。

在程序执行期间写入静态存储时,使用 C 代码进行初始化。但是,对于不会更改的数据,请保留 C 初始化声明。

尽量远离对未知 Unix 数据结构的低级接口——当数据结构无法以兼容的方式工作时,可能会发生这种情况。例如,要查找目录中的所有文件,开发人员可以使用readdir函数或任何高级接口可用函数,因为这些函数没有兼容性问题。

对于信号处理,使用 BSD 变体的signal和 POSIX 的sigaction函数。在这种情况下,USG 的signal接口不是最佳选择。如今,使用 POSIX 信号函数被认为是开发可移植程序的最简单方法。但是,使用一个函数而不是另一个完全取决于开发人员。

对于识别不可能情况的错误检查,只需中止程序,因为无需打印任何消息。这种类型的检查证明了错误的存在。要修复这些错误,开发人员将不得不检查可用的源代码,甚至启动调试器。解决这个问题的最佳方法是在源代码中使用注释描述错误和问题。在使用调试器相应地检查变量后,可以找到相关信息。

不要将程序中遇到的错误数量作为退出状态。这种做法并不是最佳的,主要是因为退出状态的值仅限于 8 位,可执行文件的执行可能有超过 255 个错误。例如,如果尝试返回进程的退出状态 256,父进程将看到状态为零,并认为程序成功完成。

如果创建了临时文件,请检查TMPDIR环境变量是个好主意。如果定义了该变量,最好使用/tmp目录。应谨慎使用临时文件,因为在可写入世界的目录中创建它们可能会导致安全漏洞。对于 C 语言,可以通过以下方式避免在临时文件中创建临时文件:

fd = open (filename, O_WRONLY | O_CREAT | O_EXCL, 0600);

这也可以使用mkstemps函数来完成,该函数由Gnulib提供。

对于 bash 环境,使用noclobber环境变量,或set -C的简短版本,以避免前面提到的问题。此外,mktemp可用实用程序是在 GNU Coreutils 软件包中的制作临时文件的更好解决方案。

注意

有关 GNU C 标准的更多信息,请访问www.gnu.org/prep/standards/standards.html

生成工具链

在介绍组成工具链的软件包之后,本节将介绍获取自定义工具链所需的步骤。将生成的工具链包含与 Poky dizzy 分支中可用的相同源。在这里,我指的是gcc版本 4.9,binutils版本 2.24 和glibc版本 2.20。对于 Ubuntu 系统,也有快捷方式可用。可以使用可用的软件包管理器安装通用工具链,还有其他选择,例如从 Board Support Packages 中下载可用的自定义工具链,甚至从 CodeSourcery 和 Linaro 等第三方下载。有关工具链的更多信息,请访问elinux.org/Toolchains。将用作演示的架构是 ARM 架构。

工具链构建过程有八个步骤。我只会概述每个步骤所需的活动,但必须提到它们都在 Yocto 项目配方中自动化。在 Yocto 项目部分,工具链是在不知不觉中生成的。与生成的工具链交互的最简单任务是调用meta-ide-support,但这将在适当的部分中介绍如下:

  • 设置:这代表了创建顶级构建目录和源子目录的步骤。在此步骤中,定义了诸如TARGETSYSROOTARCHCOMPILERPATH等变量。

  • 获取源代码:这代表了在后续步骤中可用的软件包,如binutilsgccglibcLinux 内核头文件和各种补丁。

  • GNU Binutils 设置 - 这代表了与binutils软件包交互的步骤,如下所示:

  • 解压相应版本的源代码

  • 如果适用,相应地对源代码进行打补丁

  • 相应地配置软件包

  • 编译源代码

  • 将源代码安装在相应的位置

  • Linux 内核头文件设置:这代表了与 Linux 内核源交互的步骤,如下所示:

  • 解压内核源代码。

  • 如果适用,对内核源代码进行打补丁。

  • 为所选的架构配置内核。在此步骤中,将生成相应的内核配置文件。有关 Linux 内核的更多信息将在第四章中介绍,Linux Kernel

  • 编译 Linux 内核头文件并将其复制到相应的位置。

  • 将头文件安装在相应的位置。

  • Glibc 头文件设置:这代表了设置glibc构建区域和安装头文件的步骤,如下所示:

  • 解压 glibc 存档和头文件

  • 如果适用,对源代码进行打补丁

  • 配置源代码,启用-with-headers变量以将库链接到相应的 Linux 内核头文件

  • 编译glibc头文件

  • 相应地安装头文件

  • GCC 第一阶段设置:这代表了生成 C 运行时文件(如crti.ocrtn.o)的步骤:

  • 解压 gcc 存档

  • 如有必要,对gcc源代码进行修补

  • 配置源代码,启用所需的功能

  • 编译 C 运行时组件

  • 相应地安装源代码

  • 构建glibc源代码:这代表了构建glibc源代码并进行必要的 ABI 设置的步骤,如下所示:

  • 通过相应地设置mabimarch变量来配置glibc

  • 编译源代码

  • 相应地安装glibc

  • GCC 第二阶段设置:这代表了工具链配置完成的最终设置阶段,如下所示:

  • 配置gcc源代码

  • 编译源代码

  • 在相应位置安装二进制文件

执行这些步骤后,开发人员将可以使用工具链。在 Yocto 项目中遵循相同的策略和构建过程步骤。

Yocto 项目参考

正如我所提到的,Yocto 项目环境的主要优势和可用功能在于 Yocto 项目构建不使用主机可用的软件包,而是构建和使用自己的软件包。这是为了确保主机环境的更改不会影响其可用的软件包,并且构建是为了生成自定义 Linux 系统。工具链是其中的一个组件,因为几乎所有构成 Linux 发行版的软件包都需要使用工具链组件。

Yocto 项目的第一步是确定将组合生成工具链的确切源和软件包,该工具链将被后续构建的软件包使用,如 U-Boot 引导程序、内核、BusyBox 等。在本书中,将讨论的源代码位于 dizzy 分支、最新的 poky 12.0 版本和 Yocto 项目版本 1.7 中。可以使用以下命令收集源代码:

git clone -b dizzy http://git.yoctoproject.org/git/poky

收集源代码并调查源代码,我们确定了前面标题中提到的部分软件包,并按照以下方式呈现:

cd poky
find ./ -name "gcc"
./meta/recipes-devtools/gcc
find ./ -name "binutils" 
./meta/recipes-devtools/binutils
./meta/recipes-devtools/binutils/binutils
find ./ -name "glibc"
./meta/recipes-core/glibc
./meta/recipes-core/glibc/glibc
$ find ./ -name "uclibc"
./meta-yocto-bsp/recipes-core/uclibc
./meta-yocto-bsp/recipes-core/uclibc/uclibc
./meta/recipes-core/uclibc 

GNU CC 和 GCC C 编译器软件包包括所有前面的软件包,分为多个部分,每个部分都有其目的。这主要是因为每个部分都有其目的,并且用于不同的范围,如sdk组件。然而,正如我在本章开头提到的,有多个需要确保并使用相同源代码自动化的工具链构建过程。Yocto 中支持 gcc 4.8 和 4.9 版本。快速查看gcc可用的配方显示了可用的信息:

meta/recipes-devtools/gcc/
├── gcc-4.8
├── gcc_4.8.bb
├── gcc-4.8.inc
├── gcc-4.9
├── gcc_4.9.bb
├── gcc-4.9.inc
├── gcc-common.inc
├── gcc-configure-common.inc
├── gcc-cross_4.8.bb
├── gcc-cross_4.9.bb
├── gcc-cross-canadian_4.8.bb
├── gcc-cross-canadian_4.9.bb
├── gcc-cross-canadian.inc
├── gcc-cross.inc
├── gcc-cross-initial_4.8.bb
├── gcc-cross-initial_4.9.bb
├── gcc-cross-initial.inc
├── gcc-crosssdk_4.8.bb
├── gcc-crosssdk_4.9.bb
├── gcc-crosssdk.inc
├── gcc-crosssdk-initial_4.8.bb
├── gcc-crosssdk-initial_4.9.bb
├── gcc-crosssdk-initial.inc
├── gcc-multilib-config.inc
├── gcc-runtime_4.8.bb
├── gcc-runtime_4.9.bb
├── gcc-runtime.inc
├── gcc-target.inc
├── libgcc_4.8.bb
├── libgcc_4.9.bb
├── libgcc-common.inc
├── libgcc.inc
├── libgcc-initial_4.8.bb
├── libgcc-initial_4.9.bb
├── libgcc-initial.inc
├── libgfortran_4.8.bb
├── libgfortran_4.9.bb
└── libgfortran.inc

GNU Binutils 软件包代表了二进制工具集合,如 GNU 链接器、GNU 汇编器、addr2linearnmobjcopyobjdump和其他工具及相关库。Yocto 项目支持 Binutils 版本 2.24,并且还依赖于可用的工具链构建过程,可以从源代码检查中看到:

meta/recipes-devtools/binutils/
├── binutils
├── binutils_2.24.bb
├── binutils-2.24.inc
├── binutils-cross_2.24.bb
├── binutils-cross-canadian_2.24.bb
├── binutils-cross-canadian.inc
├── binutils-cross.inc
├── binutils-crosssdk_2.24.bb
└── binutils.inc

最后的组件由 C 库组成,这些库作为 Poky dizzy 分支中的组件存在。有两个可供开发人员使用的 C 库。第一个是 GNU C 库,也称为glibc,是 Linux 系统中最常用的 C 库。glibc软件包的源代码可以在这里查看:

meta/recipes-core/glibc/
├── cross-localedef-native
├── cross-localedef-native_2.20.bb
├── glibc
├── glibc_2.20.bb
├── glibc-collateral.inc
├── glibc-common.inc
├── glibc.inc
├── glibc-initial_2.20.bb
├── glibc-initial.inc
├── glibc-ld.inc
├── glibc-locale_2.20.bb
├── glibc-locale.inc
├── glibc-mtrace_2.20.bb
├── glibc-mtrace.inc
├── glibc-options.inc
├── glibc-package.inc
├── glibc-scripts_2.20.bb
├── glibc-scripts.inc
├── glibc-testing.inc
├── ldconfig-native-2.12.1
├── ldconfig-native_2.12.1.bb
└── site_config

从这些源中,相同的位置还包括工具,如ldconfig,用于运行时依赖关系的独立本地动态链接器和绑定和交叉语言环境生成工具。在另一个名为uClibc的 C 库中,如前所述,这是为嵌入式系统设计的库,具有较少的配方,可以从 Poky 源代码中查看:

meta/recipes-core/uclibc/
├── site_config
├── uclibc-config.inc
├── uclibc-git
├── uclibc_git.bb
├── uclibc-git.inc
├── uclibc.inc
├── uclibc-initial_git.bb
└── uclibc-package.inc

uClibc 被用作glibc C 库的替代方案,因为它生成较小的可执行文件占用空间。同时,uClibc是前面列表中所呈现的唯一一个应用了bbappend的软件包,因为它扩展了对两台机器genericx86-64genericx86的支持。可以通过将TCLIBC变量更改为相应的变量来在glibcuClibc之间进行更改:TCLIBC = "uclibc"

如前所述,Yocto Project 的工具链生成过程更简单。这是在使用 Yocto Project 构建任何配方之前执行的第一个任务。要在 Bitbake 中生成交叉工具链,首先执行bitbake meta-ide-support任务。例如,可以为qemuarm架构执行该任务,但当然也可以以类似的方法为任何给定的硬件架构生成。任务完成执行过程后,工具链将生成并填充构建目录。在此之后,可以通过在tmp目录中使用environment-setup脚本来使用它:

cd poky
source oe-init-build-env ../build-test

相应地在conf/local.conf文件中将MACHINE变量设置为值qemuarm

bitbake meta-ide-support
source tmp/environment-setup

用于生成工具链的默认 C 库是glibc,但可以根据开发人员的需要进行更改。从前一节的介绍中可以看出,Yocto Project 中的工具链生成过程非常简单直接。它还避免了手动工具链生成过程中涉及的所有麻烦和问题,使得重新配置也非常容易。

摘要

在本章中,您将获得理解 Linux 工具链组成部分所需的必要信息,以及开发人员为工作或配置特定于板或架构的 Linux 工具链所采取的步骤。您还将获得有关 Yocto Project 源中可用软件包的信息,以及 Yocto Project 中定义的过程与 Yocto Project 上下文之外已经使用的过程非常相似。

在下一章中,我们将快速浏览有关引导加载程序的信息,特别强调 U-Boot 引导加载程序。您还将获得有关引导顺序和 U-Boot 源中板的配置的信息。

第三章:引导加载程序

在本章中,将介绍在嵌入式环境中使用 Linux 系统所必需的最重要的组件之一。我指的是引导加载程序,它是一种软件,可以初始化平台并使其准备好引导 Linux 操作系统。本章将介绍引导加载程序的好处和作用。本章主要关注 U-Boot 引导加载程序,但鼓励读者也了解其他引导加载程序,如 Barebox、RedBoot 等。所有这些引导加载程序都有各自的特点,没有一种特别适合所有需求;因此,在本章中欢迎进行实验和探索。

本章的主要目的是介绍嵌入式引导加载程序和固件的主要属性,它们的引导机制,以及在固件更新或修改时出现的问题。我们还将讨论与安全、安装或容错相关的问题。关于引导加载程序和固件的概念,我们有多个定义可用,其中一些是指传统的桌面系统,而我们对此不感兴趣。

固件通常代表一个固定且小型的程序,用于控制硬件系统。它执行低级操作,通常存储在闪存、只读存储器、可擦写只读存储器等上。它不经常更改。由于有时会有人对这个术语感到困惑,并且有时仅用于定义硬件设备或表示数据及其指令,因此完全避免使用。它代表两者的结合:计算机数据和信息,以及与硬件设备结合在一起的只读软件,可用于设备上。

引导加载程序代表系统初始化时首先执行的软件部分。它用于加载、解压缩和执行一个或多个二进制应用程序,比如 Linux 内核或根文件系统。它的作用是将系统添加到可以执行其主要功能的状态。这是在加载和启动它接收到的或已经保存在内部存储器上的正确二进制应用程序之后完成的。在初始化时,硬件引导加载程序可能需要初始化锁相环(PLL)、设置时钟,或者启用对 RAM 存储器和其他外围设备的访问。然而,这些初始化是在基本级别上完成的;其余的由内核驱动程序和其他应用程序完成。

今天有许多引导加载程序可用。由于本主题的空间有限,而且它们的数量很多,我们只讨论最流行的几种。U-Boot 是 PowerPC、ARM、MIPS 等架构中最流行的引导加载程序之一,它将成为本章的主要焦点。

引导加载程序的作用

第一次电流进入开发板处理器时,运行程序之前需要准备大量的硬件组件。对于每种架构、硬件制造商,甚至处理器来说,初始化过程都是不同的。在大多数情况下,它涉及一组配置和操作,对于各种处理器来说都是不同的,并最终从处理器附近的存储设备中获取引导代码。这个存储设备通常是闪存存储器,引导代码是引导加载程序的第一阶段,它初始化处理器和相关硬件外围设备。

大多数可用的处理器在通电时会转到默认地址位置,并在找到二进制数据的第一个字节后开始执行它们。基于这些信息,硬件设计师定义了闪存内存的布局和后续可以用于从可预测地址加载和引导 Linux 操作系统的地址范围。

在初始化的第一阶段,通常使用特定于处理器的汇编语言进行板子初始化,完成后,整个生态系统就准备好进行操作系统引导过程。引导程序负责这一切;它是提供加载、定位和执行操作系统的主要组件的可能性。此外,它还可以包含其他高级功能,比如升级 OS 映像、验证 OS 映像、在几个 OS 映像之间进行选择,甚至升级自身的可能性。传统 PC BIOS 和嵌入式引导程序之间的区别在于,在嵌入式环境中,引导程序在 Linux 内核开始执行后被覆盖。事实上,在它将控制权交给 OS 映像后,它就不复存在了。

在使用外围设备之前,引导程序需要仔细初始化外围设备,比如闪存或 DRAM。这并不是一件容易的事情。例如,DRAM 芯片不能以直接的方式读取或写入 - 每个芯片都有一个需要启用读写操作的控制器。同时,DRAM 需要不断刷新,否则数据将丢失。事实上,刷新操作代表了在硬件制造商规定的时间范围内读取每个 DRAM 位置。所有这些操作都是 DRAM 控制器的责任,它可能会给嵌入式开发人员带来很多挫折,因为它需要对架构设计和 DRAM 芯片有特定的了解。

引导程序没有普通应用程序的基础设施。它没有只能通过名称调用并开始执行的可能性。在获得控制权后,它通过初始化处理器和必要的硬件(如 DRAM)创建自己的上下文,如果需要,将自己移动到 DRAM 中以加快执行速度,最后开始实际的代码执行。

第一个复杂性因素是启动代码与处理器的引导顺序的兼容性。第一个可执行指令需要位于闪存内存中的预定义位置,这取决于处理器甚至硬件架构。此外,有可能有多个处理器根据接收到的硬件信号在几个位置寻找这些第一个可执行指令。

另一种可能性是在许多新的开发板上具有相同的结构,比如 Atmel SAMA5D3-Xplained:

引导程序的作用

对于 Atmel SAMA5D3-Xplained 板和其他类似的板,引导过程是从 ROM 内存中的集成引导代码 BootROM 开始的,该代码在 AT91 CPU 上加载第一阶段引导程序 AT91Bootstrap 到 SRAM 并启动它。第一阶段引导程序初始化 DRAM 内存并启动第二阶段引导程序,这种情况下是 U-Boot。有关引导序列可能性的更多信息可以在即将介绍的引导序列头部中找到。

缺乏执行上下文代表另一个复杂性。即使在一个没有内存,因此没有堆栈来分配信息的系统中编写一个简单的"Hello World"也会与众不同,这就是引导程序初始化 RAM 内存以便有一个可用的堆栈,并能够运行更高级的程序或语言,比如 C 的原因。

比较各种引导加载程序

正如前面所读到的,嵌入式系统有许多引导加载程序可用。这里将介绍以下引导加载程序:

  • U-Boot:也称为通用引导加载程序,主要适用于嵌入式 Linux 系统的 PowerPC 和 ARM 架构

  • Barebox:最初被称为 U-Boot v2,于 2007 年开始,旨在解决 U-Boot 的局限性;随着设计目标和社区的变化,它随时间改变了名称

  • RedBoot:这是一个源自 eCos 的 RedHat 引导加载程序,eCos 是一种便携式的开源实时操作系统,专为嵌入式系统设计

  • rrload:这是一个基于嵌入式 Linux 系统的 ARM 引导加载程序

  • PPCBOOT:这是用于 PowerPC 的引导加载程序,基于嵌入式 Linux 系统

  • CLR/OHH:这代表基于 ARM 架构的嵌入式 Linux 系统的闪存引导加载程序

  • Alios:这是一个主要用汇编语言编写的引导加载程序,进行 ROM 和 RAM 初始化,并试图完全消除嵌入式系统上固件的需求

有许多可用的引导加载程序,这是因为存在大量不同的架构和设备,实际上有很多,几乎不可能有一个适用于所有系统的引导加载程序。引导加载程序的种类很多;区分因素包括板类型和结构、SOC 差异甚至 CPU。

深入研究引导加载程序循环

如前所述,引导加载程序是在初始化系统后首先运行的组件,并为操作系统引导过程准备整个生态系统。这个过程因架构而异。例如,对于 x86 架构,处理器可以访问 BIOS,这是一个可用于非易失性存储器的软件,通常是 ROM。它的作用是在系统重置后开始执行并初始化硬件组件,这些组件稍后将被第一阶段引导加载程序使用。它还执行引导加载程序的第一阶段。

第一阶段引导加载程序在尺寸上非常小 - 通常只有 512 字节,并驻留在易失性存储器中。它在第二阶段执行完整的引导加载程序初始化。第二阶段引导加载程序通常驻留在第一阶段引导加载程序旁边,它们包含最多的功能并完成大部分工作。它们还知道如何解释各种文件系统格式,主要是因为内核是从文件系统加载的。

对于 x86 处理器,还有更多可用的引导加载程序解决方案:

  • GRUB:Grand Unified Bootloader 是 Linux 系统中最常用和功能强大的引导加载程序,适用于台式 PC 平台。它是 GNU 项目的一部分,也是 x86 架构系统中最强大的引导加载程序之一。这是因为它能够理解各种文件系统和内核映像格式。它能够在引导时更改引导配置。GRUB 还支持网络引导和命令行界面。它有一个在引导时处理并可修改的配置文件。有关更多信息,请访问www.gnu.org/software/grub/

  • Lilo:Linux Loader 是商业 Linux 发行版中经常使用的引导加载程序。与前面的情况类似,它适用于台式 PC 平台。它有多个组件,第一个组件出于历史原因位于磁盘驱动器的第一个扇区上;它是引导组件。出于同样的历史原因,它受限于 512 字节的尺寸,并且加载并提供控制给第二阶段引导加载程序,后者完成大部分引导加载程序的工作。Lilo 有一个配置实用程序,主要用作 Linux 内核引导过程的信息来源。有关更多信息,请访问www.tldp.org/HOWTO/LILO.html

  • Syslinux:用于可移动媒体或网络引导。Syslinux 是一个在 MS-DOS 或 Windows FAT 文件系统上运行的 Linux 操作系统引导加载程序,主要用于 Linux 的救援和首次安装。有关更多信息,请访问www.kernel.org/pub/linux/utils/boot/syslinux/

对于大多数嵌入式系统,这种引导过程并不适用,尽管有一些系统会复制这种行为。接下来将介绍两种情况。第一种情况是代码执行从固定地址位置开始,第二种情况是 CPU 在 ROM 存储器中有可调用的代码。

深入了解引导加载程序周期

图像的右侧呈现为先前提到的引导机制。在这种情况下,硬件需要一个 NOR 闪存存储器芯片,该芯片位于起始地址,以确保代码执行的开始。

NOR 存储器优于 NAND 存储器,因为它允许随机地址访问。这是第一阶段引导加载程序编程开始执行的地方,这并不使它成为最实用的引导机制。

尽管它并不是用于引导加载程序引导过程的最实用方法,但它仍然可用。然而,它在只适用于不适合更强大引导选项的板卡上才能使用。

U-Boot 引导加载程序

今天有许多开源引导加载程序可用。几乎所有这些引导加载程序都具有加载和执行程序的功能,通常涉及操作系统,并且它们的功能用于串行接口通信。然而,并非所有引导加载程序都具有通过以太网通信或自我更新的可能性。另一个重要因素是引导加载程序的广泛使用。组织和公司通常会选择一种引导加载程序,以支持它们所支持的多样化的板卡、处理器和架构。类似的情况也发生在 Yocto 项目中,当选择一个引导加载程序来代表官方支持的引导加载程序时。他们和其他类似的公司选择了 U-Boot 引导加载程序,在 Linux 社区中相当知名。

U-Boot 引导加载程序,或其官方名称 Das U-Boot,由 Wolfgang Denx 开发和维护,得到社区的支持。它在 GPLv2 许可下,其源代码在git存储库中免费提供,如第一章所示,每两个月发布一次。发布版本名称显示为U-boot vYYYY.MM。有关 U-Boot 加载程序的信息可在www.denx.de/wiki/U-Boot/ReleaseCycle找到。

U-Boot 源代码具有非常明确定义的目录结构。可以通过以下控制台命令轻松看到这一点:

tree -d -L 1
.
├── api
├── arch
├── board
├── common
├── configs
├── disk
├── doc
├── drivers
├── dts
├── examples
├── fs
├── include
├── lib
├── Licenses
├── net
├── post
├── scripts
├── test
└── tools
19 directories

arch目录包含特定架构文件和每个架构、CPU 或开发板特定目录。api包含独立于机器或架构类型的外部应用程序。board包含具有特定目录名称的所有特定板卡文件。commonmisc函数的位置。disk包含磁盘驱动处理函数,文档可在doc目录中找到。驱动程序位于drivers目录中。文件系统特定功能位于fs目录中。还有一些目录需要在这里提到,比如include目录,其中包含头文件;lib目录包含对各种实用程序的通用库支持,例如扁平设备树、各种解压缩、post(自检)等,但我会让读者的好奇心去发现它们,一个小提示是检查Directory Hierachy部分的README文件。

在 U-Boot 源代码中,可以找到每个支持的板卡的配置文件,这些文件在前一章节中下载到./include/configs文件夹中。这些配置文件是.h文件,包含了一些CONFIG_文件和有关内存映射、外围设备及其设置、命令行输出等信息,例如用于引导 Linux 系统的默认引导地址等。有关配置文件的更多信息可以在Configuration Options部分的README文件中或特定板卡配置文件中找到。对于 Atmel SAMA5D3-Xplained,配置文件是include/configs/sama5d3_xplained.h。此外,在configs目录中为该板卡提供了两种配置,分别是:

  • configs/sama5d3_xplained_mmc_defconfig

  • configs/sama5d3_xplained_nandflash_defconfig

这些配置用于定义板卡Secondary Program LoaderSPL)初始化方法。SPL 代表从 U-Boot 源代码构建的一个小型二进制文件,放置在 SRAM 内存中,用于将 U-Boot 加载到 RAM 内存中。通常,它的内存小于 4KB,这就是引导序列的样子:

U-Boot 引导程序

在实际开始为特定板卡构建 U-Boot 源代码之前,必须指定板卡配置。对于 Atmel SAMA5_Xplained 开发板,如前图所示,有两种可用的配置。配置是通过使用make ARCH=arm CROSS_COMPILE=${CC} sama5d3_xplained_nandflash_defconfig命令完成的。在这个命令后面,将创建include/config.h文件。这个头文件包含了针对所选板卡、架构、CPU 以及特定板卡头文件的定义。从include/config.h文件中读取的CONFIG_*变量包括了编译过程的确定。配置完成后,可以开始为 U-Boot 进行构建。

另一个可能非常有用的示例涉及引导嵌入式系统的另一种情况,即需要使用 NOR 存储器。在这种情况下,我们可以看一个特定的示例。这也在 Christopher Hallinan 的《嵌入式 Linux 入门》中有很好的描述,其中讨论了 AMCC PowerPC 405GP 的处理器。该处理器的硬编码地址为 0xFFFFFFFC,并且可以使用.resetvec,重置向量位置来查看。还指定了这一部分的其余部分只有值1直到 0xFFFFFFFF 堆栈的末尾;这意味着空的闪存存储器数组只包含值1。有关此部分的信息可在resetvec.S文件中找到,该文件位于arch/powerpc/cpu/ppc4xx/resetvec.Sresetvec.S文件的内容如下:

 /* Copyright MontaVista Software Incorporated, 2000 */
#include <config.h>
  .section .resetvec,"ax"
#if defined(CONFIG_440)
  b _start_440
#else
#if defined(CONFIG_BOOT_PCI) && defined(CONFIG_MIP405)
  b _start_pci
#else
  b _start
#endif
#endif

检查此文件的源代码,可以看到在这一部分中只定义了一条指令,而不管可用的配置选项如何。

U-Boot 的配置通过两种类型的配置变量完成。第一种是CONFIG_*,它引用了用户可以配置以启用各种操作功能的配置选项。另一个选项称为CFG_*,用于配置设置和引用硬件特定的细节。CFG_*变量通常需要对硬件平台、外围设备和处理器有很好的了解。SAMA5D3 Xplained 硬件平台的配置文件位于include/config.h头文件中,如下所示:

/* Automatically generated - do not edit */
#define CONFIG_SAMA5D3  1
#define CONFIG_SYS_USE_NANDFLASH        1
#define CONFIG_SYS_ARCH  "arm"
#define CONFIG_SYS_CPU   "armv7"
#define CONFIG_SYS_BOARD "sama5d3_xplained"
#define CONFIG_SYS_VENDOR "atmel"
#define CONFIG_SYS_SOC    "at91"
#define CONFIG_BOARDDIR board/atmel/sama5d3_xplained
#include <config_cmd_defaults.h>
#include <config_defaults.h>
#include <configs/sama5d3_xplained.h>
#include <asm/config.h>
#include <config_fallbacks.h>
#include <config_uncmd_spl.h>

此处提供的配置变量代表 SAMA5D3 Xplained 板的相应配置。这些配置的一部分涉及用户与引导加载程序的交互的一些标准命令。这些命令可以根据需要添加或删除,以扩展或减少可用命令行界面的命令。

有关 U-Boot 可配置命令界面的更多信息,请参阅www.denx.de/wiki/view/DULG/UBootCommandLineInterface

引导 U-Boot 选项

在工业环境中,与 U-Boot 的交互主要通过以太网接口完成。以太网接口不仅能够更快地传输操作系统映像,而且比串行连接更不容易出错。

引导加载程序内部的一个最重要的功能与对动态主机控制协议(DHCP)、简单文件传输协议(TFTP)甚至引导协议(BOOTP)的支持有关。BOOTP 和 DHPC 使以太网连接能够自行配置并从专用服务器获取 IP 地址。TFTP 使得通过 TFTP 服务器下载文件成为可能。目标设备与 DHCP/BOOTP 服务器之间传递的消息在下图中以更通用的方式表示。最初,硬件平台发送一个广播消息,到达所有可用的 DHCP/BOOTP 服务器。每个服务器都会发送其提供的 IP 地址,客户端接受最适合其目的的那个,并拒绝其他的。

引导 U-Boot 选项

目标设备与 DHCP/BOOTP 通信完成后,它将保留一个特定于目标的配置,其中包含主机名、目标 IP 和硬件以太网地址(MAC 地址)、子网掩码、tftp 服务器 IP 地址甚至是 TFTP 文件名。这些信息绑定到以太网端口,并在引导过程中后续使用。

对于引导映像,U-Boot 提供了许多与存储子系统支持相关的功能。这些选项包括 RAM 引导、MMC 引导、NAND 引导、NFS 引导等等。对这些选项的支持并不总是容易的,可能涉及硬件和软件的复杂性。

移植 U-Boot

我之前提到过,U-Boot 是最常用和知名的引导加载程序之一。这也是因为它的架构使得在新的开发平台和处理器上进行移植变得非常容易。同时,还有大量可用的开发平台可以作为参考。任何有兴趣移植新平台的开发人员首先应该做的事情是检查boardarch目录,以建立它们的基线,并同时识别它们与其他 CPU 和可用板子的相似之处。

board.cfg文件是注册新平台的起点。在这里,应该添加以下信息作为表格行:

  • 状态

  • 架构

  • CPU

  • SOC

  • 供应商

  • 板子名称

  • 目标

  • 选项

  • 维护者

要移植类似于 SAMA5D3 Xplained 的机器,可以查阅的目录之一是arch目录。它包含了一些文件,比如board.c,其中包含了与板子和 SOC 的初始化过程相关的信息。最值得注意的过程是board_init_r(),它在 RAM 中重新定位后对板子和外设进行设置和探测,board_init_f(),它在 RAM 中重新定位之前识别堆栈大小和保留地址,以及init_sequence[],它在board_init_f内部用于外设的设置。在相同位置内的其他重要文件还有bootm.cinterrupts.c文件。前者主要负责从内存中引导操作系统,后者负责实现通用中断。

board目录还有一些有趣的文件和函数需要在这里提到,比如board/atmel/sama5d3_xplained/sama5d3_xplained.c文件。它包含了一些函数,比如board_init()dram_init()board_eth_init()board_mmc_init()spl_board_init()mem_init(),用于初始化,其中一些被arch/arm/lib/board.c文件调用。

以下是一些其他相关的目录:

  • common:这包含了用户命令、中间件、用于中间件和用户命令之间的接口的 API,以及所有可用板子使用的其他函数和功能。

  • drivers:这包含了各种设备驱动程序和中间件 API 的驱动程序,比如drivers/mmc/mmc.cdrivers/pci/pci.cdrivers/watchdog/at91sam9_wdt.c等。

  • fs:各种支持的文件系统,如 USB、SD 卡、Ext2 FAT 等都可以在这里找到。

  • include:这代表了大多数板子所需的所有头文件的位置。SOC 和其他软件也可用。在 include/configs 中,可以找到特定于板子的配置,并包括从 Linux 导入的头文件;这些可以用于各种设备驱动程序、移植或其他字节操作。

  • tools:这是工具的位置,比如checkpatch.pl,这是一个用于发送到邮件列表之前用作编码风格检查的补丁检查工具,或者mkimage.c工具。这也用于 U-Boot 通用头文件的生成,以便制作 Linux 二进制文件,并确保它们能够使用 U-Boot 引导。

有关 SAMA5D3 Xplained 板的更多信息可以在相应的 doc 目录和README文件中找到,例如README.at91README.at91-socREADME.atmel_mciREADME.atmel_pmeccREADME.ARM-memory-map等。

对于有兴趣提交对 U-Boot 进行新开发板、CPU 或 SOC 移植时所做的更改的人,应遵循一些规则。所有这些都与git交互有关,并帮助您确保正确维护您的分支。

开发人员应该做的第一件事是跟踪对应于本地分支的上游分支。另一个建议是忘记git merge,而是使用git rebase。使用git fetch命令可以与上游存储库保持联系。要使用补丁,需要遵循一些一般规则,并且补丁只能有一个逻辑更改,可以是以下任何一个:

  • 更改不应包含无关或不同的修改;每个更改集只有一个补丁可用和可接受

  • 提交应尽可能使用git-bisect来检测源代码中的错误时进行调试

  • 如果一组修改影响了多个文件,则所有这些文件都应在同一个补丁中提交

  • 补丁需要进行审查,而且非常彻底

让我们看一下下面的图表,它说明了 git rebase 操作:

移植 U-Boot

如前面和后面的图表所示,git rebase操作已将工作从一个分支重新创建到另一个分支。来自一个分支的每个提交都可以在后续的一个分支上使用,就在它的最后一个提交之后。

移植 U-Boot

另一方面,git merge操作是一个具有两个父级的新提交:从中进行移植的分支和进行合并的新分支。实际上,它将一系列提交收集到一个具有不同提交 ID 的分支中,这就是为什么它们难以管理。

移植 U-Boot

有关git交互的更多信息可以在git-scm.com/documentationwww.denx.de/wiki/U-Boot/Patches找到。

几乎总是在 U-Boot 中移植新功能时,都涉及调试。对于 U-Boot 调试器,可能会出现两种不同的情况:

  • 第一种情况是lowlevel_init未被执行

  • 第二种情况是lowlevel_init被执行;这是最为人所知的情况。

在接下来的几行中,将考虑第二种情况:为 U-Boot 启用调试会话的基线。为了确保可以进行调试,需要执行elf文件。此外,它不能直接操作,因为链接地址将被重定位。为此,应该使用一些技巧:

  • 第一步是确保环境干净,旧对象不再可用:make clean

  • 下一步是确保依赖项已清理:find ./ | grep depend | xargs rm

  • 清理完成后,目标构建可以开始,并且输出可以重定向到日志文件中:make sama5d3_xplained 2>&1 > make.log

  • 生成的输出应重命名以避免多个板的调试问题:mv u-boot.bin u-boot_sama5d3_xplained.bin

  • 在板配置文件中启用 DEBUG 很重要;在include/configs/ sama5d3_xplained.h中,添加#define DEBUG 行

重定位发生后可以设置早期开发平台,并且应在重定位结束后设置适当的断点。需要重新加载 U-Boot 的符号,因为重定位将移动链接地址。对于所有这些任务,gdb脚本被指定为gdb gdb-script.sh

#!/bin/sh
${CROSS_COMPILE}-gdb u-boot –command=gdb-command-script.txt

vim gdb-command-script.txt
target remote ${ip}:${port}
load
set symbol-reloading
# break the process before calling board_init_r() function
b start.S:79
c
…
# the symbol-file need to be align to the address available after relocation
add-symbol-file u-boot ${addr}
# break the process at board_init_r() function for single stepping b board.c:494

注意

有关重定位的更多信息可以在doc/README.arm-relocation中找到。

Yocto 项目

Yocto Project 使用各种配方来定义与每个支持的引导加载程序的交互。由于引导启动有多个阶段,BSP 内也需要多个配方和包。用于各种引导加载程序的配方与 Yocto 世界中的任何其他配方并无不同。然而,它们有一些使它们独特的细节。

我们将在这里关注的板子是sama5d3_xplained开发板,它位于meta-atmel层内。在这个层内,第一阶段和第二阶段引导加载程序的相应配方可以在recipes-bsp目录中找到。在这里,我指的是at91bootstrapu-boot配方。关于第一阶段和第二阶段引导加载程序有一些误解。它们可能被称为第二级和第三级引导加载程序,因为在讨论中可能会考虑引导 ROM 代码。在本书中,我们更愿意称它们为第一阶段和第二阶段引导加载程序。

AT91bootstrap包代表了 Atmel 公司为其 SOC 提供的第一阶段引导加载程序。它管理硬件初始化,并执行从内存中的引导介质下载第二阶段引导加载程序;它在最后启动它。在meta-atmel层中,第二阶段引导加载程序是u-boot,它稍后用于 Linux 操作系统的引导。

通常,在 BSP 层内,支持多个开发板,这意味着也提供了多个版本和引导加载程序包。然而,它们之间的区别在于机器配置。对于 SAMA5D3 Xplained 开发板,机器配置可在conf/machine/sama5d3_xplained文件中找到。在这个文件中,定义了首选的引导加载程序版本、提供者和配置。如果这些配置不是MACHINE特定的,它们也可以在package配方中执行。

这是sama5d3_xplained开发板可用的配置之一:

PREFERRED_PROVIDER_virtual/bootloader = "u-boot-at91"
UBOOT_MACHINE ?= "sama5d3_xplained_nandflash_config"
UBOOT_ENTRYPOINT = "0x20008000"
UBOOT_LOADADDRESS = "0x20008000"

AT91BOOTSTRAP_MACHINE ?= "sama5d3_xplained"

摘要

在本章中,您将了解引导加载程序的信息,特别关注 U-Boot 引导加载程序。我们还讨论了与 U-Boot 交互、移植、调试、引导加载程序的一般信息、U-Boot 替代方案以及嵌入式环境中的引导序列相关的主题。还有一个与 Yocto Project 相关的部分,介绍了用于支持 BSP 内各种引导加载程序的机制。本章中提出了一些练习,它们为这个主题提供了更多的清晰度。

在下一章中,我们将讨论 Linux 内核,其特性和源代码、模块和驱动程序,以及一般来说,与 Linux 内核交互所需的大部分信息。由于您已经对此有所了解,我们还将集中讨论 Yocto Project 以及它如何能够与多个板子和练习的各种内核版本一起工作。这应该有助于您理解所呈现的信息。

第四章:Linux 内核

在本章中,您不仅将了解有关 Linux 内核的一般信息,还将了解有关它的具体信息。本章将从 Linux 的历史和作用的简要介绍开始,然后继续解释其各种特性。不会省略与 Linux 内核源代码交互的步骤。您将了解到获取 Linux 内核映像的步骤,以及新ARM 机器的移植意味着什么,以及在一般情况下使用调试各种问题的方法。最后,将切换到 Yocto 项目,展示如何为给定的机器构建 Linux 内核,以及如何集成和稍后从根文件系统映像中使用外部模块。

本章将让您了解 Linux 内核和 Linux 操作系统。没有历史组成部分,这个演示是不可能的。Linux 和 UNIX 通常被放在同一历史背景下,但尽管 Linux 内核出现在 1991 年,Linux 操作系统很快成为 UNIX 操作系统的替代品,但这两个操作系统是同一个家族的成员。考虑到这一点,UNIX 操作系统的历史不能从其他地方开始。这意味着我们需要回到 40 多年前,更准确地说,大约 45 年前的 1969 年,当丹尼斯·里奇和肯·汤普森开始开发 UNIX 时。

UNIX 的前身是多路信息和计算服务Multics),这是一个多用户操作系统项目,当时并不是最佳状态。自从 Multics 在 1969 年夏天成为贝尔实验室计算机科学研究中心无法实现的解决方案后,一个文件系统设计诞生了,后来成为今天所知的 UNIX。随着时间的推移,由于其设计和源代码随之分发,它被移植到了多台机器上。UNIX 最多产的贡献者是加州大学伯克利分校。他们还开发了自己的 UNIX 版本,名为伯克利软件发行BSD),首次发布于 1977 年。直到 1990 年代,多家公司开发并提供了自己的 UNIX 发行版,它们的主要灵感来自伯克利或 AT&T。所有这些都帮助 UNIX 成为一个稳定、健壮和强大的操作系统。UNIX 作为操作系统强大的特点包括:

  • UNIX 很简单。它使用的系统调用数量减少到只有几百个,它们的设计是基本的

  • 在 UNIX 中,一切都被视为文件,这使得数据和设备的操作更简单,并且最小化了用于交互的系统调用。

  • 更快的进程创建时间和fork()系统调用。

  • UNIX 内核和实用程序都是用 C 语言编写的,这使得它易于移植和访问。

  • 简单而健壮的进程间通信IPC)原语有助于创建快速和简单的程序,以最佳方式完成一件事。

如今,UNIX 是一个成熟的操作系统,支持虚拟内存、TCP/IP 网络、需求分页、抢占式多任务处理和多线程等功能。其功能覆盖范围广泛,从小型嵌入式设备到拥有数百个处理器的系统。它的发展已经超越了 UNIX 是一个研究项目的想法,它已经成为一个通用的操作系统,几乎适用于任何需求。所有这些都是由于其优雅的设计和经过验证的简单性。它能够在不失去简单性的能力的情况下发展。

Linux 是 UNIX 变体Minix的替代解决方案,Minix 是一个为教学目的而创建的操作系统,但它缺乏与系统源代码的简单交互。由于 Minix 的许可证,对源代码的任何更改都不容易集成和分发。Linus Torvalds 最初在终端仿真器上开始工作,以连接到他的大学的其他 UNIX 系统。在同一个学年内,仿真器演变成了一个完整的 UNIX 系统。他在 1991 年发布了供所有人使用的版本。

Linux 最吸引人的特点之一是它是一个开源操作系统,其源代码在 GNU GPL 许可证下可用。在编写 Linux 内核时,Linus Torvalds 从可用的 UNIX 变体中选择了最佳的设计选择和功能作为灵感的来源。它的许可证是推动它成为今天强大力量的原因。它吸引了大量的开发人员,他们帮助改进了代码,修复了错误等等。

今天,Linux 是一个经验丰富的操作系统,能够在多种架构上运行。它能够在比手表还小的设备上运行,也能在超级计算机集群上运行。它是我们这个时代的新感觉,并且正在以越来越多样化的方式被公司和开发人员采用。对 Linux 操作系统的兴趣非常强烈,这不仅意味着多样性,还提供了大量的好处,从安全性、新功能、嵌入式解决方案到服务器解决方案等等。

Linux 已经成为一个真正的由互联网上的庞大社区开发的协作项目。尽管在这个项目内部进行了大量的变化,但 Linus 仍然是它的创造者和维护者。变化是我们周围一切的不断因素,这也适用于 Linux 及其维护者,现在被称为 Greg Kroah-Hartman,已经担任内核维护者两年了。在 Linus 在场的时期,Linux 内核似乎是一个松散的开发者社区。这可能是因为 Linus 全球知名的严厉评论。自从 Greg 被任命为内核维护者以来,这种形象逐渐消失。我期待未来的岁月。

Linux 内核的作用

具有令人印象深刻的代码行数,Linux 内核是最重要的开源项目之一,同时也是最大的开源项目之一。Linux 内核构成了一个软件部分,帮助硬件接口,是在每个人的 Linux 操作系统中运行的最低级别代码。它被用作其他用户空间应用程序的接口,如下图所示:

Linux 内核的作用

Linux 内核的主要作用如下:

  • 它提供一组可移植的硬件和架构 API,为用户空间应用程序提供使用必要硬件资源的可能性。

  • 它有助于管理硬件资源,如 CPU、输入/输出外设和内存。

  • 它用于管理不同应用程序对必要硬件资源的并发访问和使用。

为了确保前述角色被充分理解,一个例子将非常有用。让我们假设在给定的 Linux 操作系统中,一些应用程序需要访问相同的资源,如网络接口或设备。对于这些元素,内核需要复用资源,以确保所有应用程序都可以访问它。

深入了解 Linux 内核的特性

本节将介绍 Linux 内核中的一些可用功能。它还将涵盖关于每个功能的信息,它们如何使用,代表什么,以及有关每个特定功能的任何其他相关信息。每个功能的介绍使您熟悉 Linux 内核中一些可用功能的主要作用,以及 Linux 内核和其源代码的一般情况。

更一般地说,Linux 内核具有一些最有价值的功能,如下所示:

  • 稳定性和可靠性

  • 可扩展性

  • 可移植性和硬件支持

  • 符合标准

  • 各种标准之间的互操作性

  • 模块化

  • 编程的便利性

  • 社区的全面支持

  • 安全性

前述功能并不构成实际功能,但它们在项目的开发过程中有所帮助,今天仍在帮助着它。话虽如此,有很多功能已经实现,例如快速用户空间互斥(futex)、netfileters、简化强制访问控制内核(smack)等。完整的列表可以在en.wikipedia.org/wiki/Category:Linux_kernel_features上访问和学习。

内存映射和管理

在讨论 Linux 中的内存时,我们可以将其称为物理内存和虚拟内存。RAM 内存的隔间用于包含 Linux 内核变量和数据结构,其余内存用于动态分配,如下所述:

内存映射和管理

物理内存定义了能够维护内存的算法和数据结构,它是在页面级别相对独立地由虚拟内存完成的。在这里,每个物理页面都有一个与之关联的struct page描述符,用于包含有关物理页面的信息。每个页面都有一个定义的struct page描述符。该结构的一些字段如下:

  • _count:这代表页面计数器。当它达到0值时,页面将被添加到空闲页面列表中。

  • 虚拟:这代表与物理页面关联的虚拟地址。ZONE_DMAZONE_NORMAL页面始终被映射,而ZONE_HIGHMEN不总是被映射。

  • 标志:这代表了描述页面属性的一组标志。

物理内存的区域以前已经被划分。物理内存被分割成多个节点,这些节点具有共同的物理地址空间和快速的本地内存访问。其中最小的是ZONE_DMA,介于 0 到 16Mb 之间。接下来是ZONE_NORMAL,它是介于 16Mb 到 896Mb 之间的 LowMem 区域,最大的是ZONE_HIGHMEM,介于 900Mb 到 4GB/64Gb 之间。这些信息可以在前面和后面的图像中都可见:

内存映射和管理

虚拟内存既用于用户空间,也用于内核空间。为内存区域分配意味着分配物理页面以及地址空间区域的分配;这既在页表中,也在操作系统内部可用的内部结构中完成。页表的使用因架构类型而异。对于复杂指令集计算(CISC)架构,页表由处理器使用,但对于精简指令集计算(RISC)架构,页表由核心用于页查找和转换查找缓冲器(TLB)添加操作。每个区域描述符用于区域映射。它指定了区域是否被映射以供文件使用,如果区域是只读的,写时复制的等等。地址空间描述符由操作系统用于维护高级信息。

用户空间和内核空间上下文之间的内存分配是不同的,因为内核空间内存分配无法以简单的方式分配内存。这种差异主要是因为内核上下文中的错误管理不容易完成,或者至少不是以与用户空间上下文相同的方式完成。这是本节将介绍的问题之一,以及解决方案,因为它有助于读者了解在 Linux 内核上下文中如何进行内存管理。

内核用于内存处理的方法是本节将讨论的第一个主题。这是为了确保您了解内核用于获取内存的方法。虽然处理器的最小可寻址单元是字节,但负责虚拟到物理转换的内存管理单元MMU)的最小可寻址单元是页面。页面的大小因架构而异。它负责维护系统的页表。大多数 32 位架构使用 4KB 页面,而 64 位架构通常使用 8KB 页面。对于 Atmel SAMA5D3-Xplained 板,struct page结构的定义如下:

struct page {
        unsigned long 	flags;
        atomic_t        _count;
        atomic_t        _mapcount;
        struct address_space *mapping;
        void        *virtual;
        unsigned long 	debug_flags;
        void        *shadow;
        int        _last_nid;

};

这是页面结构中最重要的字段之一。例如,flags字段表示页面的状态;这包含信息,例如页面是否脏了,是否被锁定,或者处于另一个有效状态。与此标志相关的值在include/linux/page-flags-layout.h头文件中定义。virtual字段表示与页面关联的虚拟地址,count表示页面的计数值,通常可以通过page_count()函数间接访问。所有其他字段都可以在include/linux/mm_types.h头文件中找到。

内核将硬件划分为各种内存区域,主要是因为物理内存中有一些页面对于一些任务是不可访问的。例如,有些硬件设备可以执行 DMA。这些操作只与物理内存的一个区域进行交互,简称为ZONE_DMA。对于 x86 架构,它在 0-16 Mb 之间可访问。

内核源代码中定义了四个主要的内存区域和另外两个不太显著的内存区域,这些定义在include/linux/mmzone.h头文件中。区域映射也与 Atmel SAMA5D3-Xplained 板的体系结构有关。我们有以下区域定义:

enum zone_type {
#ifdef CONFIG_ZONE_DMA
        /*
         * ZONE_DMA is used when there are devices that are not able
         * to do DMA to all of addressable memory (ZONE_NORMAL). Then we
         * carve out the portion of memory that is needed for these devices.
         * The range is arch specific.
         *
         * Some examples
         *
         * Architecture         Limit
         * ---------------------------
         * parisc, ia64, sparc  <4G
         * s390                 <2G
         * arm                  Various
         * alpha                Unlimited or 0-16MB.
         *
         * i386, x86_64 and multiple other arches
         *                      <16M.
         */
        ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
        /*
         * x86_64 needs two ZONE_DMAs because it supports devices that are
         * only able to do DMA to the lower 16M but also 32 bit devices that
         * can only do DMA areas below 4G.
         */
        ZONE_DMA32,
#endif
        /*
         * Normal addressable memory is in ZONE_NORMAL. DMA operations can be
         * performed on pages in ZONE_NORMAL if the DMA devices support
         * transfers to all addressable memory.
         */
        ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
        /*
         * A memory area that is only addressable by the kernel through
         * mapping portions into its own address space. This is for example
         * used by i386 to allow the kernel to address the memory beyond
         * 900MB. The kernel will set up special mappings (page
         * table entries on i386) for each page that the kernel needs to
         * access.
         */
        ZONE_HIGHMEM,
#endif
        ZONE_MOVABLE,
        __MAX_NR_ZONES
};

有一些分配需要与多个区域进行交互。一个例子是普通分配,可以使用ZONE_DMAZONE_NORMALZONE_NORMAL更受青睐,因为它不会干扰直接内存访问,尽管当内存使用完全时,内核可能会使用除正常情况下使用的区域之外的其他可用区域。可用的内核是一个struct zone结构,定义了每个区域的相关信息。对于 Atmel SAMA5D3-Xplained 板,该结构如下所示:

struct zone {
        unsigned long 	watermark[NR_WMARK];
        unsigned long 	percpu_drift_mark;
        unsigned long 	lowmem_reserve[MAX_NR_ZONES];
        unsigned long 	dirty_balance_reserve;
        struct per_cpu_pageset __percpu *pageset;
        spinlock_t        lock;
        int        all_unreclaimable;
        struct free_area        free_area[MAX_ORDER];
        unsigned int            compact_considered;
        unsigned int            compact_defer_shift;
        int                     compact_order_failed;
        spinlock_t              lru_lock;
        struct lruvec           lruvec;
        unsigned long         pages_scanned;
        unsigned long         flags;
        unsigned int        inactive_ratio;
        wait_queue_head_t       * wait_table;
        unsigned long         wait_table_hash_nr_entries;
        unsigned long         wait_table_bits;
        struct pglist_data    *zone_pgdat;
        unsigned long         zone_start_pfn;
        unsigned long         spanned_pages;
        unsigned long         present_pages;
        unsigned long         managed_pages;
        const char              *name;
};

如您所见,定义结构的区域是一个令人印象深刻的区域。一些最有趣的字段由watermark变量表示,其中包含所定义区域的高、中和低水印。present_pages属性表示区域内的可用页面。name字段表示区域的名称,还有其他字段,例如lock字段,一个用于保护区域结构免受同时访问的自旋锁。所有其他字段都可以在 Atmel SAMA5D3 Xplained 板的相应include/linux/mmzone.h头文件中找到。

有了这些信息,我们可以继续并了解内核如何实现内存分配。所有必要的内存分配和内存交互的可用函数都在linux/gfp.h头文件中。其中一些函数是:

struct page * alloc_pages(gfp_t gfp_mask, unsigned int order)

这个函数用于在连续位置分配物理页面。最后,如果分配成功,则返回值由第一个页面结构的指针表示,如果发生错误,则返回NULL

void * page_address(struct page *page)

这个函数用于获取相应内存页面的逻辑地址:

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

这个函数类似于alloc_pages()函数,但不同之处在于返回变量是在struct page * alloc_page(gfp_t gfp_mask)返回参数中提供的:

unsigned long __get_free_page(gfp_t gfp_mask)
struct page * alloc_page(gfp_t gfp_mask)

前两个函数是类似的函数的包装器,不同之处在于这个函数只返回一个页面信息。这个函数的顺序具有zero值:

unsigned long get_zeroed_page(unsigned int gfp_mask)

前面的函数就像其名称所示。它返回一个充满zero值的页面。这个函数与__get_free_page()函数的区别在于,在被释放后,页面被填充为zero值:

void __free_pages(struct page *page, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)
void free_page(unsigned long addr)

前面的函数用于释放给定的分配页面。传递页面时应谨慎,因为内核无法检查所提供的信息。

页面缓存和页面写回

通常磁盘比物理内存慢,所以这是内存优于磁盘存储的原因之一。对于处理器的缓存级别也是一样:它离处理器越近,对 I/O 访问就越快。将数据从磁盘移动到物理内存的过程称为页面缓存。相反的过程被定义为页面写回。这两个概念将在本小节中介绍,但主要是关于内核上下文。

内核第一次调用read()系统调用时,会验证数据是否存在于页面缓存中。在 RAM 中找到页面的过程称为缓存命中。如果数据不在那里,则需要从磁盘读取数据,这个过程称为缓存未命中

当内核发出write()系统调用时,关于这个系统调用的缓存交互有多种可能性。最简单的一种是不缓存写系统调用操作,只将数据保留在磁盘中。这种情况称为无写缓存。当写操作同时更新物理内存和磁盘数据时,该操作称为写透缓存。第三个选项由写回缓存表示,其中页面被标记为脏。它被添加到脏列表中,随着时间的推移,它被放在磁盘上并标记为非脏。脏关键字的最佳同义词由同步关键字表示。

进程地址空间

除了自己的物理内存外,内核还负责用户空间进程和内存管理。为每个用户空间进程分配的内存称为进程地址空间,它包含给定进程可寻址的虚拟内存地址。它还包含进程在与虚拟内存交互时使用的相关地址。

通常,进程接收一个平面的 32 位或 64 位地址空间,其大小取决于体系结构类型。然而,有些操作系统分配了分段地址空间。在线程之间提供了共享地址空间的可能性。虽然进程可以访问大量的内存空间,但通常只有权限访问内存的一部分。这被称为内存区域,意味着进程只能访问位于可行内存区域内的内存地址。如果它尝试管理位于其有效内存区域之外的内存地址,内核将使用段错误通知终止进程。

内存区域包含以下内容:

  • text部分映射源代码

  • 数据部分映射已初始化的全局变量

  • bss部分映射未初始化的全局变量

  • 零页部分用于处理用户空间堆栈

  • 共享库文本bss和数据特定部分

  • 映射文件

  • 匿名内存映射通常与malloc()等函数相关联

  • 共享内存段

进程地址空间在 Linux 内核源代码中通过内存描述符进行定义。这个结构被称为struct mm_struct,它在include/linux/mm_types.h头文件中定义,并包含与进程地址空间相关的信息,如使用地址空间的进程数量、内存区域列表、最后使用的内存区域、可用的内存区域数量、代码、数据、堆和栈部分的起始和结束地址。

对于内核线程,没有与之关联的进程地址空间;对于内核来说,进程描述符结构被定义为NULL。这样,内核表明内核线程没有用户上下文。内核线程只能访问与所有其他进程相同的内存。内核线程没有用户空间中的任何页面或对用户空间内存的访问权限。

由于处理器只能使用物理地址,因此需要进行物理和虚拟内存之间的转换。这些操作由页表完成,页表将虚拟地址分割为较小的组件,并具有用于指向目的的关联索引。在大多数可用的板和体系结构中,页表查找由硬件处理;内核负责设置它。

进程管理

进程是 Linux 操作系统中的基本单元,同时也是一种抽象形式。实际上,它是一个正在执行的程序,但程序本身不是一个进程。它需要处于活动状态并具有相关联的资源。通过使用fork()函数,进程能够成为父进程,从而生成一个子进程。父进程和子进程都驻留在单独的地址空间中,但它们都具有相同的内容。exec()函数族能够执行不同的程序,创建一个地址空间,并将其加载到该地址空间中。

使用fork()时,父进程的资源会被复制给子进程。这个函数的实现方式非常有趣;它使用clone()系统调用,其基础包含copy_process()函数。这个函数执行以下操作:

  • 调用dup_task_struct()函数创建一个新的内核栈。为新进程创建task_structthread_info结构。

  • 检查子进程是否超出内存区域的限制。

  • 子进程与父进程有所不同。

  • 将其设置为TASK_UNINTERRUPTIBLE以确保它不运行。

  • 更新标志。

  • PID与子进程相关联。

  • 检查已设置的标志,并根据它们的值执行适当的操作。

  • 在获取子进程指针时执行清理过程。

Linux 中的线程与进程非常相似。它们被视为共享各种资源(如内存地址空间、打开文件等)的进程。线程的创建类似于普通任务,唯一的例外是clone()函数,它传递了提到共享资源的标志。例如,clone 函数调用一个线程,即clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0),而对于正常的 fork 看起来类似于clone(SIGCHLD, 0)

内核线程的概念出现是为了解决在内核上下文的后台运行任务所涉及的问题。内核线程没有地址空间,只能在内核上下文中使用。它具有与普通进程相同的属性,但仅用于特殊任务,如ksoftirqdflush等。

在执行结束时,需要终止进程以释放资源,并通知执行进程的父进程。最常用于终止进程的方法是调用exit()系统调用。此过程需要一些步骤:

  1. 设置PF_EXITING标志。

  2. 调用del_timer_sync()函数来删除内核定时器。

  3. 在编写会计和日志信息时调用acct_update_integrals()函数。

  4. 调用exit_mm()来释放进程的mm_struct结构。

  5. 调用exit_sem()来从 IPC 信号量中出队进程。

  6. 调用exit_files()exit_fs()函数来删除与各种文件描述符的链接。

  7. 应设置任务退出代码。

  8. 调用exit_notify()通知父进程,并将任务退出状态设置为EXIT_ZOMBIE

  9. 调用schedule()切换到新进程。

在执行了前述步骤之后,与该任务关联的对象被释放,并且变得不可运行。它的内存仅作为其父进程的信息存在。在其父进程宣布此信息对其无用后,此内存将被系统释放使用。

进程调度

进程调度程序决定为可运行的进程分配哪些资源。它是一种负责多任务处理、资源分配给各种进程,并决定如何最佳设置资源和处理器时间的软件。它还决定哪些进程应该接下来运行。

Linux 调度程序的第一个设计非常简单。当进程数量增加时,它无法很好地扩展,因此从 2.5 内核版本开始,开发了一个新的调度程序。它被称为O(1)调度程序,为时间切片计算提供了常数时间算法,并且在每个处理器基础上定义了运行队列。虽然它非常适合大型服务器,但并不是普通桌面系统的最佳解决方案。从 2.6 内核版本开始,对 O(1)调度程序进行了改进,例如公平调度概念,后来从内核版本 2.6.23 实现为完全公平调度程序CFS),成为事实上的调度程序。

CFS 背后有一个简单的想法。它表现得好像我们有一个完美的多任务处理器,每个进程都获得处理器时间的1/n切片,而这个时间切片非常小。n值代表正在运行的进程数。Con Kolivas 是澳大利亚程序员,他为公平调度实现做出了贡献,也被称为旋转楼梯截止时间调度器RSDL)。它的实现需要一个红黑树来平衡自身的优先级,还需要在纳秒级别计算的时间切片。与 O(1)调度程序类似,CFS 应用了权重的概念,这意味着一些进程等待的时间比其他进程长。这是基于加权公平排队算法的。

进程调度程序构成了 Linux 内核中最重要的组件之一,因为它定义了用户与操作系统的一般交互。Linux 内核 CFS 是调度程序,吸引开发人员和用户的原因是它以最合理的方式提供了可伸缩性和性能。

系统调用

为了使进程与系统交互,应该提供一个接口,使用户空间应用程序能够与硬件和其他进程进行交互。这些被用作硬件和用户空间之间的接口。它们也被用于确保稳定性、安全性和抽象性。这些是构成内核入口点的常见层,以及陷阱和异常,如下所述:

系统调用

与 Linux 系统内大多数系统调用的交互是通过 C 库完成的。它们能够定义一些参数并返回一个值,以显示它们是否成功。通常,值为表示执行成功,如果出现错误,则errno变量中将可用错误代码。进行系统调用时,遵循以下步骤:

  1. 切换到内核模式。

  2. 对内核空间访问的任何限制都被消除。

  3. 用户空间的堆栈被传递到内核空间。

  4. 来自用户空间的任何参数都会被检查并复制到内核空间。

  5. 识别并运行与系统调用相关的例程。

  6. 切换到用户空间并继续执行应用程序。

系统调用有与之关联的syscall号码,这是一个唯一的数字,用作系统调用的参考,不能更改(无法实现系统调用)。每个系统调用号码的符号常量都在<sys/syscall.h>头文件中可用。要检查系统调用的存在,使用sys_ni_syscall(),它对于无效的系统调用返回ENOSYS错误。

虚拟文件系统

Linux 操作系统能够支持多种文件系统选项。这是由于存在虚拟文件系统VFS),它能够为大量文件系统类型提供一个通用接口,并处理与它们相关的系统调用。

VFS 支持的文件系统类型可以分为以下三类:

  • 基于磁盘的文件系统:这些管理本地磁盘或用于磁盘仿真的设备上的内存。其中一些最著名的是:

  • Linux 文件系统,如第二扩展文件系统(Ext2),第三扩展文件系统(Ext3)和第四扩展文件系统(Ext4)

  • UNIX 文件系统,如 sysv 文件系统,UFS,Minix 文件系统等

  • 微软文件系统,如 MS-DOS,NTFS(自 Windows NT 起可用)和 VFAT(自 Windows 95 起可用)

  • ISO966 CD-ROM 文件系统和磁盘格式 DVD 文件系统

  • 专有文件系统,如来自苹果、IBM 和其他公司的文件系统

  • 网络文件系统:它们允许在其他计算机上通过网络访问各种文件系统类型。其中最著名的之一是 NFS。当然,还有其他一些,但它们不那么出名。这些包括Andrew 文件系统AFS),Novel 的 NetWare Core ProtocolNCP),Constant Data AvailabilityCoda)等。

  • 特殊文件系统/proc文件系统是这类文件系统的完美例子。这类文件系统使系统应用程序更容易地访问内核的数据结构并实现各种功能。

虚拟文件系统系统调用的实现在这张图片中得到了很好的总结:

虚拟文件系统

在前面的图像中,可以看到如何轻松地从一种文件系统类型复制到另一种文件系统类型。它只使用基本的open()close()read()write()函数,这些函数对所有其他文件系统交互都可用。然而,它们都在所选文件系统下实现了特定的功能。例如,open()系统调用sys_open(),它接受与open()相同的参数并返回相同的结果。sys_open()open()之间的区别在于sys_open()是一个更宽松的函数。

其他三个系统调用都有相应的sys_read()sys_write()sys_close()函数在内部调用。

中断

中断是表示改变处理器执行指令顺序的事件的表示。中断意味着硬件生成的电信号,用于表示已发生的事件,例如按键、复位等。根据其参考系统,中断分为更多类别,如下所示:

  • 软件中断:这些通常是从外部设备和用户空间程序触发的异常

  • 硬件中断:这些是系统发出的信号,通常表示处理器特定的指令

Linux 中断处理层通过全面的 API 函数为各种设备驱动程序提供了中断处理的抽象。它用于请求、启用、禁用和释放中断,确保在多个平台上保证可移植性。它处理所有可用的中断控制器硬件。

通用中断处理使用__do_IRQ()处理程序,能够处理所有可用类型的中断逻辑。处理层分为两个组件:

  • 顶部组件用于响应中断

  • 顶部组件安排底部在稍后运行

它们之间的区别在于所有可用的中断都被允许在底部上下文中执行。这有助于顶部在底部工作时响应另一个中断,这意味着它能够将其数据保存在特定的缓冲区中,并允许底部在安全环境中运行。

对于底部处理,有四种定义好的机制可用:

  • 软中断

  • Tasklets

  • 工作队列

  • 内核线程

这里展示了可用的机制:

中断

尽管顶部和底部中断机制的模型看起来很简单,但它具有非常复杂的函数调用机制模型。这个例子展示了 ARM 架构的这一事实:

中断

对于中断的顶部组件,在中断源代码中有三个抽象级别。第一个是高级驱动程序 API,具有函数,如request_irq()free_irqdisable_irq()enable_irq()等。第二个由高级 IRQ 流处理程序表示,这是一个通用层,具有预定义或特定架构的中断流处理程序,用于在设备初始化或引导时响应各种中断。它定义了一些预定义函数,如handle_level_irq()handle_simple_irq()handle_percpu_irq()等。第三个由芯片级硬件封装表示。它定义了struct irq_chip结构,其中包含在 IRQ 流实现中使用的与芯片相关的函数。其中一些函数是irq_ack()irq_mask()irq_unmask()

模块需要注册中断通道并在之后释放它。支持的请求总数从0值计数到 IRQs 的数量-1。这些信息在<asm/irq.h>头文件中可用。注册完成后,将处理程序标志传递给request_irq()函数,以指定中断处理程序的类型,如下所示:

  • SA_SAMPLE_RANDOM:这表明中断可以为熵池做出贡献,即具有强随机属性的位的池,通过对不可预测事件进行采样,例如鼠标移动、按键间的时间、磁盘中断等

  • SA_SHIRQ:这表明中断可以在设备之间共享。

  • SA_INTERRUPT:这表示快速中断处理程序,因此在当前处理器上禁用中断-这并不是一个非常理想的情况

底半部

关于底半部中断处理的第一个机制是由softirqs代表的。它们很少使用,但可以在 Linux 内核源代码中的kernel/softirq.c文件中找到。在实现方面,它们在编译步骤时静态分配。当在include/linux/interrupt.h头文件中添加条目时,它们被创建,并且它们提供的系统信息可以在/proc/softirqs文件中找到。虽然不经常使用,但它们可以在异常、中断、系统调用以及由调度程序运行ksoftirkd守护程序后执行。

列表中的下一个是任务 let。虽然它们建立在softirqs之上,但它们更常用于底半部中断处理。以下是这样做的一些原因:

  • 它们非常快

  • 它们可以动态创建和销毁

  • 它们具有原子和非阻塞代码

  • 它们在软中断上下文中运行

  • 它们在被调度的同一处理器上运行

任务 let 有一个struct tasklet_struct结构可用。这些也可以在include/linux/interrupt.h头文件中找到,与softirqs不同,任务 let 是不可重入的。

列表中的第三个是工作队列,它代表了与先前介绍的机制相比进行工作分配的不同形式。主要区别如下:

  • 它们能够同时在多个 CPU 上运行

  • 它们可以进入睡眠状态

  • 它们在进程上下文中运行

  • 它们可以被调度或抢占

虽然它们可能具有比任务 let 稍大的延迟,但前述特性确实非常有用。任务 let 是围绕struct workqueue_struct结构构建的,该结构位于kernel/workqueue.c文件中。

底半部机制选项中的最后一个和最新的添加是由内核线程代表的,它们完全在内核模式下操作,因为它们是由内核创建/销毁的。它们出现在 2.6.30 内核发布中,并且具有与工作队列相同的优势,以及一些额外的功能,例如拥有自己的上下文的可能性。预计内核线程最终将取代工作队列和任务 let,因为它们类似于用户空间线程。驱动程序可能希望请求线程化的中断处理程序。在这种情况下,它只需要类似于request_irq()的方式使用request_threaded_irq()request_threaded_irq()函数提供了将中断处理代码分成两部分的处理程序和thread_fn的可能性。除此之外,还调用quick_check_handler来检查中断是否来自设备;如果是这种情况,它将需要调用IRQ_WAKE_THREAD来唤醒处理程序线程并执行thread_fn

执行内核同步的方法

内核正在处理的请求数量类似于服务器必须接收的请求数量。这种情况可能会导致竞争条件,因此需要一个良好的同步方法。有多种策略可用于定义内核控制路径的方式。以下是一个内核控制路径的示例:

执行内核同步的方法

前面的图像清楚地说明了为什么同步是必要的。例如,当多个内核控制路径相互关联时,可能会出现竞争条件。为了保护这些关键区域,应采取一些措施。还应考虑到中断处理程序不能被中断,softirqs不应该交错。

已经诞生了许多同步原语:

  • 每 CPU 变量:这是最简单和有效的同步方法之一。它将数据结构乘以每个 CPU 可用。

  • 原子操作:这指的是原子读-修改-写指令。

  • 内存屏障:这保证了在屏障之前完成的操作在开始屏障之后的操作之前全部完成。

  • 自旋锁:这代表一种实现忙等待的锁类型。

  • 信号量:这是一种实现休眠或阻塞等待的锁形式。

  • Seqlocks:这类似于自旋锁,但基于访问计数器。

  • 本地中断禁用:这禁止了可以在单个 CPU 上延迟使用的功能。

  • 读-拷贝-更新(RCU):这是一种旨在保护用于读取的最常用数据结构的方法。它使用指针为共享数据结构提供无锁访问。

通过上述方法,竞争条件情况试图得到解决。开发人员的工作是识别和解决可能出现的所有同步问题。

定时器

在 Linux 内核周围,有许多受时间影响的函数。从调度程序到系统正常运行时间,它们都需要一个时间参考,包括绝对时间和相对时间。例如,需要安排在未来进行的事件代表相对时间,实际上意味着有一种方法用于计算时间。

定时器的实现可以根据事件类型而变化。周期性实现由系统定时器定义,它以固定时间间隔发出中断。系统定时器是一个硬件组件,以给定频率发出定时器中断,以更新系统时间并执行必要的任务。还可以使用实时时钟,它是一个带有电池的芯片,即使在系统关闭后也能继续计时。除了系统时间,内核动态管理的动态定时器也可用于计划在特定时间后运行的事件。

定时器中断具有发生窗口,对于 ARM 来说,每秒发生 100 次。这称为系统定时器频率滴答率,其单位是赫兹Hz)。滴答率因架构而异。对于大多数架构,我们有 100 Hz 的值,还有其他架构的值为 1024 Hz,例如 Alpha 和 Itanium(IA-64)架构。当然,默认值可以更改和增加,但这种操作有其优点和缺点。

更高频率的一些优点包括:

  • 定时器将更准确地执行,并且数量更多。

  • 使用超时的系统调用以更精确的方式执行

  • 正常运行时间测量和其他类似测量变得更加精确

  • 进程抢占更准确

另一方面,更高频率的缺点意味着更高的开销。处理器在定时器中断上花费更多时间;此外,由于进行了更多的计算,将会发生功耗的增加。

Linux 操作系统上的总 ticks 数,从启动开始计时,存储在include/linux/jiffies.h头文件中的一个名为jiffies的变量中。在启动时,这个变量被初始化为零,并且每次发生中断时都会将其值加一。因此,系统正常运行时间的实际值可以以 jiffies/Hz 的形式计算出来。

Linux 内核交互

到目前为止,您已经了解了 Linux 内核的一些特性。现在,是时候介绍更多关于开发过程、版本控制方案、社区贡献以及与 Linux 内核的交互的信息了。

开发过程

Linux 内核是一个众所周知的开源项目。为了确保开发人员知道如何与其交互,将介绍如何使用git与该项目进行交互的信息,同时还将介绍一些关于其开发和发布程序的信息。该项目已经发展,其开发流程和发布程序也随之发展。

在介绍实际的开发过程之前,需要了解一些历史。在 Linux 内核项目的 2.6 版本之前,每两三年发布一次版本,并且每个版本都以偶数中间数字标识,如 1.0.x、2.0.x 和 2.6.x。相反,开发分支使用偶数号来定义,如 1.1.x、2.1.x 和 2.5.x,并且它们用于集成各种功能,直到准备好进行主要发布并准备好进行发布。所有次要发布都有名称,如 2.6.32 和 2.2.23,并且它们是在主要发布周期之间发布的。

开发过程

这种工作方式一直持续到 2.6.0 版本,当时在每个次要版本发布期间在内核中添加了大量功能,并且所有这些功能都非常好地组合在一起,以免引起需要分支出一个新的开发分支的需求。这意味着发布速度更快,可用功能更多。因此,自 2.6.14 内核发布以来,出现了以下变化:

  • 所有新的次要发布版本,如 2.6.x,都包含一个两周的合并窗口,在这个窗口中可以引入下一个发布中的一些功能。

  • 这个合并窗口将在一个名为 2.6.(x+1)-rc1 的发布测试版本关闭

  • 然后是一个 6-8 周的错误修复期,期间应该修复由新增功能引入的所有错误

  • 在错误修复间隔期间,对发布候选版本进行了测试,并发布了 2.6.(x+1)-rcY 测试版本

  • 在最终测试完成并且最后一个发布候选版本被认为足够稳定之后,将发布一个名为 2.6.(x+1)的新版本,然后这个过程将再次继续

这个过程运行得很好,但唯一的问题是,错误修复只发布给最新的稳定版本的 Linux 内核。人们需要长期支持版本和旧版本的安全更新,以及关于这些长期支持版本的一般信息等。

这个过程随着时间的推移而改变,在 2011 年 7 月,出现了 3.0 Linux 内核版本。它出现了一些小的变化,旨在改变解决先前提到的请求的交互方式。更改是针对编号方案进行的,如下所示:

  • 内核官方版本将被命名为 3.x(3.0, 3.1, 3.2 等)

  • 稳定版本将被命名为 3.x.y(3.0.1, 3.1.3 等)

尽管这个变化只是从编号方案中删除了一个数字,但这个变化是必要的,因为它标志着 Linux 内核的 20 周年。

由于 Linux 内核每天包含大量的补丁和功能,很难跟踪所有的变化和整体的大局。随着时间的推移,出现了一些网站,如kernelnewbies.org/LinuxChangeslwn.net/,帮助开发人员与 Linux 内核的世界保持联系。

除了这些链接,git版本控制系统可以提供非常需要的信息。当然,这需要工作站上存在 Linux 内核源克隆。一些提供大量信息的命令包括:

  • git log: 这列出了最新的所有提交

  • git log –p: 这列出了所有提交及其相应的diffs

  • git tag –l: 这列出了所有可用的标签

  • git checkout <tagname>: 这从工作库中检出一个分支或标签

  • git log v2.6.32..master: 这列出了给定标签和最新版本之间的所有更改

  • git log –p V2.6.32..master MAINTAINERS: 这列出了MAINTAINERS文件中两个给定分支之间的所有差异

当然,这只是一个有用命令的小列表。所有其他命令都可以在git-scm.com/docs/上找到。

内核移植

Linux 内核支持多种 CPU 架构。每个架构和单独的板都有自己的维护者,这些信息可以在MAINTAINERS文件中找到。此外,板的移植差异主要由架构决定,PowerPC 与 ARM 或 x86 非常不同。由于本书关注的开发板是一款搭载 ARM Cortex-A5 核心的 Atmel,本节将尝试关注 ARM 架构。

在我们的情况下,主要关注的是arch/arm目录,其中包含诸如bootcommonconfigscryptofirmwarekernelkvmlibmmnetnwfpeoprofiletoolsvfpxen等子目录。它还包含了许多针对不同 CPU 系列特定的目录,例如mach-*目录或plat-*目录。第一个mach-*类别包含了 CPU 和使用该 CPU 的几个板,第二个plat-*类别包含特定于平台的代码。一个例子是plat-omap,其中包含了mach-omap1mach-omap2的通用代码。

自 2011 年以来,ARM 架构的开发发生了很大变化。如果直到那时 ARM 没有使用设备树,那是因为它需要将大部分代码保留在mach-*特定目录中,对于每个在 Linux 内核中有支持的板,都会关联一个唯一的机器 ID,并且一个机器结构与包含特定信息和一组回调的每个板相关联。引导加载程序将这个机器 ID 传递给特定的 ARM 注册表,这样内核就知道了板子。

ARM 架构的流行增加是因为工作重构和设备树的引入,这大大减少了mach-*目录中可用的代码量。如果 SoC 受到 Linux 内核的支持,那么为板添加支持就像在/arch/arm/boot/dts目录中定义一个设备树一样简单,例如,对于<soc-name>-<board-name>.d,如果需要,包含相关的dtsi文件。确保通过将设备树包含到arch/arm/boot/dts/Makefile中并为板添加缺失的设备驱动程序来构建设备树 blobDTB)。

如果板上没有在 Linux 内核中的支持,需要在mach-*目录中进行适当的添加。在每个mach-*目录中,有三种类型的文件可用:

  • 通用代码文件:这些通常只有一个单词的名称,比如clock.cled.c

  • 特定于 CPU 的代码:这是用于机器 ID 的,通常采用<machine-ID>*.c的形式 - 例如,at91sam9263.cat91sam9263_devices.csama5d3.c

  • 特定于板子的代码:这通常被定义为 board-*.c,比如board-carmeva.cboard-pcontrol-g20.c

对于给定的板子,应首先在arch/arm/mach-*/Kconfig文件中进行适当的配置;为此,应该为板子 CPU 确定机器 ID。配置完成后,可以开始编译,因此应该更新arch/arm/mach-*/Makefile以确保板子支持所需的文件。另一步是由定义板子和需要在board-<machine>.c文件中定义的机器类型号的机器结构表示。

机器结构使用两个宏:MACHINE_STARTMACHINE_END。它们都在arch/arm/include/asm/march/arch.h中定义,并用于定义machine_desc结构。机器类型号可在arch/arm/tools/mach_types文件中找到。该文件用于为板子生成include/asm-arm/mach-types.h文件。

注意

机器类型的更新编号列表可在www.arm.linux.org.uk/developer/machines/download.php上找到。

在第一种情况下,当启动过程开始时,只需要将dtb传递给引导加载程序并加载以初始化 Linux 内核,而在第二种情况下,需要将机器类型号加载到R1寄存器中。在早期的启动过程中,__lookup_machine_type寻找machine_desc结构并加载它以初始化板子。

社区互动

在向您呈现了这些信息之后,如果你渴望为 Linux 内核做出贡献,那么接下来应该阅读这一部分。如果你真的想为 Linux 内核项目做出贡献,那么在开始这项工作之前应该执行一些步骤。这主要涉及文档和调查。没有人想要发送重复的补丁或徒劳地复制别人的工作,因此在互联网上搜索你感兴趣的主题可以节省大量时间。另一个有用的建议是,在熟悉了主题之后,避免发送权宜之计。尝试解决问题并提供解决方案。如果不能,报告问题并进行彻底描述。如果找到解决方案,那么在补丁中同时提供问题和解决方案。

在开源社区中最有价值的事情之一是你可以从他人那里得到帮助。分享你的问题和困难,但不要忘记提到解决方案。在适当的邮件列表中提出问题,并尽量避免联系维护者。他们通常非常忙,有数百甚至数千封邮件需要阅读和回复。在寻求帮助之前,尽量研究你想提出的问题,这将有助于表达问题,也可能提供答案。如果可能的话,使用 IRC 来提出较小的问题,最重要的是,尽量不要过度使用。

当你准备好补丁时,确保它是在相应的分支上完成的,并且你首先阅读Documentation/BUG-HUNTING文件。如果有的话,识别 bug 报告,并确保将你的补丁链接到它们。在发送之前不要犹豫阅读Documentation/SubmittingPatches指南。在发送补丁之前一定要进行适当的测试。始终签署你的补丁,并尽可能使第一行描述具有指导性。在发送补丁时,找到适当的邮件列表和维护者,并等待回复。解决评论并重新提交,直到补丁被认为是可以接受的。

内核源码

Linux 内核的官方位置位于www.kernel.org,但有许多较小的社区为 Linux 内核贡献其特性,甚至维护自己的版本。

尽管 Linux 核心包含调度程序、内存管理和其他功能,但其大小相当小。大量的设备驱动程序、架构和板支持以及文件系统、网络协议和所有其他组件使得 Linux 内核的大小真正庞大。这可以通过查看 Linux 目录的大小来看出。

Linux 源代码结构包含以下目录:

  • arch:这包含了与架构相关的代码

  • block:这包含了块层核心

  • crypto:这包含了加密库

  • drivers:这收集了除声音驱动程序之外的所有设备驱动程序的实现

  • fs:这收集了所有可用的文件系统实现

  • include:这包含了内核头文件

  • init:这包含了 Linux 初始化代码

  • ipc:这包含了进程间通信实现代码

  • kernel:这是 Linux 内核的核心

  • lib:这包含了各种库,如zlibccrc

  • mm:这包含了内存管理的源代码

  • net:这提供了对 Linux 内部支持的所有网络协议实现的访问

  • samples:这提供了许多示例实现,如kfifokobject

  • scripts:这既在内部又在外部使用

  • security:这包含了许多安全实现,如apparmorselinuxsmack

  • sound:这包含了声音驱动程序和支持代码

  • usr:这是生成源代码的initramfs cpio存档

  • virt:这包含了虚拟化支持的源代码

  • COPYING:这代表了 Linux 许可证和定义复制条件

  • CREDITS:这代表了 Linux 的主要贡献者的集合

  • Documentation:这包含了内核源代码的相应文档

  • Kbuild:这代表了顶层的内核构建系统

  • Kconfig:这是配置参数的顶层描述符

  • MAINTAINERS:这是每个内核组件的维护者列表

  • Makefile:这代表了顶层的 makefile

  • README:这个文件描述了 Linux 是什么,是理解项目的起点

  • REPORTING-BUGS:这提供了关于错误报告程序的信息

正如所见,Linux 内核的源代码非常庞大,因此需要一个浏览工具。有许多可以使用的工具,如CscopeKscope,或者 Web 浏览器Linux Cross ReferenceLXR)。Cscope 是一个庞大的项目,也可以通过vimemacs的扩展来使用。

配置内核

在构建 Linux 内核映像之前,需要进行适当的配置。考虑到我们可以访问数百甚至数千个组件,如驱动程序、文件系统和其他项目,这是困难的。在配置阶段内进行选择过程,并且这是可能的依赖关系的帮助下。用户有机会使用和定义一些选项,以便定义将用于为特定板构建 Linux 内核映像的组件。

所有支持板的特定配置都位于一个名为.config的配置文件中,它位于先前介绍的文件和目录位置的同一级别。它们的形式通常表示为configuration_key=value。当然,这些配置之间存在依赖关系,因此它们在Kconfig文件中定义。

以下是一些可用于配置键的变量选项:

  • bool:这些选项可以有真或假的值

  • 三态:除了真和假选项之外,也可以作为模块选项出现

  • int:这些值并不是那么常见,但它们通常具有一个很好的值范围

  • string:这些值也不是最常见的,但通常包含一些非常基本的信息。

关于Kconfig文件,有两个选项可用。第一个选项只有在启用选项 B 时才会显示选项 A,并被定义为取决于,第二个选项提供了启用选项 A 的可能性。当选项被自动启用并被定义为选择时,就会执行此操作。

除了手动配置.config文件之外,配置对于开发人员来说是最糟糕的选择,主要是因为它可能会忽略一些配置之间的依赖关系。我想建议开发人员使用menuconfig命令,它将启动一个文本控制台工具,用于配置内核映像。

编译和安装内核

配置完成后,可以开始编译过程。我想给出的建议是,如果主机机器提供了这种可能性,尽可能使用多个线程,因为这将有助于构建过程。构建过程的启动命令示例是make -j 8

在构建过程结束时,将提供一个vmlinux映像,以及一些特定于体系结构的映像,可在 ARM 体系结构的特定文件中使用。其结果可在arch/arm/boot/*Image中找到。此外,Atmel SAMA5D3-Xplained 板将提供一个特定的设备树文件,可在arch/arm/boot/dts/*.dtb中找到。如果vmlinux映像文件是带有调试信息的 ELF 文件,除了调试目的外不能用于引导,那么arch/arm/boot/*Image文件就是解决此目的的方法。

安装是开发完成后的下一步。对于 Linux 内核也是如此,但在嵌入式环境中,这一步似乎有点不必要。对于 Yocto 爱好者,这一步也是可用的。然而,在这一步中,为内核源代码进行适当的配置,并且要使用由存储部署步骤的依赖项使用的头文件。

内核模块,如交叉编译章节中所述,需要稍后用于编译器构建。可以使用 make modules_install命令进行内核模块的安装,这提供了在/lib/modules/<linux-kernel-version>目录中安装可用源的可能性,包括所有模块依赖项、符号和别名。

交叉编译 Linux 内核

在嵌入式开发中,编译过程意味着交叉编译,与本地编译过程最明显的区别是它具有一个以目标架构为前缀的命名。前缀设置可以使用ARCH变量来定义目标板的架构名称,以及CROSS_COMPILE变量来定义交叉编译工具链的前缀。它们都在顶层Makefile中定义。

最好的选择是将这些变量设置为环境变量,以确保不会为主机机器运行 make 过程。虽然它只在当前终端中有效,但在没有自动化工具可用的情况下,比如 Yocto 项目,这将是最好的解决方案。不过,如果您计划在主机机器上使用多个工具链,不建议更新.bashrc shell 变量。

设备和模块

如我之前提到的,Linux 内核有许多内核模块和驱动程序,这些模块和驱动程序已经在 Linux 内核的源代码中实现并可用。其中许多模块也可以在 Linux 内核源代码之外找到。将它们放在外面不仅可以减少启动时间,而且可以根据用户的请求和需求进行初始化。唯一的区别是,加载和卸载模块需要 root 访问权限。

加载和与 Linux 内核模块交互需要提供日志信息。对于任何内核模块依赖项也是如此。日志信息可通过dmesg命令获得,并且日志级别可以使用loglevel参数手动配置,也可以使用quite参数禁用。对于内核依赖项,有关它们的信息可在/lib/modules/<kernel-version>/modules.dep文件中找到。

对于模块交互,有多个用于多个操作的实用程序可用,例如modinfo用于收集有关模块的信息;insmod用于在给定内核模块的完整路径时加载模块。类似的实用程序也可用于模块。其中一个称为modprobemodprobe的区别在于不需要完整路径,因为它负责在加载自身之前加载所选内核对象的依赖模块。modprobe提供的另一个功能是-r选项。这是删除功能,它支持删除模块及其所有依赖项。这方面的替代方法是rmmod实用程序,它删除不再使用的模块。最后一个可用的实用程序是lsmod,它列出加载的模块。

可以编写的最简单的内核模块示例看起来类似于这样:

#define MODULE
#define LINUX
#define __KERNEL__

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int hello_world_init(void)
{
   printk(KERN_ALERT "Hello world!\n");
   return 0;
}

static void hello_world_exit(void)
{
   printk(KERN_ALERT "Goodbye!\n");
}

module_init(hello_world_init);
module_exit(hello_world_exit);

MODULE_LICENSE("GPL");

这是一个简单的“hello world 内核”模块。可以从上面的示例中获得的有用信息是,每个内核模块都需要在上面的示例中定义为hello_world_init()的启动函数。当模块被插入时,它被调用,当模块被移除时,被调用的清理函数称为hello_world_exit()

自 Linux 内核版本 2.2 以来,可以以这种方式使用_init__exit宏:

static int __init hello_world_init (void)
static void __exit hello_world_exit (void)

前面的宏被移除,第一个在初始化后被移除,第二个在模块内置在 Linux 内核源代码中时被移除。

注意

有关 Linux 内核模块的更多信息可以在 Linux内核模块编程指南中找到,网址为www.tldp.org/LDP/lkmpg/2.6/html/index.html

如前所述,内核模块不仅可以在 Linux 内核内部使用,还可以在 Linux 内核树之外使用。对于内置的内核模块,编译过程类似于其他可用的内核模块的编译过程,开发人员可以从中汲取灵感。在 Linux 内核驱动程序之外可用的内核模块和构建过程需要访问 Linux 内核源代码或内核头文件。

对于在 Linux 内核源代码之外可用的内核模块,有一个Makefile示例,如下:

KDIR := <path/to/linux/kernel/sources>
PWD := $(shell pwd)
obj-m := hello_world.o
all:
$(MAKE) ARCH=arm CROSS_COMPILE=<arm-cross-compiler-prefix> -C
$(KDIR) M=$(PWD)

对于在 Linux 内核内实现的模块,需要在相应的Kconfig文件中提供模块的配置,并进行正确的配置。此外,需要更新Kconfig文件附近的Makefile,以便让Makefile系统知道何时更新模块的配置并构建源代码。我们将在这里看到一个这种类型的内核设备驱动程序的示例。

Kconfig文件的示例如下:

config HELLO_WORLD_TEST 
 tristate "Hello world module test"
 help
 To compile this driver as a module chose the M option.
 otherwise chose Y option.

Makefile的示例如下:

obj-$(CONFIG_ HELLO_WORLD_TEST)  += hello_world.c

在这两个示例中,源代码文件是hello_world.c,如果没有内置,生成的内核模块称为hello_world.ko

驱动程序通常用作与公开多种硬件特性的框架的接口,或者与用于检测和与硬件通信的总线接口一起使用。最好的例子在这里展示:

设备和模块

由于使用设备驱动程序的多种情况以及可用的三种设备模式结构:

  • struct bus_type:表示总线类型,如 I2C、SPI、USB、PCI、MMC 等

  • struct device_driver:这代表了用于处理总线上特定设备的驱动程序

  • struct device:用于表示连接到总线的设备

继承机制用于从更通用的结构(例如struct device_driverstruct device)创建专门的结构,用于每个总线子系统。总线驱动程序负责表示每种类型的总线,并将相应的设备驱动程序与检测到的设备匹配,检测是通过适配器驱动程序完成的。对于不可发现的设备,在设备树或 Linux 内核源代码中进行描述。它们由支持平台驱动程序并处理平台设备的平台总线处理。

调试内核

不得不调试 Linux 内核并不是一项容易的任务,但必须完成以确保开发过程向前推进。当然,理解 Linux 内核是其中的先决条件之一。一些可用的错误非常难以解决,并且可能在 Linux 内核中存在很长一段时间。

对于大多数简单的问题,应该采取以下一些步骤。首先,正确地识别错误;这不仅在定义问题时有用,而且在重现问题时也有帮助。第二步涉及找到问题的源头。这里,我指的是首次报告错误的内核版本。对于错误或 Linux 内核源代码的良好了解总是有用的,因此在开始工作之前,请确保您理解代码。

Linux 内核中的错误有着广泛的传播。它们从变量未正确存储到竞争条件或硬件管理问题,表现出各种各样的表现形式和一系列事件。然而,调试它们并不像听起来那么困难。除了一些特定的问题,如竞争条件和时间限制,调试与调试任何大型用户空间应用程序非常相似。

调试内核的第一种、最简单、最方便的方法是使用printk()函数。它非常类似于printf()C 库函数,虽然有些过时并且不被一些人推荐,但它确实有效。新的首选方法涉及使用pr_*()函数,比如pr_emerg()pr_alert()pr_crit()pr_debug()等。另一种方法涉及使用dev_*()函数,比如dev_emerg()dev_alert()dev_crit()dev_dbg()等。它们对应于每个日志级别,并且还有一些额外的函数,用于调试目的,并且在启用CONFIG_DEBUG时进行编译。

注意

有关pr_*()dev_*()函数族的更多信息可以在 Linux 内核源代码的Documentation/dynamic-debug-howto.txt中找到。您还可以在Documentation/kernel-parameters.txt中找到有关loglevel的更多信息。

当内核发生oops崩溃时,表示内核犯了一个错误。无法修复或自杀,它提供了一堆信息,如有用的错误消息、寄存器内容和回溯信息。

Magic SysRq键是调试中使用的另一种方法。它由CONFIG_MAGIC_SYSRQ config启用,并可用于调试和救援内核信息,而不管其活动性。它提供了一系列命令行选项,可用于各种操作,从更改优先级到重新启动系统。此外,可以通过更改/proc/sys/kernel/sysrq文件中的值来切换开关。有关系统请求键的更多信息,请参阅Documentation/sysrq.txt

尽管 Linus Torvalds 和 Linux 社区并不认为内核调试器的存在对项目有多大好处,但对代码的更好理解是任何项目的最佳方法。仍然有一些调试器解决方案可供使用。GNU 调试器(gdb)是第一个,它可以像其他任何进程一样使用。另一个是kgdb,它是gdb的一个补丁,允许调试串行连接。

如果前面的方法都无法解决问题,并且您已经尝试了一切但似乎无法得出解决方案,那么您可以联系开源社区寻求帮助。总会有开发人员愿意帮助您。

注意

要获取与 Linux 内核相关的更多信息,可以查阅一些书籍。我将在这里列出一些书名:Christopher Hallinan 的嵌入式 Linux 入门,Robert Love 的Linux 内核开发,Greg Kroah-Hartman 的Linux 内核要点,最后但同样重要的是,Daniel P. Bovet 和 Marco Cesati 的理解 Linux 内核

Yocto 项目参考

转向 Yocto 项目,我们为每个支持的板上的 BSP 支持内核版本提供了配方,并为在 Linux 内核源树之外构建的内核模块提供了配方。

Atmel SAMA5D3-Xplained 板使用linux-yocto-custom内核。这是通过conf/machine/sama5d3-xplained.conf机器配置文件使用PREFERRED_PROVIDER_virtual/kernel变量进行定义的。没有提到PREFERRED_VERSION,因此首选最新版本;在这种情况下,我们谈论的是linux-yocto-custom_3.10.bb配方。

linux-yocto-custom_3.10.bb配方从 Linux Torvalds 的git存储库中提取可用的内核源代码。在do_fetch任务完成后快速查看源代码后,可以观察到实际上已经提取了 Atmel 存储库。答案可以在linux-yocto-custom_3.10.bbappend文件中找到,该文件提供了另一个SR_URI位置。您可以从这里收集到的其他有用信息是在 bbappend 文件中可用的,其中非常清楚地说明了 SAMA5D3 Xplained 机器是COMPATIBLE_MACHINE

KBRANCH = "linux-3.10-at91"
SRCREV = "35158dd80a94df2b71484b9ffa6e642378209156"
PV = "${LINUX_VERSION}+${SRCPV}"

PR = "r5"

FILESEXTRAPATHS_prepend := "${THISDIR}/files/${MACHINE}:"

SRC_URI = "git://github.com/linux4sam/linux-at91.git;protocol=git;branch=${KBRANCH};nocheckout=1"
SRC_URI += "file://defconfig"

SRCREV_sama5d4-xplained = "46f4253693b0ee8d25214e7ca0dde52e788ffe95"

do_deploy_append() {
  if [ ${UBOOT_FIT_IMAGE} = "xyes" ]; then
    DTB_PATH="${B}/arch/${ARCH}/boot/dts/"
    if [ ! -e "${DTB_PATH}" ]; then
      DTB_PATH="${B}/arch/${ARCH}/boot/"
    fi

    cp ${S}/arch/${ARCH}/boot/dts/${MACHINE}*.its ${DTB_PATH}
    cd ${DTB_PATH}
    mkimage -f ${MACHINE}.its ${MACHINE}.itb
    install -m 0644 ${MACHINE}.itb ${DEPLOYDIR}/${MACHINE}.itb
    cd -
  fi
}

COMPATIBLE_MACHINE = "(sama5d4ek|sama5d4-xplained|sama5d3xek|sama5d3-xplained|at91sam9x5ek|at91sam9rlek|at91sam9m10g45ek)"

配方首先定义了与存储库相关的信息。它通过变量(如SRC_URISRCREV)进行定义。它还通过KBRANCH变量指示存储库的分支,并且还指示defconfig需要放入源代码中以定义.config文件的位置。正如在配方中所看到的,对内核配方的do_deploy任务进行了更新,以将设备驱动程序添加到tmp/deploy/image/sama5d3-xplained目录中,与内核映像和其他二进制文件一起。

内核配方继承了kernel.bbclasskernel-yocto.bbclass文件,这些文件定义了大部分任务操作。由于它还生成设备树,因此需要访问linux-dtb.inc,该文件位于meta/recipes-kernel/linux目录中。linux-yocto-custom_3.10.bb配方中提供的信息相当通用,并且被bbappend文件覆盖,如下所示:

SRC_URI = "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git;protocol=git;nocheckout=1"

LINUX_VERSION ?= "3.10"
LINUX_VERSION_EXTENSION ?= "-custom"

inherit kernel
require recipes-kernel/linux/linux-yocto.inc

# Override SRCREV to point to a different commit in a bbappend file to
# build a different release of the Linux kernel.
# tag: v3.10 8bb495e3f02401ee6f76d1b1d77f3ac9f079e376"
SRCREV = "8bb495e3f02401ee6f76d1b1d77f3ac9f079e376"

PR = "r1"
PV = "${LINUX_VERSION}+git${SRCPV}"

# Override COMPATIBLE_MACHINE to include your machine in a bbappend
# file. Leaving it empty here ensures an early explicit build failure.
COMPATIBLE_MACHINE = "(^$)"

# module_autoload is used by the kernel packaging bbclass
module_autoload_atmel_usba_udc = "atmel_usba_udc"
module_autoload_g_serial = "g_serial"

通过运行bitbake virtual/kernel命令构建内核后,内核映像将在tmp/deploy/image/sama5d3-xplained目录下以zImage-sama5d3-xplained.bin名称可用,这是一个符号链接到完整名称文件,并具有更大的名称标识符。内核映像是从执行 Linux 内核任务的地方部署到这里的。发现该位置的最简单方法是运行bitbake –c devshell virtual/kernel。开发 shell 将可供用户直接与 Linux 内核源代码进行交互,并访问任务脚本。这种方法是首选的,因为开发人员可以访问与bitbake相同的环境。

另一方面,如果内核模块不是内置在 Linux 内核源树中,则具有不同类型行为。对于在源树之外构建的模块,需要编写一个新的配方,即继承另一个名为module.bbclassbitbake类的配方。一个外部 Linux 内核模块的示例可在meta-skeleton层的recipes-kernel/hello-mod目录中找到。

SUMMARY = "Example of how to build an external Linux kernel module"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://COPYING;md5=12f884d2ae1ff87c09e5b7ccc2c4ca7e"

inherit module

PR = "r0"
PV = "0.1"

SRC_URI = "file://Makefile \
           file://hello.c \
           file://COPYING \
          "

S = "${WORKDIR}"

# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.

在 Linux 内核外部模块的示例中提到,每个外部或内部内核模块的最后两行都使用kernel-module-前缀打包,以确保当IMAGE_INSTALL变量可用时,值 kernel-modules 将添加到/lib/modules/<kernel-version>目录中所有可用的内核模块。内核模块配方与任何可用配方非常相似,主要区别在于继承的模块形式,如继承模块一行所示。

在 Yocto Project 中,有多个可用命令与内核和内核模块配方进行交互。最简单的命令当然是bitbake <recipe-name>,但对于 Linux 内核,有许多可用命令可以使交互更容易。最常用的是bitbake -c menuconfig virtual/kernel操作,它提供了对内核配置菜单的访问。

除了已知的任务,如configurecompiledevshell,主要用于开发过程,还有其他任务,如diffconfig,它使用 Linux 内核scripts目录中可用的diffconfig脚本。 Yocto Project 的实现与 Linux 内核的可用脚本之间的区别在于前者添加了内核config创建阶段。这些config片段用于将内核配置添加到.config文件中,作为自动化过程的一部分。

摘要

在本章中,您了解了 Linux 内核的一般情况,以及与其交互的特性和方法。还有关于调试和移植特性的信息。所有这些都是为了确保在与其交互之前,您能够获得足够的信息。我认为,如果您首先了解整个情况,将更容易专注于更具体的事物。这也是 Yocto Project 参考资料被保留到最后的原因之一。您将了解如何定义 Linux 内核配方和 Linux 内核外部模块,并在稍后由特定机器使用。有关 Linux 内核的更多信息也将在下一章中提供,该章将汇总先前提供的所有信息,并向您展示开发人员如何与 Linux 操作系统映像进行交互。

除了这些信息之外,在下一章中,将会对根文件系统的组织及其背后的原理、内容和设备驱动程序进行解释。Busybox 是另一个有趣的主题,将进行讨论,还有各种可用的文件系统支持。由于它倾向于变得更大,关于最小文件系统应该是什么样子的信息也将被呈现。说到这里,我们将继续下一章。

第五章:Linux 根文件系统

在本章中,您将了解根文件系统及其结构。您还将获得有关根文件系统内容、各种设备驱动程序以及与 Linux 内核的通信的信息。我们将逐渐过渡到 Yocto 项目以及用于定义 Linux 根文件系统内容的方法。将提供必要的信息,以确保用户能够根据自己的需求定制rootfs文件系统。

将介绍根文件系统的特殊要求。您将获得有关其内容、子目录、定义目的、各种文件系统选项、BusyBox 替代方案以及许多有趣功能的信息。

在与嵌入式环境交互时,许多开发人员会从分发提供商(如 Debian)那里获得一个最小的根文件系统,并使用交叉工具链来增强它,添加各种软件包、工具和实用程序。如果要添加的软件包数量很大,这可能会是非常麻烦的工作。从头开始将是一个更大的噩梦。在 Yocto 项目中,这项工作是自动化的,无需手动工作。开发是从头开始的,并且在根文件系统中提供了大量的软件包,使工作变得有趣和有趣。因此,让我们继续前进,看看本章的内容,以更全面地了解根文件系统。

与根文件系统交互

根文件系统由目录和文件层次结构组成。在这个文件层次结构中,可以挂载各种文件系统,显示特定存储设备的内容。挂载是使用mount命令完成的,在操作完成后,挂载点将被存储设备上可用的内容填充。反向操作称为umount,用于清空挂载点的内容。

前面的命令对应用程序与各种可用文件的交互非常有用,无论它们的位置和格式如何。例如,mount命令的标准形式是mount -t type device directory。这个命令要求内核连接设备上的文件系统,该设备在命令行中指定了type格式,同时还要连接命令中提到的目录。在移除设备之前,需要使用umount命令来确保内核缓存被写入存储点。

根文件系统位于根目录结构中,也称为/。它是第一个可用的文件系统,也是不使用mount命令的文件系统,因为它是通过内核直接通过root=参数挂载的。以下是加载根文件系统的多个选项:

  • 从内存

  • 使用 NFS 从网络中

  • 从 NAND 芯片

  • 从 SD 卡分区

  • 从 USB 分区

  • 从硬盘分区

这些选项由硬件和系统架构师选择。要使用这些选项,需要相应地配置内核和引导加载程序。

除了需要与板载内存或存储设备进行交互的选项外,加载根文件系统最常用的方法之一是 NFS 选项,这意味着根文件系统在本地机器上可用,并且在目标机器上通过网络进行导出。此选项提供以下优势:

  • 由于开发机器上的存储空间比目标机器上的存储空间大得多,根文件系统的大小不会成为问题

  • 更新过程更容易,无需重新启动

  • 访问网络存储是对于内部或外部存储空间较小甚至不存在的设备的最佳解决方案

通过网络存储的缺点是需要服务器客户端架构。因此,对于 NFS,开发机器上需要提供 NFS 服务器功能。对于 Ubuntu 主机,所需的配置涉及安装nfs-kernel-server软件包,sudo apt-get install nfs-kernel-server。安装软件包后,需要指定和配置导出目录位置。这是通过/etc/exports文件完成的;在这里,类似于/nfs/rootfs <client-IP-address> (rw,no_root_squash,no_subtree_check)的配置行出现,其中每行定义了 NFS 客户端的网络共享位置。配置完成后,需要以以下方式重新启动 NFS 服务器:sudo /etc/init.d/nfs-kernel-server restart

对于目标上可用的客户端端,需要相应配置 Linux 内核,以确保启用 NFS 支持,并且在启动时 IP 地址可用。这些配置是CONFIG_NFS_FS=yCONFIG_IP_PNP=yCONFIG_ROOT_NFS=y。内核还需要配置root=/dev/nfs参数,目标的 IP 地址和 NFS 服务器nfsroot=192.168.1.110:/nfs/rootfs信息。以下是两个组件之间通信的示例:

与根文件系统交互

还有一种可能性,即将根文件系统集成到内核映像中,即最小根文件系统,其目的是启动完整功能的根文件系统。这个根文件系统称为initramfs。这种类型的文件系统对于对快速启动选项感兴趣的人非常有帮助,因为它只包含一些有用的功能,并且需要在更早的时候启动。它对于在启动时快速加载系统非常有用,但也可以作为启动实际根文件系统之前的中间步骤。根文件系统在内核引导过程之后首先启动,因此它应该与 Linux 内核一起可用,因为它驻留在 RAM 内存上的内核附近。以下图片解释了这一点:

与根文件系统交互

要创建initramfs,需要提供配置。这是通过定义根文件系统目录的路径、cpio存档的路径,甚至是描述initramfs内容的文本文件来完成的,这些都在CONFIG_INITRAMFS_SOURCE中。当内核构建开始时,将读取CONFIG_INITRAMFS_SOURCE的内容,并将根文件系统集成到内核映像中。

注意

有关initramfs文件系统选项的更多信息可以在内核文档文件Documentation/filesystems/ramfs-rootfs-initramfs.txtDocumentation/early-userspace/README中找到。

初始 RAM 磁盘或initrd是另一种挂载早期根文件系统的机制。它还需要在 Linux 内核中启用支持,并作为内核的组件加载。它包含一小组可执行文件和目录,并代表了完整功能的根文件系统的临时阶段。它只代表了对于没有能够容纳更大根文件系统的存储设备的嵌入式设备的最终阶段。

在传统系统上,使用mkinitrd工具创建initrd,实际上是一个自动化创建initrd所需步骤的 shell 脚本。以下是其功能的示例:

#!/bin/bash

# Housekeeping...
rm -f /tmp/ramdisk.img
rm -f /tmp/ramdisk.img.gz

# Ramdisk Constants
RDSIZE=4000
BLKSIZE=1024

# Create an empty ramdisk image
dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE

# Make it an ext2 mountable file system
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE

# Mount it so that we can populate
mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop=/dev/loop0

# Populate the filesystem (subdirectories)
mkdir /mnt/initrd/bin
mkdir /mnt/initrd/sys
mkdir /mnt/initrd/dev
mkdir /mnt/initrd/proc

# Grab busybox and create the symbolic links
pushd /mnt/initrd/bin
cp /usr/local/src/busybox-1.1.1/busybox .
ln -s busybox ash
ln -s busybox mount
ln -s busybox echo
ln -s busybox ls
ln -s busybox cat
ln -s busybox ps
ln -s busybox dmesg
ln -s busybox sysctl
popd

# Grab the necessary dev files
cp -a /dev/console /mnt/initrd/dev
cp -a /dev/ramdisk /mnt/initrd/dev
cp -a /dev/ram0 /mnt/initrd/dev
cp -a /dev/null /mnt/initrd/dev
cp -a /dev/tty1 /mnt/initrd/dev
cp -a /dev/tty2 /mnt/initrd/dev

# Equate sbin with bin
pushd /mnt/initrd
ln -s bin sbin
popd

# Create the init file
cat >> /mnt/initrd/linuxrc << EOF
#!/bin/ash
echo
echo "Simple initrd is active"
echo
mount -t proc /proc /proc
mount -t sysfs none /sys
/bin/ash --login
EOF

chmod +x /mnt/initrd/linuxrc

# Finish up...
umount /mnt/initrd
gzip -9 /tmp/ramdisk.img
cp /tmp/ramdisk.img.gz /boot/ramdisk.img.gz

注意

有关initrd的更多信息可以在Documentation/initrd.txt中找到。

使用initrd不像initramfs那样简单。在这种情况下,需要以类似于用于内核映像的方式复制一个存档,并且引导加载程序需要将其位置和大小传递给内核,以确保它已经启动。因此,在这种情况下,引导加载程序还需要支持initrdinitrd的中心点由linuxrc文件构成,这是第一个启动的脚本,通常用于提供对系统引导的最后阶段的访问,即真正的根文件系统。在linuxrc完成执行后,内核会卸载它并继续执行真正的根文件系统。

深入文件系统

无论它们的来源是什么,大多数可用的根文件系统都具有相同的目录组织,由文件系统层次结构FHS)定义,通常被称为。这种组织对开发人员和用户都非常有帮助,因为它不仅提到了目录层次结构,还提到了目录的目的和内容。最显著的是:

  • /bin:这是大多数程序的位置

  • /sbin:这是系统程序的位置

  • /boot:这是引导选项的位置,例如内核映像内核配置initrd系统映射和其他信息

  • /home:这是用户主目录

  • /root:这是根用户的主目录位置

  • /usr:这是用户特定的程序和库的位置,并模仿了根文件系统的部分内容

  • /lib:这是库的位置

  • /etc:这是系统范围的配置

  • /dev:这是设备文件的位置

  • /media:这是可移动设备的挂载点的位置

  • /mnt:这是静态媒体的挂载位置点

  • /proc:这是proc虚拟文件系统的挂载点

  • /sys:这是sysfs虚拟文件系统的挂载点

  • /tmp:这是临时文件的位置

  • /var:这是数据文件的位置,例如日志数据、管理信息或瞬态数据的位置

FHS 随时间而变化,但变化不大。大多数先前提到的目录出于各种原因保持不变-最简单的原因是它们需要确保向后兼容性。

注意

FHS 的最新信息可在refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.pdf上找到。

根文件系统由内核启动,这是内核在结束引导阶段之前执行的最后一步。以下是执行此操作的确切代码:

/*
  * We try each of these until one succeeds.
  *
  * The Bourne shell can be used instead of init if we are
  * trying to recover a really broken machine.
  */
  if (execute_command) {
    ret = run_init_process(execute_command);
    if (!ret)
      return 0;
    pr_err("Failed to execute %s (error %d).  Attempting defaults...\n",execute_command, ret);
  }
  if (!try_to_run_init_process("/sbin/init") ||
    !try_to_run_init_process("/etc/init") ||
    !try_to_run_init_process("/bin/init") ||
    !try_to_run_init_process("/bin/sh"))
      return 0;

  panic("No working init found.  Try passing init= option to kernel." "See Linux Documentation/init.txt for guidance.");

在此代码中,可以轻松地识别出用于搜索需要在退出 Linux 内核引导执行之前启动的init进程的多个位置。run_init_process()函数是execve()函数的包装器,如果在调用过程中未遇到错误,则不会返回值。被调用的程序覆盖了执行进程的内存空间,替换了调用线程并继承了它的PID

这个初始化阶段是如此古老,以至于 Linux 1.0 版本中也有类似的结构。这代表了用户空间处理的开始。如果内核无法在预定义的位置执行前述四个函数中的一个,则内核将停止,并且会在控制台上提示恐慌消息,以发出无法启动任何 init 进程的警报。因此,在内核空间处理完成之前,用户空间处理将不会开始。

对于大多数可用的 Linux 系统,/sbin/init是内核生成 init 进程的位置;对于 Yocto 项目生成的根文件系统,同样也是如此。它是用户空间中运行的第一个应用程序,但它并不是根文件系统的唯一必要特性。在运行根文件系统中的任何进程之前,需要解决一些依赖关系。有一些用于解决动态链接依赖引用的依赖关系,这些引用之前未解决,还有一些需要外部配置的依赖关系。对于第一类依赖关系,可以使用ldd工具来查找动态链接依赖关系,但对于第二类依赖关系,没有通用解决方案。例如,对于init进程,配置文件是inittab,它位于/etc目录中。

对于不希望运行另一个init进程的开发人员,可以使用内核命令行中的init=参数来访问此选项,其中应提供要执行的二进制文件的路径。这些信息也在前面的代码中提供。定制init进程并不是开发人员常用的方法,但这是因为init进程非常灵活,可以提供多个启动脚本。

init之后启动的每个进程都使用父子关系,其中init充当用户空间中所有进程的父进程,并且还提供环境参数。最初,init 进程根据/etc/inittab配置文件中的信息生成进程,该文件定义了运行级别的概念。运行级别表示系统的状态,并定义了已启动的程序和服务。有八个可用的运行级别,编号从06,还有一个特殊的S。它们的目的在这里描述:

运行级别值 运行级别目的
0 它指的是整个系统的关闭和关机命令
1 它是带有标准登录访问的单用户管理模式
2 它是没有 TCP/IP 连接的多用户模式
3 它指的是通用多用户
4 它由系统所有者定义
5 它指的是图形界面和 TCP/IP 连接的多用户系统
6 它指的是系统重启
s 它是提供对最小根 shell 的单用户模式访问

每个运行级别启动和终止一些服务。启动的服务以S开头,终止的服务以K开头。每个服务实际上是一个 shell 脚本,定义了它所提供的行为。

/etc/inittab配置脚本定义了运行级别和应用于所有运行级别的指令。对于 Yocto 项目,/etc/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
S0:12345:respawn:/sbin/getty 115200 ttyS0
# /sbin/getty invocations for the runlevels.
#
# The "id" field MUST be the same as the last
# characters of the device (after "tty").
#
# Format:
#  <id>:<runlevels>:<action>:<process>
#

1:2345:respawn:/sbin/getty 38400 tty1

init解析前面的inittab文件时,首先执行的是通过sysinit标签标识的si::sysinit:/etc/init.d/rcS行。然后,进入runlevel 5,并继续处理指令,直到最后一个级别,最终使用/sbin/getty symlink生成一个 shell。可以在控制台中运行man initman inittab来获取有关initinittab的更多信息。

任何 Linux 系统的最后阶段都由关机或关闭命令表示。这非常重要,因为如果不适当地执行,可能会通过损坏数据来影响系统。当然,有多种选项可以实现关闭方案,但最方便的形式仍然是使用诸如shutdownhaltreboot之类的实用程序。还可以使用init 0来关闭系统,但实际上,它们都共同使用SIGTERMSIGKILL信号。SIGTERM最初用于通知您关于关闭系统的决定,以便系统执行必要的操作。完成后,发送SIGKILL信号以终止所有进程。

设备驱动程序

Linux 系统面临的最重要挑战之一是允许应用程序访问各种硬件设备。诸如虚拟内存、内核空间和用户空间之类的概念并没有简化事情,而是为这些信息增加了另一层复杂性。

设备驱动程序的唯一目的是将硬件设备和内核数据结构与用户空间应用程序隔离开来。用户不需要知道,要向硬盘写入数据,他或她将需要使用不同大小的扇区。用户只需打开一个文件进行写入,完成后关闭即可。设备驱动程序是执行所有底层工作的程序,比如隔离复杂性。

在用户空间中,所有设备驱动程序都有关联的设备节点,实际上是表示设备的特殊文件。所有设备文件都位于/dev目录中,并通过mknod实用程序与它们进行交互。设备节点在两个抽象层上可用:

  • 块设备:这些由固定大小的块组成,通常在与硬盘、SD 卡、USB 存储设备等交互时使用

  • 字符设备:这些是不具有大小、起始或结束的字符流;它们大多不是块设备的形式,比如终端、串行端口、声卡等

每个设备都有一个提供有关其信息的结构:

  • Type标识设备节点是字符设备还是块设备

  • Major标识设备的类别

  • Minor保存设备节点的标识符

创建设备节点的mknod实用程序使用三元组信息,例如mknod /dev/testdev c 234 0。执行命令后,将出现一个new /dev/testdev文件。它应该绑定到已安装并已定义其属性的设备驱动程序。如果发出open命令,内核将寻找与设备节点相同主要编号注册的设备驱动程序。次要编号用于处理多个设备或使用相同设备驱动程序的设备系列。它被传递给设备驱动程序以便使用。没有标准的使用次要编号的方法,但通常它定义了来自共享相同主要编号的设备系列中的特定设备。

使用mknod实用程序需要手动交互和 root 权限,并允许开发人员完成识别设备节点及其设备驱动程序对应的属性所需的所有繁重工作。最新的 Linux 系统提供了自动化此过程的可能性,并且还可以在每次检测到设备或设备消失时完成这些操作。具体操作如下:

  • devfs:这是一个作为文件系统设计的设备管理器,也可在内核空间和用户空间中访问。

  • devtmpfs:这是一个虚拟文件系统,自 2.6.32 内核版本发布以来就可用,是对用于启动时间优化的devfs的改进。它只为本地系统上可用的硬件创建设备节点。

  • udev:这是指在服务器和桌面 Linux 系统上使用的守护程序。有关此的更多信息可以通过访问www.kernel.org/pub/linux/utils/kernel/hotplug/udev/udev.html来参考。Yocto 项目也将其用作默认设备管理器。

  • mdev:这提供了比udev更简单的解决方案;实际上,它是udev的一个派生物。

由于系统对象也被表示为文件,这简化了应用程序与它们交互的方法。如果没有使用设备节点,这是不可能的,设备节点实际上是文件,可以对其应用正常的文件交互功能,如open()read()write()close()

文件系统选项

根文件系统可以以非常广泛的文件系统类型部署,并且每种文件系统都比其他文件系统更适合执行特定任务。如果某些文件系统针对性能进行了优化,那么其他文件系统则更擅长节省空间甚至恢复数据。这里将介绍一些最常用和有趣的文件系统。

物理设备的逻辑分区,如硬盘或 SD 卡,称为分区。物理设备可以有一个或多个分区,覆盖其可用存储空间。它可以被视为具有文件系统供用户使用的逻辑磁盘。在 Linux 中,使用fdisk实用程序来管理分区。它可以用于创建列出销毁和其他一般交互,有 100 多种分区类型。更准确地说,在我的 Ubuntu 14.04 开发机器上有 128 种分区类型可用。

最常用和知名的文件系统分区格式之一是ext2。也称为第二扩展文件系统,它是由法国软件开发人员 Rémy Card 于 1993 年引入的。它曾被用作许多 Linux 发行版的默认文件系统,如 Debian 和 Red Hat Linux,直到被其年轻的兄弟ext3ext4取代。它继续是许多嵌入式 Linux 发行版和闪存存储设备的选择。

ext2文件系统将数据分割为块,并将块排列成块组。每个块组维护超级块的副本和该块组的描述符表。超级块用于存储配置信息,并保存引导过程所需的信息,尽管有多个副本;通常,位于文件系统第一个块中的第一个副本是所使用的。通常将文件的所有数据保存在单个块中,以便可以更快地进行搜索。除了包含的数据外,每个块组还包含有关超级块、块组的描述符表、索引节点位图和表信息以及块位图的信息。超级块是保存引导过程所需信息的地方。它的第一个块用于引导过程。最后一个概念是inode,或索引节点,它通过其权限、大小、在磁盘上的位置和所有权来表示文件和目录。

有多个应用程序用于与ext2文件系统格式进行交互。其中之一是mke2fs,用于在mke2fs /deb/sdb1 –L分区(ext2标签分区)上创建ext2文件系统。还有e2fsck命令,用于验证文件系统的完整性。如果未发现错误,这些工具会为您提供有关分区文件系统配置的信息,e2fsck /dev/sdb1。此实用程序还能够修复设备不正确使用后出现的一些错误,但不能在所有情况下使用。

Ext3 是另一个强大而广为人知的文件系统。它取代了ext2,成为 Linux 发行版中最常用的文件系统之一。实际上,它与ext2类似;不同之处在于它具有日志记录信息的可能性。可以使用tune2fs –j /dev/sdb1命令将ext2文件格式更改为ext3文件格式。基本上被视为ext2文件系统格式的扩展,它添加了日志记录功能。这是因为它被设计为向前和向后兼容。

日志记录是一种方法,用于记录文件系统上所做的所有更改,从而实现恢复功能。除了已经提到的功能外,ext3还添加了其他功能;在这里,我指的是文件系统中不需要检查一致性的可能性,主要是因为日志记录可以被撤消。另一个重要功能是,它可以在不检查关机是否正确执行的情况下挂载。这是因为系统在关机时不需要进行一致性检查。

Ext4 是ext3的后继者,旨在改善ext3中的性能和存储限制。它还向后兼容ext3ext2文件系统,并添加了许多功能:

  • 持久性预分配:这定义了fallocate()系统调用,可用于预先分配空间,这在大多数情况下是连续的形式;对于数据库和媒体流非常有用

  • 延迟分配:这也称为在刷新时分配;它用于延迟分配块,从磁盘刷新数据的时刻开始,以减少碎片化并提高性能

  • 多块分配:这是延迟分配的副作用,因为它允许数据缓冲,同时分配多个块。

  • 增加子目录限制:ext3的子目录限制为 32000 个,而ext4没有此限制,即子目录的数量是无限的

  • 日志的校验和:这用于提高可靠性

日志闪存文件系统版本 2JFFS2)是为 NAND 和 NOR 闪存设计的文件系统。它于 2001 年被包含在 Linux 主线内核中,与ext3文件系统在同一年发布,尽管在不同的月份。它在 Linux 2.4.15 版本中于 11 月发布,而 JFFS2 文件系统在 2.4.10 内核版本中于 9 月发布。由于它特别用于支持闪存设备,因此考虑了某些因素,例如需要处理小文件以及这些设备具有与之相关的磨损水平,这通过其设计解决和减少。尽管 JFFS2 是闪存的标准,但也有一些替代方案,例如 LogFS、另一个闪存文件系统(YAFFS)和未排序块映像文件系统(UBIFS)。

除了前面提到的文件系统外,还有一些伪文件系统可用,包括procsysfstmpfs。在下一节中,将描述前两者,留下最后一个让您自己发现。

proc文件系统是 Linux 的第一个版本中提供的虚拟文件系统。它被定义为允许内核向用户提供有关正在运行的进程的信息,但随着时间的推移,它已经发展,现在不仅可以提供有关正在运行的进程的统计信息,还可以提供有关内存管理、进程、中断等各种参数的调整。

随着时间的推移,proc虚拟文件系统对于 Linux 系统用户来说变得必不可少,因为它汇集了大量的用户空间功能。命令,如toppsmount,没有它将无法工作。例如,给出没有参数的mount示例将以proc挂载在/proc上的形式呈现为proc on /proc type proc (rw,noexec,nosuid,nodev)。这是因为需要将proc挂载在root文件系统上,与目录/etc/home等一起使用作为/proc文件系统的目的地。要挂载proc文件系统,使用类似于其他可用文件系统的mount –t proc nodev/proc挂载命令。有关此更多信息可以在内核源文件的Documentation/filesystems/proc.txt中找到。

proc文件系统具有以下结构:

  • 对于每个运行的进程,/proc/<pid>内有一个可用的目录。它包含有关打开的文件、使用的内存、CPU 使用情况和其他特定于进程的信息。

  • 一般设备的信息位于/proc/devices/proc/interrupts/proc/ioports/proc/iomem内。

  • 内核命令行位于/proc/cmdline内。

  • 用于更改内核参数的文件位于/proc/sys内。有关更多信息,也可以在Documentation/sysctl中找到。

sysfs文件系统用于表示物理设备。自 2.6 版 Linux 内核引入以来,它提供了将物理设备表示为内核对象并将设备驱动程序与相应设备关联的可能性。对于工具,如udev和其他设备管理器,它非常有用。

sysfs目录结构为每个主要系统设备类都有一个子目录,还有一个系统总线子目录。还有systool可以用来浏览sysfs目录结构。与 proc 文件系统类似,如果在控制台上提供了sysfs on /sys type sysfs (rw,noexec,nosuid,nodev) mount命令,systool也可以可见。可以使用mount -t sysfs nodev /sys命令进行挂载。

注意

有关可用文件系统的更多信息,请访问en.wikipedia.org/wiki/List_of_file_systems

理解 BusyBox

BusyBox 由 Bruce Perens 于 1999 年开发,旨在将可用的 Linux 工具集成到一个单一的可执行文件中。它已被广泛成功地用作许多 Linux 命令行实用程序的替代品。由于这个原因,以及它能够适应小型嵌入式 Linux 发行版,它在嵌入式环境中获得了很多的流行。它提供了文件交互的实用工具,如cpmkdirtouchlscat,以及一般实用工具,如dmesgkillfdiskmountumount等。

它不仅非常容易配置和编译,而且非常易于使用。它非常模块化,并提供高度的配置,使其成为理想的选择。它可能不包括主机 PC 上可用的完整 Linux 发行版中的所有命令,但它包含的命令已经足够了。此外,这些命令只是完整命令的简化版本,用于实现级别,并且都集成在一个单一可执行文件中,作为/bin/busybox中的符号链接。

开发人员与 BusyBox 源代码包的交互非常简单:只需配置、编译和安装,就可以了。以下是一些详细的步骤来解释以下内容:

  • 运行配置工具并选择要提供的功能

  • 执行make dep来构建依赖树

  • 使用make命令构建软件包

提示

在目标上安装可执行文件和符号链接。对于希望在其工作站上与该工具进行交互的人来说,如果该工具已安装到主机系统,则安装应该在不覆盖主机可用的任何实用程序和启动脚本的位置进行。

BusyBox 包的配置还有一个menuconfig选项,类似于内核和 U-Boot 可用的make menuconfig。它用于显示一个文本菜单,可用于更快的配置和配置搜索。要使此菜单可用,首先需要在调用make menuconfig命令的系统上安装ncurses包。

在过程结束时,BusyBox 可执行文件可用。如果没有参数调用它,它将呈现一个与此类似的输出:

Usage: busybox [function] [arguments]...
 or: [function] [arguments]...

 BusyBox is a multi-call binary that combines many common Unix
 utilities into a single executable.  Most people will create a
 link to busybox for each function they wish to use and BusyBox
 will act like whatever it was invoked as!

Currently defined functions:
 [, [[, arping, ash, awk, basename, bunzip2, busybox, bzcat, cat,
 chgrp, chmod, chown, chroot, clear, cp, crond, crontab, cut, date,
 dd, df, dirname, dmesg, du, echo, egrep, env, expr, false, fgrep,
 find, free, grep, gunzip, gzip, halt, head, hexdump, hostid, hostname,
 id, ifconfig, init, insmod, ipcalc, ipkg, kill, killall, killall5,
 klogd, length, ln, lock, logger, logread, ls, lsmod, md5sum, mesg,
 mkdir, mkfifo, mktemp, more, mount, mv, nc, "netmsg", netstat,
 nslookup, passwd, pidof, ping, pivot_root, poweroff, printf, ps,
 pwd, rdate, reboot, reset, rm, rmdir, rmmod, route, sed, seq,
 sh, sleep, sort, strings, switch_root, sync, sysctl, syslogd,
 tail, tar, tee, telnet, test, time, top, touch, tr, traceroute,
 true, udhcpc, umount, uname, uniq, uptime, vi, wc, wget, which,
 xargs, yes, zcat

它呈现了在配置阶段启用的实用程序列表。调用上述实用程序之一有两种选项。第一种选项需要使用 BusyBox 二进制文件和调用的实用程序数量,表示为./busybox ls,而第二种选项涉及使用已经在目录中可用的符号链接,如/bin、/sbin、/usr/bin等。

除了已经可用的实用程序之外,BusyBox 还为init程序提供了实现替代方案。在这种情况下,init不知道运行级别,所有配置都在/etc/inittab文件中。另一个与标准/etc/inittab文件不同的因素是,它还具有自己的特殊语法。有关更多信息,可以查看 BusyBox 中的examples/inittab。BusyBox 包中还实现了其他工具和实用程序,例如vi的轻量级版本,但我会让你自己去发现它们。

最小root文件系统

现在,所有关于root文件系统的信息都已经呈现给你,描述最小root文件系统的必备组件将是一个很好的练习。这不仅有助于您更好地理解rootfs结构及其依赖关系,还有助于满足引导时间和root文件系统大小优化的要求。

描述组件的起点是/sbin/init;在这里,可以使用ldd命令找到运行时依赖关系。对于 Yocto 项目,ldd /sbin/init命令返回:

linux-gate.so.1 (0xb7785000)
libc.so.6 => /lib/libc.so.6 (0x4273b000)
/lib/ld-linux.so.2 (0x42716000)

根据这些信息,定义了/lib目录结构。它的最小形式是:

lib
|-- ld-2.3.2.so
|-- ld-linux.so.2 -> ld-2.3.2.so
|-- libc-2.3.2.so
'-- libc.so.6 -> libc-2.3.2.so

以下是确保库的向后兼容性和版本免疫性的符号链接。在上述代码中,linux-gate.so.1文件是一个虚拟动态链接共享对象vDSO),由内核在一个已建立的位置公开。它的地址因机器架构而异。

之后,必须定义init及其运行级别。这个最小形式在 BusyBox 包中可用,因此也将在/bin目录中可用。除此之外,还需要一个用于 shell 交互的符号链接,因此/bin目录的最小形式如下:

bin
|-- busybox
'-- sh -> busybox

接下来,需要定义运行级别。在最小的root文件系统中只使用一个,不是因为这是严格要求,而是因为它可以抑制一些 BusyBox 警告。这是/etc目录的样子:

etc
'-- init.d
 '-- rcS

最后,控制台设备需要对用户进行输入和输出操作,因此root文件系统的最后一部分位于/dev目录中:

dev
'-- console

提到了所有这些,最小的root文件系统似乎只有五个目录和八个文件。其最小尺寸低于 2 MB,大约 80%的尺寸归功于 C 库软件包。还可以通过使用 Library Optimizer Tool 来最小化其大小。您可以在libraryopt.sourceforge.net/找到更多信息。

Yocto 项目

转到 Yocto 项目,我们可以查看 core-image-minimal 以确定其内容和最低要求,如 Yocto 项目中所定义的。core-image-minimal.bb镜像位于meta/recipes-core/images目录中,看起来是这样的:

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

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

IMAGE_LINGUAS = " "

LICENSE = "MIT"

inherit core-image

IMAGE_ROOTFS_SIZE ?= "8192"

您可以在这里看到这与任何其他配方都是相似的。该镜像定义了LICENSE字段,并继承了一个bbclass文件,该文件定义了其任务。使用简短的摘要来描述它,它与普通软件包配方非常不同。它没有LIC_FILES_CHKSUM来检查许可证或SRC_URI字段,主要是因为它不需要它们。作为回报,该文件定义了应包含在root文件系统中的确切软件包,并且其中一些软件包被分组在packagegroup中以便更容易处理。此外,core-image bbclass文件定义了许多其他任务,例如do_rootfs,这仅适用于镜像配方。

构建root文件系统对任何人来说都不是一件容易的事情,但 Yocto 做得更成功一些。它从 base-files 配方开始,用于根据文件系统层次结构标准FHS)布置目录结构,并且还有一些其他配方。这些信息可在./meta/recipes-core/packagegroups/packagegroup-core-boot.bb配方中找到。正如在先前的例子中所看到的,它还继承了不同类型的类,比如packagegroup.bbclass,这是所有可用的包组的要求。然而,最重要的因素是它清楚地定义了构成packagegroup的软件包。在我们的情况下,核心引导包组包含软件包,如base-filesbase-passwd(其中包含基本系统主密码和组文件),udevbusyboxsysvinit(类似于 System V 的 init)。

正如在先前显示的文件中所看到的,BusyBox 软件包是 Yocto 项目生成的发行版的核心组件。虽然有关 BusyBox 可以提供 init 替代方案的信息是可用的,但默认的 Yocto 生成的发行版并不使用这个功能。相反,它们选择转向类似于 Debian 发行版可用的 System V-like init。然而,通过meta/recipes-core/busybox位置内可用的 BusyBox 配方提供了一些 shell 交互工具。对于有兴趣增强或删除busybox软件包提供的一些功能的用户,可以使用与 Linux 内核配置相同的概念。busybox软件包使用defconfig文件,然后应用一些配置片段。这些片段可以添加或删除功能,最终得到最终的配置文件。这标识了root文件系统中可用的最终功能。

在 Yocto 项目中,可以通过使用poky-tiny.conf发行政策来最小化root文件系统的大小,这些政策可以在meta-yocto/conf/distro目录中找到。当使用这些政策时,不仅可以减小启动大小,还可以减小启动时间。最简单的示例是使用qemux86机器。在这里,变化是可见的,但与“最小根文件系统”部分中已经提到的有些不同。在qemux86上进行的最小化工作是围绕core-image-minimal镜像进行的。其目标是将结果rootfs的大小减小到 4MB 以下,启动时间减小到 2 秒以下。

现在,转向选定的 Atmel SAMA5D3 Xplained 机器,另一个rootfs被生成,其内容相当庞大。它不仅包括了packagegroup-core-boot.bb软件包组,还包括其他软件包组和单独的软件包。其中一个例子是在meta-atmel层的recipes-core/images目录中可用的atmel-xplained-demo-image.bb镜像:

DESCRIPTION = "An image for network and communication."
LICENSE = "MIT"
PR = "r1"

require atmel-demo-image.inc

IMAGE_INSTALL += "\
    packagegroup-base-3g \
    packagegroup-base-usbhost \
    "

在这个镜像中,还有另一个更通用的镜像定义被继承。我指的是atmel-demo-image.inc文件,打开后可以看到它包含了所有meta-atmel层镜像的核心。当然,如果所有可用的软件包都不够,开发人员可以决定添加自己的软件包。开发人员面临两种可能性:创建一个新的镜像,或者向已有的镜像添加软件包。最终结果是使用bitbake atmel-xplained-demo-image命令构建的。输出以各种形式可用,并且高度依赖于所定义的机器的要求。在构建过程结束时,输出将用于在实际板上引导根文件系统。

摘要

在本章中,您已经了解了 Linux rootfs的一般情况,以及与 Linux 内核、Linux rootfs的组织、原则、内容和设备驱动程序的通信。由于通信随着时间的推移而变得更加庞大,关于最小文件系统应该如何看待的信息也被呈现给您。

除了这些信息,下一章将为您概述 Yocto 项目的可用组件,因为它们大多数都在 Poky 之外。您还将被介绍并简要介绍每个组件。在本章之后,将向您介绍并详细阐述其中的一些组件。

第六章:Yocto 项目的组件

在本章中,您将简要介绍 Yocto 项目生态系统中的一些组件。本章的目的是介绍它们,以便在后续章节中更详细地介绍它们。它还试图引导读者进行额外阅读。对于每个工具、功能或有趣的事实,都提供了链接,以帮助感兴趣的读者寻找本书中的问题以及本章未涵盖的问题的答案。

本章充满了有关嵌入式开发过程的指导和相关示例,涉及特定的 Yocto 项目工具。工具的选择是纯主观的。只选择了在开发过程中被认为有帮助的工具。我们还考虑到其中一些工具可能会为嵌入式世界和嵌入式系统的开发提供新的见解。

Poky

Poky 代表了 Yocto 项目的元数据和工具的参考构建系统,这些工具是任何对与 Yocto 项目进行交互感兴趣的人的起点。它是独立于平台的,并提供了构建和定制最终结果的工具和机制,实际上是一个 Linux 软件堆栈。Poky 被用作与 Yocto 项目进行交互的中心组件。

作为开发人员使用 Yocto 项目时,了解邮件列表和Internet Relay Chat (IRC)频道的信息非常重要。此外,项目 Bugzilla 也可以作为可用 bug 和功能列表的灵感来源。所有这些元素都需要一个简短的介绍,因此最好的起点是 Yocto 项目 Bugzilla。它代表了 Yocto 项目用户的 bug 跟踪应用程序,并且是问题报告的地方。下一个组件是 IRC 的可用频道。在 freenode 上有两个可用的组件,一个用于 Poky,另一个用于与 Yocto 项目相关的讨论,如#poky#yocto。第三个元素是 Yocto 项目邮件列表,用于订阅 Yocto 项目的邮件列表:

通过lists.yoctoproject.org/listinfo,可以获取有关一般和项目特定邮件列表的更多信息。它包含了www.yoctoproject.org/tools-resources/community/mailing-lists上所有可用邮件列表的列表。

要开始使用 Yocto 项目,特别是 Poky,不仅应使用先前提到的组件;还应提供有关这些工具的信息。有关 Yocto 项目的非常好的解释可以在他们的文档页面上找到www.yoctoproject.org/documentation。对于那些对阅读更简短介绍感兴趣的人,可以查看Packt Publishing出版的Embedded Linux Development with Yocto Project,作者是Otavio SalvadorDaiane Angolini

要使用 Yocto 项目,需要满足一些特定的要求:

  • 主机系统:假设这是一个基于 Linux 的主机系统。但这不仅仅是任何主机系统;Yocto 有特定的要求。支持的操作系统在poky.conf文件中可用,该文件位于meta-yocto/conf/distro目录中。支持的操作系统在SANITY_TESTED_DISTROS变量中定义,其中一些系统如下:

  • Ubuntu-12.04

  • Ubuntu-13.10

  • Ubuntu-14.04

  • Fedora-19

  • Fedora-20

  • CentOS-6.4

  • CentOS-6.5

  • Debian-7.0

  • Debian-7.1

  • Debian-7.2

  • Debian-7.3

  • Debian-7.4

  • Debian-7.5

  • Debian-7.6

  • SUSE-LINUX-12.2

  • openSUSE-project-12.3

  • openSUSE-project-13.1

  • 所需软件包:这包含主机系统上可用的软件包的最低要求列表,除了已有的软件包。当然,这与一个主机系统到另一个主机系统是不同的,系统根据其目的而有所不同。但是,对于 Ubuntu 主机,我们需要以下要求:

  • 基本要求:这指的是sudo apt-get install gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath socat

  • 图形和 Eclipse 插件额外组件:这指的是sudo apt-get install libsdl1.2-dev xterm

  • 文档:这指的是sudo apt-get install make xsltproc docbook-utils fop dblatex xmlto

  • ADT 安装程序额外组件:这指的是sudo apt-get install autoconf automake libtool libglib2.0-dev

  • Yocto 项目发布:在开始任何工作之前,应选择一个可用的 Poky 版本。本书基于 dizzy 分支,即 Poky 1.7 版本,但开发人员可以选择最适合自己的版本。当然,由于与项目的交互是使用git版本控制系统完成的,用户首先需要克隆 Poky 存储库,并且对项目的任何贡献都应提交为开源社区的补丁。还有可能获取一个 tar 存档,但由于源代码上的任何更改更难追踪,并且还限制了与项目相关社区的交互,因此这种方法存在一些限制。

如果需要特殊要求,还有其他额外的可选要求需要注意,如下所示:

  • 自定义 Yocto 项目内核交互:如果开发人员决定 Yocto 项目维护的内核源不适合他们的需求,他们可以获取 Yocto 项目支持的内核版本的本地副本之一,该副本可在Yocto Linux Kernel下找到,并根据自己的需求进行修改。当然,这些更改以及其余的内核源都需要驻留在一个单独的存储库中,最好是git,并且将通过内核配方引入 Yocto 世界。

  • meta-yocto-kernel-extras git 存储库:在构建和修改内核映像时,此处收集所需的元数据。它包含一堆bbappend文件,可以编辑以指示本地源代码已更改,这是在开发 Linux 内核功能时更有效的方法。它在Yocto Metadata LayersYocto Metadata Layers部分提供。

  • 支持的板支持包(BSPs):有许多 BSP 层可供 Yocto Project 支持。每个 BSP 层的命名非常简单,meta-<bsp-name>,可以在git.yoctoproject.org/cgit.cgiYocto Metadata Layers部分找到。实际上,每个 BSP 层都是一组定义 BSP 提供者行为和最低要求的配方集合。有关 BSP 开发的更多信息可以在www.yoctoproject.org/docs/1.7/dev-manual/dev-manual.html#developing-a-board-support-package-bsp找到。

  • Eclipse Yocto 插件:对于有兴趣编写应用程序的开发人员,Yocto 专用插件的 Eclipse集成开发环境IDE)可用。您可以在www.yoctoproject.org/docs/1.7/dev-manual/dev-manual.html#setting-up-the-eclipse-ide找到更多信息。

Yocto Project 内的开发过程有许多含义。它可以指的是 Yocto Project Bugzilla 中可用的各种错误和功能。开发人员可以将其中之一分配给自己的帐户并解决它。各种配方可以升级,这也需要开发人员的参与;还可以添加新功能,并且需要开发人员编写各种配方。所有这些任务都需要有一个明确定义的流程,其中也涉及git的交互。

要将配方中添加的更改发送回社区,可以使用可用的 create-pull-request 和 send-pull request 脚本。这些脚本位于 poky 存储库的 scripts 目录中。此外,在本节中还有一些其他有趣的脚本可用,如create-recipe脚本等,我会让你自己去发现。将更改发送到上游的另一种首选方法是使用手动方法,其中涉及与git命令的交互,如git addgit commit –sgit format-patchgit send-email等。

在继续描述本章节中呈现的其他组件之前,将对现有的 Yocto Project 开发模型进行审查。这个过程涉及 Yocto Project 提供的这些工具:

  • 系统开发:这涵盖了 BSP 的开发、内核开发及其配置。Yocto Project 文档中有关于各自开发过程的部分,如www.yoctoproject.org/docs/1.7/bsp-guide/bsp-guide.html#creating-a-new-bsp-layer-using-the-yocto-bsp-scriptwww.yoctoproject.org/docs/1.7/kernel-dev/kernel-dev.html

  • 用户应用程序开发:这涵盖了针对目标硬件设备开发应用程序。有关在主机系统上进行应用程序开发所需设置的信息可在www.yoctoproject.org/docs/1.7/adt-manual/adt-manual.html找到。本章节还将讨论Eclipse ADT 插件部分。

  • 临时修改源代码:这涵盖了开发过程中出现的临时修改。这涉及解决项目源代码中可用的各种实现问题的解决方案。问题解决后,更改需要上游可用并相应应用。

  • Hob 镜像的开发:Hob 构建系统可用于操作和定制系统镜像。它是一个用 Python 开发的图形界面,作为与 Bitbake 构建系统更高效的接口。

  • Devshell 开发:这是一种使用 Bitbake 构建系统任务的确切环境进行开发的方法。这是用于调试或包编辑的最有效方法之一。在编写项目的各个组件时,这也是设置构建环境的最快方法之一。

对于提供的组件过时无法满足 Yocto 项目要求的操作系统,建议使用buildtools工具链来提供所需版本的软件。用于安装buildtools tarball 的方法有两种。第一种方法涉及使用已经可用的预构建 tarball,第二种方法涉及使用 Bitbake 构建系统进行构建。有关此选项的更多信息可以在 Yocto 文档超级手册的Required Git, tar, and Python Versions部分的子部分中找到,网址为www.yoctoproject.org/docs/1.7/mega-manual/mega-manual.html#required-git-tar-and-python-versions

Eclipse ADT 插件

应用程序开发工具包,也称为 ADT,提供了一个适用于自定义构建和用户定制应用程序的交叉开发平台。它由以下元素组成:

  • 交叉工具链:它与sysroot相关联,两者都是使用 Bitbake 自动生成的,并且目标特定的元数据由目标硬件供应商提供。

  • 快速仿真器环境(Qemu):用于模拟目标硬件。

  • 用户空间工具:它改善了应用程序开发的整体体验

  • Eclipse IDE:它包含 Yocto 项目特定的插件

在本节中,将讨论前述每个元素,我们将从交叉开发工具链开始。它由用于目标应用程序开发的交叉链接器、交叉调试器和交叉编译器组成。它还需要相关的目标sysroot,因为在构建将在目标设备上运行的应用程序时需要必要的头文件和库。生成的sysroot是从生成root文件系统的相同配置中获得的;这指的是image配方。

工具链可以使用多种方法生成。最常见的方法是从downloads.yoctoproject.org/releases/yocto/yocto-1.7/toolchain/下载工具链,并获取适合您的主机和目标的适当工具链安装程序。一个例子是poky-glibc-x86_64-core-image-sato-armv7a-vfp-neon-toolchain-1.7.sh脚本,当执行时将在默认位置/opt/poky/1.7/目录中安装工具链。如果在执行脚本之前提供适当的参数,则可以更改此位置。

当生成工具链时,我更喜欢使用 Bitbake 构建系统。在这里,我指的是meta-ide-support。运行bitbake meta-ide-support时,会生成交叉工具链并填充构建目录。完成此任务后,将获得与先前提到的解决方案相同的结果,但在这种情况下,将使用已经可用的构建目录。对于这两种解决方案,唯一剩下的任务是使用包含environment-setup字符串的脚本设置环境并开始使用它。

Qemu 仿真器提供了在目标设备不可用时模拟一个硬件设备的可能性。在开发过程中,有多种方法可以使其可用:

  • 使用 adt-installer 生成的脚本安装 ADT。在这个脚本中的一个可用步骤提供了在开发过程中启用或禁用 Qemu 的可能性。

  • Yocto 项目发布版被下载并在开发过程中默认设置环境。然后,Qemu 被安装并可供使用。

  • 创建 Poky 存储库的git克隆并设置环境。在这种情况下,Qemu 也被安装并可供使用。

  • cross-toolchain tarball 被下载、安装并设置环境。这也默认启用了 Qemu 并安装了它以供以后使用。

用户空间工具包含在发行版中,并在开发过程中使用。它们在 Linux 平台上非常常见,可以包括以下内容:

  • Perf:它是一个 Linux 性能计数器,用于测量特定的硬件和软件事件。有关它的更多信息可在perf.wiki.kernel.org/找到,也可以在 Yocto 的性能和跟踪手册中找到一个专门的章节。

  • PowerTop:这是一个用于确定软件消耗的功率量的功率测量工具。有关它的更多信息可在01.org/powertop/找到。

  • LatencyTop:这是一个类似于 PowerTop 的工具,不同之处在于它专注于从桌面音频跳跃和卡顿到服务器超载的延迟测量;它对这些情景进行测量并提供了延迟问题的解决方案。尽管自 2009 年以来似乎没有在这个项目中进行过提交,但由于它非常有用,至今仍在使用。

  • OProfile:它代表 Linux 生态系统的系统范围分析器,开销很低。有关它的更多信息可在oprofile.sourceforge.net/about/找到。在 Yocto 的性能和跟踪手册中也有一个章节可供参考。

  • SystemTap:它提供了关于运行中的 Linux 系统基础设施以及系统性能和功能问题的信息。但它并不作为 Eclipse 扩展,而是作为 Linux 发行版中的一个工具。关于它的更多信息可以在sourceware.org/systemtap找到。在 Yocto 的性能和跟踪手册中也有一个章节定义了它。

  • Lttng-ust:它是lttng项目的用户空间跟踪器,提供与用户空间活动相关的信息。更多信息可在lttng.org/找到。

ADT 平台的最后一个元素是 Eclipse IDE。实际上,它是最受欢迎的开发环境,并为 Yocto 项目的开发提供全面支持。通过将 Yocto 项目 Eclipse 插件安装到 Eclipse IDE 中,Yocto 项目的体验就完整了。这些插件提供了跨编译、开发、部署和在 Qemu 模拟环境中执行生成的二进制文件的可能性。还可以进行诸如交叉调试、跟踪、远程性能分析和功耗数据收集等活动。有关与使用 Yocto 项目的 Eclipse 插件相关的活动的更多信息,请参阅www.yoctoproject.org/docs/1.7/mega-manual/mega-manual.html#adt-eclipse

为了更好地理解 ADT 工具包平台和 Eclipse 应用开发的工作流程,整个过程的概述在下图中可见:

Eclipse ADT 插件

应用程序开发过程也可以使用与已经介绍的不同的其他工具。然而,所有这些选项都涉及使用 Yocto 项目组件,尤其是 Poby 参考系统。因此,ADT 是开源社区建议、测试和推荐的选项。

Hob 和 Toaster

项目—Hob—代表了 Bitbake 构建系统的图形用户界面。它的目的是简化与 Yocto 项目的交互,并为项目创建一个更简单的学习曲线,使用户能够以更简单的方式执行日常任务。它的主要重点是生成 Linux 操作系统镜像。随着时间的推移,它发展起来,现在可以被认为是一个适合有经验和无经验用户的工具。尽管我更喜欢使用命令行交互,但这个说法并不适用于所有 Yocto 项目的用户。

尽管在 Daisy 1.6 发布后 Hob 开发似乎停止了。开发活动在某种程度上转移到了新项目—Toaster—,这将很快解释;Hob 项目仍然在使用中,其功能应该被提及。因此,当前可用的 Hob 版本能够做到以下几点:

  • 自定义可用的基础镜像配方

  • 创建完全定制的镜像

  • 构建任何给定的镜像

  • 使用 Qemu 运行镜像

  • 在 USB 磁盘上部署镜像,以便在目标上进行现场引导

Hob 项目可以以与执行 Bitbake 相同的方式启动。在环境源和构建目录创建后,可以调用hob命令,用户将看到图形界面。这个工具的缺点是它不能替代命令行交互。如果需要创建新的配方,那么这个工具将无法提供任何帮助。

下一个项目叫做 Toaster。它是一个应用程序编程接口,也是 Yocto 项目构建的一个 Web 界面。在当前状态下,它只能通过 Web 浏览器收集和呈现与构建过程相关的信息。以下是它的一些功能:

  • 在构建过程中执行和重用任务的可见性

  • 构建组件的可见性,如镜像的配方和软件包 - 这与 Hob 类似地完成

  • 提供有关配方的信息,如依赖关系、许可证等

  • 提供与性能相关的信息,如磁盘 I/O、CPU 使用率等

  • 为了调试目的呈现错误、警告和跟踪报告

尽管看起来可能不多,这个项目承诺提供与 Hob 相同的构建和定制构建的可能性,以及许多其他好处。您可以在这个工具的wiki.yoctoproject.org/wiki/Toaster上找到有用的信息。

自动构建器

自动构建器是一个项目,它促进了构建测试自动化并进行质量保证。通过这个内部项目,Yocto 社区试图为嵌入式开发人员设定一条路径,使他们能够发布他们的 QA 测试和测试计划,开发新的自动测试工具、持续集成,并开发 QA 程序以展示和展示给所有相关方的利益。

这些点已经被一个使用 Autobuilder 平台发布其当前状态的项目所实现,该平台可在autobuilder.yoctoproject.org/上找到。这个链接对每个人都是可访问的,测试是针对与 Yocto 项目相关的所有更改进行的,以及所有支持的硬件平台的夜间构建。尽管起源于 Buildbot 项目,从中借用了持续集成的组件,这个项目承诺将继续前进,并提供执行运行时测试和其他必不可少的功能的可能性。

您可以在以下网址找到有关该项目的一些有用信息:wiki.yoctoproject.org/wiki/AutoBuilderwiki.yoctoproject.org/wiki/QA,该网址提供了每个发布版本的 QA 程序的访问权限,以及一些额外的信息。

Lava

Lava 项目并不是 Yocto 项目的内部工作,而是由 Linaro 开发的项目,旨在测试设备上 Linux 系统的部署的自动化验证架构。尽管其主要关注点是 ARM 架构,但它是开源的,这并不是一个缺点。它的实际名称是Linaro 自动化和验证架构LAVA)。

该项目提供了在硬件或虚拟平台上部署操作系统的可能性,定义测试,并在项目上执行测试。测试可以具有各种复杂性,它们可以组合成更大更具有决定性的测试,并且结果会随时间跟踪,之后导出结果数据进行分析。

这是一个不断发展的架构,允许测试执行以及自动化和质量控制。同时,它还为收集的数据提供验证。测试可以是从编译引导测试到对内核调度器的更改,可能会或可能不会降低功耗。

尽管它还很年轻,但这个项目已经吸引了相当多的关注,因此对该项目进行一些调查不会伤害任何人。

注意

LAVA 手册可在validation.linaro.org/static/docs/找到。

Wic

Wic更像是一个功能而不是一个项目本身。它是最不被记录的,如果搜索它,你可能找不到结果。我决定在这里提到它,因为在开发过程中可能会出现一些特殊要求,比如从可用软件包(如.deb.rpm.ipk)生成自定义的root文件系统。这项工作最适合 wic 工具。

这个工具试图解决设备或引导加载程序的一些特殊要求,比如特殊格式化或root文件系统的分区。它是一个高度定制的工具,可以扩展其功能。它是从另一个名为oeic的工具开发而来,该工具用于为硬件创建特定的专有格式化映像,并被导入到 Yocto 项目中,以为那些不想要触及配方或已经打包好的源代码的开发人员提供更广泛的目的,或者需要为其可交付的 Linux 映像进行特殊格式化。

不幸的是,这个工具没有提供文档,但我可以指导感兴趣的人到 Yocto 项目的位置。它位于 Poky 存储库中的 scripts 目录下的 wic 名称。Wic 可以像任何脚本一样使用,并提供一个帮助界面,您可以在那里寻找更多信息。此外,它的功能将在接下来的章节中进行更详细的介绍。

可以在www.yoctoproject.org/tools-resources/projects找到所有围绕 Yocto 项目开发的可用项目的列表。其中一些项目在本章的上下文中没有讨论,但我会让你自己去发现它们。还有其他未列入列表的外部项目。我鼓励你自己去了解和学习它们。

总结

在这一章中,你将看到下一章中将要讨论的元素。在接下来的章节中,之前提到的每个部分将在不同的章节中进行详细和更加应用的介绍。

在下一章中,前面提到的过程将从应用开发工具包平台开始。将解释设置平台所需的步骤,并向您介绍一些使用场景。这些涉及跨开发、使用 Qemu 进行调试以及特定工具之间的交互。

第七章:ADT Eclipse 插件

在本章中,您将看到 Yocto 项目中可用工具的新视角。本章标志着对 Yocto 项目生态系统中各种工具的介绍的开始,这些工具非常有用,并且与 Poky 参考系统不同。在本章中,将简要介绍应用开发环境ADE)并强调 Eclipse 项目和 Yocto 项目的附加插件。展示了一些插件以及它们的配置和用例。

还将向您展示应用开发工具包ADT)的更广泛视图。该项目的主要目标是提供一个能够开发、编译、运行、调试和分析软件应用程序的软件堆栈。它试图在不需要开发者额外学习的情况下实现这一点。它的学习曲线非常低,考虑到 Eclipse 是最常用的集成开发环境IDE)之一,而且随着时间的推移,它变得非常用户友好、稳定和可靠。ADT 用户体验与任何使用 Eclipse 或非 Eclipse 用户在使用 Eclipse IDE 时的体验非常相似。可用的插件尝试使这种体验尽可能相似,以便开发类似于任何 Eclipse IDE。唯一的区别在于配置步骤,这定义了一个 Eclipse IDE 版本与另一个版本之间的区别。

ADT 提供了使用独立交叉编译器、调试工具分析器、仿真器甚至是以平台无关的方式与开发板交互的可能性。虽然与硬件交互是嵌入式开发人员的最佳选择,但在大多数情况下,由于各种原因,真实硬件是缺失的。对于这些情况,可以使用 QEMU 仿真器来模拟必要的硬件。

应用开发工具包

ADT 是 Yocto 项目的组成部分,提供了一个跨开发平台,非常适合用户特定的应用程序开发。为了使开发过程有序进行,需要一些组件:

  • Eclipse IDE Yocto 插件

  • 用于特定硬件模拟的 QEMU 仿真器

  • 与特定体系结构相关的交叉工具链以及其特定的sysroot,这两者都是使用 Yocto 项目提供的元数据和构建系统生成的

  • 用户空间工具以增强开发人员在应用程序开发过程中的体验

当提供对 Eclipse IDE 的完全支持并最大化 Yocto 体验时,Eclipse 插件可用。最终结果是为 Yocto 开发人员的需求定制的环境,具有交叉工具链、在真实硬件上部署或 QEMU 仿真功能,以及一些用于收集数据、跟踪、分析和性能评估的工具。

QEMU 仿真器用于模拟各种硬件。可以通过以下方法获得它:

  • 使用 ADT 安装程序脚本,提供安装的可能性

  • 克隆一个 Poky 存储库并获取环境,可以访问 QEMU 环境

  • 下载 Yocto 发布并获取环境,以获得相同的结果

  • 安装交叉工具链并获取环境以使 QEMU 环境可用

工具链包含交叉调试器、交叉编译器和交叉链接器,在应用程序开发过程中被广泛使用。工具链还配备了用于目标设备的匹配 sysroot,因为它需要访问运行在目标架构上所需的各种头文件和库。sysroot 是从根文件系统生成的,并使用相同的元数据配置。

用户空间工具包括在前几章中已经提到的工具,如 SystemTap、PowerTop、LatencyTop、perf、OProfile 和 LTTng-UST。它们用于获取有关系统和开发应用程序的信息;例如功耗、桌面卡顿、事件计数、性能概述以及诊断软件、硬件或功能问题,甚至跟踪软件活动的信息。

设置环境

在进一步解释 ADT 项目、其 Eclipse IDE 插件、设置的其他功能之前,需要安装 Eclipse IDE。安装 Eclipse IDE 的第一步涉及设置主机系统。有多种方法可以做到这一点:

  • 使用 ADT 安装脚本:这是安装 ADT 的推荐方法,主要是因为安装过程是完全自动化的。用户可以控制他们想要的功能。

  • 使用 ADT tarball:这种方法涉及使用特定架构工具链的适当 tarball 部分,并使用脚本进行设置。该 tarball 可以通过下载和使用 Bitbake 手动构建。由于安装后并非所有功能都可用,此方法也存在限制,除了交叉工具链和 QEMU 模拟器之外。

  • 使用构建目录中的工具链:这种方法利用了构建目录已经可用的事实,因此交叉工具链的设置非常容易。此外,在这种情况下,它面临与前一点提到的相同的限制。

ADT 安装脚本是安装 ADT 的首选方法。当然,在进行安装步骤之前,需要确保必要的依赖项可用,以确保 ADT 安装脚本顺利运行。

这些软件包已经在前几章中提到过,但在这里将再次解释,以便为您简化事情。我建议您回到这些章节,再次查阅信息作为记忆练习。要查看可能对您感兴趣的软件包,请查看 ADT Installer 软件包,例如autoconf automake libtool libglib2.0-dev,Eclipse 插件以及libsdl1.2-dev xterm软件包提供的图形支持。

主机系统准备好所有所需的依赖项后,可以从downloads.yoctoproject.org/releases/yocto/yocto-1.7/adt-installer/下载 ADT tarball。在这个位置,adt_installer.tar.bz2存档可用。需要下载并提取其内容。

这个 tarball 也可以在构建目录中使用 Bitbake 构建系统生成,并且结果将在tmp/deploy/sdk/adt_installer.tar.bz2位置可用。要生成它,需要在构建目录中输入下一个命令,即bitbake adt-installer。构建目录还需要为目标设备正确配置。

存档使用tar -xjf adt_installer.tar.bz2命令解压缩。它可以在任何目录中提取,并在解压缩adt-installer目录后,创建并包含名为adt_installer的 ADT 安装程序脚本。它还有一个名为adt_installer.conf的配置文件,用于在运行脚本之前定义配置。配置文件定义了诸如文件系统、内核、QEMU 支持等信息。

这些是配置文件包含的变量:

  • YOCTOADT_REPO:这定义了安装所依赖的软件包和根文件系统。其参考值在adtrepo.yoctoproject.org//1.7中定义。在这里,定义了目录结构,其结构在发布之间是相同的。

  • YOCTOADT_TARGETS:这定义了为其设置交叉开发环境的目标架构。有一些默认值可以与此变量关联,如armppcmipsx86x86_64。也可以关联多个值,并使用空格分隔它们。

  • YOCTOADT_QEMU:此变量定义了 QEMU 模拟器的使用。如果设置为Y,则安装后将可用模拟器;否则,值设置为N,因此模拟器将不可用。

  • YOCTOADT_NFS_UTIL:这定义了将安装的 NFS 用户模式。可用的值如前所述为YN。为了使用 Eclipse IDE 插件,必须为YOCTOADT_QEMUYOCTOADT_NFS_UTIL同时定义Y值。

  • YOCTOADT_ROOTFS_<arch>:这指定了要从第一个提到的YOCTOADT_REPO变量中定义的存储库中使用哪个架构的根文件系统。对于arch变量,默认值是YOCTOADT_TARGETS变量中已经提到的值。该变量的有效值由可用的镜像文件表示,如minimalsatominimal-devsato-sdklsblsb-sdk等。对于该变量的多个参数,可以使用空格分隔符。

  • YOCTOADT_TARGET_SYSROOT_IMAGE_<arch>:这代表了交叉开发工具链的sysroot将从中生成的根文件系统。arch变量的有效值与之前提到的相同。它的值取决于之前为YOCTOADT_ROOTFS_<arch>变量定义的值。因此,如果只有一个变量被定义为YOCTOADT_ROOTFS_<arch>变量的值,那么相同的值将可用于YOCTOADT_TARGET_SYSROOT_IMAGE_<arch>。此外,如果在YOCTOADT_ROOTFS_<arch>变量中定义了多个变量,则其中一个需要定义YOCTOADT_TARGET_SYSROOT_IMAGE_<arch>变量。

  • YOCTOADT_TARGET_MACHINE_<arch>:这定义了下载镜像的目标机器,因为相同架构的机器之间可能存在编译选项的差异。该变量的有效值可以是:qemuarmqemuppcppc1022dsedgerouterbeaglebone等。

  • YOCTOADT_TARGET_SYSROOT_LOC_<arch>:这定义了安装过程结束后目标sysroot将可用的位置。

配置文件中还定义了一些变量,如YOCTOADT_BITBAKEYOCTOADT_METADATA,这些变量是为了未来的工作参考而定义的。开发人员根据需要定义所有变量后,安装过程就可以开始了。这是通过运行adt_installer脚本来完成的:

cd adt-installer
./adt_installer

以下是adt_installer.conf文件的示例:

# Yocto ADT Installer Configuration File
#
# Copyright 2010-2011 by Intel Corp.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy 
# of this software and associated documentation files (the "Software"), to deal 
# in the Software without restriction, including without limitation the rights 
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
# copies of the Software, and to permit persons to whom the Software is 
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in 
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
# THE SOFTWARE.

# Your yocto distro repository, this should include IPKG based packages and root filesystem files where the installation is based on

YOCTOADT_REPO="http://adtrepo.yoctoproject.org//1.7"
YOCTOADT_TARGETS="arm x86"
YOCTOADT_QEMU="Y"
YOCTOADT_NFS_UTIL="Y"

#YOCTOADT_BITBAKE="Y"
#YOCTOADT_METADATA="Y"

YOCTOADT_ROOTFS_arm="minimal sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_arm="sato-sdk"
YOCTOADT_TARGET_MACHINE_arm="qemuarm"
YOCTOADT_TARGET_SYSROOT_LOC_arm="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_arm"

#Here's a template for setting up target arch of x86 
YOCTOADT_ROOTFS_x86="sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_x86="sato-sdk"
YOCTOADT_TARGET_MACHINE_x86="qemux86"
YOCTOADT_TARGET_SYSROOT_LOC_x86="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_x86"

#Here's some template of other arches, which you need to change the value in ""
YOCTOADT_ROOTFS_x86_64="sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_x86_64="sato-sdk"
YOCTOADT_TARGET_MACHINE_x86_64="qemux86-64"
YOCTOADT_TARGET_SYSROOT_LOC_x86_64="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_x86_64"

YOCTOADT_ROOTFS_ppc="sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_ppc="sato-sdk"
YOCTOADT_TARGET_MACHINE_ppc="qemuppc"
YOCTOADT_TARGET_SYSROOT_LOC_ppc="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_ppc"

YOCTOADT_ROOTFS_mips="sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_mips="sato-sdk"
YOCTOADT_TARGET_MACHINE_mips="qemumips"
YOCTOADT_TARGET_SYSROOT_LOC_mips="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_mips"

安装开始后,用户会被询问交叉工具链的位置。如果没有提供替代方案,则选择默认路径,并将交叉工具链安装在/opt/poky/<release>目录中。安装过程可以以静默或交互方式可视化。通过使用I选项,可以以交互模式进行安装,而使用S选项可以启用静默模式。

安装过程结束时,交叉工具链将在其定义的位置找到。环境设置脚本将可供以后使用,并且镜像 tarball 位于adt-installer目录中,sysroot目录位于YOCTOADT_TARGET_SYSROOT_LOC_<arch>变量的位置。

如前所示,准备 ADT 环境有不止一种方法。第二种方法只涉及安装工具链安装程序,尽管它提供了预构建的交叉工具链、支持文件和脚本的可能性,比如runqemu脚本,可以在仿真器中启动类似于内核或 Linux 镜像的东西,但这不提供与第一种选择相同的可能性。此外,这个选项在sysroot目录方面有其局限性。尽管已经生成了sysroot目录,但可能仍需要将其提取并安装到单独的位置。这可能是由于各种原因,比如需要通过 NFS 引导根文件系统或者使用根文件系统作为目标sysroot开发应用程序。

根文件系统可以从已经生成的交叉工具链中提取出来,使用runqemu-extract-sdk脚本,这个脚本应该在使用 source 命令设置好交叉开发环境脚本之后才能调用。

有两种方法可以获得为第二个选项安装的工具链。第一种方法涉及使用downloads.yoctoproject.org/releases/yocto/yocto-1.7/toolchain/上可用的工具链安装程序。打开与您的开发主机机器匹配的文件夹。在此文件夹中,有多个安装脚本可用。每个脚本都与目标架构匹配,因此应为您拥有的目标选择正确的脚本。一个这样的例子可以从downloads.yoctoproject.org/releases/yocto/yocto-1.7/toolchain/x86_64/poky-glibc-x86_64-core-image-sato-armv7a-vfp-neon-toolchain-1.7.sh中看到,实际上是armv7a目标和x86_64主机机器的安装程序脚本。

如果您的目标机器不是 Yocto 社区提供的机器之一,或者您更喜欢这种方法的替代方法,那么构建工具链安装程序脚本就是适合您的方法。在这种情况下,您将需要一个构建目录,并且将呈现两种同样好的选择:

  • 第一种方法涉及使用bitbake meta-toolchain命令,最终结果是一个安装程序脚本,需要在单独的位置安装和设置交叉工具链。

  • 第二种选择涉及使用bitbake –c populate_sdk <image-name>任务,该任务提供了工具链安装程序脚本和与目标匹配的sysroot。这里的优势在于二进制文件只与一个libc链接,使得工具链是自包含的。当然,每个架构只能创建一个特定的构建,但是目标特定的选项通过gcc选项传递。使用变量,如CCLD,使得这个过程更容易维护,并且还节省了构建目录中的一些空间。

安装程序下载完成后,确保安装脚本已经正确设置执行权限,并使用./poky-glibc-x86_64-core-image-sato-armv7a-vfp-neon-toolchain-1.7.sh命令开始安装。

您需要的一些信息包括安装的位置,默认位置是/opt/poky/1.7目录。为了避免这一点,可以使用–d <install-location>参数调用脚本,并将安装位置设置为<install-location>,如上所述。

注意

确保local.conf文件中MACHINE变量设置正确。此外,如果为不同的主机机器进行构建,则还应设置SDKMACHINE。在同一个构建目录中可以生成多个MACHINE交叉工具链,但是这些变量需要正确配置。

安装过程完成后,交叉工具链将在所选位置可用,并且在需要时还将可用于源的环境脚本。

第三个选项涉及使用构建目录和执行bitbake meta-ide-support命令。在构建目录中,需要使用两个可用的构建环境设置脚本之一来设置适当的环境,其中包括oe-init-build-env脚本或oe-init-build-env-memres脚本。还需要根据目标架构相应地设置local.conf文件中的本地配置。开发人员完成这些步骤后,可以使用bitbake meta-ide-support命令开始生成交叉工具链。在过程结束时,将在<build-dir-path>/tmp目录中提供一个环境设置脚本,但在这种情况下,工具链紧密地链接到构建目录中。

环境设置完成后,可以开始编写应用程序,但开发人员仍然需要在完成活动之前完成一些步骤,例如在真实的根文件系统上测试应用程序、调试等。对于内核模块和驱动程序的实现,将需要内核源代码,因此活动刚刚开始。

Eclipse IDE

Yocto 项目为 Eclipse 提供的插件包括 ADT 项目和工具链的功能。它们允许开发人员使用交叉编译器、调试器和 Yocto 项目、Poky 和其他元层生成的所有可用工具。这些组件不仅可以在 Eclipse IDE 中使用,而且还为应用程序开发提供了熟悉的环境。

Eclipse IDE 是开发人员的另一种选择,他们不想与编辑器进行交互,比如vim,尽管在我看来,vim可以用于各种项目。即使它们的尺寸或复杂性不是问题,使用vim的开销可能并不适合所有口味。Eclipse IDE 是所有开发人员可用的最佳选择。它具有许多有用的功能和功能,可以让您的生活变得更轻松,而且很容易掌握。

Yocto 项目支持 Eclipse 的两个版本,Kepler 和 Juno。 Kepler 版本是最新 Poky 版本推荐的版本。我还建议使用 Eclipse 的 Kepler 4.3.2 版本,这是从 Eclipse 官方下载站点www.eclipse.org/downloads下载的版本。

从这个网站上,应该下载包含Java 开发工具JDT)、Eclipse 平台和主机机器的开发环境插件的 Eclipse 标准 4.3.2 版本。下载完成后,应使用 tar 命令提取接收到的存档内容:

tar xzf eclipse-standard-kepler-SR2-linux-gtk-x86_64.tar.gzls

接下来的步骤是配置。在提取内容后,需要在安装 Yocto 项目特定插件之前配置 Eclipse IDE。配置从初始化 Eclipse IDE 开始:

执行./eclipse可执行文件并设置Workspace位置后,将启动 Eclipse IDE。这是启动窗口的外观:

Eclipse IDE

Eclipse 窗口

要初始化 Eclipse IDE,请执行以下步骤:

  1. 选择工作台,您将进入空的工作台,可以在其中编写项目源代码。

  2. 现在,通过帮助菜单导航并选择安装新软件Eclipse IDE

帮助菜单

  1. 将打开一个新窗口,在使用:下拉菜单中,选择Kepler - http://download.eclipse.org/releases/kepler,如下图所示:Eclipse IDE

安装窗口

  1. 展开Linux 工具部分,并选择LTTng – Linux 跟踪工具包框,如下截图所示:Eclipse IDE

安装—LTTng – Linux 跟踪工具包框

  1. 展开移动和设备开发部分,并选择以下内容:
  • C/C++远程启动(需要 RSE 远程系统资源管理器)

  • 远程系统资源管理器终端用户运行时

  • 远程系统资源管理器用户操作

  • 目标管理终端

  • TCF 远程系统资源管理器插件

  • TCF 目标资源管理器

Eclipse IDE

  1. 展开编程语言部分,并选择以下内容:
  • C/C++ Autotools 支持

  • C/C++开发工具

如下截图所示:

Eclipse IDE

可用软件列表窗口

  1. 在快速查看安装详细信息菜单并启用许可协议后完成安装:Eclipse IDE

安装详细信息窗口

完成这些步骤后,可以将 Yocto 项目 Eclipse 插件安装到 IDE 中,但在重新启动 Eclipse IDE 之前,不能确保前述更改生效。配置阶段结束后的结果在此可见:

Eclipse IDE

Eclipse—配置阶段结果

要安装 Yocto 项目的 Eclipse 插件,需要执行以下步骤:

  1. 按照前面提到的方法启动 Eclipse IDE。

  2. 如前面的配置所示,从帮助菜单中选择安装新软件选项。

  3. 单击添加按钮,并在 URL 部分插入downloads.yoctoproject.org/releases/eclipse-plugin/1.7/kepler/。根据此处的指示为新的Work with:站点命名:Eclipse IDE

编辑站点窗口

  1. 按下OK按钮并更新Work with站点后,会出现新的框。选择所有这些框,如此图所示,并单击下一步按钮:Eclipse IDE

安装详细信息窗口

  1. 最后一次查看已安装的组件,安装即将结束。Eclipse IDE

安装详细信息窗口

  1. 如果出现此警告消息,请按确定并继续。它只是让您知道已安装的软件包具有未签名的内容。Eclipse IDE

安全警告窗口

只有在重新启动 Eclipse IDE 后更改才会生效,安装才算完成。

安装完成后,Yocto 插件可用并准备好进行配置。配置过程涉及设置特定于目标的选项和交叉编译器。对于每个特定的目标,需要相应地执行前述配置步骤。

通过从窗口菜单中选择首选项选项来完成配置过程。将打开一个新窗口,从中应选择Yocto 项目 ADT选项。更多细节可参见以下截图:

Eclipse IDE

Eclipse IDE—首选项

接下来要做的事情涉及配置交叉编译器的可用选项。第一个选项是工具链类型,有两个选项可用,独立预构建工具链构建系统派生工具链,默认选择后者。前者是指特定于已有现有内核和根文件系统的架构的工具链,因此开发的应用程序将手动在镜像中提供。但是,由于所有组件都是分开的,这一步并不是必需的。后者是指在 Yocto 项目构建目录中构建的工具链。

需要配置的下一个元素是工具链位置、sysroot位置和目标架构。工具链根位置用于定义工具链安装位置。例如,使用adt_installer脚本安装时,工具链将位于/opt/poky/<release>目录中。第二个参数Sysroot 位置表示目标设备根文件系统的位置。它可以在/opt/poky/<release>目录中找到,如前面的示例所示,或者如果使用其他方法生成它,则甚至可以在构建目录中找到。这一部分的第三个和最后一个选项由目标架构表示,它表示所使用或模拟的硬件类型。正如在窗口中所看到的,它是一个下拉菜单,用户可以选择所需的选项,并找到所有支持的架构列表。在所需架构在下拉菜单中不可用的情况下,将需要构建相应的架构镜像。

最后剩下的部分是目标特定选项。这指的是使用 QEMU 模拟架构或在外部可用的硬件上运行镜像的可能性。对于外部硬件,请使用需要选择的外部硬件选项以完成工作,但对于 QEMU 模拟,除了选择QEMU选项外,还有其他事情要做。在这种情况下,用户还需要指定内核自定义选项。对于内核选择,过程很简单。如果选择了独立预构建工具链选项,它将位于预构建镜像位置,或者如果选择了构建系统派生工具链选项,则将位于tmp/deploy/images/<machine-name>目录中。对于第二个选项自定义选项参数,添加它的过程不会像前面的选项那样简单。

自定义选项字段需要填写各种选项,例如kvm、nographic、publicvncserial,它们表示模拟架构或其参数的主要选项。这些选项被保存在尖括号内,并包括参数,例如使用的内存(-m 256)、网络支持(-net)和全屏支持(-full-screen)。有关可用选项和参数的更多信息可以使用man qemu命令找到。在定义项目后,可以使用更改 Yocto 项目设置选项从项目菜单中覆盖所有前述配置。

要定义一个项目,需要执行以下步骤:

  1. 文件 | 新建菜单选项中选择项目…选项,如下所示:Eclipse IDE

Eclipse IDE—项目

  1. C/C++选项中选择C 项目。这将打开一个C 项目窗口:Eclipse IDE

Eclipse IDE—新项目窗口

  1. C 项目窗口中,有多个选项可用。让我们选择Yocto 项目 ADT Autotools 项目,然后选择Hello World ANSI C Autotools 项目选项。为新项目添加名称,我们准备进行下一步:Eclipse IDE

C 项目窗口

  1. C 项目窗口中,您将被提示相应地添加作者版权声明Hello world 问候许可字段的信息:Eclipse IDE

C 项目—基本设置窗口

  1. 添加所有信息后,可以单击完成按钮。用户将在新的特定于C/C++的透视图中得到提示,该透视图特定于打开的项目,并且新创建的项目将出现在菜单的左侧。

  2. 创建项目并编写源代码后,要构建项目,请从项目…菜单中选择构建项目选项。

QEMU 模拟器

QEMU 在 Yocto 项目中作为各种目标架构的虚拟化机器和仿真器使用。它非常有用,可以运行和测试各种 Yocto 生成的应用程序和映像,除了完成其他目的。在 Yocto 世界之外,它的主要用途也是 Yocto 项目的卖点,使其成为默认工具来模拟硬件。

注意

有关 QEMU 用例的更多信息,请访问www.yoctoproject.org/docs/1.7/adt-manual/adt-manual.html#the-qemu-emulator

与 QEMU 仿真的交互是在 Eclipse 中完成的,如前所示。为此发生,需要适当的配置,如在前一节中所述。在这里启动 QEMU 仿真是使用“运行”菜单中的“外部工具”选项完成的。将为仿真器打开一个新窗口,并在传递相应的登录信息到提示后,shell 将可供用户交互。应用程序也可以在仿真器上部署和调试。

注意

有关 QEMU 交互的更多信息,请访问www.yoctoproject.org/docs/1.7/dev-manual/dev-manual.html#dev-manual-qemu

调试

如果存在,还可以使用 QEMU 仿真器或实际目标硬件来调试应用程序。当项目配置时,将生成一个C/C+远程应用程序实例的运行/调试 Eclipse 配置,并且可以根据其名称找到,该名称符合<project-name>_gdb_-<suffix>的语法。例如,TestProject_gdb_armv5te-poky-linux-gnueabi可能是一个例子。

要连接到 Eclipse GDB 界面并启动远程目标调试过程,用户需要执行一些步骤:

  1. 从“运行”|“调试配置”菜单中选择“C/C++远程应用程序”,并从左侧面板中的“C/C++远程应用程序”中选择运行/调试配置。

  2. 从下拉列表中选择适当的连接。

  3. 选择要部署的二进制应用程序。如果项目中有多个可执行文件,在按下“搜索项目”按钮后,Eclipse 将解析项目并提供所有可用二进制文件的列表。

  4. 通过相应地设置“C/C++应用程序的远程绝对文件路径:”字段,输入应用程序将部署的绝对路径。

  5. 在“调试器”选项卡中可以选择调试器选项。要调试共享库,需要进行一些额外的步骤:

  • 从“源”选项卡中选择“添加”|“路径映射”选项,以确保调试配置中有路径映射可用。

  • 从“调试/共享库”选项卡中选择“自动加载共享库符号”,并相应地指示共享库的路径。这个路径高度依赖于处理器的架构,所以非常小心地指定库文件。通常,对于 32 位架构,选择lib目录,对于 64 位架构,选择lib64目录。

  • 在“参数”选项卡上,有可能在执行时向应用程序二进制文件传递各种参数。

  1. 完成所有调试配置后,单击“应用”和“调试”按钮。将启动一个新的 GDB 会话,并打开“调试透视”。当调试器正在初始化时,Eclipse 将打开三个控制台:
  • 一个名为之前描述的 GDB 二进制文件的 GDB 控制台,用于命令行交互

  • 用于运行应用程序显示结果的远程 shell

  • 一个名为二进制路径的本地机器控制台,在大多数情况下,不会被使用。它仍然是一个工件。

  1. 在调试配置设置完成后,可以使用工具栏中的调试图标重新构建和执行应用程序。实际上,如果您只想运行和部署应用程序,可以使用运行图标。

性能分析和跟踪

Yocto 工具菜单中,您可以看到用于跟踪和分析开发应用程序的支持工具。这些工具用于增强应用程序的各种属性,总的来说,是为了提高开发过程和体验。将介绍的工具包括 LTTng、Perf、LatencyTop、PerfTop、SystemTap 和 KGDB。

我们首先要看的是 LTTng Eclipse 插件,它提供了跟踪目标会话和分析结果的可能性。要开始使用该工具,首先需要进行快速配置,如下所示:

  1. 窗口菜单中选择打开透视图来开始跟踪透视图。

  2. 文件 | 新建菜单中选择项目来创建一个新的跟踪项目。

  3. 窗口 | 显示视图 | 其他... | Lttng菜单中选择控制视图。这将使您能够访问所有这些所需的操作:

  • 创建一个新的连接

  • 创建一个会话

  • 开始/停止跟踪

  • 启用事件

接下来,我们将介绍一个名为Perf的用户空间性能分析工具。它为多个线程和内核提供应用程序代码的统计分析和简单的 CPU 分析。为了做到这一点,它使用了许多性能计数器、动态探针或跟踪点。要使用 Eclipse 插件,需要远程连接到目标。可以通过 Perf 向导或使用文件 | 新建 | 其他菜单中的远程系统资源管理器 | 连接选项来完成。远程连接设置完成后,与该工具的交互与该工具的命令行支持相同。

LatencyTop是一个用于识别内核中可用延迟及其根本原因的应用程序。由于 ARM 内核的限制,此工具不适用于启用了对称多处理SMP)支持的 ARM 内核。此应用程序还需要远程连接。远程连接设置完成后,与该工具的命令行支持相同。此应用程序是使用sudo从 Eclipse 插件运行的。

PowerTop用于测量电力消耗。它分析在 Linux 系统上运行的应用程序、内核选项和设备驱动程序,并估计它们的功耗。它非常有用,可以识别使用最多功率的组件。此应用程序需要远程连接。远程连接设置完成后,与该工具的命令行支持相同。此应用程序是使用-Eclipse 插件运行的,使用-d 选项在 Eclipse 窗口中显示输出。

SystemTap是一种工具,它可以使用脚本从运行中的 Linux 系统中获取结果。SystemTap 提供了一个自由软件(GPL)基础设施,用于简化通过跟踪所有内核调用来收集有关运行中 Linux 系统的信息。它与 Solaris 的 dtrace 非常相似,但与 dtrace 不同的是,它仍然不适用于生产系统。它使用类似于awk的语言,其脚本具有.stp扩展名。监视的数据可以被提取,并且可以对其进行各种过滤和复杂处理。Eclipse 插件使用crosstap脚本将.stp脚本转换为 C 语言,创建一个Makefile,运行 C 编译器以创建一个插入到目标内核的目标架构的内核模块,然后从内核中收集跟踪数据。要在 Eclipse 中启动 SystemTap 插件,需要遵循一些步骤。

  1. Yocto 项目工具菜单中选择systemtap选项。

  2. 在打开的窗口中,需要传递 crosstap 参数:

  • Metadata Location变量设置为相应的poky目录

  • 通过输入 root(默认选项)来设置Remote User ID,因为它对目标具有ssh访问权限-任何其他具有相同权限的用户也是一个不错的选择

  • Remote Host变量设置为目标的相应 IP 地址

  • 使用Systemtap Scripts变量来获取.stp脚本的完整路径

  • 使用Systemtap Args字段设置额外的交叉选项

.stp脚本的输出应该在 Eclipse 的控制台视图中可用。

我们将要看的最后一个工具是KGDB。这个工具专门用于调试 Linux 内核,只有在 Eclipse IDE 内进行 Linux 内核源代码开发时才有用。要使用这个工具,需要进行一些必要的配置设置:

  • 禁用 C/C++索引:

  • Window | Preferences菜单中选择C/C++ Indexer选项

  • 取消选择Enable indexer复选框

  • 创建一个可以导入内核源代码的项目:

  • File | New菜单中选择C/C++ | C Project选项

  • 选择Makefile project | Empty project选项,并为项目命名

  • 取消选择Use default location选项

  • 单击Browse按钮并标识内核源代码本地 git 存储库的位置

  • 按下Finish按钮,项目应该已创建

在满足先决条件后,实际配置可以开始:

  • Run菜单中选择Debug Configuration选项。

  • 双击GDB Hardware Debugging选项以创建名为 Default的默认配置。

  • Main选项卡,浏览到vmlinux构建图像的位置,选择Disable auto build单选按钮,以及GDB (DFS) Hardware Debugging Launcher选项。

  • 对于Debugger选项卡中可用的C/C++ Application选项,浏览工具链内可用的 GDB 二进制文件的位置(如果 ADT 安装程序脚本可用,则其默认位置应为/opt/poky/1.7/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gdb)。从JTAG Device菜单中选择Generic serial optionUse remote target选项是必需的。

  • Startup选项卡,选择Load symbols选项。确保Use Project binary选项指示正确的vmlinux图像,并且未选择Load image选项。

  • 按下Apply按钮以确保先前的配置已启用。

  • 为串行通信调试准备目标:

  • 设置echo ttyS0,115200 | /sys/module/kgdboc/parameters/kgdboc选项以确保适当的设备用于调试

  • echo g | /proc/sysrq-trigger目标上启动 KGDB

  • 关闭目标终端但保持串行连接

  • Run菜单中选择Debug Configuration选项

  • 选择先前创建的配置,然后单击Debug按钮

按下Debug按钮后,调试会话应该开始,并且目标将在kgdb_breakpoint()函数中停止。从那里,所有特定于 GDB 的命令都可用并准备好使用。

Yocto Project bitbake 指挥官

bitbake 指挥官提供了编辑配方和创建元数据项目的可能性,类似于命令行中可用的方式。两者之间的区别在于使用 Eclipse IDE 进行元数据交互。

为了确保用户能够执行这些操作,需要进行一些步骤:

  • File | New菜单中选择Project选项

  • 从打开的窗口中选择Yocto Project BitBake Commander向导

  • 选择New Yocto Project选项,将打开一个新窗口来定义新项目的属性

  • 使用项目位置,识别poky目录的父目录

  • 使用项目名称选项定义项目名称。其默认值为 poky

  • 对于远程服务提供商变量,选择本地选项,并在连接名称下拉列表中使用相同的选项

  • 确保对已安装的poky源目录未选择克隆复选框

通过使用 Eclipse IDE,其功能可供使用。其中最有用的功能之一是快速搜索选项,对一些开发人员可能非常有用。其他好处包括使用模板创建配方的可能性,使用语法高亮、自动完成、实时错误报告等进行编辑,以及许多其他功能。

注意

使用 bitbake commander 仅限于本地连接。远程连接会导致 IDE 由于上游可用的错误而冻结。

摘要

在本章中,您了解了 Yocto 项目提供的 ADE 功能的信息,以及可用于应用程序开发的众多 Eclipse 插件,这不仅是一种替代方案,也是对连接到他们的 IDE 的开发人员的解决方案。尽管本章以介绍命令行爱好者的应用程序开发选项开始,但很快就变成了关于 IDE 交互的内容。这是因为需要提供替代解决方案,以便开发人员可以选择最适合他们需求的内容。

在下一章中,将介绍一些 Yocto 项目的组件。这一次,它们与应用程序开发无关,而涉及元数据交互、质量保证和持续集成服务。我将尝试展示 Yocto 项目的另一面,我相信这将帮助读者更好地了解 Yocto 项目,并最终与适合他们和他们需求的组件进行交互和贡献。

第八章:Hob,Toaster 和 Autobuilder

在本章中,您将被介绍 Yocto 社区中使用的新工具和组件。正如标题所示,本章专门介绍另一类工具。我将从 Hob 作为图形界面开始,它正在逐渐消失,并将被一个名为 Toaster 的新网络界面所取代。本章还将介绍一个新的讨论点。在这里,我指的是 QA 和测试组件,在大多数情况下,它是缺失或不足的。Yocto 非常重视这个问题,并为其提供了解决方案。这个解决方案将在本章的最后一节中介绍。

您还将获得有关 Hob,Toaster 和 Autobuilder 等组件的更详细的介绍。将分别评估这些组件,并详细查看它们的优势和用例。对于前两个组件(即 Hob 和 Toaster),提供了有关构建过程的信息以及各种设置方案。Hob 类似于 BitBake,并与 Poky 和构建目录紧密集成。另一方面,Toaster 是一个更松散的替代方案,提供多种配置选择和设置,并且性能部分对于任何有兴趣改进构建系统整体性能的开发人员非常有用。本章以 Autobuilder 部分结束。该项目是 Yocto 项目的基石,致力于使嵌入式开发和开源更加用户友好,但也提供了更安全和无错误的项目。希望您喜欢本章;让我们继续到第一节。

Hob

Hob 项目代表了 BitBake 构建系统的图形界面替代方案。它的目的是以更简单更快的方式执行最常见的任务,但并不会消除命令行交互。这是因为大多数配方和配置的部分仍然需要手动完成。在上一章中,引入了 BitBake Commander 扩展作为编辑配方的替代解决方案,但在这个项目中,它有其局限性。

Hob 的主要目的是使用户更容易地与构建系统进行交互。当然,有些用户不喜欢图形用户界面的替代方案,而更喜欢命令行选项,我有点同意他们,但这是另一个讨论。Hob 也可以是他们的选择;它不仅是为那些喜欢在面前有界面的人提供的选择,也是为那些喜欢他们的命令行交互的人提供的选择。

Hob 除了最常见的任务外,可能无法执行很多任务,例如构建图像,修改现有的配方,通过 QEMU 模拟器运行图像,甚至在目标设备上将其部署到 USB 设备以进行一些现场引导操作。拥有所有这些功能并不多,但非常有趣。您在 Yocto Project 中使用工具的经验在这里并不重要。前面提到的任务可以非常轻松和直观地完成,这是 Hob 最有趣的地方。它以非常简单的方式为用户提供所需的功能。与之交互的人可以从它所提供的教训中学到东西,无论他们是图形界面爱好者还是命令行专家。

在本章中,我将向您展示如何使用 Hob 项目构建 Linux 操作系统图像。为了演示这一点,我将使用 Atmel SAMA5D3 Xplained 机器,这也是我在前几章中进行其他演示时使用的机器。

首先,让我们看看当您第一次启动 Hob 时它是什么样子。结果显示在以下截图中:

Hob

要检索图形界面,用户需要执行 BitBake 命令行交互所需的给定步骤。首先,需要创建一个构建目录,并从该构建目录开始,用户需要使用以下 Hob 命令启动 Hob 图形界面:

source poky/oe-init-build-env ../build-test
hob

下一步是确定构建所需的层。您可以通过在窗口中选择它们来完成。对于meta-atmel层的第一步是将其添加到构建中。尽管您可能在已经存在的构建目录中开始工作,但 Hob 将无法检索现有的配置,并将在bblayers.conflocal.conf配置文件上创建一个新的配置。它将使用下一个#added by hob消息标记添加的行。

Hob

在构建目录中添加了相应的meta-atmel层之后,所有支持的机器都可以在选择机器下拉菜单中找到,包括meta-atmel层添加的机器。从可用选项中,需要选择sama5d3-xplained机器:

Hob

当选择 Atmel sama5d3-xplained机器时,会出现如下截图所示的错误:

Hob

在将meta-qt5层添加到层部分后,此错误消失,构建过程可以继续。要检索meta-qt5层,需要以下git命令:

git clone -b dizzy https://github.com/meta-qt5/meta-qt5.git

由于所有可用的配置文件和配方都被解析,解析过程需要一段时间,之后您会看到如下截图所示的错误:

Hob

经过快速检查后,您会看到以下代码:

find ../ -name "qt4-embedded*"
./meta/recipes-qt/qt4/qt4-embedded_4.8.6.bb
./meta/recipes-qt/qt4/qt4-embedded.inc
./meta-atmel/recipes-qt/qt4/qt4-embedded-4.8.5
./meta-atmel/recipes-qt/qt4/qt4-embedded_4.8.5.bbappend

唯一的解释是meta-atmel层没有更新其配方,而是附加它们。这可以通过两种方式克服。最简单的方法是更新.bbappend文件的配方,并确保新的可用配方被转换为上游社区的补丁。稍后将向您解释在meta-atmel层内具有所需更改的补丁,但首先,我将介绍可用的选项和解决构建过程中存在的问题所需的必要更改。

另一个解决方案是包含meta-atmel在构建过程中所需的必要配方。最好的地方也将其放在meta-atmel中。然而,在这种情况下,.bbappend配置文件应与配方合并,因为在同一位置拥有配方及其附加文件并不太合理。

在解决了这个问题之后,用户将可以看到新的选项,如下截图所示:

Hob

现在,用户有机会选择需要构建的镜像,以及需要添加的额外配置。这些配置包括:

  • 选择分发类型

  • 选择镜像类型

  • 打包格式

  • 根文件系统周围的其他小调整

其中一些如下图所示:

Hob

我选择将分发类型从poky-tiny更改为poky,并且生成的根文件系统输出格式可在下图中看到:

Hob

经过调整后,配方被重新解析,当此过程完成后,可以选择生成的镜像,从而开始构建过程。此演示中选择的镜像是atmel-xplained-demo-image镜像,与同名的配方相对应。这些信息也显示在下图中:

Hob

点击构建镜像按钮开始构建过程。构建开始后一段时间,将出现一个错误,告诉我们meta-atmelBSP 层需要我们定义更多的依赖项:

Hob

这些信息是从iperf配方中收集的,该配方不在包含的层中;它在meta-openembedded/meta-oe层内可用。在进行更详细的搜索和更新过程后,有一些发现。meta-atmel BSP 层需要的层依赖关系比所需的更多,如下所示:

  • meta-openembedded/meta-oe

  • meta-openembedded/meta-networking

  • meta-openembedded/meta-ruby

  • meta-openembedded/meta-python

  • meta-qt5

最终结果可在bblayers.conf文件中找到的BBLAYERS变量中找到,如下所示:

#added by hob
BBFILES += "${TOPDIR}/recipes/images/custom/*.bb"
#added by hob
BBFILES += "${TOPDIR}/recipes/images/*.bb"

#added by hob
BBLAYERS = "/home/alex/workspace/book/poky/meta /home/alex/workspace/book/poky/meta-yocto /home/alex/workspace/book/poky/meta-yocto-bsp /home/alex/workspace/book/poky/meta-atmel /home/alex/workspace/book/poky/meta-qt5 /home/alex/workspace/book/poky/meta-openembedded/meta-oe /home/alex/workspace/book/poky/meta-openembedded/meta-networking /home/alex/workspace/book/poky/meta-openembedded/meta-ruby /home/alex/workspace/book/poky/meta-openembedded/meta-python"

在开始完整构建之前,meta-atmel层中需要进行一些必要的更改,如下所示:

  • packagegroup-core-full-cmdline替换packagegroup-core-basic,因为最新的 Poky 已更新了packagegroup名称。

  • 删除python-setuptools,因为它在meta-openembedded/meta-oe层中不再可用,也不在新的meta-openembedded/meta-python层中,后者是所有与 Python 相关的配方的新占位符。python-setuptools工具被删除,因为它具有下载、构建、安装、升级和卸载额外 Python 软件包的能力,并且不是 Yocto 的强制要求。这是它的一般目的。

  • 关于更新到qt4-embedded-4.8.6的前述更改,出现了错误。

meta-atmel层的所有更改都包含在以下补丁中:

From 35ccf73396da33a641f307f85e6b92d5451dc255 Mon Sep 17 00:00:00 2001
From: "Alexandru.Vaduva" <vaduva.jan.alexandru@gmail.com>
Date: Sat, 31 Jan 2015 23:07:49 +0200
Subject: [meta-atmel][PATCH] Update suppport for atmel-xplained-demo-image
 image.

The latest poky contains updates regarding the qt4 version support
and also the packagegroup naming.
Removed packages which are no longer available.

Signed-off-by: Alexandru.Vaduva <vaduva.jan.alexandru@gmail.com>
---
 recipes-core/images/atmel-demo-image.inc           |  3 +--
 ...qt-embedded-linux-4.8.4-phonon-colors-fix.patch | 26 ----------------------
 ...qt-embedded-linux-4.8.4-phonon-colors-fix.patch | 26 ++++++++++++++++++++++
 recipes-qt/qt4/qt4-embedded_4.8.5.bbappend         |  2 --
 recipes-qt/qt4/qt4-embedded_4.8.6.bbappend         |  2 ++
 5 files changed, 29 insertions(+), 30 deletions(-)
 delete mode 100644 recipes-qt/qt4/qt4-embedded-4.8.5/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
 create mode 100644 recipes-qt/qt4/qt4-embedded-4.8.6/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
 delete mode 100644 recipes-qt/qt4/qt4-embedded_4.8.5.bbappend
 create mode 100644 recipes-qt/qt4/qt4-embedded_4.8.6.bbappend

diff --git a/recipes-core/images/atmel-demo-image.inc b/recipes-core/images/atmel-demo-image.inc
index fe13303..a019586 100644
--- a/recipes-core/images/atmel-demo-image.inc
+++ b/recipes-core/images/atmel-demo-image.inc
@@ -2,7 +2,7 @@ IMAGE_FEATURES += "ssh-server-openssh package-management"

 IMAGE_INSTALL = "\
     packagegroup-core-boot \
-    packagegroup-core-basic \
+    packagegroup-core-full-cmdline \
     packagegroup-base-wifi \
     packagegroup-base-bluetooth \
     packagegroup-base-usbgadget \
@@ -23,7 +23,6 @@ IMAGE_INSTALL = "\
     python-smbus \
     python-ctypes \
     python-pip \
-    python-setuptools \
     python-pycurl \
     gdbserver \
     usbutils \
diff --git a/recipes-qt/qt4/qt4-embedded-4.8.5/qt-embedded-linux-4.8.4-phonon-colors-fix.patch b/recipes-qt/qt4/qt4-embedded-4.8.5/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
deleted file mode 100644
index 0624eef..0000000
--- a/recipes-qt/qt4/qt4-embedded-4.8.5/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-diff --git a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
-index 89d5a9d..8508001 100644
---- a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
-+++ b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
-@@ -18,6 +18,7 @@
- #include <QApplication>
- #include "videowidget.h"
- #include "qwidgetvideosink.h"
-+#include <gst/video/video.h>
-
- QT_BEGIN_NAMESPACE
-
-@@ -106,11 +107,7 @@ static GstStaticPadTemplate template_factory_rgb =-     GST_STATIC_PAD_TEMPLATE("sink",- GST_PAD_SINK,
-                             GST_PAD_ALWAYS,
--                            GST_STATIC_CAPS("video/x-raw-rgb, "
--                                            "framerate = (fraction) [ 0, MAX ], "
--                                            "width = (int) [ 1, MAX ], "
--                                            "height = (int) [ 1, MAX ],"
--                                            "bpp = (int) 32"));
-+                            GST_STATIC_CAPS(GST_VIDEO_CAPS_xRGB_HOST_ENDIAN));
-
- template <VideoFormat FMT>
- struct template_factory;
-
diff --git a/recipes-qt/qt4/qt4-embedded-4.8.6/qt-embedded-linux-4.8.4-phonon-colors-fix.patch b/recipes-qt/qt4/qt4-embedded-4.8.6/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
new file mode 100644
index 0000000..0624eef
--- /dev/null
+++ b/recipes-qt/qt4/qt4-embedded-4.8.6/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
@@ -0,0 +1,26 @@
+diff --git a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
+index 89d5a9d..8508001 100644
+--- a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
++++ b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
+@@ -18,6 +18,7 @@
+ #include <QApplication>
+ #include "videowidget.h"
+ #include "qwidgetvideosink.h"
++#include <gst/video/video.h>
+
+ QT_BEGIN_NAMESPACE
+
+@@ -106,11 +107,7 @@ static GstStaticPadTemplate template_factory_rgb =+     GST_STATIC_PAD_TEMPLATE("sink",+ GST_PAD_SINK,+ GST_PAD_ALWAYS,+- GST_STATIC_CAPS("video/x-raw-rgb, "+-                                            "framerate = (fraction) [ 0, MAX ], "
+-                                            "width = (int) [ 1, MAX ], "
+-                                            "height = (int) [ 1, MAX ],"
+-                                            "bpp = (int) 32"));
++                            GST_STATIC_CAPS(GST_VIDEO_CAPS_xRGB_HOST_ENDIAN));
+
+ template <VideoFormat FMT>
+ struct template_factory;
+
diff --git a/recipes-qt/qt4/qt4-embedded_4.8.5.bbappend b/recipes-qt/qt4/qt4-embedded_4.8.5.bbappend
deleted file mode 100644
index bbb4d26..0000000
--- a/recipes-qt/qt4/qt4-embedded_4.8.5.bbappend
+++ /dev/null
@@ -1,2 +0,0 @@
-FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}-${PV}:"
-SRC_URI += "file://qt-embedded-linux-4.8.4-phonon-colors-fix.patch"
diff --git a/recipes-qt/qt4/qt4-embedded_4.8.6.bbappend b/recipes-qt/qt4/qt4-embedded_4.8.6.bbappend
new file mode 100644
index 0000000..bbb4d26
--- /dev/null
+++ b/recipes-qt/qt4/qt4-embedded_4.8.6.bbappend
@@ -0,0 +1,2 @@
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}-${PV}:"
+SRC_URI += "file://qt-embedded-linux-4.8.4-phonon-colors-fix.patch"
-- 
1.9.1

这个补丁在本章中作为 Git 交互的一个示例,并且在创建需要上游到社区的补丁时是必需的。在撰写本章时,这个补丁尚未发布到上游社区,因此这可能是一个礼物,供有兴趣向 meta-atmel 社区特别是 Yocto 社区添加贡献的人使用。

在更改完成后获得此补丁所需的步骤被简要描述。它定义了生成补丁所需的步骤,如下命令所示,即0001-Update-suppport-for-atmel-xplained-demo-image-image.patch。可以通过README文件和git send-email命令将其上游到社区或直接发送给meta-atmel层的维护者:

git status 
git add --all .
git commit -s
git fetch -a
git rebase -i origin/master 
git format-patch -s --subject-prefix='meta-atmel]PATCH' origin/master
vim 0001-Update-suppport-for-atmel-xplained-demo-image-image.patch

Toaster

Toaster 是 Hob 的替代品,在某个特定时间点将完全取代它。它还是 BitBake 命令行的基于 Web 的界面。这个工具比 Hob 更有效;它不仅能够以与 Hob 类似的方式执行最常见的任务,而且还包括一个构建分析组件,收集有关构建过程和结果的数据。这些结果以非常易于理解的方式呈现,提供了搜索、浏览和查询信息的机会。

从收集的信息中,我们可以提到以下内容:

  • 图像目录的结构

  • 可用的构建配置

  • 构建的结果以及注册的错误和警告

  • 图像配方中存在的软件包

  • 构建的配方和软件包

  • 执行的任务

  • 有关执行任务的性能数据,如 CPU 使用率、时间和磁盘 I/O 使用情况

  • 配方的依赖关系和反向依赖关系

Hob 解决方案也存在一些缺点。Toaster 目前还不能配置和启动构建。但是,已经采取了措施将 Hob 内的这些功能包含在 Toaster 中,这将在不久的将来实现。

Toaster 项目的当前状态允许在各种设置和运行模式下执行。每个都将被呈现并相应地定义如下:

  • 交互模式:这是在 Yocto Project 1.6 版本中提供的模式。它基于toasterui构建记录组件和toastergui构建检查和统计用户界面。

  • 管理模式:除了 Yocto Project 1.6 版本之外,这是处理从 Web 界面触发的构建配置、调度和执行的模式。

  • 远程管理模式:这是托斯特主机模式,用于生产环境,因为它支持多个用户和定制安装。

  • 本地管理模式或 _ 本地 _ 模式:这是在 Poky 检出后可用的模式,允许使用本地机器代码和构建目录进行构建。这也是任何第一次与 Toaster 项目交互的人使用的模式。

  • 对于交互模式,需要与 Yocto Project 构建运行的硬件分开设置,例如使用 AutoBuilder、BuildBot 或 Jenkins 等工具进行构建。在普通的 Toaster 实例后面,有三件事情发生:

  • 启动 BitBake 服务器

  • 启动 Toaster UI,并连接到 BitBake 服务器以及 SQL 数据库。

  • 启动 Web 服务器是为了读取与数据库相关的信息,并在 Web 界面上显示它

有时会出现多个 Toaster 实例在多台远程机器上运行的情况,或者单个 Toaster 实例在多个用户和构建服务器之间共享的情况。所有这些情况都可以通过修改 Toaster 启动的模式以及相应地更改 SQL 数据库和 Web 服务器的位置来解决。通过拥有一个共同的 SQL 数据库、Web 服务器和多个 BitBake 服务器,以及每个单独的构建目录的 Toaster 用户界面,可以解决前面提到的问题。因此,Toaster 实例中的每个组件都可以在不同的机器上运行,只要适当进行通信并且各组件了解彼此。

要在 Ubuntu 机器上设置 SQL 服务器,需要安装一个软件包,使用以下命令:

apt-get install mysgl-server

拥有必要的软件包还不够,还需要设置它们。因此,需要适当的用户名和密码来访问 Web 服务器,以及 MySQL 帐户的适当管理权限。此外,还需要 Toaster 主分支的克隆用于 Web 服务器,源代码可用后,请确保在bitbake/lib/toaster/toastermain/settings.py文件中,DATABASES变量指示了先前设置的数据库。确保使用为其定义的用户名和密码。

设置完成后,可以按以下方式开始数据库同步:

python bitbake/lib/toaster/manage.py syncdb
python bitbake/lib/toaster/manage.py migrate orm
python bitbake/lib/toaster/manage.py migrate bldcontrol

现在,可以使用python bitbake/lib/toaster/manage.py runserver命令启动 Web 服务器。对于后台执行,可以使用nohup python bitbake/lib/toaster/manage.py runserver 2>toaster_web.log >toaster_web.log &命令。

这可能足够作为起步,但由于构建需要案例日志,因此需要一些额外的设置。在bitbake/lib/toaster/toastermain/settings.py文件中,DATABASES变量指示用于日志服务器的 SQL 数据库。在构建目录中,调用source toaster start命令,并确保conf/toaster.conf文件可用。在此文件中,请确保启用了 Toaster 和构建历史bbclasses,以记录有关软件包的信息:

INHERIT += "toaster"
INHERIT += "buildhistory"
BUILDHISTORY_COMMIT = "1"

设置完成后,使用以下命令启动 BitBake 服务器和日志界面:

bitbake --postread conf/toaster.conf --server-only -t xmlrpc -B localhost:0 && export BBSERVER=localhost:-1
nohup bitbake --observe-only -u toasterui >toaster_ui.log &

完成后,可以启动正常的构建过程,并且在构建在 Web 界面内运行时,日志和数据可供检查。不过,要注意一点:在完成在构建目录内的工作后,不要忘记使用bitbake –m命令关闭 BitBake 服务器。

本地与迄今为止介绍的 Yocto Project 构建非常相似。这是个人使用和学习与工具交互的最佳模式。在开始设置过程之前,需要安装一些软件包,使用以下命令行:

sudo apt-get install python-pip python-dev build-essential 
sudo pip install --upgrade pip 
sudo pip install --upgrade virtualenv

安装了这些软件包后,请确保安装烤面包机所需的组件;在这里,我指的是 Django 和 South 软件包:

sudo pip install django==1.6
sudo pip install South==0.8.4

与 Web 服务器交互时,需要80008200端口,因此请确保它们没有被其他交互预留。考虑到这一点,我们可以开始与烤面包机交互。使用前几章节中提供的下载中可用的 Poky 构建目录,调用oe-init-build-env脚本创建一个新的构建目录。这可以在已经存在的构建目录上完成,但有一个新的构建目录将有助于识别可用于与烤面包机交互的额外配置文件。

根据您的需求设置构建目录后,应调用source toaster start命令,如前所述,启动烤面包机。在http://localhost:8000上,如果没有执行构建,您将看到以下屏幕截图:

![烤面包机在控制台中运行构建,它将自动在 Web 界面中更新,如下面的屏幕截图所示:烤面包机

构建完成后,Web 界面将相应地更新。我关闭了标题图像和信息,以确保在 Web 页面中只有构建可见。

烤面包机

如前面的例子所示,在前面的屏幕截图中有两个已完成的构建。它们都是内核构建。第一个成功完成,而第二个有一些错误和警告。我这样做是为了向用户展示他们构建的替代输出。

由于主机机器上的内存和空间不足,导致构建失败,如下面的屏幕截图所示:

烤面包机

对于失败的构建,有一个详细的失败报告可用,如下面的屏幕截图所示:

烤面包机

成功完成的构建提供了大量信息的访问。以下屏幕截图显示了构建应该具有的有趣功能。对于内核构建,它显示了使用的所有 BitBake 变量、它们的值、它们的位置和简短描述。这些信息对所有开发人员都非常有用,不仅因为它在一个位置提供了所有这些信息,而且因为它提供了一个减少寻找麻烦变量所需的搜索时间的搜索选项:

烤面包机

在执行活动完成后,可以使用source toaster stop命令停止烤面包机。

在构建目录中,烤面包机创建了许多文件;它们的命名和目的在以下行中介绍:

  • bitbake-cookerdaemon.log:这个日志文件对于 BitBake 服务器是必要的

  • .toastermain.pid:这是包含 Web 服务器pid的文件

  • .toasterui.pid:它包含 DSI 数据桥,pid

  • toaster.sqlite:这是数据库文件

  • toaster_web.log:这是 Web 服务器日志文件

  • toaster_ui.log:这是用户界面组件使用的日志文件

提到了所有这些因素,让我们转到下一个组件,但在提供有关烤面包机的一些有趣视频链接之前。

注意

有关烤面包机手册 1.7 的信息可在www.yoctoproject.org/documentation/toaster-manual-17上访问。

自动构建器

Autobuilder 是负责 QA 的项目,在 Yocto Project 内部提供了一个测试构建。它基于 BuildBot 项目。虽然这本书没有涉及这个主题,但对于那些对 BuildBot 项目感兴趣的人,可以在以下信息框中找到更多信息。

注意

Buildbot 的起始页面可以在trac.buildbot.net/上访问。您可以在docs.buildbot.net/0.8.5/tutorial/tour.html找到有关快速启动 BuildBot 的指南,其概念可以在docs.buildbot.net/latest/manual/concepts.html找到。

我们现在要讨论的是一个在一般开发人员中受到非常糟糕对待的软件领域。我指的是开发过程的测试和质量保证。事实上,这是一个需要我们更多关注的领域,包括我自己在内。Yocto Project 通过 AutoBuilder 倡议试图引起更多对这一领域的关注。此外,在过去几年中,开源项目的 QA 和持续集成(CI)出现了转变,这主要可以在 Linux Foundation 的伞下项目中看到。

Yocto Project 积极参与 AutoBuilder 项目的以下活动:

  • 使用 Bugzilla 测试用例和计划发布测试和 QA 计划(bugzilla.yoctoproject.org)。

  • 展示这些计划并使它们对所有人可见。当然,为此,您将需要相应的帐户。

  • 为所有人开发工具、测试和 QA 程序。

在上述活动作为基础的基础上,他们提供了对 Poky 主分支当前状态的公共 AutoBuilder 的访问。每晚为所有支持的目标和架构执行构建和测试集,并且所有人都可以在autobuilder.yoctoproject.org/上找到。

注意

如果您没有 Bugzilla 帐户来访问 Yocto Project 内部完成的 QA 活动,请参阅wiki.yoctoproject.org/wiki/QA

与 AutoBuilder 项目互动,设置在README-QUICKSTART文件中定义为一个四步程序:

cat README-QUICKSTART 
Setting up yocto-autobuilder in four easy steps:
------------------------------------------------
git clone git://git.yoctoproject.org/yocto-autobuilder
cd yocto-autobuilder
. ./yocto-autobuilder-setup
yocto-start-autobuilder both

该项目的配置文件位于config目录中。autobuilder.conf文件用于定义项目的参数,例如DL_DIRSSTATE_DIR,以及其他构建工件对于生产设置非常有用,但对于本地设置则不太有用。要检查的下一个配置文件是yoctoABConfig.py,它位于yocto-controller目录中,用于定义执行构建的属性。

此时,AutoBuilder 应该正在运行。如果它在 Web 界面内启动,结果应该类似于以下截图:

自动构建

从网页标题中可以看出,不仅可以执行构建,还可以以不同的视图和角度查看它们。以下是其中一种可视化视角:

自动构建

这个项目对其用户有更多的提供,但我会让其余的通过试验和阅读 README 文件来发现。请记住,这个项目是基于 Buildbot 构建的,因此工作流程与它非常相似。

总结

在本章中,您将了解到 Yocto Project 中提供的一组新组件。在这里,我指的是 Hob、Toaster 和 AutoBuilder 项目。本章首先介绍了 Hob 作为 BitBake 的替代方案。接着介绍了 Toaster 作为 Hob 的替代方案,它也具有许多有趣的功能,尽管现在还不是最好的,但随着时间的推移,它将成为开发人员的真正解决方案,他们不想学习新技术,而是只需与工具交互,以快速简便的方式获得他们想要的东西。本章最后介绍了 AutoBuilder 项目,为 Yocto Project 社区提供了一个质量保证和测试平台,并可以转变为一个持续集成工具。

在下一章中,将介绍一些其他工具,但这次重点将稍微转向社区以及其小工具的外部。我们还将涵盖项目和工具,例如 Swabber,这是一个不断发展的项目。我们还将看看 Wic,一个性格鲜明的小工具,以及来自 Linaro 的新感觉 LAVA。希望您喜欢学习它们。

第九章:Wic 和其他工具

在本章中,将简要介绍一些解决各种问题并以巧妙方式解决它们的工具。这一章可以被认为是为你准备的开胃菜。如果这里介绍的任何工具似乎引起了你的兴趣,我鼓励你满足你的好奇心,尝试找到更多关于那个特定工具的信息。当然,这条建议适用于本书中提供的任何信息。然而,这条建议特别适用于本章,因为我选择了对我介绍的工具进行更一般的描述。我这样做是因为我假设你们中的一些人可能对冗长的描述不感兴趣,而只想把兴趣集中在开发过程中,而不是其他领域。对于其他对了解更多其他关键领域感兴趣的人,请随意浏览本章中提供的信息扩展。

在本章中,将提供对 Swabber、Wic 和 LAVA 等组件的更详细解释。这些工具不是嵌入式开发人员在日常工作中会遇到的工具,但与这些工具的交互可能会让生活变得更轻松一些。我应该首先提到这些工具的一件事是它们彼此之间没有任何共同之处,它们之间非常不同,并且解决了不同的问题。如果 Swabber,这里介绍的第一个工具,用于在主机开发机器上进行访问检测,那么第二个工具代表了 BitBake 在复杂打包选项方面的限制的解决方案。在这里,我指的是 wic 工具。本章介绍的最后一个元素是名为 LAVA 的自动化测试框架。这是来自 Linaro 的一个倡议,我认为这个项目非常有趣。它还与 Jenkins 等持续集成工具结合在一起,这可能对每个人都是一个致命的组合。

拖把

Swabber 是一个项目,虽然它在 Yocto Project 的官方页面上展示,但据说它还在进行中;自 2011 年 9 月 18 日以来没有任何活动。它没有维护者文件,您无法在其中找到更多关于其创建者的信息。然而,对于任何对这个项目感兴趣的人来说,提交者列表应该足够了解更多。

本章选择介绍这个工具,因为它构成了 Yocto Project 生态系统的另一个视角。当然,对主机系统进行访问检测的机制并不是一个坏主意,对于检测可能对系统有问题的访问非常有用,但在开发软件时并不是首选的工具。当你有可能重新构建并手动检查主机生态系统时,你往往会忽视工具也可以用于这个任务,并且它们可以让你的生活更轻松。

与 Swabber 交互,需要首先克隆存储库。可以使用以下命令来实现这一目的:

git clone http://git.yoctoproject.org/git/swabber

源代码在主机上可用后,存储库的内容应如下所示:

tree swabber/
swabber/
├── BUGS
├── canonicalize.c
├── canonicalize.h
├── COPYING
├── detect_distro
├── distros
│   ├── Fedora
│   │   └── whitelist
│   ├── generic
│   │   ├── blacklist
│   │   ├── filters
│   │   └── whitelist
│   ├── Ubuntu
│   │   ├── blacklist
│   │   ├── filters
│   │   └── whitelist
│   └── Windriver
│       └── whitelist
├── dump_blob.c
├── lists.c
├── lists.h
├── load_distro.c
├── Makefile
├── packages.h
├── README
├── swabber.c
├── swabber.h
├── swabprof.c
├── swabprof.in
├── swab_testf.c
├── update_distro
├── wandering.c
└── wandering.h

5 directories, 28 files

正如你所看到的,这个项目并不是一个重大项目,而是由一些热情的人提供的一些工具。其中包括来自Windriver的两个人:Alex deVries 和 David Borman。他们独自开发了之前介绍的工具,并将其提供给开源社区使用。Swabber 是用 C 语言编写的,这与 Yocto Project 社区提供的通常的 Python/Bash 工具和其他项目有很大的不同。每个工具都有自己的目的,相似之处在于所有工具都是使用相同的 Makefile 构建的。当然,这不仅限于使用二进制文件;还有两个 bash 脚本可用于分发检测和更新。

有关该工具的更多信息可以从其创建者那里获得。他们的电子邮件地址,可在项目的提交中找到,分别是<alex.devries@windriver.com><david.borman@windriver.com>。但请注意,这些是工作场所的电子邮件地址,而曾经参与 Swabber 工作的人现在可能没有相同的电子邮件地址。

与 Swabber 工具的交互在README文件中有很好的描述。在这里,关于 Swabber 的设置和运行的信息是可用的,不过,为了你的方便,这也将在接下来的几行中呈现,以便你能更快地理解和更容易地掌握。

第一个必要的步骤是编译源代码。这是通过调用make命令来完成的。在源代码构建并可执行文件可用后,可以使用update_distro命令对主机分发进行配置,然后是分发目录的位置。我们选择的名称是Ubuntu-distro-test,它是特定于执行工具的主机分发。这个生成过程一开始可能需要一些时间,但之后,对主机系统的任何更改都将被检测到,并且过程所需的时间将更少。在配置过程结束时,Ubuntu-distro-test目录的内容如下:

Ubuntu-distro-test/
├── distro
├── distro.blob
├── md5
└── packages

主机分发配置文件后,可以基于创建的配置文件生成一个 Swabber 报告。此外,在创建报告之前,还可以创建一个配置文件日志,以备报告过程中使用。为了生成报告,我们将创建一个具有特定日志信息的日志文件位置。日志可用后,就可以生成报告了:

strace -o logs/Ubuntu-distro-test-logs.log -e trace=open,execve -f pwd
./swabber -v -v -c all -l logs/ -o required.txt -r extra.txt -d Ubuntu-distro-test/ ~ /tmp/

工具需要这些信息,如其帮助信息所示:

Usage: swabber [-v] [-v] [-a] [-e]
 -l <logpath> ] -o <outputfile> <filter dir 1> <filter dir 2> ...

 Options:
 -v: verbose, use -v -v for more detail
 -a: print progress (not implemented)
 -l <logfile>: strace logfile or directory of log files to read
 -d <distro_dir>: distro directory
 -n <distro_name>: force the name of the distribution
 -r <report filename>: where to dump extra data (leave empty for stdout)
 -t <global_tag>: use one tag for all packages
 -o <outputfile>: file to write output to
 -p <project_dir>: directory were the build is being done
 -f <filter_dir>: directory where to find filters for whitelist,
 blacklist, filters
 -c <task1>,<task2>...: perform various tasks, choose from:
 error_codes: show report of files whose access returned an error
 whitelist: remove packages that are in the whitelist
 blacklist: highlight packages that are in the blacklist as
 being dangerous
 file_detail: add file-level detail when listing packages
 not_in_distro: list host files that are not in the package
 database
 wandering: check for the case where the build searches for a
 file on the host, then finds it in the project.
 all: all the above

从前面代码中附加的帮助信息中,可以调查测试命令所选参数的作用。此外,由于 C 文件中不超过 1550 行,最大的文件是swabber.c文件,因此建议检查工具的源代码。

required.txt文件包含有关使用的软件包和特定文件的信息。有关配置的更多信息也可以在extra.txt文件中找到。这些信息包括可以访问的文件和软件包,各种警告以及主机数据库中不可用的文件,以及各种错误和被视为危险的文件。

对于跟踪的命令,输出信息并不多。这只是一个示例;我鼓励你尝试各种场景,并熟悉这个工具。这可能对你以后有所帮助。

Wic

Wic 是一个命令行工具,也可以看作是 BitBake 构建系统的扩展。它是由于需要有一个分区机制和描述语言而开发的。很容易得出结论,BitBake 在这些方面存在不足,尽管已经采取了一些措施,以确保这样的功能在 BitBake 构建系统内可用,但这只能在一定程度上实现;对于更复杂的任务,Wic 可以是一个替代解决方案。

在接下来的几行中,我将尝试描述与 BitBake 功能不足相关的问题,以及 Wic 如何以简单的方式解决这个问题。我还将向你展示这个工具是如何诞生的,以及灵感来源是什么。

在使用 BitBake 构建图像时,工作是在继承image.bbclass的图像配方中完成的,以描述其功能。在这个类中,do_rootfs()任务是负责创建后续将包含在最终软件包中的根文件系统目录的 OS。该目录包含了在各种板上引导 Linux 图像所需的所有源。完成do_rootfs()任务后,会查询一系列命令,为每种图像定义类型生成输出。图像类型的定义是通过IMAGE_FSTYPE变量完成的,对于每种图像输出类型,都有一个IMAGE_CMD_type变量被定义为从外部层继承的额外类型,或者是在image_types.bbclass文件中描述的基本类型。

实际上,每种类型背后的命令都是针对特定的根文件系统格式的 shell 命令。其中最好的例子就是ext3格式。为此,定义了IMAGE_CMD_ext3变量,并调用了这些命令,如下所示:

genext2fs -b $ROOTFS_SIZE ... ${IMAGE_NAME}.rootfs.ext3
tune2fs -j ${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}.rootfs.ext3

在调用命令后,输出以image-*.ext3文件的形式呈现。这是根据定义的FSTYPES变量值新创建的 EXT3 文件系统,并包含了根文件系统内容。这个例子展示了一个非常常见和基本的文件系统创建命令。当然,在工业环境中可能需要更复杂的选项,这些选项不仅包括根文件系统,还可能包括额外的内核或甚至是引导加载程序。对于这些复杂的选项,需要广泛的机制或工具。

Yocto 项目中可见的可用机制在image_types.bbclass文件中通过IMAGE_CMD_type变量可见,并具有以下形式:

image_types_foo.bbclass:
  IMAGE_CMD_bar = "some shell commands"
  IMAGE_CMD_baz = "some more shell commands"

要使用新定义的图像格式,需要相应地更新机器配置,使用以下命令:

foo-default-settings.inc
  IMAGE_CLASSES += "image_types_foo"

通过在image.bbclass文件中使用inherit ${IMAGE_CLASSES}命令,新定义的image_types_foo.bbclass文件的功能可见并准备好被使用,并添加到IMAGE_FSTYPE变量中。

前面的实现意味着对于每个实现的文件系统,都会调用一系列命令。这对于非常简单的文件系统格式是一个很好的简单方法。然而,对于更复杂的文件系统,需要一种语言来定义格式、状态以及图像格式的属性。Poky 中提供了各种其他复杂的图像格式选项,如vmdklivedirectdisk文件类型,它们都定义了一个多阶段的图像格式化过程。

要使用vmdk图像格式,需要在IMAGE_FSTYPE变量中定义一个vmdk值。然而,为了生成和识别这种图像格式,应该可用并继承image-vmdk.bbclass文件的功能。有了这些功能,可以发生三件事:

  • do_rootfs()任务中创建了对 EXT3 图像格式的依赖,以确保首先生成ext3图像格式。vmdk图像格式依赖于此。

  • ROOTFS变量被设置为boot-directdisk功能。

  • 继承了boot-directdisk.bbclass

此功能提供了生成可以复制到硬盘上的映像的可能性。在其基础上,可以生成 syslinux 配置文件,并且启动过程还需要两个分区。最终结果包括 MBR 和分区表部分,后跟一个包含引导文件、SYSLINUX 和 Linux 内核的 FAT16 分区,以及用于根文件系统位置的 EXT3 分区。此图像格式还负责将 Linux 内核、syslinux.cfgldlinux.sys 配置移动到第一个分区,并使用 dd 命令将 EXT3 图像格式复制到第二个分区。在此过程结束时,使用 tune2fs 命令为根目录保留空间。

从历史上看,directdisk 在其最初版本中是硬编码的。对于每个图像配方,都有一个类似的实现,它镜像了基本实现,并在 image.bbclass 功能的配方中硬编码了遗产。对于 vmdk 图像格式,添加了 inherit boot-directdisk 行。

关于自定义定义的图像文件系统类型,一个示例可以在 meta-fsl-arm 层中找到;此示例可在 imx23evk.conf 机器定义中找到。此机器添加了下面两种图像文件系统类型:uboot.mxsboot-sdcardsdcard

meta-fsl-arm/imx23evk.conf
  include conf/machine/include/mxs-base.inc
  SDCARD_ROOTFS ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}.rootfs.ext3"
  IMAGE_FSTYPES ?= "tar.bz2 ext3 uboot.mxsboot-sdcard sdcard"

在前面的行中包含的 mxs-base.inc 文件又包含了 conf/machine/include/fsl-default-settings.inc 文件,后者又添加了 IMAGE_CLASSES +="image_types_fsl" 行,如一般情况所示。使用前面的行提供了首先为 uboot.mxsboot-sdcard 格式可用的命令执行 IMAGE_CMD 命令的可能性,然后是 sdcard IMAGE_CMD 命令特定的图像格式。

image_types_fsl.bbclass 文件定义了 IMAGE_CMD 命令,如下所示:

inherit image_types
  IMAGE_CMD_uboot.mxsboot-sdcard = "mxsboot sd ${DEPLOY_DIR_IMAGE}/u-boot-${MACHINE}.${UBOOT_SUFFIX} \
${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}.rootfs.uboot.mxsboot-sdcard"

在执行过程结束时,使用 mxsboot 命令调用 uboot.mxsboot-sdcard 命令。执行此命令后,将调用 IMAGE_CMD_sdcard 特定命令来计算 SD 卡的大小和对齐方式,初始化部署空间,并将适当的分区类型设置为 0x53 值,并将根文件系统复制到其中。在此过程结束时,将可用多个分区,并且它们具有相应的 twiddles,用于打包可引导的映像。

有多种方法可以创建各种文件系统,它们分布在大量现有的 Yocto 层中,并且一些文档可供一般公众使用。甚至有许多脚本用于为开发人员的需求创建合适的文件系统。其中一个示例是 scripts/contrib/mkefidisk.sh 脚本。它用于从另一种图像格式(即 live.hddimg)创建一个 EFI 可引导的直接磁盘映像。然而,一个主要的想法仍然存在:这种类型的活动应该在没有在中间阶段生成的中间图像文件系统的情况下进行,并且应该使用无法处理复杂场景的分区语言。

牢记这些信息,似乎在前面的示例中,我们应该使用另一个脚本。考虑到可以在构建系统内部和外部构建映像的可能性,开始寻找适合我们需求的一些工具。这个搜索结果是 Fedora Kickstart 项目。尽管它的语法也适用于涉及部署工作的领域,但它通常被认为对开发人员最有帮助。

注意

有关 Fedora Kickstart 项目的更多信息,请访问 fedoraproject.org/wiki/Anaconda/Kickstart

从这个项目中,最常用和有趣的组件是clearpartpartbootloader,这些对我们的目的也很有用。当您查看 Yocto 项目的 Wic 工具时,它也可以在配置文件中找到。如果 Wic 的配置文件在 Fedora kickstart 项目中定义为.wks,则配置文件使用.yks扩展名。一个这样的配置文件定义如下:

def pre():
    free-form python or named 'plugin' commands

  clearpart commands
  part commands
  bootloader commands
  named 'plugin' commands

  def post():
    free-form python or named 'plugin' commands  

前面脚本背后的想法非常简单:clearpart组件用于清除磁盘上的任何分区,而part组件用于相反的操作,即用于创建和安装文件系统的组件。定义的第三个工具是bootloader组件,用于安装引导加载程序,并处理从part组件接收到的相应信息。它还确保引导过程按照配置文件中的描述进行。定义为pre()post()的函数用于创建图像、阶段图像工件或其他复杂任务的预和后计算。

如前述描述所示,与 Fedora kickstarter 项目的交互非常富有成效和有趣,但源代码是在 Wic 项目内使用 Python 编写的。这是因为搜索了一个类似工具的 Python 实现,并在pykickstarted库的形式下找到了。这并不是 Meego 项目在其Meego Image CreatorMIC)工具中使用的前述库的全部用途。该工具用于 Meego 特定的图像创建过程。后来,该项目被 Tizen 项目继承。

注意

有关 MIC 的更多信息,请参阅github.com/01org/mic

Wic,我承诺在本节中介绍的工具,源自 MIC 项目,它们两者都使用 kickstarter 项目,因此所有三者都基于定义了创建各种图像格式过程行为的插件。在 Wic 的第一个实现中,它主要是 MIC 项目的功能。在这里,我指的是它定义的 Python 类,几乎完全复制到了 Poky 中。然而,随着时间的推移,该项目开始拥有自己的实现,也有了自己的个性。从 Poky 存储库的 1.7 版本开始,不再直接引用 MIC Python 定义的类,使 Wic 成为一个独立的项目,具有自己定义的插件和实现。以下是您可以检查 Wic 中可访问的各种格式配置的方法:

tree scripts/lib/image/canned-wks/
scripts/lib/image/canned-wks/
├── directdisk.wks
├── mkefidisk.wks
├── mkgummidisk.wks
└── sdimage-bootpart.wks

Wic 中定义了配置。然而,考虑到这个工具近年来的兴趣增加,我们只能希望支持的配置数量会增加。

我之前提到 MIC 和 Fedora kickstarter 项目的依赖关系已经被移除,但在 Poky scripts/lib/wic目录中快速搜索会发现情况并非如此。这是因为 Wic 和 MIC 都有相同的基础,即pykickstarted库。尽管 Wic 现在在很大程度上基于 MIC,并且两者都有相同的父级,即 kickstarter 项目,但它们的实现、功能和各种配置使它们成为不同的实体,尽管相关,但它们已经走上了不同的发展道路。

LAVA

LAVALinaro 自动化和验证架构)是一个连续集成系统,专注于物理目标或虚拟硬件部署,其中执行一系列测试。执行的测试种类繁多,从只需要启动目标的最简单测试到需要外部硬件交互的非常复杂的场景。

LAVA 代表一系列用于自动验证的组件。LAVA 堆栈的主要思想是创建一个适用于各种规模项目的质量受控测试和自动化环境。要更仔细地查看 LAVA 实例,读者可以检查已经创建的实例,由 Linaro 在剑桥托管的官方生产实例。您可以在validation.linaro.org/访问它。希望您喜欢使用它。

LAVA 框架支持以下功能:

  • 它支持在各种硬件包上对多个软件包进行定期自动测试

  • 确保设备崩溃后系统会自动重新启动

  • 它进行回归测试

  • 它进行持续集成测试

  • 它进行平台启用测试

  • 它支持本地和云解决方案

  • 它提供了结果捆绑支持

  • 它提供性能和功耗的测量

LAVA 主要使用 Python 编写,这与 Yocto 项目提供的内容没有什么不同。正如在 Toaster 项目中看到的那样,LAVA 还使用 Django 框架进行 Web 界面,项目使用 Git 版本控制系统进行托管。这并不奇怪,因为我们正在谈论 Linaro,这是一个致力于自由开源项目的非营利组织。因此,应用于项目的所有更改应返回到上游项目,使项目更容易维护。但是,它也更健壮,性能更好。

注意

对于那些对如何使用该项目的更多细节感兴趣的人,请参阅validation.linaro.org/static/docs/overview.html

使用 LAVA 框架进行测试,第一步是了解其架构。了解这一点不仅有助于测试定义,还有助于扩展测试,以及整个项目的开发。该项目的主要组件如下:

               +-------------+
               |web interface|
               +-------------+
                      |
                      v
                  +--------+
            +---->|database|
            |     +--------+
            |
+-----------+------[worker]-------------+
|           |                           |
|  +----------------+     +----------+  |
|  |scheduler daemon|---→ |dispatcher|  |
|  +----------------+     +----------+  |
|                              |        |
+------------------------------+--------+
                               |
                               V
                     +-------------------+
                     | device under test |
                     +-------------------+

第一个组件Web 界面负责用户交互。它用于存储数据和使用 RDBMS 提交作业,并负责显示结果、设备导航,或者通过 XMLRPC API 进行作业提交接收活动。另一个重要组件是调度程序守护程序,负责分配作业。它的活动非常简单。它负责从数据库中汇集数据,并为由调度程序提供给它们的作业保留设备,调度程序是另一个重要组件。调度程序是负责在设备上运行实际作业的组件。它还管理与设备的通信,下载图像并收集结果。

有时只能使用调度程序的情况;这些情况涉及使用本地测试或测试功能开发。还有一些情况,所有组件都在同一台机器上运行,比如单个部署服务器。当然,理想的情况是组件解耦,服务器在一台机器上,数据库在另一台机器上,调度程序守护程序和调度程序在另一台机器上。

对于使用 LAVA 进行开发过程,推荐的主机是 Debian 和 Ubuntu。与 LAVA 合作的 Linaro 开发团队更喜欢 Debian 发行版,但它也可以在 Ubuntu 机器上很好地运行。有一些需要提到的事情:对于 Ubuntu 机器,请确保宇宙存储库可供包管理器使用并可见。

必需的第一个软件包是lava-dev;它还有脚本指示必要的软件包依赖项,以确保 LAVA 工作环境。以下是执行此操作所需的必要命令:

sudo apt-get install lava-dev
git clone http://git.linaro.org/git/lava/lava-server.git
cd lava-server
/usr/share/lava-server/debian-dev-build.sh lava-server

git clone http://git.linaro.org/git/lava/lava-dispatcher.git
cd lava-dispatcher
/usr/share/lava-server/debian-dev-build.sh lava-dispatcher

考虑到更改的位置,需要采取各种行动。例如,对于“模板”目录中的 HTML 内容的更改,刷新浏览器就足够了,但在*_app目录的 Python 实现中进行的任何更改都需要重新启动apache2ctlHTTP 服务器。此外,*_daemon目录中的 Python 源代码的任何更改都需要完全重新启动lava-server

注意

对于所有对获取有关 LAVA 开发的更多信息感兴趣的人,开发指南构成了一份良好的文档资源,可在validation.linaro.org/static/docs/#developer-guides找到。

要在 64 位 Ubuntu 14.04 机器上安装 LAVA 或任何与 LAVA 相关的软件包,除了启用通用存储库deb http://people.linaro.org/~neil.williams/lava jessie main之外,还需要新的软件包依赖项,以及之前为 Debian 发行版描述的安装过程。我必须提到,当安装lava-dev软件包时,用户将被提示进入一个菜单,指示nullmailer mailname。我选择让默认值保持不变,实际上这是运行nullmailer服务的计算机的主机名。我还保持了默认为smarthost定义的相同配置,并且安装过程已经继续。以下是在 Ubuntu 14.04 机器上安装 LAVA 所需的命令:

sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
sudo apt-get update
sudo add-apt-repository "deb http://people.linaro.org/~neil.williams/lava jessie main"
sudo apt-get update

sudo apt-get install postgresql
sudo apt-get install lava
sudo a2dissite 000-default
sudo a2ensite lava-server.conf
sudo service apache2 restart

注意

有关 LAVA 安装过程的信息可在validation.linaro.org/static/docs/installing_on_debian.html#找到。在这里,您还可以找到 Debian 和 Ubuntu 发行版的安装过程。

总结

在本章中,您被介绍了一组新的工具。我必须诚实地承认,这些工具并不是在嵌入式环境中最常用的工具,但它们被引入是为了为嵌入式开发环境提供另一个视角。本章试图向开发人员解释,嵌入式世界不仅仅是开发和帮助这些任务的工具。在大多数情况下,相邻的组件可能是对开发过程影响最大的组件。

在下一章中,将简要介绍 Linux 实时要求和解决方案。我们将强调在这一领域与 Linux 一起工作的各种功能。将提供 meta-realtime 层的简要介绍,并讨论 Preempt-RT 和 NOHZ 等功能。话不多说,让我们继续下一章。希望您会喜欢它的内容。

第十章:实时

在本章中,您将了解 Yocto 项目的实时组件的信息。此外,在相同的背景下,将解释操作系统和实时操作系统的通用目的的简要讨论。然后我们将转向 PREEMPT_RT 补丁,试图将正常的 Linux 变成一个功能强大的实时操作系统;我们将尝试从更多角度来看待它,并最终总结并得出结论。这还不是全部,任何实时操作都需要其应用程序,因此还将简要介绍适用于实时操作系统背景下的应用程序编写的注意事项。记住所有这些,我相信现在是时候继续本章内容了;希望您喜欢。

您将在本章找到对实时组件的更详细解释。还将向您展示 Linux 与实时的关系。众所周知,Linux 操作系统被设计为一个类似于已有的 UNIX 的通用操作系统。很容易看出,多用户系统(如 Linux)和实时系统在某种程度上存在冲突。这主要是因为对于通用目的,多用户操作系统(如 Linux)被配置为获得最大的平均吞吐量。这牺牲了对实时操作系统来说恰恰相反的延迟要求。

实时的定义相当容易理解。在计算中,其主要思想是计算机或任何嵌入式设备能够及时向其环境提供反馈。这与快速不同;事实上,在系统的上下文中足够快。对于汽车行业或核电厂来说,足够快是不同的。此外,这种系统将提供可靠的响应以做出不影响任何外部系统的决策。例如,在核电厂中,它应该检测并防止任何异常情况,以确保避免灾难发生。

理解 GPOS 和 RTOS

当提到 Linux 时,通常会将通用目的操作系统GPOS)与之联系起来,但随着时间的推移,对 Linux 具有与实时操作系统RTOS)相同的好处的需求变得更为迫切。任何实时系统的挑战在于满足给定的时间约束,尽管存在各种随机的异步事件。这并不是一项简单的任务,对实时系统的理论进行了大量的论文和研究。实时系统的另一个挑战是对延迟设置上限,称为调度截止日期。根据系统如何应对这一挑战,它们可以分为硬实时、稳固实时和软实时:

  • 硬实时系统:这代表了一个如果错过截止日期将导致完全系统故障的系统。

  • 稳固实时系统:这代表了一个截止日期错过是可以接受的,但系统质量可能会降低的系统。此外,在错过截止日期后,所提供的结果将不再有用。

  • 软实时系统:这代表了一个错过截止日期会降低所收到结果的有用性,从而降低系统的质量的系统。在这种系统中,满足截止日期被视为一个目标而不是严格要求。

有多个原因导致 Linux 不适合作为 RTOS:

  • 分页:通过虚拟内存的页面交换过程是没有限制的。目前没有方法可以知道从磁盘获取页面需要多长时间,这意味着页面故障可能导致的延迟没有上限。

  • 粗粒度同步:在这里,Linux 内核的定义是不可抢占的。这意味着一旦一个进程处于内核上下文中,它就不能被抢占,直到退出上下文。在事件发生时,新事件需要等待调度,直到已有的事件退出内核上下文。

  • 批处理:可以对操作进行批处理,以更有效地利用资源。这种方法的最简单示例是页面释放过程。Linux 能够传递多个页面并尽可能多地进行清理,而不是释放每个单独的页面。

  • 请求重排序:可以对进程的 I/O 请求进行重新排序,使硬件的使用过程更加高效。

  • 调度公平性:这是 UNIX 的遗产,指的是调度程序试图对所有运行的进程公平。这个特性提供了等待时间较长的较低优先级进程在较高优先级进程之前被调度的可能性。

所有前述特征构成了任务或进程的延迟不能应用上限的原因,也是 Linux 不能成为硬实时操作系统的原因。让我们看一下下面的图表,它说明了 Linux 操作系统提供实时特性的方法:

理解 GPOS 和 RTOS

任何人可以做的第一件事来改善标准 Linux 操作系统的延迟就是尝试更改调度策略。默认的 Linux 时间共享调度策略称为SCHED_OTHER,它使用公平算法,给予所有进程零优先级,即可用的最低优先级。其他类似的调度策略有SCHED_BATCH用于进程的批处理调度和SCHED_IDLE,适用于极低优先级作业的调度。这些调度策略的替代方案是SCHED_FIFOSCHED_RR。它们都是用作实时策略的,适用于需要精确控制进程和它们的延迟的时间关键应用程序。

为了给 Linux 操作系统提供更多的实时特性,还有另外两种方法可以提出。第一种是对 Linux 内核更具抢占性的实现。这种方法可以利用已有的用于 SMP 支持的自旋锁机制,确保多个进程不会同时执行,尽管在单处理器的情况下,自旋锁是无操作的。中断处理也需要修改以进行重新调度,以便在出现另一个更高优先级的进程时进行可能的重新调度;在这种情况下,可能还需要一个新的调度程序。这种方法的优点是不改变用户空间的交互,并且可以使用诸如 POSIX 或其他 API。缺点是内核的更改非常严重,每次内核版本更改时,这些更改都需要相应地进行调整。如果这项工作还不够,最终结果并不是完全的实时操作系统,而是减少了操作系统的延迟。

另一种可用的实现是中断抽象。这种方法基于这样一个事实,即并非所有系统都需要硬实时确定性,大多数系统只需要执行其任务的一部分在实时环境中执行。这种方法的理念是在实时内核下以空闲任务的优先级运行 Linux,并继续执行非实时任务,就像它们通常做的那样。这种实现伪装了实时内核的中断禁用,但实际上是传递给了实时内核。对于这种类型的实现,有三种可用的解决方案:

  • RTLinux:它代表中断抽象方法的原始实现,是在新墨西哥矿业技术研究所开发的。尽管它仍有开源实现,但大部分开发现在是由 FSMLabs 工程师完成的,后来被 Wind River System 收购用于其商业版本。对 RTLinux 的商业支持于 2011 年 8 月结束。

  • RTAI:这是对在米兰理工大学航空航天工程系开发的 RTLinux 解决方案的增强。该项目非常活跃,有大量开发人员,并且有当前版本可用。

  • Xenomai:它代表第三种实现。它的历史有些扭曲:它于 2001 年 8 月出现,只是在 2013 年与 RTAI 合并,以生成适合生产的实时操作系统。然而,这种融合在 2005 年解散,又重新成为一个独立项目。

以下图表展示了基本的 RTLinux 架构。

理解 GPOS 和 RTOS

与前面图表中显示的类似架构适用于另外两种解决方案,因为它们都是从 RTLinux 实现中诞生的。它们之间的区别在于实现级别,每种都提供各种好处。

PREEMPT_RT

当需要实时解决方案时,PREEMPT_RT 补丁是每个开发人员的首选。对于一些开发人员,PREEMPT_RT 补丁将 Linux 转变为适合其需求的实时解决方案。这个解决方案不能取代实时操作系统,但实际上适用于大量系统。

PREEMPT_RT 相对于 Linux 的其他实时解决方案的最大优势在于,它实际上将 Linux 转变为实时操作系统。所有其他替代方案通常创建一个微内核,作为超级监视器执行,而 Linux 只作为其任务执行,因此实时任务与非实时任务之间的通信是通过这个微内核完成的。对于 PREEMPT_RT 补丁,这个问题不复存在。

标准版的 Linux 内核只能提供基本的软实时要求,如基本的 POSIX 用户空间操作,其中没有保证的截止期。通过添加补丁,如 Ingo Molnar 的 PREEMPT_RT 补丁,以及 Thomas Gheixner 关于提供高分辨率支持的通用时钟事件层的补丁,可以说你有一个提供高实时能力的 Linux 内核。

随着实时抢占补丁在行业中的出现,出现了许多有趣的机会,使其成为工业控制或专业音频等领域的坚实和硬实时应用的选择。这主要是因为 PREEMPT_RT 补丁的设计及其旨在集成到主线内核中。我们将在本章中进一步了解其用法。以下图表显示了可抢占 Linux 内核的工作原理:

PREEMPT_RT

PREEMPT_RT 补丁通过以下技巧将 Linux 从通用操作系统转变为可抢占的操作系统:

  • 使用可抢占的rwlock_t preemptiblespinlock_t来保护关键部分。仍然可以使用旧的解决方案,使用raw_spinlock_t,它与spinlock_t具有相同的 API。

  • 使用rtmutexes抢占内核锁定机制。

  • mutexesspinlocksrw_semaphores实现了优先级倒置和优先级继承机制。

  • 将现有的 Linux 定时器 API 转换为具有高分辨率定时器的 API,从而提供超时的可能性。

  • 实现使用内核线程作为中断处理程序。实时抢占补丁将软中断处理程序处理为内核线程上下文,使用task_struct结构来处理每个用户空间进程。还可以将 IRQ 注册到内核上下文中。

注意

有关优先级反转的更多信息,请参阅www.embedded.com/electronics-blogs/beginner-s-corner/4023947/Introduction-to-Priority-Inversion

应用 PREEMPT_RT 补丁

在移动到实际配置部分之前,您应该下载适合内核的版本。最好的灵感来源是www.kernel.org/,这应该是起点,因为它不包含任何额外的补丁。收到源代码后,可以从www.kernel.org/pub/linux/kernel/projects/rt/下载相应的rt补丁版本。本演示选择的内核版本是 3.12 内核版本,但如果需要其他内核版本,则可以采取类似的步骤,获得类似的结果。实时抢占补丁的开发非常活跃,因此任何缺失的版本支持都会很快得到解决。此外,对于其他子级版本,可以在特定内核版本的incr或旧的子目录中找到补丁。以下是子级版本的示例:

wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.12.38.tar.xz
wget https://www.kernel.org/pub/linux/kernel/projects/rt/3.12/patch-3.12.38-rt52.patch.gz

收到源代码后,需要解压源代码并应用补丁:

tar xf linux-3.12.38.tar.xz
cd linux-3.12.38/
gzip -cd ../patch-3.12.38-rt52.patch.gz | patch -p1

下一步涉及内核源代码的配置。配置因架构而异,但总体思路保持不变。在 Poky 内支持 QEMU ARM 机器需要以下配置。要为机器启用 PREEMPT_RT 支持,有多种选项可用。您可以实现低延迟支持版本,这对于使用类似于这样的内核配置片段的台式计算机最合适:

CONFIG_GENERIC_LOCKBREAK=y
CONFIG_TREE_PREEMPT_RCU=y
CONFIG_PREEMPT_RCU=y
CONFIG_UNINLINE_SPIN_UNLOCK=y
CONFIG_PREEMPT=y
CONFIG_PREEMPT__LL=y
CONFIG_PREEMPT_COUNT=y
CONFIG_DEBUG_PREEMPT=y
CONFIG_RCU_CPU_STALL_VERBOSE=y

这个选项是最常用的选项之一,也构成了 PREEMPT_RT 补丁的主要使用来源。另一种选择是使用类似于这样的配置启用 PREEMPT_RT 补丁的全抢占支持:

CONFIG_PREEMPT_RT_FULL=y
CONFIG_HZ_1000=y
CONFIG_HZ=1000

如果您有兴趣手动配置内核,可以使用menuconfig选项。以下CONFIG_PREEMPT*配置可更轻松地访问所需的选项。第一个图像主要包含CONFIG_PREEMPTCONFIG_PREEMPT_COUNT变量,这应该是启用的第一个变量。还有一个名为CONFIG_PREEMPT_NONE的配置选项,用于不强制进行抢占操作。

应用 PREEMPT_RT 补丁

在下一个图像中,CONFIG_PREEMPT_RCUCONFIG_PREEMPT_RT_FULL配置可用。有关RCU的更多信息,请参阅lwn.net/Articles/262464/

应用 PREEMPT_RT 补丁

第三个图像包含CONFIG_PREEMPT__LL配置。另一个有趣的配置是CONFIG_PREEMPT_VOLUNTARY,它与CONFIG_PREEMPT__LL配置一起减少延迟,适用于台式计算机。

有关低延迟台式机选项的有趣论点可在sevencapitalsins.wordpress.com/2007/08/10/low-latency-kernel-wtf/找到。

应用 PREEMPT_RT 补丁

最后一个包含CONFIG_TREE_PREEMPT_RCU配置,用于更改RCU实现。可以使用相同的过程搜索和启用其他不包含搜索词的配置。

应用 PREEMPT_RT 补丁

有关 PREEMPT_RT 补丁的更多信息,请参阅varun-anand.com/preempt.htmlwww.versalogic.com/mediacenter/whitepapers/wp_linux_rt.asp

获得了新应用和配置的实时可抢占内核补丁的内核映像后,需要引导它以确保活动被适当地完成,以便最终结果可以被使用。使用uname –a命令,patch rt*修订号是可见的,并且应该应用于内核版本。当然,还有其他方法可以用来识别这些信息。uname –a命令的替代方法是dmesg命令,其输出字符串应该可见实时抢占支持,但只需要一种方法就足够了。以下图像提供了uname –a命令输出应该是什么样子的表示:

应用 PREEMPT_RT 补丁

查看进程列表时,可以看到,如前所述,IRQ 处理程序是使用内核线程处理的。由于它被放置在方括号之间,这些信息在下一个ps命令输出中是可见的。单个 IRQ 处理程序由类似于用户空间的task_struct结构表示,使它们可以很容易地从用户空间进行控制:

ps ax 
PID TTY      STAT   TIME COMMAND 
1 ?        S      0:00 init [2] 
2 ?        S      0:00 [softirq-high/0] 
3 ?        S      0:00 [softirq-timer/0] 
4 ?        S      0:00 [softirq-net-tx/] 
5 ?        S      0:00 [softirq-net-rx/] 
6 ?        S      0:00 [softirq-block/0] 
7 ?        S      0:00 [softirq-tasklet] 
8 ?        S      0:00 [softirq-hrtreal] 
9 ?        S      0:00 [softirq-hrtmono] 
10 ?        S<     0:00 [desched/0] 
11 ?        S<     0:00 [events/0] 
12 ?        S<     0:00 [khelper] 
13 ?        S<     0:00 [kthread] 
15 ?        S<     0:00 [kblockd/0] 
58 ?        S      0:00 [pdflush] 
59 ?        S      0:00 [pdflush] 
61 ?        S<     0:00 [aio/0] 
60 ?        S      0:00 [kswapd0] 
647 ?        S<     0:00 [IRQ 7] 
648 ?        S<     0:00 [kseriod] 
651 ?        S<     0:00 [IRQ 12] 
654 ?        S<     0:00 [IRQ 6] 
675 ?        S<     0:09 [IRQ 14] 
687 ?        S<     0:00 [kpsmoused] 
689 ?        S      0:00 [kjournald] 
691 ?        S<     0:00 [IRQ 1] 
769 ?        S<s    0:00 udevd --daemon 
871 ?        S<     0:00 [khubd] 
882 ?        S<     0:00 [IRQ 10] 
2433 ?        S<     0:00 [IRQ 11] 
[...] 

需要收集的下一个信息涉及中断过程条目的格式,这些条目与普通内核使用的条目有些不同。可以通过检查/proc/interrupts文件来查看此输出:

cat /proc/interrupts 
CPU0 
0:     497464  XT-PIC         [........N/  0]  pit 
2:          0  XT-PIC         [........N/  0]  cascade 
7:          0  XT-PIC         [........N/  0]  lpptest 
10:          0  XT-PIC         [........./  0]  uhci_hcd:usb1 
11:      12069  XT-PIC         [........./  0]  eth0 
14:       4754  XT-PIC         [........./  0]  ide0 
NMI:          0 
LOC:       1701 
ERR:          0 
MIS:          0 

然后,第四列中提供的信息提供了 IRQ 线通知,例如:[........N/ 0]。在这里,每个点代表一个属性,每个属性都是一个值,如下所述。它们的出现顺序如下:

  • I (IRQ_INPROGRESS): 这指的是活动的 IRQ 处理程序

  • D (IRQ_DISABLED): 这表示 IRQ 被禁用了

  • P (IRQ_PENDING): 这里的 IRQ 被表示为处于挂起状态

  • R (IRQ_REPLAY): 在此状态下,IRQ 已被回复,但尚未收到 ACK

  • A (IRQ_AUTODETECT): 这表示 IRQ 处于自动检测状态

  • W (IRQ_WAITING): 这指的是 IRQ 处于自动检测状态,但尚未被看到

  • L (IRQ_LEVEL): IRQ 处于电平触发状态

  • M (IRQ_MASKED): 这表示 IRQ 不再被视为被屏蔽的状态

  • N (IRQ_NODELAY): 这是 IRQ 必须立即执行的状态

在上面的示例中,可以看到多个 IRQ 被标记为可见和在内核上下文中运行的硬 IRQ。当 IRQ 状态标记为IRQ_NODELAY时,它向用户显示 IRQ 的处理程序是一个内核线程,并且将作为一个内核线程执行。IRQ 的描述可以手动更改,但这不是本文将描述的活动。

注意

有关如何更改进程的实时属性的更多信息,一个很好的起点是chrt工具,可在linux.die.net/man/1/chrt上找到。

Yocto 项目-rt 内核

在 Yocto 中,应用了带有 PREEMPT_RT 补丁的内核配方。目前,只有两个配方包含了 PREEMPT_RT 补丁;两者都在 meta 层中可用。涉及内核版本 3.10 和 3.14 的配方及其命名为linux-yocto-rt_3.10.bblinux-yocto-rt_3.14.bb。命名中的–rt表示这些配方获取了 Yocto 社区维护的 Linux 内核版本的 PREEMPT_RT 分支。

这里呈现了 3.14 内核配方的格式:

cat ./meta/recipes-kernel/linux/linux-yocto-rt_3.14.bb
KBRANCH ?= "standard/preempt-rt/base"
KBRANCH_qemuppc ?= "standard/preempt-rt/qemuppc"

require recipes-kernel/linux/linux-yocto.inc

SRCREV_machine ?= "0a875ce52aa7a42ddabdb87038074381bb268e77"
SRCREV_machine_qemuppc ?= "b993661d41f08846daa28b14f89c8ae3e94225bd"
SRCREV_meta ?= "fb6271a942b57bdc40c6e49f0203be153699f81c"

SRC_URI = "git://git.yoctoproject.org/linux-yocto-3.14.git;bareclone=1;branch=${KBRANCH},meta;name=machine,meta"

LINUX_VERSION ?= "3.14.19"

PV = "${LINUX_VERSION}+git${SRCPV}"

KMETA = "meta"

LINUX_KERNEL_TYPE = "preempt-rt"

COMPATIBLE_MACHINE = "(qemux86|qemux86-64|qemuarm|qemuppc|qemumips)"

# Functionality flags
KERNEL_EXTRA_FEATURES ?= "features/netfilter/netfilter.scc features/taskstats/taskstats.scc"
KERNEL_FEATURES_append = " ${KERNEL_EXTRA_FEATURES}"
KERNEL_FEATURES_append_qemux86=" cfg/sound.scc cfg/paravirt_kvm.scc"
KERNEL_FEATURES_append_qemux86=" cfg/sound.scc cfg/paravirt_kvm.scc"
KERNEL_FEATURES_append_qemux86-64=" cfg/sound.scc"

如图所示,似乎有一个重复的行,需要打补丁来删除它:

commit e799588ba389ad3f319afd1a61e14c43fb78a845
Author: Alexandru.Vaduva <Alexandru.Vaduva@enea.com>
Date:   Wed Mar 11 10:47:00 2015 +0100

    linux-yocto-rt: removed duplicated line

    Seemed that the recipe contained redundant information.

    Signed-off-by: Alexandru.Vaduva <Alexandru.Vaduva@enea.com>

diff --git a/meta/recipes-kernel/linux/linux-yocto-rt_3.14.bb b/meta/recipes-kernel/linux/linux-yocto-rt_3.14.bb
index 7dbf82c..bcfd754 100644
--- a/meta/recipes-kernel/linux/linux-yocto-rt_3.14.bb
+++ b/meta/recipes-kernel/linux/linux-yocto-rt_3.14.bb
@@ -23,5 +23,4 @@ COMPATIBLE_MACHINE = "(qemux86|qemux86-64|qemuarm|qemuppc|qemumips)"
 KERNEL_EXTRA_FEATURES ?= "features/netfilter/netfilter.scc features/taskstats/taskstats.scc"
 KERNEL_FEATURES_append = " ${KERNEL_EXTRA_FEATURES}"
 KERNEL_FEATURES_append_qemux86=" cfg/sound.scc cfg/paravirt_kvm.scc"
-KERNEL_FEATURES_append_qemux86=" cfg/sound.scc cfg/paravirt_kvm.scc"
 KERNEL_FEATURES_append_qemux86-64=" cfg/sound.scc"

前面的配方与基本配方非常相似。这里,我指的是linux-yocto_3.14.bb;它们是应用了 PREEMPT_RT 补丁的配方。它们之间的区别在于每个配方都来自其特定的分支,到目前为止,没有一个带有 PREEMPT_RT 补丁的 Linux 内核版本为qemumips64兼容的机器提供支持。

PREEMPT_RT 补丁的缺点

Linux 是一个针对吞吐量进行优化的通用操作系统,这与实时操作系统的要求完全相反。当然,它通过使用大型、多层缓存提供了高吞吐量,这对于硬实时操作过程来说是一场噩梦。

要实现实时 Linux,有两种可用的选项:

  • 第一种方法涉及使用 PREEMPT_RT 补丁,通过最小化延迟并在线程上下文中执行所有活动来提供抢占。

  • 第二种解决方案涉及使用实时扩展,这些扩展充当 Linux 和用于管理实时任务的硬件之间的层。这第二种解决方案包括前面提到的 RTLinux、RTAI 和 XENOMAI 解决方案,以及其他商业解决方案和涉及移动层并将其分离为多个组件的变体。

第二个选项的变体意味着各种解决方案,从为实时活动隔离核心到为此类任务分配核心。还有许多解决方案涉及使用虚拟化程序或在 Linux 内核下方提供一定数量的中断服务给 RTOS。这些替代方案的存在不仅为读者提供了其他选项,也是因为 PREEMPT_RT 补丁有其缺点。

一个显著的缺点是通过强制内核在出现更高优先级任务时抢占任务来减少延迟。当然,这会降低系统的吞吐量,因为它不仅在进程中增加了一些上下文切换,而且使较低优先级的任务等待时间比正常的 Linux 内核更长。

preempt-rt补丁的另一个缺点是需要将其从一个内核版本移植到另一个内核版本,并从一个架构或软件供应商调整到另一个。这仅意味着特定供应商应该内部具备 Linux 内核的知识,并且应该为其每个可用的内核调整解决方案。这一事实使得它对 BSP 或 Linux 操作系统提供商来说不太受欢迎。

有关 Linux 抢占的一个有趣演示可在以下链接中找到。可咨询此链接以获取有关 Linux 实时解决方案的更多信息,网址为www.slideshare.net/jserv/realtime-linux

Linux 实时应用程序

拥有实时操作系统并不总是对每个人都足够。有些人还需要在操作系统上运行经过实时优化的应用程序。为了确保可以设计和与实时应用程序交互,操作系统和硬件上都需要确定性。就硬件配置而言,要求涉及低延迟中断处理。导致 ISR 延迟的机制应该在几十微秒左右。

关于实时应用程序所需的内核配置,需要以下配置:

  • 按需 CPU 缩放:使用此配置有助于在 CPU 处于低功耗模式时创建长延迟事件。

  • NOHZ:此配置禁用 CPU 接收的定时器中断。启用此选项后,CPU 唤醒所花费的延迟将减少。

要编写应用程序,需要注意一些事项,例如确保禁用交换以减少页面错误引起的延迟。全局变量或数组的使用应尽量减少。99 优先级号未配置为运行应用程序,而是使用优先级继承 futexes 而不是其他自旋锁。还要避免输入/输出操作和应用程序之间的数据共享。

对于设备驱动程序,建议有所不同。之前我们提到实时内核的中断处理是在线程上下文中进行的,但硬件中断上下文仍然可以在这里发挥作用。为了从中断处理程序中识别硬件中断上下文,可以使用IRQF_NODELAY标志。如果使用IRQF_NODELAY上下文,请确保避免使用wake_up()up()complete()等函数。

基准测试

Linux 操作系统长期以来被视为 GPOS,但在过去几年中,一些项目试图通过修改 Linux 内核成为 RTOS 来改变这一点。其中一个项目是之前提到的 PREEMPT_RT 补丁。

在本章的这一部分,我将讨论一系列测试,这些测试可以针对 Linux OS 的两个版本执行,无论是否应用了 PREEMPT_RT 补丁。我应该提到,对于那些对一些实际结果感兴趣的人,有许多可用的论文试图调查 PREEMPT_RT 的延迟效应或其优缺点。其中一个例子可在www.versalogic.com/downloads/whitepapers/real-time_linux_benchmark.pdf找到。

在继续之前,我认为有必要定义一些技术术语,以便正确理解一些信息:

  • 中断延迟:指中断生成后到中断处理程序中的执行开始之间经过的时间。

  • 调度延迟:表示事件唤醒信号和调度程序有机会为其安排线程之间的时间。也称为分派延迟

  • 最坏情况延迟:指发出需求后到接收到该需求的响应之间经过的时间。

  • 上下文切换:表示 CPU 从一个进程或线程切换到另一个进程或线程。它只发生在内核模式中。

LPPTest包含在 PREEMPT_RT 补丁中,它包含一个 Linux 驱动程序,只需更改并行端口上的位值以识别响应时间。另一个驱动程序响应位值的变化,用户空间应用程序测量结果。要执行此测试,需要两台机器:一台用于发送信号,另一台用于接收和发送响应。这一要求很严格,因为使用回环电缆可能会影响测量结果。

RealFeel是一个用于中断处理的测试。该程序使用/dev/rtc来触发周期性中断,测量一个中断到另一个中断之间的持续时间,并将其与预期值进行比较。最后,它无限期地打印与预期值的偏差,以便将这些变化导出到日志文件中以供以后处理。

Linux 实时基准测试框架(LRTB)代表了一组脚本和驱动程序,用于评估 Linux 内核的各种性能计数器,并添加了实时功能。它测量了实时补丁所施加的负载,以及它们获取更确定性中断响应的能力。

在基准测试阶段,可以使用hackbenchlmbench或甚至Ingo Molnar dohell脚本等程序。当然,还有许多其他工具可用于测试(cyclictesthourglass等)或基准测试(unixbenchcache-calibrator或任何将实时性能推至极限的其他压力测试),但我会让用户测试并应用最适合他们需求的工具。

PREEMPT_RT 补丁提高了 Linux 内核的抢占性,但这并不意味着它是最好的解决方案。如果应用领域的各个方面发生变化,PREEMPT_RT 补丁的有用性可能会有所不同。关于 PREEMPT_RT 补丁,它已经准备好在硬实时系统中使用。不能得出一个结论,但我必须承认,如果它用于维持生命或任务关键系统,它可以被认为是硬实时材料。这是每个人都要做出的决定,因此需要进行测试。支持这一观点的一个意见来自 Steven Rostedt,他是 Linux 内核开发人员,也是红帽公司实时 Linux 内核补丁稳定版本的维护者。该信息可以在www.linux.com/news/featured-blogs/200-libby-clark/710319-intro-to-real-time-linux-for-embedded-developers上找到。

注意

关于这个问题的一些有趣信息可以在elinux.org/Realtime_Testing_Best_Practices上找到。

元实时

meta-realtime层是由 WindRiver 的 Bruce Ashfield 维护的一个倡议,旨在创建一个与 Linux 内核或系统开发相关的实时活动的场所。它被创建为 PREEMPT_RT、SCHED_DEADLINE、POSIX 实时和通用操作系统和实时操作系统的替代配对的占位符,无论这涉及用户空间 RTOS、虚拟机监视程序还是 AMP 解决方案。此外,这也是系统分区、CPU 隔离和其他相关应用程序的所在地。当然,如果没有为整个 Linux 操作系统提供一些性能分析和基准测试应用程序,这一切都不会被认为是完整的。

虽然这个层描述起初听起来很激动人心,但其内容实际上非常贫乏。它只能整合一些测试工具,更准确地说,其中两个是schedtool-dlrt-app,以及额外的脚本,试图在目标机器上远程运行rt-app并收集结果数据。

第一个schedtool-dl应用是一个用于截止时间调度的调度器测试工具。它出现的原因是需要在 Linux 下更改或查询 CPU 调度策略,甚至是进程级别。它还可以用于在 SMP/NUMA 系统上锁定各种 CPU 上的进程,以避免音频/视频应用程序中的跳过,并且通常可以在高负载下保持高水平的交互和响应能力。

注意

有关schedtool-dl应用程序的更多信息可以在github.com/jlelli/schedtool-dl上找到。

下一个也是最后一个可用的应用是rt-app,它用作系统上实时负载的模拟测试应用程序。它通过在给定时间段启动多个线程来实现这一点。它支持 SCHED_FIFO、SCHED_OTHER、SCHED_RR、SCHED_DEADLINE,以及自适应服务质量架构AQuoSA)框架,这是一个旨在为 Linux 内核提供自适应服务质量QoS)的开源项目。

注意

有关rt-app应用程序和 AQuoSa 框架的更多信息可以在github.com/scheduler-tools/rt-appaquosa.sourceforge.net/上找到。

除了包含的软件包外,该层还包含一个集成了它们的镜像,但这远远不足以使该层包含实质性内容。虽然它内部并不包含大量信息,但本章将介绍该层,因为它包含了起点,并提供了迄今为止所呈现的所有信息的发展视角。当然,应该驻留在该层中的一些应用程序已经分布在多个其他层中,比如meta-linaro中可用的idlestat软件包。然而,这并不构成本解释的核心。我只想指出可以包含任何实时相关活动的最合适的地方,而在我看来,meta-realtime就是这个地方。

总结

在本章中,您对 PREEMPT_RT 和 Linux 内核实时问题的其他替代解决方案进行了简要介绍。我们还探讨了一些可用于相关实时活动的工具和应用程序。然而,如果不提及 Yocto 项目,不仅涉及到 PREEMPT_RT Linux 内核的配方,还涉及meta-realtime层的应用程序,这个介绍就不完整。开发适用于新环境的应用程序也是一个关注点,因此在Linux 实时应用程序部分解决了这个问题。最后,我希望通过本章中提供的链接来呈现这个主题的完整画面,以激发读者的好奇心。

在下一章中,将对meta-securitymeta-selinux层进行简要解释,并提供 Linux 生态系统和 Yocto 项目的安全需求的更广泛视角。还将介绍一些旨在保护我们的 Linux 系统的工具和应用程序的信息,但这还不是全部。看看下一章吧;我相信你会喜欢它。

第十一章:安全

在本章中,您将了解各种安全增强工具。我们首先来到 Linux 内核,在这里,有两个工具,SELinux 和 grsecurity,这两个工具都非常有趣,也非常必要。接下来,将解释 Yocto 项目的安全特定层。这包括包含大量工具的 meta-security 和 meta-selinux,可用于保护或审计 Linux 系统的各个组件。由于这个主题很广泛,我还会让您检查各种其他解决方案,既在 Linux 内核中实施,也在外部实施。希望您喜欢本章,并且觉得这些信息有趣且有用。

在任何操作系统中,安全性对用户和开发人员都是一个非常重要的关注点。开发人员已经开始以各种方法解决这些安全问题。这导致了许多可用操作系统的安全方法和改进。在本章中,将介绍一些安全增强工具,以及一些旨在确保各种组件(如 Linux 内核或 Yocto 项目)足够安全以供使用的策略和验证例程。我们还将看看在本章进行过程中如何处理各种威胁或问题。

SELinux 和 grsecurity 是对 Linux 内核进行的两项显著的安全改进,试图强制执行 Linux。SELinux 是一种强制访问控制(MAC)机制,提供基于身份和角色的访问控制,以及域类型强制。第二个选择 grsecurity 更类似于 ACL,并且实际上更适合支持远程连接的 Web 服务器和其他系统。关于 Linux 的安全实现以及 Yocto 项目如何处理这个领域,这些方面将在下一节中介绍。我必须承认的一件事是,在撰写本章时,Yocto 项目内部的安全处理仍然是一个年轻的项目,但我怀着热情期待看到迭代次数随着时间的推移而增加。

Linux 中的安全

在每个 Linux 系统的核心是 Linux 内核。任何能够损害或控制系统的恶意代码也会对影响 Linux 内核产生影响。因此,用户清楚地知道,拥有一个安全的内核也是方程式的重要部分。幸运的是,Linux 内核是安全的,并且具有许多安全功能和程序。所有这些背后的人是 James Morris,Linux 内核安全子系统的维护者。甚至还有一个专门的 Linux 存储库,可以在git.kernel.org/?p=linux/kernel/git/jmorris/linux-security.git;a=summary上访问。此外,通过检查kernsec.org/wiki/index.php/Main_Page,即 Linux 内核安全子系统的主页,您可以看到在该子系统内部管理的确切项目,并且如果感兴趣,也许可以帮助他们。

还有一个工作组,为 Linux 内核提供安全增强和验证,以确保其安全性,并在 Linux 生态系统的安全性方面保持一定水平的信任。他们的活动包括但不限于对各种漏洞进行验证和测试,或开发辅助安全 Linux 内核的工具。该工作组还包括对安全子系统的指导和维护,或者对各种项目或构建工具添加的安全改进。

所有其他 Linux 软件组件都有自己的安全团队。当然,有些软件组件没有明确定义这些团队,或者有一些与此主题相关的内部规则,但它们仍然意识到围绕其组件发生的安全威胁,并尝试修复这些漏洞。Yocto 项目试图帮助解决这些问题,并在某些方面统一这些软件组件。我希望在这个领域的一些改进会在未来几年内实现。

SELinux

SELinux 是 Linux 内核的安全增强功能,由国家安全局信息保障办公室开发。它具有基于策略的架构,是建立在Linux 安全模块LSM)接口上的 Linux 安全模块之一,旨在实现军事级别的安全性。

目前,它已经随着大量的发行版一起发布,包括最知名和经常使用的发行版,如 Debian、SuSe、Fedora、Red Hat 和 Gentoo。它基于 MAC,管理员可以控制系统用户空间组件的所有交互。它使用最小权限的概念:在这里,默认情况下,用户和应用程序没有权限访问系统资源,因为所有这些权限都是由管理员实体授予的。这构成了系统安全策略的一部分,其重点显示在以下图中:

SELinux

SELinux 内部的基本功能通过 MAC 的实现进行了隔离。在沙盒中,每个应用程序只允许执行其设计为在安全策略中定义的任务。当需要访问时,当然,标准的 Linux 权限仍然适用于系统,并且在策略之前将进行咨询。如果没有权限可用,SELinux 将无法以任何方式影响系统。但是,如果权限允许访问,则应咨询 SELinux 策略以提供最终的许可或拒绝访问的裁决。

在 SELinux 的上下文中,访问决策是基于主体的安全上下文进行的。这可能是与特定用户上下文相关联的进程,该进程与实际尝试的操作(例如文件读取操作)进行比较,以及可用对象的安全上下文,该对象可以是文件。

在继续之前,我们将看看如何在 Ubuntu 机器上启用 SELinux 支持。我将首先介绍一些与 SELinux 相关的基本概念:

  • 用户:在 SELinux 上下文中,用户与 UNIX 上下文中的用户不同。它们之间的主要区别在于,在 SELinux 上下文中,用户在用户会话期间不会改变,并且有可能有更多的 UNIX 用户在相同的 SELinux 用户上下文中操作。然而,也有可能进行 1:1 用户映射的操作,例如 Linux 根用户和 SELinux 根用户。通常,SELinux 用户的命名中会添加_u后缀。

  • 角色:SELinux 用户可以拥有一个或多个角色。角色的含义在策略中定义。对象通常具有object_r角色,角色通常以_r字符串结尾。

  • 类型:这是应用授权决策的主要方法。它也可以被称为域,通常以_t结尾。

  • 上下文:每个进程和对象都有自己的上下文。实际上,它是一个属性,确定是否应该允许对象和进程之间的访问。SELinux 上下文表示为三个必需字段和一个可选字段,例如user:role:type:range。前三个字段代表 SELinux 用户、角色和类型。最后一个代表 MLS 的范围,稍后将介绍。有关 MLS 的更多信息,请参阅web.mit.edu/rhel-doc/5/RHEL-5-manual/Deployment_Guide-en-US/sec-mls-ov.html

  • 对象类:一个 SELinux 对象类表示可用对象的类别。类别,如dir表示目录,file表示文件,还有一组与它们相关的权限。

  • 规则:这些是 SELinux 的安全机制。它们被用作一种强制执行,并且是使用对象和进程的类型来指定的。规则通常说明了一个类型是否被允许执行各种操作。

如前所述,SELinux 非常出名和受人赞赏,以至于它被包含在大多数可用的 Linux 发行版中。它的成功也通过大量关于这个主题的书籍得到了证明。有关更多信息,请参阅www.amazon.com/s/ref=nb_ss_gw/102-2417346-0244921?url=search-alias%3Daps&field-keywords=SELinux&Go.x=12&Go.y=8&Go=Go。说到这一点,让我们来看看在 Ubuntu 主机上安装 SELinux 所需的步骤。第一步是安装 SELinux 软件包:

sudo apt-get install selinux

安装软件包后,需要将 SELinux 模式从禁用(不执行或记录 SELinux 策略的模式)更改为其他两个可用选项之一:

  • 强制执行:这在生产系统中最有用:
sudo sed -i 's/SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config 

  • 宽容:在此模式下,策略不会被执行。但是,任何拒绝都会被记录下来,主要用于调试活动和开发新策略时:
sudo sed -i 's/SELINUX=.*/SELINUX=permissive/' /etc/selinux/config

配置实施后,系统需要重新启动,以确保系统文件被正确标记。

有关 SELinux 的更多信息也可以在 Yocto 项目中找到。有一个专门的层专门支持 SELinux。此外,有关此工具的更多信息,建议阅读专门讨论此问题的书籍之一。如果您不喜欢这种方法,那么还有其他手册提供与 SELinux 相关的信息,可在各种发行版中找到,如 Fedora (docs.fedoraproject.org/en-US/Fedora/19/html/Security_Guide/ch09.html),Red Hat (access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/4/html/SELinux_Guide/index.html)等。

Grsecurity

Grsecurity 是一个补丁套件,根据 GNU 通用公共许可证发布,适用于 Linux 内核,并将有助于增强 Linux 的安全性。这个补丁套件提供了四个主要的好处:

  • 无需配置的操作

  • 保护免受各种地址空间更改错误的影响

  • 它包括一个访问控制列表系统和一些相当全面的审计系统,以满足各种需求

  • 它能够与多个操作系统和处理器架构进行交互

grsecurity 软件是免费的,其开发始于 2001 年,首先从 Openwall 项目移植了一些增强安全性的补丁。它首次发布于 2.4.1 Linux 内核版本,自那时以来,开发一直在进行。随着时间的推移,它包括了一个 PaX 捆绑补丁,提供了保护内存页面的可能性。这是通过使用最小特权方法来完成的,这意味着在执行程序时,应该采取的操作不应超过必要的行动,借助额外或更少的步骤。

注意

如果您对了解更多有关 PaX 的信息感兴趣,可以访问en.wikipedia.org/wiki/PaXpax.grsecurity.net/

Grsecurity 具有许多功能,主要适用于 Web 服务器或接受来自不受信任用户的 shell 访问的服务器。其中一个主要功能是基于角色的访问控制RBAC),它是已有的 UNIX 自主访问控制DAC)的替代方案,甚至是由 Smack 或 SELinux 提供的强制访问控制(MAC)。RBAC 的目标是提供最少特权系统,其中进程和用户只具有完成任务所需的最低特权。grsecurity 的另一个功能与加固chroot()系统调用有关,以确保消除特权升级。除此之外,还有一些其他功能,如审计和/proc限制。

我已经将 grsecurity 的功能分组保留在章节中,因为我认为了解其功能将有助于用户和开发人员在需要安全解决方案时做出正确的决定。以下是所有 grsecurity 功能的列表:

  • 内存损坏防御:

  • 自动响应暴力利用

  • 针对喷洒攻击的加固 BPF JIT

  • 加固的用户空间内存权限

  • 线程堆栈之间的随机填充

  • 防止内核直接访问用户空间

  • 行业领先的 ASLR

  • 内核边界检查复制到/从用户空间

  • 文件系统加固:

  • Chroot 加固

  • 消除针对管理员终端的侧信道攻击

  • 防止用户欺骗 Apache 访问其他用户文件

  • 隐藏非特权用户的进程

  • 提供可信路径执行

  • 其他保护:

  • 防止基于 ptrace 的进程窥探

  • 防止无法读取的二进制文件转储

  • 防止攻击者自动加载易受攻击的内核模块

  • 拒绝访问过于宽松的 IPC 对象

  • 强制一致的多线程特权

  • RBAC:

  • 直观的设计

  • 自动完整系统策略学习

  • 自动策略分析

  • 人类可读的策略和日志

  • 与 LSM 堆叠

  • 非常规功能

  • GCC 插件:

  • 防止大小参数中的整数溢出

  • 防止从先前的系统调用中泄漏堆栈数据

  • 在早期引导和运行时增加熵

  • 随机化内核结构布局

  • 使只读敏感内核结构

  • 确保所有内核函数指针指向内核

牢记 grsecurity 的功能,我们现在可以进入 grsecurity 的安装阶段和其名为gradm的管理员。

需要做的第一件事是获取相应的软件包和补丁。如下所示,启用 grsecurity 的内核版本为3.14.19

wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.14.19.tar.gz
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.14.19.tar.sign
wget http://grsecurity.net/stable/gradm-3.1-201502222102.tar.gz
wget http://grsecurity.net/stable/gradm-3.1-201502222102.tar.gz.sig
wget http://grsecurity.net/stable/grsecurity-3.1-3.14.36-201503182218.patch
wget http://grsecurity.net/stable/grsecurity-3.1-3.14.36-201503182218.patch.sig

软件包可用后,需要检查其签名。Linux 内核的签名检查过程很大,与其他系统不同,如下所示:

wget http://grsecurity.net/spender-gpg-key.asc
sudo gpg --import spender-gpg-key.asc
sudo gpg --verify gradm-3.1-201502222102.tar.gz.sig
sudo gpg --verify grsecurity-3.1-3.14.35-201503092203.patch.sig
gzip -d linux-3.14.19.tar.gz
sudo gpg --verify linux-3.14.19.tar.sign

第一次调用此命令时,不会验证签名,但 ID 字段将可供以后使用。它用于从 PGP 密钥服务器识别公钥:

gpg: Signature made Mi 17 sep 2014 20:20:53 +0300 EEST using RSA key ID 6092693E
sudo gpg --keyserver hkp://keys.gnupg.net --recv-keys 6092693E
sudo gpg --verify linux-3.14.19.tar.sign

在所有软件包都可用且经过适当验证后,我们现在可以进入内核配置阶段。第一步是修补过程,使用 grsecurity 补丁完成,但这首先需要访问 Linux 内核源代码:

tar xf linux-3.14.19.tar 
cd linux-3.14.19/
patch -p1 < ../grsecurity-3.1-3.14.35-201503092203.patch

在修补过程中,源代码中缺少include/linux/compiler-gcc5.h,因此需要跳过此部分。然而,在此之后,修补过程顺利完成。完成此步骤后,配置阶段可以继续。有一些通用配置应该可以在不进行任何额外修改的情况下工作,但对于每个发行版,总会有一些特定的配置可用。可以使用以下命令来查看它们,并确保每个配置与您的硬件匹配:

make menuconfig

如果您是第一次调用它,前面的命令会有一个警告消息,提示您如下:

HOSTCC  scripts/basic/fixdep
HOSTCC  scripts/kconfig/conf.o
 *** Unable to find the ncurses libraries or the
 *** required header files.
 *** 'make menuconfig' requires the ncurses libraries.
 *** 
 *** Install ncurses (ncurses-devel) and try again.
 *** 
make[1]: *** [scripts/kconfig/dochecklxdialog] Error 1
make: *** [menuconfig] Error 2

可以通过安装libncurses5-dev软件包来解决这个问题,使用以下命令:

sudo apt-get install libncurses5-dev

有了这些问题解决后,配置过程可以继续。grsecurity选项位于安全选项子菜单中,如下截图所示:

Grsecurity

grsecurity选项中,还有两个子菜单选项。有关此的更多详细信息可以在以下截图中看到:

Grsecurity

第一个选项是配置方法,可以是自定义自动

Grsecurity

第二个选项是实际可用的配置选项:

Grsecurity

注意

有关 Grsecurity 和 PaX 配置选项的更多信息可以在en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options找到。

我想提供的一个建议是,首先启用自动配置方法,然后再进行自定义配置,以微调 Grsecurity 和 PaX 设置(如果需要)。另一个提示是启用Grsecurity | 自定义配置 | Sysctl 支持选项,因为它提供了在不重新编译内核的情况下更改 grsecurity 选项的可能性。当然,如果选择了自动配置方法,则此选项默认启用。审计选项会产生大量日志,为了防止日志泛滥,请确保Grsecurity | 自定义配置 | 日志选项也已启用。

grsecurity 家族的下一个工具是gradm管理员,它是 ACL 的强大解析器,也对其进行优化。为了确保可以安装此实用程序,安装过程要求gradm的主机操作机器提供 grsecurity 支持,否则编译过程将失败。在安装gradm之前还需要一些其他软件包:lexflexbyaccbison,甚至pam(如果需要)。

一旦满足了所有依赖关系,安装过程就可以开始了。我想给你的最后一点信息是,如果您使用的发行版带有对 grsecurity 补丁的内核支持,那么您可能首先要检查它,因为补丁也可能预先安装了gradm实用程序。

注意

有关 Grsecurity 管理的更多信息可以在以下链接找到:

en.wikibooks.org/wiki/Grsecurity/The_Administration_Utility

en.wikibooks.org/wiki/Grsecurity/Additional_Utilities

en.wikibooks.org/wiki/Grsecurity/Runtime_Configuration

在 Yocto 层中,支持meta-oe层内的gradm配方。它位于主分支的recipes-support/gradm/gradm_3.0.bb。此外,meta-accel层的主分支上提供了 grsecurity 内核配置;配置片段的确切位置是recipes-kernel/linux/linux-yocto-iio/grsec.cfg。对于任何对 Yocto 中提供的具体 grsecurity 支持感兴趣的人,我相信你可以开始着手做这件事。不过,我建议你首先向 Yocto 项目社区询问是否已经有人开始做这件事。

Yocto 项目的安全性

在 Yocto 项目中,安全问题仍然很年轻。由于该项目宣布不到五年,讨论安全问题是很正常的,最近一年左右才开始。当然,安全团队有专门的邮件列表,其中包括来自各个公司的大量成员,但他们的工作程序还没有完全完成,因为目前仍处于进行中的状态。

安全团队成员主要开展的活动包括了解最新和最危险的安全威胁,并确保找到修复方法,即使包括自己修复并应用更改到 Yocto 的可用层内。

目前,安全活动中最耗时的是围绕 Poky 参考系统展开的,但也有各个公司采取的倡议,试图向各种 BSP 维护层或其他第三方层推送一系列补丁。对于感兴趣的人,与安全相关的讨论邮件列表是<yocto-security@yoctoproject.org>。此外,在团队形成之前,他们可以在#yocto IRC 上找到,网址是webchat.freenode.net/?channels=#yocto,甚至可以参加每两周举行一次的 Yocto 技术团队会议。

注意

有关安全团队的更多信息可以在其 Wiki 页面上找到。我鼓励所有对这个主题感兴趣的人至少访问一次wiki.yoctoproject.org/wiki/Security

Meta-security 和 meta-selinux

在这一部分,介绍了与 Linux 安全工具相关的层倡议。在这一章中,为 Linux 内核及其库提供安全和硬化工具的两个层可供使用。它们的目的是简化嵌入式设备的模式,确保它们是安全的,并可能提供类似桌面的安全级别。

由于嵌入式设备变得越来越强大,与安全相关的问题只能是自然而然的。 Yocto 项目的倡议层,我指的是 meta-security 和 meta-selinux,在简化确保安全、硬化和保护 Linux 系统的过程中迈出了另一步。与检测和修复漏洞系统一起,它们被实施在安全团队内部,并有助于在嵌入式设备上实现与桌面相同级别的安全性,并进一步推动这一理念。话虽如此,让我们继续讲解层的实际内容。

Meta-security

在 meta-security 层中,有一些工具用于保护、加固和保护嵌入式设备,这些设备可能向各种实体提供外部访问。如果设备连接到互联网或容易受到任何形式的攻击或劫持,那么 meta-security 层可能是您的第一站。通过这一层和 meta-selinux 层,Yocto 项目试图为大多数社区或嵌入式用户设备提供适当的安全级别。当然,增强对各种工具的支持或添加新工具并不是被禁止的,所以如果您感到需要或有冲动,不要犹豫,为增强工具做出您的贡献。欢迎任何新的提交或提交者-我们的社区真的很友好。

正如您已经习惯的那样,提供的工具是适用于嵌入式设备的开源软件包。在 meta-security 层中,有许多可用的软件包,每个软件包都试图提供不仅系统加固,还有安全检查、安全、端口扫描和其他针对各种安全级别的有用功能。包括以下软件包:

  • Bastille

  • Redhat-security

  • Pax-utils

  • Buck-security

  • Libseccomp

  • Ckecksecurity

  • Nikto

  • Nmap

  • Clamav

  • Isic

  • Samhain

  • Suricata

  • Tripwire

除了这些软件包,还有许多库和TOMOYO,一个用于 MAC 实现的内核安全模块,也非常有用作为系统分析工具。它于 2003 年 3 月首次发布,并由日本 NTT 数据公司赞助,直到 2012 年 3 月。

TOMOYO 的主要关注点是系统行为。为此,参与系统创建的每个进程都声明了其行为和实现目的所需的必要资源。它由两个组件组成:一个内核组件,linux-ccs,和一个用户空间组件,ccs-tools;两者都需要才能正常运行。TOMOYO 试图提供一个既实用又易于使用的 MAC 实现。最后,它希望让系统对大多数用户可用,非常适合普通用户和系统管理员。它与 SELinux 不同,因为它具有LEARNING 模式提供的自动策略配置机制;此外,它的策略语言非常容易理解。

启用保护后,TOMOYO Linux 充当一个看门狗,限制进程使用超出其最初声明的资源。其主要特点包括以下内容:

  • 系统分析

  • 提供策略生成过程中的辅助工具

  • 简单易用的语法

  • 易于使用

  • 通过 MAC 实现增强系统安全性

  • 包含少量依赖项(嵌入式 GNU C 库、libncurses 和 GNU readline 库)

  • 不修改根文件系统中已有的二进制文件

  • 自 2.6.30 版本以来,Linux 内核与 TOMOYO 内核模块合并,只需要在配置阶段启用模块即可。它起初是一个提供 MAC 支持的补丁,将其移植到主线内核需要使用LSM(Linux 安全模块)的钩子,其中还包括 SELinux、AppArmor 和 SMACK。然而,由于需要更多的钩子来集成剩余的 MAC 功能,因此该项目有另外两条并行的开发线:

  • TOMOYO Linux 1.x:这是原始代码版本:

  • 它使用非标准的特定钩子

  • 它提供了所有的 MAC 功能

  • 它作为内核的补丁发布,因为它不依赖于 LSM

  • 其最新版本为 1.7.1

  • TOMOYO Linux 2.x:这是主线源代码版本:

  • 它使用标准的 LSM 钩子

  • 它包含了更少的功能子集

  • 它是 2.6.30 Linux 内核版本的一个组成部分

  • 最新版本是 2.5.0,支持 Linux 内核版本 3.2

  • AKARI 和 TOMOYO 1.x 分支版本

  • 它还使用标准的 LSM 钩子

  • 它的特点是与 TOMOYO 1.x 相比具有较少的功能,但与 TOMOYO 2.x 不同。

  • 它作为 LSM 发布;不需要重新编译内核

注意

对于那些对三个版本进行比较感兴趣的人,请参阅akari.sourceforge.jp/comparison.html.en

下一个软件包是samhain,这是一个系统完整性监控和报告工具,由系统管理员使用,用于怀疑系统上的更改或活动。它的操作基于客户端/服务器环境,并能够监视多个主机,同时提供集中的维护和日志记录系统。除了已经宣传的功能外,它还能提供端口监控、检测恶意 SUID、rootkit 检测,以及隐藏进程,这使得它支持多个平台;这是一个非常有趣的工具。

这里的下一个元素属于与samhain相同的类别,称为tripwire。这是另一个完整性工具,但它试图检测文件系统对象的更改,并作为主机入侵检测系统工作。在每次文件扫描后,信息都存储在数据库中,并与已有结果进行比较。任何进行的更改都会向用户报告。

Bastille是一个用于保护 Unix 主机环境和系统的加固程序。它使用规则来实现其目标,首先通过调用bastille -c命令,让您通过一长串问题。回答完后,将创建并执行一个配置文件,这意味着您的操作系统现在根据您的需求已经加固。如果系统上已经有一个配置文件,可以通过调用bastille -b来设置系统加固。

下一个工具是redhat-security,它是一组用于与安全扫描相关的各种问题的脚本集合。以下是运行redhat-security脚本所需的工具集合,只需在终端中调用一个脚本:

  • find-chroot.sh:此工具扫描整个系统以查找调用chroot并包括对chdir的调用的 ELF 文件。未通过此测试的程序不包含chroot内的cwd,它们不受保护,不安全。

  • find-chroot-py.sh:此工具类似于前面的工具,但仅测试 Python 脚本。

  • rpm-chksec.sh:此工具接受一个 rpm 文件,并检查其编译标志。出于安全原因进行此操作。如果结果是绿色,则一切正常,黄色表示可以接受,红色需要用户注意。

  • find-nodrop-groups.sh:此工具扫描整个系统,查找在不调用setgroupsinitgroups调用的情况下更改 UID 或 GID 的程序。

  • rpm-drop-groups.sh:此工具类似于前一个工具,但这个工具使用可用的 RPM 文件。

  • find-execstack.sh:此工具扫描整个系统以查找将堆栈标记为可执行的 ELF 文件。它用于识别易受堆栈缓冲区溢出攻击的程序。

  • find-sh4errors.sh:此工具扫描整个系统以查找 shell 脚本,并使用sh -n命令检查其正确性。

  • find-hidden-exec.sh:此工具扫描系统以查找隐藏的可执行文件,并将结果报告给用户进行调查。

  • selinux-ls-unconfined.sh:此工具用于扫描所有运行中的进程,并查找其中的initrc_t标签或inetd(这意味着它们是运行不受限制的守护进程)。问题应报告为 SELinux 策略问题。

  • selinux-check-devides.sh:此工具检查所有可用设备,以查看它们是否正确标记。它也被标记为应该解决的 SELinux 策略问题。

  • find-elf4tmp.sh:此工具扫描整个系统,并检查所使用的tmp文件是否为众所周知,是否使用mktemp创建,或者是否具有某种模糊的格式。

  • find-sh3tm.sh:此工具还扫描文件系统,尽管仅在/tmp内部查找 ELF 文件。当找到它们时,它会检查是否通过调查符号表对它们中的任何随机名称生成器函数进行了调用。如果结果是肯定的,它将输出字符串值。

  • lib-bin-check.sh:此工具检查库的软件包及其包含的软件包。它基于这样一个想法,即系统上可用的二进制文件越少,系统就越安全。

另一个包含的工具是pax-utils。它还包括一些用于扫描 ELF 二进制文件的脚本,主要用于一致性检查,但这并非全部。看一下其中一些:

  • scanelf:此工具用于查找有关二进制文件的 ELF 结构的预先信息

  • dumpelf:此工具是一个用户空间实用程序,用于以等效的 C 结构转储内部 ELF 结构,用于调试或参考目的

  • pspax:此工具用于扫描/proc并列出各种可用的 ELF 类型及其对应的 PaX 标志、属性和文件名

现在,接下来要介绍的工具是一种与已经介绍的 bastille 不同的安全扫描器。与redhat-security命令类似,这个命令也执行一些脚本,并可以根据用户的需求进行配置。它适用于 Debian 和 Ubuntu 用户,在调用 buck-security 可执行文件之前,需要进行一些配置。使用export GPG_TTY=tty``来确保启用 buck-security 的所有功能,并在执行该工具之前,检查conf/buck-security.conf配置文件,以确保满足您的需求。

Suricata是一个用于网络的高性能 IDS/IPS 和安全监控引擎。它由OISFOpen Information Security Foundation)及其支持者拥有和维护。它使用HTP库,这是一个非常强大的 HTTP 解析器和标准化器,并提供一些不错的功能,如协议识别、MD5 校验和文件识别等。

另一方面,ISIC正如其名字所示,是一个 IP 堆栈完整性检查器。实际上,它是一套用于 IP 堆栈和其他堆栈(如 TCP、ICMP、UDP 等)的实用程序,用于测试防火墙或协议本身。

对于任何 Web 服务器,nikto是在您的设备上执行的工具。它是一个用于运行一系列测试的扫描程序,用于识别危险的 CGI1 或其他文件。它还为超过 1250 个服务器的过时版本和每个版本的各种漏洞提供了列表。

接下来是libseccomp库,它提供了一个易于使用的抽象接口到 Linux 内核的syscall过滤机制,称为seccomp。它通过将 BPF syscall过滤语言抽象化,并以更用户友好的格式呈现给应用程序开发人员来实现这一点。

Checksecurity是下一行的包,它使用一系列 shell 脚本和其他插件来测试对setuid程序的各种更改。使用/etc/checksecurity.conf中定义的过滤器,它扫描已挂载的文件系统,并将已有的setuid程序列表与新扫描的程序进行比较,并将更改打印给用户查看。它还提供有关这些已挂载不安全文件系统的信息。

ClamAV是 Unix 的一种命令行操作的防病毒软件。它是一个非常好的引擎,用于跟踪木马、恶意软件、病毒和其他恶意威胁的检测。它可以做很多事情,从电子邮件扫描到网络扫描和端点安全。它还具有非常多功能和可扩展的守护程序、命令行扫描程序和数据库交互工具。

列表中的最后一个是网络映射器nmap)。这是最著名的安全审计工具,也是网络和系统管理员用于网络发现的工具。它用于管理服务升级计划、网络清单、监控各种服务,甚至主机的正常运行时间。

这些是 meta-security 层内支持和提供的工具。我在简洁的方式中介绍了大部分工具,目的是让它们以简单的方式对您可用。我认为对于安全问题,不应该过于复杂,只保留最适合您需求的解决方案。通过提供大量工具和软件组件,我试图做两件事:为公众提供更多的工具,并帮助您在寻求提供甚至维护安全系统的过程中做出决策。当然,鼓励好奇心,所以请确保您查看任何其他可能帮助您了解更多安全信息的工具,以及为什么它们不应该集成到 meta-security 层内。

Meta-selinux

另一个可用的安全层由 meta-selinux 层表示。这与 meta-security 不同,因为它只支持一个工具,但正如前面的工具所述,它是如此庞大和广泛,以至于它将其翅膀展开到整个系统。

该层的目的是支持 SELinux 并通过 Poky 向 Yocto Project 社区中的任何人提供使用。正如之前提到的,由于它影响整个 Linux 系统,因此该层的大部分工作都是在 bbappend 文件中完成的。我希望您喜欢使用该层内可用的功能,并且如果您认为合适,甚至可以为其做出贡献。

这一层不仅包含许多令人印象深刻的 bbappend 文件,还提供了一系列不仅可以用作 SELinux 扩展的软件包。这些软件包也可以用于其他独立的目的。meta-selinx 层中可用的软件包如下:

  • audit

  • libcap-ng

  • setools

  • swig

  • ustr

我将从audit用户空间工具开始介绍这一层,正如其名称所示,这是一个用于审计的工具,更具体地说是用于内核审计。它使用多种实用程序和库来搜索和存储记录的数据。数据是通过 Linux 内核中可用的审计子系统生成的。它被设计为一个独立的组件,但如果没有第二个安全组件可用,它就无法提供公共标准CC)或FIPS 140-2功能。

列表中的下一个元素是libcap-ng,这是一个替代库,具有简化的 POSIX 功能,可以与传统的 libcap 解决方案进行比较。它提供了分析运行应用程序并打印其功能的实用程序,或者如果它们具有开放的边界集。对于缺乏securebit的开放边界集,只有使用execve()调用才能允许保留0 UID 的应用程序保留完整的功能。通过使用 libcap-ng 库,这些具有最高权限的应用程序非常容易识别和处理。与其他工具进行交互和检测,如netcappscapfilecap

SETools是一个策略分析工具。实际上,它是 SELinux 的扩展,包含一系列库、图形工具和命令行,试图简单地分析 SELinux 策略。这个开源项目的主要工具如下:

  • apol:这是一个用于分析 SELinux 策略的工具

  • sediff:这是一个用于比较 SELinux 策略的语义差异器

  • seaudit:这是一个用于分析 SELinux 审计消息的工具

  • seaudit-report:这用于基于可用的审计日志生成高度可定制的审计报告

  • sechecker:这是一个用于对 SELinux 策略进行模块化检查的命令行工具

  • secmds:这是另一个用于访问和分析 SELinux 策略的命令行工具

接下来是SWIG简化包装器和接口生成器),这是一个软件开发工具,用于与各种目标语言一起创建高级编程环境、用户界面和其他必要的内容。它通常用于快速测试或原型设计,因为它生成了目标语言可以在 C 或 C++代码中调用的粘合剂。

最后要介绍的组件是用于 C 语言的微字符串 API,称为ustr,它与可用的 API 相比具有更低的开销。它在 C 代码中非常容易使用,因为它只包括一个头文件并且可以立即使用。与strdup()相比,对于字符串的开销从 1-9 字节的 85.45 变化到 1-198 字节的 23.85。举个简单的例子,如果一个 8 字节的存储 ustr 使用 2 字节,strdup()函数使用 3 字节。

这是其他工具和库与 SELinux 功能一起提供的地方,尽管其中一些可以作为单独的组件或与此处介绍的其他可用软件组件一起使用。这将为 SELinux 产品增加更多价值,因此在同一位置找到它们似乎是公平的。

对于那些有兴趣获得 SELinux 增强发行版的人,您可以选择在 meta-selinux 层中使用两个可用的映像之一:core-image-selinux-minimal.bbcore-image-selinux.bb。另一种选择是根据开发人员的需求将其中一个可用的 SELinux 特定定义的软件包组,packagegroup-selinux-minimalpackagegroup-core-selinux,合并到新定义的映像中。在做出这个选择并相应地进行配置之后,唯一剩下的就是为所选择的映像调用bitbake,在构建过程结束时,将会显示一个启用了 SELinux 支持的自定义 Linux 发行版,并且如果需要,可以进一步进行调整。

总结

在本章中,您将了解有关内核特定安全项目和外部项目的信息。其中大多数以不好的方式呈现。您还将获得有关各种安全子系统和子组如何跟上各种安全威胁和安全项目实施的信息。

在下一章中,我们将继续探讨另一个有趣的主题。在这里,我指的是虚拟化领域。您将在稍后了解更多关于元虚拟化方面的内容,以及各种虚拟化实现,例如 KVM,在过去几年中已经积累了大量经验,并已经确立了自己的标准。我将让下一章中将介绍的其他元素成为一个秘密。现在让我们进一步探索本书的内容。

第十二章:虚拟化

在本章中,您将了解到 Linux 虚拟化部分出现的各种概念。正如一些人可能知道的那样,这个主题非常广泛,仅选择一些组件进行解释也是一个挑战。我希望我的决定能够让大多数对这个领域感兴趣的人满意。本章提供的信息可能并不适合每个人的需求。因此,我附上了多个链接,以获取更详细的描述和文档。我鼓励您在必要时开始阅读并了解更多。我知道我无法用几句话包含所有必要的信息。

在任何 Linux 环境中,Linux 虚拟化并不是一件新事物。它已经存在了十多年,并且以一种非常迅速和有趣的方式发展。现在的问题不再围绕虚拟化作为解决方案,而更多地是关于部署虚拟化解决方案和虚拟化什么。

当然,也有一些情况下虚拟化并不是解决方案。在嵌入式 Linux 中,有一大类领域不适用虚拟化,主要是因为一些工作负载更适合在硬件上运行。然而,对于那些没有这种要求的领域,使用虚拟化有相当多的优势。本章将讨论有关各种虚拟化策略、云计算和其他相关主题的更多信息,让我们来看看。

Linux 虚拟化

当人们看到虚拟化时,首先看到的好处是服务器利用率的提高和能源成本的降低。使用虚拟化,服务器上的工作负载得到了最大化,这与硬件只使用计算能力的一小部分的情况截然不同。它可以减少与各种环境的交互复杂性,同时还提供了一个更易于使用的管理系统。如今,由于大多数工具提供的可扩展性,与大量虚拟机一起工作并不像与其中几个交互那样复杂。此外,部署时间真的已经减少了。在几分钟内,您可以取消配置和部署操作系统模板,或者创建一个虚拟环境用于虚拟设备部署。

虚拟化带来的另一个好处是灵活性。当工作负载对分配的资源来说太大时,它可以很容易地复制或移动到另一个更适合其需求的环境中,无论是在相同的硬件上还是在更强大的服务器上。对于基于云的解决方案,这里的可能性是无限的。限制可能是由云类型所施加的,基于是否有可用于主机操作系统的工具。

随着时间的推移,Linux 能够为每一个需求和组织提供许多出色的选择。无论您的任务涉及企业数据中心中的服务器整合,还是改善小型非营利组织的基础设施,Linux 都应该有一个适合您需求的虚拟化平台。您只需要弄清楚在哪里以及应该选择哪个项目。

虚拟化是广泛的,主要是因为它包含了广泛的技术范围,而且大部分术语都没有明确定义。在本章中,您将只了解与 Yocto 项目相关的组件,以及我个人感兴趣的一个新倡议。这个倡议试图使网络功能虚拟化NFV)和软件定义网络SDN)成为现实,被称为NFV 开放平台OPNFV)。这里将对其进行简要介绍。

SDN 和 NFV

我决定从这个话题开始,因为我相信这个领域的所有研究都开始得到各种领域和行业的开源倡议的支持,这是非常重要的。这两个概念并不新。它们自 20 年前首次被描述以来就存在,但过去几年使它们有可能重新出现为真实而非常可能的实现。本节的重点将放在NFV部分,因为它受到了最多的关注,并包含了各种实施提议。

NFV

NFV 是一种网络架构概念,用于将整个网络节点功能虚拟化为可以相互连接以创建通信服务的块。它不同于已知的虚拟化技术。它使用虚拟网络功能VNF),可以包含在一个或多个虚拟机中,执行不同的进程和软件组件,可用于服务器、交换机甚至云基础设施。一些例子包括虚拟化负载均衡器、入侵检测设备、防火墙等。

由于各种标准和协议需要很长时间才能达到一致性和质量,电信行业的产品开发周期非常严格和漫长。这使得快速发展的组织有可能成为竞争对手,并迫使它们改变自己的方法。

2013 年,一个行业规范组发布了一份关于软件定义网络和 OpenFlow 的白皮书。该组是欧洲电信标准化协会ETSI)的一部分,被称为网络功能虚拟化。在这份白皮书发布后,还发布了更深入的研究论文,从术语定义到各种使用案例,都有参考供应商可以考虑使用 NFV 实现。

ETSI NFV

ETSI NFV 工作组对电信行业非常有用,可以创建更灵活的开发周期,并且能够及时响应来自动态和快速变化环境的需求。SDN 和 NFV 是两个互补的概念,在这方面是关键的启用技术,并包含了电信和 IT 行业共同开发的主要技术要素。

NFV 框架包括六个组件:

  • NFV 基础设施(NFVI):它需要支持各种用例和应用。它包括为部署 VNF 创建环境的软件和硬件组件的总体。它是一个多租户基础设施,负责同时利用多种标准虚拟化技术用例。它在以下NFV 行业规范组NFV ISG)文件中有描述:

  • NFV 基础设施概述

  • NFV 计算

  • NFV 虚拟化程序域

  • NFV 基础设施网络域

以下图片展示了 NFV 基础设施的各种用例和应用领域的可视化图表。

ETSI NFV

  • NFV 管理和编排(MANO):它是负责将计算、网络和存储组件与软件实现分离的组件,借助虚拟化层。它需要管理新元素和编排它们之间的新依赖关系,这需要一定的互操作性标准和一定的映射。

  • NFV 软件架构:它涉及已实施的网络功能的虚拟化,如专有硬件设备。它意味着从硬件实施到软件实施的理解和过渡。过渡基于可以在过程中使用的各种定义的模式。

  • NFV 可靠性和可用性:这些是真正的挑战,这些组件的工作始于各种问题、用例、需求和原则的定义,并且它提出要提供与传统系统相同水平的可用性。它涉及可靠性组件,文档只是为未来的工作奠定基础。它只确定了各种问题,并指出了在设计具有弹性的 NFV 系统中使用的最佳实践。

  • NFV 性能和可移植性:总体而言,NFV 的目的是改变未来网络的工作方式。为此,它需要证明自己是行业标准的解决方案。本节解释了如何在一般 VNF 部署中应用与性能和可移植性相关的最佳实践。

  • NFV 安全性:由于它是行业的一个重要组成部分,因此它关注并且也依赖于网络和云计算的安全性,这使得确保 NFV 安全性至关重要。安全专家组专注于这些问题。

这些组件的架构如下所示:

ETSI NFV

在所有文档就位之后,需要执行一些概念验证,以测试这些组件的限制,并相应地调整理论组件。它们还出现鼓励 NFV 生态系统的发展。

注意

有关 NFV 可用概念和规范的更多信息,请参考以下链接:www.etsi.org/technologies-clusters/technologies/nfv/nfv-poc?tab=2www.etsi.org/technologies-clusters/technologies/nfv

SDN

软件定义网络SDN)是一种网络方法,通过将可用功能的抽象提供给管理员,实现了管理各种服务的可能性。这是通过将系统分离为控制平面和数据平面,并根据发送的网络流量做出决策来实现的;这代表了控制平面领域,而数据平面代表了流量的转发位置。当然,控制平面和数据平面之间需要一种通信方法,因此 OpenFlow 机制首先进入了方程式;然而其他组件也可以取代它。

SDN 的目的是提供一种可管理、成本效益高、适应性强、动态的架构,以及适用于当今动态和高带宽场景的解决方案。OpenFlow 组件是 SDN 解决方案的基础。SDN 架构允许以下内容:

  • 直接编程:控制平面是直接可编程的,因为它完全与数据平面分离。

  • 可编程配置:SDN 允许通过程序对资源进行管理、配置和优化。这些程序可以由任何人编写,因为它们不依赖于任何专有组件。

  • 灵活性:两个组件之间的抽象允许根据开发人员的需求调整网络流量。

  • 中央管理:逻辑组件可以集中在控制平面上,为其他应用程序、引擎等提供了一个网络视图。

  • 开放标准和供应商中立性:它使用开放标准实施,这简化了 SDN 的设计和操作,因为控制器提供的指令数量较少。这与其他情况相比要小得多,在其他情况下,需要处理多个供应商特定的协议和设备。

此外,考虑到传统解决方案无法满足市场需求,尤其是新兴的移动设备通信、物联网(IoT)、机器对机器(M2M)、工业 4.0 等市场都需要网络支持。考虑到各个 IT 部门进一步发展的可用预算,他们都面临着做出决定的困境。似乎移动设备通信市场都决定朝着开源的方向发展,希望这种投资能够证明其真正的能力,并带来更加光明的未来。

OPNFV

网络功能虚拟化项目的开放平台试图提供一个开源参考平台,该平台具有运营商级别的紧密集成,以便促进行业同行帮助改进和推动 NFV 概念。其目的是在已经存在的众多模块和项目之间提供一致性、互操作性和性能。该平台还将尝试与各种开源项目密切合作,并不断帮助集成,同时填补它们中任何一个留下的开发空白。

该项目预计将带来性能、可靠性、可维护性、可用性和功耗效率的提高,同时也将提供一个广泛的仪器平台。它将从开发 NFV 基础设施和虚拟化基础设施管理系统开始,其中将结合多个已有项目。其参考系统架构由 x86 架构表示。

该项目的初始重点和拟议实施可以在以下图片中查看。从这张图片中可以很容易地看出,尽管该项目自 2014 年 11 月开始,但已经有了加速的起步,并已经提出了一些实施建议。已经有许多大公司和组织开始着手他们的特定演示。OPNFV 并没有等待他们完成,已经在讨论一些拟议项目和倡议。这些旨在满足其成员的需求,并确保各种组件的可靠性,如持续集成、故障管理、测试基础设施等。以下图描述了 OPNFV 的结构。

OPNFV

该项目一直在利用尽可能多的开源项目。对这些项目所做的所有调整可以在两个地方进行。首先,如果不需要导致与其目的和路线图背道而驰的重大功能更改,可以在项目内部进行。第二个选项是对第一个选项的补充,对于不属于第一类的更改,应该在 OPNFV 项目的代码库中的某个地方包含它们。在 OPNFV 的开发周期内,没有经过适当测试的更改不应该被上游。

还需要提到的另一个重要因素是,OPNFV 不使用任何特定或额外的硬件。只要支持 VI-Ha 参考点,它就可以使用现有的硬件资源。在前面的图片中,可以看到这已经通过提供商实现,例如英特尔提供计算硬件,NetApp 提供存储硬件,Mellanox 提供网络硬件组件。

OPNFV 董事会和技术指导委员会拥有大量的开源项目。它们涵盖从基础设施即服务(IaaS)和虚拟化管理程序到 SDN 控制器等各种项目。这为大量贡献者提供了尝试一些可能没有时间或机会学习的技能的可能性。此外,更多样化的社区提供了对同一主题的更广泛的视角。

OPNFV 项目有各种各样的设备。移动部署的虚拟网络功能多种多样,其中移动网关(如 Serving Gateway(SGW)、Packet Data Network Gateway(PGW)等)和相关功能(Mobility Management Entity(MME)和网关)、防火墙或应用级网关和过滤器(Web 和电子邮件流量过滤器)用于测试诊断设备(服务级别协议(SLA)监控)。这些 VNF 部署需要易于操作、扩展,并且可以独立于部署的 VNF 类型进行演进。OPNFV 旨在创建一个平台,支持以下一系列特性和用例:

  • 需要一种常见的机制来管理 VNF 的生命周期,包括部署、实例化、配置、启动和停止、升级/降级以及最终的取消

  • 使用一种一致的机制来指定和互连 VNF、VNFC 和 PNF;这些与物理网络基础设施、网络覆盖等无关,即虚拟链路

  • 使用一种常见的机制来动态实例化新的 VNF 实例或取消足够的实例以满足当前的性能、规模和网络带宽需求

  • 使用一种机制来检测 NFVI、VIM 和基础设施的其他组件中的故障和失败,并从这些故障中恢复

  • 使用一种机制从/向虚拟网络功能源/接收流量到/从物理网络功能

  • NFVI 作为服务用于在同一基础设施上托管来自不同供应商的不同 VNF 实例

这里应该提到一些显著且易于理解的用例示例。它们分为四类。让我们从第一类开始:住宅/接入类。它可以用于虚拟化家庭环境,但也提供对 NFV 的固定访问。接下来是数据中心:它具有 CDN 的虚拟化,并提供处理它的用例。移动类别包括移动核心网络和 IMS 的虚拟化,以及移动基站的虚拟化。最后,有云类别,包括 NFVIaaS、VNFaaS、VNF 转发图(服务链)以及 VNPaaS 的用例。

注意

有关该项目和各种实施组件的更多信息,请访问www.opnfv.org/。有关缺失术语的定义,请参阅www.etsi.org/deliver/etsi_gs/NFV/001_099/003/01.02.01_60/gs_NFV003v010201p.pdf

Yocto Project 的虚拟化支持

meta-virtualization层试图创建一个长期和中期的、专门用于嵌入式虚拟化的生产就绪层。它的作用是:

  • 简化协作基准测试和研究的方式,使用 KVM/LxC 虚拟化等工具,结合先进的核心隔离和其他技术

  • 集成和贡献项目,如 OpenFlow、OpenvSwitch、LxC、dmtcp、CRIU 等,这些项目可以与其他组件一起使用,如 OpenStack 或 Carrier Graded Linux。

简而言之,这一层试图在构建基于 OpenEmbedded 和 Yocto Project 的虚拟化解决方案时提供支持。

我将简要讨论的这一层中可用的软件包如下:

  • CRIU

  • Docker

  • LXC

  • Irqbalance

  • Libvirt

  • Xen

  • Open vSwitch

这一层可以与提供各种云解决方案的云代理和 API 支持的meta-cloud-services层一起使用。在这一部分,我提到这两个层,因为我认为一起呈现这两个组件是合适的。在meta-cloud-services层中,还有一些将被讨论和简要介绍的软件包,如下所示:

  • openLDAP

  • SPICE

  • Qpid

  • RabbitMQ

  • 风暴

  • Cyrus-SASL

  • Puppet

  • oVirt

  • OpenStack

提到了这些组件,我现在将继续解释每个工具。让我们从元虚拟化层的内容开始,更确切地说是CRIU软件包,这是一个为 Linux 实现用户空间中的检查点/恢复的项目。它可以用于冻结已经运行的应用程序,并将其检查点到硬盘上作为一组文件。这些检查点可以用于从该点恢复和执行应用程序。它可以作为许多用例的一部分使用,如下所示:

  • 容器的实时迁移:这是该项目的主要用例。容器被检查点,生成的镜像被移动到另一个盒子中并在那里恢复,使整个体验对用户几乎是不可察觉的。

  • 无缝升级内核:内核替换活动可以在不停止活动的情况下进行。它可以被检查点,通过调用 kexec 替换,并且所有服务可以在之后恢复。

  • 加快启动速度慢的服务:对于启动过程缓慢的服务,可以在第一次启动完成后进行检查点,并在后续启动时从该点恢复。

  • 网络负载均衡:它是TCP_REPAIR套接字选项的一部分,并将套接字切换到特殊状态。实际上,套接字被放置在操作结束时所期望的状态中。例如,如果调用connect(),则套接字将被放置在所请求的ESTABLISHED状态中,而不会检查来自另一端的通信确认,因此卸载可以在应用程序级别进行。

  • 桌面环境的挂起/恢复:它基于这样一个事实,即屏幕会话或X应用程序的挂起/恢复操作比关闭/打开操作要快得多。

  • 高性能和计算问题:它可以用于在集群上平衡任务的负载和保存集群节点状态以防发生崩溃。对应用程序进行多个快照不会对任何人造成伤害。

  • 进程的复制:类似于远程fork()操作。

  • 应用程序的快照:一系列应用程序状态可以被保存并在必要时恢复。它可以被用作应用程序所需状态的重做,也可以用于调试目的。

  • 在没有此选项的应用程序中保存能力:这样的应用程序的一个例子可能是游戏,在达到一定级别后,建立检查点是你需要的。

  • 将遗忘的应用程序迁移到屏幕上:如果您忘记将一个应用程序包含在屏幕上,而您已经在那里,CRIU 可以帮助进行迁移过程。

  • 调试挂起的应用程序:对于因git而被卡住并需要快速重启的服务,可以使用服务的副本进行恢复。也可以使用转储过程,并通过调试找到问题的原因。

  • 在不同机器上分析应用程序行为:对于那些在不同机器上可能表现不同的应用程序,可以使用该应用程序的快照,并将其转移到另一台机器上。在这里,调试过程也可以是一个选项。

  • 干运行更新:在系统或内核更新之前,可以将其服务和关键应用程序复制到虚拟机上,系统更新并且所有测试用例通过后,才能进行真正的更新。

  • 容错系统:它可以成功用于在其他机器上复制进程。

下一个元素是irqbalance,这是一个分布式硬件中断系统,可跨多个处理器和多处理器系统使用。实际上,它是一个用于在多个 CPU 之间平衡中断的守护程序,其目的是在 SMP 系统上提供更好的性能以及更好的 IO 操作平衡。它有替代方案,如smp_affinity,理论上可以实现最大性能,但缺乏irqbalance提供的同样灵活性。

libvirt工具包可用于连接到最近的 Linux 内核版本中提供的虚拟化功能,这些功能已根据 GNU Lesser General Public License 许可。它支持大量软件包,如下所示:

  • KVM/QEMU Linux 监督员

  • Xen 监督员

  • LXC Linux 容器系统

  • OpenVZ Linux 容器系统

  • Open Mode Linux 是一个半虚拟化内核

  • 包括 VirtualBox、VMware ESX、GSX、Workstation 和 player、IBM PowerVM、Microsoft Hyper-V、Parallels 和 Bhyve 在内的虚拟机监视器

除了这些软件包,它还支持多种文件系统的存储,如 IDE、SCSI 或 USB 磁盘、FiberChannel、LVM 以及 iSCSI 或 NFS,以及虚拟网络的支持。它是其他专注于节点虚拟化的更高级别应用程序和工具的构建块,并以安全的方式实现这一点。它还提供了远程连接的可能性。

注意

有关libvirt的更多信息,请查看其项目目标和术语libvirt.org/goals.html

接下来是Open vSwitch,一个多层虚拟交换机的生产质量实现。这个软件组件根据 Apache 2.0 许可证授权,并旨在通过各种编程扩展实现大规模网络自动化。Open vSwitch软件包,也缩写为OVS,提供了硬件虚拟化的两个堆栈层,并支持计算机网络中的大量标准和协议,如 sFlow、NetFlow、SPAN、CLI、RSPAN、802.1ag、LACP 等。

Xen 是一个具有微内核设计的虚拟化程序,提供服务,可以在同一架构上执行多个计算机操作系统。它最初是在 2003 年在剑桥大学开发的,并在 GNU 通用公共许可证第 2 版下开发。这个软件运行在更高特权状态下,并可用于 ARM、IA-32 和 x86-64 指令集。

虚拟机监视器是一种软件,涉及 CPU 调度和各种域的内存管理。它是从域 0dom0)执行的,控制所有其他非特权域,称为domU;Xen 从引导加载程序引导,并通常加载到 dom0 主机域,一个半虚拟化操作系统。Xen 项目架构的简要介绍在这里:

Yocto 项目的虚拟化支持

Linux 容器LXC)是 meta-virtualization 层中提供的下一个元素。它是一组著名的工具和库,通过在 Linux 控制主机机器上提供隔离容器,以操作系统级别进行虚拟化。它结合了内核控制组cgroups)的功能与对隔离命名空间的支持,以提供一个隔离的环境。它受到了相当多的关注,主要是由于稍后将简要提到的 Docker。此外,它被认为是完整机器虚拟化的轻量级替代方案。

这两个选项,容器和机器虚拟化,都有相当多的优点和缺点。如果选择容器,它们通过共享某些组件来提供低开销,但可能会发现它的隔离效果不好。机器虚拟化恰恰相反,提供了很好的隔离解决方案,但开销更大。这两种解决方案也可以看作是互补的,但这只是我个人对这两种解决方案的看法。实际上,它们每个都有自己特定的一套优点和缺点,有时也可能是互补的。

有关 Linux 容器的更多信息,请访问linuxcontainers.org/

将讨论的meta-virtualization层的最后一个组件是 Docker,这是一款开源软件,试图自动化在 Linux 容器中部署应用程序的方法。它通过在 LXC 上提供一个抽象层来实现这一点。它的架构在这张图片中更好地描述了:

Yocto 项目的虚拟化支持

正如您在上图中所看到的,这个软件包能够使用操作系统的资源。我指的是 Linux 内核的功能,并且已经将其他应用程序从操作系统中隔离出来。它可以通过 LXC 或其他替代方案(如libvirtsystemd-nspawn)来实现,也可以直接通过libcontainer库来实现,这个库从 Docker 的 0.9 版本开始就存在了。

Docker 是一个很好的组件,如果您想要为分布式系统(如大规模 Web 部署、面向服务的架构、持续部署系统、数据库集群、私有 PaaS 等)获得自动化。有关其用例的更多信息,请访问www.docker.com/resources/usecases/。确保您查看这个网站;这里经常有有趣的信息。

有关 Docker 项目的更多信息,请访问他们的网站。在www.docker.com/whatisdocker/上查看什么是 Docker?部分。

完成meta-virtualization层后,我将转向包含各种元素的meta-cloud-services层。我将从独立计算环境的简单协议Spice)开始。这可以被翻译成用于虚拟化桌面设备的远程显示系统。

它最初是作为闭源软件开始的,在两年后决定将其开源。然后它成为了与设备交互的开放标准,无论它们是虚拟化的还是非虚拟化的。它建立在客户端-服务器架构上,使其能够处理物理和虚拟化设备。后端和前端之间的交互是通过 VD-Interfaces(VDI)实现的,如下图所示,它目前的重点是远程访问 QEMU/KVM 虚拟机:

Yocto 项目的虚拟化支持

接下来是oVirt,一个提供 Web 界面的虚拟化平台。它易于使用,并有助于管理虚拟机、虚拟化网络和存储。它的架构由 oVirt Engine 和多个节点组成。引擎是一个配备了用户友好界面的组件,用于管理逻辑和物理资源。它还运行虚拟机,这些虚拟机可以是 oVirt 节点、Fedora 或 CentOS 主机。使用 oVirt 的唯一缺点是它只支持有限数量的主机,如下所示:

  • Fedora 20

  • CentOS 6.6, 7.0

  • Red Hat Enterprise Linux 6.6, 7.0

  • Scientific Linux 6.6, 7.0

作为一个工具,它真的很强大。它与libvirt集成,用于虚拟桌面和服务器管理器VDSM)与虚拟机的通信,还支持能够实现远程桌面共享的 SPICE 通信协议。这是一个由 Red Hat 发起并主要由其维护的解决方案。它是他们Red Hat 企业虚拟化RHEV)的基本元素,但有一件有趣的事情需要注意的是,Red Hat 现在不仅是 oVirt 和 Aeolus 等项目的支持者,自 2012 年以来还是 OpenStack 基金会的白金会员。

注意

有关 oVirt、Aeolus 和 RHEV 等项目的更多信息,以下链接对您可能有用:www.redhat.com/promo/rhev3/?sc_cid=70160000000Ty5wAAC&offer_id=70160000000Ty5NAAS http://www.aeolusproject.org/,以及www.ovirt.org/Home

我现在将转向另一个组件。在这里,我指的是轻量级目录访问协议的开源实现,简称为OpenLDAP。尽管它有一个有争议的许可证,称为OpenLDAP Public License,在本质上类似于 BSD 许可证,但它没有在 opensource.org 上记录,因此未经开源倡议OSI)认证。

这个软件组件是一套元素,如下所示:

  • 一个独立的 LDAP 守护程序,扮演服务器的角色,称为slapd

  • 一些实现 LDAP 协议的库

  • 最后但同样重要的是,一系列工具和实用程序,它们之间也有一些客户端示例

还有一些应该提到的附加内容,例如用 C++编写的 ldapc++和库,用 Java 编写的 JLDAP 和库;LMDB,一个内存映射数据库库;Fortress,基于角色的身份管理;也是用 Java 编写的 SDK;以及用 Java 编写的 JDBC-LDAP 桥驱动程序,称为JDBC-LDAP

Cyrus SASL是一个用于简单认证和安全层SASL)认证的通用客户端-服务器库实现。这是一种用于为基于连接的协议添加认证支持的方法。基于连接的协议添加一个命令,用于标识和认证用户到请求的服务器,如果需要协商,还会在协议和连接之间添加一个额外的安全层,用于安全目的。有关 SASL 的更多信息,请参阅 RFC 2222,网址为www.ietf.org/rfc/rfc2222.txt

注意

有关 Cyrus SASL 的更详细描述,请参阅www.sendmail.org/~ca/email/cyrus/sysadmin.html

Qpid是 Apache 开发的消息工具,它理解高级消息队列协议AMQP),并支持各种语言和平台。AMQP 是一个设计用于在网络上以可靠的方式进行高性能消息传递的开源协议。有关 AMQP 的更多信息,请访问www.amqp.org/specification/1.0/amqp-org-download。在这里,您可以找到有关协议规范以及项目的更多信息。

Qpid 项目推动了 AMQP 生态系统的发展,通过提供消息代理和 API,可以在任何开发人员打算在其产品中使用 AMQP 消息传递的应用程序中使用。为此,可以执行以下操作:

  • 让源代码开源。

  • 使 AMQP 在各种计算环境和编程语言中可用。

  • 提供必要的工具来简化应用程序开发过程。

  • 创建一个消息基础设施,以确保其他服务可以与 AMQP 网络很好地集成。

  • 创建一个消息产品,使得与 AMQP 对于任何编程语言或计算环境来说都是微不足道的集成。确保您查看 Qpid Proton qpid.apache.org/proton/overview.html

注意

有关前述功能的更多信息,请访问qpid.apache.org/components/index.html#messaging-apis

RabbitMQ是另一个实现 AMQP 的消息代理软件组件,也可作为开源软件使用。它有一些组件,如下:

  • RabbitMQ 交换服务器

  • HTTP、流文本定向消息协议STOMP)和消息队列遥测传输MQTT)的网关

  • 各种编程语言的 AMQP 客户端库,尤其是 Java、Erlang 和.Net Framework

  • 一种用于许多自定义组件的插件平台,还提供了一系列预定义的组件:

  • 铲子:这是一个在经纪人之间执行消息复制/移动操作的插件

  • 管理:它使经纪人和经纪人集群的控制和监视成为可能

  • 联邦:它使经纪人之间在交换级别共享消息

注意

您可以通过参考 RabbitMQ 文档部分www.rabbitmq.com/documentation.html了解有关 RabbitMQ 的更多信息。

比较 Qpid 和 RabbitMQ,可以得出 RabbitMQ 更好的结论,而且它有一个很棒的文档。这使得它成为 OpenStack 基金会的首选,也是对于对这些框架感兴趣的读者来说,提供了超过这些框架的基准信息。它也可以在blog.x-aeon.com/2013/04/10/a-quick-message-queue-benchmark-activemq-rabbitmq-hornetq-qpid-apollo/找到。为了比较目的,这样的结果也可以在这个图像中找到:

Yocto 项目的虚拟化支持

下一个元素是木偶,这是一个开源的配置管理系统,允许 IT 基础设施定义某些状态,并强制执行这些状态。通过这样做,它为系统管理员提供了一个很好的自动化系统。这个项目由 Puppet Labs 开发,并在 GNU 通用公共许可证下发布,直到 2.7.0 版。之后,它移至 Apache 许可证 2.0,现在有两种风味:

  • 开源木偶版本:它与前述工具大致相似,能够提供允许定义和自动化状态的配置管理解决方案。它适用于 Linux 和 UNIX 以及 Max OS X 和 Windows。

  • 企业版木偶:这是一个商业版本,超出了开源木偶的能力,并允许自动化配置和管理过程。

这是一个工具,为系统配置定义了一个声明性语言,以供以后使用。它可以直接应用于系统,甚至可以编译为目录,并使用客户端-服务器范式部署到目标上,通常是 REST API。另一个组件是一个代理,强制执行清单中可用的资源。资源抽象当然是通过一个抽象层来完成的,该抽象层通过更高级别的术语定义配置,这些术语与操作系统特定的命令非常不同。

注意

如果您访问docs.puppetlabs.com/,您将找到与 Puppet 和其他 Puppet Lab 工具相关的更多文档。

有了这一切,我相信是时候介绍元云服务层的主要组件OpenStack了。它是一个基于控制大量组件的云操作系统,共同提供计算、存储和网络资源池。所有这些资源都通过一个仪表板进行管理,当然,这个仪表板是由另一个组件提供的,并提供管理员控制。它为用户提供了通过同一网络界面提供资源的可能性。下面是一个描述开源云操作系统 OpenStack 的图像:

Yocto 项目的虚拟化支持

它主要用作 IaaS 解决方案,其组件由 OpenStack 基金会维护,并在 Apache 许可证第 2 版下提供。在基金会中,今天有 200 多家公司为软件的源代码和一般开发和维护做出贡献。在其核心,所有组件都保持着,每个组件都有一个用于简单交互和自动化可能性的 Python 模块:

  • 计算(Nova):它用于托管和管理云计算系统。它管理环境中计算实例的生命周期。它负责根据需要生成、退役和调度各种虚拟机。在虚拟化程序方面,KVM 是首选选项,但其他选项如 Xen 和 VMware 也是可行的。

  • 对象存储(Swift):它用于通过 RESTful 和 HTTP API 进行存储和数据结构检索。它是一个可伸缩和容错系统,允许对象和文件在多个磁盘驱动器上进行数据复制。它主要由一个名为SwiftStack的对象存储软件公司开发。

  • 块存储(Cinder):它为 OpenStack 实例提供持久的块存储。它管理块设备的创建以及附加和分离操作。在云中,用户管理自己的设备,因此应支持绝大多数存储平台和场景。为此,它提供了一个可插拔的架构,简化了这个过程。

  • 网络(Neutron):它是负责网络相关服务的组件,也被称为作为服务的网络连接。它提供了一个用于网络管理的 API,并确保防止某些限制。它还具有基于可插拔模块的架构,以确保尽可能支持尽可能多的网络供应商和技术。

  • 仪表板(Horizon):它为管理员和用户提供基于 Web 的图形界面,用于与所有其他组件提供的其他资源进行交互。它还考虑了可扩展性,因为它能够与负责监控和计费的其他组件以及其他管理工具进行交互。它还提供了根据商业供应商的需求重新品牌的可能性。

  • 身份服务(Keystone):它是一个身份验证和授权服务。它支持多种形式的身份验证,还支持现有的后端目录服务,如 LDAP。它为用户和他们可以访问的资源提供了目录。

  • 镜像服务(Glance):它用于发现、存储、注册和检索虚拟机的镜像。一些已存储的镜像可以用作模板。OpenStack 还提供了一个用于测试目的的操作系统镜像。Glance 是唯一能够在各个服务器和虚拟机之间添加、删除、复制和共享 OpenStack 镜像的模块。所有其他模块都使用 Glance 的可用 API 与镜像进行交互。

  • 遥测(Ceilometer):它是一个模块,通过大量计数器的帮助,提供了跨所有当前和未来的 OpenStack 组件的计费、基准测试和统计结果,从而实现了可扩展性。这使得它成为一个非常可扩展的模块。

  • 编排器(Heat):它是一个管理多个复合云应用程序的服务,借助各种模板格式,如 Heat 编排模板(HOT)或 AWS CloudFormation。通信既可以在 CloudFormation 兼容的查询 API 上进行,也可以在 Open Stack REST API 上进行。

  • 数据库(Trove):它提供可靠且可扩展的云数据库服务功能。它使用关系型和非关系型数据库引擎。

  • 裸金属配置(Ironic):它是一个提供虚拟机支持而不是裸金属机支持的组件。它起初是作为 Nova 裸金属驱动程序的一个分支开始的,并发展成为裸金属超级监视器的最佳解决方案。它还提供了一组插件,用于与各种裸金属超级监视器进行交互。它默认与 PXE 和 IPMI 一起使用,但当然,借助可用插件的帮助,它可以为各种特定于供应商的功能提供扩展支持。

  • 多租户云消息传递(Zaqar):正如其名称所示,这是一个面向对软件即服务SaaS)感兴趣的 Web 开发人员的多租户云消息传递服务。他们可以使用它通过多种通信模式在各种组件之间发送消息。然而,它也可以与其他组件一起用于向最终用户呈现事件以及在云层进行通信。它以前的名称是Marconi,并且还提供可扩展和安全的消息传递的可能性。

  • 弹性 Map Reduce(Sahara):它是一个试图自动化提供 Hadoop 集群功能的模块。它只需要定义各种字段,如 Hadoop 版本、各种拓扑节点、硬件细节等。之后,几分钟内,一个 Hadoop 集群就部署好并准备好进行交互。它还提供了部署后的各种配置的可能性。

说了这么多,也许您不介意在下面的图像中呈现一个概念架构,以向您展示上述先前组件的交互方式。为了在生产环境中自动部署这样的环境,可以使用自动化工具,例如前面提到的 Puppet 工具。看一下这个图表:

Yocto 项目的虚拟化支持

现在,让我们继续看看如何使用 Yocto 项目的功能部署这样的系统。为了开始这项活动,应将所有所需的元数据层放在一起。除了已经可用的 Poky 存储库外,还需要其他存储库,并且它们在 OpenEmbedded 网站的层索引中定义,因为这次,README文件是不完整的:

git clone –b dizzy git://git.openembedded.org/meta-openembedded
git clone –b dizzy git://git.yoctoproject.org/meta-virtualization
git clone –b icehouse git://git.yoctoproject.org/meta-cloud-services
source oe-init-build-env ../build-controller

创建适当的控制器构建后,需要进行配置。在conf/layer.conf文件中,添加相应的机器配置,例如 qemux86-64,在conf/bblayers.conf文件中,应相应地定义BBLAYERS变量。除了已经可用的层外,还有额外的元数据层。应该在此变量中定义的层是:

  • meta-cloud-services

  • meta-cloud-services/meta-openstack-controller-deploy

  • meta-cloud-services/meta-openstack

  • meta-cloud-services/meta-openstack-qemu

  • meta-openembedded/meta-oe

  • meta-openembedded/meta-networking

  • meta-openembedded/meta-python

  • meta-openembedded/meta-filesystem

  • meta-openembedded/meta-webserver

  • meta-openembedded/meta-ruby

使用bitbake openstack-image-controller命令完成配置后,将构建控制器镜像。可以使用runqemu qemux86-64 openstack-image-controller kvm nographic qemuparams="-m 4096"命令启动控制器。完成这项活动后,可以以这种方式开始计算的部署:

source oe-init-build-env ../build-compute

有了新的构建目录,由于大部分构建过程的工作已经在控制器上完成,因此可以在它们之间共享构建目录,如downloadssstate-cache。这些信息应该通过DL_DIRSSTATE_DIR来指示。conf/bblayers.conf文件的两个之间的区别在于build-compute构建目录的第二个文件用meta-cloud-services/meta-openstack-controller-deploy替换了meta-cloud-services/meta-openstack-compute-deploy

这次构建是用bitbake openstack-image-compute完成的,应该会更快。完成构建后,可以使用runqemu qemux86-64 openstack-image-compute kvm nographic qemuparams="-m 4096 –smp 4"命令启动计算节点。这一步意味着为 OpenStack Cirros 加载镜像,如下所示:

wget download.cirros-cloud.net/0.3.2/cirros-0.3.2-x86_64-disk.img 
scp cirros-0.3.2-x86_64-disk.img  root@<compute_ip_address>:~
ssh root@<compute_ip_address>
./etc/nova/openrc
glance image-create –name "TestImage" –is=public true –container-format bare –disk-format qcow2 –file /home/root/cirros-0.3.2-x86_64-disk.img

完成所有这些后,用户可以使用http://<compute_ip_address>:8080/访问 Horizon 网页浏览器。登录信息是 admin,密码是 password。在这里,您可以玩耍,创建新实例,与它们交互,总之,做任何你想做的事情。如果您对实例做错了什么,不要担心;您可以删除它并重新开始。

meta-cloud-services层的最后一个元素是用于 OpenStack 的Tempest 集成测试套件。它通过一组测试来执行 OpenStack 主干上的测试,以确保一切都按预期工作。对于任何 OpenStack 部署来说都非常有用。

注意

有关 Tempest 的更多信息,请访问github.com/openstack/tempest

总结

在本章中,不仅介绍了一些虚拟化概念,如 NFV、SDN、VNF 等,还介绍了一些贡献于日常虚拟化解决方案的开源组件。我为您提供了示例,甚至进行了一些小练习,以确保信息在您阅读本书后仍然留在您心中。我希望我引起了一些人对某些事情的好奇心。我也希望有些人记录了这里没有介绍的项目,比如OpenDaylightODL)倡议,它只在图像中被提及作为一个实施建议。如果是这样,我可以说我实现了我的目标。如果不是,也许这个总结会让您再次翻阅前面的页面。

在下一章中,我们将访问一个新的真实的载波级产品。这将是本书的最后一章,我将以一个对我个人非常重要的主题作为总结。我将讨论 Yocto 羞涩倡议称为meta-cgl及其目的。我将介绍Carrier Graded LinuxCGL)的各种规范和变化,以及Linux Standard BaseLSB)的要求。我希望您阅读它时和我写作时一样享受。

第十三章:CGL 和 LSB

在本章中,您将了解本书最后一个主题的信息,即Carrier Grade LinuxCGL)和Linux Standard BaseLSB)倡议,当然还有与 Yocto 项目集成和支持相关的内容。这里也会提到这些标准及其规范的一些信息,以及 Yocto 为它们提供的支持水平。我还将介绍一些与 CGL 相关的倡议,如Automotive Grade LinuxCarrier Grade Virtualization。它们也构成了一系列可行的解决方案,适用于各种应用。

在今天的任何 Linux 环境中,都需要一个可用的 Linux 发行版的通用语言。如果没有定义实际的规范,这种通用语言是无法实现的。这些规范的一部分也由载波级别的替代方案代表。它与本书或其他类似书籍中已经介绍的其他规范共存。查看可用的规范和标准化只会向我们展示 Linux 生态系统随着时间的推移已经发展了多少。

Linux 基金会发布的最新报告显示了 Linux 内核的开发实际情况,工作情况,赞助情况,对其进行的更改以及发展速度。该报告可在www.linuxfoundation.org/publications/linux-foundation/who-writes-linux-2015上找到。

报告中描述,不到 20%的内核开发是由个人开发者完成的。大部分开发是由公司完成的,比如英特尔、红帽、Linaro、三星等。这意味着超过 80%的 Linux 内核开发人员是为他们的工作而获得报酬的。Linaro 和三星是提交次数最多的公司之一,这只是一种对 ARM 处理器和特别是 Android 的有利看法。

另一个有趣的信息是,超过一半的 Linux 内核开发人员是第一次提交。这意味着只有很少一部分开发者在做大部分的工作。Linux 基金会正在尝试通过为学生提供各种项目来减少 Linux 内核开发过程中的这种功能障碍。这是否成功,只有时间才能告诉我们,但我认为他们正在做正确的事情,朝着正确的方向发展。

所有这些信息都是针对 Linux 内核解释的,但其中的一部分也适用于其他开源组件。我想在这里强调的是,Linux 中的 ARM 支持比 PowerPC 或 MIPS 等架构要成熟得多。这不仅已经变得显而易见,而且也表明了英特尔 x86 阶段所采取的方法。到目前为止,这种方法还没有受到任何干扰。

Linux 标准基础

LSB 似乎降低了 Linux 平台提供的支持成本,通过减少各种可用 Linux 发行版之间的差异。它还有助于应用程序的移植成本。每当开发人员编写应用程序时,他们需要确保在一个 Linux 发行版上产生的源代码也能在其他发行版上执行。他们还希望确保这在多年内仍然可能。

LSB 工作组是 Linux 基金会的一个项目,旨在解决这些确切的问题。为此,LSB 工作组开始制定一个描述 Linux 发行版应支持的一组 API 的标准。随着标准的定义,工作组还进一步开发了一套工具和测试来衡量支持水平。通过这样做,他们能够定义一定的符合性集,并检测各种发行版之间的差异。

LSB 是 Linux 基金会在这个方向上的首次努力,成为了所有试图为 Linux 平台的各个领域提供标准化的工作组的总称。所有这些工作组都有相同的路线图,并提供相应的规范、软件组件(如符合性测试、开发工具)以及其他可用的样本和实现。

由工作组开发的每个软件组件,如果在 Linux 标准基础中可用,都被定义为lsb模块。所有这些模块都有一个共同的格式,以便更容易地进行集成。有必需的和可选的模块。必需的模块是符合 LSB 接受标准的模块。可选的模块仍在进行中,在规范定义的时候,还没有写入接受标准,但将包括在未来版本的 LSB 标准中。

当然,也有一些工作组并不生产lsb模块。他们也没有制定标准,而是在项目中集成了各种补丁,比如 Linux 内核或其他软件包,甚至是文档。这一节只考虑与 LSB 相关的工作组。

不时地,每当发布新的规范文档时,测试工具包也会提供给供应商,以测试该工具包对特定版本的合规性。供应商可以测试他们的产品的合规性,可以是一个应用程序或一个 Linux 发行版。测试工具包的结果是一个证书,表明他们的产品是 LSB 认证的。对于应用程序,我们当然有一个LSB 应用程序测试工具包。也有一个类似的工具包适用于 Linux 发行版,以及适用于各种发行版的其他工具包。

对于对可选模块感兴趣的供应商,这些模块不仅可用于帮助供应商准备未来的 LSB 合规认证,还可以让他们接触到可选模块,以便从他们那里获得更多的评价和贡献。此外,供应商的投票与这些模块在未来 LSB 规范文档中的存在有关,这一发布也很重要。供应商可以确定一个可选模块是否符合未来的纳入条件。

LSB 工作组由指导委员会管理,并由选举产生的主席领导。这两个实体代表了工作组的利益。工作组采用粗略共识模式运作。这表明了工作组对特定问题的解决方案,即由选举产生的主席确定的解决方案。如果贡献者不认同他们的决定,并且不符合达成粗略共识所需的标准,那么就会向指导委员会提出申诉。

LSB 工作组的所有业务都在一个开放的论坛内进行。它可以包括邮件列表、会议、维基页面,甚至是面对面的会议;这些活动对工作组成员都是开放的。此外,成员资格并不受限制,决定也都有明确的记录,因为随时可能会就某个特定主题进行进一步讨论。

工作组中有明确定义的角色:

  • 贡献者:这指的是积极参与的个人。他们始终有名单可供主席查阅,但任何个人都可以要求被列入贡献者名单。

  • 主席:这指的是代表项目领导者。一个人被贡献者选举到这个职位,并得到指导委员会和 Linux 基金会董事会的批准。一旦当选,他们可以连任两年。没有限制一个人可以当选的次数。如果指导委员会或 Linux 基金会董事会对其缺乏信任,就会被免职。职位空缺后,将进行新的选举。在空缺期间,指导委员会将指定一名代理主席。

  • 选举委员会:这指的是由指导委员会为主席选举成立的贡献者委员会。它负责在主席任期到期前至少 30 天或主席职位空缺后 10 天内选择主席候选人。它负责进行选举,通过电子投票进行。每个人只能投一票;投票是秘密的,只有符合条件的成员才能进行投票。投票期为一周,然后结果提交给指导委员会,由其批准并宣布获胜者。

  • 指导委员会:它由代表工作组利益相关者组成。他们可能是发行商、OEM 厂商、ISV、上游开发人员,以及 LSB 宪章下的 LSB 子工作组的主席。该委员会由主席任命,并根据他们在工作组活动中的参与程度,可以无限期地保持职位。一个成员可以被指导委员会的三个实体之一罢免:主席、其他指导委员会成员,或 Linux 基金会董事会。

这是一张展示 LSB 工作组更详细结构的图片:

Linux 标准基础

LSB 是一个相当复杂的结构,正如前面的图片所示,因此如果需要,工作组可以定义更多的角色。工作组的主要焦点仍然是其使命;为了实现这一目标,需要推广和培育新的工作组。它们需要一定程度的独立性,但也需要对 LSB 主席所做的活动负责。这主要涉及确保满足某些截止日期,并确保项目按照其路线图进行。

与 LSB 可交付成果互动的第一步应该是确定目标系统需要满足的确切 LSB 要求。规范分为两个组成部分:与体系结构相关和与体系结构无关,或者也称为通用组件。与体系结构相关的组件包含三个模块:

  • 核心

  • C++

  • 桌面

与体系结构无关的组件包含五个模块:

  • 核心

  • C++

  • 桌面

  • 打印

  • 语言

当然,还有另一种结构用于对它们进行排序。在这里,我指的是其中一些是强制性的,而另一些处于试验和测试阶段。第一类是为了拥有符合 LSB 标准的发行版,而第二类并不是拥有符合标准的发行版的严格要求,但可能代表未来几个版本 LSB 的候选人。

以下图片代表 LSB 的关键可交付组件。我希望它能指导您了解该项目的组件,并收集您未来与 LSB 工作组各个组件互动所需的信息。

Linux 标准基础

根据用户的兴趣,他们可以选择与发行版开发或应用程序组件开发进行交互。正如前面的图像清楚地显示的那样,这两条道路都有适合工作的工具。在开始工作之前,请确保查看 LSB Navigator 的网站并收集所需的信息。对于对 LSB 导航器感兴趣的用户,可以在以下链接中找到一个演示,其中还涉及与 Yocto 的交互。确保您查看并与之交互,以了解其工作原理。

注意

可以在www.linuxbase.org/navigator/commons/welcome.php访问 LSB Navigator。

假设交互已经完成,现在您有兴趣参与这个项目。当然,有多种方法可以做到这一点。无论您是开发人员还是软件供应商,您的反馈对任何项目都是有帮助的。此外,对于希望通过代码做出贡献的开发人员,有多个组件和工具可以从您的帮助中受益。这还不是全部。有许多测试框架和测试基础设施始终需要改进,因此某人不仅可以通过代码做出贡献,还可以进行错误修复和工具的开发或测试。还要记住,您的反馈总是受到赞赏的。

在进入下一节之前,我想再介绍一件事。如前图所示,任何由开发人员执行的与 LSB 工作组组件相关的活动,都应在检查 LSB 规范并选择适当版本之后进行。例如,在 CGL 规范中,至少需要 LSB 3.0,并且在相同的要求描述中指出了所需的模块。对于希望了解所需规范及其组件的开发人员,请参阅refspecs.linuxfoundation.org/lsb.shtml。确保您还检查了新推出的 LSB 5 规范的进展,该规范已经通过了测试阶段,目前处于 RC1 状态。有关此的更多信息,请访问www.linuxfoundation.org/collaborate/workgroups/lsb/lsb-50-rc1

注意

有关 LSB 的更多信息,请访问www.linuxfoundation.org/collaborate/workgroups/lsb

运营商级别选项

本节将讨论多种选项,并且我们将从定义术语运营商级别开始。这似乎是一个完美的开始。那么,在电信环境中,这个术语是什么意思呢?它指的是一个真正可靠的系统、软件,甚至硬件组件。在这里,我不仅指的是 CGL 提供的五个九或六个九,因为并非所有行业和场景都需要这种可靠性。我们只会提到在项目范围内可以定义为可靠的东西。要将系统、软件或硬件组件定义为运营商级别,它还应该经过全面测试,并具有各种功能,如高可用性、容错性等。

这些五个九和六个九指的是产品可用时间占 99.999 或 99.9999%。这意味着每年的停机时间约为五个九的 5 分钟和六个九的 30 秒。解释完这一点后,我将继续介绍运营商级别的可用选项。

运营商级别 Linux

这是第一个也是最古老的选项。它出现是因为电信行业需要定义一组规范,从而为基于 Linux 的操作系统定义一组标准。实施后,这将使系统具备运营商级别的能力。

CGL 背后的动机是提出一个开放架构作为电信系统中已有的专有和闭源解决方案的可能解决方案或替代方案。开放架构的替代方案不仅因为它避免了单一形式,不难维护、扩展和开发,而且它还提供了速度的优势。拥有一个解耦并使其组件对更多软件或硬件工程师可访问的系统更快、更便宜。所有这些组件最终都能够达到相同的目的。

该工作组最初由开放源开发实验室OSDL)发起,后来与自由标准组合并形成了 Linux 基金会。现在所有工作都转移到那里,包括工作组。CGL 的最新可用版本是 5.0,其中包括 Wind River、MontaVista 和 Red Flag 等注册的 Linux 发行版。

OSDL CGL 工作组有三类 CGL 可能适用的应用程序:

  • 信令服务器应用程序:包括为呼叫和服务提供控制服务的产品,如路由、会话控制和状态。这些产品通常处理大量连接,大约有 1 万到 10 万个同时连接,而且由于它们有需要在毫秒内从进程中获取结果的实时要求。

  • 网关应用程序:提供技术和管理域的桥接。除了已经提到的特征,这些应用程序在实时环境中处理大量连接,但接口数量并不是很多。它们还要求在通信过程中不丢失帧或数据包。

  • 管理应用程序:通常提供计费操作、网络管理和其他传统服务。它们对实时操作没有同样强烈的要求,而是集中于快速数据库操作和其他面向通信的请求。

为了确保它能满足前述类别,CGL 工作组专注于两项主要活动。第一项涉及与前述类别的沟通,确定它们的需求,并编写应该由分发供应商实施的规范。第二项涉及收集和帮助满足规范中定义的要求的项目。总之,CGL 试图代表的不仅是电信行业代表和 Linux 发行版,还有最终用户和服务提供商;它还为每个类别提供了运营商级别的选项。

每个希望获得 CGL 认证的发行供应商都会提供其实施作为模板。它填充了软件包的版本、名称和其他额外信息。然而,它在不披露太多有关实施过程的信息的情况下进行,这些软件包可能是专有软件。此外,披露的信息由供应商拥有和维护。CGL 工作组只显示供应商提供的链接。

规范文档现在已经到了 5.0 版本,包含了对于载波级认证的 Linux 发行版中实际上是强制性的或可选的应用程序的要求。强制性的要求由 P1 优先级描述,可选的要求标记为 P2。其他元素与缺口方面有关,表示一个功能,由于没有针对它的开源实现,因此未实现。规范文档中提出了这些要求,以激励发行版开发人员为其做出贡献。

如下图所示,并且在规范文档中强调的信息中所述,CGL 系统应提供大量功能:

Carrier Grade Linux

由于功能数量的要求很大,工作组决定将它们分成以下各类:

  • 可用性:对于单节点的可用性和恢复是相关的。

  • 集群:它描述了在从个体系统构建集群中有用的组件。其背后的关键目标是系统的高可用性和负载平衡,这也可能带来一些性能改进。

  • 可维护性:它涵盖了系统的维护和维修功能。

  • 性能:它描述了一些功能,如实时要求等,可以帮助系统实现更好的性能。

  • 标准:这些作为各种 API、标准和规范的参考。

  • 硬件:它提供了必要的载波级操作系统的各种硬件特定支持。其中大部分来自参与此过程的硬件供应商,而这一部分的要求在最新的 CGL 规范发布中已大大减少。

  • 安全:它代表了构建安全系统所需的相关功能。

注意

有关 CGL 要求的更多信息,请参考www.linuxfoundation.org/sites/main/files/CGL_5.0_Specification.pdf。您还可以参考 CGL 工作组www.linuxfoundation.org/collaborate/workgroups/cgl

汽车级 Linux

汽车级 Linux 也是 Linux 基金会的一个工作组。它是新成立的,试图提供一个具有汽车应用的开源解决方案。它的主要重点是车载信息娱乐领域,但它还包括远程信息系统和仪表盘。它的努力基于已经可用的开源组件。这些组件适用于它的目的,并且还试图实现快速开发,这在这个行业中是非常需要的。

工作组的目标是:

  • 一个透明、协作和开放的环境,涉及到的元素。

  • 一个专注于汽车的 Linux 操作系统堆栈,利用开源社区的支持,如开发人员、学术组件和公司。

  • 开源社区的集体声音这次以相反的形式发布,从 AGL 到社区。

  • 用于快速原型设计的嵌入式 Linux 发行版。

通过使用项目,如 Tizen,作为参考发行版,并拥有像 Jaguar、Nissan、路虎或丰田这样的项目,这个项目足够有趣,值得密切关注。它刚刚被开发出来,但有改进的潜力。对于那些对此感兴趣的人,请参考www.linuxfoundation.org/collaborate/workgroups/automotive-grade-linux。该项目的维基页面是一个有趣的资源,可以在wiki.automotivelinux.org/上查阅。

运营商级虚拟化

CGL 的最新发展使得虚拟化成为运营商级领域的一个有趣选择,因为它涉及到降低成本以及透明地利用运行单核设计应用程序的多核设备。虚拟化选项也需要满足其他运营商级系统的期望。

运营商级虚拟化一直试图成为集成在已有的运营商级平台中的一个重要组成部分。这是为了保留系统的属性和性能。它还试图扩展设备目标,并允许原始设备制造商(OEM)从与 CGL 相同的支持中获益。这些好处以成熟的目标形式存在。

虚拟化的应用更加广泛,可以看到从 x86 架构到基于 ARM 和 DSP 的处理器以及各种领域。从运营商级的角度来看虚拟化的研究是这个解决方案的重点,因为这样可以更清晰地看到需要改进的领域。通过这种方式,可以识别这些领域,并根据需要进行改进。不幸的是,这个倡议并没有像其他一些倡议那样被广泛宣传,但它仍然是一个非常好的文档来源,并且可以从 virtualLogix 的www.linuxpundit.com/documents/CGV_WP_Final_FN.pdf获取。希望你喜欢它的内容。

Yocto 项目的特定支持

在 Poky 参考系统中,支持 LSB 和兼容 LSB 应用程序的开发。在 Poky 中,有一个特殊的poky-lsb.conf分发策略配置,如果一个发行版有兴趣开发符合 LSB 标准的应用程序,就需要定义这个配置。这在生成一个符合 LSB 标准的 Linux 发行版或者至少准备获得 LSB 认证时是成立的。这里将介绍准备 LSB 认证所需的 Linux 发行版构建步骤。如果你有兴趣开发符合 LSB 标准的应用程序,这个过程会更简单,也会在这里简要介绍;然而,这与前者相反。

第一步很简单:只需要克隆 poky 存储库和meta-qt3依赖层,因为 LSB 模块的要求。

git clone git://git.yoctoproject.org/poky.git
git clone git://git.yoctoproject.org/meta-qt3

接下来,需要创建构建目录:

source oe-init-build-env -b ../build_lsb

conf/bblayers.conf文件中,只需要添加meta-qt3层。在conf/local.conf文件中,应选择相应的机器。我建议选择一个性能强大的平台,但如果为这样的演示提供了足够的 CPU 和内存,使用模拟架构,比如qemuppc,也应该足够了。还要确保将DISTRO变量更改为poky-lsb。所有这些准备就绪后,构建过程可以开始。这需要以下命令:

bitbake core-image-lsb

生成的二进制文件在所选的机器上生成并引导后,用户可以使用LSB_Test.sh脚本运行所有测试,该脚本还设置了 LSB 测试框架环境,或者运行特定的测试套件:

/usr/bin/LSB_Test.sh

你也可以使用以下命令:

cd /opt/lsb/test/manager/utils
./dist-checker.pl –update
./dist-checker.pl –D –s 'LSB 4.1' <test_suite>

如果各种测试未通过,系统需要重新配置以确保所需的兼容性水平。在meta/recipes-extended/images中,除了core-image-lsb.bb配方外,还有两个类似的配方:

  • core-image-lsb-sdk.bb:它包括一个meta-toolchain和生成应用程序开发所需的必要库和开发头文件

  • core-image-lsb-dev.bb:适用于目标开发工作,因为它包括dev-pkgs,这些包暴露了特定于图像的软件包所需的头文件和库

在 Yocto 项目中,有一个名为meta-cgl的层,旨在成为 CGL 倡议的基石。它汇集了 CGL 工作组定义的所有可用和必需的软件包。该层的格式试图为将来支持各种机器上的 CGL 设置舞台。在meta-cgl层内,有两个子目录:

  • meta-cgl-common:这是活动的焦点位置,也是在 poky 内提供支持的子目录,例如qemuarmqemuppc等。

  • meta-cgl-fsl-ppc:这是一个定义 BSP 特定支持的子目录。如果需要其他机器的支持,应该提供这样的层。

正如我已经提到的,meta-cgl层负责 CGL 支持。正如之前提到的,CGL 的要求之一是具有 LSB 支持,而这种支持在 Poky 内是可用的。它作为一个特定要求集成在这个层内。meta-cgl层的另一个建议是将所有可用的软件包分组到定义各种类别的软件包组中。可用的软件包组非常通用,但所有可用的软件包都集成在一个称为packagegroup-cgl.bb的核心软件包组中。

该层还公开了一个符合 CGL 的操作系统镜像。该镜像试图首先包含各种 CGL 特定的要求,并打算通过包含 CGL 规范文档中定义的所有要求来增长。除了符合 CGL 要求并准备进行 CGL 认证的结果 Linux 操作系统外,该层还试图定义一个特定于 CGL 的测试框架。这项任务可能看起来类似于 LSB 检查兼容性所需的任务,但我向您保证它并不相同。它不仅需要根据定义的规范制定一个特定于 CGL 的语言定义,还需要一系列与语言定义保持同步的测试定义。此外,有一些要求可以通过一个软件包或软件包的功能来满足,这些要求应该被收集并组合在一起。还有各种其他情景可以被解释和正确回答;这是使 CGL 测试成为一项艰巨任务的条件。

meta-cgl层内,有以下软件包的配方:

  • cluster-glue

  • cluster-resource-agents

  • corosync

  • heartbeat

  • lksctp-tools

  • monit

  • ocfs2-tools

  • openais

  • pacemaker

  • openipmi

除了这些配方,还有其他一些对于各种 CGL 要求是必要的。meta-cgl倡议所提供的支持正如前面所述的那样。它还将包含这些软件包:

  • evlog

  • mipv6-daemon-umip

  • makedumpfile

所有这些都是必要的,以提供具有 LSB 支持和 CGL 兼容性的基于 Linux 的操作系统。这将在适当的时候完成,也许当这本书到达您手中时,该层将以其最终格式存在,并成为 CGL 兼容性的标准。

我现在将开始解释一些您可能在 CGL 环境中遇到的软件包。我将首先从 Heartbeat 守护程序开始,它为集群服务提供通信和成员资格。将其放置在那里将使客户端能够确定其他机器上可用进程的当前状态,并与它们建立通信。

为了确保 Heartbeat 守护程序是有用的,它需要与集群资源管理器CRM)一起使用,后者负责启动和停止各种服务,以获得高可用性的 Linux 系统。这个 CRM 被称为Pacemaker,它无法检测资源级别的故障,只能与两个节点进行交互。随着时间的推移,它得到了改进,现在有更好的支持和额外的用户界面可用。其中一些服务如下:

  • crm shell:这是由 Dejan Muhamedagic 实现的命令行界面,用于隐藏 XML 配置并帮助进行交互。

  • 高可用性 Web 控制台:这是一个 AJAX 前端

  • Heartbeat GUI:这是一个提供大量相关信息的高级 XML 编辑器

  • Linux 集群管理控制台(LCMC):它最初是DRBD-管理控制台DRBD-MC),是一个用于 Pacemaker 管理目的的 Java 平台。

Pacemaker 接受三种类型的资源代理(资源代理代表集群资源之间的标准接口)。资源代理是 Linux-HA 管理的项目,由 ClusterLabs 的人员提供和维护。根据所选择的类型,它能够执行操作,如对给定资源的启动/停止,监视,验证等。支持的资源代理包括:

  • LSB 资源代理

  • OCF 资源代理

  • 传统的 Heartbeat 资源代理

Cluster Glue是与 Pacemaker/Heartbeat 一起使用的一组库、实用程序和工具。它基本上是将集群资源管理器(我指的是 Pacemaker)和消息传递层(可能是 Heartbeat)之间的一切联系在一起的粘合剂。尽管它最初是 Heartbeat 的一个组件,但现在它作为 Linux-HA 子项目的一个独立组件进行管理。它有一些有趣的组件:

  • Local Resource Manager (LRM):它充当 Pacemaker 和资源代理之间的接口,不具备集群感知能力。其任务包括处理从 CRM 接收的命令,将其传递给资源代理,并报告这些活动。

  • Shoot The Other Node In The Head (STONITH):这是一种机制,用于通过使集群认为已经死亡的节点来进行节点围栏,以便将其从中移除并防止任何交互风险。

  • hb_report:这是一个经常用于故障修复和隔离问题的错误报告实用程序。

  • 集群管道库:这是一个低级别的集群间通信库。

注意

有关 Linux-HA 的更多信息,以下链接可能会有所帮助:www.linux-ha.org/doc/users-guide/users-guide.html

接下来的元素是 Corosync 集群引擎。它是从 OpenAIS 衍生出来的项目,即将介绍。它是一个具有一系列功能和实现的组通信系统,试图提供高可用性支持,并在 BSD 许可下授权。其功能包括以下内容:

  • 一个用于在故障发生时重新启动应用程序的可用性管理器。

  • 一个关于仲裁状态及其是否已经实现的仲裁系统通知。

  • 一个支持同步以复制状态机的封闭进程组通信模型。

  • 一个驻留在内存中的配置和统计数据库。它提供了接收、检索、设置和更改各种通知的能力。

接下来,我们将看看 OpenAIS。这是由Service Availability ForumSASA Forum)提供的Application Interface SpecificationAIS)的开放实现。它代表了提供高可用性支持的接口。OpenAIS 中的源代码随着时间的推移在 OpenAIS 中进行了重构,只剩下了 SA Forum 特定的 API 和 Corosync 中的核心基础设施组件。OpenAIS 与 Heartbeat 非常相似;事实上,它是 Heartbeat 的替代品,是行业标准特定的。它也得到了 Pacemaker 的支持。

注意

有关 AIS 的更多信息可以在其维基百科页面和 SA 论坛网站上找到www.saforum.org/page/16627~217404/Service-Availability-Forum-Application-Interface-Specification

接下来是ocfs2-tools软件包。这是一组实用程序,可以以创建、调试、修复或管理 OCFS2 文件系统的形式进行工作。它包括与 Linux 用户习惯的非常相似的工具,如mkfs.ocfs2mount.ocfs2 fsck.ocfs2tunefs.ocfs2debugfs.ocfs2

Oracle Cluster File System (OCFS)是由 Oracle 开发的第一个共享磁盘文件系统,并在 GNU 通用公共许可证下发布。它不是一个符合 POSIX 标准的文件系统,但当 OCFS2 出现并集成到 Linux 内核中时,情况发生了变化。随着时间的推移,它成为了一个分布式锁管理器,能够提供高可用性和高性能。现在它被用于各种场合,如虚拟化、数据库集群、中间件和设备。以下是它的一些显著特点:

  • 优化的分配

  • REFLINKs

  • 元数据校验和

  • 索引目录

  • 每个 inode 的扩展属性

  • 用户和组配额

  • 高级安全性,如 SELinux 和 POSIX ACL 支持

  • 集群感知工具,如前面提到的工具,包括 mkfs、tunefs、fsck、mount 和 debugfs

  • 内置的具有分布式锁管理器的 Clusterstack

  • 日志记录

  • 可变块和簇大小

  • 缓冲,内存映射,拼接,直接,异步 I/O

  • 架构和端中立

lksctp-tools软件包是一个 Linux 用户空间实用程序,包括一个库和适当的 C 语言头文件,用于与 SCTP 接口进行交互。自 2.6 版本以来,Linux 内核就支持 SCTP,因此用户空间兼容性工具的存在对任何人来说都不足为奇。Lksctp 提供对 SCTP 基于套接字的 API 的访问。该实现是根据 IETF 互联网草案制定的,可在tools.ietf.org/html/draft-ietf-tsvwg-sctpsocket-15上找到。它提供了一种灵活和一致的开发基于套接字的应用程序的方法,利用了Stream Control Transmission ProtocolSCTP)。

SCTP 是一种面向消息的传输协议。作为传输层协议,它在 IPv4 或 IPv6 实现上运行,并且除了 TCP 的功能外,还提供对这些功能的支持:

  • 多流

  • 消息帧

  • 多宿

  • 有序和无序消息传递

  • 安全和认证

这些特殊功能对于行业载波级系统是必要的,并且在电话信令等领域中使用。

注意

有关 SCTP 的更多信息,请访问www.ietf.org/rfc/rfc2960.txtwww.ietf.org/rfc/rfc3286.txt

现在,我将稍微改变一下节奏,解释一下monit,这是一个非常小但功能强大的实用程序,用于监视和管理系统。它在自动维护和修复 Unix 系统方面非常有用,例如 BSD 发行版、各种 Linux 发行版以及可能包括 OS X 在内的其他平台。它可用于各种任务,包括文件监视、文件系统更改以及与事件进程的交互,如果通过各种阈值。

很容易配置和控制 monit,因为所有配置都基于易于理解的基于令牌的语法。此外,它提供了各种日志和关于其活动的通知。它还提供了一个网页浏览器界面,以便更容易访问。因此,拥有一个通用的系统资源管理器,也很容易与之交互,使 monit 成为运营商级 Linux 系统的选择。如果您有兴趣了解更多信息,请访问项目的网站mmonit.com/monit/

OpenIPMI智能平台管理接口IPMI)的实现,旨在提供对 IPMI 所有功能的访问,并为更容易使用提供抽象。它由两个组件组成:

  • 可插入 Linux 内核的内核驱动程序

  • 提供 IPMI 的抽象功能并提供对操作系统使用的各种服务的访问的库

IPMI 代表一组计算机接口规范,旨在通过提供智能和自主的系统来监视和管理主机系统的功能,从而降低总体拥有成本。这里我们不仅指的是操作系统,还包括固件和 CPU 本身。这个智能接口的开发由英特尔领导,现在得到了令人印象深刻的公司的支持。

注意

有关 IPMI、OpenIMPI 和其他支持的 IPMI 驱动程序和功能的更多信息,请访问openipmi.sourceforge.net/www.intel.com/content/www/us/en/servers/ipmi/ipmi-home.html

meta-cgl层中还应该有一些软件包,但在撰写本章时,它们仍然不可用。我将从mipv6-daemon-umip开始,它试图为移动互联网协议版本 6MIPv6)守护程序提供数据分发。UMIP是一个基于 MIPL2 的开源移动 IPv6 堆栈,维护着最新的内核版本。该软件包是UniverSAl playGround for Ipv6USAGI)项目对 MIPL2 的一组补丁,该项目试图为 Linux 系统提供 IPsec(IPv6 和 IPv4 选项)和 IPv6 协议栈实现的行业就绪质量。

注意

有关 UMIP 的更多信息,请访问umip.linux-ipv6.org/index.php?n=Main.Documentation

Makedumfile是一个工具,可以压缩转储文件的大小,并且还可以排除不需要进行分析的内存页面。对于一些 Linux 发行版,它与一个名为kexec-tools的软件包一起提供,可以使用运营商级规范支持的软件包管理器 RPM 在您的发行版中安装。它与gzipsplit等命令非常相似。它只接收来自 ELF 格式文件的输入,这使得它成为kdumps的首选。

另一个有趣的项目是evlog,这是一个用于企业级系统的 Linux 事件记录系统。它也符合 POSIX 标准,并提供了从printksyslog以及其他内核和用户空间函数的各种形式的日志记录。输出事件以符合 POSIX 标准的格式提供。它还在选择与特定定义的过滤器匹配的日志或注册特殊事件格式时提供支持。只有在满足注册的事件过滤器时才能通知这些事件。它的功能确实使这个软件包变得有趣,并且可以在evlog.sourceforge.net/上找到。

还有许多其他软件包可以包含到meta-cgl层中。查看注册的 CGL 发行版可以帮助你了解这样一个项目的复杂性。为了更容易地访问这个列表,请参考www.linuxfoundation.org/collaborate/workgroups/cgl/registered-distributions以简化搜索过程。

meta-cgl层互动的第一步是确保所有相互依赖的层都可用。关于如何构建兼容运营级的 Linux 镜像的最新信息始终可以在附加的README文件中找到。我在这里也给出了一个示例以进行演示:

git clone git://git.yoctoproject.org/poky.git
cd ./poky
git clone git://git.yoctoproject.org /meta-openembedded.git
git clone git://git.enea.com/linux/meta-cgl.git
git clone git://git.yoctoproject.org/meta-qt3
git clone git://git.yoctoproject.org/meta-virtualization
git clone git://git.yoctoproject.org/meta-selinux
git clone git://git.yoctoproject.org/meta-cloud-services
git clone git://git.yoctoproject.org/meta-security
git clone https://github.com/joaohf/meta-openclovis.git

接下来,需要创建和配置构建目录:

source oe-init-build-env -b ../build_cgl

conf/bblayers.conf文件中,需要添加以下层:

meta-cgl/meta-cgl-common
meta-qt3
meta-openembedded/meta-networking
meta-openembedded/meta-filesystems
meta-openembedded/meta-oe
meta-openembedded/meta-perl
meta-virtualization
meta-openclovis
meta-selinux
meta-security
meta-cloud-services/meta-openstack

conf/local.conf文件中,应选择相应的机器。我建议选择qemuppc,以及可以更改为poky-cglDISTRO变量。由于食谱的重复,应该提供BBMASK

BBMASK = "meta-openembedded/meta-oe/recipes-support/multipath-tools"

有了这些准备,构建过程就可以开始了。这个过程的必要命令是:

bitbake core-image-cgl

确保你有时间花在这上面,因为构建可能需要一段时间,这取决于你的主机系统的配置。

总结

在本章中,你们了解了运营级 Linux 和 Linux 标准基础所需的规范信息。其他选项,如汽车级和运营级虚拟化,也得到了解释,最后,为了完成这个学习过程,我向你们展示了对 Yocto 项目的支持和一些演示。

这是本书的最后一章,我希望你们喜欢这段旅程。同时,我希望我能够把我所学到的一些信息传递给你们。既然我们已经到了这本书的结尾,我必须承认在写书的过程中我也学到了新的信息。我希望你们也能对 Yocto 项目产生兴趣,并且能够为 Yocto 项目和开源社区做出贡献。我相信从现在开始,嵌入式世界对你来说将不再有太多秘密。确保你也向其他人阐明这个话题!

posted @ 2024-05-16 19:17  绝不原创的飞龙  阅读(52)  评论(0编辑  收藏  举报