Linux 基础开发工具(二)

文章目录

  • [Linux 基础开发工具(二)](#Linux 基础开发工具(二))
    • [1. 自动化构建 -make / Makefile](#1. 自动化构建 -make / Makefile)
      • [1.1 基本用法 (part 1)](#1.1 基本用法 (part 1))
      • [1.1 基本用法(part 2)](#1.1 基本用法(part 2))
      • [1.2 推导过程](#1.2 推导过程)
      • [1.3 适度推导语法](#1.3 适度推导语法)
    • [2. Linux第一个系统程序 -- 进度条](#2. Linux第一个系统程序 -- 进度条)
      • [2.1 补充,换行和回车](#2.1 补充,换行和回车)
      • [5.2 行缓冲区](#5.2 行缓冲区)
      • [5.3 倒计时](#5.3 倒计时)
      • [5.4 进度条代码](#5.4 进度条代码)

Linux 基础开发工具(二)

1. 自动化构建 -make / Makefile

1.1 基本用法 (part 1)

会不会写make和makefile,侧面说明一个人是否完全具备完成大型工程的能力。那么make和makefile是什么呢?我们知道,在linux里面,以.c文件为例,我们手动编译一个.c文件就要写一句指令,那么如果有非常多的文件都要一起编译呢?假设有一千个文件,总不能写一千个指令吧。所以, make 和 makefile 就是自动化编译工具。

那么make和makefile怎么写呢? make其实是一句指令, 而makefile是一个文件,开头不区分大小写。我们要通过写makefile文件里面的内容,然后make一下对象,就可以实现自动化编译。

我们来看例子,首先,我们要创建一个makefile文件:

然后,我们要怎么来写呢?首先我们先创建一个.c文件,一会儿举例子用,叫 myproc.c ,然后,我们 vim 打开makefile,

可以看到这里,我们就完成了一个简易的makefile,限制我们来解释一下这里面为什么要这么写。首先,我们先看前两行,第一行叫依赖关系,第二行叫依赖方法,我们的makefile能起作用除了语法外最主要的就是把这两个搞明白。

我们再来看第一行,冒号左边的 Myproc 是目标,右边是依赖文件列表,故名思意,可以有很多个 .c 文件。其中, Myproc就是就是将来要 make的目标, myproc.c 执行这个命令的时候依赖的文件。

就好比月底找家长要钱救急, :右边就是"家长列表",表示钱从哪里来,左边是目标对象。

再来看第二行红色的,这个就是依赖关系,还是用刚才的例子,找家长要到钱了,家长通过什么方式给你? 比方说微信,支付宝之类的。这里的依赖关系也类似,相当于是如何执行。

后面两行我们暂时先不看,先来看看在这个makefile效果怎么样: 哦,对了,再补充一句,刚在的第二行依赖关系前面必须是一个 tab 键,不能是空格之类的。

可以看到啊,这个makefile也是成功起作用了,我只运行了一个 make Myproc 就直接给我生成可执行文件了。

1.1 基本用法(part 2)

我们再把刚才没解释完的图拿下来:

那么,后面三行是什么意思呢? 这个 PHONY 可以理解为一种修饰符, 它修饰的是 clean ,我们称clean是一个伪目标。

后面两行的形式和上面的类似,其实,这也是一种依赖关系和依赖方法。

那么 clean 为什么用 PHONY 修饰呢? 因为这个伪目标的依赖关系和依赖方法总是要被执行。说到这里大家可能有些蒙,什么叫总是要被执行,我们来看下图:

我们之前执行过一次,make Myproc ,再次重复执行的话,就会出现上图中的提示, 换句话说,它只能执行一次。

再来看clean:

make clean我想执行几次,就执行几次。看出区别来了吧,所以这就是总是需要被执行。

那么Myproc为什么不总需要被执行,其实翻译一下提示的话就差不多明白, 因为myproc没有更新。 因为,这个依赖关系是去生成可执行文件,而,源文件没有更新的话,我其实是不需要再重新编译链接的,所以,这个就不总需要被执行。

好,那么问题有来了,make 怎么知道 Myproc没有更新呢? 这个就涉及到之前没有详细说过的,文件的一个时间问题。

我们说,一个文件包括文件内容和属性,我们列一下,myproc.c的信息,

我们可以看到,这个文件的时间有三个,第一个是 Access 第二个是 Modify 第三个是 Change ,咱也不卖关子,第一个是查看时间,第二个是内容修改时间,第三个是属性修改时间。

我们先挑后面两个说,内容修改时间显而易见,就是最后一次修改内容的时间。make 就是看这个最后一次修改内容时间来的。

其实,说个有意思的,修改内容,就说不定会修改文件的大小,文件的大小也是属性,所以,有时第二个和第三个会一起改。

那么,有没有只修改change时间的呢?当然有,只修改一下文件的权限就可以了。 (chmod)

然后,再来看,我们的第一个时间,查看时间,为什么这个特别呢?

来看下图,我第一次查看,然后列出,有一个查看时间,但是,我第二次查看,再次列出,按理来说查看时间应该和最后一次保持一致,但是没有。它没变,甚至毫秒都没变。其实这就是它特殊的地方,查看时间,查看一次后就要过一段时间才会变化,中间怎么查看都不变。

为什么要这样?属性也是文件的一部分,其实我们在操作的时候查看文件内容是非常频繁的,入股查一次改一次,改一次就要去磁盘里面修改文件信息,这是非常,麻烦和浪费的。所以,就设置一段时间后再改,会更加的快速便捷。

综上所述,我们也就知道了,为什么这个make要看的是内容修改时间,而不是另外两个,因为只有内容修改时间在内容修改就要变的情况下最靠谱。

所以,只要 myproc.c 的内容修改时间,早于exe的修改时间,那就说明,可以继续make了。

好了,最后一个小问题,如果想要执行 make Myproc 的话,只写一个make也可以。

这是为什么呢?因为make的检索规则是从上到下的,什么都不写,默认就是第一个。

1.2 推导过程

这个怎么说呢?我们重新写一下这个makefile,这样写:

之前我们提到过,make的扫描过程是从上到下的,如果我们这么写的话,我们发现,第一个依赖关系列表里面的.o文件一开始是不存在的,同理 第二个的 .s 文件也不存在,那么这样可以make成功吗?

答案是可以的。

可以看到的是,这里回显的过程显示,make成功了, ll 列一下这些文件也确实都在。

那么这是为什么呢?其实这和make的推导过程有关。

其实 make 在推导的时候,有一个类似栈的结构,如果第一个来了没有匹配上,就入栈,然后执行下一个,没有匹配上就入栈,执行下一个。这样直到某一个出现需要的文件,然后就开始和消消乐一样,一直往下执行, 出栈,执行, 出栈。

所以,其实正着写和反着些都是可以的。但是,事实上,没有人会这么一条一条地写,主要是太麻烦了。

这个推导过程中,如果有一个地方写错了推到不出来,当然就执行不了了。

1.3 适度推导语法

有没有感觉有了这个make和makefile也没有自动化到哪里去,也还不是要自己手敲。如果依赖关系列表里面有一千个文件,真的要手敲一千个文件吗?当然不。

其实,makefile里面是可以定义变量的,还有一些特殊符号,所以,我们最终一版的makefile应该是这样的:

下面我们一句一句来解释,首先,开头带等号的几句实在定义变量,比如 BIN指的就是 proc.exe ,CC 指的就是 gcc

下一句里面的 #SRC=$(shell ls *.c) 也是在定义变量,前面的井号是注释的意思,因为这句的作用和下一句是一样的,

$()的意思就相当于是"解引用", 换句话说就是,从这个变量里面拿到它代指的对象。

这句话的意思就是shell 命令里面的 ls 列举出来的所有 .c文件,给给 SRC。

下一句里面的wildcard 作用和上一句是一样的,只不过wildcard是makefile里面的语法,类似一个关键词。

下一句里面的 OBJ=$(SRC:.c=.o) 意思是SRC里面的所有 .c 文件全部的替换为同名 .o文件,给给OBJ,形成目标文件列表,别问为什么,问就是语法。 其实,大家也可以感受到,这两句就是让我们可以不写一千个 .c文件的关键。

然后继续走,还是定义变量,一般有关选项的时候,我们会用FLGA, 连接时做一下区分加上L。

下面的大家估计也看个大差不差,然后介绍一下特殊符号。

首当其中的就是 @(CC) (LFLAG) @ ^ 里面, 第一个@的意思是让这句命令执行的时候不回显,不知道大家有没有注意,之前make的时候,是把依赖方式打出来的,加上@以后,就不会了。

然后是@ 它代指的就是 : 左边的变量解引用之后,是目标文件名。 同理, ^ 是冒号右边,是依赖文件列表。

再往下走就到了编译时, %.o : %.c 的意思就是,所有的 .c 文件生成同名 .o 。同样是依赖关系

下一句里面的就是, $< 的意思就是把所有 .c 文件全部 一个个地交给 gcc.

这就是一个基础一些的,可以用的 makefile文件。

2. Linux第一个系统程序 -- 进度条

2.1 补充,换行和回车

我们平时要换行就会敲回车,但是,这是严格意义上的换行吗?假设我们有一个作文纸,如图:

其实这样才叫换行,看出和平时回车的区别来了吗?平时的回车,换行以后会回到第二行的起点,其实这个操作才叫"回车",

为什么键盘上敲一下回车键,会执行换行和回车,因为它把这两个操作合体了,所以,回车键才设计成一个拐弯的形式。

在C语言里面呢, 我们知道有转义字符代表回车键的操作,是\n, 其实这个也是合体之后的。再往下细分的话,换行是\n

回车是\r, 所以,如果要拆开的话就是 \r\n。

5.2 行缓冲区

什么叫行缓冲区,大家在学C语言的时候,多多少少都会接触一些关于缓冲区的知识,缓冲区可以相当于一块内存块。在程序开始的时候,默认打开三个文件,一个是stdin,一个是stdout,一个是stderror。也就分别对应之前提到过的三个设备,键盘,显示器,显示器。对于输出操作来说,只有要输出的内容刷新在缓冲区里,我们才可以在显示器上看到,对应就是stdout。

所以,要想看得见输出的内容,就必须刷新缓冲区。

一个C程序结束的时候,默认会刷新一次缓冲区。

其次,当我们在printf的时候,如果在要输出的内容里面加上\n 那么程序在执行完这一行的时候,也会刷新一次缓冲区。

这个就叫做是行刷新。

当然,我们也可以强制程序在某一行刷新缓冲区,这里就要用到一个函数叫fflush,参数就是要刷新的文件,比如说,要刷新stdout, 对应的写法就是 fflush(stdout) ;

5.3 倒计时

我们知道一个进度条其实是有多个部分组成的,首先就是符号版的进度条,然后是进度百分比,还有一个旋转的斜线表示现在这个进度条有没有卡住不动。

我们先不着急写,先来一个倒计时练练手;

噢,对了,如果在linux里面,要使用sleep函数的话,要包含的头文件是unistd.h。

因为有些简单,节省时间我就先略过了,嘻嘻。

5.4 进度条代码

我们依然采用头文件声明,.c 文件实现的方式。就按照之前提到过的进度条特点来实现。首先就是进度条里面的符号慢慢变多,这里我用的是等号。这个实现起来也是比较简单,用一个for循环,加上一个sleep函数就可以做到这个效果。我们最长想到的肯定是这样写

这里再补充一下,这里没有用sleep而是用的usleep,这两个函数的作用都是休眠,但是单位不同,sleep是秒。usleep是微秒,为了方便演示,也方便写代码,就使用了usleep。

打出来是这么个样,骇人。(说实话刚才makefile写错了,make clean的时候全删了,气的肚子疼。)

可以看到上图中的这部分是现的就有问题,首先,这个打出来是一长串,因为printf的时候没有处理好。我们要的是等号逐渐增加的效果,所以,我们可以加上 \r 让光标回开头,这样,新的buffer比旧的buffer多一个等号,算是小小覆盖一下,这个效果就出来了。

但是这样就完了吗?当然没有,加上 \r 以后我们发现它还不是一个一个打印的,它是一块一块打印的,这个动图我就不放了,因为不会,嘻嘻。那么这里我们要怎么解决一下呢,答案就是行刷新一下。用一个fflush。

那么,这个效果的最终版就是这样的:

那么接下来要实现什么呢?就是这个进度条到了百分之多少了,这个就更简单了,可以看到我们这里是打印了正好一百个等号,所以,这里把cnt一起打印出来,然后加上一点打印小格式就好了,我就不挨着演示了。

下一个效果是旋转的线,告诉这个进度条不动了的时候,是还在继续还是已经死了。这个实现起来也很简单。只需要一个数组含着 '/''|' '\' '-'的数组,然后来回遍历就好了。因为数组大小只有四,所以要循环着来,只需要让cnt % len(数组长度),然后把这个结果当下标就好了,不会越界的。

这里唯一一个需要注意的点就是反斜杠是转义字符,所以要用两个反斜杠。

所以这里完整实现出来的代码就是这样的。

这里注意哈,这里百分号也是转义字符,两个百分号放一起,和之前反斜杠是一样的道理。

这样的话,我们再make一下,结果就是这样:

看着有模有样的说实话。

这个,细心的同学可能发现了,这里的函数叫process_v1,也就是版本一,还有第二个版本,因为这个进度条分上传的进度条和下载的进度条嘛,刚才的那个是上传的,再补一个下载的。

这个下载的进度条呢,因为搞不来下载数据,所以,还是用usleep来充当下载数据,那么这个下载怎么写。

假设,这个下载的总大小是 1024 , 下载的速度是 1 ,单位不重要,相同就行。

这就是一个简单的框架,然后,我们去完成这个FlushProcess函数就好了。

需要注意的点都打了注释,主要就是static 的cnt的用处。

哦,对了这个函数里面嵌套函数是C里面本质应该不允许的,我这个是因为找不着剪切在哪里了,再加上gcc高一些的版本支持在某些情况下的嵌套函数,小偷个懒,嘻嘻。然后,我就没改。

就行。

外链图片转存中...(img-d6WTyCoe-1776001396505)

这就是一个简单的框架,然后,我们去完成这个FlushProcess函数就好了。

外链图片转存中...(img-pbxoXmrM-1776001396505)

需要注意的点都打了注释,主要就是static 的cnt的用处。

哦,对了这个函数里面嵌套函数是C里面本质应该不允许的,我这个是因为找不着剪切在哪里了,再加上gcc高一些的版本支持在某些情况下的嵌套函数,小偷个懒,嘻嘻。然后,我就没改。

相关推荐
雾岛听蓝4 小时前
Linux线程基础
linux·开发语言·经验分享
齐落山大勇4 小时前
Linux的文件IO
linux·运维·服务器
七七powerful4 小时前
运维养龙虾--Tmux 终端复用器完全指南:从入门到 AI Agent 远程操控
运维·服务器·人工智能
leaves falling4 小时前
C/C++ const:修饰变量和指针的区别(和引用底层关系)
c语言·开发语言·c++
网域小星球4 小时前
C 语言从 0 入门(十二)|指针与数组:数组名本质、指针遍历数组
c语言·算法·指针·数组·指针遍历数组
Tairitsu_H4 小时前
C语言:排序(一)
c语言·数据结构·排序
tod1134 小时前
深入解析ext2文件系统架构
linux·服务器·c++·文件系统·ext
m0_694845574 小时前
CRUD (Nestjsx)部署教程:自动生成RESTful接口
服务器·人工智能·后端·开源·自动化·restful
萧行之4 小时前
FRP 0.62.0 + Mac Ollama 公网穿透部署+排障实录(标准 TOML 格式)
linux·服务器