扩展比较 |
musl |
uClibc |
dietlibc |
glibc |
Complete .a set |
426k |
500k |
120k |
2.0M † |
Complete .so set |
527k |
560k |
185k |
7.9M † |
最小的静态C程序 |
1.8k |
5k |
0.2k |
662k |
静态hello(使用printf) |
13k |
70k |
6k |
662k |
动态开销 (min. dirty) |
20k |
40k |
40k |
48k |
静态开销(min. dirty) |
8k |
12k |
8k |
28k |
静态stdio开销 (min. dirty) |
8k |
24k |
16k |
36k |
可配置功能集 |
no |
yes |
最小 |
最小 |
资源枯竭行为 |
musl |
uClibc |
dietlibc |
glibc |
本地线程存储 |
报告失败 |
失败 |
n/a |
失败 |
SIGEV_THREAD计时器 |
无错误 |
n/a |
n/a |
超支失败 |
pthread_cancel |
无错误 |
失败 |
n/a |
失败 |
regcomp与regexec |
报告失败 |
失败 |
报告失败 |
失败 |
fnmatch |
无错误 |
unknown |
无错误 |
报告失败 |
printf类 |
无错误 |
无错误 |
无错误 |
报告失败 |
strtol类 |
无错误 |
无错误 |
无错误 |
无错误 |
性能比较 |
musl |
uClibc |
dietlibc |
glibc |
小额分配和免费 |
0.005 |
0.004 |
0.013 |
0.002 |
大额分配和免费 |
0.027 |
0.018 |
0.023 |
0.016 |
分配争用,本地 |
0.048 |
0.134 |
0.393 |
0.041 |
分配争用,共享 |
0.050 |
0.132 |
0.394 |
0.062 |
零填充(memset) |
0.023 |
0.048 |
0.055 |
0.012 |
字符串长度(strlen) |
0.081 |
0.098 |
0.161 |
0.048 |
字节搜索(strchr) |
0.142 |
0.243 |
0.198 |
0.028 |
子字符串 (strstr) |
0.057 |
1.273 |
1.030 |
0.088 |
线程创建/连接 |
0.248 |
0.126 |
45.761 |
0.142 |
互斥锁/解锁 |
0.042 |
0.055 |
0.785 |
0.046 |
UTF-8解码缓冲 |
0.073 |
0.140 |
0.257 |
0.351 |
UTF-8逐字节解码 |
0.153 |
0.395 |
0.236 |
0.563 |
Stdio putc/getc |
0.270 |
0.808 |
7.791 |
0.497 |
Stdio putc/getc解锁 |
0.200 |
0.282 |
0.269 |
0.144 |
Regex编译 |
0.058 |
0.041 |
0.014 |
0.039 |
Regex搜索(a{25}b) |
0.188 |
0.188 |
0.967 |
0.137 |
Self-exec (静态链接) |
234µs |
245µs |
272µs |
457µs |
Self-exec (动态链接) |
446µs |
590µs |
675µs |
864µs |
ABI和版本控制比较 |
musl |
uClibc |
dietlibc |
glibc |
稳定的ABI |
yes |
no |
非正式 |
yes |
LSB兼容ABI |
不完整 |
no |
no |
yes |
向后兼容性 |
yes |
no |
非正式 |
yes |
前向兼容性 |
yes |
no |
非正式 |
no |
原子升级 |
yes |
no |
no |
no |
符号版本控制 |
no |
no |
no |
yes |
算法比较 |
musl |
uClibc |
dietlibc |
glibc |
子字符串搜索(strstr) |
双向 |
天真的 |
天真的 |
双向 |
正则表达式 |
dfa |
dfa |
原路返回 |
dfa |
排序(qsort) |
平滑排序 |
shellsort |
天真的 quicksort |
向内排序 |
分配器 (malloc) |
musl-native |
dlmalloc |
diet-native |
ptmalloc |
功能比较 |
musl |
uClibc |
dietlibc |
glibc |
合格打印 |
yes |
yes |
no |
yes |
精确浮点打印 |
yes |
no |
no |
yes |
C99数学库 |
yes |
部分的 |
no |
yes |
C11线程API |
yes |
no |
no |
no |
C11线程本地存储 |
yes |
yes |
no |
yes |
GCC libstdc++兼容性 |
yes |
yes |
no |
yes |
POSIX线程 |
yes |
yes, on most archs |
broken |
yes |
POSIX过程调度 |
stub |
不正确 |
no |
不正确 |
POSIX线程优先调度 |
yes |
yes |
no |
yes |
POSIX localedef |
no |
no |
no |
yes |
宽字符界面 |
yes |
yes |
最小 |
yes |
旧式8位代码页 |
no |
yes |
最小 |
slow, via gconv |
传统CJK编码 |
no |
no |
no |
slow, via gconv |
UTF-8多字节 |
native; 100%合格 |
native; 不合格 |
危险的不合格 |
slow, via gconv; 不合格 |
Iconv字符转换 |
大多数主要编码 |
主要UTFs |
no |
the kitchen sink |
Iconv音译扩展 |
no |
no |
no |
yes |
开放墙式TCB阴影 |
yes |
no |
no |
no |
Sun RPC, NIS |
no |
yes |
yes |
yes |
Zoneinfo (高级时区) |
yes |
no |
yes |
yes |
Gmon评测 |
no |
no |
yes |
yes |
调试功能 |
no |
no |
no |
yes |
各种Linux扩展 |
yes |
yes |
部分的 |
yes |
目标体系结构比较 |
musl |
uClibc |
dietlibc |
glibc |
i386 |
yes |
yes |
yes |
yes |
x86_64 |
yes |
yes |
yes |
yes |
x86_64 x32 ABI (ILP32) |
实验 |
no |
no |
不合格 |
ARM |
yes |
yes |
yes |
yes |
Aarch64 (64-bit ARM) |
yes |
no |
no |
yes |
MIPS |
yes |
yes |
yes |
yes |
SuperH |
yes |
yes |
no |
yes |
Microblaze |
yes |
部分的 |
no |
yes |
PowerPC (32- and 64-bit) |
yes |
yes |
yes |
yes |
Sparc |
no |
yes |
yes |
yes |
Alpha |
no |
yes |
yes |
yes |
S/390 (32-bit) |
no |
no |
yes |
yes |
S/390x (64-bit) |
yes |
no |
yes |
yes |
OpenRISC 1000 (or1k) |
yes |
no |
no |
非upstream |
摩托罗拉680x0 (m68k) |
yes |
yes |
no |
yes |
MMU-less微控制器 |
yes, elf/fdpic |
yes, bflt |
no |
no |
构建环境比较 |
musl |
uClibc |
dietlibc |
glibc |
旧式代码友好型头文件 |
部分的 |
yes |
no |
yes |
轻型头文件 |
yes |
no |
yes |
no |
无需本机工具链即可使用 |
yes |
no |
yes |
no |
尊重C命名空间 |
yes |
LFS64问题 |
no |
LFS64问题 |
尊重POSIX命名空间 |
yes |
LFS64问题 |
no |
LFS64问题 |
安全性/硬化性比较 |
musl |
uClibc |
dietlibc |
glibc |
注意角落案例 |
yes |
yes |
no |
太多malloc |
安全UTF-8解码器 |
yes |
yes |
no |
yes |
避免超线性big-O's |
yes |
有时 |
no |
yes |
栈溢出保护功能 |
yes |
yes |
no |
yes |
堆损坏检测 |
yes |
no |
no |
yes |
Misc. c比较 |
musl |
uClibc |
dietlibc |
glibc |
许可证 |
MIT |
LGPL 2.1 |
GPL 2 |
LGPL 2.1+ w/例外情况 |
(交叉)编译工具链组成部分分析
发布时间 2023-10-17 05:33:26作者: 吴建明wujianming
(交叉)编译工具链组成部分分析
GUN 交叉编译工具链中有三个核心组件:Binutils、GCC、C库,如果需要支持 Linux,则还有个 Linux kernel headers。在源代码组织上他们是相互独立的,需要单独进行交叉编译。
Binutils:包括一些二进文件相关的工具。
1.主要工具
主要工具,归纳如下:
(1)ld 链接器。
(3)as 汇编器。
2.调试/分析工具和其他工具
(1)调试/分析工具和其他工具,归纳如下:
addr2line、ar、c++filt、gold、gprof、nm、objcopy、objdump、ranlib、
readelf、size、strings、strip。
(2)需要针对每种 CPU 架构进行配置。
(3)交叉编译非常简单,不需要特殊的依赖项。
3.gcc工具
gcc(GNU Compiler Collection)使用场景,归纳如下:
(1)C、C++、Fortran、Go 等编译器前端。
(2)各种 CPU 架构的编译器后端。
不要被 gcc 这个名字误导,它其实是个 wrapper,会根据输入文件调用一系列其他程序。国外资料中被称为 compiler driver,国内有些资料称为引导器。构建 gcc 比构建 binutils 要复杂的多。
4.Provides
供应商分类:
(1)编译器本身。例如 cc1 for C、cc1plus for C++。
(2)编译器调用程序。gcc、g++ 不但调用编译器本身,也调用 binutils 中的
汇编器、连接器。
5.引导器分类
引导器分类,归纳如下:
(1)目标库:libgcc(gcc 运行时)、libstdc ++(c ++ 库)、libgfortran
(fortran运行时)。
(2)标准 c++ 库的头文件。
Linux内核头文件:构建需要支持 Linux 系统时必须提供。这些头文件定义了用户空间与内核之间的接口(系统调用、数据结构等)。
(1)为了构建一个 C 库,需要 Linux 内核头文件中系统调用号的定义、各种结构
类型和定义。
(2)在内核中,头文件被分开。
(3)一种头文件是用户空间可见的头文件,存储在 uapi 目录中:include/uapi/、arch/<ARCH>/include/uapi/asm。
(4)另一种头文件是内部的内核头文件。
6.在安装过程中需要使用的工具
在安装过程中需要使用的工具,归纳如下:
(1)安装包括一个清理过程,用于从头文件中删除特定于内核的结构体。
(2)从 Linux 4.8 开始,安装 756 个头文件。
(3)内核到用户空间 ABI 通常是向后兼容的。内核头文件的版本必须等于或者小于目标 Linux 的版本。
7.C库文件
C库文件,归纳如下:
(1)提供 POSIX 标准函数的实现,以及其他几个标准和扩展。
(2)基于 Linux 系统调用。
(3)几个可用的实现。
8.几个可用的C库文件实现
几个可用的C库文件实现,归纳如下:
(1)glibc:The GNU C Library 是 Linux C 库的事实标准,常见的 Linux 发行
版中都使用它。支持众多的架构和操作系统,但是不支持没有 MMU 的平
台,不支持静态链接。早些年由于硬件限制及 glibc 本身太大基本不能直接
用于嵌入式,如今貌似也可以了。
(2)uClibc-ng:以前叫 uClibc,始于 2000 年,支持非常灵活的配置。支持架构很多(包括一些 glibc 不支持的),但是仅支持 Linux 操作系统。支持多种没有 MMU 的架构,如 ARM noMMU、Blackfin 等,支持静态链接。STM32F MCU 没有 MMU,嵌入式 Linux 环境中编译工具链就是使用它。
(3)musl:始于 2011 年,开发非常积极,最近添加了对于 noMMU 的支持。它非常小,尤其是在静态链接时。兼容性好,并且严格遵循 C 标准。
(4)bionic:安卓系统使用。
(5)其他一些特殊用途的:newlib(用于裸机)、dietlibc、klibc。musl 的作者对于Linux 常用的这几个库做了一个对比,Linux 常用的这几个库做了一个
对比,见表1。
表1 Linux 常用的这几个库做了一个对比
9.在编译和安装后,输出结果
在编译和安装之后,提供了以下输出结果:
(1)动态链接器 ld.so。
(2)C 库本身 libc.so,及其配套库:libm、librt、libpthread、libutil、libnsl、libresolv、libcrypt。
(3)C 库的头文件:stdio.h、string.h 等。
GUN 将编译器和 C 库分开放在两个软件包里,好处是比较灵活,方便在工具链中可以选择不同的 C 库。但是,也带来了编译器和 C 库的循环依赖问题:编译 C 库需要 C 编译器,但是 C 编译器又依赖 C 库。理论上编译器是不应该依赖 C 库的,它应该只负责将源代码翻译为汇编代码即可,但实际上并非如此。
C99 标准定义了两种实现:一种称为 hosted 实现,一种称为 freestanding 实现。其中,hosted 实现支持完整的 C 标准,包括语言标准和库标准,它用于编译在有宿主系统的环境下运行的程序。freestanding 实现仅支持完整的语言标准,对于库标准它只要求支持部分库标准。
构建(交叉)编译工具链分为好多步,而且单是编译 gcc 就要多次。
10.LLVM编译器
传统编译器的工作原理基本上都是三段式的,可以分为前端(Frontend)、优化器(Optimizer)、后端(Backend)。前端负责解析源代码,检查语法错误,并将其翻译为抽象的语法树(Abstract Syntax Tree)。优化器对这一中间代码进行优化,试图使代码更高效。后端则负责将优化器优化后的中间代码转换为目标机器的代码,这一过程后端会最大化的利用目标机器的特殊指令,以提高代码的性能。
虽然这种三段式的编译器有很多有点,并且被写到了教科书上,但是在实际中,这一结构却从来没有被完美实现过。
回顾 GCC 的历史,虽然它取得了巨大的成功,但开发 GCC 的初衷是提供一款免费的开源的编译器,仅此而已。可后来随着GCC支持了越来越多的语言,GCC 架构的问题也逐渐暴露出来。
LLVM 作为后起之秀,从开始就是按照前端(Frontend)、优化器(Optimizer)、后端(Backend)的三段式进行设计,整个编译器框架非常符合人们对于编译器的设计,以及非常容易理解和学习。
LLVM 的命名最早源自于底层虚拟机(Low Level Virtual Machine)的首字母缩写,但这个项目并不局限于创建一个虚拟机,开发者因而决定放弃这个缩写的意涵。现在 LLVM 是一个专用名词,表示编译器框架整个项目。
目前,很多平台都开始转投 LLVM了,例如苹果、安卓、ARM等等。