本文针对在 x86 平台(Ubuntu 系统)如何交叉编译其他平台程序给出思路和解决方案,实践性较强。
一、思路
目的:在 x86 上用 Docker 方式交叉编译 arm 平台程序。有时项目庞大,依赖库之多,以至于用新标准,或 boost 库,不一而足。使用容器方式,既可保证版本环境一致,又可保持宿主机系统之纯净,还可重用编译环境之镜像,一举多得,可谓编译界之大器,不得不学。
二、如何运行 arm 版本容器
x86 无法运行 arm 平台程序,然 qemu 提供用户级别之机制,通过 qemu-arm-static 可以达到目的。在 Ubuntu 上安装:
sudo apt install qemu-user-static
64 位:
docker run -it --rm -v /usr/bin/qemu-aarch64-static:/usr/bin/qemu-aarch64-static arm64v8/ubuntu:16.04 uname -m
32 位:
docker run -it --rm -v /usr/bin/qemu-arm-static:/usr/bin/qemu-arm-static arm32v7/ubuntu:16.04 uname -m
另一种方法,可以借助 qemu-user-static 镜像,此情况下无须手动拷贝 qemu-arm-static。示例:
# 首先注册 qemu 模拟器
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# 32 位
docker run --rm -t arm32v7/ubuntu:16.04 uname -m
# armv7l
# 64 位
docker run --rm -t arm64v8/ubuntu:16.04 uname -m
# aarch64
注:GitHub 上的 multiarch 组织下还有许多跨平台相关的项目可供参考。
此步解决了如何运行容器的问题。
三、交叉编译
下面列出 2 种方式。第 1 种方式控制编译操作进程;第 2 种方便一键操作(当然,国内网络环境非常难达到一键之目的)。
编译方式一:容器内
概述如下:下载 arm 版本的基础镜像,使用上节方式运行容器,再在其中进行编译,至于源码及压缩包,使用 docker cp 命令即可。
# 64 位的:
docker run -it --rm -v /usr/bin/qemu-aarch64-static:/usr/bin/qemu-aarch64-static arm64v8/ubuntu:16.04 bash
# 32 位的:
docker run -it --rm -v /usr/bin/qemu-arm-static:/usr/bin/qemu-arm-static arm32v7/ubuntu:16.04 bash
进入容器后用 getconf LONG_BIT 可确诊系统位数。在此容器中安装 gcc、make 等均为 arm 版本,此时无须再指定 CROSS_COMPILE 变量。安装了交叉编译环境后的镜像可保存备用,如上传到 Docker Hub 上。
编译方式二:容器外
概述如下:编写 Dockerfile 文件,基于 arm 版本的基础镜像,其它如 apt-get install 无须修改。
创建文件 Dockerfile.arm,以示区别。内容概述:
ARG BUILD_FROM=arm64v8
FROM ${BUILD_FROM}/ubuntu:16.04
RUN apt-get update
RUN ...
注意,此处为了兼容不同版本的 arm,因而使用了参数。
首先运行 qemu-user-static 镜像,否则无法执行 arm 版本的构建:
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker build -t metaverse -f Dockerfile.arm --build-arg BUILD_FROM=arm64v8 .
--build-arg 表示指定构建镜像时的参数,须与 Dockerfile 保持一致。
四、未完事宜
在容器中编译、运行问题,即把编译、运行分开,而不是放到一个容器。本文所述与 Docker 版本有关,最低为何种,有说 17.05,但未研究。另外,对内核版本也有要求,测试 4.4 版本无法使用,4.8 版本支持。因此,建议使用较新版本。
五、笔记
此节为笔记,可忽略。进入了 arm 版本的容器,绝大部分的操作与 x86 Ubuntu 完全一致,非常方便。
arm64 版本更新时提示信息:
Get:6 http://ports.ubuntu.com/ubuntu-ports xenial/universe arm64 Packages [9493 kB]
Get:6 http://ports.ubuntu.com/ubuntu-ports xenial/universe arm64 Packages [9493 kB]
Get:7 http://ports.ubuntu.com/ubuntu-ports xenial/multiverse arm64 Packages [146 kB]
arm32 版本如下:
Get:3 http://ports.ubuntu.com/ubuntu-ports xenial-backports InRelease [107 kB]
Get:4 http://ports.ubuntu.com/ubuntu-ports xenial-security InRelease [109 kB]
Get:5 http://ports.ubuntu.com/ubuntu-ports xenial/main armhf Packages [1486 kB]
Get:6 http://ports.ubuntu.com/ubuntu-ports xenial/restricted armhf Packages [8491 B]
Get:7 http://ports.ubuntu.com/ubuntu-ports xenial/universe armhf Packages [9531 kB]
编译 boost 库示例。arm64 版本,执行:
./bootstrap.sh && ./b2 install
输出:
Performing configuration checks
- 32-bit : no
- 64-bit : yes
- arm : no
- mips1 : no
- power : no
- sparc : no
- x86 : no
- combined : no
- has_icu builds : no
- lockfree boost::atomic_flag : no
...
g++: error: unrecognized command line option '-m64'
原因:未检测到 arm 平台。其检测代码在 $BOOST_DIR/libs/config/checks/architecture/arm.cpp 文件:
#if !defined(__arm__) && !defined(__thumb__) && \
!defined(__TARGET_ARCH_ARM) && !defined(__TARGET_ARCH_THUMB) && \
!defined(_ARM) && !defined(_M_ARM)
#error "Not ARM"
#endif
添加 arm 64 的支持:!defined(__aarch64__):
#if !defined(__arm__) && !defined(__thumb__) && \
!defined(__TARGET_ARCH_ARM) && !defined(__TARGET_ARCH_THUMB) && \
!defined(_ARM) && !defined(_M_ARM) && !defined(__aarch64__)
#error "Not ARM"
#endif
修改后,上述 arm 项变成 yes,因为是运行时检测的。
实际上,可以通过参数 architecture 指定平台:
./bootstrap.sh && ./b2 architecture=arm install
此时,当然无法检测,但实际上是 arm 平台。
arm32 版本,执行 ./b2 install,输出:
Performing configuration checks
- 32-bit : yes
- arm : yes
- has_icu builds : yes
- lockfree boost::atomic_flag : yes
因为在 arm.cpp 中默认有 arm 的宏定义,故检测通过。
编译速度依赖于机器性能,在阿里云单核 2GB 主机上交叉编译一套不具名的开源项目,加上涉及 boost 库和 C++14 标准,从 2 月 25 日 13 点开始,至 2 月 26 日 17 时,耗时约 28 小时。