armdocker:在x86上基于容器交叉编译

一、背景

本文针对在x86平台(ubuntu系统)如何交叉编译提出思路和解决方案。具有实践性。

二、思路

目的:在x86上用 docker 方式交叉编译 arm 平台程序。
有时项目庞大,依赖库之多,以至于用新标准,或 boost 库,不一而足。使用容器方式,既可保证版本环境一致,又可保持宿主机系统之纯净,还可重用编译环境之镜像,一举多得,可谓编译界之大器,不得不学。

三、如何运行 arm 版本容器

x86 无法运行 arm 平台程序,然 qemu 提供用户级别之机制,通过 qemu-arm-static 可以达到目的。在 ubuntu 上安装:

1
2
3
4
5
6
7
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。示例:

1
2
3
4
5
# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# docker run --rm -t arm32v7/ubuntu:16.04 uname -m
armv7l
# docker run --rm -t arm64v8/ubuntu:16.04 uname -m
aarch64

注:https://github.com/multiarch 还有许多跨平台的项目。

此步解决了如何运行容器的问题。

四、交叉编译

下面列出2种方式。第1种方式控制编译操作进程。第2种方便一键操作(当然,国内网络环境非常难达到一键之目的)。

4.2 编译方式:容器内

概述如下:下载arm版本的基础镜像,使用上节方式运行容器,再在其中进行编译,至于源码及压缩包,使用docker cp命令即可。

1
2
3
4
5
# 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 变量。
安装了交叉编译环境后的镜像可保存备用,如上传到 dockerhub 上。

4.3 编译方式:容器外

概述如下:编写 Dockerfile 文件,基于arm版本的基础镜像,其它如apt-get install无须修改。

创建文件 Dockerfile.arm,以示区别。内容概述:

1
2
3
4
5
6
ARG BUILD_FROM=arm64v8
FROM ${BUILD_FROM}/ubuntu:16.04

RUN apt-get update

RUN ...

注意,此处为了兼容不同版本的arm,因而使用了参数。

首先运行 qemu-user-static 镜像,否则无须执行 arm 版本的构建:

1
2
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 版本更新时提示信息:

1
2
3
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版本如下:

1
2
3
4
5
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 版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
./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文件:

1
2
3
4
5
#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__)

1
2
3
4
5
#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 指定平台。

1
./bootstrap.sh && ./b2 architecture=arm install

此时,当然无法检测,但实际上是 arm 平台。

arm32 版本:

1
2
3
4
5
6
7
./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小时。