CPU架构对开发环境的影响

最近更换了新的笔记本 M4 Pro,之前是2019款的 MacBook,处理器是 Intel i7。

在使用新电脑进行开发的过程中,遇到一个问题就是构建Docker镜像的时候,发现有个警告。意思是基础镜像是 linux/amd64 平台的,而当前预期使用 linux/arm64:

docker-build-warning

在 MacBook 上运行时,可以运行,但有类似的警告:

docker-run-warning

在 Linux 拉取该镜像会直接失败:

docker-run-error

我们先了解一下CPU的架构、程序的组成,最终我们会理解,为什么有上边的警告和失败。

一、CPU架构与程序

ARM与Intel

apple-chip

MacBook的 Apple 芯片版本(如M4、M4 Pro、M3等)基于ARM架构设计,与之前基于 Intel 架构的 i7、i9 芯片采用不同的 CPU 架构。

ARM架构之前主要用于移动设备和嵌入式系统,因为其功耗较低 ;而Intel架构的芯片由于性能较强,多用于笔记本、台式机和服务器。

近些年,苹果出于战略考虑,使用自主设计的M系列芯片。在性能与能效的提升、软硬件深度优化、高度集成的SoC设计等方面,都有比较明显的收益。

arm-vs-intel

ARM 架构和 Intel 架构(x86/x64架构)最核心的区别是他们的指令集体系结构(Instruction-Set Archietecture, ISA)不同:ARM(Advanced RISC Machine)是基于RISC(精简指令集),强调简单和高效;而x86/x64架构是基于CISC(复杂指令集),强调功能丰富和灵活性,包括上千条指令

前边提到的「ARM 功耗低而Intel性能高」以及应用场景的不同,只是这个核心区别的一个的结果。

上边提到的AMD64,x64于1999年由AMD设计,AMD首次公开64位集以扩展给x86,Intel 的 64 位处理器也兼容这个架构。Intel 称之为 "Intel 64",RPM包管理以称之为 "x86-64" 或 "x86_64",Microsoft称之为 "x64",Linux发行版则使用 "amd64"。比如在Ubuntu的官方文档中,我们也能看到这些称呼方式:

arch-names

为什么程序需要适配不同架构

我们知道一个程序的编译过程如下图,会经过预处理器、编译器、汇编器、链接器最终得到可执行的二进制目标文件。

compilation-system

其中汇编语言 ,使用人类可读的符号和助记符来表示二进制的机器指令和操作数,可以被认为是机器码的符号化表示。

汇编器(Assembler)负责将汇编代码转换成相应的机器码。你肯定也听过反汇编,是将二进制目标文件反向转换成可读的汇编代码。

这里提到的机器指令 也就是上边说的CPU指令集中的指令,因此汇编语言是架构依赖的,不同架构的汇编语言是不同的。

反过来说,ARM架构下的可执行二进制文件拿到 AMD64 架构上不能用,因为其机器指令就不一样。再往前推一步,ARM架构上的编译器构建出的.s文件和AMD64架构上的也不一样。

平台(platform) = 操作系统 x 处理器架构

再说回上图,我们经常说的 C 语言可以跨平台移植

比如一个 Hello World 的C代码,可以在 Linux 上编译出来运行,Windows上也可以;可以在 ARM 架构的机器上编译出来,AMD64 架构上也可以。

但在 Linux 上构建出来的二进制文件,放到 Windows 上是不能运行的。

这里常说的平台,通常是指 操作系统 x 处理器架构。除了上边解释了处理器架构影响二进制结果,操作系统也会有影响,因为不同的操作系统有不同的ABI(应用二进制接口)、系统调用机制、目标文件格式(如ELF、PE等)。

后边我们也会看到通过 platform 参数指定特定平台,构建适用于不同平台的镜像:

build-platform-demo

操作系统是程序

前边介绍了说「程序是可执行的二进制文件」,其实操作系统也可以认为是一种可执行的二进制文件。

我们可以从构建使用来理解一下,操作系统这一特殊的程序,也是需要适配不同的处理器架构的。

先说构建 ,我们可以在 AMD64 的环境中,来构建 ARM 架构的 Linux 内核 ,这里需要使用 ARCH 指定架构,使用 CROSS_COMPILE 来指定交叉编译工具的前缀:

cross-compile-arm-linux

再说使用,我们这里以发行版 Ubuntu 为例,其官方文档支持 amd64、i386、arm64 等很多主流架构:

ubuntu-supported-architectures

你在页面可以直接下载安装镜像,而没有让用户选择架构,是因为页面可以通过浏览器获得一些平台信息,包括架构、操作系统版本等信息:

chrome-request-with-arch

二、Docker容器与镜像

前面有提到交叉编译(Cross Compilation) ,意思是指在一种平台上编译程序,以便在另一种平台上运行

同样地,我们也可以在一种平台上,构建另外一种平台上可以运行的镜像

容器是程序

我这样描述不太准确,但是为了让大家理解为什么不同的平台上需要特定的镜像

更准确的应该是,运行的容器是进程 ,所以要运行的文件也是需要适配平台的------就是镜像。

前面的文章 《揭秘容器(三):容器镜像》镜像索引小节中有介绍,OCI容器镜像由所谓的 manifest(清单) 、配置、层集和可选的镜像索引组成。清单内容如下,是一个对象数组,每个对象中有通过 platform 指定操作系统和架构。

docker-index-json-content

通过这种方式,Docker镜像就可以支持多平台。在不同的平台上要运行,选择合适的平台版本即可。

接下来,我们看看 ARM64 平台和 AMD64 平台上交叉构建和使用尽享的过程。下文为了表达更清晰,我直接用Mac M4 替换 ARM64。

Buildx 和 QEMU 仿真器

Docker Buildx 是 Docker 的一个扩展工具。提供了强大构建功能:

  • 多平台构建:Buildx 允许你在一个构建过程中生成适用于多个平台(如 x86_64、ARM、ARM64 等)的镜像。
  • 高级构建功能:支持更复杂的构建场景,如多阶段构建、缓存优化、外部存储驱动等。
  • 并行构建:可以并行构建多个镜像,提升构建速度。

QEMU 是一个开源的仿真器和虚拟机管理器,能够仿真多种不同的硬件平台:

  • 跨平台仿真:允许在一个平台上运行为另一个平台编译的二进制文件。例如,在 x86_64 主机上运行 ARM64 的二进制文件。
  • 开发和测试:开发者可以在本地开发和测试为不同架构编写的代码,而无需实际的硬件。
  • 容器仿真:与 Docker 结合使用,允许在不同架构的主机上运行不同架构的容器镜像。

Docker Desktop通常已经包含了 Buildx 插件,并且整合了 QEMU。

M4上构建x64镜像

我们直接使用一个最简单的 Dockerfile:

css 复制代码
FROM ubuntu:24.04

M4 MacBook上可以利用 Docker 的Buildx工具,来构建可以在 x86 架构上运行的 Docker 镜像。

build-multiple-arch

我们可以看到构建的过程中,有使用两种不同的镜像。

使用 docker buildx 构建的多平台镜像不会直接显示在 docker image ls 列表中。可以将其 push 到远端,然后再进行使用。构建并 push 的指令:

bash 复制代码
docker buildx build --platform linux/amd64,linux/arm64 -t xxx.com/wlbcoder/ubuntu:base --push .

这样我们就可以再直接使用 docker run 来运行了。为了比较,我这里在 M4 MacBook 和 Intel 的开发机上分别运行镜像,你会看到他们使用的是不同的架构

docker-run-on-diff-arch

M4上运行x64镜像

你可以直接拉取并运行 amd64 架构的镜像,Docker 会自动使用 QEMU 进行仿真。

下边是在 MacBook 上运行两种环境的镜像的比较,第二条指令中的 --platform=linux/amd64 指定了要运行的镜像架构。

docker-run-diff-arch-on-mac

Docker 会自动处理架构仿真,除了platform之外,不需要用户额外的操作。

三、其他

关于架构和程序的话题,你还能想到哪些?可以评论补充

.jar包不算程序

前边说 C 的 Hello World 构建出来之后是可执行二进制文件,放到不同平台通常不能运行。而 Java 的 Hello World 会生成 .jar 包则不同,.jar 包是 Java 应用程序的打包格式,与平台无关的中间表示形式------字节码,可以在任何安装了 Java 虚拟机(JVM)的平台上运行。JVM 负责将字节码翻译成特定平台的机器码并执行。

.jar 包的运行,依赖目标平台上有兼容的 JVM,因此 .jar 包不能算上完整的程序。这点就跟 Python 代码需要解释器运行差不多。

相关推荐
lang201509289 小时前
Spring Boot 入门:5分钟搭建Hello World
java·spring boot·后端
间彧11 小时前
Windows Server,如何使用WSFC+nginx实现集群故障转移
后端
间彧11 小时前
Nginx + Keepalived 实现高可用集群(Linux下)
后端
间彧11 小时前
在Kubernetes中如何部署高可用的Nginx Ingress Controller?
后端
间彧11 小时前
Ribbon负载均衡器和Nginx负载均衡器有什么区别
后端
间彧11 小时前
Nacos详解与项目实战
后端
间彧11 小时前
nginx、网关Gateway、Nacos、多个服务实例之间的数据链路详解
后端
间彧11 小时前
Nacos与Eureka在性能上有哪些具体差异?
后端
间彧11 小时前
详解Nacos健康状态监测机制
后端
间彧11 小时前
如何利用Nacos实现配置的灰度发布?
后端