最后更新:2017-08-11
源码地址:https://github.com/xaljer/petalinux-docker
镜像地址:https://hub.docker.com/r/xaljer/petalinux
本文地址:http://blog.csdn.net/elegant__/article/details/76162435
PetaLinux 是 Xilinx 推出的用于在其自家 SoC 上构建嵌入式 Linux 的一套工具集,集成了编译、调试、仿真等众多工具。
实验室的项目基于 Xilinx 的 Zynq 系列 SoC 开发,需要使用 PetaLinux 构建嵌入式 Linux 系统,第一步就是搭建开发环境。团队成员平时的主力系统是 Windows,使用虚拟机的方式搭建 Linux 开发环境。搭建开发环境出现困难,或为节省时间的目的时会从其他成员那里复制搭建好环境的虚拟机。这些方式已产生或可能产生的问题可以总结如下:
Docker 作为在很多场景下虚拟机的替代方案备受瞩目,其资源消耗小、为单一应用配置环境、易于通过网络共享等特点很好的解决了上面提到的诸多问题。通过 Docker 来构建 PetaLinux 开发环境,我们可以获得以下优势:
现在理想很丰满,但现实中还需要经过一番探究和试验,下面就让我们开始。
Docker 的优势就在于我们可以使用“代码”来表示需要的环境,它既能描述环境,也能直接指导生成环境,这份“代码”就是 Dockerfile。下面详细的记述了这份 Dockerfile 的每一部分,过程中遇到的问题、解决办法、注意事项等。关于 Dockerfile,可以参考官方的 Dockerfile reference1,以及 Best practices for writing Dockerfiles2。
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 的默认网桥,通过它联通本地网络服务器和构建时的临时容器。关于这一部分,我会在后面详述。
通常情况下,PetaLinux 使用一个设置脚本来添加自身的各项工具到环境变量中,在使用相关工具前需要通过 source
来执行脚本。但现在我要制作一个专属于 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 自带的一个环境检查工具,用于检查网络、磁盘剩余空间等信息,使用前述设置环境变量的方式就忽略这个检查工具了。
在 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
选项在一条命令中更改软连接的指向。这个技巧在后面还会用到。
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 那奇怪的语法,所幸它有很棒的模糊匹配功能,我们只需要匹配最后一个 >
字符就可以了。
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
指令的方式导入安装包。
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.5 节所述,使用 build-image.sh
脚本构建镜像。如果安装包在互联网或本地服务器上,则直接使用 docker build
命令,并使用 installer_url
参数指定访问地址。
你可以自行按照上面的方法自行构建镜像,也可以从 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 <project-name>
cd <project-name>
petalinux-build # 构建整个工程,会比较慢
PetaLinux 2014.4 支持的原本是 Ubuntu 14.04,但使用此版本的镜像时发现,其软件源似乎有些问题,经常安装失败,故没有使用。
PetaLinux 会提示找不到 tftp,这是因为没有对其进行进一步的配置。如果不使用 tftp 可以忽略这个问题。
aufs
后可修复。然而 Windows 下构建的镜像仍有其他问题,无法使用,作者尚未对其作更多的测试和探究。docker exec
来执行命令,此时可以在容器外面为整个命令添加别名。如果并不需要 Docker 的一些优势,我们也可以考虑将 PetaLinux 装进 Windows 的 Linux 子系统(WSL),这样可以有更好的性能和更无缝的操作。
使用虚拟机在 Windows 下搭建嵌入式开发环境是以往非常常用的方式,但也是一种比较笨重的方式。随着一些新的技术、平台的出现,如 Docker 和 WSL,我们可以尝试利用它们搭建开发环境,提升开发的效率。