Linux 系统编程入门

Linux 系统编程入门

gcc 编译命令

1
2
3
4
5
6
7
8
9
10
11
gcc [FileName] [-o ProgramName]

# 常用编译选项
[-o] 目标文件名
[-E] 预处理,生成预处理后的代码,不编译
[-S] 编译,生成汇编代码,不汇编
[-c] 汇编,生成目标代码,但是不进行链接
[-I directory] 指定 include 包含文件的搜索目录
[-g] 在编译的时候,生成调试信息,该程序可以被调试器调试
[-D] 编译的时候定义一个宏
[-l] 链接的库

g++ 在编译的阶段调用 gcc,但是 gcc 不能自动和 c++ 程序使用的库链接,除非添加编译选项 gcc -lstdc++

静态库和动态库

静态库的制作

  1. 命名规则:

    1. Linux:libxxx.a
    2. Windows:libxxx.lib
  2. 静态库的制作

    1. gcc 编译生成 -o 汇编文件

      gcc -c a.c b.c

    2. 对汇编文件打包,创建静态库

      ar rcs libxxx.a a.o b.o

静态库的使用

1
gcc FileName -l xxx

xxx 为 库名称,而非库文件名称

动态库的制作

  1. 命名规则:

    1. Linux:libxxx.so
    2. Windows:libxxx.dll
  2. 动态库的制作

    1. gcc 编译生成 -o 汇编文件

      gcc -c -fpic/-fPIC a.c b.c

    2. gcc 制作动态库

      gcc -shared *.o -o libxxx.so

动态库的使用

  1. 程序运行时动态库加载失败的原因:

    系统加载可执行代码的时候,除了需要知道所依赖的动态库的名称,还需要知到其绝对路径,此时需要系统的动态载入器来获取其绝对路径。

  2. 解决方法:

    1. 添加环境变量,只在当前终端生效(临时)

      export LD_LIBRARY_PATH = $LD_LIBRARY_PATH:libso_path

    2. 添加环境变量,在~/.bashrc中添加(用户级别)

      export LD_LIBRARY_PATH = $LD_LIBRARY_PATH:libso_path

      使环境变量生效

      source ~/.bashrc 或者 ,~/.bashrc

    3. 添加环境变量,在/etc/profile中添加(系统级别)

      export LD_LIBRARY_PATH = $LD_LIBRARY_PATH:libso_path

      使环境变量生效

      source /etc/profile 或者 ,/etc/profile

    4. 修改 /etc/ld.so.cache,二进制文件,不能直接修改,通过 /etc/ld.so.conf 修改,直接添加libso_pathldconfig 使修改生效。

    5. 将动态库文件放在 /lib/ 或者 /usr/lib/ 目录下(不建议),可能会替换同名的系统库文件。

静态库 vs. 动态库

  1. 静态库

    • 优点:

      a. 加载速度快

      b. 发布程序无需提供静态库,移植方便

    • 缺点:

      a. 消耗系统资源,浪费内存

      b. 更新、部署、发布麻烦

  2. 动态库

    • 优点:

      a. 可以实现进程间的资源共享

      b. 更新、部署、发布简单

      c. 可以控制核实加载动态库

    • 缺点:

      a. 加载速度比静态库慢

      b. 发布程序是需要提供依赖的动态库

Makefile

什么是 Makefile

  • 工程的源文件根据类型、功能、模块分别放在不同的目录中,Makefile 文件定义了以系列规则来指定哪些文件先编译,哪些文件后编译,哪些文件需要重新编译,甚至更复杂的功能。
  • Makefile 带来的好处:“自动化编译”,make 是解释 Makefile 的命令工具。

Makefile 文件命名和规则

  1. 文件命名:

    Makefile 或者 makefile

  2. 规则:

1
2
3
4
5
6
7
8
9
目标 ... : 依赖...
命令 (shell 命令)
...

目标: 最终要生成的文件 (伪目标除外)
依赖: 生成目标所需要的文件或是目标
命令: 通过执行命令对依赖操作生成目标 (前有缩进)

Makefile 文件里的其他规则一般是为第一条规则服务的.

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 1
app:sub.c add.c mult.c div.c main.c
gcc sub.c add.c mult.c div.c main.c -o app

# 2
app:sub.o add.o mult.o div.o main.o
gcc sub.o add.o mult.o div.o main.o -o app

sub.o:sub.c
gcc -c sub.c -o sub.o

add.o:add.c
gcc -c add.c -o add.o

mult.o:mult.c
gcc -c mult.c -o mult.o

div.o:div.c
gcc -c div.c -o div.o

main.o:main.c
gcc -c main.c -o main.o

# 3
#定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)

sub.o:sub.c
gcc -c sub.c -o sub.o

add.o:add.c
gcc -c add.c -o add.o

mult.o:mult.c
gcc -c mult.c -o mult.o

div.o:div.c
gcc -c div.c -o div.o

main.o:main.c
gcc -c main.c -o main.o

# 4
#定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)

%.o:%.c
$(CC) -c $< -o $@

# 5
#定义变量
# add.c sub.c main.c mult.c div.c
src=$(wildcard ./*.c)
objs=$(patsubst %.c, %.o, $(src))
target=app
$(target):$(objs)
$(CC) $(objs) -o $(target)

%.o:%.c
$(CC) -c $< -o $@

.PHONY:clean
clean:
rm $(objs) -f

GDB 调试

命令:

向可执行程序中加入调试信息.

1
gcc -g test.c -o test

启动/退出 gdb:

1
2
3
gdb [可执行文件] # 启动

(gdb) q/quit # 退出 gdb

gdb 常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 给程序设置参数/获取设置参数
(gdb) set args 10 20 # 设置函数参数
(gdb) show args # 获取函数参数

(gdb) help [command] # 查看帮助文档

# 查看当前文件代码
(gdb) list/l # 从默认位置显, 默认10行
(gdb) list/l line # 从指定的行显示, 行号上下文
(gdb) list/l func # 从指定的函数显示, func上下文

# 查看非当前文件代码
(gdb) list/l filename : line
(gdb) list/l filename : func

# 设置显示的行数
(gdb) show list/listsize
(gdb) set list/listsize line

# 设置断点
(gdb) b/break line
(gdb) b/break func
(gdb) b/break filename : line
(gdb) b/break filename : func

# 查看断点
(gdb) i/info b/break

# 删除断点
(gdb) d/del/delete brk_num

# 设置断点无效
(gdb) dis/disable brk_num

# 设置断点生效
(gdb) en/enable brk_num

# 设置条件断点 (一般用在循环位置)
(gdb) b/break 10 if i==5

# 运行GDB程序
(gdb) start # 停在main第一行
(gdb) run # 停在第一个断点

# 继续运行
(gdb) c/continue # 下一个断点
(gdb) n/next # 下一行, 不会进入函数体
(gdb) s/step # 单步调试, 会进入函数体
(gdb) finish # 跳出函数体

# 变量操作
(gdb) p/print variable # 打印变量值
(gdb) ptype variable # 打印变量类型

# 自动变量操作
(gdb) display variable # 自动打印指定变量
(gdb) i/info display
(gdb) undisplay no.

# 其他操作
(gdb) set var variable = value # 设置变量的值
(gdb) until # 跳出循环

gdb 不输入命名按回车默认执行上一条命令。

文件 IO

标准 C 库 IO 函数

标准 C 库中的IO函数 调用操作系统的 API,可以跨平台。
标准 C 库的IO函数带有缓冲区,效率较高。

![](https://blog-1312962011.cos.ap-nanjing.myqcloud.com/imgs/202208120301129.png)

虚拟地址空间

32位操作系统的虚拟地址空间:

虚拟地址空间

文件描述符

1
2
3
4
# 文件描述符
0 -> STDIN_FILENO # 标准输入
1 -> STDOUT_FILENO # 标准输出
2 -> STDERR_FILENO # 标准错误

文件描述符

文件描述符表是一个数组,默认大小为1024。前三个元素被占用,默认打开,指向当前终端。

Linux 系统 IO 函数

1
2
3
4
5
6
7
8
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int close(int fd); // 关闭文件描述符,fd 不再指向任何文件,并可以被再次使用
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

可使用man 命令查看说明文档:

1
man 2 open

open 系统调用

1
2
3
4
5
6
7
// 所需头文件: 
# include <sys/types.h> // flags : 权限标记, Linux 里定义为宏, 在前两个头文件里
# include <sys/stat.h> //
# include <fcntl.h> // open 的函数声明

int open(const char *pathname, int flags); // 打开一个已存在文件
int open(const char *pathname, int flags, mode_t mode); // 创建一个新的文件

C 没有函数重载, 两个 open 通过可变参数实现:

1
int open(const char *pathname, int flags, ...);

打开一个已存在文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
// 打开一个已存在文件
int open(const char *pathname, int flags);
parameters:
pathname : 要打开的文件路径
flags : 对文件的操作权限设置和其他设置
必选 : O_RDONLY, O_WRONLY, or O_RDWR. 这三个互斥
可选 : O_APPEND, O_CREAT, O_TRUNC 等

return value :
成功返回新的文件描述符, 失败返回 -1
errno :
属于 Linux 系统函数库, 库里面的全局变量, 记录的是最近的错误号

perror函数
# include <stdio.h>
// 打印对应 errno 对应的错误描述
void perror(const char *s);
输出: s: system error message
*/
# include <sys/types.h> // flag : 权限标记, Linux 里定义为宏, 在前两个头文件里
# include <sys/stat.h> //
# include <fcntl.h> // open 的函数声明
# include <stdio.h>
# include <unistd.h>

int main() {

int fd = open("a.txt", O_RDONLY); // 当前目录不存在 a.txt

if(fd == -1) {
perror("open"); // 输出 : open: No such file or directory
}

// 关闭文件描述符
close(fd);

return 0;
}

创建一个新的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
# include <sys/types.h> // flags : 权限标记, Linux 里定义为宏, 在前两个头文件里
# include <sys/stat.h> //
# include <fcntl.h> // open 的函数声明

// 创建一个新的文件
int open(const char *pathname, int flags, mode_t mode);
parameters:
pathname : 要打开的文件路径
flags : 对文件的操作权限设置和其他设置
必选 : O_RDONLY, O_WRONLY, or O_RDWR. 这三个互斥
可选 : O_APPEND, O_CREAT, O_TRUNC 等
mode: 八进制数, 表示创建出的文件操作权限, 比如 0775, 0 表示八进制
最终的权限是 mode & ~umask

return value :
成功返回新的文件描述符, 失败返回 -1
*/


# include <sys/types.h> // flag : 权限标记, Linux 里定义为宏, 在前两个头文件里
# include <sys/stat.h> //
# include <fcntl.h> // open 的函数声明
# include <stdio.h>
# include <unistd.h>

int main() {

int fd = open("create.txt", O_RDWR | O_CREAT, 0777); // 创建 create.txt

if(fd == -1) {
perror("open");
}

// 关闭文件描述符
close(fd);

return 0;
}

read、write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*  
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
- buf:需要读取数据存放的地方,数组的地址(传出参数)
- count:指定的数组的大小
返回值:
- 成功:
>0: 返回实际的读取到的字节数
=0:文件已经读取完了
- 失败:-1 ,并且设置errno

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
- buf:要往磁盘写入的数据,数据
- count:要写的数据的实际的大小
返回值:
成功:实际写入的字节数
失败:返回-1,并设置errno
*/
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {

// 1.通过open打开english.txt文件
int srcfd = open("english.txt", O_RDONLY);
if(srcfd == -1) {
perror("open");
return -1;
}

// 2.创建一个新的文件(拷贝文件)
int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
if(destfd == -1) {
perror("open");
return -1;
}

// 3.频繁的读写操作
char buf[1024] = {0};
int len = 0;
while((len = read(srcfd, buf, sizeof(buf))) > 0) {
write(destfd, buf, len);
}

// 4.关闭文件
close(destfd);
close(srcfd);


return 0;
}

lseek

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*  
标准C库的函数
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);

Linux系统函数,重置文件指针偏移量
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
- fd:文件描述符,通过open得到的,通过这个fd操作某个文件
- offset:偏移量,偏移量一旦超出了文件的长度,下一次进行文件IO写入操作文件的时候便会延续偏移量的位置继续写入,进而在文件中间产生了空洞的部分,这部分会以”\0”填充,而从原来的文件结尾到新写入数据间的这段空间就被称为“文件空洞”。
- whence:
SEEK_SET
设置文件指针的偏移量
SEEK_CUR
设置偏移量:当前位置 + 第二个参数offset的值
SEEK_END
设置偏移量:文件大小 + 第二个参数offset的值
返回值:返回当前位置到文件开始位置以字节为单位的偏移量


作用:
1.移动文件指针到文件头
lseek(fd, 0, SEEK_SET);

2.获取当前文件指针的位置
lseek(fd, 0, SEEK_CUR);

3.获取文件长度
lseek(fd, 0, SEEK_END);


*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {

//原始文件内容: hello,world
int fd = open("hello.txt", O_RDWR);

if(fd == -1) {
perror("open");
return -1;
}

// 扩展文件的长度
int ret = lseek(fd, 10000000, SEEK_END);
if(ret == -1) {
perror("lseek");
return -1;
}

// 在offset = 10000011处写入一个数据
write(fd, ".", SEEK_CUR);

// 关闭文件
close(fd);

return 0;
}

什么是空洞文件(hole file)?在Linux中,lseek的系统调用是可以改变在文件上面的偏移量的,而且还允许其超出文件的长度。偏移量一旦超出了文件的长度,下一次进行文件IO写入操作文件的时候便会延续偏移量的位置继续写入,进而在文件中间产生了空洞的部分,这部分会以”\0”填充,而从原来的文件结尾到新写入数据间的这段空间就被称为“文件空洞”。

在Linux中,EOF(文件结束符)并不是一个字符,而是在读取到文件末尾的时候返回的一个信号值,也就是-1。

文件空洞部分实际上是不会占用任何的物理空间的,直到在某个时刻对空洞部分进行写入文件内容的时候才会为它分配对应的空间。但是在空洞文件形成的时候,逻辑上面的文件大小是分配了空洞部分的大小的。

生成元素文件 hello.txt 的拷贝:

1
cp hello.txt hello.prev

编译上述代码后运行。

使用od命令来查看hello.txt文件的二进制内容

1
od -c hello.txt

od -c hello.txt

使用ls查看文件的逻辑大小:

1
ls -lh hello.*

逻辑大小

逻辑大小:

hello.prev: 11 Bytes

hello.txt: 10000012 Bytes = 10000012 / 1024 / 1024 M = 9.6 M

使用du查看文件实际占用物理块的大小:

1
du -h hello.*

实际占用物理块

文件系统的基本操作单位是数据块,Linux 中的逻辑块大小为 4KB,所以 hello.prev 实际占用的空间为 4k,由于 "hello,world""." 在逻辑上不在一个块内,所以 hello.txt 实际占用的空间为 8k,远小于 9.6 M,所以文件空洞部分实际上是不会占用任何的物理空间的。

stat、lstat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *statbuf);
作用:获取一个文件相关的一些信息
参数:
- pathname:操作的文件的路径
- statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
返回值:
成功:返回0
失败:返回-1 设置errno

int lstat(const char *pathname, struct stat *statbuf);
参数:
- pathname:操作的文件的路径
- statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
返回值:
成功:返回0
失败:返回-1 设置errno

*/

statlstat的区别:当文件是一个符号链接时,lstat返回的是该符号链接本身的信息;而stat返回的是该链接指向的文件的信息。

stat 结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct stat {
dev_t st_dev; // 文件的设备编号
ino_t st_ino; // 节点
mode_t st_mode; // 文件的类型和存取的权限
nlink_t st_nlink; // 连到该文件的硬连接数目
uid_t st_uid; // 用户ID
gid_t st_gid; // 组ID
dev_t st_rdev; // 设备文件的设备编号
off_t st_size; // 文件字节数(文件大小)
blksize_t st_blksize; // 最优的 IO 块大小,Linux 为 4096B
blkcnt_t st_blocks; // 块数(以 512B 为单位,而不是 4096B,实际的物理意义应该是磁盘扇区数)

time_t st_atime; // 最后一次访问时间
time_t st_mtime; // 最后一次修改时间
time_t st_ctime; // 最后一次改变时间(指属性)
};

使用stat命令查看文件的stat结构体(没有lstat命令):

stat 命令

hello.txt : Blocks = 8K / 512B = 16

hello.prev: Blocks = 4K / 512B = 8

st_mode 变量(16位):

st_mode 变量

文件属性的操作函数

1
2
3
4
int access(const char *pathname, int mode); // 判断某个文件是否有某个权限,或者判断文件是否存在
int chmod(const char *filename, int mode); // 修改文件的权限
int chown(const char *path, uid_t owner, gid_t group); // 修改文件所有者或者所在组
int truncate(const char *path, off_t length); // 缩减或者扩展文件的尺寸至指定的大小,不会占用实际的物理内存,多余的部分实际上是一个文件空洞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
#include <unistd.h>
int access(const char *pathname, int mode);
作用:判断某个文件是否有某个权限,或者判断文件是否存在
参数:
- pathname: 判断的文件路径
- mode:
R_OK: 判断是否有读权限
W_OK: 判断是否有写权限
X_OK: 判断是否有执行权限
F_OK: 判断文件是否存在
返回值:成功返回0, 失败返回-1
*/

/*
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
修改文件的权限
参数:
- pathname: 需要修改的文件的路径
- mode:需要修改的权限值,八进制的数
返回值:成功返回0,失败返回-1

*/

/*
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
作用:缩减或者扩展文件的尺寸至指定的大小,不会占用实际的物理内存,多余的部分实际上是一个文件空洞
参数:
- path: 需要修改的文件的路径
- length: 需要最终文件变成的大小
返回值:
成功返回0, 失败返回-1
*/

lseek 和 truncate 的区别

lseek 修改的是文件偏移量(读写位置),truncate修改的是文件的文件长度(逻辑大小),如果 lseek 文件偏移量大于文件之后不在该位置进行写入,则无法扩展文件长度(逻辑大小)。

lseek 和 truncate 产生的文件空洞都不会占用实际物理磁盘空间。

lseek 不进行写入:

lseek

truncate:

truncate

目录的操作函数

1
2
3
4
5
int rename(const char *oldpath, const char *newpath);
int chdir(const char *path);
char *getcwd(char *buf, size_t size);
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录
参数:
pathname: 创建的目录的路径
mode: 权限,八进制的数
返回值:
成功返回0, 失败返回-1
*/

/*

#include <unistd.h>
int chdir(const char *path);
作用:修改进程的工作目录
参数:
path : 需要修改的工作目录

#include <unistd.h>
char *getcwd(char *buf, size_t size);
作用:获取当前工作目录
参数:
- buf : 存储的路径,指向的是一个数组(传出参数)
- size: 数组的大小
返回值:
返回的指向的一块内存,这个数据就是第一个参数

*/

目录的遍历函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);

struct dirent
{
// 此目录进入点的inode
ino_t d_ino;
// 目录文件开头至此目录进入点的位移
off_t d_off;
// d_name 的长度, 不包含NULL字符
unsigned short int d_reclen;
// d_name 所指的文件类型
unsigned char d_type;
// 文件名
char d_name[256];
};

/*
d_type
DT_BLK - 块设备
DT_CHR - 字符设备
DT_DIR - 目录
DT_LNK - 软连接
DT_FIFO - 管道
DT_REG - 普通文件
DT_SOCK - 套接字
DT_UNKNOWN - 未知
*/

/*
// 打开一个目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
参数:
- name: 需要打开的目录的名称
返回值:
DIR * 类型,理解为目录流
错误返回NULL


// 读取目录中的数据
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
- 参数:dirp是opendir返回的结果
- 返回值:
struct dirent,代表读取到的文件的信息
读取到了末尾或者失败了,返回NULL

// 关闭目录
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);

*/

读取某个目录下所有的普通文件的个数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// #define __USE_MISC

int getFileNum(const char * path);

// 读取某个目录下所有的普通文件的个数
int main(int argc, char * argv[]) {

if(argc < 2) {
printf("%s path\n", argv[0]);
return -1;
}

int num = getFileNum(argv[1]);

printf("普通文件的个数为:%d\n", num);

return 0;
}

// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path) {

// 1.打开目录
DIR * dir = opendir(path);

if(dir == NULL) {
perror("opendir");
exit(0);
}

struct dirent *ptr;

// 记录普通文件的个数
int total = 0;

while((ptr = readdir(dir)) != NULL) {

// 获取名称
char * dname = ptr->d_name;

// 忽略掉. 和..
if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) {
continue;
}

// 判断是否是普通文件还是目录
if(ptr->d_type == DT_DIR) {
// 目录,需要继续读取这个目录
char newpath[256];
sprintf(newpath, "%s/%s", path, dname);
total += getFileNum(newpath);
}

if(ptr->d_type == DT_REG) {
// 普通文件
total++;
}


}

// 关闭目录
closedir(dir);

return total;
}

dup、dup2

1
2
int dup(int oldfd);					// 复制文件描述符,返回最小的未使用的文件描述符
int dup2(int oldfd, int newfd); // 重定向文件描述符

dup 函数示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
#include <unistd.h>
int dup(int oldfd);
作用:复制一个新的文件描述符
fd=3, int fd1 = dup(fd),
fd指向的是a.txt, fd1也是指向a.txt
从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符


*/

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

int main() {

int fd = open("a.txt", O_RDWR | O_CREAT, 0664);

int fd1 = dup(fd); // 关闭文件描述符

if(fd1 == -1) {
perror("dup");
return -1;
}

printf("fd : %d , fd1 : %d\n", fd, fd1);

close(fd);

char * str = "hello,world";
int ret = write(fd1, str, strlen(str)); // 通过 fd1 向文件中写入 "hello,world"
if(ret == -1) {
perror("write");
return -1;
}

close(fd1);

return 0;
}

dup2 函数示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
#include <unistd.h>
int dup2(int oldfd, int newfd);
作用:重定向文件描述符, 复制文件描述符并指定新的文件描述符为 newfd
调用函数成功后 oldfd 指向 a.txt, newfd 指向 b.txt
如果 newfd 本身和 b.txt 关联,则在复制文件描述符前关闭文件描述符 newfd,使其不再指向 b.txt,然后将其重新指向a.txt
oldfd 必须是一个有效的文件描述符
oldfd 和 newfd值相同,相当于什么都没有做
*/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {

int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
if(fd == -1) {
perror("open");
return -1;
}

int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664);
if(fd1 == -1) {
perror("open");
return -1;
}

printf("fd : %d, fd1 : %d\n", fd, fd1);

int fd2 = dup2(fd, fd1);
if(fd2 == -1) {
perror("dup2");
return -1;
}

// 通过fd1去写数据,实际操作的是1.txt,而不是2.txt
char * str = "hello, dup2";
int len = write(fd1, str, strlen(str));

if(len == -1) {
perror("write");
return -1;
}

printf("fd : %d, fd1 : %d, fd2 : %d\n", fd, fd1, fd2);

close(fd);
close(fd1);

return 0;
}

fcntl 函数

1
2
int fcntl(int fd, int cmd, ... /* arg */ );
// 使用 cmd 操作文件文件描述符,如复制文件描述符、设置/获取文件的状态标志等

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ...);
参数:
fd : 表示需要操作的文件描述符
cmd: 表示对文件描述符进行如何操作
- F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
int ret = fcntl(fd, F_DUPFD);

- F_GETFL : 获取指定的文件描述符文件状态flag
获取的flag和我们通过open函数传递的flag是一个东西。

- F_SETFL : 设置文件描述符文件状态flag
必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
可选性:O_APPEND, O)NONBLOCK
O_APPEND 表示追加数据
NONBLOK 设置成非阻塞

阻塞和非阻塞:描述的是函数调用的行为。
*/

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main() {

// 1.复制文件描述符
// int fd = open("1.txt", O_RDONLY);
// int ret = fcntl(fd, F_DUPFD);

// 2.修改或者获取文件状态flag
int fd = open("1.txt", O_RDWR);
if(fd == -1) {
perror("open");
return -1;
}

// 获取文件描述符状态flag
int flag = fcntl(fd, F_GETFL);
if(flag == -1) {
perror("fcntl");
return -1;
}
flag |= O_APPEND; // flag = flag | O_APPEND

// 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
int ret = fcntl(fd, F_SETFL, flag);
if(ret == -1) {
perror("fcntl");
return -1;
}

char * str = "hello";
write(fd, str, strlen(str));

close(fd);

return 0;
}

Linux 系统编程入门
https://ww1820.github.io/posts/bb4cd573/
作者
AWei
发布于
2022年8月14日
更新于
2022年8月16日
许可协议