Blog:
深入了解 Torizon Extension

Tuesday, December 14, 2021

Torizon 是一种基于容器形式开发应用的操作系统。这不仅是指用户的应用在容器里面运行,应用的编译和调试也是需要借助容器。为了便于用户开发,Toradex 推出的Visual Studio Code/ Visual Studio Torizon Extension 能够完成后台的容器配置,便于用户使用。本文将介绍 Torizon Extension 后台实现的流程,从而帮助用户更好地调试应用程序。

总体来讲,开发语言可以分为两类,一种需要编译后在目标设备上运行,例如 C/C++、Qt 等 ,另外一种是可以直接解释运行,如 Python。这里我们以需要编译的开发语言 C/C++ 为例进行说明。对于传统的嵌入式 Linux C/C++ 应用开发,首先要在开发电脑上安装交叉编译工具或者包含交叉编译工具的 SDK,然后编写应用代码,用编译工具进行交叉编译,最后把编译好的二进制文件部署到目标设备上进行调试,常见的工具如 GDB。Torizon 的总体流程也是如此,但最大的区别是交叉编译工具的安装、编译和调试并不需要用户手动运行相关指令,而是 Torizon Extension 会在开发电脑上自动创建一个 SDK 容器,里面包含所需的交叉编译工具和头文件等,用户的代码在容器里面进行编译并通过 GDB 调试。由于采用的是容器形式,因此 Torizon 的开发电脑可以是 Linux,也可以是 Windows 系统,Windows 需要安装 WSL

构建 SDK 容器

当用户在 Visual Studio Code/ Visual Studio 上安装了 Torizon Extension 之后,总体情况如下。在开发电脑和计算机模块上都将运行 Docker。Torizon Extension 会根据用户代码类型,如 C/C++、.Net 自动生成合适的 SDK 容器。用户代码文件通过 Torizon Extension 在 SDK 容器中使用合适的编译工具进行编译。然后 Torizon Extension 再生成一个可以部署到计算机模块上运行的 release 或者 debug 版本容器。对于 debug 版本容器,Torizon Extension 通过 SDK 容器对其进行调试,如使用 GDB 工具。



下面我们将使用 Visual Studio Code 创建一个单 C 文件的 Hello World 工程,完成编译、调试和部署流程,并介绍这背后 Torizon Extension 是如何生成 SDK 容器、目标设备的 release/debug 版本容器以及如何执行编译、调试和部署任务。

首先在 Visual Studio Code 中创建一个 C/C++ 项目,选择 arm64v8-debian-no-ssh_bullseys 平台。

创建完毕后,点击 Visual Studio Code 中 Terminal → New Terminal, 点击右边 Torizon IDE backend process 标签。在这个终端中会输出 SDK 容器构建时后台日志。

SDK 容器构建成功后,可以在开发电脑的终端上输入下面命令,查看正在运行的容器。

$ docker container ls

这里名为 keen_yalow 即为 Torizon Extension 生成的 SDK 容器。使用下面命令进入 SDK 容器,在容器的 /workspaces/HelloWorld 位置可以看到 Hello World 项目文件,包括 C 文件等。

$ docker exec -it keen_yalow bash

root@dell-5400:/# cd /workspaces/HelloWorld/
root@dell-5400:/workspaces/HelloWorld# ls
HelloWorld.c  appconfig_0

除了开发电脑的终端里使用 docker 命令进入 SDK 容器外,还可以在 Visual Studio Code 中 Terminal → New Terminal 新建一个 bash 终端直接进入 SDK 容器,同样也可以看到项目文件。

这些项目文件同样也位于 Visual Studio Code 的文件浏览器中,用户可以直接对其进行修改。

在开发电脑的终端中使用下面命令,看到 SDK 容器中 /workspaces/HelloWorld 目录实际上 bind 到开发电脑上创建项目时选择的保存目录。

$ docker inspect keen_yalow

回到 Visual Studio Code 中打开的终端中,可以看到 SDK 容器中已经安装了 aarch64-linux-gnu-gcc 等交叉编译工具。这是 Torizon Extension 在创建 SDK 容器时自动安装的工具。用户无需手动安装。

那么 Torizon Extension 是如何构建该 SDK 容器呢?得益于 Torizon Extension 的开源,我们可以一窥其中究竟。在一开始使用 Visual Studio Code 创建一个 C/C++ 项目,我们选择了 arm64v8-debian-no-ssh_bullseys 平台,Torizon Extension 则使用对应的模板文件。其中的 sdk.dockerfile 就是用于生成 SDK 容器的 dockerfile 文件,以及 config.yaml 配置文件。

sdk.dockerfile 中的 FROM #%platform.sdkbaseimage%# 指定 SDK 容器构建的基础容器镜像,sdkbaseimage 的配置位于 config.yaml 文件,即使用 torizon/debian:2-bullseye

baseimage:
common:
   - "torizon/debian"
   - "2-bullseye"

然后通过 apt-get 命令继续安装其他调试所需的软件,如 gdb-multiarch 等。在 sdk.dockerfile 中还有一些其他参数,如 #%application.sdkpreinstallcommands%#,这就允许用户使用 dockerfile 文件编写语法在构建 SDK 容器时能够添加其他的软件和第三方库文件,甚至修改 SDK 容器本身的配置,如更改 debian 下载源。这些具体的参数可以参考这里。当然用户并不需要直接修改这里的配置文件,在 Visual Studio Code 的 Torizon Extension 插件中可以十分方便地进行更改。

至此,我们已经阐释了 Torizon Extension 是如果构建 SDK 容器。用到的工具大致和传统嵌入式 Linux 开发类似,只是这操作都由 Torizon Extension 在容器内完成,并为用户提供灵活的配置功能。上述过程总体如下图。

接下我们将看看编译是如何完成的。


应用程序编译

点击 Terminal -> Run Task -> build_debug 即开始编译 Debug 版本的应用编译。编译的时候在 Visual Studio Code 可以看到下面输出日志。

SDK 容器中实际上执行了 aarch64-linux-gnu-gcc -g HelloWorld.c -o HelloWord 命令对 C 进行编译。本文创建项目是选了单个 C 文件的项目类型,所以编译时仅使用 HelloWorld.c

进入 SDK 容器的 /workspaces/HelloWorld目录,我们可以看到编译生成的二进制可执行文件 HelloWorld。它是针对 ARM aarch64 处理器进行交叉编译的。

之所以能够完成上述编译任务,是由于在创建项目时,Torizon Extension 自动生成了 Visual Studio Code 配置文件 tasks.json。这里定义了 build_debug 任务具体执行的命令参数。用户因此只需要像上面提到的步骤,点击 build_debug 即可开始编译。

编译总体过程如下图。

完成编译后,接下来就是开始调试。


应用程序调试

在编译完成后点击 F5 开始调试用于程序。在 Torizon 上应用是以容器形式运行的,调试过程实际上将应用打包成一个可以在目标设备上运行的容器,并在容器里面启动调试工具。所以在按 F5 之后,Torizon Extension 会开始构建一个包含应用的新容器。

当容器构建完毕并部署到目标设备后在 Visual Studio Code 会进入单步调试界面。

此时我们查看设备上的的镜像 Images 和容器 Containers 会发现一个 debug 版本的 helloworld 镜像和一个正在运行的 debug 版本的 helloworld 容器。这些就是刚才按 F5 开始调试时,Torizon Extension 自动部署过来的。

目标设备上的容器和开发电脑上的 SDK 容器之间通过 gdb 实现调试功能。使用 SSH 登录目标设备,运行下面命令查看正在运行的容器信息。

$ docker container ls

目标设备上 0.0.0.0:32768 映射到容器内部的 6502 端口。

查看容器 ID 并进入容器。通过 ps 命令列出容器中所有运行的进程。这里可以看到有个 gdbserver 进程正在运行并监听 6502 端口。通过该进程,连接开发电脑上SDK 容器中的 gdb 客户端从而实现 Visual Studio Code 的调试功能。

同样在开发电脑查看 SDK 容器内运行的进程,其中另一个是 gdb-multiarch 就是 gdb 客户端。

那么现在我们将揭示 Torizon Extension 是如何为目标设备构建 debug 版本容器,以及在 VS Code 中是如何启动调试任务的。我们回到 Torizon Extension 的开源项目中的 arm64v8-debian-no-ssh_bullseye 模板。其中 debug.dockerfile就是用于生成 debug 版本容器的文件。

首先,FROM --platform=#%platform.architecture%# #%platform.baseimage%# 指定了 debug 版本容器使用的基础镜像,对应在 config.yaml 中配置属性参数。EXPOSE 6502 指定 gbdserver 监听端口。接下来 apt-get 命令安装 gdbserver。这期间还可以通过 #%application.extrapackages%# 来安装第三方软件和库文件,这些具体的参数可以参考这里。最后设置 debug 版本容器启动时运行的命令,CMD stdbuf -oL -eL gdbserver 0.0.0.0:6502 /#%application.appname%#/#%application.exename%# #%application.appargs%#。这里看到启动 gdbserver,并可以接收执行应用程序的参数。最终通过该容器即可在目标设备上启动一个 debug 版本容器,并在容器内使用 gdbserver 与VS Code 连接,调试用户应用程序。

仔细的你可能发现,debug.dockerfile 中并没有将编译好的应用二进制文件包含进来。使用 SSH 登录目标设备,运行下面命令查看正在运行的容器的详细信息。ec85215a7553 为正在运行的 debug 版本容器 ID

$ docker container inspect ec85215a7553

Torizon 系统的 /home/torizon/HelloWorld 目录被映射到 debug 版本容器的 /HelloWorld 目录。Torizon Extension通过 rsync 将开发电脑上编译好的二进制文件 HelloWorld 复制到目标设备上默认用户 toriozn 的用户目录下 /home/torizon/HelloWorld。因此在上面的截图中我们能够看到 debug 版本容器中 gdbserver 其指向的应用路径位于 /HelloWorld/HelloWorld。之所以不将二进制应用直接放入容器的原因是,在调试期间用户应用代码可能会经常做修改,采用这样的方式只需要重新编译应用代码本身,而不必重新构建和部署整个 debug 版本容器。重新编译生成的二进制文件可以重新映射到原来的容器,这样的设计能够极大提高了调试效率。

Visual Studio Code 上按 F5 启动调试时,该任务是在 launch.json 文件中配置的。这里指定 gdb 作为调试器,具体为 gdbmultiarch。详细说明可以参考 VS Code Configuring C/C++ debugging


调试总体过程如下。


最终应用部署

当完成应用调试后,通常生成一个 release 版本最终发布容器。在上面的 debug 版本容器虽然已经包含了用户开发的应用,但也包含了 gdbserver 等额外的工具,并且应用通过 gdbserver 启动。这样的容器显然不适合用于最终的发布。因此我们需要在 VS Code 依次点击 Torizon C/C++:Switch to release configuration -> build_release ->Torizon: Build release container for the application -> Torizon: Deploy release container 生成一个包含用户应用的最终发布版本容器。

进行上面步骤前,在 Torizon Extension 插件中我们先删除之前已经部署和运行的 debug 版本镜像和容器。

然后完成上述构建并部署发布版本容器。在开发电脑的终端上使用 docker images 可以查看到生成的发布版本容器。相比于 debug 版本容器,由于不再安装部分软件,因此发布版本容器体积也会小一些。

部署完毕后,在目标设备上也可以看到这个发布版本容器。

如果你已经看到这里,恭喜,你已经了解 Torizon Extension 后台运行的大部分过程。现在只剩下发布版本容器是如何构建的。这部分则相对简单些。继续回到Torizon Extension 的开源项目中的 arm64v8-debian-no-ssh_bullseye 模板。其中 release.dockerfile 就是用于生成发布版本容器的文件。发布版本容器使用的基础镜像和 debug 版本一样,FROM --platform=#%platform.architecture%# #%platform.baseimage%#,但不再使用 apt-get 安装 gdbserver 等工具。用户指定的第三方软件和库通过 "#%application.extrapackages%#" 仍可以安装。COPY work/#%application.appname%# /#%application.appname%# 将之前编译好的二进制应用文件复制到容器中。最后设置容器启动时运行的命令 CMD /#%application.appname%#/#%application.exename%# #%application.appargs%#。这里我们看到发布版本容器已经不再像 debug.dockerfile 使用 gdbserver 启动应用,而是直接执行用户的应用。这是发布版本容器和debug 版本容器最大的不同。

当通过 VS Code 运行发布版本容器后,在 Torizon Extension 界面中可以看到该容器相关信息。由于演示代码中执行完 printf 函数后程序会直接退出,因此这里看到的容器是停止状态。在下方容器信息窗口中,默认运行的程序是 /HelloWorld/HelloWorld,即上通过 release.dockerfile 生成容器时所添加的二进制文件。

构建和部署发布版本容器总体过程如下。


总结

Torizon Extension 简化了在嵌入式系统上使用容器进行应用开发,在代码编写、调试、部署各个阶段都提供了丰富的功能,采用容器形式的开发方式也有别于传统嵌入式 Linux,希望通过本文的介绍能够使您了解 Torizon Extension 背后的任务流程,从而更好地使用 Torizon。当然 Torizon Extension 的功能也远不止如此。

Author: 胡珊逢,FAE,韬睿(上海)
Share this on:

Leave a comment

Your email ID will be kept confidential. Required fields are marked *



* Your comment will be reviewed and then added. Thank you.

Have a Question?