交叉编译概念详解
p_fly
读完需要
14分钟
速读仅需 5 分钟
1
交叉编译简介
1.1
什么是交叉编译
对于没有做过嵌入式编程的人, 可能不太理解交叉编译的概念, 那么什么是交叉编译?它有什么作用?
在解释什么是交叉编译之前,先要明白什么是本地编译。
本地编译:
本地编译可以理解为,在当前编译平台下,编译出来的程序只能放到当前平台(CPU 和系统)下运行。平时我们常见的软件开发,都是属于本地编译:
比如,我们在 x86 平台上,编写程序并编译成可执行程序。这种方式下,我们使用 x86 平台上的工具,开发针对 x86 平台本身的可执行程序,这个编译过程称为本地编译。
交叉编译:
交叉编译可以理解为,在当前编译平台下,编译出来的程序能运行在体系结构不同的另一种目标平台上,但是编译平台本身却不能运行该程序:
比如,我们在 x86 平台上,编写程序并编译成能运行在 ARM 平台的程序,编译得到的程序在 x86 平台上是不能运行的,必须放到 ARM 平台上才能运行。
1.2
为什么会有交叉编译
之所以要有交叉编译,主要原因是:
- Speed: 目标平台的运行速度往往比当前编译主机慢得多,许多专用的嵌入式硬件被设计为低成本和低功耗,没有太高的性能;
- Capability: 整个编译过程是非常消耗资源的,嵌入式系统往往没有足够的内存或磁盘空间;
- Availability: 即使目标平台资源很充足,可以本地编译,但是第一个在目标平台上运行的本地编译器总需要通过交叉编译获得;
- Flexibility: 一个完整的 Linux 编译环境需要很多支持包,交叉编译使我们不需要花时间将各种支持包移植到目标机器上。
1.3
为什么交叉编译比较困难
交叉编译的困难点在于两个方面:
不同的体系架构拥有不同的机器特性
- Word size: 是 64 位还是 32 位系统
- Endianness: 是大端还是小端系统
- Alignment: 是否必须按照 4 字节对齐方式进行访问
- Default signedness: 默认数据类型是有符号还是无符号
- NOMMU: 是否支持 MMU
交叉编译时的主机环境与目标环境不同
- Configuration issues:具有单独配置步骤(标准./configure make make install)的软件包通常会测试字节序或页面大小等内容,以便在本地编译时可移植。交叉编译时,这些值在主机系统和目标系统之间会有所不同,因此在主机系统上运行测试会给出错误的答案。当目标没有该程序包或版本不兼容时,配置还可以检测主机上是否存在该程序包并包括对该程序包的支持;
- HOSTCC vs TARGETCC:许多构建过程需要编译内容才能在主机系统上运行,例如上述配置测试或生成代码的程序(例如创建.h 文件的 C 程序,然后在主构建过程中#include )。仅用目标编译器替换主机编译器就会破坏需要构建在构建本身中运行的事物的软件包。这样的软件包需要访问主机和目标编译器,并且需要教它们何时使用它们;
- Toolchain Leaks:配置不正确的交叉编译工具链可能会将主机系统的某些位泄漏到已编译的程序中,从而导致通常易于检测但难以诊断和纠正的故障。工具链可能#include 错误的头文件,或在链接时搜索错误的库路径。共享库通常依赖于其他共享库,这些共享库也可能潜入对主机系统的意外链接时引用;
- Libraries:动态链接的程序必须在编译时访问适当的共享库。需要将与目标系统共享的库添加到交叉编译工具链中,以便程序可以针对它们进行链接;
- Testing:在本机版本上,开发系统提供了便利的测试环境。交叉编译时,确认"hello world"构建成功可能需要配置(至少)引导加载程序,内核,根文件系统和共享库。
更详细的对比可以参看这篇文章,已经写的很详细了,在这就不细说了:Introduction to cross-compiling for Linux ( http://landley.net/writing/docs/cross-compiling.html )
2
交叉编译链
2.1
什么是交叉编译链
明白了什么是交叉编译,那我们来看看什么是交叉编译链。
首先编译过程是按照不同的子功能,依照先后顺序组成的一个复杂的流程,如下图:
那么编译过程包括了预处理、编译、汇编、链接等功能。既然有不同的子功能,那每个子功能都是一个单独的工具来实现,它们合在一起形成了一个完整的工具集。
同时编译过程又是一个有先后顺序的流程,它必然牵涉到工具的使用顺序,每个工具按照先后关系串联在一起,这就形成了一个链式结构。
因此,交叉编译链就是为了编译跨平台体系结构的程序代码而形成的由多个子工具构成的一套完整的工具集。同时,它隐藏了预处理、编译、汇编、链接等细节,当我们指定了源文件(.c)时,它会自动按照编译流程调用不同的子工具,自动生成最终的二进制程序映像(.bin)。
注意: 严格意义上来说,交叉编译器,只是指交叉编译的gcc,但是实际上为了方便,我们常说的交叉编译器就是交叉工具链。本文对这两个概念不加以区分,都是指编译链。
2.2
交叉编译链的命名规则
我们使用交叉编译链时,常常会看到这样的名字:
arm-none-linux-gnueabi-gcc
arm-cortex_a8-linux-gnueabi-gcc
mips-malta-linux-gnu-gcc
其中,对应的前缀为:
arm-none-linux-gnueabi-
arm-cortex_a8-linux-gnueabi-
mips-malta-linux-gnu-
这些交叉编译链的命名规则似乎是通用的,有一定的规则:
arch-core-kernel-system
- arch: 用于哪个目标平台;
- core: 使用的是哪个 CPU Core,如 Cortex A8,但是这一组命名好像比较灵活,在其它厂家提供的交叉编译链中,有以厂家名称命名的,也有以开发板命名的,或者直接是 none 或 cross 的;
- kernel: 所运行的 OS,见过的有 Linux,uclinux,bare(无 OS);
- system: 交叉编译链所选择的库函数和目标映像的规范,如 gnu,gnueabi 等。其中 gnu 等价于 glibc+oabi、gnueabi 等价于 glibc+eabi。
注意: 这个规则是一个猜测,并没有在哪份官方资料上看到过。而且有些编译链的命名确实没有按照这个规则,也不清楚这是不是历史原因造成的。如果有谁在资料上见到过此规则的详细描述,欢迎指出错误。
3
包含的工具
Binutils 是 GNU 工具之一,它包括链接器、汇编器和其他用于目标文件和档案的工具,它是二进制代码的处理维护工具。
Binutils 工具包含的子程序如下:
- ld - GNU 链接器;
- as - GNU 汇编器;
- gold - 一个新的,更快的 ELF 链接器;
- addr2line - 把地址转换成文件名和所在的行数;
- ar - 用于创建,修改和提取档案的实用程序;
- c ++ filt-过滤以解编码编码的 C ++符号;
- dlltool-创建用于构建和使用 DLL 的文件;
- elfedit-允许更改 ELF 格式文件;
- gprof-显示分析信息;
- nlmconv-将目标代码转换为 NLM;
- nm-列出目标文件中的符号;
- objcopy-复制并转换目标文件;
- objdump-显示目标文件中的信息;
- ranlib-生成指向档案内容的索引;
- readelf-显示来自任何 ELF 格式对象文件的信息;
- size -列出的对象或归档文件的部分的尺寸;
- strings -列出文件中的可打印字符串;
- strip - 丢弃的符号;
- windmc -Windows 兼容的消息编译器。
- windres -Windows 资源文件的编译器。
binutils 介绍 ( https://sourceware.org/binutils/ ) binutils 详解 ( <https://www.crifan.com/files/doc/docbook/binutils_intro/release/html/binutils_intro.html> ) 详细页面。
3.1
GCC
GNU 编译器套件,支持 C, C++, Java, Ada, Fortran, Objective-C 等众多语言。
3.2
Glibc
Linux 上通常使用的 C 函数库为 glibc。glibc 是 linux 系统中最底层的 api,几乎其它任何运行库都会依赖于 glibc。glibc 除了封装 linux 操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现。
glibc 各个库作用介绍 ( http://www.cnblogs.com/cute/archive/2011/05/03/2035645.html )
因为嵌入式环境的资源及其紧张,所以现在除了 glibc 外,还有 uClibc 和 eglibc 可以选择,三者的关系可以参见这两篇文章:
uclibc eglibc glibc 之间的区别和联系 ( http://www.crifan.com/relation_between_uclibc_glibc_eglibc/ )
Glibc vs uClibc Differences ( https://www.uclibc.org/downloads/Glibc_vs_uClibc_Differences.txt )
3.3
GDB
GDB 用于调试程序
4
如何得到交叉编译链
既然明白了交叉编译链的功能,那么在针对嵌入式系统开发时,我们需要的交叉编译链从哪儿得到?
主要有三个方式可以获取
4.1
下载已经做好的交叉编译链
使用其他人针对某些 CPU 平台已经编译好的交叉编译链。我们只需要找到合适的,下载下来使用即可。
常见的交叉编译链下载地址:
在 http://ftp.arm.linux.org.uk/pub/armlinux/toolchain/ ( http://ftp.arm.linux.org.uk/pub/armlinux/toolchain/ ) 下载已经编译好的交叉编译链在 http://www.denx.de/en/Software/WebHome ( http://www.denx.de/en/Software/WebHome ) 下载已经编译好的交叉编译链在 https://launchpad.net/gcc-arm-embedded 下载已经编译好的交叉编译链一些制作交叉编译链的工具中,包含了已经制作好的交叉编译链,可以直接拿来使用。如 crosstool-NG 如果购买了某个芯片或开发板,一般厂商会提供对应的整套开发软件,其中就包含了交叉编译链。厂家提供的工具一般是经过了严格的测试,并打入了一些必要的补丁,所以这种方式往往是最可靠的工具来源。
4.2
使用工具定制交叉编译链
使用现存的制作工具,以简化制作交叉编译链这个事情的复杂度。我们只需要了解有哪些工具可以实现,并选个合适的工具,搞懂它的操作步骤即可。
- crosstool-NG
- Buildroot
- Embedded Linux Development Kit (ELDK)
工具还有很多,各有各的优势和劣势,大家可以慢慢研究,在这就不细说了。
4.3
从零开始构建交叉编译链
这个是最困难也最耗时间的,毕竟制作交叉编译链这样的事情,需要对嵌入式的编译原理了解的比较透彻,至少要知道出了问题要往哪个方面去翻阅资料。而且,也是最考耐心和细心的地方,配错一个选项或是一个步骤,都可能出现以前从来没见过的问题,而且这些问题往往还无法和这个选项或步骤直接联系起来。
当然如果搭建出来,肯定也是收获最大的,至少对于编译的流程和依赖都比较清楚了,细节上的东西可能还需要去翻看相应的协议或标准,但至少骨架会比较清楚。
详细的搭建过程可以参看后续的文章,这里面有详细的参数和步骤:交叉编译详解 二 从零制作交叉编译链 ( http://blog.csdn.net/pengfei240/article/details/52917018 )
为了方便大家搭建交叉编译链,我写了一个一键生成的脚本(包括源码下载和自动编译)。如果大家自己一直搭建不成功,不妨试试这个脚本,然后对比下自己的流程是否一致,参数是否有差异,也许能帮大家迈过这个障碍:交叉编译详解 三 使用脚本自动生成交叉编译链 ( http://blog.csdn.net/pengfei240/article/details/53968167 )
4.4
对比三种构建方式
项目 |
使用已有交叉编译链 |
自己制作交叉编译链 |
---|---|---|
安装 |
一般提供压缩包 |
需要自己打包 |
源码版本 |
一般使用较老的稳定版本,对于一些新的 GCC 特性不支持 |
可以使用自己需要的 GCC 特性的版本 |
补丁 |
一般都会打上修复补丁 |
普通开发者很难辨别需要打上哪些补丁,资深开发者可以针对自己的需求合入补丁 |
源码溯源 |
可能不清楚源码版本和补丁情况 |
一切都可以定制 |
升级 |
一般不会升级 |
可以随时升级 |
优化 |
一般已经针对特定 CPU 特性和性能进行优化 |
一般无法做到比厂家优化的更好,除非自己设计的 CPU |
技术支持 |
可以通过 FAE 进行支持,可能需要收费 |
只能通过社区支持,免费 |
可靠性验证 |
已经通过了完善的验证 |
自己验证,肯定没有专业人士验证的齐全 |
5
参考资料
1、Introduction to cross-compiling for Linux ( http://landley.net/writing/docs/cross-compiling.html )
2、binutils 介绍 ( https://sourceware.org/binutils/ )
3、 glibc 各个库作用介绍 ( http://www.cnblogs.com/cute/archive/2011/05/03/2035645.html )
4、 uclibc eglibc glibc 之间的区别和联系 ( http://www.crifan.com/relation_between_uclibc_glibc_eglibc/ )
5、 Glibc vs uClibc Differences ( https://www.uclibc.org/downloads/Glibc_vs_uClibc_Differences.txt )
6、交叉编译链下载地址
- http://ftp.arm.linux.org.uk/pub/armlinux/toolchain/ ( http://ftp.arm.linux.org.uk/pub/armlinux/toolchain/ )
- http://www.denx.de/en/Software/WebHome ( http://www.denx.de/en/Software/WebHome )
- https://launchpad.net/gcc-arm-embedded ( https://launchpad.net/gcc-arm-embedded )
分享原文: http://suo.im/6mHowB ( http://suo.im/6mHowB )
戳“阅读原文”一起来充电吧!
- 独家 | 一文读懂TensorFlow(附代码、学习资料)
- 解决openssh漏洞,升级openssh版本
- 解决NTPD漏洞,升级Ntpd版本
- 独家 | 手把手教TensorFlow(附代码)
- HBase Region自动切分细节
- eclipse搭建ssh后台
- 解决mysql漏洞 Oracle MySQL Server远程安全漏洞(CVE-2015-0411)
- im4java包处理图片
- centOS7 mini配置linux服务器(五) 安装和配置tomcat和mysql
- RedisPool操作Redis,工具类实例
- centOS7 mini配置linux服务器(四) 配置jdk
- 老司机教你“飙”EventBus3
- Android listView异步下载和convertView复用产生的错位问题
- 实用Android 屏幕适配方案分享
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法