【 I.MX6U

目录
1、Hello World!
1.1、编写代码
1.2、编译代码
2、GCC编译器
2.1、gcc 命令
2.2 编译流程
3、基础
3.1、何为
3.2、 的引入
4、语法
4.1、 规则格式
4.2、 变量
4.2.1、赋值符“=”
4.2.2、赋值符“:=”
4.2.3、赋值符“?=”
4.2.4、变量追加“+=”
4.3、 模式规则
4.4、 自动化变量
4.5、 伪目标
4.6、 条件判断
4.7、 函数使用
4.7.1、函数 subst
4.7.2、函数
4.7.3、函数 dir
4.7.4、函数
4.7.5、函数
4.7.6、函数
【 I.MX6U-ALPHA 】嵌入式 Linux 系统入门系列(一) 系统安装
【 I.MX6U-ALPHA 】嵌入式Linux 系统入门系列(二) 系统入门
1、Hello World!
我们所说的编写代码包括两部分:代码编写和编译 , 在下可以使用来完成这两部分 , 可以在下编写代码然后直接点击编译就可以了 。但是在 Linux下这两部分是分开的 , 比如我们用 VIM 进行代码编写 , 编写完成以后在使用 GCC 编译器进行编译 , 其中代码编写工具很多 , 比如 VIM 编辑器、 Emacs 编辑器、编辑器等等 , 本教程使用自带的 VIM 编辑器 。先来编写一个最简单的“Hello World”程序 , 把 Linux 下的 C 编程完整的走一遍
1.1、编写代码
1.1.1、VIM 编辑器设置
VIM 编辑器默认 TAB 键为 8 空格 , 我们改成 4 空格 , 用 vi 打开文件/etc/vim/vimrc , 在此文件最后面输入如下代码:set ts=4
VIM 编辑器默认是不显示行号的 , 不显示行号不利于代码查看 , 我们设置 VIM 编辑器显示行号 , 同样是通过在文件/etc/vim/vimrc 中添加代码来实现 , 在文件最后面加入下面一行代码即可:set nu
1.1.2 编写main.c代码
使用 vim 指令创建一个名为“main.c”的文件
vim main.c
然后在里面输入如下代码:
1.2、编译代码
下的 C 语言编译器是 GCC ,  GCC 编译器在我们的时候就已经默认安装好了 , 可以通过如下命令查看 GCC 编译器的版本号:
gcc -v
在终端中输入上述命令以后终端输出如图所示:
如果输入命令“gcc -v”命令以后 , 你的终端输出类似图中的信息 , 那么说明你的电脑已经有 GCC 编译器了 。最后下面的“gcc5.4.0”说明本机的 GCC 编译器版本为 5.4.0的 。注意观察在图中有“: -linux-gnu”一行 , 这说明自带的 GCC 编译器是针对 X86 架构的 , 因此只能编译在 X86 架构 CPU 上运行的程序 。如果想要编译在 ARM上运行的程序就需要针对 ARM 的 GCC 编译器 , 也就是交叉编译器!我们是 ARM 开发 , 因此肯定要安装针对 ARM 架构的 GCC 交叉编译器 , 现在我们不用管这些 , 只要知道不同的目标架构 , 其 GCC 编译器是不同的 。如何使用 GCC 编译器来编译 main.c 文件呢? GCC 编译器是命令模式的 , 因此需要输入命令来使用 gcc 编译器来编译文件 , 输入如下命令:
gcc main.c -o main
上述命令的功能就是使用 gcc 编译器来编译 main.c 这个 c 文件 , 过程如图所示
我们使用“gcc main.c –o main”来编译 main.c 文件 , 使用参数“-o”来指定编译生成的可执行文件名字 , 至此我们就完成 Linux 下 C 编程和编译的一整套过程 。
2、GCC编译器 2.1、gcc 命令
我们已经使用过 GCC 编译器来编译 C 文件了 , 我们使用到是 gcc 命令 ,  gcc 命令格式如下:
gcc [选项] [文件名字]

【 I.MX6U

文章插图
主要选项如下:
-c: 只编译不链接为可执行文件 , 编译器将输入的.c 文件编译为.o 的目标文件 。
-o: 用来指定编译结束以后的输出文件名 , 如果使用这个选项的话 GCC 默认编译出来的可执行文件名字为 a.out 。
-g: 添加调试信息 , 如果要使用调试工具(如 GDB)的话就必须加入此选项 , 此选项指示编译的时候生成调试所需的符号信息 。
-O: 对程序进行优化编译 , 如果使用此选项的话整个源代码在编译、链接的的时候都会进行优化 , 这样产生的可执行文件执行效率就高 。
-O2: 比-O 更幅度更大的优化 , 生成的可执行效率更高 , 但是整个编译过程会很慢 。
2.2 编译流程
GCC 编译器的编译流程是:预处理、编译、汇编和链接 。预处理就是展开所有的头文件、替换程序中的宏、解析条件编译并添加到文件中 。编译是将经过预编译处理的代码编译成汇编代码 , 也就是我们常说的程序编译 。汇编就是将汇编语言文件编译成二进制目标文件 。链接就是将汇编出来的多个二进制目标文件链接在一起 , 形成最终的可执行文件 , 链接的时候还会涉及到静态库和动态库等问题
3、基础 3.1、何为
上一小节我们讲了如何使用 GCC 编译器在 Linux 进行 C 语言编译 , 通过在终端执行 gcc 命令来完成 C 文件的编译 , 如果我们的工程只有一两个 C 文件还好 , 需要输入的命令不多 , 当文件有几十、上百甚至上万个的时候用终端输入 GCC 命令的方法显然是不现实的 。如果我们能够编写一个文件 , 这个文件描述了编译哪些源码文件、如何编译那就好了 , 每次需要编译工程的时只需要使用这个文件就行了 。这种问题怎么可能难倒聪明的程序员 , 为此提出了一个解决大工程编译的工具: make , 描述哪些文件需要编译、哪些需要重新编译的文件就叫做  ,  就跟脚本文件一样 , 里面还可以执行系统命令 。使用的时候只需要一个 make命令即可完成整个工程的自动编译 , 极大的提高了软件开发的效率 。如果大家以前一直使用 IDE来编写 C 语言的话肯定没有听说过这个东西 , 其实这些 IDE 是有的 , 只不过这些 IDE对其进行了封装 , 提供给大家的是已经经过封装后的图形界面了 , 我们在 IDE 中添加要编译的C 文件 , 然后点击按钮就完成了编译 。在 Linux 下用的最多的是 GCC 编译器 , 这是个没有 UI的编译器 , 因此就需要我们自己来编写了 。作为一个专业的程序员 , 是一定要懂得 的 , 一是因为在 Linux 下你不得不懂  , 再就是通过你就能了解整个工程的处理过程 。
3.2、 的引入
我们完成这样一个小工程 , 通过键盘输入两个整形数字 , 然后计算他们的和并将结果显示在屏幕上 , 在这个工程中我们有 main.c、 input.c 和 calcu.c 这三个 C 文件和 input.h、 calcu.h 这两个头文件 。其中 main.c 是主体 ,  input.c 负责接收从键盘输入的数值 ,  calcu.h 进行任意两个数相加 , 其中 main.c 文件内容如下:
#include #include "input.h"#include "calcu.h"int main(int argc, char *argv[]){int a, b, num;input_int(&a, &b);num = calcu(a, b);printf("%d + %d = %d\r\n", a, b, num);}
input.c 文件内容如下:
#include #include "input.h"void input_int(int *a, int *b){printf("input two num:");scanf("%d %d", a, b);printf("\r\n");}
calcu.c 文件内容如下:
#include "calcu.h"int calcu(int a, int b){return (a + b);}
文件 input.h 内容如下:
#ifndef _INPUT_H#define _INPUT_Hvoid input_int(int *a, int *b);#endif
文件 calcu.h 内容如下:
#ifndef _CALCU_H#define _CALCU_Hint calcu(int a, int b);#endif
在终端输入如下命令:
gcc main.c calcu.c input.c -o main
可以看出我们的代码按照我们所设想的工作了 , 使用命令“gcc main.c calcu.c input.c -o main”看起来很简单是吧 , 只需要一行就可以完成编译 , 但是我们这个工程只有三个文件啊!如果几千个文件呢?再就是如果有一个文件被修改了以 , 使用上面的命令编译的时候所有的文件都会重新编译 , 如果工程有几万个文件(Linux 源码就有这么多文件! ) , 想想这几万个文件编译一次所需要的时间就可怕 。最好的办法肯定是哪个文件被修改了 , 只编译这个被修改的文件即可 , 其它没有修改的文件就不需要再次重新编译了 , 为此我们需要这样一个工具:
1、如果工程没有编译过 , 那么工程中的所有.c 文件都要被编译并且链接成可执行程序 。
2、如果工程中只有个别 C 文件被修改了 , 那么只编译这些被修改的 C 文件即可 。
3、如果工程的头文件被修改了 , 那么我们需要编译所有引用这个头文件的 C 文件 , 并且链接成可执行文件 。
很明显 , 能够完成这个功能的就是了 , 在工程目录下创建名为“”的文件 , 文件名一定要叫做“”!!!区分大小写的哦!
和 C 文件是处于同一个目录的 , 在文件中输入如下代码:
main: main.o input.o calcu.ogcc -o main main.o input.o calcu.omain.o: main.cgcc -c main.cinput.o: input.cgcc -c input.ccalcu.o: calcu.cgcc -c calcu.cclean:rm *.orm main
上述代码中所有行首需要空出来的地方一定要使用“TAB”键!不要使用空格键!这是 的语法要求 , 编写好得如图所示:
使用命令“make”编译完成以后就会在当前工程目录下生成各种.o 和可
执行文件 , 说明我们编译成功了 。
使用 make 命令编译工程的时候可能会提示如图所示错误:
图中的错误来源一般有两点:
1、中命令缩进没有使用 TAB 键!
2、 VI/VIM 编辑器使用空格代替了 TAB 键 , 修改文件/etc/vim/vimrc , 在文件最后面加上如下所示代码:
set
4、语法 4.1、 规则格式
里面是由一系列的规则组成的 , 这些规则格式如下:
目标…... : 依赖文件集合……
命令 1
命令 2
……
比如下面这条规则:
main: main.o input.o calcu.ogcc -o main main.o input.o calcu.o
这条规则的目标是 main ,  main.o、 input.o 和 calcu.o 是生成 main 的依赖文件 , 如果要更新目标 main , 就必须先更新它的所有依赖文件 , 如果依赖文件中的任何一个有更新 , 那么目标也必须更新 , “更新”就是执行一遍规则中的命令列表 。
命令列表中的每条命令必须以 TAB 键开始 , 不能使用空格!
命令列表中的每条命令必须以 TAB 键开始 , 不能使用空格
命令列表中的每条命令必须以 TAB 键开始 , 不能使用空格!
make 命令会为中的每个以 TAB 开始的命令创建一个 Shell 进程去执行 。
main: main.o input.o calcu.ogcc -o main main.o input.o calcu.omain.o: main.cgcc -c main.cinput.o: input.cgcc -c input.ccalcu.o: calcu.cgcc -c calcu.cclean:rm *.orm main
上述代码中一共有 5 条规则 ,  1~2 行为第一条规则 ,  3~4 行为第二条规则 ,  5~6 行为第三条规则 ,  7~8 行为第四条规则 ,  10~12 为第五条规则 ,  make 命令在执行这个的时候其执行步骤如下:
首先更新第一条规则中的 main , 第一条规则的目标成为默认目标 , 只要默认目标更新了那么就认为的工作 , 完成了整个就是为了完成这个工作 。在第一次编译的时候由于 main 还不存在 , 因此第一条规则会执行 , 第一条规则依赖于文件 main.o、 input.o 和 calcu.o这个三个.o 文件 , 这三个.o 文件目前还都没有 , 因此必须先更新这三个文件 。make 会查找以这三个.o 文件为目标的规则并执行 。以 main.o 为例 , 发现更新 main.o 的是第二条规则 , 因此会执行第二条规则 , 第二条规则里面的命令为“gcc –c main.c” ,  这行命令很熟悉了吧 , 就是不链接编译 main.c , 生成 main.o , 其它两个.o 文件同理 。最后一个规则目标是 clean , 它没有依赖文件 , 因此会默认为依赖文件都是最新的 , 所以其对应的命令不会执行 , 当我们想要执行 clean 的话
可以直接使用命令“make clean” , 执行以后就会删除当前目录下所有的.o 文件以及 main , 因此clean 的功能就是完成工程的清理 , “make clean”的执行过程如图所示
从图可以看出 , 当执行“make clean”命令以后 , 前面编译出来的.o 和 main 可执行文件都被删除掉了 , 也就是完成了工程清理工作 。
我们在来总结一下 Make 的执行过程:
1、 make 命令会在当前目录下查找以 ( 其实也可以)命名的文件 。
2、当找到文件以后就会按照中定义的规则去编译生成最终的目标文件 。
3、当发现目标文件不存在 , 或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标 。
这就是 make 的执行过程 ,  make 工具就是在中一层一层的查找依赖关系 , 并执行相应的命令 。编译出最终的可执行文件 。的好处就是“自动化编译” , 一旦写好了文件 , 以后只需要一个 make 命令即可完成整个工程的编译 , 极大的提高了开发效率 。把 make和和做菜类似 , 目标都是呈现出一场盛宴 , 它们之间的对比关系如表所示:
总结一下 , 中规则用来描述在什么情况下使用什么命令来构建一个特定的文件 , 这个文件就是规则的“目标” , 为了生成这个“目标”而作为材料的其它文件称为“目标”的依赖 , 规则的命令是用来创建或者更新目标的 。
除了的“终极目标”所在的规则以外 , 其它规则的顺序在中是没有意义的 , “终极目标”就是指在使用 make 命令的时候没有指定具体的目标时 ,  make 默认的那个目标 , 它是文件中第一个规则的目标 , 如果中的第一个规则有多个目标 , 那么这些目标中的第一个目标就是 make 的“终极目标” 。
4.2、 变量
跟 C 语言一样也支持变量的 , 先看一下前面的例子:
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
上述语句中 ,  main.o input.o 和 .o 这三个依赖文件 , 我们输入了两遍 , 我们这个比较小 , 如果复杂的时候这种重复输入的工作就会非常费时间 , 而且非常容易输错 , 为了解决这个问题 , 加入了变量支持 。不像 C 语言中的变量有 int、 char等各种类型 , 中的变量都是字符串!类似 C 语言中的宏 。使用变量将上面的代码修改 , 修改以后如下所示:
1 #Makefile 变量的使用2 objects = main.o input.o calcu.o3 main: $(objects)4 gcc -o main $(objects)
我们来分析一下“示例代码” , 第 1 行是注释 , 中可以写注释 , 注释开头要用符号“#” , 不能用 C 语言中的“//”或者“/**/”!第 2 行我们定义了一个变量  , 并且给这个变量进行了赋值 , 其值为字符串“main.o input.o calcu.o” , 第 3 和 4 行使用到了变量  ,  中变量的引用方法是“$(变量名)” , 比如本例中的“$()”就是使用变量。
在“示例代码”中我们在定义变量的时候使用“=”对其进行了赋值 ,  变量的赋值符还有其它两个“:=”和“?=” , 我们来看一下这三种赋值符的区别:
4.2.1、赋值符“=”
使用“=”在给变量的赋值的时候 , 不一定要用已经定义好的值 , 也可以使用后面定义的值 , 比如如下代码:
1 name = hh2 curname = $(name)3 name = hanhui4 5 print:6@echo curname: $(curname)
我们来分析一下上述代码 , 第 1 行定义了一个变量 name , 变量值为“hh” , 第 2 行也定义了一个变量 ,  的变量值引用了变量name , 按照我们C写语言的经验此时的值就是“hh” 。第 3 行将变量 name 的值改为了“” , 第 5、 6 行是输出变量 的值 。在要输出一串字符的话使用“echo” , 就和 C 语言中的“”一样 , 第 6 行中的“echo”前面加了个“@”符号 , 因为 Make 在执行的过程中会自动输出命令执行过程 , 在命令前面加上“@”的话就不会输出命令执行过程 , 大家可以测试一下不加“@”的效果 。使用命令“make print”来执行上述代码 , 结果如图所示:
在图中可以看到的值不是“hh” , 竟然是“” , 也就是变量“name”最后一次赋值的结果 , 这就是赋值符“=”的神奇之处!借助另外一个变量 , 可以将变量的真实值推到后面去定义 。也就是变量的真实值取决于它所引用的变量的最后一次有效值 。
4.2.2、赋值符“:=”
在“示例代码” 上来测试赋值符“:=” , 修改“示例代码”中的第 2 行 , 将其中的“=”改为“:=” , 修改完成以后的代码如下:
1 name = hh2 curname := $(name)3 name = hanhui4 5 print:6@echo curname: $(curname)
从图中可以看到此时的是 hh , 不是 了 。这是因为赋值符“:=”不会使用后面定义的变量 , 只能使用前面已经定义好的 , 这就是“=”和“:=”两个的区别 。
4.2.3、赋值符“?=”
“?=”是一个很有用的赋值符 , 比如下面这行代码:
?=
上述代码的意思就是 , 如果变量前面没有被赋值 , 那么此变量就是“” , 如果前面已经赋过值了 , 那么就使用前面赋的值 。
4.2.4、变量追加“+=”
中的变量是字符串 , 有时候我们需要给前面已经定义好的变量添加一些字符串进去 , 此时就要使用到符号“+=” , 比如如下所示代码:
= main.o .o
+= calcu.o
一开始变量的值为“main.o input.o” , 后面我们给他追加了一个“calcu.o” , 因此变量变成了“main.o input.o calcu.o” , 这个就是变量的追加 。
4.3、 模式规则
1 main: main.o input.o calcu.o2gcc -o main main.o input.o calcu.o3 main.o: main.c4gcc -c main.c5 input.o: input.c6gcc -c input.c7 calcu.o: calcu.c8gcc -c calcu.c910 clean:11rm *.o12rm main
上述中第 3~8 行是将对应的.c 源文件编译为.o 文件 , 每一个 C 文件都要写一个对应的规则 , 如果工程中 C 文件很多的话显然不能这么做 。为此 , 我们可以使用中的模式规则 , 通过模式规则我们就可以使用一条规则来将所有的.c 文件编译为对应的.o 文件 。
模式规则中 , 至少在规则的目标定定义中要包涵“%” , 否则就是一般规则 , 目标中的“%”表示对文件名的匹配 , “%”表示长度任意的非空字符串 , 比如“%.c”就是所有的以.c 结尾的文件 , 类似与通配符 ,  a.%.c 就表示以 a.开头 , 以.c 结束的所有文件 。
当“%”出现在目标中的时候 , 目标中“%”所代表的值决定了依赖中的“%”值 , 使用方法如下:
%.o : %.c命令
因此“示例代码”中的可以改为如下形式:
1 objects = main.o input.o calcu.o2 main: $(objects)3gcc -o main $(objects)4 5 %.o : %.c6#命令7 8 clean:9rm *.o10rm main
示例代码中第 5、 6 这两行代码替代了“示例代码”中的 3~8 行代码 , 修改以后的还不能运行 , 因为第 6 行的命令我们还没写呢 , 第 6 行的命令我们需要借助另外一种强大的变量—自动化变量 。
4.4、 自动化变量
上面讲的模式规则中 , 目标和依赖都是一系列的文件 , 每一次对模式规则进行解析的时候都会是不同的目标和依赖文件 , 而命令只有一行 , 如何通过一行命令来从不同的依赖文件中生成对应的目标?自动化变量就是完成这个功能的!所谓自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出 , 直至所有的符合模式的文件都取完 , 自动化变量只应该出现在规则的命令中 , 常用的自动化变量如表:
【【 I.MX6U】表中的 7 个自动化变量中 , 常用的三种: $@、 $