嵌入式linux开发工具:
1. 编译器gcc
2. T调试器gdb
3. 工程管理器make
4. 脚本语言shell
一、 gcc编译器
编译器gcc所具备的优点
1、 GNU CC(简称为gcc)是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。gcc不仅功能强大,而且可以编译如C、C++、Object C、Java、和Ada等多种语言,而且gcc又是一个交叉平台编译器,它能够在当前CPU平台上为多种不同体系结构的硬件平台开发软件,因此尤其适合在嵌入式领域的开发编译.
简而言之:
①可以编译多个程序语言
②交叉编译
2、那么什么是交叉编译呢?
交叉编译就是:在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码编译过程。简单地说,就是在一个平台上生成另一个平台上的可执行代码。比如说:我们的宿主机pc机是x86体系结构的处理机,我们的目标机手机是arm体系结构的处理机,然后我们在pc机上编译了一段可以在手机上执行的代码,这个过程就是交叉编译。
这里需要注意的是所谓平台,实际上包含两个概念:体系结构(Architecture)、操作系统(Operating System)。同一个体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,我们常说的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是Intel x86体系结构和Windows NT for x86操作系统的简称。
Gcc编译的过程:
主要有下列几大步骤
①预处理②编译 ③汇编 ④链接
一、 预处理过程
主要有三个过程:头文件展开、宏替换、条件编译
下面讲一下具体的操作
1、预处理
gcc -E表示编译器只进行预处理步骤
例如:
gcc –E main.c 让编译器只进行预处理步骤
gcc –E main.c > main. i 对预处理进行重定向,输出到.i 文件中。
2、头文件的写法
头文件的两种写法:一种是 < >包含的头文件,一种是“ ” 包含的头文件
例如:
#include<stdio.h>
#include"stdio.h"
< >所包含的头文件系统在预处理过程中,头文件展开,先在系统目录下/usr/include寻找,找不到就报错。
“ ” 所包含的头文件,先在当前目录下寻找,再到系统目录下寻找,再找不到就报错。
一般情况下,系统自带头文件用< > 包含,程序人员自己编写的用 ” “包含。
3、宏替换:
#define MAX 100
#include <stdio.h>
int main()
{
int a[MAX];//预处理后int a[100],vim main.i文件可以查看
}
4、条件编译
#ifndef ....
#ifdef ...
#endif //方便程序的不需的部分注掉
二、编译
具体的指令操作
gcc -c main.c //到编译阶段停止
//产生一个 main.o 文件
编译过程主要的工作:
- 语法检查
- 词法分析
三、汇编
具体操作:
gcc -S main.c//main.s为汇编文件,gcc -S //在汇编阶段停止
四、链接
gcc main.c -o main //给可执行文件重命名
链接,负载根据目标文件及所需的库文件产生最终的可执行文件。
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够在操作系统装入执行的统一整体。
链接主要解决了模块间的相互引用的问题,分为地址和空间分配,符号解析和重定位几个步骤。实际上在编译阶段生成目标文件时,会暂时搁置那些外部引用,而这些外部引用就是在链接时进行确定的。链接器在链接时,会根据符号名称去相应模块中寻找对应符号。待符号确定之后,链接器会重写之前那些未确定的符号的地址,这个过程就是重定位。
下面讲一下一些常用的指令操作:
gcc -Wall main.c //开启所有警告
gcc main.c add.c -o add//对多个.c文件的编译
gcc -I /root/指定第三方的头文件搜索目录
gcc -D //外加宏定义
静态库与动态库的制作与使用
一、库文件
库文件就是把一些函数与变量封装在库里,我们能够使用和调用这些函数与变量的功能,但是却看不到其实现过程的文件。
你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数,但是不能看到函数的实现过程,其文件也不能单独运行!这是库文件的一大特点。
Linux的库文件分为两类:
1、静态库:是在执行程序之前就已经加入到执行代码中,成为执行程序的一部分来执行的,后缀名:.a;
2、动态库:是在执行程序启动时加载到执行代码中,后缀名:..so 。
那么其两者的区别在于:
静态库:执行程序之前加载到执行代码中,导致编译速度慢,但是执行效率高;导致可执行文件比较臃肿,体积比较大,占用更多内存;静态库不易升级,升级需重新编译;静态库代码易布局,布局也会影响执行效率。
动态库:在执行程序启动时加载到执行代码中,导致编译速度快,但是执行效率低;可执行文件体积比较小,占用内存比较少;动态库易升级,更新库即可;动态库代码不易布局,影响执行效率,减慢。
/lib-系统库
/usr/lib-程序库
二、静态库的制作和使用
创建:
gcc -c 源文件.c
ar rcs 静态库名 目标文件1 目标文件2
库的命名必须加前缀lib,后缀.a
gcc -c add.c //有一个add.o文件产生
ar rcs libadd.a add.o
gcc main.c -ladd //gcc -ladd连接库文件
gcc main.c -ladd -L. -o main
三、动态库的制作
创建:
gcc -shared -fPIC -o 库名.so 源文件.c
eg:
gcc -shared -fPIC -o libadd1.so add.c
gcc main.c ./libadd1.so -o add
下面具体程序
add.c
int add(int a, int b)
{
return a+b;
}
main.c
#include <stdio.h>
int main()
{
int sum = add(6,5);
printf("sum = %d\n",sum);
return 0;
}
11
四、相关编译选项
-shared:表示产生共享对象,产生的代码会在装载时进行重定位。但是无法做到让一份指令由多个进程共享。因为单纯的装载时重定位会对程序中所有指令和数据中的绝对地址进行修改。要做到让多个进程共享,还需要加上-fPIC。
-fPIC:地址无关代码,是为了能让多个进程共享一份指令。基本思想就是将指令中需要进行修改的那部分分离出来,跟数据放到一块。这样指令部分就可以保持不变,而需要变化的那部分则与数据一块,每个进程都有自己的一份副本。
gcc -shared
gcc -static
第一个表示的是调用动态库,第二个static表示的是编译的时候禁止调用动态库,此时就是说完全编译出的东西是比较大的.
最后,关于gcc -O1/-O2/-O3三级优化具体各是什么:
以下摘自网络:
GNU编译器提供-O选项供程序优化使用:
-O 提供基础级别的优化
-O2 提供更加高级的代码优化,会占用更长的编译时间
-O3 提供*的代码优化
-O4 不优化,这是默认值
不同的优化级别使用的优化技术也可以单独的应用于代码。 可以使用-f命令行选项引用每个 单独的优化技术。
1、 编译器优化级别1
在优化的第一个级别执行基础代码的优化。 这个级别试图执行9种单独的优化功能:
-fdefer-pop: 这种优化技术与汇编语言代码在函数完成时如何进行操作有关。 一般 情况下, 函数的输入值被保存在堆栈中并且被函数访问。 函数返回时, 输入值还在 堆栈中。 一般情况下, 函数返回之后, 输入值被立即弹出堆栈。这样做会使堆栈中的内容有些杂乱。
-fmerge-constans: 使用这种优化技术, 编译器试图合并相同的常量. 这一特性有 时候会导致很长的编译时间, 因为编译器必须分析c或者c++程序中用到的每个常量,并且相互比较他们.
-fthread-jumps: 使用这种优化技术与编译器如果处理汇编代码中的条件和非条件 分支有关。 在某些情况下, 一条跳转指令可能转移到另一条分支语句。 通过一连串跳转, 编译器确定多个跳转之间的最终目标并且把第一个跳转重新定向到最终目标。
-floop-optimize: 通过优化如何生成汇编语言中的循环, 编译器可以在很大程序上提高应用程序的性能。 通常, 程序由很多大型且复杂的循环构成。 通过删除在循环内没有改变值的变量赋值操作, 可以减少循环内执行指令的数量, 在很大程度上提高性能。 此外优化那些确定何时离开循环的条件分支, 以便减少分支的影响。
-fif-conversion: if-then语句应该是应用程序中仅次于循环的最消耗时间的部分。
简单的if-then语句可能在最终的汇编语言代码中产生众多的条件分支。 通过减少 或者删除条件分支, 以及使用条件传送 设置标志和使用运算技巧来替换他们, 编译器可以减少if-then语句中花费的时间量。
-fif-conversion2: 这种技术结合更加高级的数学特性, 减少实现if-then语句所 需的条件分支。
-fdelayed-branch: 这种技术试图根据指令周期时间重新安排指令。 它还试图把 尽可能多的指令移动到条件分支前, 以便最充分的利用处理器的治理缓存。
-fguess-branch-probability: 就像其名称所暗示的, 这种技术试图确定条件分支最可能的结果, 并且相应的移动指令, 这和延迟分支技术类似。 因为在编译时预测代码的安排,所以使用这一选项两次编译相同的c或者c++代码很可能会产生不同的汇编语言代码, 这取决于编译时编译器认为会使用那些分支。 因为这个原因, 很多程序员不喜欢采用这个特性, 并且 专门地使用-fno-guess-branch-probability选项关闭这个特性
-fcprop-registers: 因为在函数中把寄存器分配给变量, 所以编译器执行第二次检查以便减少调度依赖性(两个段要求使用相同的寄存器)并且删除不必要的寄存器复制操作。
2、 编译器优化级别2
结合了第一个级别的所有优化技术, 再加上一下一些优化:
-fforce-mem: 这种优化再任何指令使用变量前, 强制把存放再内存位置中的所有变量都复制到寄存器 中。 对于只涉及单一指令的变量, 这样也许不会有很大的优化效果. 但是对于再很多指令(必须数学操作) 中都涉及到的变量来说, 这会时很显著的优化, 因为和访问内存中的值相比 ,处理器访问寄存器中的值要快的多。
-foptimize-sibling-calls: 这种技术处理相关的和/或者递归的函数调用。 通常, 递归的函数调用可以被展开为一系列一般的指令, 而不是使用分支。 这样处理器的指令缓存能够加载展开的指令并且处理他们, 和指令保持为需要分支操作的单独函数调用相比, 这样更快。
-fstrength-reduce: 这种优化技术对循环执行优化并且删除迭代变量。 迭代变量是捆绑到循环计数器的变量, 比如使用变量, 然后使用循环计数器变量执行数学操作的for-next循环。
-fgcse: 这种技术对生成的所有汇编语言代码执行全局通用表达式消除历程。 这些优化操作试图分析生成的汇编语言代码并且结合通用片段, 消除冗余的代码段。如果代码使用计算性的goto, gcc指令推荐
使用-fno-gcse选项。
-fcse-follow-jumps: 这种特别的通用子表达式消除技术扫描跳转指令, 查找程序中通过任何其他途径都不会到达的目标代码。 这种情况最常见的例子就式if-then-else语句的else部分。
-frerun-cse-after-loop: 这种技术在对任何循环已经进行过优化之后重新运行通用子表达式消除例程这样确保在展开循环代码之后更进一步地优化还编代码。
-fdelete-null-pointer-checks: 这种优化技术扫描生成的汇编语言代码, 查找检查空指针的代码。编译 器假设间接引用空指针将停止程序。 如果在间接引用之后检查指针, 它就不可能为空。
-fextensive-optimizations: 这种技术执行从编译时的角度来说代价高昂的各种优化技术,但是它可能对运行时的性能产生负面影响。
-fregmove: 编译器试图重新分配mov指令中使用的寄存器, 并且将其作为其他指令操作数, 以便最大化捆绑的寄存器的数量。
-fschedule-insns: 编译器将试图重新安排指令, 以便消除等待数据的处理器。 对于在进行浮点运算时有延迟的处理器来说, 这使处理器在等待浮点结果时可以加载其他指令。
-fsched-interblock: 这种技术使编译器能够跨越指令块调度指令。 这可以非常灵活地移动指令以便等待期间完成的工作最大化。
-fcaller-saves: 这个选项指示编译器对函数调用保存和恢复寄存器, 使函数能够访问寄存器值, 而且不必保存和恢复他们。 如果调用多个函数, 这样能够节省时间, 因为只进行一次寄存器的保存和恢复操作, 而不是在每个函数调用中都进行。
-fpeephole2: 这个选项允许进行任何计算机特定的观察孔优化。
-freorder-blocks: 这种优化技术允许重新安排指令块以便改进分支操作和代码局部性。
-fstrict-aliasing: 这种技术强制实行高级语言的严格变量规则。 对于c和c++程序来说, 它确保不在数据 类型之间共享变量. 例如, 整数变量不和单精度浮点变量使用相同的内存位置。
-funit-at-a-time: 这种优化技术指示编译器在运行优化例程之前读取整个汇编语言代码。 这使编译器可以重新安排不消耗大量时间的代码以便优化指令缓存。 但是, 这会在编译时花费相当多的内存, 对于小型计算机可能 是一个问题。
-falign-functions: 这个选项用于使函数对准内存中特定边界的开始位置。大多数处理器按照页面读取内存,并且确保全部函数代码位于单一内存页面内, 就不需要叫化代码所需的页面。
-fcrossjumping: 这是对跨越跳转的转换代码处理, 以便组合分散在程序各处的相同代码。 这样可以减少 代码的长度, 但是也许不会对程序性能有直接影响。
3, 编译器优化级别3
它整合了第一和第二级别中的左右优化技巧, 还包括一下优化:
-finline-functions: 这种优化技术不为函数创建单独的汇编语言代码,而是把函数代码包含在调度程序的代码中。 对于多次被调用的函数来说, 为每次函数调用复制函数代码。 虽然这样对于减少代码长度不利, 但是
通过最充分的利用指令缓存代码, 而不是在每次函数调用时进行分支操作, 可以提高性能。
-fweb: 构建用于保存变量的伪寄存器网络。 伪寄存器包含数据, 就像他们是寄存器一样, 但是可以使用各种其他优化技术进行优化, 比如cse和loop优化技术。
-fgcse-after-reload: 这中技术在完全重新加载生成的且优化后的汇编语言代码之后执行第二次gcse优化, 帮助消除不同优化方式创建的任何冗余段。
简而言之:
O0:这个等级(字母“O”后面跟个零)关闭所有优化选项,也是CFLAGS或CXXFLAGS中没有设置-O等级时的默认等级。这样就不会优化代码,这通常不是我们想要的。
-O1:这是最基本的优化等级。编译器会在不花费太多编译时间的同时试图生成更快更小的代码。这些优化是非常基础的,但一般这些任务肯定能顺利完成。
-O2:-O1的进阶。这是推荐的优化等级,除非你有特殊的需求。-O2会比-O1启用多一些标记。设置了-O2后,编译器会试图提高代码性能而不会增大体积和大量占用的编译时间。
-O3:这是最高最危险的优化等级。用这个选项会延长编译代码的时间,并且在使用gcc4.x的系统里不应全局启用。自从3.x版本以来gcc的行为已经有了极大地改变。在3.x,-O3生成的代码也只是比-O2快一点点而已,而gcc4.x中还未必更快。用-O3来编译所有的软件包将产生更大体积更耗内存的二进制文件,大大增加编译失败的机会或不可预知的程序行为(包括错误)。这样做将得不偿失,记住过犹不及。在gcc 4.x.中使用-O3是不推荐的。
-Os:这个等级用来优化代码尺寸。其中启用了-O2中不会增加磁盘空间占用的代码生成选项。这对于磁盘空间极其紧张或者CPU缓存较小的机器非常有用。但也可能产生些许问题,因此软件树中的大部分ebuild都过滤掉这个等级的优化。使用-Os是不推荐的。