标签 嵌入式 下的文章

使用 TotalCross 来快速构建嵌入式系统程序的用户界面。

 title=

从头开始构建 GUI 是一个非常耗时的过程,以硬编码的方式处理所有的位置和对齐对于一些程序员来说确实很困难。所以在本文中,我将演示如何使用 XML 加快这一过程。

本项目使用 TotalCross 作为目标框架。TotalCross 是一个开源的跨平台软件开发工具包(SDK),旨在更快地为嵌入式设备创建 GUI。TotalCross 无需在设备上运行 Java 即可提供 Java 的开发优势,因为它使用自己的字节码和虚拟机( TC 字节码 TC bytecode 和 TCVM)来增强性能。

我还使用了 Knowcode-XML,这是一个用于 TotalCross 框架的开源 XML 解析器,它可以将 XML 文件转换为 TotalCross 组件。

项目需求

要重现此项目,你需要:

制作嵌入式应用程序

该应用程序由一个具有扫描、打印和复印等基本打印功能的嵌入式 GUI 组成。

 title=

构建这个 GUI 需要几个步骤,包括使用 Android-XML 生成 GUI,然后使用 Knowcode-XML 解析器在 TotalCross 框架上运行它。

1、生成 Android XML

要创建 XML 文件,首先构建一个简单的 Android 屏幕,然后对其进行自定义。如果你不知道如何编写 Android-XML,或者你只是想简单尝试一下,你可以从这个 GitHub 项目 中下载这个应用程序的 XML。该项目还包含渲染 GUI 要用到的图片。

2、调整 XML

生成 XML 文件后,你需要进行一些微调以确保所有内容都已经对齐、比例正确并且图像的路径正确。

将 XML 布局添加到 Layouts 文件夹,将所有资源添加到 Drawable 文件夹。然后你就可以开始自定义 XML 了。

例如,如果想要更改 XML 对象的背景,可以更改 android:background 属性:

android:background="@drawable/scan"

你也可以使用 tools:layout_editor_absoluteXtools:layout_editor_absoluteY 更改对象的位置:

tools:layout_editor_absoluteX="830dp"
tools:layout_editor_absoluteY="511dp"

或者使用 android:layout_widthandroid:layout_height 更改对象的大小:

android:layout_width="70dp"
android:layout_height="70dp"

如果要在对象上放置文本,可以使用 android:textSizeandroid:textandroid:textStyleandroid:textColor

android:textStyle="bold"
android:textColor="#000000"
android:textSize="20dp"
android:text="2:45PM"

下面是一个完整的 XML 对象的示例:

    <ImageButton
           android:id="@+id/ImageButton"
           android:layout_width="70dp"
           android:layout_height="70dp"
           tools:layout_editor_absoluteX="830dp"
           tools:layout_editor_absoluteY="511dp"
           android:background="@drawable/home_config" />

3、在 TotalCross 上运行 GUI

完成所有 XML 调整后,就可以在 TotalCross 上运行它了。在 TotalCross 扩展(LCTT 译注:在 VSCode 里面)上创建一个新项目,并将 XMLDrawable 文件夹添加到 Main 文件夹里。如果你仍然不确定如何创建 TotalCross 项目,请参阅我们的 入门指南

配置好环境后,使用 totalcross.knowcode.parse.XmlContainerFactoryimport totalcross.knowcode.parse.XmlContainerLayout 在 TotalCross 框架上使用 XML GUI。 你可以在其 GitHub 页面 上找到更多关于使用 KnowCode-XML 的信息。

4、添加过渡效果

这个项目的平滑过渡效果是由 SlidingNavigator 类创建的,它使用 TotalCross 的 ControlAnimation 类从一个屏幕滑到另一个屏幕。

XMLpresenter 类上调用 SlidingNavigator

new SlidingNavigator(this).present(HomePresenter.class);

SlidingNavigator 类上实现 present 函数:

public void present(Class<? extends XMLPresenter> presenterClass)
         throws InstantiationException, IllegalAccessException {
      final XMLPresenter presenter = cache.containsKey(presenterClass) ? cache.get(presenterClass)
            : presenterClass.newInstance();
      if (!cache.containsKey(presenterClass)) {
         cache.put(presenterClass, presenter);
      }

      if (presenters.isEmpty()) {
         window.add(presenter.content, LEFT, TOP, FILL, FILL);
      } else {
         XMLPresenter previous = presenters.lastElement();

         window.add(presenter.content, AFTER, TOP, SCREENSIZE, SCREENSIZE, previous.content);

使用动画控件中的 PathAnimation 来创建从一个屏幕到另一个屏幕的滑动动画:

         PathAnimation.create(previous.content, -Settings.screenWidth, 0, new ControlAnimation.AnimationFinished() {
            @Override
            public void onAnimationFinished(ControlAnimation anim) {
               window.remove(previous.content);
            }
         }, 1000).with(PathAnimation.create(presenter.content, 0, 0, new ControlAnimation.AnimationFinished() {
            @Override
            public void onAnimation Finished(Control Animation anim) {
               presenter.content.setRect(LEFT, TOP, FILL, FILL);
            }
         }, 1000)).start();
      }
      presenter.setNavigator(this);
      presenters.push(presenter);
      presenter.bind2();
      if (presenter.isFirstPresent) {
         presenter.onPresent();
         presenter.isFirstPresent = false;
      }

5、加载环形进度条

打印机应用程序的另一个不错的功能是显示进度的加载屏幕动画。它包括文本和旋转动画。

 title=

通过添加定时器和定时器监听器来更新进度标签,然后调用函数 spinner.start() 来实现此功能。所有的动画都是由 TotalCross 和 KnowCode 自动生成的:

public void startSpinner() {
        time = content.addTimer(500);
        content.addTimerListener((e) -> {
            try {
                progress(); // Updates the Label
            } catch (InstantiationException | IllegalAccessException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        });
        Spinner spinner = (Spinner) ((XmlContainerLayout) content).getControlByID("@+id/spinner");
        spinner.start();
    }

这里的环形进度条被实例化为对 XML 文件中描述的 XmlContainerLayout spinner 的引用:

<ProgressBar
android:id="@+id/spinner"
android:layout_width="362dp"
android:layout_height="358dp"
tools:layout_editor_absoluteX="296dp"
tools:layout_editor_absoluteY="198dp"
   android:indeterminateTint="#2B05C7"
style="?android:attr/progressBarStyle" />

6、构建应用程序

是时候构建应用程序了。你可以在 pom.xml 中查看和更改 目标系统 target systems 。 请确保 Linux Arm 目标可用。

如果你使用的是 VSCode,请按下键盘上的 F1 键,选择 TotalCross: Package 并等待完成。 然后就可以在 Target 文件夹中看到安装文件了。

7、在树莓派上部署和运行应用程序

要使用 SSH 协议在 树莓派 上部署应用程序,请按键盘上的 F1。选择 TotalCross: Deploy&Run 并提供有关你的 SSH 连接的信息,如:用户名、IP地址、密码和应用程序路径。

 title=

 title=

 title=

 title=

 title=

总结

KnowCode 让使用 Java 创建和管理应用程序屏幕变得更加容易。Knowcode-XML 将你的 XML 转换为 TotalCross GUI 界面,然后生成二进制文件以在你的树莓派上运行。

将 KnowCode 技术与 TotalCross 相结合,使你能够更快地创建嵌入式应用程序。 你可以访问我们在 GitHub 上的 嵌入式示例 并编辑你自己的应用程序,了解你还可以做什么。

如果你有问题、需要帮助,或者只是想与其他嵌入式 GUI 开发人员互动,请随时加入我们的 Telegram 小组,讨论任何框架上的嵌入式应用程序。


via: https://opensource.com/article/21/3/raspberry-pi-totalcross

作者:Edson Holanda Teixeira Junior 选题:lujun9972 译者:CoWave-Fall 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

RT-Thread Smart 致力于物联网和边缘计算领域的开源。

 title=

目前对 嵌入式操作系统 有巨大的需求,你建立的操作系统最好是开源的。RT-Thread 项目的研发团队花了两年时间,研发出了该项目的最新成果:RT-Thread Smart。这是一款微内核的操作系统,主要针对中高端的处理器,如具有内存管理单元(MMU)的 RISC-V 或 Arm Cortex-A,为嵌入式领域的所有行业提供了一个具有竞争力的、基于 POSIX 的软件平台。

谁需要 RT-Thread Smart?

RT-Thread Smart 是一款专业的、高性能的微内核操作系统,用于实时应用。它为所有市场的嵌入式设备提供了开源基础,如安全(IP 摄像头)、工业控制、车载设备、消费电子及其他嵌入式科技应用,可谓一切场景。它的意义在于:不像传统的物联网操作系统,一个微内核的操作系统可以填补传统实时操作系统 RTOS 和相对大型的操作系统如 Linux 之间的空白,实现实时性能、成本、安全、启动速度等等各方面之间的最佳平衡。

RT-Thread Smart 的架构

RT-Thread Smart 通过 MMU 和系统调用将系统分割为内核模式和用户模式,并为每种模式区分了地址空间(一个 32 位系统可以提供 4G 地址空间)。

 title=

(RT-Thread, CC BY-SA 4.0

RT-Thread Smart 内核包括平台的基础功能,并支持定制化。RT-Thread Smart 的用户应用环境使用 musl libc 来提供 POSIX 接口调用和 C 语言的运行时支持。它也继承了原始的 RT-Thread 生态系统,使用 SCons 或者其他编译工具如 Autotools、Makefile、CMake 等等来支持开发,以及 RT-Thread 开箱即用的在线软件包(撰写本文时超过 342 个)。你甚至可以将 Linux 应用程序(如 wget/cURL、BusyBox、OpenSSL 和 Simple DirectMedia Layer)移植到你的平台。

压缩的 RT-Thread Smart 内核仅 217 KB,搭配一个 127 KB 的根文件系统。大约 2 MB的存储占用。包括了对文件系统、网络协议栈、多媒体的完整支持。RT-Thread 只需要 3 到 5 秒完成启动,而在不运行其他功能组件时,RT-Thread Smart 需要的启动及准备时间不到 500ms。

通过其集成的 Persimmon 用户界面(UI)组件,RT-Thread Smart 从上电到运行 UI 需要大约 1 秒。换句话说,这是一个非常轻巧快速的系统。当然,“实时”不是指启动,而是指系统随着时间推进而表现出的一致性。对于 RT-Thread ,实时性能需要优先考虑,中断时延小于 1μs,满足大部分实时性要求严格的场景需求。

RT-Thread Smart 和 RT-Thread

你可能想知道 RT-Thread Smart 和 RT-Thread 之间的不同。简单来说, RT-Thread Smart 是一个基于 RT-Thread RTOS 的操作系统,但它整合了用户态的处理过程。RT-Smart 的内核部分本质上是 RT-Thread RTOS,它在虚拟地址上运行,增加了进程管理,使用进程间通信机制(IPC)、虚拟内存/地址空间管理、ELF 加载器等等,以上特性全部在 RT-Thread RTOS 内实现,当这些组件被禁用时,RT-Smart 会回归 RT-Thread RTOS。

以下是对比:

RT-ThreadRT-Thread Smart
支持芯片Cortex-M/R、RISC-V RV32IMAC(以及类似)、Cortex-A MPUCortex-A 等具有 MMU 的 MPU
编译内核和应用都编译到一个镜像内核和应用可以被分开编译和运行
存储使用线性地址空间(即使有 MMU),使用物理地址的虚拟寻址运行在内核占用超过 1GB 的 32 位操作系统,拥有完整 4G 地址空间的用户态进程彼此隔离,外设驱动程序必须通过虚拟地址访问外设
运行错误当一个应用程序失败时,整个系统就会崩溃当应用程序失败时,它不会影响内核和其他进程的执行
运行模式多线程模型多进程模型(进程内支持多线程,内核线程由内核支持)
用户模型单用户模型单用户模型
APIRT-Thread API、POSIX PSE52RT-Thread API(内核态和用户态),以及完整的 POSIX API
实时性抢占式硬实时系统抢占式硬实时系统
资源使用非常小相对小
调试通常需要模拟器调试支持 GDB 调试,不需要模拟器

RT-Thread RTOS 非常紧凑,它的所有应用和子系统都编译到镜像中,多线程应用运行并分享相同的地址空间。

RT-Thread Smart 是独立的。系统和应用是分别编译和运行的。应用拥有完整且互相隔离的地址空间。它也继承了 RT-Thread 优秀的实时性,同时也具有 POSIX 环境的特性。

类似地,它们都与 RT-Thread API 兼容。RT-Thread RTOS 的应用可以被平滑移植到 RT-Thread Smart。

嵌入式开源

RT-Thread Smart 是一个开源项目,项目地址:GitHub。你可以下载代码和文档,尝试一下,并提交评论和反馈,将该项目传播给更多开源倡导者。嵌入式系统属于它们的用户,有太多的嵌入式开发人员没有找到太多可用的嵌入式系统。

如果你是开发人员,请帮助改进 RT-Thread Smart。随着 RT-Thread 项目的不断推进,我们希望创建物联网和边缘计算的令人激动的开源世界。


via: https://opensource.com/article/21/7/rt-thread-smart

作者:Zhu Tianlong 选题:lujun9972 译者:tendertime 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

现在,很多嵌入式系统都是在 Linux 上运行的。但是,在很久很久以前,嵌入式系统要么在一个定制的专有的平台上运行,要么在 DOS 上运行。

 title=

FreeDOS 网站 宣称,大多数人使用 FreeDOS 来完成三项主要任务:

  1. 玩经典的 DOS 游戏
  2. 运行老式的 DOS 软件
  3. 运行一款嵌入式系统

但是,运行一个“嵌入式”系统的意义是什么呢?

嵌入式系统基本上是一款非常小的系统,专用于运行一个特定的任务。你可以把现在的嵌入式系统当作是 物联网(IoT)的一部分,这包括传感器、恒温器和门铃摄像头。现在,很多嵌入式系统都是在 Linux 上运行的。

但是,在很久很久以前,嵌入式系统要么在一个定制的专有的平台上运行,要么在 DOS 系统上运行。在现在,一些基于 DOS 的嵌入式系统仍然在运行,例如,收银机或电话专用交换机(PBX)系统。举个例子来说,在 2017 年,酷爱列车的人发现一个正在运行 FreeDOS 的俄罗斯的电动列车控制系统 (俄语: САВПЭ),它使用特殊的软件来控制和监控郊区列车的线路,并发布乘客通告。

在 DOS 上建立一个嵌入式系统需要定义一个最小化的 DOS 环境来运行单个应用程序。幸运的是,设置一个最小化的 FreeDOS 环境是非常容易的。从技术上来说,启动 FreeDOS 并运行 DOS 应用程序仅需要内核和一个 FDCONFIG.SYS 配置文件。

安装一款最小化的系统

我们可以使用 QEMU 仿真器来模拟一个专用的、最小化的 FreeDOS 系统,并给它分配很少的资源。为了更准确地反映一个嵌入式系统,我将定义一个只有 8 MB 的存储器和仅仅有 2 MB 的硬盘驱动器的虚拟机。

为创建这个微小的虚拟硬盘,我将使用这个 qemu-img 命令来定义一个 2M 的文件:

$ qemu-img create tiny.img 2M
Formatting 'tiny.img', fmt=raw size=2097152

下面的这行命令定义了一个 32 位的 “i386” CPU、8MB 的存储器,使用 2MB 的 tiny.img 文件作为硬盘驱动器镜像,使用 FreeDOS 1.3 RC4 LiveCD 作为 CD-ROM 介质。我们也将机器设置为从 CD-ROM 驱动器启动(-boot order=d),尽管我们只需要用它来安装系统。在我们完成所有的设置后,我们将从该硬盘启动完成的嵌入式系统:

qemu-system-i386 -m 8 -hda tiny.img -cdrom FD13LIVE.iso -boot order=d

使用 “ 现场环境模式 Live Environment mode ” 来启动系统,这将为我们提供一个正在运行的 FreeDOS 系统,我们可以使用它来将一个最小化的 FreeDOS 转移到硬盘上。

 title=

启动到 LiveCD 环境之中(Jim Hall, CC-BY SA 4.0

我们需要在虚拟硬盘驱动器上为我们的程序创建一个分区。为此,从命令行中运行 FDISK 程序。FDISK 是 FreeDOS 上的一个标准的 磁盘分区 实用程序。使用 FDISK 来创建一个单个硬盘驱动器分区,占用整个(2 MB)硬盘驱动器。

 title=

FDISK,在创建 2 MB 分区后(Jim Hall, CC-BY SA 4.0

但是,在你重新启动 FreeDOS 之前,FreeDOS 不会看到新的硬盘驱动器分区 — FreeDOS 仅在启动时读取硬盘详细信息。退出 FDISK ,并重新启动 FreeDOS 。

在重新启动后,你需要在新的硬盘驱动器上创建一个 DOS 文件系统。因为这里只有一个虚拟硬盘,FreeDOS 将识别其为 C: 驱动器。你可以使用 FORMAT 命令来在 C: 驱动器上创建一个 DOS 文件系统。使用 /S 选项将把操作系统文件(内核,外加一个 COMMAND.COM shell 的副本)转移到新的驱动器上。

 title=

格式化新的驱动器来创建一个 DOS 文件系统(Jim Hall, CC-BY SA 4.0 你已经创建了硬盘驱动器并将其格式化,现在,你可以安装应用程序,这些应用程序是将会在新安装的嵌入式系统上运行的。

安装专用的应用程序

嵌入式系统实际上只是一个运行在一个专用系统上的单一用途的应用程序。这些应用程序通常是为其将要控制的系统而自定义构建的,例如,一台收银机、显示终端、或控制环境。在这个演示中,让我们使用一个来自 FreeDOS 1.3 RC4 安装光盘中的程序。它需要足够小,以适应我们为其创建的 2 MB 微型硬盘驱动器。这可以是任何东西,所以,为了好玩,让我们把它变成一个游戏。

FreeDOS 1.3 RC4 包含一些有趣的游戏。我喜欢的一个游戏是一个名称为 “Simple Senet” 的棋类游戏。它是一个基于 Senet 的古埃及棋类游戏。游戏的细节对这个演示并不重要,我们将安装它,并将其设置为嵌入式系统的专业应用程序。

为安装应用程序,在 FreeDOS 1.3 RC4 LiveCD 上,进入 \PACKAGES\GAMES 目录。你将在其中看到一个很长的软件包列表,而我们想要的 SENET.ZIP

 title=

来自 FreeDOS 1.3 RC4 的一个游戏软件包列表(Jim Hall, CC-BY SA 4.0

为解压缩 “Simple Senet” 软件包到虚拟硬盘上,使用 UNZIP 命令。所有的 FreeDOS 软件包都是 Zip 文件,因此,你可以使用任意与 Zip 兼容的档案实用程序来管理它们。FreeeDOS 1.3 RC4 包含创建 Zip 档案文件的 ZIP 和提取 Zip 档案文件的 UNZIP 。它们都来自 Info-Zip 项目

UNZIP SENET.ZIP -d C:\FDOS

通常,使用 UNZIP 来提取 Zip 文件到当前目录中。在命令行结尾的 -d C:\FDOS 选项将告诉 UNZIP 来提取 Zip 文件到 C:\FDOS 目录之中。(-d 指的是“目的地”)

 title=

解压缩 Simple Senet 游戏(Jim Hall, CC-BY SA 4.0

为了让嵌入式系统启动时运行 “Simple Senet” 游戏,我们需要告诉 FreeDOS 来使用 Senet 作为系统的 “shell” 。 默认的 FreeDOS 的 shell 是 COMMAND.COM 程序,但是,你可以在 FDCONFIG.SYS 内核配置文件中使用 SHELL= 指令来定义一个不同的 shell 程序。我们可以使用 FreeDOS 的 Edit 来创建新的 C:\FDCONFIG.SYS 文件。

 title=

(Jim Hall, CC-BY SA 4.0

如果你需要定义其它的参数来支持嵌入式系统,你可以将其添加到 FDCONFIG.SYS 文件之中。例如,你可能需要使用 SET 动作来设置环境变量,或者使用 FILES=BUFFERS= 语句来调整 FreeDOS 内核。

运行嵌入式系统

在全面地完成嵌入式系统的定义之后,现在,我们可以重新启动计算机来运行嵌入式应用程序。运行一个嵌入式系统通常仅需要有限的资源,因此,在这个演示中,我们需要调整 QEMU 命令行来只从硬盘驱动器(-boot order=c)中启动,而不再定义一个 CD-ROM 驱动器:

qemu-system-i386 -m 8 -hda tiny.img -boot order=c

当 FreeDOS 内核启动时,它将读取 FDCONFIG.SYS 文件以获取启动参数。然后,它将使用 SHELL= 行的定义来运行 shell 。这将自动地运行 “Simple Senet” 游戏。

 title=

作为一个嵌入式系统运行 Simple Senet(Jim Hall, CC-BY SA 4.0

我们已经使用了 “Simple Senet” 来演示如何在 FreeDOS 上设置一个嵌入式系统。根据你的需要,你可以使用任何你喜欢的独立应用程序。在 FDCONFIG.SYS 中使用 SHELL= 行将其定义为 DOS 的 shell ,FreeDOS 将在启动时自动地启动该应用程序。

不过,在这里有一个限制。嵌入式系统通常不需要退回到一个命令行提示符之中,因此这些专用应用程序通常不允许用户退出到 DOS 之中。如果你设法退出了嵌入式应用程序,你可能会看到一个 “Bad or missing Command Interpreter” 的提示,你将需要在其中输入一个新的 shell 的完整路径。对于一个以用户为中心的桌面系统来说,这将是一个问题。但是在一个嵌入式系统上,它只专注执行一种工作的,那么,你也永远不需要退出嵌入式应用程序。


via: https://opensource.com/article/21/6/freedos-embedded-system

作者:Jim Hall 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Rust 的高性能、高可靠性和高生产力使它适合于嵌入式系统。

 title=

在过去的几年里,Rust 在程序员中获得了热情的追捧。技术潮流来来去去,所以很难将仅仅因为某项新技术而产生的兴奋与对某项技术的优点的兴奋区分开来,但我认为 Rust 是一种真正设计良好的语言。它的目标是帮助开发者建立可靠和高效的软件,而且它从一开始就是为这个目的设计的。你可能听过一些 Rust 的关键特性,在这篇文章中,我会证明这些特性正是 Rust 也恰好适合嵌入式系统的原因。比如:

  • 高性能:它速度快,内存利用率高
  • 可靠性:在编译过程中可以消除内存错误
  • 生产力:很棒的文档,友好的编译器,有用的错误信息,以及一流的工具化。它有一个集成的包管理器和构建工具,智能的多编辑器支持自动补完和类型检查、自动格式化等等。

为什么使用 Rust 进行嵌入式开发?

Rust 的设计是为了保证安全和高性能。嵌入式软件会出现的问题主要是内存的问题。从某种程度上说,Rust 是一种面向编译器的语言,所以你可以确保在编译时安全使用内存。以下是使用 Rust 在嵌入式设备上开发的一些好处:

  • 强大的静态分析
  • 灵活的内存
  • 无畏的并发性
  • 互操作性
  • 可移植性
  • 社区驱动

在这篇文章中,我使用开源的 RT-Thread 操作系统 来演示如何使用 Rust 进行嵌入式开发。

如何在 C 语言中调用 Rust

在 C 代码中调用 Rust 代码时,你必须将 Rust 源代码打包成静态库文件。当 C 代码编译时,将其链接进去。

用 Rust 创建一个静态库

在这个过程中,有两个步骤:

1、使用 cargo init --lib rust_to_c 在 Clion 中建立一个 lib 库。在 lib.rs 中加入以下代码。下面的函数计算两个类型为 i32 的值的总和并返回结果:

#![no_std]
use core::panic::PanicInfo;

#[no_mangle]
pub extern "C" fn sum(a: i32, b: i32) -> i32 {
    a + b
}

#[panic_handler]
fn panic(_info:&PanicInfo) -> !{
    loop{}
}

2、在你的 Cargo.toml 文件中添加以下代码,以告诉 Rustc 要生成什么类型的库:

[lib]
name = "sum"
crate-type = ["staticlib"]
path = "src/lib.rs"

交叉编译

你可以针对你的目标平台进行交叉编译。假设你的嵌入式系统是基于 Arm 的,步骤很简单:

$ rustup target add armv7a-none-eabi

生成静态库文件:

$ cargo build --target=armv7a-none-eabi --release --verbose
Fresh rust_to_c v0.1.0
Finished release [optimized] target(s) in 0.01s

生成头文件

你也需要头文件:

1、安装 cbindgencbindgen 工具会从 Rust 库中生成一个 C 或 C++11 的头文件:

$ cargo install --force cbindgen

2、在你的项目文件夹下创建一个新的 cbindgen.toml 文件。

3、生成一个头文件:

$ cbindgen --config cbindgen.toml --crate rust_to_c --output sum.h

调用 Rust 库文件

现在你可以对你的 Rust 库进行调用了。

1、把生成的 sum.hsum.a 文件放到 rt-thread/bsp/qemu-vexpress-a9/applications 目录下。

2、修改 SConscript 文件并添加一个静态库:

   from building import *
   
   cwd     = GetCurrentDir()
   src     = Glob('*.c') + Glob('*.cpp')
   CPPPATH = [cwd]
   
   LIBS = ["libsum.a"]
   LIBPATH = [GetCurrentDir()]
   
   group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH, LIBS = LIBS, LIBPATH = LIBPATH)
   
   Return('group')

3、在主函数中调用 sum 函数,得到返回值,并 printf 该值:

   #include <stdint.h>
   #include <stdio.h>
   #include <stdlib.h>
   #include <rtthread.h>
   #include "sum.h"
   
   int main(void)
   {
       int32_t tmp;
   
       tmp = sum(1, 2);
       printf("call rust sum(1, 2) = %d\n", tmp);
   
       return 0;
   }

4、在 RT-Thread Env 环境中,使用 scons 来编译项目并运行:

$ scons -j6
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
[...]
scons: done building targets.

$ qemu.sh
 \ | /
- RT -     Thread Operating System
 / | \     4.0.4 build Jul 28 2021
2006 - 2021 Copyright by rt-thread team
lwIP-2.1.2 initialized!
[...]
call rust sum(1, 2) = 3

加、减、乘、除

你可以在 Rust 中实现一些复杂的数学运算。在 lib.rs 文件中,使用 Rust 语言来实现加、减、乘、除:

#![no_std]
use core::panic::PanicInfo;

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[no_mangle]
pub extern "C" fn subtract(a: i32, b: i32) -> i32 {
    a - b
}

#[no_mangle]
pub extern "C" fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

#[no_mangle]
pub extern "C" fn divide(a: i32, b: i32) -> i32 {
    a / b
}

#[panic_handler]
fn panic(_info:&PanicInfo) -> !{
    loop{}
}

构建你的库文件和头文件,并把它们放在应用程序目录中。使用 scons 来编译。如果在链接过程中出现错误,请在官方 Github 页面 中找到解决方案。

修改 rtconfig.py 文件,并添加链接参数 --allow-multiple-definition

       DEVICE = ' -march=armv7-a -marm -msoft-float'
       CFLAGS = DEVICE + ' -Wall'
       AFLAGS = ' -c' + DEVICE + ' -x assembler-with-cpp -D__ASSEMBLY__ -I.'
       LINK_SCRIPT = 'link.lds'
       LFLAGS = DEVICE + ' -nostartfiles -Wl,--gc-sections,-Map=rtthread.map,-cref,-u,system_vectors,--allow-multiple-definition'+\
                         ' -T %s' % LINK_SCRIPT
   
       CPATH = ''
       LPATH = ''

编译并运行 QEMU 来看看你的工作。

在 Rust 中调用 C 语言

Rust 可以在 C 代码中调用,但是如何在你的 Rust 代码中调用 C 呢?下面是一个在 Rust 代码中调用 rt_kprintf C 函数的例子。

首先,修改 lib.rs 文件:

    // The imported rt-thread functions list
    extern "C" {
        pub fn rt_kprintf(format: *const u8, ...);
    }
   
    #[no_mangle]
    pub extern "C" fn add(a: i32, b: i32) -> i32 {
        unsafe {
            rt_kprintf(b"this is from rust\n" as *const u8);
        }
        a + b
    }

接下来,生成库文件:

$ cargo build --target=armv7a-none-eabi --release --verbose
Compiling rust_to_c v0.1.0
Running `rustc --crate-name sum --edition=2018 src/lib.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type staticlib --emit=dep-info,link -C opt-level=3 -C embed-bitcode=no -C metadata=a
Finished release [optimized] target(s) in 0.11s

而现在,要运行代码,将 Rust 生成的库文件复制到应用程序目录中,然后重新构建:

$ scons -j6 scons: Reading SConscript files ... scons: done reading SConscript files. [...]
scons: Building targets ... scons: done building targets.

再次运行 QEMU,可以在你的嵌入式镜像中看到结果。

你可以拥有这一切

在你的嵌入式开发中使用 Rust,你可以获得 Rust 的所有功能,而不需要牺牲灵活性或稳定性。今天就在你的嵌入式系统上试试 Rust 吧。关于嵌入式 Rust 的过程(以及 RT-Thread 本身)的更多信息,请查看 RT-Thread 项目的 YouTube 频道。请记住,嵌入式也可以是开放的。


via: https://opensource.com/article/21/10/rust-embedded-development

作者:Alan Smithee 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

了解 Yocto、Buildroot、 OpenWRT,和改造过的桌面发行版以确定哪种方式最适合你的项目。

Linux 被部署到比 Linus Torvalds 在他的宿舍里开发时所预期的更广泛的设备。令人震惊的支持了各种芯片,使得Linux 可以应用于大大小小的设备上:从 IBM 的巨型机到不如其连接的端口大的微型设备,以及各种大小的设备。它被用于大型企业数据中心、互联网基础设施设备和个人的开发系统。它还为消费类电子产品、移动电话和许多物联网设备提供了动力。

在为桌面和企业级设备构建 Linux 软件时,开发者通常在他们的构建机器上使用桌面发行版,如 Ubuntu 以便尽可能与被部署的机器相似。如 VirtualBoxDocker 这样的工具使得开发、测试和生产环境更好的保持了一致。

什么是嵌入式系统?

维基百科将嵌入式系统定义为:“在更大的机械或电气系统中具有专用功能的计算机系统,往往伴随着实时计算限制。”

我觉得可以很简单地说,嵌入式系统是大多数人不认为是计算机的计算机。它的主要作用是作为某种设备,而不被视为通用计算平台。

嵌入式系统编程的开发环境通常与测试和生产环境大不相同。它们可能会使用不同的芯片架构、软件堆栈甚至操作系统。开发工作流程对于嵌入式开发人员与桌面和 Web 开发人员来说是非常不同的。通常,其构建后的输出将包含目标设备的整个软件映像,包括内核、设备驱动程序、库和应用程序软件(有时也包括引导加载程序)。

在本文中,我将对构建嵌入式 Linux 系统的四种常用方式进行纵览。我将介绍一下每种产品的工作原理,并提供足够的信息来帮助读者确定使用哪种工具进行设计。我不会教你如何使用它们中的任何一个;一旦缩小了选择范围,就有大量深入的在线学习资源。没有任何选择适用于所有情况,我希望提供足够的细节来指导您的决定。

Yocto

Yocto 项目 定义为“一个开源协作项目,提供模板、工具和方法,帮助您为嵌入式产品创建定制的基于 Linux 的系统,而不管硬件架构如何。”它是用于创建定制的 Linux 运行时映像的配方、配置值和依赖关系的集合,可根据您的特定需求进行定制。

完全公开:我在嵌入式 Linux 中的大部分工作都集中在 Yocto 项目上,而且我对这个系统的认识和偏见可能很明显。

Yocto 使用 Openembedded 作为其构建系统。从技术上讲,这两个是独立的项目;然而,在实践中,用户不需要了解区别,项目名称经常可以互换使用。

Yocto 项目的输出大致由三部分组成:

  • 目标运行时二进制文件:这些包括引导加载程序、内核、内核模块、根文件系统映像。以及将 Linux 部署到目标平台所需的任何其他辅助文件。
  • 包流:这是可以安装在目标上的软件包集合。您可以根据需要选择软件包格式(例如,deb、rpm、ipk)。其中一些可能预先安装在目标运行时二进制文件中,但可以构建用于安装到已部署系统的软件包。
  • 目标 SDK:这些是安装在目标平台上的软件的库和头文件的集合。应用程序开发人员在构建代码时使用它们,以确保它们与适当的库链接

优点

Yocto 项目在行业中得到广泛应用,并得到许多有影响力的公司的支持。此外,它还拥有一个庞大且充满活力的开发人员社区生态系统。开源爱好者和企业赞助商的结合的方式有助于推动 Yocto 项目。

获得 Yocto 的支持有很多选择。如果您想自己动手,有书籍和其他培训材料。如果您想获得专业知识,有许多有 Yocto 经验的工程师。而且许多商业组织可以为您的设计提供基于 Yocto 的 Turnkey 产品或基于服务的实施和定制。

Yocto 项目很容易通过 进行扩展,层可以独立发布以添加额外的功能,或针对项目发布时尚不可用的平台,或用于保存系统特有定制功能。层可以添加到你的配置中,以添加未特别包含在市面上版本中的独特功能;例如,“meta-browser” 层包含 Web 浏览器的清单,可以轻松为您的系统进行构建。因为它们是独立维护的,所以层可以按不同的时间发布(根据层的开发速度),而不是跟着标准的 Yocto 版本发布。

Yocto 可以说是本文讨论的任何方式中最广泛的设备支持。由于许多半导体和电路板制造商的支持,Yocto 很可能能够支持您选择的任何目标平台。主版本 Yocto 分支仅支持少数几块主板(以便达成合理的测试和发布周期),但是,标准工作模式是使用外部主板支持层。

最后,Yocto 非常灵活和可定制。您的特定应用程序的自定义可以存储在一个层进行封装和隔离,通常将要素层特有的自定义项存储为层本身的一部分,这可以将相同的设置同时应用于多个系统配置。Yocto 还提供了一个定义良好的层优先和覆盖功能。这使您可以定义层应用和搜索元数据的顺序。它还使您可以覆盖具有更高优先级的层的设置;例如,现有清单的许多自定义功能都将保留。

缺点

Yocto 项目最大的缺点是学习曲线陡峭。学习该系统并真正理解系统需要花费大量的时间和精力。 根据您的需求,这可能对您的应用程序不重要的技术和能力投入太大。 在这种情况下,与一家商业供应商合作可能是一个不错的选择。

Yocto 项目的开发时间和资源相当高。 需要构建的包(包括工具链,内核和所有目标运行时组件)的数量相当不少。 Yocto 开发人员的开发工作站往往是大型系统。 不建议使用小型笔记本电脑。 这可以通过使用许多提供商提供的基于云的构建服务器来缓解。 另外,Yocto 有一个内置的缓存机制,当它确定用于构建特定包的参数没有改变时,它允许它重新使用先前构建的组件。

建议

为您的下一个嵌入式 Linux 设计使用 Yocto 项目是一个强有力的选择。 在这里介绍的选项中,无论您的目标用例如何,它都是最广泛适用的。 广泛的行业支持,积极的社区和广泛的平台支持使其成为必须设计师的不错选择。

Buildroot

Buildroot 项目定义为“通过交叉编译生成嵌入式 Linux 系统的简单、高效且易于使用的工具。”它与 Yocto 项目具有许多相同的目标,但它注重简单性和简约性。一般来说,Buildroot 会禁用所有软件包的所有可选编译时设置(有一些值得注意的例外),从而生成尽可能小的系统。系统设计人员需要启用适用于给定设备的设置。

Buildroot 从源代码构建所有组件,但不支持按目标包管理。因此,它有时称为固件生成器,因为镜像在构建时大部分是固定的。应用程序可以更新目标文件系统,但是没有机制将新软件包安装到正在运行的系统中。

Buildroot 输出主要由三部分组成:

  • 将 Linux 部署到目标平台所需的根文件系统映像和任何其他辅助文件
  • 适用于目标硬件的内核,引导加载程序和内核模块
  • 用于构建所有目标二进制文件的工具链。

优点

Buildroot 对简单性的关注意味着,一般来说,它比 Yocto 更容易学习。核心构建系统用 Make 编写,并且足够短以便开发人员了解整个系统,同时可扩展到足以满足嵌入式 Linux 开发人员的需求。 Buildroot 核心通常只处理常见用例,但它可以通过脚本进行扩展。

Buildroot 系统使用普通的 Makefile 和 Kconfig 语言来进行配置。 Kconfig 由 Linux 内核社区开发,广泛用于开源项目,使得许多开发人员都熟悉它。

由于禁用所有可选的构建时设置的设计目标,Buildroot 通常会使用开箱即用的配置生成尽可能最小的镜像。一般来说,构建时间和构建主机资源的规模将比 Yocto 项目的规模更小。

缺点

关注简单性和最小化启用的构建方式意味着您可能需要执行大量的自定义来为应用程序配置 Buildroot 构建。此外,所有配置选项都存储在单个文件中,这意味着如果您有多个硬件平台,则需要为每个平台进行每个定制更改。

对系统配置文件的任何更改都需要全部重新构建所有软件包。与 Yocto 相比,这个问题通过最小的镜像大小和构建时间得到了一定的解决,但在你调整配置时可能会导致构建时间过长。

中间软件包状态缓存默认情况下未启用,并且不像 Yocto 实施那么彻底。这意味着,虽然第一次构建可能比等效的 Yocto 构建短,但后续构建可能需要重建许多组件。

建议

对于大多数应用程序,使用 Buildroot 进行下一个嵌入式 Linux 设计是一个不错的选择。如果您的设计需要多种硬件类型或其他差异,但由于同步多个配置的复杂性,您可能需要重新考虑,但对于由单一设置组成的系统,Buildroot 可能适合您。

OpenWRT/LEDE

OpenWRT 项目开始为消费类路由器开发定制固件。您当地零售商提供的许多低成本路由器都可以运行 Linux 系统,但可能无法开箱即用。这些路由器的制造商可能无法提供频繁的更新来解决新的威胁,即使他们这样做,安装更新镜像的机制也很困难且容易出错。 OpenWRT 项目为许多已被其制造商放弃的设备生成更新的固件镜像,让这些设备焕发新生。

OpenWRT 项目的主要交付物是可用于大量商业设备的二进制镜像。它有网络可访问的软件包存储库,允许设备最终用户将新软件添加到他们的系统中。 OpenWRT 构建系统是一个通用构建系统,它允许开发人员创建自定义版本以满足他们自己的需求并添加新软件包,但其主要重点是目标二进制文件。

优点

如果您正在为商业设备寻找替代固件,则 OpenWRT 应位于您的选项列表中。它的维护良好,可以保护您免受制造商固件无法解决的问题。您也可以添加额外的功能,使您的设备更有用。

如果您的嵌入式设计专注于网络,则 OpenWRT 是一个不错的选择。网络应用程序是 OpenWRT 的主要用例,您可能会发现许多可用的软件包。

缺点

OpenWRT 对您的设计限制很多(与 Yocto 和 Buildroot 相比)。如果这些决定不符合您的设计目标,则可能需要进行大量的修改。

在部署的设备中允许基于软件包的更新是很难管理的。按照其定义,这会导致与您的 QA 团队测试的软件负载不同。此外,很难保证大多数软件包管理器的原子安装,以及错误的电源循环可能会使您的设备处于不可预知的状态。

建议

OpenWRT 是爱好者项目或商用硬件再利用的不错选择。它也是网络应用程序的不错选择。如果您需要从默认设置进行大量定制,您可能更喜欢 Buildroot 或 Yocto。

桌面发行版

设计嵌入式 Linux 系统的一种常见方法是从桌面发行版开始,例如 DebianRed Hat,并删除不需要的组件,直到安装的镜像符合目标设备的占用空间。这是 Raspberry Pi 平台流行的 Raspbian发行版的方法。

优点

这种方法的主要优点是熟悉。通常,嵌入式 Linux 开发人员也是桌面 Linux 用户,并且精通他们的选择发行版。在目标上使用类似的环境可能会让开发人员更快地入门。根据所选的分布,可以使用 apt 和 yum 等标准封装工具安装许多其他工具。

可以将显示器和键盘连接到目标设备,并直接在那里进行所有的开发。对于不熟悉嵌入式空间的开发人员来说,这可能是一个更为熟悉的环境,无需配置和使用棘手的跨开发平台设置。

大多数桌面发行版可用的软件包数量通常大于前面讨论的嵌入式特定的构建器可用软件包数量。由于较大的用户群和更广泛的用例,您可能能够找到您的应用程序所需的所有运行时包,这些包已经构建并可供使用。

缺点

将目标平台作为您的主要开发环境可能会很慢。运行编译器工具是一项资源密集型操作,根据您构建的代码的多少,这可能会严重妨碍您的性能。

除了一些例外情况,桌面发行版的设计并不适合低资源系统,并且可能难以充分裁剪目标映像。同样,桌面环境中的预设工作流程对于大多数嵌入式设计来说都不理想。以这种方式获得可再现的环境很困难。手动添加和删除软件包很容易出错。这可以使用特定于发行版的工具进行脚本化,例如基于 Debian 系统的 debootstrap。为了进一步提高可再现性,您可以使用配置管理工具,如 CFEngine(我的雇主 Mender.io 完整披露了 这一工具)。但是,您仍然受发行版提供商的支配,他们将更新软件包以满足他们的需求,而不是您的需求。

建议

对于您打算推向市场的产品,请谨慎使用此方法。这对于爱好者应用程序来说是一个很好的模型;但是,对于需要支持的产品,这种方法很可能会遇到麻烦。虽然您可能能够获得更快的起步,但从长远来看,您可能会花费您的时间和精力。

其他考虑

这个讨论集中在构建系统的功能上,但通常有非功能性需求可能会影响您的决定。如果您已经选择了片上系统(SoC)或电路板,则您的选择很可能由供应商决定。如果您的供应商为特定系统提供板级支持包(BSP),使用它通常会节省相当多的时间,但请研究 BSP 的质量以避免在开发周期后期发生问题。

如果您的预算允许,您可能需要考虑为目标操作系统使用商业供应商。有些公司会为这里讨论的许多选项提供经过验证和支持的配置,除非您拥有嵌入式 Linux 构建系统方面的专业知识,否则这是一个不错的选择,可以让您专注于核心能力。

作为替代,您可以考虑为您的开发人员进行商业培训。这可能比商业操作系统供应商便宜,并且可以让你更加自给自足。这是快速找到您选择的构建系统基础知识的学习曲线。

最后,您可能已经有一些开发人员拥有一个或多个系统的经验。如果你的工程师有倾向性,当你做出决定时,肯定值得考虑。

总结

构建嵌入式 Linux 系统有多种选择,每种都有优点和缺点。将这部分设计放在优先位置至关重要,因为在以后的过程中切换系统的成本非常高。除了这些选择之外,还有新的系统在开发中。希望这次讨论能够为评估新的系统(以及这里提到的系统)提供一些背景,并帮助您为下一个项目做出坚实的决定。


via: https://opensource.com/article/18/6/embedded-linux-build-tools

作者:Drew Moseley 选题:lujun9972 译者:LHRChina 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

便宜的物联网板的普及意味着它不仅会控制应用程序,还会控制整个软件平台。 那么,如何构建一个针对特定用途的交叉编译应用程序的自定义发行版呢? 正如 Michael J. Hammel 在这里解释的那样,它并不像你想象的那么难。

为什么要定制?

以前,许多嵌入式项目都使用现成的发行版,然后出于种种原因,再将它们剥离到只剩下基本的必需的东西。首先,移除不需要的包以减少占用的存储空间。在启动时,嵌入式系统一般不需要大量的存储空间以及可用存储空间。在嵌入式系统运行时,可能从非易失性内存中拷贝大量的操作系统文件到内存中。第二,移除用不到的包可以降低可能的攻击面。如果你不需要它们就没有必要把这些可能有漏洞的包挂在上面。最后,移除用不到包可以降低发行版管理的开销。如果在包之间有依赖关系,意味着任何一个包请求从上游更新,那么它们都必须保持同步。那样可能就会出现验证噩梦。

然而,从一个现有的发行版中去移除包并不像说的那样容易。移除一个包可能会打破与其它包保持的各种依赖关系,以及可能在上游的发行版管理中改变依赖。另外,由于一些包原生集成在引导或者运行时进程中,它们并不能轻易地简单地移除。所有这些都是项目之外的平台的管理,并且有可能会导致意外的开发延迟。

一个流行的选择是使用上游发行版供应商提供的构建工具去构建一个定制的发行版。无论是 Gentoo 还是 Debian 都提供这种自下而上的构建方式。这些构建工具中最为流行的可能是 Debian 的 debootstrap 实用程序。它取出预构建的核心组件并允许用户去精选出它们感兴趣的包来构建用户自己的平台。但是,debootstrap 最初仅在 x86 平台上可用,虽然,现在有了 ARM(也有可能会有其它的平台)选项。debootstrap 和 Gentoo 的 catalyst 仍然需要从本地项目中将依赖管理移除。

一些人认为让别人去管理平台软件(像 Android 一样)要比自己亲自管理容易的多。但是,那些发行版都是多用途的,当你在一个轻量级的、资源有限的物联网设备上使用它时,你可能会再三考虑从你手中被拿走的任何资源。

系统引导的基石

一个定制的 Linux 发行版要求许多软件组件。其中第一个就是 工具链 toolchain 。工具链是用于编译软件的一套工具集。包括(但不限于)一个编译器、链接器、二进制操作工具以及标准的 C 库。工具链是为一个特定的目标硬件设备专门构建的。如果一个构建在 x86 系统上的工具链想要用于树莓派,那么这个工具链就被称为交叉编译工具链。当在内存和存储都十分有限的小型嵌入式设备上工作时,最好是使用一个交叉编译工具链。需要注意的是,即便是使用像 JavaScript 这样的需要运行在特定平台的脚本语言为特定用途编写的应用程序,也需要使用交叉编译工具链编译。

图 1. 编译依赖和引导顺序

交叉编译工具链用于为目标硬件构建软件组件。需要的第一个组件是 引导加载程序 bootloader 。当计算机主板加电之后,处理器(可能有差异,取决于设计)尝试去跳转到一个特定的内存位置去开始运行软件。那个内存位置就是保存引导加载程序的地方。硬件可能有内置的引导加载程序,它可能直接从它的存储位置或者可能在它运行前首先拷贝到内存中。也可能会有多个引导加载程序。例如,第一阶段的引导加载程序可能位于硬件的 NAND 或者 NOR 闪存中。它唯一的功能是设置硬件以便于执行第二阶段的引导加载程序——比如,存储在 SD 卡中的可以被加载并运行的引导加载程序。

引导加载程序能够从硬件中取得足够的信息,将 Linux 加载到内存中并跳转到正确的位置,将控制权有效地移交到 Linux。Linux 是一个操作系统。这意味着,在这种设计中,它除了监控硬件和向上层软件(也就是应用程序)提供服务外,它实际上什么都不做。Linux 内核 中通常是各种各样的固件块。那些预编译的软件对象,通常包含硬件平台使用的设备的专用 IP(知识资产)。当构建一个定制发行版时,在开始编译内核之前,它可能会要求获得一些 Linux 内核源代码树没有提供的必需的固件块。

应用程序保存在根文件系统中,这个根文件系统是通过编译构建的,它集合了各种软件库、工具、脚本以及配置文件。总的来说,它们都提供各种服务,比如,网络配置和 USB 设备挂载,这些都是将要运行的项目应用程序所需要的。

总的来说,一个完整的系统构建要求下列的组件:

  1. 一个交叉编译工具链
  2. 一个或多个引导加载程序
  3. Linux 内核和相关的固件块
  4. 一个包含库、工具以及实用程序的根文件系统
  5. 定制的应用程序

使用适当的工具开始构建

交叉编译工具链的组件可以手工构建,但这是一个很复杂的过程。幸运的是,现有的工具可以很容易地完成这一过程。构建交叉编译工具链的最好工具可能是 Crosstool-NG,这个工具使用了与 Linux 内核相同的 kconfig 菜单系统来构建工具链的每个细节和方面。使用这个工具的关键是,为目标平台找到正确的配置项。配置项通常包含下列内容:

  1. 目标架构,比如,是 ARM 还是 x86。
  2. 字节顺序:小端字节顺序(一般情况下,Intel 采用这种顺序)还是大端字节顺序(一般情况下,ARM 或者其它的平台采用这种顺序)。
  3. 编译器已知的 CPU 类型,比如,GCC 可以使用 -mcpu--with-cpu
  4. 支持的浮点类型,如果有的话,比如,GCC 可以使用 -mfpu--with-fpu
  5. 二进制实用工具 binutils 、C 库以及 C 编译器的特定版本信息。

图 2. Crosstool-NG 配置菜单

前四个一般情况下可以从处理器制造商的文档中获得。对于较新的处理器,它们可能不容易找到,但是,像树莓派或者 BeagleBoards(以及它们的后代和分支),你可以在像 嵌入式 Linux Wiki 这样的地方找到相关信息。

二进制实用工具、C 库、以及 C 编译器的版本,将与任何第三方提供的其它工具链分开。首先,它们中的每一个都有多个提供者。Linaro 为最新的处理器类型提供了最先进的版本,同时致力于将该支持合并到像 GNU C 库这样的上游项目中。尽管你可以使用各种提供者的工具,你可能依然想去使用现成的 GNU 工具链或者相同的 Linaro 版本。

在 Crosstool-NG 中的另外的重要选择是 Linux 内核的版本。这个选择将得到用于各种工具链组件的 头文件 headers ,但是它没有必要一定与你在目标硬件上将要引导的 Linux 内核相同。选择一个不比目标硬件的内核更新的 Linux 内核是很重要的。如果可能的话,尽量选择一个比目标硬件使用的内核更老的长周期支持(LTS)的内核。

对于大多数不熟悉构建定制发行版的开发者来说,工具链的构建是最为复杂的过程。幸运的是,大多数硬件平台的二进制工具链都可以想办法得到。如果构建一个定制的工具链有问题,可以在线搜索像 嵌入式 Linux Wiki 这样的地方去查找预构建工具链。

引导选项

在构建完工具链之后,接下来的工作是引导加载程序。引导加载程序用于设置硬件,以便于越来越复杂的软件能够使用这些硬件。第一阶段的引导加载程序通常由目标平台制造商提供,它通常被烧录到类似于 EEPROM 或者 NOR 闪存这类的在硬件上的存储中。第一阶段的引导加载程序将使设备从这里(比如,一个 SD 存储卡)开始引导。树莓派的引导加载程序就是这样的,它样做也就没有必要再去创建一个定制引导加载程序。

尽管如此,许多项目还是增加了第二阶段的引导加载程序,以便于去执行一个多样化的任务。在无需使用 Linux 内核或者像 plymouth 这样的用户空间工具的情况下提供一个启动动画,就是其中一个这样的任务。一个更常见的第二阶段引导加载程序的任务是去提供基于网络的引导或者使连接到 PCI 上的磁盘可用。在那种情况下,一个第三阶段的引导加载程序,比如 GRUB,可能才是让系统运行起来所必需的。

最重要的是,引导加载程序加载 Linux 内核并使它开始运行。如果第一阶段引导加载程序没有提供一个在启动时传递内核参数的机制,那么,在第二阶段的引导加载程序中就必须要提供。

有许多的开源引导加载程序可以使用。U-Boot 项目 通常用于像树莓派这样的 ARM 平台。CoreBoot 一般是用于像 Chromebook 这样的 x86 平台。引导加载程序是特定于目标硬件专用的。引导加载程序的选择总体上取决于项目的需求以及目标硬件(可以去网络上在线搜索开源引导加载程序的列表)。

现在到了 Linux 登场的时候

引导加载程序将加载 Linux 内核到内存中,然后去运行它。Linux 就像一个扩展的引导加载程序:它进行进行硬件设置以及准备加载高级软件。内核的核心将设置和提供在应用程序和硬件之间共享使用的内存;提供任务管理器以允许多个应用程序同时运行;初始化没有被引导加载程序配置的或者是已经配置了但是没有完成的硬件组件;以及开启人机交互界面。内核也许不会配置为在自身完成这些工作,但是,它可以包含一个嵌入的、轻量级的文件系统,这类文件系统大家熟知的有 initramfs 或者 initrd,它们可以独立于内核而创建,用于去辅助设置硬件。

内核操作的另外的事情是去下载二进制块(通常称为固件)到硬件设备。固件是用特定格式预编译的对象文件,用于在引导加载程序或者内核不能访问的地方去初始化特定硬件。许多这种固件对象可以从 Linux 内核源仓库中获取,但是,还有很多其它的固件只能从特定的硬件供应商处获得。例如,经常由它们自己提供固件的设备有数字电视调谐器或者 WiFi 网卡等。

固件可以从 initramfs 中加载,也或者是在内核从根文件系统中启动 init 进程之后加载。但是,当你去创建一个定制的 Linux 发行版时,创建内核的过程常常就是获取各种固件的过程。

轻量级核心平台

Linux 内核做的最后一件事情是尝试去运行一个被称为 init 进程的专用程序。这个专用程序的名字可能是 init 或者 linuxrc 或者是由加载程序传递给内核的名字。init 进程保存在一个能够被内核访问的文件系统中。在 initramfs 这种情况下,这个文件系统保存在内存中(它可能是被内核自己放置到那里,也可能是被引导加载程序放置在那里)。但是,对于运行更复杂的应用程序,initramfs 通常并不够完整。因此需要另外一个文件系统,这就是众所周知的根文件系统。

图 3. 构建 root 配置菜单

initramfs 文件系统可以使用 Linux 内核自身构建,但是更常用的作法是,使用一个被称为 BusyBox 的项目去创建。BusyBox 组合许多 GNU 实用程序(比如,grep 或者 awk)到一个单个的二进制文件中,以便于减小文件系统自身的大小。BusyBox 通常用于去启动根文件系统的创建过程。

但是,BusyBox 是特意轻量化设计的。它并不打算提供目标平台所需要的所有工具,甚至提供的工具也是经过功能简化的。BusyBox 有一个“姊妹”项目叫做 Buildroot,它可以用于去得到一个完整的根文件系统,提供了各种库、实用程序,以及脚本语言。像 Crosstool-NG 和 Linux 内核一样,BusyBox 和 Buildroot 也都允许使用 kconfig 菜单系统去定制配置。更重要的是,Buildroot 系统自动处理依赖关系,因此,选定的实用程序将会保证该程序所需要的软件也会被构建并安装到 root 文件系统。

Buildroot 可以用多种格式去生成一个根文件系统包。但是,需要重点注意的是,这个文件系统是被归档的。单个的实用程序和库并不是以 Debian 或者 RPM 格式打包进去的。使用 Buildroot 将生成一个根文件系统镜像,但是它的内容不是单独的包。即使如此,Buildroot 还是提供了对 opkg 和 rpm 包管理器的支持的。这意味着,虽然根文件系统自身并不支持包管理,但是,安装在根文件系统上的定制应用程序能够进行包管理。

交叉编译和脚本化

Buildroot 的其中一个特性是能够生成一个临时树。这个目录包含库和实用程序,它可以被用于去交叉编译其它应用程序。使用临时树和交叉编译工具链,使得在主机系统上而不是目标平台上对 Buildroot 之外的其它应用程序编译成为可能。使用 rpm 或者 opkg 包管理软件之后,这些应用程序可以在运行时使用包管理软件安装在目标平台的根文件系统上。

大多数定制系统的构建都是围绕着用脚本语言构建应用程序的想法去构建的。如果需要在目标平台上运行脚本,在 Buildroot 上有多种可用的选择,包括 Python、PHP、Lua 以及基于 Node.js 的 JavaScript。对于需要使用 OpenSSL 加密的应用程序也提供支持。

接下来做什么

Linux 内核和引导加载程序的编译过程与大多数应用程序是一样的。它们的构建系统被设计为去构建一个专用的软件位。Crosstool-NG 和 Buildroot 是 元构建 metabuild 。元构建是将一系列有自己构建系统的软件集合封装为一个构建系统。可靠的元构建包括 YoctoOpenEmbedded。Buildroot 的好处是可以将更高级别的元构建进行轻松的封装,以便于将定制 Linux 发行版的构建过程自动化。这样做之后,将会打开 Buildroot 指向到项目专用的缓存仓库的选项。使用缓存仓库可以加速开发过程,并且可以在无需担心上游仓库变化的情况下提供构建快照。

一个实现高级构建系统的示例是 PiBox。PiBox 就是封装了在本文中讨论的各种工具的一个元构建。它的目的是围绕所有工具去增加一个通用的 GNU Make 目标架构,以生成一个核心平台,这个平台可以构建或分发其它软件。PiBox 媒体中心和 kiosk 项目是安装在核心平台之上的应用层软件的实现,目的是用于去产生一个构建平台。Iron Man 项目 是为了家庭自动化的目的而扩展了这种应用程序,它集成了语音管理和物联网设备的管理。

但是,PiBox 如果没有这些核心的软件工具,它什么也做不了。并且,如果不去深入了解一个完整的定制发行版的构建过程,那么你将无法正确运行 PiBox。而且,如果没有 PiBox 开发团队对这个项目的长期奉献,也就没有 PiBox 项目,它完成了定制发行版构建中的大量任务。


via: http://www.linuxjournal.com/content/custom-embedded-linux-distributions

作者:Michael J.Hammel 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出