Linux服务器项目-2
1.4 Makefile
概述:
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中。
Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令。
Makefile 带来的好处就是“自动化编译” ”,一旦写好,只需要一个 make命令。
文件命名:makefile或者Makefile
一个Makefile文件中可以有一个或者多个规则。
目标…: 依赖 …
命令(Shell命令)
…
- 目标:最终要生成的文件(伪目标除外)
- 依赖:生成目标所需要的文件或是目标
- 命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)
比如前面的加减乘除的例子Makefile的写法为:
1 | app:sub.c add.c mult.c div.c main.c |
工作原理:
命令在执行之前,需要先检查规则中的依赖是否存在
- 如果存在,执行命令
- 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令,Makefile 中的其它规则一般都是为第一条规则服务的。
检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间,如果Makefile中需要的文件有更新,也就是依赖文件的时间晚于目标文件,执行make时才会重新运行Makefile中的指令。
变量:
自定义变量
变量名 = 变量值 var = hello
预定义变量
AR:归档维护程序的名称,默认值为 ar
CC : C编译器的名称,默认值为 cc
CXX : C++编译器的名称,默认值为 g++
$@ :目标的完整名称
$< :第一个依赖文件的名称
$^:所有的依赖文件
获取变量的值
$(变量名)
有了这些变量,就可以进行如下的简化:
1 | 原来的方式 |
模式匹配:
1 | 原来的方式 |
函数:
$(wildcard PATTERN...)
功能:获取指定目录下指定类型的文件列表
参数: PATTERN 指的是某个或多个目录下的对应的某种类型的文件。如果有多个目录,一般使用空格间隔
返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
示例:$(wildcard *.c ./sub/*.c)
返回值格式: a.c b.c c.c d.c e.c f.c
,即路径下的各个文件名
$(patsubst <pattern>,<replacement>,<text>)
功能:查找 < 中的单词 单词以“空格”、“ Tab ”或“回车”“换行”分隔 是否符合模式 < pattern>,如果匹配的话,则以 < 替换。
示例:$(patsubst %.c, %.o, x.c bar.c)
;返回值格式: x.o bar.o
最终版本的Makefile:
1 | 定义变量 |
1.5 GDB调试
概述:GDB是GNU提供的一个调试工具,同GCC组成了一套完整的开发环境,GDB是Linux和许多类Unix系统中的标准开发环境。
主要功能:
- 启动程序,可以按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事
- 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG
通常,在为了调试而编译时,会关闭编译器的优化选项(-O
),打开调试选项(-g
),另外,Wall
在尽量不影响程序行为的情况下打开所有warning,避免不必要的bug。
-g
选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb
能找到源文件。
加入-g
选项后的文件大小明显大于没有加入此选项的文件。
1 | ##########基础操作 |
1.6 文件IO概述
文件角度的IO:输入就是将内存中的内容输到文件中,输出就是将文件的内容输到内存中。
内存角度的IO:输入就是将文件中的内容输到内存中,输出就是将内存的内容输到文件中。
程序的跨平台:调用不同的库函数时需要不同平台的API执行。
标准C库IO和系统级IO的比较:
标准C库的IO执行时会调用系统的API,系统级的IO更靠近底层。但是标准C库的效率更高。
标准C库使用fopen打开返回一个文件的指针,FILE *;可以通过man 3 函数名
查看函数的信息。而Linux系统的函数可以通过man 2 函数名
查看
而系统级的文件IO实际上是操作文件描述符对应的文件。
有了这个文件指针,就可以通过fread和fwrite向文件读写数据
linux中的FILE
类型实际上是_IO_FILE
这个结构体的别名。_IO_FILE
结构体中有一个_fileno
变量即为文件描述符,文件指针就是通过文件描述符找到文件的。文件描述符在内核区,由内核中的PCB(进程控制块)管理。PCB是一个非常复杂的结构体,将需要管理的数据放在PCB中。
1 | //ptr是写入的文件的数据,size是写入数据块的大小,nmemb是每个块的数量,比如一个字符串每个数据块就是一个字符,FILE *stream文件指针用来操作文件。File中维护了文件描述符(整型值),文件读写指针和I/O缓存区(内存地址) |
利用标准C库IO函数文件向内存中写内容时,先将自身的信息写入到缓存区中,缓存区满了,或者调用fflush,或者正常关闭文件时(fclose,return,exit)时将内容写到内存中。默认的Buffer大小是8192byte(8k)
文件IO和标准C库的IO函数的应用场景也不一样,在网络通信这种对时延要求高的系统中,使用文件IO更好。对于向磁盘读写文件来说,使用标准C库函数由于用到了缓存区,效率更高。
进程:为了运行程序操作系统为程序分配了一些资源,同时创建一个进程。程序只是磁盘上的代码,不占用内存空间,而进程(运行中的程序)会将自身信息加载到内存中。
虚拟地址空间:
虚拟地址空间最终会被CPU中的逻辑管理单元MMU映射到物理内存上。
受保护的地址:包括NULL,nullptr等;
.text:程序运行时加载到这部分,都是二进制机器指令。
堆空间:通过new,malloc等创建出的空间,从低地址向高地址存;栈空间:从高地址向低地址存。
查看环境变量:env
内核区普通用户没有读写权限,想要操作内核就需要系统调用,即调用Linux系统的API。
文件描述符:
PCB中有文件描述符表,其用数组来描述,前三个文件描述符已经被占用,标准输入,标准输出和标准错误。不同的文件描述符可以对应同一个文件,比如要对一个文件进行打开,读,写;这些处理的文件描述符都是不一样的。
Linux具有一切皆文件的思想,IO设备实际上也可以抽象为设备文件。
1.7 Linux系统IO函数
1.7.1 open函数
标准C库的IO函数实际上和Linux系统的IO函数是相对应的。比如fopen对应open函数。
1 | /* open函数的使用:打开一个已经存在的文件 |
通过ll
命令显示出的文件的权限包括10个字符,第一个字符表示文件的类型,后面的几个表示文件的权限,每三个一组。前三个表示当前用户对文件的权限,中间三个表示当前文件的组对文件的权限,最后三个表示其他组对文件的权限。比如-rw-r--r--
如果有读,写,可执行的权限,对应的就是7。三组都有读,写,可执行的权限,就对应777。
1 | /* |
1.7.2 read,write函数
利用read和write函数实现文件拷贝
1 | /* |
1.7.3 lseek函数
1 | /* |
1.7.4 stat,lstat函数
stat
函数用来获取文件的信息,lstat
函数用来获取软链接文件的信息
创建软链接:从b.txt
到a.txt
;ln -s a.txt b.txt
stat结构体内容如下:
1 | struct stat { |
使用stat函数的例子
1 | /* |
通过stat函数可以实现一个和ls -l
功能相同的程序
通过ls -l a.txt
可以输出以下的信息:-rw-rw-r-- 1 ygtrece ygtrece 12 6月 5 02:06 a.txt
1 |
|
1.7.5 文件属性操作函数
1 | int access(const char *pathname, int mode); |
access函数的使用:判断文件是否存在,或者文件的权限
1 | /* |
chmod函数的使用:修改文件的权限
1 | /* |
chown函数:
通过vim /etc/passwd
可以查看用户ID,通过vim /etc/group
可以查看组ID。通过sudo useradd xxx
可以新建用户。通过id xxx
可以查看xxx
的用户ID,组ID。
truncate函数:对文件进行缩减或扩展
1 | /* |
1.7.6 目录操作函数
1 | int mkdir(const char *pathname, mode_t mode);//创建目录 |
rm -rf aaa
:删除aaa目录,注意只有目录有可执行权限,才能进入目录,否则不能进入目录。
mkdir函数:
1 | /* |
rename函数:
1 | /* |
chdir函数和*getcwd函数:修改当前的目录,获取当前的路径
1 | /* |
1.7.7 目录遍历函数
1 | DIR *opendir(const char *name);//打开一个目录 |
一个案例:获取某个路径下的普通文件的个数。
1 | /* |
1.7.8 dup和dup2函数
1 | int dup(int oldfd);//复制文件描述符 |
dup函数的使用
1 | /* |
dup2函数:
1 | /* |
1.7.9 fcntl函数
1 | //复制文件描述符,设置获取文件的状态标志 |
阻塞和非阻塞:描述的是函数调用的行为,阻塞函数指的是当执行到这个函数时进程挂起,得到返回值之后进程再继续执行,非阻塞函数不会影响当前的进程。
当新建一个命令行窗口时,就是阻塞态,命令行也是一个进程。
例子:实现向文件中添加数据。
1 | /* |