Docker 构建 PetaLinux 开发环境应用教程

FPGA/ASIC技术

190人已加入

描述

0. 背景

0.1 PetaLinux 是什么

PetaLinux 是 Xilinx 推出的用于在其自家 SoC 上构建嵌入式 Linux 的一套工具集,集成了编译、调试、仿真等众多工具。

0.2 原有的搭建 PetaLinux 开发环境的方式有什么问题

实验室的项目基于 Xilinx 的 Zynq 系列 SoC 开发,需要使用 PetaLinux 构建嵌入式 Linux 系统,第一步就是搭建开发环境。团队成员平时的主力系统是 Windows,使用虚拟机的方式搭建 Linux 开发环境。搭建开发环境出现困难,或为节省时间的目的时会从其他成员那里复制搭建好环境的虚拟机。这些方式已产生或可能产生的问题可以总结如下:

1. 安装开发环境的依赖稍显复杂,且根据操作系统不同而异,新手不易搞定。安装过程没有被很好的文档化,团队成员重复劳动明显。

2. 统一操作系统版本困难。开发人员(现有成员、未来加入成员、项目其他团队的成员)可能使用着不同的 Linux 发行版和版本,因为他们可能有不同的喜好,版本上喜欢尝鲜或守旧,在既有系统上已经有顺手的开发工具和设置等等。而当大家希望统一开发环境或自行搭建失败时,往往选择复制整个虚拟机镜像。

3. 使用复制整个虚拟机的方式传递开发环境不够灵活。表现为:

使用了一段时间后的虚拟机大小可能动辄四五十个 GB,拷贝时间长,难以通过网络共享。

为保留可回退的环境,可能为虚拟机添加快照,但这会进一步显著增加虚拟机的体积。

传递虚拟机不仅传递了需要的开发环境,还传递了大量无用的软件和个人设置,难以兼顾不同开发者之间的习惯差异。

对于不同项目、用途可能存在多个虚拟机,资源冗余很大。

4. 使用虚拟机性能表现差。主机配置不高时,使用虚拟机(可能会再虚拟机内运行 IDE 等其他开发工具)会经常卡顿,降低了开发效率。

5. 使用为某一开发环境而复制来的虚拟机,迁就其环境不愿再安装合适的开发工具(如 IDE 等)。

0.3 基于 Docker 的解决方案

Docker 作为在很多场景下虚拟机的替代方案备受瞩目,其资源消耗小、为单一应用配置环境、易于通过网络共享等特点很好的解决了上面提到的诸多问题。通过 Docker 来构建 PetaLinux 开发环境,我们可以获得以下优势:

经过压缩的镜像体积只有 1GB 左右,方便网络传输。

很容易再团队中统一开发环境,包括操作系统和各种库。Docker 镜像的只读特性保证了有一个可回退的一致环境。

开发环境中的依赖被 Dockerfile 清晰、明确的记录下来,具有很好的文档效应,方便团队长期共享和维护。

Docker 可以运行在之前的虚拟机的操作系统上,也可以运行在一个“精简”的操作系统上(如 Docker for Windows 的方式),还可以放到服务器上,运行多个容器供团队成员使用。这使得使用 Linux 环境的方式更加多样、灵活,且能减少虚拟机性能原因带来的影响

现在理想很丰满,但现实中还需要经过一番探究和试验,下面就让我们开始。

1. 构建 Docker 镜像

Docker 的优势就在于我们可以使用“代码”来表示需要的环境,它既能描述环境,也能直接指导生成环境,这份“代码”就是 Dockerfile。下面详细的记述了这份 Dockerfile 的每一部分,过程中遇到的问题、解决办法、注意事项等。关于 Dockerfile,可以参考官方的 Dockerfile reference1,以及 Best practices for writing Dockerfiles2。

1.1 设置构建参数

Dockerfile 中允许使用 ARG 指令设置构建时参数,这些参数在 Dockerfile 中具有默认值,在构建时可以通过 --build-arg 参数指定新的值来覆盖默认值。这些参数可以在 Dockerfile 中被引用(引用方式与在 shell 中引用变量一样),但不会出现在最终的镜像里。注意一条 ARG 指令只能指定一个参数,这一点和 ENV 指令是不同的。这里我设置了两个参数如下:
ARG install_dir=/opt
ARG installer_url=172.17.0.1:8000

其中 install_dir 用来指定 PetaLinux 的安装路径,installer_url 用来指定 PetaLinux 安装包的网络地址。如果安装包在互联网上,则这里是一个访问链接,如果安装包在本地,则这里被指定为 Docker 的默认网桥,通过它联通本地网络服务器和构建时的临时容器。关于这一部分,我会在后面详述。

1.2 设置环境变量

通常情况下,PetaLinux 使用一个设置脚本来添加自身的各项工具到环境变量中,在使用相关工具前需要通过 source /settings.sh 来执行脚本。但现在我要制作一个专属于 PetaLinux 的环境,完全可以把环境变量设置好来免去这个步骤。在 Dockerfile 中使用 ENV 指令来设置环境变量:
ENV PETALINUX_VER=2014.4 \
PETALINUX=${install_dir}/petalinux-v2014.4-final
ENV PATH="${PETALINUX}/tools/linux-i386/arm-xilinx-gnueabi/bin:\
${PETALINUX}/tools/linux-i386/arm-xilinx-linux-gnueabi/bin:\
${PETALINUX}/tools/linux-i386/microblaze-xilinx-elf/bin:\
${PETALINUX}/tools/linux-i386/microblazeel-xilinx-linux-gnu/bin:\
${PETALINUX}/tools/linux-i386/petalinux/bin:\
${PETALINUX}/tools/common/petalinux/bin:\
${PATH}"

这里有两点需要注意。一是 ENV 虽然支持并推荐在一条指令下设置多个环境变量,但如果这些环境变量之间存在相互引用的情况,就在分开写了。比如这里设置 PATH 变量时引用了 PETALINUX 变量,它们就不能在同一个 ENV 指令下进行设置了。二是 PATH 这个变量中每个路径之间不可以有空格,否则是搜索不到可执行文件的,所以这里也只能不顾缩进来保证没有空格了。网络上似乎没有什么解决这个问题的讨论,而且在 ENV 指令下我们无法使用任何其他的工具去处理这个字符串。

我们其实也可以把 PetaLinux 提供的 settings.sh 脚本添加到 .bashrc 文件中,使得其每次被自动执行。实际上这个脚本中除了配置环境变量,最后还运行了 PetaLinux 自带的一个环境检查工具,用于检查网络、磁盘剩余空间等信息,使用前述设置环境变量的方式就忽略这个检查工具了。

1.3 安装依赖

在 PetaLinux 的参考指南3中给出了它所依赖的工具和库,然而并不全面和准确。一是因为有些包已经被替代,现在无法获得4;二是对于 32 位库支持5只是一笔带过,并未具体列出;三是有的包可能因非常基础而未列出,但是在 Docker 的基础镜像中却没有包含,如 bc。以下是我测试成功的、在当前基础镜像下需要的所有依赖:
RUN dpkg --add-architecture i386 && \
apt-get update && apt-get install -y --no-install-recommends \
# Required tools and libraries of Petalinux.
# See in: ug1144-petalinux-tools-reference-guide, v2014.4.
tofrodos \
iproute \
gawk \
gcc-4.7 \
git-core \
make \
net-tools \
rsync \
wget \
tftpd-hpa \
zlib1g-dev \
flex \
bison \
bc \
lib32z1 \
lib32gcc1 \
libncurses5-dev \
libncursesw5-dev \
libncursesw5:i386 \
libncurses5:i386 \
libbz2-1.0:i386 \
libc6:i386 \
libstdc++6:i386 \
libselinux1 \
libselinux1:i386 \
# Using expect to install Petalinux automatically.
expect \
&& rm -rf /var/lib/apt/lists/* /tmp/* \
&& ln -fs gcc-4.7 /usr/bin/gcc \
&& ln -fs gcc-ar-4.7 /usr/bin/gcc-ar \
&& ln -fs gcc-nm-4.7 /usr/bin/gcc-nm \
&& ln -fs gcc-ranlib-4.7 /usr/bin/gcc-ranlib

这里我们使用 --no-install-recommends 参数来避免安装不必要的包,并在安装结束后清理 /var/lib/apt/lists/ 和 /tmp/ 目录,以尽可能的使镜像精简。

由于在 Ubuntu 16.04 上安装 GCC 会默认安装 gcc 5 的版本,而 2014.4 版本的 PetaLinux 应该没有适配 gcc 5,会出现很多警告。这里采取的办法是安装 gcc-4.7,并修改符号链接,使 /usr/bin/gcc 指向这一版本的 GCC。事实上我并不确切知道应该安装哪个版本,在 PetaLinux 2016.4 中指定了使用 gcc 4.8。这里影响应该不大,因为真正用于构建项目的交叉编译器是 PetaLinux 自带的。

在网上搜索时发现,一般资料都没有介绍如何直接更改一个软连接的指向。不知道的情况下,就只能先删除再重建这个链接了。最终还是在 Stack Overflow 上找到了答案,其实我们可以使用 -f 选项在一条命令中更改软连接的指向。这个技巧在后面还会用到。

1.4 使用 expect 脚本自动安装 PetaLinux

PetaLinux 的安装包在安装过程中会显示许可证协议,并要求用户输入确认信息。这样的交互方式给我们的自动化处理造成了一点小困难,然而程序员前辈们肯定是不允许这种不能自动化的情况持续的,expect 这个工具就是专门用来自动处理这种需要交互输入的情况的。expect 常常用来处理 SSH 登陆等需要交互输入密钥的情况,它会监视一个程序的输出,并在捕获到了特定的输出后给出一个预设的输入。这里我们使用一个 auto-install.sh 脚本来自动安装 PetaLinux,脚本的内容如下:
#!/usr/bin/env expect
set timeout -1
set install_dir [lindex $argv 0]

spawn ./petalinux-v2014.4-final-installer.run $install_dir
expect "Press Enter to display the license agreements"
send "\r"
expect "*>*"
send "y\r"
expect "*>*"
send "y\r"
expect eof

第一行声明使用 expect 这个工具来解释此脚本,/usr/bin/env 会遍历 PATH 变量来寻找后面的可执行文件,这样避免了依赖于 expect 的安装路径。

第二行设置等待超时,因为 PetaLinux 的安装过程比较慢,这里将其设为 -1,即一直等待。

第三行设置一个变量来接收此脚本的参数,我们借此来指定希望将 PetaLinux 安装到哪个目录下。注意 expect 脚本设置参数的方式和 bash 脚本不同,参数 0 代表我们调用脚本时给出的第一个参数,而在 bash 脚本中,参数 0 代表脚本本身的名字。

第五行用 spawn 命令去执行安装程序。PetaLinux 的安装程序的第一个参数也是安装路径。

接下来我们用 expext 命令来捕获程序的输出,用 send 命令发送预设的输入。安装程序提示你确认协议的语句是这样的:Do you accept this license? [y/N] >,直接用 expext 匹配这一句会有问题,因为至少 [] 在 expect 的语法中是有特定含义的,需要转义。已无心情研究 expect 那奇怪的语法,所幸它有很棒的模糊匹配功能,我们只需要匹配最后一个 > 字符就可以了。

1.5 减小镜像体积

PetaLinux 的安装包在 Xilinx 官方网站上可以下载,但需要先注册,没有固定的下载链接。所以要么需要在构建前把它下载到本地,要么在互联网上寻找一个合适的托管地点,可以提供稳定的下载链接。

PetaLinux 的安装包比较大(2014.4 版有 1.2GB,而 2016.4 已经到了丧心病狂的 8.3 GB),在安装完成后,安装包再留在镜像中已经没有什么意义了,还会显著的增加镜像的体积。这里要理解 Docker 的镜像是由一个个的层(layer)组成的,Dockerfile 中的每一条指令都对应于一层,每一层都是在前一层的基础上进行的增量的改变。这意味着,一旦我们在某一层中引入了一个文件,即使在下一层中将其删除,对体积的减小也无济于事,我们只是无法在最终的容器中“看见”它们而已。如果我们使用 COPY 指令将 PetaLinux 的安装包添加进去,则 COPY 指令会生成一个层,我们无法再把它产生的体积抹除掉。Stack Overflow 上有一个关于这个问题的讨论6,主要提到了三种方式:一是在本地构建一个网络服务器,通过网络的方式传到 Docker 容器的内部,我采用了这种方式,后面详述;二是不能使用 Dockerfile 的方式构建容器,而是在容器中完成安装和清理工作后手动提交更改到镜像;三是使用第三方工具对生成的镜像进行再压缩。

这里使用网络是更好的方式,一方面如果我们在互联网或者私有服务器上存放了安装包,通过更改 installer_url 变量就可以使用新的地址获取文件;另一方面,在本地可以使用 Python 轻松的创建一个 HTTP 服务器。在 Dockerfile 中,我们使用 wget 下载安装包、配置其权限、运行自动安装脚本,最后删除安装包。这些步骤必须在一个 RUN 指令下完成,这样安装包才不会留在最终的镜像里。
WORKDIR $install_dir
COPY ./auto-install.sh .

RUN wget -q $installer_url/petalinux-v2014.4-final-installer.run && \
chmod a+x petalinux-v2014.4-final-installer.run && \
./auto-install.sh $install_dir && \
rm -rf petalinux-v2014.4-final-installer.run

在外部,我使用了一个脚本来封装启动 HTTP 服务器、构建 Docker 镜像、停止服务器的步骤:
#!/usr/bin/env bash
installer_dir=$1
docker_context=`pwd`

echo "Start to build petalinux tools docker image ..."
echo "-----------------------------------------------"

cd $installer_dir
python3 -m http.server &
server_pid=$!

cd $docker_context
installer_ip=`ifconfig docker0 | grep 'inet\s' | awk '{print $2}'`
docker build -t petalinux-docker:2014.4 \
--build-arg installer_url=${installer_ip}:8000 \
.

kill $server_pid
echo "---------------"
echo " Finish. ^_^ "
echo "---------------"

这个脚本的第一个参数是安装包在本地的路径。首先让服务器在后台建立,并记录下其对应的 pid,在完成镜像的构建后再将其杀死。Python 创建的服务器会默认监听 8000 端口。在容器内部(Docker 构建的过程即相当于在临时的容器中执行 Dockerfile 的过程)可以通过 Docker 的默认网桥(docker0)的 IP 地址来访问本地主机。网桥对应的 IP 地址并不是唯一的,Docker 是根据主机中网卡的配置不同,选择一个没有被占用的私有网段(如果 3 类私有 IP 网段都被占用了,Docker 启动时会报错),也可以自行更改,所以这里我们从 ifconfig 的输出中提取 docker0 对应得 IP 地址。docker build 命令中 -t 参数为镜像指定标签,--build-arg 参数用来覆盖我们在 Dockerfile 内部设置的参数,最后一个参数 . 指的是构建环境(build context)为当前路径。注意,如果你将安装包放在了这个构建环境的同一个目录下,一定要通过 .dockerignore 文件来忽略这个安装包文件,因为否则它会被发送到 Docker daemon 上,增加构建时间且毫无用处,除非你要使用 COPY 指令的方式导入安装包。

1.6 其他

PetaLinux 会检查 shell 环境,并推荐使用 Bash。在 Ubuntu:16.04 的镜像中 /bin/sh 这个软连接指向的是 /bin/dash,这里我们将其更改为 /bin/bash。

RUN ln -fs /bin/bash /bin/sh # bash is PetaLinux recommended shell

使用 WORKDIR 指令新建了一个 /workspace 的路径用于连接数据卷。最后一个 WORKDIR 指定的路径就会是进入容器后的所在路径,这一点似乎官方文档没有明说。

WORKDIR /workspac

1.7 镜像的构建

如果安装包放在本地,则如 1.5 节所述,使用 build-image.sh 脚本构建镜像。如果安装包在互联网或本地服务器上,则直接使用 docker build 命令,并使用 installer_url 参数指定访问地址。

2. 测试

你可以自行按照上面的方法自行构建镜像,也可以从 Docker Hub 上下载我上传好的镜像:
docker pull xaljer/petalinux:2014.4

运行容器:
docker run -ti -v /path/to/projects:/workspace xaljer/petalinux:2014.4

在容器中创建工程并编译:
petalinux-create -t project -s
-n
cd
petalinux-build # 构建整个工程,会比较慢

3. 现有问题和下一步工作

3.1 现有问题

PetaLinux 2014.4 支持的原本是 Ubuntu 14.04,但使用此版本的镜像时发现,其软件源似乎有些问题,经常安装失败,故没有使用。

PetaLinux 会提示找不到 tftp,这是因为没有对其进行进一步的配置。如果不使用 tftp 可以忽略这个问题。

在 Docker for Windows 下构建时,可能会出现错误7,将存储驱动更改为 aufs 后可修复。然而 Windows 下构建的镜像仍有其他问题,无法使用,作者尚未对其作更多的测试和探究。

3.2 下一步工作

添加 Vivado SDK 的一些工具。

PetaLinux 工具的名字都有点长,可以考虑在镜像里对常用的操作添加别名。但在容器外部作可能会更方便一些,因为我们不必交互式的进入容器,而是使用 docker exec 来执行命令,此时可以在容器外面为整个命令添加别名。

有了标准化环境,不仅可以在自己的电脑上运行,还希望放在私有服务器上,让大家通过网络访问。要达到这样的目的,一要能通过 SSH 访问容器,二要能在服务器的数据卷和本地计算机之间同步数据(源码及编译结果)。对于 SSH,初步设想可以通过外部的一些 Docker 工具来完成,而不是在容器内部建立 SSH 服务器,因为有多个容器时,要对应多个不同端口等问题。对于数据同步,可以在本地的 Windows 系统上通过 Linux 子系统(WSL)建立 NFS 服务器,在容器内部挂载 NFS,或者通过 Docker 的插件实现直接将远端的 NFS 作为数据卷挂载。

4. 总结

如果并不需要 Docker 的一些优势,我们也可以考虑将 PetaLinux 装进 Windows 的 Linux 子系统(WSL),这样可以有更好的性能和更无缝的操作。

使用虚拟机在 Windows 下搭建嵌入式开发环境是以往非常常用的方式,但也是一种比较笨重的方式。随着一些新的技术、平台的出现,如 Docker 和 WSL,我们可以尝试利用它们搭建开发环境,提升开发的效率。

参考

Dockerfile reference 

Best practices for writing Dockerfiles 

PetaLinux Reference Guide 

unable to locate packag lib32bz2-1-0 

how to run 32 bit app in ubuntu 64 bit 

how to add a file to an image in dockerfile without using the add or copy direct 

fails on ‘tar’ with: “Directory renamed before its status could be extracted” 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分