前言
🚀 欢迎来到《Linux系统实战》!
这里是命令行到内核的跃迁基地,也是你从"rm -rf恐惧症"到"权限管理大师"的修炼场。
🔍 专栏特色:
- 图解+实战:用最直观的方式拆解Linux核心机制
- 从应用到底层:覆盖Shell脚本、系统调优、内核模块开发
- 真实场景:每篇附服务器运维/开发中的实际问题解决方案
💡 学习建议:
1️⃣ 先动手尝试(搞崩了也没关系)
2️⃣ 对照文章分析原理
3️⃣ 用文末【实战任务】巩固技能
📌 Linux经典名言:
“Linux不是背出来的,是在一次次Permission denied中练出来的!”
一、重要性
- 会不会写Makefile,从侧面说明了一个人是否具备完成大型工程的能力。
- 一个工程的源文件不计其数,按照其类型、功能、模块分别放在若干个目录当中,Makefile定义了一系列的规则来指定:哪些文件需要先编译,哪些文件需要后编译,甚至于进行更复杂的功能操作。
- Makefile带来的好处就是“自动化编译”,一旦写好,只需一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- mak是一个命令工具,是一个解释Makefile当中指令的命令工具,一般来说,大多数的IDE都有这个命令,例如:Delphi的make,VisualC++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法。
- make是一条命令,Makefile是一个文件,两个搭配使用,完成项目自动化构建。
1.简单使用
- 这里以一个简单的c语言的打印程序为例进行讲解
- 在当前目录下使用touch指令创建一个名为Makefile普通文件
- 使用vim打开文件进行编辑,关于vim的使用请点击,按照如下格式进行编写,注意红色字体的那一行一定要以一个tab键开头,编写完成后底行模式 wq 保存并退出即可
- 命令行输入make完成自动编译可执行程序,ls后发现可执行程序名为test,如何运行?————> ./test(即./ 可执行程序文件名)
- 不想使用后直接在命令行输入make clean删除即可
二、依赖关系和依赖方法
- 文件A的变更会影响到文件B,那么就称文件B依赖于文件A。
- 如果文件B依赖于文件A,那么通过文件A得到文件B的方法,就是文件B依赖于文件A的依赖方法。
- 我们的依赖关系的是以:进行分隔的,冒号的左侧是目标文件,冒号的右侧是依赖文件列表,这个依赖文件列表中可以有多个依赖文件也可以没有依赖文件,即目标目标依赖于依赖文件才能够生成
- 依赖方法:依赖方法其实就是一行指令,以编写的指令为例,即使用了gcc编译器去编译我们的源文件 test.c gcc编译器的使用
- 同时clean目标文件的依赖关系中不需要依赖文件,因为clean是对我们生成的临时文件或可执行文件进行清理删除,其不需要依赖文件,直接使用依赖方法中的 rm 指令直接对临时文件或可执行文件进行清理删除即可
三、make和make clean
为什么使用make完成的是对代码的编译,而使用make clean完成的是对代码的清理?那么可以让make完成的是对代码的清理,使用make test完成的是对代码的编译吗?答案是可以的
- 因为make执行规则是在Makefile文件查找并只生成所有目标文件中的第一个,如果第一个目标文件的依赖文件列表中存在的依赖文件不存在,那么make会继续向下递归式的寻找生成依赖文件的依赖关系,并且会递归式的生成目标文件所需要的所有依赖文件,类似于栈结构
- 对于其它并不是处于所有目标文件中的第一个的其它目标文件的生成需要我们使用 make 目标文件名 去显示调用其对应的依赖方法才可以生成目标文件.
对Makefile进行更改一下顺序,那么我们执行make会去执行什么?
四、递归式生成目标文件
- 使用vim修改一下在当前目录下的Makefile文件,使要生成第一个目标文件test就要先一次生成它的依赖文件
- 依赖文件的生成查找方案是先查找test目标文件的依赖文件test.o没有,再去查找test.o的依赖关系中的依赖文件test.s没有,再去查找test.s的依赖关系中的依赖文件test.i没有,再去查找test.i的依赖关系中的依赖文件test.c有,那么先生成test.i,再根据test.i生成test.s,再根据test.s生成test.o,再根据test.o生成我们真正所需要的所有目标文件中的第一个的test目标文件。这个过程不就类似于我们递归调用函数的过程,进行函数调用不断建立栈帧,当遇到返回条件(这里指遇到存在的依赖文件)的时候进行返回,类似于栈的先进后出的结构,最后遇到的先执行,先遇到的后执行。
- 同时这个递归生成的过程中临时文件都会生成出来,不要忘记使用 rm 指令进行清理的时候要连同临时文件和可执行文件一并清理。
五、细节深入
1.回显问题
- 为了验证可以使用makefile进行替换Makefile,可以使用mv指令修改一下文件名
- 我们观察,对于我们输入的make和make clean在屏幕上都有回显的其对应的依赖方法指令,如果我们不想要回显,那么可以在makefile文件中的依赖方法的指令前加上@ 即可
2.特殊符号
特殊符号 @代表依赖关系中的冒号前面的目标文件,^代表的是依赖关系中冒号后面的依赖文件列表中的依赖文件,在进行编写的时候,我们可以使用这两个特殊符号来进行一定的替代,所达成的效果和普通编写方式并无不同 进行修改后,执行一下 make 和 make clean 之后结果和普通书写方式相同
3.注释
makefile文件中我们也可以加注释,不同于windows操作系统中的vs的注释 // 或 /* */ ,在linux中这个makefile文件中的注释一律使用 # 的方式进行注释
4.Makefile语法解析表格
以下是整理成表格的Makefile语法解析(含代码高亮和注释对齐):
| | | |
|---|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| @echo "linking ... $^ to $@" | | |
| | | |
| | | |
| @echo "compling ... $< to $@" | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
关键概念说明
- 回车(\r):光标回到行首(老式打字机回车操作)
- 换行(\n):光标移动到下一行
- \r\n:Windows系统中的行结束符(回车+换行)
- 进度条实现:通常使用
\\r实现原地刷新效果,避免换行导致的跳动
六、make原理
1. 为什么通常情况下make不能连续执行
当一个文件需要进行编译链接形成可执行文件的时候,我们使用make命令来执行,执行完一次之后,再去执行make后编译器就不让我们执行,提示我们我们的test文件已经是最新的了。
没有必要,我们已经使用了make生成了可执行程序,再使用make去生成可执行没有什么用。并且有的文件进行编译链接形成可执行程序占用的内存很大,如果多次形成可执行程序,那么操作系统的效率就会很低,为了在一定程度上提高效率,所以编译器默认只允许make生成了可执行程序的操作执行一次
2.如何做到不能连续执行
make通过判断源文件(依赖文件)和目标文件(可执行程序/文件)的新旧,进而来判断是否需要重新执行依赖方法进行编译形成目标文件
- 一定是先有源文件,再有的可执行文件,因为是源文件编译链接形成可执行文件,所以一般而言,源文件要比可执行文件老,这时候不允许使用make重新执行依赖方法形成可执行文件
- 如果我们更改了源文件,历史上还有可执行文件,那么源文件要比可执行要新,这时候可以使用make重新执行依赖方法形成可执行文件
是使用时间进行比较,时间又可以转化为时间戳,底层实际上是采用时间戳进行比较的
stat命令查看的三种时间属性
- stat命令查看文件时间信息
stat命令可查看文件的三个核心时间戳:
- Access时间:记录文件最后被访问的时间(如读取内容)
- Modify时间:记录文件内容最后一次被修改的时间(如编辑文本)
- Change时间:记录文件元数据(属性)最后一次被更改的时间(如权限、文件名变更)
- 文件构成与时间戳比较基准
文件由内容数据(文本/二进制等)和元数据(文件名、大小等属性)共同构成。在时间戳比较时,系统主要依据Modify时间(内容修改时间)作为文件变更的判断标准。
- 内容修改与属性变更的关联性
当文件内容发生增删改时,通常会导致文件大小的变化,这种内容层面的修改(Modify时间更新)必然触发文件属性的同步变更(Change时间随之更新),二者存在因果关系。
- 属性修改的内容独立性
仅修改文件属性(如重命名、调整权限)只会更新Change时间,而不会影响Modify时间。例如:修改文件名会使Change时间更新,但文件内容未变,故Modify时间保持不变。
- Access时间的优化机制
由于访问时间(Access)在多人协作环境下可能被频繁更新(如千次/秒的读取操作),而直接写入磁盘会严重影响性能。为此,系统采用延迟更新策略:通过内存计数器累计访问次数,仅当达到阈值时才实际更新磁盘上的Access时间戳,以此平衡准确性与I/O效率。
- 观察下图,test.c的文件内容的修改时间要老于可执行文件的修改时间,所以这时候不能使用make再次形成可执行文件
- 同时我们还可以使用touch指令更新时间,当touch后是一个不存在的文件的时候,touch的作用是创建此文件,当touch后是一个已经存在的文件后,那么touch的作用是将此文件的三种时间Access,Modify,Change都进行更新到当前时间的最新时间,所以我们就可以使用touch指令的方式人为更改文件的时间
- 当我们更改源文件的时间后,历史上已经存在了可执行文件,此时源文件的时间比可执行文件的时间要新,使用make可以重新调用依赖方法重新编译链接形成可执行文件
- 此时重新形成的可执行文件的Modify修改时间要新于源文件的Modify修改时间,此时我们再执行一次make自然会显示失败
- 当然还是有办法让每次这个目标文件都被执行的,那就是.PHONY进行修饰目标文件,修饰后,每次make指令就可以被执行了
- 伪目标(.PHONY)的特性与用途
通过
.PHONY声明的是伪目标,这类目标具有以下特点:
- 与磁盘上的实际文件完全解耦,即使存在同名文件也不影响其执行
- 每次调用时强制触发命令执行(不进行依赖检查)
- 典型应用是修饰
clean目标,确保清理操作(如rm -f *.o)始终被执行,为重新编译提供干净的构建环境
- touch命令的时间戳管理机制
touch通过选项精确控制时间戳更新:
-m:仅更新Modify时间(文件内容修改时间)至当前系统时间-a:仅更新Access时间(文件访问时间)至当前系统时间
连锁反应:由于时间戳间的关联性,单独更新某一时间戳可能导致:- 使用
-m时,Change时间(属性变更时间)会同步更新(因mtime属于文件属性) - 使用
-a时,某些文件系统可能连带更新Change时间(取决于具体实现)
注意:默认无参数的touch会同时更新Access和Modify时间