当我们在github下载开源代码的release的时候,通常不只一个构建产物压缩包或安装包,经常会看到x86_64 arm x86 amd64 等字样。这些符号表示什么意思呢?没错,都是表示指令集架构,本文将会围绕芯片,指令集,系统位数和编译器进行一些讨论。
先提出一个问题:世界上拥有如此多的芯片类型,程序员却写着一样的c语言,为什么c语言可以运行在不同的芯片上面? 明明这些芯片中的电路都不一样。
答案就是芯片是遵循指令集架构设计的,只要芯片使用了某种指令集架构,把c语言程序根据对应的指令集架构进行翻译,就可以运行在该芯片上。
ISA 是一份极其详尽的技术规范文档,规定了cpu有多少个寄存器,可以执行哪些操作(比如加法,乘法)?以及如何寻址等。 里面包含了cpu操作的指令,告诉编译器如何把加法操作转化为cpu能看懂的二进制指令。
目前市面上的主流架构分为两种:
| 架构类型 | 特点 | 代表架构 |
|---|---|---|
| 精简指令集 RISC | 指令短小精悍且等长,每条只做最简单的动作(如只加载、只存储)。硬件简单,功耗低,依赖编译器优化。 | ARM、RISC-V、MIPS |
| 复杂指令集 CISC | 指令功能强大且多变,一条指令能干很多事(如直接乘加并存回内存)。硬件复杂,但编译简单。 | x86(英特尔、AMD) |
为了更好的理解指令集架构的差异,举一个简单的例子,通过c语言让stm32的单片机芯片执行a+b的操作。首先我们需要编写对应的c代码,然后在自己的电脑上编译并烧录到单片机中。这里的编译就不能够直接编译,否则c代码编译出来的二进制指令只能在当前电脑上运行(默认是根据当前电脑指令集架构编译,而stm32使用的一般是ARM Cortex-M 指令集架构)。所以需要使用到一种叫做交叉编译的技术,跨架构进行编译,从我们的x86_64的操作系统上编译出ARM Cortex-M的二进制程序。
一般用交叉编译器执行以下命令:
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb main.c -o main.elf
这样就可以将编译出来的main.elf 烧录到单片机中。
编译器的本质是一个文本处理程序,将高级语言根据目标指令集架构进行翻译,所以可以实现在x86上编译arm的程序,这种操作就是交叉编译。
现代编译器一般是三段式架构设计:编译器并不是直接把 C 语言一步变成机器码的,它分成了 前端(Frontend)、中端(Optimizer) 和 后端(Backend) 三个部分。
前端主要是处理语法树,把高级语言翻译成中间语言(例如 llvm IR [Intermediate Representation]) ,中间语言是不依赖架构的一种抽象语言,是纯逻辑的一种中间语言。
中端则是负责对IR进行逻辑优化,去掉没用的死循环和归并一些无效操作。
后端则是基于架构的翻译,把优化后的IR翻译成机器指令,所以选择什么样的后端就会决定编译后的指令是在什么指令架构的芯片上运行。
C 语言代码 (抽象的业务逻辑) │ ┌──────▼──────┐ │ 编译器前端 │ (只管理解 C 语言语法,检查语法错误) └──────┬──────┘ │ ▼ 中间代码 (IR - Intermediate Representation) 【这是一种统一的、不依赖任何 CPU 架构的“世界语”】 │ ┌──────▼──────┐ │ 编译器中端 │ (负责通用逻辑优化,比如去掉没用的死循环) └──────┬──────┘ │ ├──────────────────────┬──────────────────────┐ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ x86 后端 │ │ ARM 后端 │ │ RISC-V 后端 │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ ▼ ▼ ▼ x86 机器码 ARM 机器码 RISC-V 机器码
从这张图不难推理:如果想要发明一个玩具语言,只需要做编译器前端就可以了。剩下的交给llvm,当然离生产级的语言还有十万八千里。
这里介绍一下LLVM ,其全称是 Low Level Virtual Machine(底层虚拟机),但如今它是指一套模块化、可重用的编译器基础设施(框架)。其最伟大的发明就是定义了一套通用的“中间语言”(LLVM IR)。无论用 Clang(C/C++)还是 Rust(rustc)写代码,前端都会先把它翻译成这套统一的 IR。LLVM 的核心精力,就是对这套 IR 做“刀尖上跳舞”般的优化(如死代码消除、循环展开、内联),与具体的编程语言和 CPU 架构完全解耦。
一句话来说就是llvm实现了“写一次前端,跑在所有芯片上”的功能。
最后一个话题就是32位的架构和64位架构区别是什么? 这里存在一个名称陷阱,x86_64和amd64是一样的指令架构,只是因为64位架构由于是amd公司先提出的,intel公司跟进后一开始叫intel 64 但是为了避免商业纠纷就改名叫x86_64,表示是x86 32位架构的扩展。
32位和64位架构的主要区别在于寄存器的位数,比如32位指针的能够寻址的最大空间只有4GB,所以程序最多只能使用4GB的内存,此外就是32位芯片设计的时候寄存器数量也会少于64位,无法直接支持double int64的数据直接存储在寄存器。
本文作者:James
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!