Docker 系列(四十二):X86 平台使用容器交叉编译

本文针对在 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 小时。

系列导航


   转载规则


《Docker 系列(四十二):X86 平台使用容器交叉编译》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Docker 系列(四十三):Nginx 容器实战 Docker 系列(四十三):Nginx 容器实战
Nginx 是开源的高效的 Web 服务器实现,支持 HTTP、HTTPS、SMTP、POP3、IMAP 等协议。官方在 Docker Hub 上维护了官方镜像仓库,提供了 Nginx 1.0 ~ 1.19.x 各个版本的镜像。 启动静态页
2023-06-06
下一篇 
Docker 系列(四十一):Docker Compose Docker 系列(四十一):Docker Compose
Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速地部署分布式应用。本文将介绍 Compose 项目的定位以及安装方式。 Compose 简介Compose 定位为「定义和运行多容器 Docker 应
2023-05-23
  目录