signalfd系统调用及示例

好的,我们来深入学习 signalfd 和 signalfd4 系统调用

signalfd

1. 函数介绍

在传统的 Linux 信号处理中,我们使用 sigaction 来设置信号处理函数。当信号到达时,内核会中断程序的正常执行流程,转而去执行我们注册的处理函数。这是一种异步的处理方式。

但是,有时候我们希望用一种同步、基于文件描述符 (File Descriptor) 的方式来处理信号。这样做的好处是:

统一 I/O 模型:可以将信号处理集成到 select, poll, epoll 等 I/O 多路复用机制中。程序可以像等待文件描述符就绪一样等待信号。

避免信号处理函数的复杂性:信号处理函数有诸多限制(只能调用异步信号安全函数),并且容易引入竞态条件。使用 signalfd 可以在程序的主循环中处理信号,避免这些问题。

获取更详细的信号信息:可以从文件描述符中读取到完整的 signalfd_siginfo 结构体,包含信号的所有信息。

signalfd (Signal File Descriptor) 系统调用就是为此而设计的。它创建一个特殊的文件描述符,该描述符用于接收由你指定的信号集中的信号。当这些信号中的任何一个到来时,这个文件描述符就会变为“可读”状态。你可以用 read() 从这个文件描述符中读取信号信息。

signalfd64 是 signalfd 的一个更新的、64 位兼容的版本。在现代 glibc 和内核中,用户空间程序通常调用的是 signalfd,而它在底层可能会根据系统架构和内核版本自动选择使用 signalfd 或 signalfd64。对于我们编程来说,直接使用 signalfd 即可。

简单来说,signalfd 就是把信号“变成”了可以像文件一样读取的数据流,让你可以用处理文件 I/O 的方式来处理信号。

2. 函数原型

1
2
3
4
5
#include <sys/signalfd.h> // 包含系统调用声明和相关结构体

// glibc 提供的标准接口
int signalfd(int fd, const sigset_t *mask, int flags);

注意:底层系统调用可能有 signalfd 和 signalfd64 之分,但 glibc 会为我们处理好兼容性问题。

3. 功能

创建或修改一个信号文件描述符,该描述符可以用来接收由 mask 参数指定的信号集中的信号。

4. 参数

fd:

  • int 类型。

  • 如果是 -1,则表示创建一个新的 signalfd。

  • 如果是一个已存在的 signalfd 的文件描述符,则表示修改该 signalfd 的信号掩码。

mask:

  • const sigset_t * 类型。

  • 一个指向信号集的指针。这个信号集定义了你希望通过这个 signalfd 接收的信号。在创建或修改 signalfd 之前,你必须先使用 sigprocmask() 将这些信号阻塞掉。这是 signalfd 能正常工作的前提。

flags:

  • int 类型。

用于修改 signalfd 行为的标志位。常用的标志有:

  • SFD_CLOEXEC: 在执行 exec() 系列函数时自动关闭该文件描述符。

  • SFD_NONBLOCK: 使 read() 操作变为非阻塞模式。如果当前没有信号可读,read() 会立即返回 -1 并设置 errno 为 EAGAIN 或 EWOULDBLOCK。

5. 返回值

  • 成功: 返回一个有效的信号文件描述符(一个非负整数)。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

  • EINVAL: flags 参数包含无效标志,或者 fd 是一个已存在的 signalfd 但 mask 为 NULL。

  • EMFILE: 进程已打开的文件描述符数量达到上限。

  • ENFILE: 系统已打开的文件描述符数量达到上限。

  • ENOMEM: 内核内存不足。

  • EBADF: fd 参数不是 -1,也不是一个有效的 signalfd 文件描述符。

7. 读取信号信息

一旦 signalfd 创建成功并有信号到达,你就可以使用 read() 系统调用从该文件描述符中读取信号信息。

1
2
3
4
5
6
7
8
struct signalfd_siginfo si;
ssize_t s = read(sfd, &si, sizeof(si));
if (s != sizeof(si)) {
// Handle error
}
// Now you can access signal information in 'si'
printf("Got signal %d from PID %d\n", si.ssi_signo, si.ssi_pid);

struct signalfd_siginfo 结构体包含了丰富的信号信息,例如:

  • ssi_signo: 信号编号。

  • ssi_errno: 伴随信号的错误码。

  • ssi_code: 信号产生的原因。

  • ssi_pid: 发送信号的进程 ID (如果适用)。

  • ssi_uid: 发送信号的用户 ID (如果适用)。

  • ssi_fd: 与信号相关的文件描述符 (如果适用,如 SIGIO)。

  • ssi_band: 与 SIGIO/SIGURG 相关的带外数据。

  • ssi_overrun: 实时信号队列溢出的数量。

  • ssi_trapno: 导致信号产生的陷阱号。

  • ssi_status: 退出状态或信号 (对于 SIGCHLD)。

  • ssi_int / ssi_ptr: 通过 sigqueue() 发送的伴随数据。

  • ssi_utime / ssi_stime: 用户和系统 CPU 时间。

  • ssi_addr: 导致信号产生的内存地址。

8. 相似函数或关联函数

  • sigaction: 传统的信号处理方式,设置信号处理函数。

  • sigprocmask: 用于设置/查询信号屏蔽字。在使用 signalfd 之前必须先用它来阻塞要监听的信号。

  • read: 用于从 signalfd 中读取信号信息。

  • poll, select, epoll_wait: I/O 多路复用函数,可以监听 signalfd 文件描述符的可读事件。

  • close: 关闭 signalfd 文件描述符。

9. 示例代码

下面的示例演示了如何使用 signalfd 来同步处理信号,并将其集成到 poll 系统调用中。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> // 信号处理相关
#include <sys/signalfd.h> // signalfd 相关
#include <poll.h> // poll 相关
#include <string.h>
#include <errno.h>
#include <sys/wait.h> // wait 相关

int main() {
sigset_t mask;
int sfd, j;
struct pollfd fds&#91;2]; // 监听 signalfd 和 标准输入
struct signalfd_siginfo si;
ssize_t s;
char buf&#91;1024]; // 用于读取标准输入

printf("--- Demonstrating signalfd ---\n");
printf("PID: %d\n", getpid());
printf("Try sending signals:\n");
printf(" kill -USR1 %d\n", getpid());
printf(" kill -USR2 %d\n", getpid());
printf(" Or press Ctrl+C (SIGINT) or Ctrl+\\ (SIGQUIT)\n");
printf(" Type 'exit' and press Enter to quit.\n");
printf(" This program uses poll() to wait for signals or input.\n");

// 1. 创建要监听的信号集
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigaddset(&mask, SIGINT); // Ctrl+C
sigaddset(&mask, SIGQUIT); // Ctrl+\

// 2. 阻塞这些信号
// 这是使用 signalfd 的关键前提!
// 必须先阻塞信号,这样信号才不会被默认处理或由 sigaction 处理
// 而是排队等待 signalfd 读取
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
printf("Blocked signals: SIGUSR1, SIGUSR2, SIGINT, SIGQUIT\n");

// 3. 创建 signalfd
// -1 表示创建新的 signalfd
// &mask 是要监听的信号集
// SFD_CLOEXEC 在 exec 时关闭 fd,SFD_NONBLOCK 设置为非阻塞
sfd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK);
if (sfd == -1) {
perror("signalfd");
exit(EXIT_FAILURE);
}
printf("Created signalfd: %d\n", sfd);

// 4. 设置 poll 的文件描述符数组
// 监听 signalfd
fds&#91;0].fd = sfd;
fds&#91;0].events = POLLIN; // 等待可读事件
// 监听标准输入 (stdin)
fds&#91;1].fd = STDIN_FILENO;
fds&#91;1].events = POLLIN; // 等待可读事件

// 5. 主循环:使用 poll 等待事件
while (1) {
// poll 会阻塞,直到 fds 数组中的任何一个 fd 准备好
// -1 表示无限期等待
int poll_num = poll(fds, 2, -1);
if (poll_num == -1) {
if (errno == EINTR) {
// poll 被信号中断 (不太可能发生,因为我们用的是 signalfd)
continue;
} else {
perror("poll");
break;
}
}

if (poll_num > 0) {
// 检查 signalfd 是否有数据可读
if (fds&#91;0].revents & POLLIN) {
// 6. 从 signalfd 读取信号信息
s = read(sfd, &si, sizeof(si));
if (s != sizeof(si)) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 非阻塞模式下没有数据可读 (不太可能,因为 poll 说了有)
continue;
} else {
perror("read signalfd");
break;
}
}

// 7. 处理接收到的信号
printf("\nReceived signal: %d", si.ssi_signo);
switch(si.ssi_signo) {
case SIGUSR1:
printf(" (SIGUSR1)");
break;
case SIGUSR2:
printf(" (SIGUSR2)");
break;
case SIGINT:
printf(" (SIGINT - Ctrl+C)");
break;
case SIGQUIT:
printf(" (SIGQUIT - Ctrl+\\)");
break;
}
printf("\n Sender PID: %d\n", si.ssi_pid);
printf(" Sender UID: %d\n", si.ssi_uid);

// 如果收到 SIGINT 或 SIGQUIT,则退出
if (si.ssi_signo == SIGINT || si.ssi_signo == SIGQUIT) {
printf("Exiting due to signal.\n");
break;
}
}

// 检查标准输入是否有数据可读
if (fds&#91;1].revents & POLLIN) {
ssize_t nread = read(STDIN_FILENO, buf, sizeof(buf) - 1);
if (nread > 0) {
buf&#91;nread] = '\0';
printf("Read from stdin: %s", buf); // buf 可能已包含 \n
// 如果用户输入 'exit',则退出
if (strncmp(buf, "exit\n", 5) == 0) {
printf("Exiting due to 'exit' command.\n");
break;
}
} else if (nread == 0) {
// EOF on stdin (e.g., Ctrl+D)
printf("EOF on stdin. Exiting.\n");
break;
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("read stdin");
break;
}
}
}

// 检查是否有错误或挂起事件
if (fds&#91;0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
fprintf(stderr, "Error on signalfd.\n");
break;
}
if (fds&#91;1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
fprintf(stderr, "Error on stdin.\n");
break;
}
}
}

// 8. 清理资源
printf("Closing signalfd...\n");
close(sfd);
printf("Program finished.\n");

return 0;
}

10. 编译和运行

1
2
3
4
5
6
7
8
# 假设代码保存在 signalfd_example.c 中
gcc -o signalfd_example signalfd_example.c

# 运行程序
./signalfd_example
# 程序会输出 PID,并提示你可以发送哪些信号
# 在另一个终端尝试发送信号,或在程序运行的终端按 Ctrl+C 等

11. 预期输出

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
--- Demonstrating signalfd ---
PID: 12345
Blocked signals: SIGUSR1, SIGUSR2, SIGINT, SIGQUIT
Created signalfd: 3
Try sending signals:
kill -USR1 12345
kill -USR2 12345
Or press Ctrl+C (SIGINT) or Ctrl+\ (SIGQUIT)
Type 'exit' and press Enter to quit.
This program uses poll() to wait for signals or input.

Read from stdin: hello
Read from stdin: world

Received signal: 10 (SIGUSR1)
Sender PID: 12346
Sender UID: 1000

Received signal: 12 (SIGUSR2)
Sender PID: 12346
Sender UID: 1000

Received signal: 2 (SIGINT - Ctrl+C)
Sender PID: 0
Sender UID: 0
Exiting due to signal.
Closing signalfd...
Program finished.

12. 总结

signalfd 提供了一种现代化、同步化的方式来处理信号,特别适合于事件驱动的程序(如服务器)。通过将其与 poll, select, epoll 等 I/O 多路复用技术结合,可以构建出高效且结构清晰的程序。记住两个关键点:

先阻塞,再创建:必须先用 sigprocmask 阻塞要监听的信号。

像文件一样读取:使用 read() 从 signalfd 中读取 struct signalfd_siginfo 结构体来获取信号信息。

这种方式避免了传统信号处理函数的复杂性和潜在风险,是编写健壮 Linux 应用程序的有力工具。好的,我们来深入学习 signalfd 和 signalfd4 系统调用,从 Linux 编程小白的角度出发。

socketpair系统调用及示例

好的,我们继续按照您的列表顺序,介绍下一个函数socketpair 是一个 Linux 系统调用,用于创建一对相互连接的匿名套接字。这对套接字就像一个双向的管道(bidirectional pipe),允许两个进程(通常是具有亲缘关系的进程,如父子进程或线程)通过套接字语义进行双向(全双工)通信。

1. 函数介绍

socketpair 是一个 Linux 系统调用,用于创建一对相互连接的匿名套接字。这对套接字就像一个双向的管道(bidirectional pipe),允许两个进程(通常是具有亲缘关系的进程,如父子进程或线程)通过套接字语义进行双向(全双工)通信。

你可以把它想象成一对连在一起的对讲机:

  • 你和朋友各拿一个对讲机(sv[0] 和 sv[1])。

  • 你对你的对讲机说话,朋友能从他的对讲机里听到。

  • 朋友对他的对讲机说话,你能从你的对讲机里听到。

  • 这两个对讲机是预先配对好的,它们之间有直接的通信链路。

与 pipe 创建的单向管道不同,socketpair 创建的是双向的。虽然 socketpair 创建的是套接字,但它们是匿名的,没有关联网络地址(如 IP 和端口),只能用于本地进程间通信。

2. 函数原型

1
2
3
4
#include <sys/socket.h> // 必需

int socketpair(int domain, int type, int protocol, int sv&#91;2]);

3. 功能

  • 创建套接字对: 请求内核创建两个未命名(匿名)的、相互连接的套接字。

返回文件描述符: 将这两个套接字的文件描述符通过 sv 数组返回给调用者。

  • sv[0] 是第一个套接字的文件描述符。

  • sv[1] 是第二个套接字的文件描述符。

建立连接: 这两个套接字在创建后就处于已连接状态,数据可以从 sv[0] 写入并从 sv[1] 读出,反之亦然。

4. 参数

int domain: 指定套接字的通信域。

  • AF_UNIX (或 AF_LOCAL): Unix 域套接字。这是 socketpair 最常用且在 POSIX 标准中唯一要求支持的域。它用于同一台主机上的进程间通信。

  • AF_INET: 在某些系统(如 Linux)上也支持,但这不是标准行为,且不常用。

int type: 指定套接字的类型。

  • SOCK_STREAM: 流式套接字。提供有序、可靠、双向的字节流服务。数据传输没有边界(像管道一样)。

  • SOCK_DGRAM: 数据报套接字。提供无连接、不可靠、有边界的数据报服务。每个 send/write 调用对应一个独立的消息单元。

  • (在 Linux 上,SOCK_SEQPACKET 也可能被支持,提供有序、可靠、有边界的连接)

int protocol: 指定具体的协议。

  • 通常设置为 0,表示使用给定 domain 和 type 的默认协议。

int sv[2]: 这是一个包含两个整数的数组,用于接收 socketpair 调用返回的两个相互连接的套接字文件描述符。

  • sv[0]: 第一个套接字的文件描述符。

  • sv[1]: 第二个套接字的文件描述符。

5. 返回值

  • 成功时: 返回 0。同时,sv[0] 和 sv[1] 被填充为有效的文件描述符。

  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EAFNOSUPPORT 不支持的地址族,EMFILE 进程打开的文件描述符已达上限,ENFILE 系统打开的文件总数已达上限,EOPNOTSUPP 指定的类型或协议在此域中不受支持,EPROTONOSUPPORT 不支持的协议等)。

6. 相似函数,或关联函数

  • pipe: 创建一个单向的匿名管道。socketpair 可以看作是 pipe 的双向版本。

  • socket / bind / connect: 用于创建和连接命名套接字(如网络套接字)。socketpair 创建的是匿名的、预先连接的套接字。

  • fork: 经常与 socketpair 结合使用,父进程和子进程各自保留套接字对的一端,用于彼此通信。

  • read / write / send / recv: 用于对 socketpair 返回的文件描述符进行数据传输。

7. 示例代码

示例 1:父子进程通过 socketpair 通信

这个经典的例子演示了如何使用 socketpair 在父进程和子进程之间建立双向通信通道。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// socketpair_fork.c
// 注意:为了编译此示例,需要链接 pthread 库: gcc -o socketpair_fork socketpair_fork.c
#include <sys/socket.h> // socketpair
#include <sys/wait.h> // wait
#include <unistd.h> // fork, read, write, close
#include <stdio.h> // perror, printf
#include <stdlib.h> // exit
#include <string.h> // strlen

#define BUFFER_SIZE 256

int main() {
int sv&#91;2]; // 用于存储 socketpair 返回的两个文件描述符
pid_t pid;
char buf&#91;BUFFER_SIZE];
ssize_t bytes_read;

// 1. 创建一对相互连接的 Unix 流式套接字
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
perror("socketpair failed");
exit(EXIT_FAILURE);
}

printf("Socket pair created: sv&#91;0] = %d, sv&#91;1] = %d\n", sv&#91;0], sv&#91;1]);

// 2. 创建子进程
pid = fork();
if (pid == -1) {
perror("fork failed");
// 创建子进程失败,需要关闭已创建的套接字
close(sv&#91;0]);
close(sv&#91;1]);
exit(EXIT_FAILURE);
}

if (pid == 0) {
// --- 子进程 ---
// 关闭不需要的 sv&#91;0]
close(sv&#91;0]);

// 子进程通过 sv&#91;1] 发送消息给父进程
const char *msg_to_parent = "Hello from child process!";
printf("Child: Sending message to parent...\n");
if (write(sv&#91;1], msg_to_parent, strlen(msg_to_parent)) != (ssize_t)strlen(msg_to_parent)) {
perror("Child: write to parent failed");
close(sv&#91;1]);
_exit(EXIT_FAILURE);
}
printf("Child: Message sent.\n");

// 子进程通过 sv&#91;1] 接收来自父进程的消息
printf("Child: Waiting for message from parent...\n");
bytes_read = read(sv&#91;1], buf, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buf&#91;bytes_read] = '\0'; // 确保字符串结束
printf("Child: Received from parent: %s\n", buf);
} else if (bytes_read == 0) {
printf("Child: Parent closed the connection.\n");
} else {
perror("Child: read from parent failed");
}

// 子进程完成通信,关闭其套接字
close(sv&#91;1]);
printf("Child: Exiting.\n");
_exit(EXIT_SUCCESS);

} else {
// --- 父进程 ---
// 关闭不需要的 sv&#91;1]
close(sv&#91;1]);

// 父进程通过 sv&#91;0] 接收来自子进程的消息
printf("Parent: Waiting for message from child...\n");
bytes_read = read(sv&#91;0], buf, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buf&#91;bytes_read] = '\0';
printf("Parent: Received from child: %s\n", buf);
} else if (bytes_read == 0) {
printf("Parent: Child closed the connection.\n");
} else {
perror("Parent: read from child failed");
}

// 父进程通过 sv&#91;0] 发送消息给子进程
const char *msg_to_child = "Hello from parent process!";
printf("Parent: Sending message to child...\n");
if (write(sv&#91;0], msg_to_child, strlen(msg_to_child)) != (ssize_t)strlen(msg_to_child)) {
perror("Parent: write to child failed");
} else {
printf("Parent: Message sent.\n");
}

// 父进程完成通信,关闭其套接字
close(sv&#91;0]);

// 等待子进程结束
int status;
if (wait(&status) == -1) {
perror("Parent: wait for child failed");
exit(EXIT_FAILURE);
}

if (WIFEXITED(status)) {
printf("Parent: Child exited with status %d.\n", WEXITSTATUS(status));
} else {
printf("Parent: Child did not exit normally.\n");
}

printf("Parent: Exiting.\n");
}

return 0;
}

代码解释:

调用 socketpair(AF_UNIX, SOCK_STREAM, 0, sv) 创建一对 Unix 域流式套接字。

调用 fork() 创建子进程。

子进程 (pid == 0):

  • 关闭不需要的 sv[0]。

  • 通过 sv[1] 向父进程发送一条消息。

  • 通过 sv[1] 从父进程读取一条消息。

  • 通信完成后,关闭 sv[1] 并退出。

父进程 (pid > 0):

  • 关闭不需要的 sv[1]。

  • 通过 sv[0] 从子进程读取一条消息。

  • 通过 sv[0] 向子进程发送一条消息。

  • 通信完成后,关闭 sv[0]。

  • 调用 wait() 等待子进程结束并检查其退出状态。

示例 2:使用 socketpair 和 SOCK_DGRAM 进行有边界通信

这个例子演示了使用 SOCK_DGRAM 类型的 socketpair,它保留了消息边界。

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
// socketpair_dgram.c
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 256

int main() {
int sv&#91;2];
char buf&#91;BUFFER_SIZE];
ssize_t bytes_read;

// 创建一对 Unix 数据报套接字
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) == -1) {
perror("socketpair SOCK_DGRAM failed");
exit(EXIT_FAILURE);
}

printf("SOCK_DGRAM Socket pair created: sv&#91;0] = %d, sv&#91;1] = %d\n", sv&#91;0], sv&#91;1]);

// 通过 sv&#91;0] 发送两条独立的消息
const char *msg1 = "First message";
const char *msg2 = "Second message is longer than the first one.";

printf("Sending two messages through sv&#91;0]...\n");
if (write(sv&#91;0], msg1, strlen(msg1)) != (ssize_t)strlen(msg1)) {
perror("write msg1 failed");
close(sv&#91;0]);
close(sv&#91;1]);
exit(EXIT_FAILURE);
}
if (write(sv&#91;0], msg2, strlen(msg2)) != (ssize_t)strlen(msg2)) {
perror("write msg2 failed");
close(sv&#91;0]);
close(sv&#91;1]);
exit(EXIT_FAILURE);
}
printf("Messages sent.\n");

// 通过 sv&#91;1] 逐个接收消息 (保留边界)
printf("Receiving messages through sv&#91;1]...\n");

bytes_read = read(sv&#91;1], buf, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buf&#91;bytes_read] = '\0';
printf("Received message 1 (%zd bytes): %s\n", bytes_read, buf);
}

bytes_read = read(sv&#91;1], buf, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buf&#91;bytes_read] = '\0';
printf("Received message 2 (%zd bytes): %s\n", bytes_read, buf);
}

// 如果再读一次,会因为没有数据而阻塞 (或返回 0 如果另一端关闭)
// bytes_read = read(sv&#91;1], buf, BUFFER_SIZE - 1);
// printf("Third read returned: %zd\n", bytes_read); // Would block

close(sv&#91;0]);
close(sv&#91;1]);

printf("Communication finished.\n");
return 0;
}

代码解释:

调用 socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) 创建一对 Unix 域数据报套接字。

通过 sv[0] 连续发送两条长度不同的消息。

通过 sv[1] 连续调用 read 两次,每次读取一条完整的消息,展示了 SOCK_DGRAM 的消息边界特性。

如果再调用一次 read,它会因为没有更多数据而阻塞(对于阻塞套接字)。

示例 3:线程间使用 socketpair 通信

这个例子演示了如何在线程之间使用 socketpair 进行通信。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// socketpair_threads.c
// 编译时需要链接 pthread 库: gcc -o socketpair_threads socketpair_threads.c -lpthread
#define _GNU_SOURCE // 启用 GNU 扩展,如 pthread_yield
#include <sys/socket.h>
#include <pthread.h> // pthread_create, pthread_join
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 128

// 全局变量,用于在线程间共享 socketpair 文件描述符
int sv&#91;2];

void* thread_function(void *arg) {
char buffer&#91;BUFFER_SIZE];
ssize_t bytes_read;

printf("Thread: Running...\n");

// 线程通过 sv&#91;1] 发送消息
const char *msg = "Message from thread";
printf("Thread: Sending message...\n");
if (write(sv&#91;1], msg, strlen(msg)) != (ssize_t)strlen(msg)) {
perror("Thread: write failed");
return NULL;
}

// 线程通过 sv&#91;1] 接收消息
printf("Thread: Waiting for reply...\n");
bytes_read = read(sv&#91;1], buffer, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("Thread: Received reply: %s\n", buffer);
}

printf("Thread: Exiting.\n");
return NULL;
}

int main() {
pthread_t thread;
char buffer&#91;BUFFER_SIZE];
ssize_t bytes_read;

// 创建 socketpair
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
perror("socketpair failed");
exit(EXIT_FAILURE);
}

printf("Main: Socket pair created.\n");

// 创建线程
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("pthread_create failed");
close(sv&#91;0]);
close(sv&#91;1]);
exit(EXIT_FAILURE);
}

// 主线程通过 sv&#91;0] 接收消息
printf("Main: Waiting for message from thread...\n");
bytes_read = read(sv&#91;0], buffer, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("Main: Received from thread: %s\n", buffer);
}

// 主线程通过 sv&#91;0] 发送回复
const char *reply = "Reply from main";
printf("Main: Sending reply...\n");
if (write(sv&#91;0], reply, strlen(reply)) != (ssize_t)strlen(reply)) {
perror("Main: write reply failed");
}

// 等待线程结束
if (pthread_join(thread, NULL) != 0) {
perror("pthread_join failed");
}

close(sv&#91;0]);
close(sv&#91;1]);
printf("Main: Exiting.\n");

return 0;
}

代码解释:

在 main 函数中创建 socketpair,并将返回的文件描述符存储在全局变量 sv 中。

创建一个新线程 thread。

主线程:

  • 通过 sv[0] 从线程读取消息。

  • 通过 sv[0] 向线程发送回复。

  • 等待线程结束。

线程函数:

  • 通过 sv[1] 向主线程发送消息。

  • 通过 sv[1] 从主线程读取回复。

  • 退出。

重要提示与注意事项:

域的选择: 在可移植代码中,应始终使用 AF_UNIX 作为 domain 参数。

类型的选择:

  • SOCK_STREAM: 提供无边界的字节流,行为类似 pipe,但双向。

  • SOCK_DGRAM: 提供有边界的、独立的消息传递,行为类似 UDP,但本地。

与 pipe 的比较:

  • pipe 是单向的,socketpair 是双向的。

  • socketpair 提供了套接字的所有功能(如 send, recv, 套接字选项等),而 pipe 只能用 read/write。

关闭: 与管道和普通套接字一样,使用完毕后需要调用 close() 关闭两端的文件描述符。

错误处理: 始终检查返回值。失败可能由资源限制或不支持的参数引起。

用途: 常用于需要双向 IPC 的场景,特别是在 fork 之后或在多线程环境中。

总结:

socketpair 是一个强大的工具,用于在同一主机上创建一对相互连接的匿名套接字。它提供了比 pipe 更灵活的双向通信能力,并且具有套接字的所有特性。理解其参数和使用方法对于实现高效的本地进程间或线程间通信非常有帮助。

https://www.calcguide.tech/2025/08/10/socketpair系统调用及示例/

https://www.calcguide.tech/2025/08/26/linux开源软件路线图/

socket系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 socket 函数,它是网络编程的基础,用于创建一个套接字 (socket),这是进程间通过网络进行通信的端点。

1. 函数介绍

socket 是一个 Linux 系统调用,用于创建一个新的**套接字 **(socket)。套接字是一种抽象的概念,它是网络通信的基础端点。你可以把套接字想象成电话的听筒:

  • 你需要先有一个听筒(调用 socket 创建)。

  • 然后你可以用它来拨打电话(connect,作为客户端)或接听电话(bind + listen + accept,作为服务器)。

  • 通过这个听筒,你可以说话(send/write)和听话(recv/read)。

socket 函数本身并不执行网络连接,它只是创建一个通信的“插头”或“接口”,后续需要使用 bind, listen, accept, connect, send, recv 等函数来完成具体的网络操作。

2. 函数原型

1
2
3
4
5
#include <sys/socket.h> // 必需
#include <sys/types.h> // 有时需要

int socket(int domain, int type, int protocol);

3. 功能

  • 创建套接字: 请求内核创建一个新的套接字对象。

  • 指定通信特性: 通过参数定义套接字的**通信域 **(domain)、**类型 (type) 和协议 **(protocol),从而确定套接字的行为和能力。

  • 返回文件描述符: 如果成功,返回一个与新创建的套接字关联的**文件描述符 **(file descriptor)。后续所有对该套接字的操作(如 bind, connect, read, write)都将使用这个文件描述符。

4. 参数

int domain: 指定套接字的通信域,即套接字可以通信的范围。

  • AF_INET: IPv4 Internet 协议域。这是最常用的域,用于通过 IPv4 网络进行通信。

  • AF_INET6: IPv6 Internet 协议域。用于通过 IPv6 网络进行通信。

  • AF_UNIX 或 AF_LOCAL: Unix 域套接字。用于同一台机器上进程间的本地通信,不涉及网络协议栈,非常高效。

  • AF_PACKET: 用于直接访问网络接口(数据链路层)。

  • AF_NETLINK: 用于与内核进行通信。

int type: 指定套接字的通信语义或类型。

  • SOCK_STREAM: 流式套接字。提供面向连接、可靠、有序的双向数据传输。TCP 协议就是基于流式套接字的。数据像水流一样,没有边界。

  • SOCK_DGRAM: 数据报套接字。提供无连接、不可靠(可能丢包、重复、乱序)、有边界的数据传输。UDP 协议就是基于数据报套接字的。数据以一个独立的“包裹”(数据报)形式发送。

  • SOCK_RAW: 原始套接字。允许直接访问底层协议(如 IP 或 ICMP)。通常需要 root 权限。

  • SOCK_SEQPACKET: 有序数据包套接字。提供面向连接、可靠、有边界且有序的数据传输(像 TCP 一样可靠有序,但像 UDP 一样有消息边界)。

修饰符 (可以按位或 | 到 type 上):

  • SOCK_NONBLOCK: 将套接字设置为非阻塞模式。等同于创建套接字后调用 fcntl(sock, F_SETFL, O_NONBLOCK)。

  • SOCK_CLOEXEC: 在调用 exec() 时自动关闭该套接字。等同于创建后调用 fcntl(sock, F_SETFD, FD_CLOEXEC)。这可以防止将套接字意外传递给新执行的程序。

int protocol: 指定在给定域和类型下使用的具体协议。
在大多数情况下,对于 AF_INET 和 AF_INET6:

  • 如果 type 是 SOCK_STREAM,则 protocol 通常为 IPPROTO_TCP (或 0,内核会选择默认协议 TCP)。

  • 如果 type 是 SOCK_DGRAM,则 protocol 通常为 IPPROTO_UDP (或 0,内核会选择默认协议 UDP)。

通常设置为 0,表示使用给定 domain 和 type 的默认协议。

对于原始套接字 (SOCK_RAW),需要显式指定协议,如 IPPROTO_ICMP, IPPROTO_RAW 等。

5. 返回值

  • 成功时: 返回一个非负整数,即新创建套接字的**文件描述符 **(file descriptor)。

  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EACCES 权限不足,EAFNOSUPPORT 不支持的地址族,EINVAL 无效参数,EMFILE 进程打开的文件描述符已达上限,ENFILE 系统打开的文件总数已达上限,ENOMEM 内存不足等)。

6. 相似函数,或关联函数

  • bind: 将套接字与一个本地地址(IP 地址和端口号)关联起来。

  • listen: 使套接字进入监听状态,准备接收来自客户端的连接请求(用于服务器)。

  • accept: 从监听套接字的连接队列中提取第一个未决连接,创建一个新的套接字用于与该客户端通信(用于服务器)。

  • connect: 主动向服务器发起连接请求(用于客户端)。

  • close: 关闭套接字文件描述符,释放相关资源。

  • read / write / send / recv: 通过套接字发送和接收数据。

  • getaddrinfo: 现代的、线程安全的地址解析函数,用于将主机名和服务名转换为套接字地址结构。

7. 示例代码

示例 1:创建不同类型的套接字

这个例子演示了如何创建几种常见的套接字。

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
#include <sys/socket.h> // socket
#include <stdio.h> // perror, printf
#include <stdlib.h> // exit

int main() {
int sockfd;

// 1. 创建一个 IPv4 的 TCP 流式套接字 (最常用)
printf("Creating AF_INET, SOCK_STREAM socket...\n");
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket AF_INET SOCK_STREAM");
// 不 exit,继续演示其他类型
} else {
printf("Success! Socket file descriptor: %d\n", sockfd);
close(sockfd); // 创建后立即关闭,仅作演示
}

// 2. 创建一个 IPv4 的 UDP 数据报套接字
printf("\nCreating AF_INET, SOCK_DGRAM socket...\n");
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("socket AF_INET SOCK_DGRAM");
} else {
printf("Success! Socket file descriptor: %d\n", sockfd);
close(sockfd);
}

// 3. 创建一个 Unix 域流式套接字
printf("\nCreating AF_UNIX, SOCK_STREAM socket...\n");
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket AF_UNIX SOCK_STREAM");
} else {
printf("Success! Socket file descriptor: %d\n", sockfd);
close(sockfd);
}

// 4. 创建一个非阻塞的 TCP 套接字 (使用 SOCK_NONBLOCK 修饰符)
printf("\nCreating non-blocking AF_INET, SOCK_STREAM socket...\n");
sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (sockfd == -1) {
perror("socket AF_INET SOCK_STREAM | SOCK_NONBLOCK");
} else {
printf("Success! Non-blocking socket file descriptor: %d\n", sockfd);
// 检查是否真的非阻塞 (可选)
// int flags = fcntl(sockfd, F_GETFL, 0);
// if (flags & O_NONBLOCK) printf("Confirmed: socket is non-blocking.\n");
close(sockfd);
}

// 5. 尝试创建一个无效的套接字组合 (例如,原始套接字需要权限)
printf("\nTrying to create a raw socket (may fail without root)...\n");
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd == -1) {
perror("socket AF_INET SOCK_RAW IPPROTO_ICMP (expected to fail without root)");
} else {
printf("Success! Raw socket file descriptor: %d\n", sockfd);
close(sockfd);
}

printf("\nSocket creation examples completed.\n");
return 0;
}

代码解释:

演示了创建四种不同类型的套接字:

  • IPv4 TCP 流式套接字 (AF_INET, SOCK_STREAM):这是网络编程中最常见的类型,用于可靠的、面向连接的通信(如 HTTP)。

  • IPv4 UDP 数据报套接字 (AF_INET, SOCK_DGRAM):用于无连接的、不可靠但快速的通信(如 DNS 查询)。

  • Unix 域流式套接字 (AF_UNIX, SOCK_STREAM):用于同一主机上进程间的高效通信。

  • 非阻塞 TCP 套接字 (AF_INET, SOCK_STREAM | SOCK_NONBLOCK):使用 SOCK_NONBLOCK 修饰符创建,避免后续 I/O 操作阻塞。

每次调用 socket 后都检查返回值。

如果成功,打印返回的文件描述符,并立即调用 close 关闭它(因为这只是演示创建)。

最后尝试创建一个需要 root 权限的原始套接字 (SOCK_RAW),在普通用户权限下会失败。

示例 2:简单的 TCP 服务器套接字设置

这个例子演示了服务器端如何创建、绑定、监听一个 TCP 套接字。

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
73
74
75
76
#include <sys/socket.h> // socket, bind, listen
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h> // inet_addr (虽然此例未用,但常与网络编程相关)
#include <unistd.h> // close
#include <stdio.h> // perror, printf
#include <stdlib.h> // exit
#include <string.h> // memset

#define PORT 8080
#define BACKLOG 10 // 等待连接队列的最大长度

int main() {
int server_fd;
struct sockaddr_in address;
int opt = 1; // 用于 setsockopt

// 1. 创建套接字
printf("Creating server socket...\n");
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
printf("Socket created successfully. File descriptor: %d\n", server_fd);

// 2. (可选但推荐) 设置套接字选项
// SO_REUSEADDR: 允许套接字绑定到处于 TIME_WAIT 状态的地址
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt SO_REUSEADDR failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Socket option SO_REUSEADDR set.\n");

// 3. 配置服务器地址结构
memset(&address, 0, sizeof(address)); // 清零结构体
address.sin_family = AF_INET; // IPv4
address.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口 (0.0.0.0)
address.sin_port = htons(PORT); // 端口号,从主机字节序转换为网络字节序

// 4. 将套接字绑定到地址和端口
printf("Binding socket to port %d...\n", PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Socket bound successfully.\n");

// 5. 让套接字进入监听状态
printf("Putting socket into listening mode (backlog: %d)...\n", BACKLOG);
if (listen(server_fd, BACKLOG) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server is now listening for incoming connections.\n");

printf("\nServer setup complete. Waiting for connections...\n");
printf("(Run a client to connect, e.g., 'telnet localhost %d' or 'nc localhost %d')\n", PORT, PORT);

// --- 服务器已准备好,可以调用 accept() 来接受连接 ---
// 这里为了演示 socket, bind, listen,暂时不实现 accept 循环

// (在实际服务器中,这里会有一个循环调用 accept, fork/handle, close client_sock)

// 按 Ctrl+C 退出程序
pause(); // 永久挂起,直到收到信号

// 6. 关闭套接字 (在实际程序中,这会在适当的地方调用)
close(server_fd);
printf("Server socket closed.\n");

return 0;
}

代码解释:

调用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 IPv4 TCP 套接字。

(重要) 调用 setsockopt 设置 SO_REUSEADDR 选项。这允许服务器在重启时立即绑定到同一个地址,即使之前的连接可能处于 TIME_WAIT 状态。这是一个很好的实践。

初始化 sockaddr_in 结构体 address 来指定服务器的地址和端口:

  • sin_family = AF_INET:指定 IPv4。

  • sin_addr.s_addr = INADDR_ANY:绑定到所有可用的网络接口(服务器可能有多个网卡)。如果只想绑定到特定 IP,可以使用 inet_addr(“192.168.1.100”) 之类的函数。

  • sin_port = htons(PORT):设置端口号。重要:使用 htons() (host to network short) 将主机字节序的端口号转换为网络字节序。网络协议要求使用大端字节序。

调用 bind(server_fd, …) 将套接字与指定的地址和端口绑定。

调用 listen(server_fd, BACKLOG) 使套接字进入监听模式。BACKLOG 参数指定了内核为此套接字维护的未完成连接队列的最大长度。

此时,服务器已准备好接收客户端连接。后续需要在一个循环中调用 accept() 来处理连接。

为了演示,程序调用 pause() 挂起,等待用户按 Ctrl+C 退出。

程序退出前关闭服务器套接字。

编译和测试:

1
2
3
4
5
6
7
gcc -o tcp_server tcp_server.c
./tcp_server
# 在另一个终端:
# telnet localhost 8080
# 或者
# nc localhost 8080

示例 3:简单的 TCP 客户端套接字设置

这个例子演示了客户端如何创建套接字并连接到服务器。

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
73
74
75
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // inet_addr, inet_ntoa
#include <unistd.h> // close, read, write
#include <stdio.h> // perror, printf
#include <stdlib.h> // exit
#include <string.h> // strlen, memset

#define PORT 8080
#define SERVER_IP "127.0.0.1" // 本地回环地址

int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char *hello = "Hello from client";
char buffer&#91;1024] = {0};

// 1. 创建套接字
printf("Creating client socket...\n");
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket creation error");
exit(EXIT_FAILURE);
}
printf("Client socket created successfully. File descriptor: %d\n", sock);

// 2. 配置服务器地址结构
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);

// 将服务器 IP 地址从文本转换为二进制
// inet_addr 已过时,推荐使用 inet_pton
// if (inet_addr(SERVER_IP) == INADDR_NONE) { ... handle error ... }
if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
perror("inet_pton error or invalid address");
close(sock);
exit(EXIT_FAILURE);
}

// 3. 连接到服务器
printf("Connecting to server %s:%d...\n", SERVER_IP, PORT);
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("connection failed");
close(sock);
exit(EXIT_FAILURE);
}
printf("Connected to server successfully.\n");

// 4. 发送数据到服务器
printf("Sending message to server: %s\n", hello);
if (send(sock, hello, strlen(hello), 0) != (int)strlen(hello)) {
perror("send failed");
close(sock);
exit(EXIT_FAILURE);
}
printf("Message sent.\n");

// 5. 读取服务器的响应
printf("Reading response from server...\n");
int valread = read(sock, buffer, 1024);
if (valread > 0) {
printf("Received from server: %s\n", buffer);
} else if (valread == 0) {
printf("Server closed the connection.\n");
} else {
perror("read failed");
}

// 6. 关闭套接字
close(sock);
printf("Client socket closed.\n");

return 0;
}

代码解释:

调用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 IPv4 TCP 套接字。

初始化 sockaddr_in 结构体 serv_addr 来指定服务器的地址和端口。

  • sin_family = AF_INET。

  • sin_port = htons(PORT):服务器端口。

  • sin_addr:服务器 IP 地址。使用 inet_pton() 将点分十进制字符串 “127.0.0.1” 转换为网络二进制格式。inet_addr() 是旧函数,不推荐。

调用 connect(sock, …) 主动向服务器发起连接请求。这个调用会阻塞,直到连接建立或失败。

连接成功后,使用 send() (或 write()) 向服务器发送数据。

使用 read() (或 recv()) 从服务器读取响应数据。

通信结束后,调用 close() 关闭套接字。

重要提示与注意事项:

字节序: 网络协议规定使用大端字节序(Big-Endian)。主机字节序可能是大端或小端。使用 htons (host to network short), htonl (host to network long), ntohs, ntohl 进行转换。

错误处理: 始终检查 socket 及后续网络函数的返回值。

资源管理: 使用完套接字后,务必调用 close() 关闭它,以释放文件描述符和内核资源。

阻塞与非阻塞: 默认情况下,套接字是阻塞的。connect, read, write 等操作可能会无限期挂起。可以使用 SOCK_NONBLOCK 创建非阻塞套接字,或用 fcntl 修改现有套接字的标志。

bind 对于客户端?: 客户端通常不需要显式调用 bind。操作系统会自动为客户端套接字分配一个临时的端口号(ephemeral port)。

getaddrinfo: 对于需要处理 IPv4/IPv6 透明性或域名解析的现代程序,推荐使用 getaddrinfo() 来获取地址信息,而不是手动填充 sockaddr_in。

总结:

socket 函数是网络编程的起点,它创建了通信的端点。理解其参数(域、类型、协议)对于选择正确的通信方式至关重要。它是构建客户端和服务器应用程序的基础。

splice系统调用及示例

splice 函数详解

  1. 函数介绍

splice 是Linux系统调用,用于在两个文件描述符之间高效地移动数据,而无需将数据复制到用户空间。它是Linux特有的零拷贝I/O操作,特别适用于管道、套接字和文件之间的数据传输,能够显著提高I/O性能。

  1. 函数原型
1
2
3
4
5
6
7
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>

ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out,
size_t len, unsigned int flags);

  1. 功能

splice 在两个文件描述符之间直接传输数据,避免了用户空间和内核空间之间的数据拷贝。它利用内核中的管道缓冲区机制,实现了高效的零拷贝数据传输。

  1. 参数
  • int fd_in: 源文件描述符

  • *loff_t off_in: 源文件偏移量指针(NULL表示使用当前文件位置)

  • int fd_out: 目标文件描述符

  • *loff_t off_out: 目标文件偏移量指针(NULL表示使用当前文件位置)

  • size_t len: 要传输的数据长度

  • unsigned int flags: 控制标志

  1. 返回值
  • 成功: 返回实际传输的字节数

  • 失败: 返回-1,并设置errno

  1. 相似函数,或关联函数
  • tee: 在两个管道之间复制数据

  • vmsplice: 将用户空间数据写入管道

  • sendfile: 在文件描述符之间传输数据

  • copy_file_range: 复制文件数据

  1. 示例代码

示例1:基础splice使用

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>

/**
* 演示基础splice使用方法
*/
int demo_splice_basic() {
int pipefd&#91;2];
int input_fd, output_fd;
const char *input_file = "input.txt";
const char *output_file = "output.txt";
char test_data&#91;] = "This is test data for splice operation.\nHello, splice!\n";
ssize_t bytes_transferred;

printf("=== 基础splice使用示例 ===\n");

// 创建测试输入文件
input_fd = open(input_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (input_fd == -1) {
perror("创建输入文件失败");
return -1;
}

write(input_fd, test_data, strlen(test_data));
close(input_fd);
printf("创建测试输入文件: %s\n", input_file);

// 创建管道
if (pipe(pipefd) == -1) {
perror("创建管道失败");
unlink(input_file);
return -1;
}

printf("创建管道: 读端=%d, 写端=%d\n", pipefd&#91;0], pipefd&#91;1]);

// 打开输入文件用于读取
input_fd = open(input_file, O_RDONLY);
if (input_fd == -1) {
perror("打开输入文件失败");
close(pipefd&#91;0]);
close(pipefd&#91;1]);
unlink(input_file);
return -1;
}

// 打开输出文件用于写入
output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (output_fd == -1) {
perror("创建输出文件失败");
close(input_fd);
close(pipefd&#91;0]);
close(pipefd&#91;1]);
unlink(input_file);
return -1;
}

printf("开始splice操作:\n");

// 第一步:从文件读取到管道(使用SPLICE_F_MOVE标志)
bytes_transferred = splice(input_fd, NULL, pipefd&#91;1], NULL,
strlen(test_data), SPLICE_F_MOVE);
if (bytes_transferred == -1) {
perror("splice读取到管道失败");
goto cleanup;
}
printf(" 从文件到管道传输了 %zd 字节\n", bytes_transferred);

// 第二步:从管道写入到文件
bytes_transferred = splice(pipefd&#91;0], NULL, output_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (bytes_transferred == -1) {
perror("splice从管道写入文件失败");
goto cleanup;
}
printf(" 从管道到文件传输了 %zd 字节\n", bytes_transferred);

printf("splice操作完成\n");

// 验证输出文件内容
printf("\n验证输出文件内容:\n");
char buffer&#91;256];
lseek(output_fd, 0, SEEK_SET);
int output_read_fd = open(output_file, O_RDONLY);
if (output_read_fd != -1) {
ssize_t read_bytes = read(output_read_fd, buffer, sizeof(buffer) - 1);
if (read_bytes > 0) {
buffer&#91;read_bytes] = '\0';
printf("输出文件内容: %s", buffer);
}
close(output_read_fd);
}

cleanup:
close(input_fd);
close(output_fd);
close(pipefd&#91;0]);
close(pipefd&#91;1]);
unlink(input_file);
unlink(output_file);

return 0;
}

int main() {
return demo_splice_basic();
}

示例2:管道到套接字的数据传输

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

/**
* 服务器线程函数
*/
void* server_thread(void *arg) {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
int pipefd&#91;2];
char buffer&#91;1024];
ssize_t bytes_read, bytes_written;

printf("服务器线程启动\n");

// 创建TCP服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("创建服务器套接字失败");
return NULL;
}

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);

// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(server_fd);
return NULL;
}

// 监听连接
if (listen(server_fd, 1) == -1) {
perror("监听失败");
close(server_fd);
return NULL;
}

printf("服务器监听在端口 8080\n");

// 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("接受连接失败");
close(server_fd);
return NULL;
}

printf("客户端连接: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

// 创建管道用于splice操作
if (pipe(pipefd) == -1) {
perror("创建管道失败");
close(client_fd);
close(server_fd);
return NULL;
}

// 准备测试数据
const char *test_data = "Hello from splice server!\nThis is data transferred using splice.\n";

// 将数据写入管道
bytes_written = write(pipefd&#91;1], test_data, strlen(test_data));
if (bytes_written == -1) {
perror("写入管道失败");
close(pipefd&#91;0]);
close(pipefd&#91;1]);
close(client_fd);
close(server_fd);
return NULL;
}

printf("写入管道 %zd 字节数据\n", bytes_written);

// 使用splice将管道数据传输到套接字
ssize_t bytes_transferred = splice(pipefd&#91;0], NULL, client_fd, NULL,
bytes_written, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred == -1) {
perror("splice传输失败");
} else {
printf("splice传输了 %zd 字节到客户端\n", bytes_transferred);
}

// 清理资源
close(pipefd&#91;0]);
close(pipefd&#91;1]);
close(client_fd);
close(server_fd);

printf("服务器线程结束\n");
return NULL;
}

/**
* 客户端函数
*/
int client_function() {
int client_fd;
struct sockaddr_in server_addr;
char buffer&#91;1024];
ssize_t bytes_received;

printf("客户端启动\n");

// 创建TCP客户端套接字
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("创建客户端套接字失败");
return -1;
}

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

// 连接到服务器
printf("连接到服务器...\n");
if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("连接服务器失败");
close(client_fd);
return -1;
}

printf("连接成功\n");

// 接收数据
bytes_received = read(client_fd, buffer, sizeof(buffer) - 1);
if (bytes_received > 0) {
buffer&#91;bytes_received] = '\0';
printf("接收到服务器数据 (%zd 字节):\n%s", bytes_received, buffer);
} else if (bytes_received == -1) {
perror("接收数据失败");
} else {
printf("连接关闭\n");
}

close(client_fd);
printf("客户端结束\n");
return 0;
}

/**
* 演示管道到套接字的splice传输
*/
int demo_pipe_to_socket_splice() {
pthread_t server_tid;

printf("=== 管道到套接字splice传输演示 ===\n");

// 创建服务器线程
if (pthread_create(&server_tid, NULL, server_thread, NULL) != 0) {
perror("创建服务器线程失败");
return -1;
}

// 等待服务器启动
sleep(1);

// 运行客户端
client_function();

// 等待服务器线程结束
pthread_join(server_tid, NULL);

return 0;
}

int main() {
return demo_pipe_to_socket_splice();
}

示例3:文件到文件的零拷贝传输

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>

/**
* 创建大文件用于测试
*/
int create_large_test_file(const char *filename, size_t size) {
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
return -1;
}

// 分配缓冲区
char *buffer = malloc(1024 * 1024); // 1MB缓冲区
if (!buffer) {
perror("分配缓冲区失败");
close(fd);
return -1;
}

// 填充测试数据
for (int i = 0; i < 1024 * 1024; i++) {
buffer&#91;i] = 'A' + (i % 26);
}

// 写入数据
size_t written = 0;
while (written < size) {
size_t to_write = (size - written < 1024 * 1024) ? size - written : 1024 * 1024;
ssize_t result = write(fd, buffer, to_write);
if (result == -1) {
perror("写入文件失败");
free(buffer);
close(fd);
return -1;
}
written += result;
}

free(buffer);
close(fd);

printf("创建了 %zu MB 的测试文件: %s\n", size / (1024 * 1024), filename);
return 0;
}

/**
* 使用splice进行文件传输
*/
ssize_t splice_file_transfer(int src_fd, int dst_fd, size_t total_size) {
int pipefd&#91;2];
ssize_t total_transferred = 0;
ssize_t bytes_transferred;
const size_t chunk_size = 64 * 1024; // 64KB chunks

// 创建管道
if (pipe(pipefd) == -1) {
perror("创建管道失败");
return -1;
}

printf("开始splice文件传输 (%zu 字节)...\n", total_size);

while (total_transferred < (ssize_t)total_size) {
size_t remaining = total_size - total_transferred;
size_t to_transfer = (remaining < chunk_size) ? remaining : chunk_size;

// 从源文件读取到管道
bytes_transferred = splice(src_fd, NULL, pipefd&#91;1], NULL,
to_transfer, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue; // 重试
}
perror("splice读取失败");
close(pipefd&#91;0]);
close(pipefd&#91;1]);
return -1;
}

if (bytes_transferred == 0) {
break; // 文件结束
}

// 从管道写入到目标文件
ssize_t bytes_written = splice(pipefd&#91;0], NULL, dst_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (bytes_written == -1) {
perror("splice写入失败");
close(pipefd&#91;0]);
close(pipefd&#91;1]);
return -1;
}

total_transferred += bytes_written;

if (total_transferred % (10 * 1024 * 1024) == 0) { // 每10MB显示一次进度
printf("已传输: %.1f MB\n", total_transferred / (1024.0 * 1024.0));
}
}

close(pipefd&#91;0]);
close(pipefd&#91;1]);

printf("文件传输完成: %zd 字节\n", total_transferred);
return total_transferred;
}

/**
* 比较splice和传统read/write性能
*/
int compare_transfer_performance() {
const char *src_file = "large_source.dat";
const char *dst_file_splice = "large_dest_splice.dat";
const char *dst_file_traditional = "large_dest_traditional.dat";
const size_t file_size = 100 * 1024 * 1024; // 100MB
struct timespec start, end;
double splice_time, traditional_time;

printf("=== 文件传输性能对比 ===\n");

// 创建测试文件
if (create_large_test_file(src_file, file_size) != 0) {
return -1;
}

// 测试splice性能
printf("\n1. 测试splice传输性能:\n");
clock_gettime(CLOCK_MONOTONIC, &start);

int src_fd = open(src_file, O_RDONLY);
int dst_fd = open(dst_file_splice, O_CREAT | O_WRONLY | O_TRUNC, 0644);

if (src_fd != -1 && dst_fd != -1) {
ssize_t transferred = splice_file_transfer(src_fd, dst_fd, file_size);
if (transferred != -1) {
clock_gettime(CLOCK_MONOTONIC, &end);
splice_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
printf("splice传输时间: %.3f 秒\n", splice_time);
printf("splice传输速度: %.2f MB/s\n", file_size / (1024.0 * 1024.0) / splice_time);
}
}

if (src_fd != -1) close(src_fd);
if (dst_fd != -1) close(dst_fd);

// 测试传统read/write性能
printf("\n2. 测试传统read/write性能:\n");
clock_gettime(CLOCK_MONOTONIC, &start);

src_fd = open(src_file, O_RDONLY);
dst_fd = open(dst_file_traditional, O_CREAT | O_WRONLY | O_TRUNC, 0644);

if (src_fd != -1 && dst_fd != -1) {
char *buffer = malloc(64 * 1024);
if (buffer) {
ssize_t bytes_read, bytes_written;
ssize_t total_transferred = 0;

while ((bytes_read = read(src_fd, buffer, 64 * 1024)) > 0) {
bytes_written = write(dst_fd, buffer, bytes_read);
if (bytes_written == -1) {
perror("写入失败");
break;
}
total_transferred += bytes_written;
}

free(buffer);
clock_gettime(CLOCK_MONOTONIC, &end);
traditional_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
printf("传统传输时间: %.3f 秒\n", traditional_time);
printf("传统传输速度: %.2f MB/s\n", file_size / (1024.0 * 1024.0) / traditional_time);
}
}

if (src_fd != -1) close(src_fd);
if (dst_fd != -1) close(dst_fd);

// 显示性能对比
printf("\n=== 性能对比结果 ===\n");
if (splice_time > 0 && traditional_time > 0) {
double improvement = (traditional_time - splice_time) / traditional_time * 100;
printf("splice性能提升: %.1f%%\n", improvement);
}

// 验证文件一致性
printf("\n验证文件一致性:\n");
struct stat src_stat, splice_stat, traditional_stat;

if (stat(src_file, &src_stat) == 0 &&
stat(dst_file_splice, &splice_stat) == 0 &&
stat(dst_file_traditional, &traditional_stat) == 0) {

if (src_stat.st_size == splice_stat.st_size &&
splice_stat.st_size == traditional_stat.st_size) {
printf("✓ 文件大小一致\n");
} else {
printf("✗ 文件大小不一致\n");
}
}

// 清理测试文件
unlink(src_file);
unlink(dst_file_splice);
unlink(dst_file_traditional);

return 0;
}

int main() {
return compare_transfer_performance();
}

示例4:网络代理服务器

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <poll.h>

/**
* 代理连接结构
*/
typedef struct {
int client_fd;
int target_fd;
int pipe_to_target&#91;2];
int pipe_to_client&#91;2];
volatile int active;
} proxy_connection_t;

/**
* 创建到目标服务器的连接
*/
int connect_to_target(const char *target_ip, int target_port) {
int target_fd;
struct sockaddr_in target_addr;

target_fd = socket(AF_INET, SOCK_STREAM, 0);
if (target_fd == -1) {
perror("创建目标连接失败");
return -1;
}

memset(&target_addr, 0, sizeof(target_addr));
target_addr.sin_family = AF_INET;
target_addr.sin_port = htons(target_port);
target_addr.sin_addr.s_addr = inet_addr(target_ip);

if (connect(target_fd, (struct sockaddr*)&target_addr, sizeof(target_addr)) == -1) {
perror("连接目标服务器失败");
close(target_fd);
return -1;
}

printf("成功连接到目标服务器 %s:%d\n", target_ip, target_port);
return target_fd;
}

/**
* 代理连接处理线程
*/
void* proxy_thread(void *arg) {
proxy_connection_t *conn = (proxy_connection_t*)arg;
ssize_t bytes_transferred;
struct pollfd fds&#91;2];
int nfds = 2;

printf("代理线程启动,处理客户端连接 %d\n", conn->client_fd);

// 设置非阻塞模式
int flags = fcntl(conn->client_fd, F_GETFL, 0);
fcntl(conn->client_fd, F_SETFL, flags | O_NONBLOCK);
flags = fcntl(conn->target_fd, F_GETFL, 0);
fcntl(conn->target_fd, F_SETFL, flags | O_NONBLOCK);

// 初始化poll结构
fds&#91;0].fd = conn->client_fd;
fds&#91;0].events = POLLIN;
fds&#91;1].fd = conn->target_fd;
fds&#91;1].events = POLLIN;

// 主代理循环
while (conn->active) {
int activity = poll(fds, nfds, 1000); // 1秒超时
if (activity == -1) {
if (errno == EINTR) continue;
perror("poll失败");
break;
}

if (activity == 0) {
continue; // 超时,继续循环
}

// 处理客户端到目标服务器的数据
if (fds&#91;0].revents & POLLIN) {
bytes_transferred = splice(conn->client_fd, NULL, conn->pipe_to_target&#91;1], NULL,
64 * 1024, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred > 0) {
printf("从客户端接收 %zd 字节\n", bytes_transferred);

// 将数据从管道传输到目标服务器
ssize_t sent = splice(conn->pipe_to_target&#91;0], NULL, conn->target_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (sent > 0) {
printf("转发到目标服务器 %zd 字节\n", sent);
} else if (sent == -1) {
perror("转发到目标服务器失败");
break;
}
} else if (bytes_transferred == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("从客户端接收数据失败");
break;
}
} else if (bytes_transferred == 0) {
printf("客户端连接关闭\n");
break;
}
}

// 处理目标服务器到客户端的数据
if (fds&#91;1].revents & POLLIN) {
bytes_transferred = splice(conn->target_fd, NULL, conn->pipe_to_client&#91;1], NULL,
64 * 1024, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred > 0) {
printf("从目标服务器接收 %zd 字节\n", bytes_transferred);

// 将数据从管道传输到客户端
ssize_t sent = splice(conn->pipe_to_client&#91;0], NULL, conn->client_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (sent > 0) {
printf("转发到客户端 %zd 字节\n", sent);
} else if (sent == -1) {
perror("转发到客户端失败");
break;
}
} else if (bytes_transferred == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("从目标服务器接收数据失败");
break;
}
} else if (bytes_transferred == 0) {
printf("目标服务器连接关闭\n");
break;
}
}

// 检查错误条件
if (fds&#91;0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("客户端连接异常\n");
break;
}

if (fds&#91;1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("目标服务器连接异常\n");
break;
}
}

conn->active = 0;
printf("代理线程结束\n");
return NULL;
}

/**
* 代理服务器主函数
*/
int demo_proxy_server() {
int server_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);

printf("=== 网络代理服务器演示 ===\n");

// 创建代理服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("创建代理服务器失败");
return -1;
}

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8081);

// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定代理服务器失败");
close(server_fd);
return -1;
}

// 监听连接
if (listen(server_fd, 10) == -1) {
perror("代理服务器监听失败");
close(server_fd);
return -1;
}

printf("代理服务器监听在端口 8081\n");
printf("注意:这是一个演示程序,实际使用需要完善错误处理\n");

// 由于这是一个演示程序,我们只处理一个连接
printf("等待客户端连接...\n");

int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("接受客户端连接失败");
close(server_fd);
return -1;
}

printf("客户端连接: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

// 连接到目标服务器(这里使用回环地址作为示例)
int target_fd = connect_to_target("127.0.0.1", 80); // 假设80端口有服务
if (target_fd == -1) {
printf("注意:无法连接到目标服务器,演示继续但不会转发数据\n");
target_fd = socket(AF_INET, SOCK_STREAM, 0);
if (target_fd == -1) {
close(client_fd);
close(server_fd);
return -1;
}
}

// 创建代理连接结构
proxy_connection_t conn;
conn.client_fd = client_fd;
conn.target_fd = target_fd;
conn.active = 1;

// 创建管道
if (pipe(conn.pipe_to_target) == -1 || pipe(conn.pipe_to_client) == -1) {
perror("创建管道失败");
close(client_fd);
close(target_fd);
close(server_fd);
return -1;
}

printf("代理连接建立完成\n");
printf("代理服务器功能演示:\n");
printf(" - 使用splice实现零拷贝数据传输\n");
printf(" - 双向数据转发\n");
printf(" - 非阻塞I/O操作\n");
printf(" - 高效的网络代理\n");

// 由于这是一个演示,我们不实际运行代理循环
// 在实际应用中,这里会启动代理线程处理数据转发

sleep(2); // 模拟处理时间

// 清理资源
close(conn.pipe_to_target&#91;0]);
close(conn.pipe_to_target&#91;1]);
close(conn.pipe_to_client&#91;0]);
close(conn.pipe_to_client&#91;1]);
close(client_fd);
close(target_fd);
close(server_fd);

printf("代理服务器演示完成\n");
return 0;
}

int main() {
return demo_proxy_server();
}

示例5:splice性能优化和最佳实践

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/resource.h>

/**
* splice性能测试配置
*/
typedef struct {
size_t buffer_size;
size_t chunk_size;
int use_splice;
int use_flags;
const char *description;
} splice_test_config_t;

/**
* 创建测试数据文件
*/
int create_test_data_file(const char *filename, size_t size) {
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
return -1;
}

// 使用随机数据填充文件
char *buffer = malloc(1024 * 1024);
if (!buffer) {
perror("分配缓冲区失败");
close(fd);
return -1;
}

srand(time(NULL));
for (size_t i = 0; i < size; i += 1024) {
for (int j = 0; j < 1024 && i + j < size; j++) {
buffer&#91;j] = rand() % 256;
}
write(fd, buffer, (size - i < 1024) ? size - i : 1024);
}

free(buffer);
close(fd);

printf("创建测试文件: %s (%.1f MB)\n", filename, size / (1024.0 * 1024.0));
return 0;
}

/**
* 使用splice进行数据传输
*/
double test_splice_performance(const char *src_file, const char *dst_file,
size_t chunk_size) {
int src_fd, dst_fd, pipefd&#91;2];
struct timespec start, end;
ssize_t bytes_transferred, total_transferred = 0;
struct stat file_stat;

// 获取文件大小
if (stat(src_file, &file_stat) == -1) {
perror("获取文件状态失败");
return -1;
}

// 打开文件
src_fd = open(src_file, O_RDONLY);
dst_fd = open(dst_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (src_fd == -1 || dst_fd == -1) {
perror("打开文件失败");
if (src_fd != -1) close(src_fd);
if (dst_fd != -1) close(dst_fd);
return -1;
}

// 创建管道
if (pipe(pipefd) == -1) {
perror("创建管道失败");
close(src_fd);
close(dst_fd);
return -1;
}

// 开始计时
clock_gettime(CLOCK_MONOTONIC, &start);

// 执行splice传输
while (total_transferred < file_stat.st_size) {
size_t remaining = file_stat.st_size - total_transferred;
size_t to_transfer = (remaining < chunk_size) ? remaining : chunk_size;

// 读取到管道
bytes_transferred = splice(src_fd, NULL, pipefd&#91;1], NULL,
to_transfer, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred <= 0) {
if (bytes_transferred == -1) {
perror("splice读取失败");
}
break;
}

// 写入到目标文件
ssize_t bytes_written = splice(pipefd&#91;0], NULL, dst_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (bytes_written <= 0) {
if (bytes_written == -1) {
perror("splice写入失败");
}
break;
}

total_transferred += bytes_written;
}

// 结束计时
clock_gettime(CLOCK_MONOTONIC, &end);

// 清理资源
close(pipefd&#91;0]);
close(pipefd&#91;1]);
close(src_fd);
close(dst_fd);

double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
return elapsed;
}

/**
* 使用传统read/write进行数据传输
*/
double test_traditional_performance(const char *src_file, const char *dst_file,
size_t buffer_size) {
int src_fd, dst_fd;
struct timespec start, end;
char *buffer;
ssize_t bytes_read, bytes_written;
ssize_t total_transferred = 0;
struct stat file_stat;

// 获取文件大小
if (stat(src_file, &file_stat) == -1) {
perror("获取文件状态失败");
return -1;
}

// 分配缓冲区
buffer = malloc(buffer_size);
if (!buffer) {
perror("分配缓冲区失败");
return -1;
}

// 打开文件
src_fd = open(src_file, O_RDONLY);
dst_fd = open(dst_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (src_fd == -1 || dst_fd == -1) {
perror("打开文件失败");
free(buffer);
if (src_fd != -1) close(src_fd);
if (dst_fd != -1) close(dst_fd);
return -1;
}

// 开始计时
clock_gettime(CLOCK_MONOTONIC, &start);

// 执行传统传输
while ((bytes_read = read(src_fd, buffer, buffer_size)) > 0) {
bytes_written = write(dst_fd, buffer, bytes_read);
if (bytes_written == -1) {
perror("写入失败");
break;
}
total_transferred += bytes_written;
}

// 结束计时
clock_gettime(CLOCK_MONOTONIC, &end);

// 清理资源
free(buffer);
close(src_fd);
close(dst_fd);

double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
return elapsed;
}

/**
* 演示splice性能优化和最佳实践
*/
int demo_splice_optimization() {
const char *src_file = "performance_test_src.dat";
const char *dst_file_splice = "performance_test_dst_splice.dat";
const char *dst_file_traditional = "performance_test_dst_traditional.dat";
const size_t file_size = 200 * 1024 * 1024; // 200MB
const size_t chunk_sizes&#91;] = {4096, 16384, 65536, 262144, 1048576}; // 4KB到1MB
const int num_chunk_sizes = sizeof(chunk_sizes) / sizeof(chunk_sizes&#91;0]);

printf("=== splice性能优化和最佳实践演示 ===\n");

// 检查系统是否支持splice
printf("检查splice支持:\n");
int test_pipe&#91;2];
if (pipe(test_pipe) == 0) {
printf(" ✓ 系统支持管道操作\n");
close(test_pipe&#91;0]);
close(test_pipe&#91;1]);
} else {
printf(" ✗ 系统不支持管道操作\n");
return -1;
}

// 创建测试文件
printf("\n创建测试文件...\n");
if (create_test_data_file(src_file, file_size) != 0) {
return -1;
}

printf("\n=== 性能测试结果 ===\n");
printf("%-12s %-12s %-15s %-15s %-10s\n",
"传输方式", "块大小", "传输时间(秒)", "传输速度(MB/s)", "性能提升");
printf("%-12s %-12s %-15s %-15s %-10s\n",
"--------", "--------", "------------", "------------", "--------");

// 测试不同块大小的splice性能
for (int i = 0; i < num_chunk_sizes; i++) {
char dst_file&#91;256];
snprintf(dst_file, sizeof(dst_file), "splice_chunk_%zu.dat", chunk_sizes&#91;i]);

double elapsed = test_splice_performance(src_file, dst_file, chunk_sizes&#91;i]);
if (elapsed > 0) {
double speed = file_size / (1024.0 * 1024.0) / elapsed;
printf("%-12s %-12zu %-15.3f %-15.2f %-10s\n",
"splice", chunk_sizes&#91;i], elapsed, speed, "N/A");
}

unlink(dst_file);
}

// 测试传统方法性能(使用不同缓冲区大小)
const size_t buffer_sizes&#91;] = {4096, 16384, 65536, 262144};
const int num_buffer_sizes = sizeof(buffer_sizes) / sizeof(buffer_sizes&#91;0]);

for (int i = 0; i < num_buffer_sizes; i++) {
char dst_file&#91;256];
snprintf(dst_file, sizeof(dst_file), "traditional_buf_%zu.dat", buffer_sizes&#91;i]);

double elapsed = test_traditional_performance(src_file, dst_file, buffer_sizes&#91;i]);
if (elapsed > 0) {
double speed = file_size / (1024.0 * 1024.0) / elapsed;
printf("%-12s %-12zu %-15.3f %-15.2f %-10s\n",
"传统方法", buffer_sizes&#91;i], elapsed, speed, "N/A");
}

unlink(dst_file);
}

// 最佳实践建议
printf("\n=== splice使用最佳实践 ===\n");
printf("1. 块大小选择:\n");
printf(" - 小文件: 4KB-16KB\n");
printf(" - 大文件: 64KB-1MB\n");
printf(" - 根据系统和硬件调整\n");

printf("\n2. 标志使用:\n");
printf(" - SPLICE_F_MOVE: 移动页面而不是复制\n");
printf(" - SPLICE_F_MORE: 提示还有更多数据\n");
printf(" - SPLICE_F_GIFT: 释放用户页面\n");

printf("\n3. 性能优化:\n");
printf(" - 使用合适的管道大小\n");
printf(" - 避免频繁的小块传输\n");
printf(" - 结合非阻塞I/O使用\n");
printf(" - 监控系统资源使用\n");

printf("\n4. 错误处理:\n");
printf(" - 检查返回值和errno\n");
printf(" - 处理部分传输情况\n");
printf(" - 优雅降级到传统方法\n");

// 清理测试文件
unlink(src_file);
unlink(dst_file_splice);
unlink(dst_file_traditional);

return 0;
}

int main() {
return demo_splice_optimization();
}

splice 使用注意事项

系统要求:

内核版本: 需要Linux 2.6.17或更高版本

架构支持: 支持所有主流架构

编译选项: 需要定义_GNU_SOURCE

文件描述符要求:

管道支持: 至少一个文件描述符必须是管道

类型限制: 某些文件类型可能不支持splice

权限要求: 需要适当的文件访问权限

标志参数:

SPLICE_F_MOVE: 尝试移动页面而不是复制

SPLICE_F_NONBLOCK: 非阻塞操作

SPLICE_F_MORE: 提示还有更多数据

SPLICE_F_GIFT: 释放用户页面给内核

错误处理:

EINVAL: 参数无效

ENOMEM: 内存不足

ESPIPE: 文件描述符不支持lseek

EAGAIN: 非阻塞操作时资源不可用

性能考虑:

块大小: 选择合适的传输块大小

管道大小: 调整管道缓冲区大小

系统调用: 减少不必要的系统调用

内存对齐: 考虑内存对齐优化

安全考虑:

权限检查: 确保有适当的文件访问权限

资源限制: 避免消耗过多系统资源

错误恢复: 妥善处理错误情况

最佳实践:

适当使用: 在合适的场景下使用splice

性能测试: 在实际环境中测试性能

错误处理: 完善的错误处理机制

资源管理: 及时释放分配的资源

splice vs 相似函数对比

splice vs sendfile:

1
2
3
4
5
6
7
// splice: 需要管道作为中间缓冲
splice(fd_in, NULL, pipe_write, NULL, len, flags);
splice(pipe_read, NULL, fd_out, NULL, len, flags);

// sendfile: 直接在文件描述符间传输
sendfile(fd_out, fd_in, &offset, count);

splice vs tee:

1
2
3
4
5
6
// splice: 移动数据
splice(pipe1_read, NULL, pipe2_write, NULL, len, flags);

// tee: 复制数据(不消耗源数据)
tee(pipe1_read, pipe2_write, len, flags);

常见使用场景

1. 网络代理:

1
2
3
4
// 在代理服务器中高效转发数据
splice(client_fd, NULL, pipe_write, NULL, len, SPLICE_F_MOVE);
splice(pipe_read, NULL, target_fd, NULL, len, SPLICE_F_MOVE);

2. 文件复制:

1
2
3
4
// 高效的文件复制操作
splice(src_fd, NULL, pipe_write, NULL, len, SPLICE_F_MOVE);
splice(pipe_read, NULL, dst_fd, NULL, len, SPLICE_F_MOVE);

3. 日志处理:

1
2
3
4
// 实时日志转发和处理
splice(log_fd, NULL, pipe_write, NULL, len, SPLICE_F_MORE);
splice(pipe_read, NULL, network_fd, NULL, len, SPLICE_F_MOVE);

总结

splice 是Linux系统中强大的零拷贝I/O操作函数,提供了:

高效传输: 避免用户空间和内核空间的数据拷贝

灵活使用: 支持多种文件描述符类型

性能优化: 显著提高大文件和网络传输性能

系统集成: 与Linux内核深度集成

通过合理使用 splice,可以构建高性能的数据传输应用。在实际应用中,需要注意系统要求、错误处理和性能优化等关键问题。

statfs系统调用及示例

好的,我们来深入学习 statfs 系统调用

1. 函数介绍

在 Linux 系统中,文件和目录都存储在各种各样的文件系统之上,比如你系统盘常用的 ext4,或者 U 盘上的 vfat (FAT32)。每个文件系统都有自己的特性,比如总容量多大、现在用了多少、还剩多少空间、文件名最长支持多少个字符等等。

statfs (Stat File System) 系统调用的作用就是查询指定路径所在文件系统的各种统计信息和属性。

你可以把它想象成一个“文件系统信息查询器”。你随便给它一个路径(比如 /home, /tmp, /mnt/my_usb),它就能告诉你这个路径所在的那个磁盘分区(文件系统)的详细情况。

简单来说,statfs 就是让你用程序来查看某个磁盘分区或挂载点的“健康报告”和“容量信息”。

典型应用场景:

  • 磁盘空间监控:检查磁盘剩余空间,防止程序因磁盘写满而崩溃。

  • 系统信息工具:像 df 命令就是使用 statfs (或类似的 statvfs) 来显示磁盘使用情况的。

  • 文件系统类型检查:确认某个挂载点使用的是什么类型的文件系统(例如,检查 /tmp 是否是 tmpfs)。

  • 资源管理:根据可用空间决定是否执行某些操作。

2. 函数原型

1
2
3
4
5
6
#include <sys/vfs.h>    // 包含系统调用声明 (在某些系统上可能是 <sys/statfs.h>)
// #include <sys/statfs.h> // 备选包含

int statfs(const char *path, struct statfs *buf);
int fstatfs(int fd, struct statfs *buf); // fstatfs 通过已打开的文件描述符查询

3. 功能

获取指定路径 (path) 或文件描述符 (fd) 所在文件系统的统计信息,并将结果存储在 buf 指向的 struct statfs 结构体中。

4. 参数

path:

  • const char * 类型。

  • 指向一个以 null 结尾的字符串,表示文件系统中的任意一个路径名。函数会查询这个路径所在的文件系统的统计信息。

fd:

  • int 类型。

  • 一个已打开文件的有效文件描述符。fstatfs 会查询该文件描述符对应的文件所在的文件系统的统计信息。

buf:

  • struct statfs * 类型。

  • 一个指向 struct statfs 结构体的指针。函数调用成功后,会将查询到的文件系统信息填充到这个结构体中。

5. 返回值

  • 成功: 返回 0。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

  • EACCES: (对于 statfs) 搜索 path 中的一个或多个组件时权限不足。

  • EFAULT: path 或 buf 指向了调用进程无法访问的内存地址。

  • EIO: I/O 错误(例如,读取文件系统超级块失败)。

  • ELOOP: 解析 path 时遇到符号链接循环。

  • ENAMETOOLONG: path 太长。

  • ENOENT: path 指定的文件或目录不存在。

  • ENOMEM: 内核内存不足。

  • ENOSYS: 系统不支持 statfs。

  • ENOTDIR: path 的某个前缀不是目录。

  • EOVERFLOW: 结构体中的某些值溢出。

  • EBADF: (对于 fstatfs) fd 不是有效的文件描述符。

7. struct statfs 结构体

这个结构体包含了文件系统的各种信息。主要成员包括(定义可能因架构和内核版本略有不同):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct statfs {
__fsword_t f_type; /* 文件系统类型 (Magic Number) */
__fsword_t f_bsize; /* 文件系统最优传输块大小 (Optimal transfer block size) */
fsblkcnt_t f_blocks; /* 文件系统中的总块数 */
fsblkcnt_t f_bfree; /* 文件系统中空闲的块数 */
fsblkcnt_t f_bavail; /* 对非特权用户可用的空闲块数 (可能小于 f_bfree) */
fsfilcnt_t f_files; /* 文件系统中的总 Inode 数 */
fsfilcnt_t f_ffree; /* 文件系统中空闲的 Inode 数 */
fsid_t f_fsid; /* 文件系统 ID */
__fsword_t f_namelen; /* 文件名最大长度 */
__fsword_t f_frsize; /* 片段大小 (Fragment size) */
__fsword_t f_flags; /* 文件系统挂载标志 */
... /* 可能还有其他字段 */
};

关键字段解释:

  • f_bsize: 这是文件系统推荐用于 I/O 操作的块大小。进行读写操作时使用这个大小通常效率最高。

  • f_blocks: 文件系统总共有多少个块。

  • f_bfree: 文件系统总共有多少个空闲块。

  • f_bavail: 对普通用户(非 root)来说,实际还可以使用的空闲块数量。有些文件系统会保留一部分空间给 root 用户,以防系统关键进程因磁盘满而无法运行。

  • f_files: 文件系统总共包含多少个 Inode(索引节点)。每个文件或目录都对应一个 Inode。

  • f_ffree: 文件系统中空闲的 Inode 数量。

  • f_type: 文件系统的类型,用一个魔数 (Magic Number) 表示。例如,EXT4_SUPER_MAGIC (0xEF53) 代表 ext4,TMPFS_MAGIC (0x01021994) 代表 tmpfs。可以通过比较这个值来判断文件系统类型。

  • f_namelen: 文件系统支持的文件名或目录名的最大长度。

8. 相似函数或关联函数

  • statvfs / fstatvfs: POSIX 标准定义的函数,功能与 statfs / fstatfs 几乎相同,但使用 struct statvfs 结构体。通常推荐使用 statvfs 以获得更好的可移植性。

  • df: 命令行工具,显示文件系统磁盘空间使用情况。它在底层调用的就是 statfs 或 statvfs。

  • getmntent: 用于读取 /proc/mounts 或 /etc/mtab 文件,获取系统上所有已挂载文件系统的信息。

  • du: 命令行工具,估算文件和目录的空间使用情况。它通过遍历目录和文件来计算,而不是查询文件系统元数据。

9. 示例代码

下面的示例演示了如何使用 statfs 来查询不同路径的文件系统信息。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/vfs.h> // 包含 statfs
#include <string.h>
#include <errno.h>
#include <fcntl.h> // 包含 open

// 打印文件系统信息的辅助函数
void print_fs_info(const char* path, const struct statfs *sfs) {
printf("Filesystem information for '%s':\n", path);
printf(" Optimal I/O Block Size: %ld bytes\n", (long)sfs->f_bsize);
printf(" Total Data Blocks: %ld\n", (long)sfs->f_blocks);
printf(" Free Blocks: %ld\n", (long)sfs->f_bfree);
printf(" Available Blocks: %ld (for non-root users)\n", (long)sfs->f_bavail);
printf(" Total Inodes: %ld\n", (long)sfs->f_files);
printf(" Free Inodes: %ld\n", (long)sfs->f_ffree);
printf(" Filesystem ID: %d:%d\n", (int)sfs->f_fsid.__val&#91;0], (int)sfs->f_fsid.__val&#91;1]);
printf(" Maximum Filename Length: %ld\n", (long)sfs->f_namelen);
printf(" Fragment Size: %ld\n", (long)sfs->f_frsize);
printf(" Mount Flags: 0x%lx\n", (unsigned long)sfs->f_flags);

// 计算并打印人类可读的空间大小
// 注意:块大小和数量可能非常大,使用 double 避免溢出
double total_bytes = (double)sfs->f_blocks * sfs->f_bsize;
double free_bytes = (double)sfs->f_bfree * sfs->f_bsize;
double avail_bytes = (double)sfs->f_bavail * sfs->f_bsize;

const char *units&#91;] = {"B", "KiB", "MiB", "GiB", "TiB"};
int unit_index = 0;
double size_to_print = total_bytes;
while (size_to_print >= 1024 && unit_index < 4) {
size_to_print /= 1024;
unit_index++;
}
printf(" Total Size: %.2f %s\n", size_to_print, units&#91;unit_index]);

unit_index = 0;
size_to_print = avail_bytes;
while (size_to_print >= 1024 && unit_index < 4) {
size_to_print /= 1024;
unit_index++;
}
printf(" Available Size: %.2f %s\n", size_to_print, units&#91;unit_index]);

// 识别文件系统类型 (仅列举几个常见类型)
switch (sfs->f_type) {
case 0xEF53: // EXT2/3/4
printf(" Filesystem Type: ext2/ext3/ext4\n");
break;
case 0x6969: // NFS
printf(" Filesystem Type: NFS\n");
break;
case 0x517B: // SMB/CIFS
printf(" Filesystem Type: SMB/CIFS\n");
break;
case 0x52654973: // ReiserFS
printf(" Filesystem Type: ReiserFS\n");
break;
case 0x5346544E: // NTFS
printf(" Filesystem Type: NTFS\n");
break;
case 0x4d44: // FAT
printf(" Filesystem Type: FAT\n");
break;
case 0x01021994: // TMPFS
printf(" Filesystem Type: tmpfs\n");
break;
case 0x9123683E: // Btrfs
printf(" Filesystem Type: Btrfs\n");
break;
default:
printf(" Filesystem Type: Unknown (Magic: 0x%lx)\n", (unsigned long)sfs->f_type);
break;
}
printf("\n");
}

int main(int argc, char *argv&#91;]) {
struct statfs sfs;
int fd;

printf("--- Demonstrating statfs ---\n");

// 1. 如果没有提供命令行参数,则查询几个常见的路径
if (argc == 1) {
const char *default_paths&#91;] = {"/", "/tmp", "/home", "/proc", "/sys", "/dev"};
int num_paths = sizeof(default_paths) / sizeof(default_paths&#91;0]);

for (int i = 0; i < num_paths; i++) {
if (statfs(default_paths&#91;i], &sfs) == 0) {
print_fs_info(default_paths&#91;i], &sfs);
} else {
// 忽略某些可能不存在或无法访问的路径的错误
if (errno != ENOENT && errno != EACCES) {
printf("Failed to statfs '%s': %s\n", default_paths&#91;i], strerror(errno));
printf("\n");
}
}
}
} else {
// 2. 如果提供了命令行参数,则查询指定的路径
for (int i = 1; i < argc; i++) {
const char *path = argv&#91;i];
printf("Querying path: '%s'\n", path);

// 使用 statfs 查询
if (statfs(path, &sfs) == 0) {
print_fs_info(path, &sfs);
} else {
printf("statfs failed for '%s': %s\n", path, strerror(errno));
}

// 使用 fstatfs 查询 (如果路径是文件)
// 先打开文件
fd = open(path, O_RDONLY);
if (fd != -1) {
if (fstatfs(fd, &sfs) == 0) {
printf("Querying via file descriptor for '%s':\n", path);
print_fs_info("(via fd)", &sfs);
} else {
printf("fstatfs failed for fd of '%s': %s\n", path, strerror(errno));
}
close(fd);
} else {
// 如果打开失败,可能是因为 path 是目录,跳过 fstatfs
printf("Could not open '%s' as file, skipping fstatfs.\n", path);
}
printf("------------------------\n");
}
}

// 3. 演示一个实际应用:检查磁盘空间是否充足
printf("--- Checking Disk Space ---\n");
const char *check_path = "/tmp"; // 检查 /tmp 分区
if (statfs(check_path, &sfs) == 0) {
double avail_bytes = (double)sfs.f_bavail * sfs.f_bsize;
double required_bytes = 100 * 1024 * 1024; // 假设需要 100MB

printf("Checking if '%s' has at least %.2f MiB available...\n",
check_path, required_bytes / (1024 * 1024));

if (avail_bytes >= required_bytes) {
printf("OK: %.2f MiB available, %.2f MiB required.\n",
avail_bytes / (1024 * 1024), required_bytes / (1024 * 1024));
} else {
printf("WARNING: Only %.2f MiB available, %.2f MiB required. Insufficient space!\n",
avail_bytes / (1024 * 1024), required_bytes / (1024 * 1024));
}
} else {
perror("statfs for space check");
}

return 0;
}

10. 编译和运行

1
2
3
4
5
6
7
8
9
10
11
12
13
# 假设代码保存在 statfs_example.c 中
gcc -o statfs_example statfs_example.c

# 1. 不带参数运行,查询默认路径
./statfs_example

# 2. 带参数运行,查询指定路径
./statfs_example / /home /tmp /proc/self

# 3. 查询一个文件
touch /tmp/test_statfs_file
./statfs_example /tmp/test_statfs_file

11. 预期输出 (片段)

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
--- Demonstrating statfs ---
Filesystem information for '/':
Optimal I/O Block Size: 4096 bytes
Total Data Blocks: 123456789
Free Blocks: 56789012
Available Blocks: 50000000 (for non-root users)
Total Inodes: 30000000
Free Inodes: 25000000
Filesystem ID: 12345:67890
Maximum Filename Length: 255
Fragment Size: 4096
Mount Flags: 0x400
Total Size: 471.86 GiB
Available Size: 190.73 GiB
Filesystem Type: ext2/ext3/ext4

Filesystem information for '/tmp':
Optimal I/O Block Size: 4096 bytes
Total Data Blocks: 2048000
Free Blocks: 2048000
Available Blocks: 2048000 (for non-root users)
Total Inodes: 512000
Free Inodes: 512000
Filesystem ID: 0:1234567
Maximum Filename Length: 255
Fragment Size: 4096
Mount Flags: 0x41c
Total Size: 7.81 GiB
Available Size: 7.81 GiB
Filesystem Type: tmpfs

Filesystem information for '/proc':
...
Filesystem Type: proc
...

Filesystem information for '/sys':
...
Filesystem Type: sysfs
...

--- Checking Disk Space ---
Checking if '/tmp' has at least 100.00 MiB available...
OK: 8000.00 MiB available, 100.00 MiB required.

12. 总结

statfs 是一个非常实用的系统调用,用于获取文件系统级别的信息。它对于系统管理、磁盘监控和资源检查类的应用程序非常有价值。通过检查空闲块数 (f_bavail) 和块大小 (f_bsize),可以轻松计算出可用磁盘空间,这对于防止程序因磁盘写满而出错至关重要。理解 struct statfs 中各个字段的含义是使用此函数的关键。

https://www.calcguide.tech/2025/08/26/linux开源软件路线图/

statvfs-statfs系统调用及示例

我们来深入学习 statfs 和 statvfs 这两个系统调用。这两个函数功能非常相似,主要区别在于标准化和可移植性。

1. 函数介绍

在 Linux 系统中,文件和目录都存储在各种各样的文件系统之上(如 ext4, XFS, Btrfs, tmpfs 等)。每个文件系统都有自己的特性,比如总容量多大、现在用了多少、还剩多少空间、支持的文件名最长多少字符等等。

statfs 和 statvfs 系统调用的作用就是查询指定路径所在文件系统的各种统计信息和属性。

你可以把它们想象成“文件系统信息查询器”。你随便给它一个路径(比如 /home, /tmp, /mnt/my_usb),它就能告诉你这个路径所在的那个磁盘分区(文件系统)的详细情况。

简单来说,statfs 和 statvfs 就是让你用程序来查看某个磁盘分区或挂载点的“健康报告”和“容量信息”。

2. 函数原型

1
2
3
4
5
6
7
8
9
10
11
12
13
// statfs: Linux 特定的系统调用
#include <sys/vfs.h> // 包含系统调用声明 (在某些系统上可能是 <sys/statfs.h>)
// #include <sys/statfs.h> // 备选包含

int statfs(const char *path, struct statfs *buf);
int fstatfs(int fd, struct statfs *buf); // 通过已打开的文件描述符查询

// statvfs: POSIX 标准定义的系统调用,可移植性更好
#include <sys/statvfs.h> // 包含系统调用声明

int statvfs(const char *path, struct statvfs *buf);
int fstatvfs(int fd, struct statvfs *buf); // 通过已打开的文件描述符查询

3. 功能

获取指定路径 (path) 或文件描述符 (fd) 所在文件系统的统计信息,并将结果存储在相应的结构体 (buf) 中。

4. 参数

两者参数完全相同:

path:

  • const char * 类型。

  • 指向一个以 null 结尾的字符串,表示文件系统中的任意一个路径名。函数会查询这个路径所在的文件系统的统计信息。

fd:

  • int 类型。

  • 一个已打开文件的有效文件描述符。fstatfs/fstatvfs 会查询该文件描述符对应的文件所在的文件系统的统计信息。

buf:

  • struct statfs * 或 struct statvfs * 类型。

  • 一个指向相应结构体的指针。函数调用成功后,会将查询到的文件系统信息填充到这个结构体中。

5. 返回值

两者返回值也相同:

  • 成功: 返回 0。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

两者共享许多相同的错误码:

  • EACCES: (对于 statfs/statvfs) 搜索 path 中的一个或多个组件时权限不足。

  • EFAULT: path 或 buf 指向了调用进程无法访问的内存地址。

  • EIO: I/O 错误(例如,读取文件系统超级块失败)。

  • ELOOP: 解析 path 时遇到符号链接循环。

  • ENAMETOOLONG: path 太长。

  • ENOENT: path 指定的文件或目录不存在。

  • ENOMEM: 内核内存不足。

  • ENOSYS: 系统不支持该调用。

  • ENOTDIR: path 的某个前缀不是目录。

  • EOVERFLOW: 结构体中的某些值溢出。

  • EBADF: (对于 fstatfs/fstatvfs) fd 不是有效的文件描述符。

7. struct statfs 和 struct statvfs 结构体

这两个结构体包含的信息非常相似,但 struct statvfs 是 POSIX 标准化的,字段名更规范。

struct statfs (Linux 特定)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct statfs {
__fsword_t f_type; /* 文件系统类型 (Magic Number) */
__fsword_t f_bsize; /* 文件系统最优传输块大小 (Optimal transfer block size) */
fsblkcnt_t f_blocks; /* 文件系统中的总块数 */
fsblkcnt_t f_bfree; /* 文件系统中空闲的块数 */
fsblkcnt_t f_bavail; /* 对非特权用户可用的空闲块数 (可能小于 f_bfree) */
fsfilcnt_t f_files; /* 文件系统中的总 Inode 数 */
fsfilcnt_t f_ffree; /* 文件系统中空闲的 Inode 数 */
fsid_t f_fsid; /* 文件系统 ID */
__fsword_t f_namelen; /* 文件名最大长度 */
__fsword_t f_frsize; /* 片段大小 (Fragment size) */
__fsword_t f_flags; /* 文件系统挂载标志 */
... /* 可能还有其他字段 */
};

struct statvfs (POSIX 标准)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct statvfs {
unsigned long f_bsize; /* 文件系统块大小 (用于统计) */
unsigned long f_frsize; /* 片段大小 (Fragment size) */
fsblkcnt_t f_blocks; /* 文件系统中的总片段数 (f_frsize) */
fsblkcnt_t f_bfree; /* 文件系统中空闲的片段数 */
fsblkcnt_t f_bavail; /* 对非特权用户可用的片段数 */
fsfilcnt_t f_files; /* 文件系统中的总 Inode 数 */
fsfilcnt_t f_ffree; /* 文件系统中空闲的 Inode 数 */
fsfilcnt_t f_favail; /* 对非特权用户可用的空闲 Inode 数 */
unsigned long f_fsid; /* 文件系统 ID */
unsigned long f_flag; /* 挂载标志 */
unsigned long f_namemax; /* 最大文件名长度 */
};

关键字段解释 (两个结构体通用概念):

  • f_bsize / f_frsize: f_frsize 是文件系统的基本片段大小。f_bsize 是推荐用于 I/O 操作的块大小(可能与 f_frsize 相同或不同)。计算总大小时通常用 f_blocks * f_frsize。

  • f_blocks: 文件系统总共有多少个片段 (f_frsize)。

  • f_bfree: 文件系统总共有多少个空闲片段。

  • f_bavail: 对普通用户(非 root)来说,实际还可以使用的空闲片段数量。有些文件系统会保留一部分空间给 root 用户。

  • f_files: 文件系统总共包含多少个 Inode(索引节点)。

  • f_ffree: 文件系统中空闲的 Inode 数量。

  • f_type / f_fsid: 文件系统的类型标识符或 ID。

  • f_namelen / f_namemax: 文件系统支持的文件名或目录名的最大长度。

8. statfs vs statvfs:该如何选择?

statfs:

  • 优点:是 Linux 原生接口,可能包含一些 statvfs 没有的 Linux 特定信息(如 f_type 魔数)。

  • 缺点:仅限 Linux。如果你的代码需要在其他 Unix 或类 Unix 系统(如 BSD, macOS)上编译运行,statfs 可能不存在或行为不同。

statvfs:

  • 优点:POSIX 标准。这意味着它在所有符合 POSIX 标准的系统上都可用,具有最佳的可移植性。

  • 缺点:可能缺少一些 Linux 特定的详细信息。

对于 Linux 编程小白的建议:

优先使用 statvfs。它是标准的、可移植的。除非你有特殊需求必须使用 statfs 的特定功能,否则 statvfs 是更好的选择。

如果你确定只在 Linux 上运行,并且需要访问 f_type 这样的 Linux 特定信息,可以使用 statfs。

9. 示例代码

下面的示例演示了如何使用 statfs 和 statvfs 来查询文件系统信息,并比较它们的输出。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/vfs.h> // 包含 statfs
#include <sys/statvfs.h> // 包含 statvfs
#include <string.h>
#include <errno.h>
#include <fcntl.h> // 包含 open

// 辅助函数:打印人类可读的大小
void print_human_readable(double bytes, const char* prefix) {
const char *units&#91;] = {"B", "KiB", "MiB", "GiB", "TiB"};
int unit_index = 0;
double size_to_print = bytes;
while (size_to_print >= 1024 && unit_index < 4) {
size_to_print /= 1024;
unit_index++;
}
printf("%s%.2f %s", prefix, size_to_print, units&#91;unit_index]);
}

// 使用 statfs 打印信息
void print_fs_info_statfs(const char* path, const struct statfs *sfs) {
printf("=== Filesystem information for '%s' (via statfs) ===\n", path);
printf(" Optimal I/O Block Size: %ld bytes\n", (long)sfs->f_bsize);
printf(" Fragment Size: %ld bytes\n", (long)sfs->f_frsize);
printf(" Total Fragments: %ld\n", (long)sfs->f_blocks);
printf(" Free Fragments: %ld\n", (long)sfs->f_bfree);
printf(" Available Fragments: %ld (for non-root users)\n", (long)sfs->f_bavail);
printf(" Total Inodes: %ld\n", (long)sfs->f_files);
printf(" Free Inodes: %ld\n", (long)sfs->f_ffree);
printf(" Filesystem ID: %d:%d\n", (int)sfs->f_fsid.__val&#91;0], (int)sfs->f_fsid.__val&#91;1]);
printf(" Maximum Filename Length: %ld\n", (long)sfs->f_namelen);
printf(" Mount Flags: 0x%lx\n", (unsigned long)sfs->f_flags);

// 计算并打印人类可读的空间大小
double total_bytes = (double)sfs->f_blocks * sfs->f_frsize;
double free_bytes = (double)sfs->f_bfree * sfs->f_frsize;
double avail_bytes = (double)sfs->f_bavail * sfs->f_frsize;

printf(" Total Size: ");
print_human_readable(total_bytes, "");
printf("\n");
printf(" Available Size: ");
print_human_readable(avail_bytes, "");
printf("\n");

// 识别文件系统类型 (仅列举几个常见类型)
switch (sfs->f_type) {
case 0xEF53: // EXT2/3/4
printf(" Filesystem Type: ext2/ext3/ext4\n");
break;
case 0x6969: // NFS
printf(" Filesystem Type: NFS\n");
break;
case 0x517B: // SMB/CIFS
printf(" Filesystem Type: SMB/CIFS\n");
break;
case 0x52654973: // ReiserFS
printf(" Filesystem Type: ReiserFS\n");
break;
case 0x5346544E: // NTFS
printf(" Filesystem Type: NTFS\n");
break;
case 0x4d44: // FAT
printf(" Filesystem Type: FAT\n");
break;
case 0x01021994: // TMPFS
printf(" Filesystem Type: tmpfs\n");
break;
case 0x9123683E: // Btrfs
printf(" Filesystem Type: Btrfs\n");
break;
default:
printf(" Filesystem Type: Unknown (Magic: 0x%lx)\n", (unsigned long)sfs->f_type);
break;
}
printf("\n");
}

// 使用 statvfs 打印信息
void print_fs_info_statvfs(const char* path, const struct statvfs *svfs) {
printf("=== Filesystem information for '%s' (via statvfs) ===\n", path);
printf(" Filesystem Block Size: %ld bytes\n", (long)svfs->f_bsize);
printf(" Fragment Size: %ld bytes\n", (long)svfs->f_frsize);
printf(" Total Fragments: %ld\n", (long)svfs->f_blocks);
printf(" Free Fragments: %ld\n", (long)svfs->f_bfree);
printf(" Available Fragments: %ld (for non-root users)\n", (long)svfs->f_bavail);
printf(" Available Fragments (non-root): %ld\n", (long)svfs->f_bavail); // Same as f_bavail in most cases
printf(" Total Inodes: %ld\n", (long)svfs->f_files);
printf(" Free Inodes: %ld\n", (long)svfs->f_ffree);
printf(" Available Inodes (non-root): %ld\n", (long)svfs->f_favail);
printf(" Filesystem ID: %ld\n", (unsigned long)svfs->f_fsid);
printf(" Maximum Filename Length: %ld\n", (long)svfs->f_namemax);
printf(" Mount Flags: 0x%lx\n", svfs->f_flag);

// 计算并打印人类可读的空间大小
double total_bytes = (double)svfs->f_blocks * svfs->f_frsize;
double free_bytes = (double)svfs->f_bfree * svfs->f_frsize;
double avail_bytes = (double)svfs->f_bavail * svfs->f_frsize;

printf(" Total Size: ");
print_human_readable(total_bytes, "");
printf("\n");
printf(" Available Size: ");
print_human_readable(avail_bytes, "");
printf("\n");
printf("\n");
}

int main(int argc, char *argv&#91;]) {
struct statfs sfs;
struct statvfs svfs;
int fd;

printf("--- Demonstrating statfs vs statvfs ---\n");

// 1. 如果没有提供命令行参数,则查询根目录 "/"
const char *path = "/";
if (argc > 1) {
path = argv&#91;1];
}
printf("Querying path: '%s'\n\n", path);

// 2. 使用 statfs 查询
if (statfs(path, &sfs) == 0) {
print_fs_info_statfs(path, &sfs);
} else {
printf("statfs failed for '%s': %s\n\n", path, strerror(errno));
}

// 3. 使用 statvfs 查询
if (statvfs(path, &svfs) == 0) {
print_fs_info_statvfs(path, &svfs);
} else {
printf("statvfs failed for '%s': %s\n\n", path, strerror(errno));
}

// 4. 演示 fstatfs 和 fstatvfs (通过文件描述符)
printf("=== Comparing fstatfs and fstatvfs ===\n");
fd = open(path, O_RDONLY); // 尝试打开路径 (对于目录是合法的)
if (fd != -1) {
printf("Opened '%s' as file descriptor %d\n", path, fd);

if (fstatfs(fd, &sfs) == 0) {
printf("\n--- fstatfs via fd %d ---\n", fd);
printf(" Total Fragments (fstatfs): %ld\n", (long)sfs.f_blocks);
printf(" Free Fragments (fstatfs): %ld\n", (long)sfs.f_bfree);
} else {
printf("fstatfs failed: %s\n", strerror(errno));
}

if (fstatvfs(fd, &svfs) == 0) {
printf("\n--- fstatvfs via fd %d ---\n", fd);
printf(" Total Fragments (fstatvfs): %ld\n", (long)svfs.f_blocks);
printf(" Free Fragments (fstatvfs): %ld\n", (long)svfs.f_bfree);
} else {
printf("fstatvfs failed: %s\n", strerror(errno));
}

close(fd);
} else {
// 如果打开失败(例如 path 是文件),尝试打开文件本身
fd = open(path, O_RDONLY);
if (fd != -1) {
printf("Opened file '%s' as file descriptor %d\n", path, fd);
// ... (对文件进行 fstatfs/fstatvfs 调用)
// 为简洁起见,此处省略对文件的详细查询,逻辑同上
close(fd);
} else {
printf("Could not open '%s' as file/dir for fstatfs/fstatvfs demo.\n", path);
}
}

// 5. 演示一个实际应用:检查磁盘空间是否充足
printf("\n=== Checking Disk Space ===\n");
const char *check_path = "/tmp"; // 检查 /tmp 分区
if (statvfs(check_path, &svfs) == 0) {
double avail_bytes = (double)svfs.f_bavail * svfs.f_frsize;
double required_bytes = 100 * 1024 * 1024; // 假设需要 100MB

printf("Checking if '%s' has at least %.2f MiB available...\n",
check_path, required_bytes / (1024.0*1024.0));

if (avail_bytes >= required_bytes) {
printf("OK: ");
print_human_readable(avail_bytes, "");
printf(" available, %.2f MiB required.\n", required_bytes / (1024.0*1024.0));
} else {
printf("WARNING: Only ");
print_human_readable(avail_bytes, "");
printf(" available, %.2f MiB required. Insufficient space!\n", required_bytes / (1024.0*1024.0));
}
} else {
perror("statvfs for space check");
}

return 0;
}

10. 编译和运行

1
2
3
4
5
6
7
8
9
10
# 假设代码保存在 statfs_statvfs_example.c 中
gcc -o statfs_statvfs_example statfs_statvfs_example.c

# 1. 不带参数运行,查询根目录 "/"
./statfs_statvfs_example

# 2. 带参数运行,查询指定路径
./statfs_statvfs_example /home
./statfs_statvfs_example /tmp

11. 预期输出 (片段)

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
--- Demonstrating statfs vs statvfs ---
Querying path: '/'

=== Filesystem information for '/' (via statfs) ===
Optimal I/O Block Size: 4096 bytes
Fragment Size: 4096 bytes
Total Fragments: 123456789
Free Fragments: 56789012
Available Fragments: 50000000 (for non-root users)
Total Inodes: 30000000
Free Inodes: 25000000
Filesystem ID: 12345:67890
Maximum Filename Length: 255
Mount Flags: 0x400
Total Size: 471.86 GiB
Available Size: 190.73 GiB
Filesystem Type: ext2/ext3/ext4

=== Filesystem information for '/' (via statvfs) ===
Filesystem Block Size: 4096 bytes
Fragment Size: 4096 bytes
Total Fragments: 123456789
Free Fragments: 56789012
Available Fragments: 50000000 (for non-root users)
Available Fragments (non-root): 50000000
Total Inodes: 30000000
Free Inodes: 25000000
Available Inodes (non-root): 25000000
Filesystem ID: 12345
Maximum Filename Length: 255
Mount Flags: 0x400
Total Size: 471.86 GiB
Available Size: 190.73 GiB

=== Comparing fstatfs and fstatvfs ===
Opened '/' as file descriptor 3

--- fstatfs via fd 3 ---
Total Fragments (fstatfs): 123456789
Free Fragments (fstatfs): 56789012

--- fstatvfs via fd 3 ---
Total Fragments (fstatvfs): 123456789
Free Fragments (fstatvfs): 56789012

=== Checking Disk Space ===
Checking if '/tmp' has at least 100.00 MiB available...
OK: 7.81 GiB available, 100.00 MiB required.

12. 总结

statfs 和 statvfs 都是用于获取文件系统信息的强大工具。

  • statfs: Linux 特定的系统调用,可能提供更详细的 Linux 专有信息(如文件系统类型魔数)。

  • statvfs: POSIX 标准系统调用,具有更好的可移植性,是跨平台开发的首选。

  • 共同点:都能获取文件系统的总容量、可用空间、Inode 信息等关键统计数据。

选择建议:

  • 默认选择 statvfs,以确保代码的可移植性。

  • 仅当需要 Linux 特定信息且确定只在 Linux 上运行时,才考虑使用 statfs。

实际应用:常用于磁盘空间监控、系统信息工具(如 df 命令就是基于它们实现的)、资源管理等场景。理解返回结构体中各个字段的含义是正确使用它们的关键。

statx系统调用及示例

好的,我们继续按照您的要求学习 Linux 系统编程中的重要函数。这次我们介绍 statx。

1. 函数介绍

statx 是一个相对较新的 Linux 系统调用(内核版本 >= 4.11),它是对传统 stat, lstat, fstat 系列函数的现代化扩展和增强。

它的主要功能是获取文件的状态信息(如大小、权限、所有者、时间戳等),与 stat 系列函数相同。但 statx 提供了以下显著优势:

更高效: 通过 flags 参数,调用者可以精确指定需要查询哪些文件属性,内核只返回请求的信息,避免了获取不必要的数据,从而提高了效率。

更丰富的信息: statx 可以返回一些传统 stat 结构 (struct stat) 无法提供的新属性,例如:

  • 创建时间 (stx_btime): 文件的创建时间(birth time),这是许多文件系统支持但传统 stat 无法获取的。

  • 扩展的文件类型和权限: 提供了更详细的文件类型和权限信息。

  • 文件系统 ID (stx_dev_major, stx_dev_minor): 更明确地标识文件所在的设备。

更好的可扩展性: statx 使用了新的 struct statx 结构,这个结构设计时就考虑了未来的扩展性,更容易添加新字段而不会破坏现有程序。

统一接口: 一个函数就能实现 stat, lstat, fstat 的功能,通过 flags 参数控制是否跟随符号链接。

简单来说,statx 是一个更快、更强大、更灵活的 stat。

2. 函数原型

1
2
3
4
5
6
#include <fcntl.h>     // 定义 AT_* 常量和 AT_STATX_* 常量
#include <sys/stat.h> // 定义 struct statx

int statx(int dirfd, const char *pathname, int flags,
unsigned int mask, struct statx *statxbuf);

3. 功能

  • 获取文件状态: 根据提供的 pathname(结合 dirfd 和 flags)获取指定文件的详细状态信息。

  • 按需查询: 通过 mask 参数,调用者可以指定只查询感兴趣的文件属性子集,提高效率。

  • 灵活路径解析: 通过 dirfd, pathname, flags 的组合,可以实现相对路径查找、绝对路径查找、控制符号链接行为等多种路径解析方式。

4. 参数

int dirfd: 用作相对路径查找的起始目录文件描述符。

  • 如果 pathname 是相对路径(例如 “subdir/file.txt”),则相对于 dirfd 指向的目录进行查找。

  • 如果 pathname 是绝对路径(例如 “/home/user/file.txt”),则 dirfd 被忽略。

  • 可以传入特殊的值 AT_FDCWD,表示使用当前工作目录作为起始点进行相对路径查找。

const char *pathname: 指向要查询状态的文件的路径名。可以是相对路径或绝对路径。

int flags: 控制路径解析行为的标志位。可以是以下值的按位或组合:

  • 0: 默认行为。

  • AT_SYMLINK_NOFOLLOW: 如果 pathname 是一个符号链接,则不跟随该链接,而是返回符号链接本身的属性(类似 lstat 的行为)。

  • AT_NO_AUTOMOUNT: 阻止在路径解析过程中触发自动挂载文件系统。

  • AT_EMPTY_PATH: 如果 pathname 是一个空字符串 (“”),则查询 dirfd 本身所引用的文件的状态。dirfd 必须是一个有效的文件描述符。

  • AT_STATX_SYNC_AS_STAT: 使 statx 的同步语义与 stat 相同(默认行为)。

  • AT_STATX_FORCE_SYNC: 强制与服务器同步,获取最新的属性(例如,对于网络文件系统)。

  • AT_STATX_DONT_SYNC: 不要与服务器同步,使用缓存中的属性(如果可用)。

unsigned int mask: 这是一个位掩码,用于指定调用者感兴趣的文件属性。内核只会填充 struct statx 中与 mask 对应的字段。常用的掩码标志包括:

  • STATX_TYPE: 文件类型 (e.g., 普通文件, 目录, 符号链接)。

  • STATX_MODE: 文件权限和类型。

  • STATX_NLINK: 硬链接数。

  • STATX_UID: 所有者用户 ID。

  • STATX_GID: 所有者组 ID。

  • STATX_ATIME: 上次访问时间。

  • STATX_MTIME: 上次修改时间。

  • STATX_CTIME: 上次状态更改时间。

  • STATX_INO: inode 编号。

  • STATX_SIZE: 文件大小 (字节)。

  • STATX_BLOCKS: 分配的 512B 块数。

  • STATX_BASIC_STATS: 以上所有基本属性的组合。

  • STATX_BTIME: 文件创建时间(birth time)。

  • STATX_MNT_ID: (Linux 5.8+) 挂载 ID。

  • STATX_DIOALIGN: (Linux 6.1+) 直接 I/O 对齐要求。

  • STATX_ALL: 所有已知属性的组合。

struct statx *statxbuf: 指向一个 struct statx 类型的结构体的指针。函数调用成功后,该结构体将被填入文件的状态信息。

5. struct statx 结构体

statx 使用新的 struct statx 结构体来返回信息,比 struct stat 更丰富和可扩展。

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
struct statx {
__u32 stx_mask; // 哪些字段被填充了 (对应 mask)
__u32 stx_blksize; // 文件系统 I/O 块大小
__u64 stx_attributes; // 文件的额外属性 (如不可变、追加)
__u32 stx_nlink; // 硬链接数
__u32 stx_uid; // 所有者用户 ID
__u32 stx_gid; // 所有者组 ID
__u16 stx_mode; // 文件类型和权限
__u16 stx_pad1; // 填充
__u64 stx_ino; // inode 编号
__u64 stx_size; // 文件大小 (字节)
__u64 stx_blocks; // 分配的 512B 块数
__u64 stx_attributes_mask; // 有效 attributes 位掩码
struct statx_timestamp stx_atime; // 上次访问时间
struct statx_timestamp stx_btime; // 创建时间 (birth time)
struct statx_timestamp stx_ctime; // 上次状态更改时间
struct statx_timestamp stx_mtime; // 上次修改时间
__u32 stx_rdev_major; // (如果是设备文件) 设备 ID 主号
__u32 stx_rdev_minor; // (如果是设备文件) 设备 ID 次号
__u32 stx_dev_major; // 文件所在设备 ID 主号
__u32 stx_dev_minor; // 文件所在设备 ID 次号
__u64 stx_mnt_id; // 挂载 ID (Linux 5.8+)
__u32 stx_dio_mem_align; // 直接 I/O 内存对齐 (Linux 6.1+)
__u32 stx_dio_offset_align; // 直接 I/O 偏移对齐 (Linux 6.1+)
__u64 stx_subvol; // 子卷 ID (Btrfs) (Linux 6.9+)
__u64 stx_dax; // DAX 支持 (Linux 6.10+)
__u64 stx_pad2&#91;9]; // 保留供将来扩展
};

struct statx_timestamp {
__s64 tv_sec; // 秒
__u32 tv_nsec; // 纳秒
__s32 __reserved;
};

6. 返回值

  • 成功时: 返回 0。同时,statxbuf 指向的 struct statx 结构体被成功填充。

  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 ENOENT 文件不存在,EACCES 权限不足,EINVAL 参数无效等)。

7. 示例代码

示例 1:基本使用 statx 获取文件信息

这个例子演示了如何使用 statx 获取文件的基本信息,并与传统 stat 进行比较。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// statx_basic_example.c
#define _GNU_SOURCE // For statx
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>

void print_statx_info(const char *pathname, struct statx *sbx) {
printf("--- statx info for '%s' ---\n", pathname);
printf(" Mask (fields filled): 0x%x\n", sbx->stx_mask);
printf(" Inode: %llu\n", (unsigned long long)sbx->stx_ino);
printf(" Size: %llu bytes\n", (unsigned long long)sbx->stx_size);
printf(" Blocks: %llu (512B blocks)\n", (unsigned long long)sbx->stx_blocks);
printf(" Device ID: %xh/%d (major), %xh/%d (minor)\n",
sbx->stx_dev_major, sbx->stx_dev_major,
sbx->stx_dev_minor, sbx->stx_dev_minor);
printf(" Links: %u\n", sbx->stx_nlink);
printf(" Mode: %o (octal)\n", sbx->stx_mode);
printf(" UID: %u\n", sbx->stx_uid);
printf(" GID: %u\n", sbx->stx_gid);

// 检查并打印时间戳
if (sbx->stx_mask & STATX_ATIME) {
char time_buf&#91;100];
struct tm *tm_info = localtime((time_t*)&sbx->stx_atime.tv_sec);
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
printf(" Last Access: %s.%09ld\n", time_buf, sbx->stx_atime.tv_nsec);
}
if (sbx->stx_mask & STATX_MTIME) {
char time_buf&#91;100];
struct tm *tm_info = localtime((time_t*)&sbx->stx_mtime.tv_sec);
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
printf(" Last Modify: %s.%09ld\n", time_buf, sbx->stx_mtime.tv_nsec);
}
if (sbx->stx_mask & STATX_CTIME) {
char time_buf&#91;100];
struct tm *tm_info = localtime((time_t*)&sbx->stx_ctime.tv_sec);
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
printf(" Last Status Change: %s.%09ld\n", time_buf, sbx->stx_ctime.tv_nsec);
}
if (sbx->stx_mask & STATX_BTIME) {
char time_buf&#91;100];
struct tm *tm_info = localtime((time_t*)&sbx->stx_btime.tv_sec);
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
printf(" Birth Time: %s.%09ld\n", time_buf, sbx->stx_btime.tv_nsec);
} else {
printf(" Birth Time: Not available\n");
}
printf("---------------------------\n");
}

int main(int argc, char *argv&#91;]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv&#91;0]);
exit(EXIT_FAILURE);
}

const char *pathname = argv&#91;1];
struct statx statx_buf;

// --- 使用 statx 获取基本信息 ---
// dirfd = AT_FDCWD: 使用当前目录作为相对路径起点
// pathname = argv&#91;1]: 文件路径
// flags = 0: 默认行为,跟随符号链接
// mask = STATX_BASIC_STATS: 请求所有基本文件属性
// statx_buf: 用于接收结果的缓冲区
printf("Calling statx with STATX_BASIC_STATS...\n");
if (statx(AT_FDCWD, pathname, 0, STATX_BASIC_STATS, &statx_buf) == -1) {
perror("statx");
exit(EXIT_FAILURE);
}

print_statx_info(pathname, &statx_buf);

// --- 比较: 使用传统 stat ---
printf("\n--- Comparing with traditional stat ---\n");
struct stat stat_buf;
if (stat(pathname, &stat_buf) == -1) {
perror("stat");
// 即使 stat 失败,也继续演示 statx 的其他功能
} else {
printf(" stat() - Size: %ld bytes\n", (long)stat_buf.st_size);
printf(" stat() - Inode: %ld\n", (long)stat_buf.st_ino);
// ... 可以打印更多 stat 字段 ...
}

// --- 使用 statx 只获取特定信息 (例如,只获取大小和修改时间) ---
printf("\nCalling statx with specific mask (SIZE | MTIME)...\n");
struct statx statx_buf_minimal;
if (statx(AT_FDCWD, pathname, 0, STATX_SIZE | STATX_MTIME, &statx_buf_minimal) == -1) {
perror("statx minimal");
} else {
printf(" Minimal query result:\n");
if (statx_buf_minimal.stx_mask & STATX_SIZE) {
printf(" Size: %llu bytes\n", (unsigned long long)statx_buf_minimal.stx_size);
}
if (statx_buf_minimal.stx_mask & STATX_MTIME) {
char time_buf&#91;100];
struct tm *tm_info = localtime((time_t*)&statx_buf_minimal.stx_mtime.tv_sec);
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
printf(" Last Modify: %s.%09ld\n", time_buf, statx_buf_minimal.stx_mtime.tv_nsec);
}
printf(" Note: Only requested fields are filled. Mask = 0x%x\n", statx_buf_minimal.stx_mask);
}

return 0;
}

如何测试:

1
2
3
4
5
6
7
8
# 创建一个测试文件
echo "Hello, statx!" > test_statx_file.txt
touch -d "2023-01-01 10:00:00" test_statx_file.txt # 设置修改时间

# 编译并运行
gcc -o statx_basic_example statx_basic_example.c
./statx_basic_example test_statx_file.txt

代码解释:

定义了一个 print_statx_info 函数来格式化并打印 struct statx 的内容。

在 main 函数中,首先调用 statx(AT_FDCWD, pathname, 0, STATX_BASIC_STATS, &statx_buf)。

  • AT_FDCWD: 使用当前工作目录解析相对路径。

  • 0: 默认 flags,表示如果 pathname 是符号链接,则跟随它。

  • STATX_BASIC_STATS: 请求所有基本的文件状态信息。

  • &statx_buf: 指向用于接收结果的 struct statx 变量。

调用成功后,打印所有获取到的信息。

为了对比,调用传统的 stat() 函数获取相同文件的信息。

再次调用 statx,但这次只请求 STATX_SIZE 和 STATX_MTIME。

  • 这展示了 statx 的效率优势:内核只会填充请求的字段。

  • 打印结果时,可以看到 stx_mask 只包含 STATX_SIZE | STATX_MTIME 对应的位。

示例 2:使用 statx 处理符号链接和获取创建时间

这个例子演示了如何使用 statx 的 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
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// statx_symlink_btime.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

int main() {
const char *target_file = "target_file.txt";
const char *symlink_name = "my_symlink_to_target";

// 1. 创建目标文件
FILE *f = fopen(target_file, "w");
if (f) {
fprintf(f, "This is the target file.\n");
fclose(f);
printf("Created target file: %s\n", target_file);
} else {
perror("Failed to create target file");
}

// 2. 创建符号链接
if (symlink(target_file, symlink_name) == -1) {
if (errno != EEXIST) { // EEXIST 表示链接已存在,可以接受
perror("Failed to create symbolic link");
unlink(target_file);
exit(EXIT_FAILURE);
} else {
printf("Symbolic link '%s' already exists.\n", symlink_name);
}
} else {
printf("Created symbolic link '%s' -> '%s'\n", symlink_name, target_file);
}

struct statx statx_buf;

// --- 3a. 使用 statx 跟随符号链接 (默认行为) ---
printf("\n--- statx('%s', 0) - Following symlink ---\n", symlink_name);
if (statx(AT_FDCWD, symlink_name, 0, STATX_BASIC_STATS | STATX_BTIME, &statx_buf) == -1) {
perror("statx following symlink");
} else {
printf(" Inode: %llu (This is the INODE of the TARGET file)\n", (unsigned long long)statx_buf.stx_ino);
printf(" File Type: ");
if ((statx_buf.stx_mode & S_IFMT) == S_IFREG) printf("Regular File\n");
else if ((statx_buf.stx_mode & S_IFMT) == S_IFLNK) printf("Symbolic Link\n");
else printf("Other\n");
if (statx_buf.stx_mask & STATX_BTIME) {
printf(" Birth Time available.\n");
} else {
printf(" Birth Time NOT available.\n");
}
}

// --- 3b. 使用 statx 不跟随符号链接 ---
printf("\n--- statx('%s', AT_SYMLINK_NOFOLLOW) - NOT Following symlink ---\n", symlink_name);
if (statx(AT_FDCWD, symlink_name, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS | STATX_BTIME, &statx_buf) == -1) {
perror("statx NOT following symlink");
} else {
printf(" Inode: %llu (This is the INODE of the SYMBOLIC LINK itself)\n", (unsigned long long)statx_buf.stx_ino);
printf(" File Type: ");
if ((statx_buf.stx_mode & S_IFMT) == S_IFREG) printf("Regular File\n");
else if ((statx_buf.stx_mode & S_IFMT) == S_IFLNK) printf("Symbolic Link\n");
else printf("Other\n");
if (statx_buf.stx_mask & STATX_BTIME) {
printf(" Birth Time available.\n");
} else {
printf(" Birth Time NOT available.\n");
}
}

// --- 4. 尝试获取创建时间 (Birth Time) ---
printf("\n--- Attempting to get Birth Time (stx_btime) ---\n");
// 需要确保内核和文件系统支持
if (statx(AT_FDCWD, target_file, 0, STATX_BTIME, &statx_buf) == -1) {
perror("statx for btime");
} else {
if (statx_buf.stx_mask & STATX_BTIME) {
char time_buf&#91;100];
struct tm *tm_info = localtime((time_t*)&statx_buf.stx_btime.tv_sec);
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
printf(" Birth Time of '%s': %s.%09ld\n", target_file, time_buf, statx_buf.stx_btime.tv_nsec);
} else {
printf(" Birth Time is NOT supported by the filesystem for '%s'.\n", target_file);
}
}

// --- 5. 使用 AT_EMPTY_PATH 查询已打开文件的状态 ---
printf("\n--- Using AT_EMPTY_PATH with an open file descriptor ---\n");
int fd = open(target_file, O_RDONLY);
if (fd == -1) {
perror("open target file");
} else {
// pathname 为空字符串 "", dirfd 是有效的文件描述符
if (statx(fd, "", AT_EMPTY_PATH, STATX_BASIC_STATS, &statx_buf) == -1) {
perror("statx with AT_EMPTY_PATH");
} else {
printf(" Status of file descriptor %d (refers to '%s'):\n", fd, target_file);
printf(" Size: %llu bytes\n", (unsigned long long)statx_buf.stx_size);
printf(" Inode: %llu\n", (unsigned long long)statx_buf.stx_ino);
}
close(fd);
}

// 清理 (可选)
// printf("\nCleaning up...\n");
// unlink(symlink_name);
// unlink(target_file);

return 0;
}

如何测试:

1
2
3
gcc -o statx_symlink_btime statx_symlink_btime.c
./statx_symlink_btime

代码解释:

创建一个目标文件 target_file.txt 和一个指向它的符号链接 my_symlink_to_target。

比较 flags:

  • 调用 statx(symlink_name, 0, …):默认行为,跟随符号链接。返回的是目标文件的信息(inode 是目标文件的)。

  • 调用 statx(symlink_name, AT_SYMLINK_NOFOLLOW, …):不跟随符号链接。返回的是符号链接本身的信息(inode 是符号链接的)。

获取创建时间:

  • 调用 statx(target_file, 0, STATX_BTIME, …) 尝试获取目标文件的创建时间。

  • 检查返回的 statx_buf.stx_mask 是否包含 STATX_BTIME。如果包含,说明文件系统支持并返回了创建时间;否则,说明不支持。

使用 AT_EMPTY_PATH:

  • 首先打开目标文件得到文件描述符 fd。

  • 调用 statx(fd, “”, AT_EMPTY_PATH, …)。这里 pathname 是空字符串,dirfd 是 fd,AT_EMPTY_PATH 标志告诉 statx 查询 fd 本身引用的文件。

  • 这提供了一种通过文件描述符获取文件状态的方法,类似于 fstat,但具有 statx 的所有优势。

重要提示与注意事项:

内核版本: statx 需要 Linux 内核 4.11 或更高版本。

glibc 版本: 需要 glibc 2.28 或更高版本才能在 <sys/stat.h> 中提供 statx 函数声明。如果 glibc 版本较低,可能需要手动定义或使用 syscall。

效率: 通过使用 mask 参数,只请求需要的字段,可以显著提高性能,尤其是在网络文件系统或需要频繁查询的场景下。

stx_btime (创建时间): 这个字段的可用性高度依赖于底层文件系统。例如,ext4 在较新内核上可能支持,而 tmpfs 或某些网络文件系统可能不支持。务必检查 stx_mask 来确认字段是否有效。

dirfd 和 AT_* 标志: 这些参数提供了强大的路径解析能力,特别是 AT_SYMLINK_NOFOLLOW 和 AT_EMPTY_PATH。

错误处理: 始终检查返回值和 errno。ENOENT (文件不存在)、EACCES (权限不足) 是常见的错误。

替代方案: 对于不支持 statx 的旧系统,必须回退到使用 stat, lstat, fstat。

总结:

statx 是 Linux 文件状态查询功能的一次重要升级。它通过引入更精细的查询控制 (mask)、更丰富的属性(如 btime)、更灵活的路径解析 (dirfd, flags) 以及更好的可扩展性 (struct statx),为开发者提供了更强大、更高效的文件元数据获取能力。对于追求性能和需要访问现代文件系统特性的应用程序来说,statx 是首选的文件状态查询接口。

https://www.calcguide.tech/2025/08/10/statx系统调用及示例/

stat系统调用及示例

好的,我们继续学习 Linux 系统编程中的重要函数。这次我们介绍一组用于获取文件状态信息的函数:stat, fstat, 和 lstat。它们让你能够查询文件的各种属性,如大小、权限、所有者、时间戳等,而无需打开文件(stat, lstat)或只需已打开的文件描述符(fstat)。

1. 函数介绍

stat, fstat, 和 lstat 是三个密切相关的 Linux 系统调用,它们都用于获取文件的状态信息。这些信息被填充到一个 struct stat 类型的结构体中。

  • stat: 通过文件路径名 (pathname) 获取文件状态。如果路径名指向一个符号链接 (symbolic link),它会跟随链接并返回链接目标文件的状态。

  • fstat: 通过已打开的文件描述符 (file descriptor) 获取文件状态。因为文件已经打开,所以它直接操作文件描述符,不涉及路径解析。

  • lstat: 也通过文件路径名 (pathname) 获取文件状态。但它不会跟随符号链接,而是返回符号链接本身的信息。

理解这三个函数的关键在于区分符号链接和其目标文件,以及何时需要获取哪个的信息。

2. 函数原型

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/types.h>  // 通常需要
#include <sys/stat.h> // 必需,包含 struct stat 和函数声明
#include <unistd.h> // 通常需要

// 通过路径名获取文件状态
int stat(const char *pathname, struct stat *statbuf);

// 通过文件描述符获取文件状态
int fstat(int fd, struct stat *statbuf);

// 通过路径名获取文件状态,但不跟随符号链接
int lstat(const char *pathname, struct stat *statbuf);

3. 功能

这三个函数的功能都是将指定文件的详细状态信息填充到调用者提供的 struct stat 结构体指针 statbuf 所指向的内存中。

  • stat(pathname, buf): 查找 pathname,如果它是一个符号链接,则查找链接指向的目标文件,并将该目标文件的状态信息存入 buf。

  • fstat(fd, buf): 获取与文件描述符 fd 关联的文件的状态信息并存入 buf。

  • lstat(pathname, buf): 查找 pathname,即使它是一个符号链接,也将该符号链接本身的状态信息存入 buf。

4. 参数

  • const char *pathname (stat, lstat): 指向一个以空字符结尾的字符串,该字符串包含要查询状态的文件的路径名。

  • int fd (fstat): 一个有效的、已打开的文件描述符。

  • struct stat *statbuf: 指向一个 struct stat 类型的结构体的指针。函数调用成功后,该结构体将被填入文件的状态信息。

5. struct stat 结构体

struct stat 结构体包含了大量的文件属性信息。虽然具体实现可能因系统而异,但以下字段是标准且常用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct stat {
dev_t st_dev; // 文件所在设备的 ID
ino_t st_ino; // inode 节点号 (唯一标识文件)
mode_t st_mode; // 文件类型和权限
nlink_t st_nlink; // 硬链接数
uid_t st_uid; // 所有者用户 ID
gid_t st_gid; // 所有者组 ID
dev_t st_rdev; // (如果是设备文件) 设备 ID
off_t st_size; // 文件大小 (以字节为单位)
blksize_t st_blksize; // 文件系统 I/O 块大小
blkcnt_t st_blocks; // 分配的 512B 块数
time_t st_atime; // 上次访问时间 (自 Unix 纪元以来的秒数)
time_t st_mtime; // 上次修改时间
time_t st_ctime; // 上次状态改变时间 (如权限、所有者)
// ... 可能还有其他字段,取决于具体实现 ...
};

常用字段解释:

st_mode: 这个字段包含了文件类型和权限信息。可以通过一系列宏来测试:

文件类型:

  • S_ISREG(m): 是否为普通文件。

  • S_ISDIR(m): 是否为目录。

  • S_ISCHR(m): 是否为字符设备文件。

  • S_ISBLK(m): 是否为块设备文件。

  • S_ISFIFO(m): 是否为命名管道 (FIFO)。

  • S_ISLNK(m): 是否为符号链接 (对 lstat 结果有效)。

  • S_ISSOCK(m): 是否为套接字。

权限位 (按位与 & 操作检查):

  • S_IRUSR, S_IWUSR, S_IXUSR: 文件所有者的读、写、执行权限。

  • S_IRGRP, S_IWGRP, S_IXGRP: 文件所属组的读、写、执行权限。

  • S_IROTH, S_IWOTH, S_IXOTH: 其他用户的读、写、执行权限。

  • S_IRWXU, S_IRWXG, S_IRWXO: 分别代表所有者、组、其他用户的读/写/执行权限组合。

st_size: 文件的大小,以字节为单位。对于目录或设备文件,此值可能没有意义。

st_mtime: 文件内容上次被修改的时间。常用于判断文件是否已更新。

6. 返回值

这三个函数的返回值规则相同:

  • 成功时: 返回 0。同时,statbuf 指向的结构体被成功填充。

  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 ENOENT 文件不存在、EACCES 权限不足、EBADF fd 无效 (仅 fstat) 等)。

7. 示例代码

示例 1:使用 stat 获取普通文件信息

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h> // getpwuid
#include <grp.h> // getgrgid
#include <time.h> // ctime

void print_file_info(const char *pathname) {
struct stat file_stat;
struct passwd *pwd;
struct group *grp;
char date_str&#91;100];

// 调用 stat 获取文件状态
if (stat(pathname, &file_stat) == -1) {
perror("stat");
return; // 或 exit(EXIT_FAILURE);
}

// 打印基本信息
printf("File: %s\n", pathname);
printf(" Size: %ld bytes\n", (long)file_stat.st_size);
printf(" Inode: %ld\n", (long)file_stat.st_ino);
printf(" Device ID: %ld\n", (long)file_stat.st_dev);

// 打印文件类型
printf(" Type: ");
if (S_ISREG(file_stat.st_mode)) {
printf("Regular File\n");
} else if (S_ISDIR(file_stat.st_mode)) {
printf("Directory\n");
} else if (S_ISLNK(file_stat.st_mode)) {
printf("Symbolic Link\n");
} else if (S_ISCHR(file_stat.st_mode)) {
printf("Character Device\n");
} else if (S_ISBLK(file_stat.st_mode)) {
printf("Block Device\n");
} else if (S_ISFIFO(file_stat.st_mode)) {
printf("FIFO (Named Pipe)\n");
} else if (S_ISSOCK(file_stat.st_mode)) {
printf("Socket\n");
} else {
printf("Unknown Type\n");
}

// 打印权限 (简化版)
printf(" Permissions: ");
printf( (S_ISDIR(file_stat.st_mode)) ? "d" : "-");
printf( (file_stat.st_mode & S_IRUSR) ? "r" : "-");
printf( (file_stat.st_mode & S_IWUSR) ? "w" : "-");
printf( (file_stat.st_mode & S_IXUSR) ? "x" : "-");
printf( (file_stat.st_mode & S_IRGRP) ? "r" : "-");
printf( (file_stat.st_mode & S_IWGRP) ? "w" : "-");
printf( (file_stat.st_mode & S_IXGRP) ? "x" : "-");
printf( (file_stat.st_mode & S_IROTH) ? "r" : "-");
printf( (file_stat.st_mode & S_IWOTH) ? "w" : "-");
printf( (file_stat.st_mode & S_IXOTH) ? "x" : "-");
printf("\n");

// 打印所有者和组
pwd = getpwuid(file_stat.st_uid);
grp = getgrgid(file_stat.st_gid);
printf(" Owner: %s (UID: %d)\n", pwd ? pwd->pw_name : "Unknown", (int)file_stat.st_uid);
printf(" Group: %s (GID: %d)\n", grp ? grp->gr_name : "Unknown", (int)file_stat.st_gid);

// 打印时间戳 (使用 ctime 格式化)
// 注意: ctime 返回的字符串末尾自带 \n
printf(" Last Access: %s", ctime(&file_stat.st_atime)); // ctime 返回的字符串已包含 \n
printf(" Last Modify: %s", ctime(&file_stat.st_mtime));
printf(" Last Status Change: %s", ctime(&file_stat.st_ctime));

}

int main(int argc, char *argv&#91;]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv&#91;0]);
exit(EXIT_FAILURE);
}

print_file_info(argv&#91;1]);

return 0;
}

代码解释:

定义了一个 print_file_info 函数来封装 stat 调用和信息打印。

调用 stat(argv[1], &file_stat) 获取文件状态。

检查返回值,失败则打印错误。

使用 S_ISREG, S_ISDIR 等宏判断文件类型。

通过按位与操作检查 st_mode 来构建权限字符串。

使用 getpwuid 和 getgrgid 将 UID/GID 转换为用户名/组名。

使用 ctime 函数将 time_t 时间戳格式化为人类可读的字符串。

编译并运行:gcc -o stat_example stat_example.c && ./stat_example /etc/passwd

示例 2:比较 stat, lstat, fstat 对符号链接的行为

这个例子创建一个符号链接,并比较三个函数获取的信息。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
struct stat stat_info, lstat_info, fstat_info;
int fd;
const char *target_file = "target_file.txt";
const char *symlink_name = "my_symlink";

// 创建一个目标文件 (如果不存在)
FILE *f = fopen(target_file, "w");
if (f) {
fprintf(f, "This is the target file.\n");
fclose(f);
printf("Created target file: %s\n", target_file);
} else {
perror("Failed to create target file");
// 即使创建失败,也可能文件已存在,继续尝试后续步骤
}

// 创建符号链接 (如果不存在)
if (symlink(target_file, symlink_name) == -1) {
if (errno != EEXIST) { // EEXIST 表示链接已存在,可以接受
perror("Failed to create symbolic link");
// 清理可能已创建的目标文件
unlink(target_file);
exit(EXIT_FAILURE);
} else {
printf("Symbolic link '%s' already exists.\n", symlink_name);
}
} else {
printf("Created symbolic link '%s' -> '%s'\n", symlink_name, target_file);
}

// --- 使用 stat ---
printf("\n--- Using stat('%s', ...) ---\n", symlink_name);
if (stat(symlink_name, &stat_info) == -1) {
perror("stat failed");
} else {
printf(" Inode: %ld (This is the INODE of the TARGET file)\n", (long)stat_info.st_ino);
printf(" Size: %ld bytes\n", (long)stat_info.st_size);
if (S_ISLNK(stat_info.st_mode)) {
printf(" Type: Symbolic Link (This should NOT happen with stat)\n");
} else {
printf(" Type: Not a Symbolic Link (stat followed the link)\n");
}
}

// --- 使用 lstat ---
printf("\n--- Using lstat('%s', ...) ---\n", symlink_name);
if (lstat(symlink_name, &lstat_info) == -1) {
perror("lstat failed");
} else {
printf(" Inode: %ld (This is the INODE of the SYMBOLIC LINK itself)\n", (long)lstat_info.st_ino);
printf(" Size: %ld bytes (Size of the link's PATHNAME string)\n", (long)lstat_info.st_size);
if (S_ISLNK(lstat_info.st_mode)) {
printf(" Type: Symbolic Link (lstat did NOT follow the link)\n");
} else {
printf(" Type: Not a Symbolic Link (Unexpected)\n");
}
}

// --- 使用 fstat ---
printf("\n--- Using fstat(fd_of_symlink, ...) ---\n");
// 打开符号链接本身 (需要 O_PATH 或 O_RDONLY, 且不跟随链接的行为取决于系统和标志,但 fstat 总是作用于已打开的 fd)
// 更准确的做法是打开目标文件来演示 fstat
fd = open(target_file, O_RDONLY); // 打开目标文件
if (fd == -1) {
perror("Failed to open target file for fstat");
// 清理
unlink(symlink_name);
unlink(target_file);
exit(EXIT_FAILURE);
}

if (fstat(fd, &fstat_info) == -1) {
perror("fstat failed");
} else {
printf(" Inode: %ld (Inode of the file associated with fd %d)\n", (long)fstat_info.st_ino, fd);
printf(" Size: %ld bytes\n", (long)fstat_info.st_size);
if (S_ISLNK(fstat_info.st_mode)) {
printf(" Type: Symbolic Link (Associated file is a symlink)\n");
} else {
printf(" Type: Not a Symbolic Link (Associated file is regular/dir/etc)\n");
}
}
close(fd); // 关闭 fstat 使用的文件描述符

// 清理 (可选)
// printf("\nCleaning up...\n");
// unlink(symlink_name);
// unlink(target_file);

return 0;
}

代码解释:

首先创建一个目标文件 target_file.txt 和一个指向它的符号链接 my_symlink。

调用 stat(symlink_name, …): 它返回的是目标文件 (target_file.txt) 的信息,包括目标文件的 inode 和大小。

调用 lstat(symlink_name, …): 它返回的是符号链接本身 (my_symlink) 的信息,包括符号链接文件的 inode 和大小(大小通常是其指向路径名字符串的长度)。

调用 fstat(fd, …): 它需要一个文件描述符。这里打开的是目标文件 target_file.txt,所以返回的是目标文件的信息。如果打开的是符号链接(且系统支持),fstat 仍然会作用于链接最终指向的文件(除非用特殊标志打开链接本身,但这比较复杂且不常用)。

这个例子清晰地展示了 stat 跟随链接,lstat 不跟随链接,而 fstat 作用于已打开的文件描述符所关联的文件。

理解 stat, fstat, lstat 对于编写需要根据文件属性进行不同处理的程序至关重要。

swapon-off系统调用及示例

swapon/swapoff 函数详解

  1. 函数介绍

swapon 和 swapoff 是Linux系统调用,用于管理系统的交换空间(swap space)。交换空间是磁盘上的一块区域,当物理内存不足时,系统会将不常用的内存页移动到交换空间,从而释放物理内存供其他进程使用。

  1. 函数原型
1
2
3
4
5
#include <sys/swap.h>

int swapon(const char *path, int swapflags);
int swapoff(const char *path);

  1. 功能
  • swapon: 启用指定的交换空间(文件或设备)

  • swapoff: 禁用指定的交换空间

  1. 参数
  • *const char path: 交换文件或设备的路径

  • int swapflags: swapon的标志位(通常为0)

  1. 返回值
  • 成功: 返回0

  • 失败: 返回-1,并设置errno

  1. 相似函数,或关联函数
  • swapctl: 更通用的交换控制接口

  • mkswap: 创建交换空间

  • /proc/swaps: 交换空间信息文件

  • free: 显示内存使用情况

  1. 示例代码

示例1:基础swapon/swapoff使用

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include <sys/swap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

/**
* 显示当前交换空间信息
*/
void show_swap_info() {
FILE *fp;
char line&#91;256];

printf("=== 当前交换空间信息 ===\n");

fp = fopen("/proc/swaps", "r");
if (fp) {
printf("交换空间列表:\n");
while (fgets(line, sizeof(line), fp)) {
printf(" %s", line);
}
fclose(fp);
} else {
printf("无法读取交换空间信息: %s\n", strerror(errno));
}

printf("\n内存使用情况:\n");
system("free -h");
printf("\n");
}

/**
* 创建交换文件
*/
int create_swap_file(const char *filename, size_t size_mb) {
int fd;
char *buffer;
size_t chunk_size = 1024 * 1024; // 1MB chunks
size_t written = 0;

printf("创建交换文件: %s (%zu MB)\n", filename, size_mb);

// 创建文件
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0600);
if (fd == -1) {
perror("创建交换文件失败");
return -1;
}

// 设置文件大小
if (ftruncate(fd, size_mb * 1024 * 1024) == -1) {
perror("设置文件大小失败");
close(fd);
unlink(filename);
return -1;
}

// 可选:填充文件内容(对于某些文件系统可能需要)
buffer = malloc(chunk_size);
if (buffer) {
memset(buffer, 0, chunk_size);

// 写入一些数据以确保文件分配
ssize_t result = write(fd, buffer, chunk_size);
if (result == -1) {
perror("写入文件失败");
}

free(buffer);
}

close(fd);

// 创建交换空间
printf("初始化交换空间...\n");
char cmd&#91;256];
snprintf(cmd, sizeof(cmd), "mkswap %s", filename);

int result = system(cmd);
if (result != 0) {
printf("创建交换空间失败\n");
unlink(filename);
return -1;
}

printf("交换文件创建成功\n");
return 0;
}

/**
* 演示基础swapon/swapoff使用方法
*/
int demo_swapon_swapoff_basic() {
const char *swap_file = "/tmp/test_swapfile";
const size_t swap_size = 64; // 64MB
int result;

printf("=== 基础swapon/swapoff使用示例 ===\n");

// 检查权限
uid_t uid = getuid();
printf("用户权限检查:\n");
printf(" 当前用户ID: %d\n", uid);
if (uid == 0) {
printf(" ✓ 具有root权限,可以管理交换空间\n");
} else {
printf(" ✗ 没有root权限,交换空间管理将失败\n");
printf(" 注意:swapon/swapoff需要root权限\n");
return 0;
}

// 显示原始交换空间信息
printf("\n1. 原始交换空间信息:\n");
show_swap_info();

// 创建交换文件
printf("2. 创建测试交换文件:\n");
if (create_swap_file(swap_file, swap_size) != 0) {
printf("创建交换文件失败\n");
return -1;
}

// 启用交换文件
printf("3. 启用交换文件:\n");
printf(" 交换文件路径: %s\n", swap_file);

result = swapon(swap_file, 0);
if (result == 0) {
printf(" ✓ 交换文件启用成功\n");
show_swap_info();
} else {
printf(" ✗ 交换文件启用失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 原因:权限不足\n");
} else if (errno == EBUSY) {
printf(" 原因:交换空间已在使用中\n");
} else if (errno == EINVAL) {
printf(" 原因:无效的交换文件\n");
}
unlink(swap_file);
return -1;
}

// 禁用交换文件
printf("4. 禁用交换文件:\n");
result = swapoff(swap_file);
if (result == 0) {
printf(" ✓ 交换文件禁用成功\n");
show_swap_info();
} else {
printf(" ✗ 交换文件禁用失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 原因:权限不足\n");
} else if (errno == EINVAL) {
printf(" 原因:无效的交换文件路径\n");
} else if (errno == EBUSY) {
printf(" 原因:交换空间忙或正在使用\n");
}
}

// 清理测试文件
printf("5. 清理测试文件:\n");
if (unlink(swap_file) == 0) {
printf(" ✓ 测试文件清理成功\n");
} else {
printf(" ✗ 测试文件清理失败: %s\n", strerror(errno));
}

return 0;
}

int main() {
return demo_swapon_swapoff_basic();
}

示例2:交换空间管理工具

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#include <sys/swap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <getopt.h>

/**
* 交换空间条目结构
*/
typedef struct {
char path&#91;256];
long size_kb;
long used_kb;
int priority;
char type&#91;32];
} swap_entry_t;

/**
* 解析交换空间信息
*/
int parse_swap_info(swap_entry_t *entries, int max_entries) {
FILE *fp;
char line&#91;512];
int count = 0;

fp = fopen("/proc/swaps", "r");
if (!fp) {
return -1;
}

// 跳过标题行
fgets(line, sizeof(line), fp);

while (fgets(line, sizeof(line), fp) && count < max_entries) {
char *token;
char *saveptr;
int field = 0;

token = strtok_r(line, " \t\n", &saveptr);
while (token && field < 5) {
switch (field) {
case 0: // Filename
strncpy(entries&#91;count].path, token, sizeof(entries&#91;count].path) - 1);
entries&#91;count].path&#91;sizeof(entries&#91;count].path) - 1] = '\0';
break;
case 1: // Type
strncpy(entries&#91;count].type, token, sizeof(entries&#91;count].type) - 1);
entries&#91;count].type&#91;sizeof(entries&#91;count].type) - 1] = '\0';
break;
case 2: // Size
entries&#91;count].size_kb = atol(token);
break;
case 3: // Used
entries&#91;count].used_kb = atol(token);
break;
case 4: // Priority
entries&#91;count].priority = atoi(token);
break;
}
token = strtok_r(NULL, " \t\n", &saveptr);
field++;
}

if (field >= 4) {
count++;
}
}

fclose(fp);
return count;
}

/**
* 显示详细的交换空间信息
*/
void show_detailed_swap_info() {
swap_entry_t entries&#91;32];
int count = parse_swap_info(entries, 32);

printf("=== 详细交换空间信息 ===\n");

if (count <= 0) {
printf("没有活动的交换空间\n");
return;
}

printf("%-20s %-10s %-12s %-12s %-10s %-8s\n",
"路径", "类型", "大小(MB)", "已用(MB)", "可用(MB)", "优先级");
printf("%-20s %-10s %-12s %-12s %-10s %-8s\n",
"----", "----", "--------", "--------", "--------", "------");

long total_size = 0, total_used = 0;

for (int i = 0; i < count; i++) {
double size_mb = entries&#91;i].size_kb / 1024.0;
double used_mb = entries&#91;i].used_kb / 1024.0;
double free_mb = (entries&#91;i].size_kb - entries&#91;i].used_kb) / 1024.0;

printf("%-20s %-10s %-12.1f %-12.1f %-10.1f %-8d\n",
entries&#91;i].path,
entries&#91;i].type,
size_mb,
used_mb,
free_mb,
entries&#91;i].priority);

total_size += entries&#91;i].size_kb;
total_used += entries&#91;i].used_kb;
}

printf("\n总计:\n");
printf(" 总大小: %.1f MB\n", total_size / 1024.0);
printf(" 已使用: %.1f MB\n", total_used / 1024.0);
printf(" 可用: %.1f MB\n", (total_size - total_used) / 1024.0);
printf(" 使用率: %.1f%%\n",
total_size > 0 ? (total_used * 100.0 / total_size) : 0.0);
}

/**
* 交换空间管理工具
*/
int demo_swap_management_tool() {
const char *test_swap_file = "/tmp/swap_management_test";
uid_t uid = getuid();

printf("=== 交换空间管理工具演示 ===\n");

// 权限检查
printf("权限检查:\n");
printf(" 当前用户ID: %d\n", uid);
if (uid == 0) {
printf(" ✓ 具有root权限\n");
} else {
printf(" ✗ 没有root权限,部分功能将受限\n");
}

// 显示当前交换空间状态
printf("\n1. 当前交换空间状态:\n");
show_detailed_swap_info();

// 如果有root权限,演示创建和管理交换文件
if (uid == 0) {
printf("\n2. 创建测试交换文件:\n");

// 创建小的交换文件用于测试
int fd = open(test_swap_file, O_CREAT | O_WRONLY | O_TRUNC, 0600);
if (fd != -1) {
// 设置文件大小为32MB
if (ftruncate(fd, 32 * 1024 * 1024) == 0) {
close(fd);

// 创建交换空间
char cmd&#91;256];
snprintf(cmd, sizeof(cmd), "mkswap %s >/dev/null 2>&1", test_swap_file);
if (system(cmd) == 0) {
printf(" ✓ 创建交换文件成功: %s\n", test_swap_file);

// 启用交换文件
printf("3. 启用交换文件:\n");
int result = swapon(test_swap_file, 0);
if (result == 0) {
printf(" ✓ 交换文件启用成功\n");
show_detailed_swap_info();

// 禁用交换文件
printf("\n4. 禁用交换文件:\n");
result = swapoff(test_swap_file);
if (result == 0) {
printf(" ✓ 交换文件禁用成功\n");
} else {
printf(" ✗ 交换文件禁用失败: %s\n", strerror(errno));
}
} else {
printf(" ✗ 交换文件启用失败: %s\n", strerror(errno));
}
} else {
printf(" ✗ 初始化交换空间失败\n");
}
} else {
perror(" 设置文件大小失败");
close(fd);
}

// 清理测试文件
unlink(test_swap_file);
} else {
perror(" 创建交换文件失败");
}
}

// 显示交换空间管理建议
printf("\n=== 交换空间管理建议 ===\n");
printf("1. 交换空间大小:\n");
printf(" - 物理内存 < 2GB: 交换空间 = 物理内存 × 2\n");
printf(" - 物理内存 2-8GB: 交换空间 = 物理内存\n");
printf(" - 物理内存 > 8GB: 交换空间 = 4-8GB\n");

printf("\n2. 交换空间类型:\n");
printf(" - 交换分区: 性能更好,推荐用于生产环境\n");
printf(" - 交换文件: 灵活性更好,便于管理\n");

printf("\n3. 优先级设置:\n");
printf(" - 高速设备设置高优先级\n");
printf(" - SSD交换空间优先级高于HDD\n");

printf("\n4. 监控和维护:\n");
printf(" - 定期检查交换空间使用情况\n");
printf(" - 避免交换空间使用率过高\n");
printf(" - 及时清理不需要的交换文件\n");

return 0;
}

int main() {
return demo_swap_management_tool();
}

示例3:动态交换空间管理

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#include <sys/swap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>

/**
* 交换空间监控器
*/
typedef struct {
long total_swap_kb;
long used_swap_kb;
double usage_percentage;
time_t last_update;
} swap_monitor_t;

/**
* 获取交换空间使用情况
*/
int get_swap_usage(swap_monitor_t *monitor) {
FILE *fp;
char line&#91;256];

fp = fopen("/proc/meminfo", "r");
if (!fp) {
return -1;
}

long swap_total = 0, swap_free = 0;

while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "SwapTotal:", 10) == 0) {
sscanf(line + 10, "%ld", &swap_total);
} else if (strncmp(line, "SwapFree:", 9) == 0) {
sscanf(line + 9, "%ld", &swap_free);
}
}

fclose(fp);

monitor->total_swap_kb = swap_total;
monitor->used_swap_kb = swap_total - swap_free;
monitor->usage_percentage = swap_total > 0 ?
(monitor->used_swap_kb * 100.0 / swap_total) : 0.0;
monitor->last_update = time(NULL);

return 0;
}

/**
* 显示交换空间使用情况
*/
void show_swap_usage(const swap_monitor_t *monitor) {
printf("交换空间使用情况:\n");
printf(" 总大小: %.1f MB\n", monitor->total_swap_kb / 1024.0);
printf(" 已使用: %.1f MB\n", monitor->used_swap_kb / 1024.0);
printf(" 可用: %.1f MB\n", (monitor->total_swap_kb - monitor->used_swap_kb) / 1024.0);
printf(" 使用率: %.1f%%\n", monitor->usage_percentage);

char time_str&#91;64];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S",
localtime(&monitor->last_update));
printf(" 更新时间: %s\n", time_str);
}

/**
* 动态交换空间管理器
*/
typedef struct {
char swap_file&#91;256];
size_t initial_size_mb;
size_t max_size_mb;
int is_active;
swap_monitor_t monitor;
} dynamic_swap_manager_t;

/**
* 创建动态交换文件
*/
int create_dynamic_swap_file(dynamic_swap_manager_t *manager, size_t size_mb) {
int fd;

printf("创建动态交换文件: %s (%zu MB)\n", manager->swap_file, size_mb);

// 创建文件
fd = open(manager->swap_file, O_CREAT | O_WRONLY | O_TRUNC, 0600);
if (fd == -1) {
perror("创建交换文件失败");
return -1;
}

// 设置文件大小
if (ftruncate(fd, size_mb * 1024 * 1024) == -1) {
perror("设置文件大小失败");
close(fd);
unlink(manager->swap_file);
return -1;
}

close(fd);

// 创建交换空间
char cmd&#91;512];
snprintf(cmd, sizeof(cmd), "mkswap %s >/dev/null 2>&1", manager->swap_file);

if (system(cmd) != 0) {
printf("初始化交换空间失败\n");
unlink(manager->swap_file);
return -1;
}

printf("动态交换文件创建成功\n");
return 0;
}

/**
* 启用动态交换空间
*/
int activate_dynamic_swap(dynamic_swap_manager_t *manager) {
if (manager->is_active) {
printf("交换空间已在使用中\n");
return 0;
}

printf("启用动态交换空间...\n");

int result = swapon(manager->swap_file, 0);
if (result == 0) {
manager->is_active = 1;
printf("✓ 动态交换空间启用成功\n");
return 0;
} else {
printf("✗ 动态交换空间启用失败: %s\n", strerror(errno));
return -1;
}
}

/**
* 禁用动态交换空间
*/
int deactivate_dynamic_swap(dynamic_swap_manager_t *manager) {
if (!manager->is_active) {
printf("交换空间未启用\n");
return 0;
}

printf("禁用动态交换空间...\n");

int result = swapoff(manager->swap_file);
if (result == 0) {
manager->is_active = 0;
printf("✓ 动态交换空间禁用成功\n");
return 0;
} else {
printf("✗ 动态交换空间禁用失败: %s\n", strerror(errno));
return -1;
}
}

/**
* 演示动态交换空间管理
*/
int demo_dynamic_swap_management() {
dynamic_swap_manager_t manager = {0};
uid_t uid = getuid();

printf("=== 动态交换空间管理演示 ===\n");

// 权限检查
if (uid != 0) {
printf("需要root权限来管理交换空间\n");
return 0;
}

// 初始化管理器
strncpy(manager.swap_file, "/tmp/dynamic_swap", sizeof(manager.swap_file) - 1);
manager.initial_size_mb = 64;
manager.max_size_mb = 256;
manager.is_active = 0;

printf("动态交换管理器初始化:\n");
printf(" 交换文件: %s\n", manager.swap_file);
printf(" 初始大小: %zu MB\n", manager.initial_size_mb);
printf(" 最大大小: %zu MB\n", manager.max_size_mb);

// 获取初始交换空间状态
printf("\n1. 初始交换空间状态:\n");
if (get_swap_usage(&manager.monitor) == 0) {
show_swap_usage(&manager.monitor);
}

// 创建并启用动态交换空间
printf("\n2. 创建动态交换空间:\n");
if (create_dynamic_swap_file(&manager, manager.initial_size_mb) != 0) {
printf("创建动态交换空间失败\n");
return -1;
}

printf("\n3. 启用动态交换空间:\n");
if (activate_dynamic_swap(&manager) != 0) {
printf("启用动态交换空间失败\n");
unlink(manager.swap_file);
return -1;
}

// 显示启用后的状态
printf("\n4. 启用后的交换空间状态:\n");
sleep(1); // 等待系统更新状态
if (get_swap_usage(&manager.monitor) == 0) {
show_swap_usage(&manager.monitor);
}

// 模拟监控和调整
printf("\n5. 监控交换空间使用情况:\n");
for (int i = 0; i < 3; i++) {
sleep(2);
if (get_swap_usage(&manager.monitor) == 0) {
printf("监控轮次 %d:\n", i + 1);
show_swap_usage(&manager.monitor);

// 简单的使用率监控逻辑
if (manager.monitor.usage_percentage > 80.0) {
printf(" 警告:交换空间使用率过高 (%.1f%%)\n",
manager.monitor.usage_percentage);
} else if (manager.monitor.usage_percentage > 50.0) {
printf(" 提示:交换空间使用率中等 (%.1f%%)\n",
manager.monitor.usage_percentage);
} else {
printf(" 状态:交换空间使用率正常 (%.1f%%)\n",
manager.monitor.usage_percentage);
}
}
}

// 禁用并清理
printf("\n6. 禁用动态交换空间:\n");
if (deactivate_dynamic_swap(&manager) != 0) {
printf("禁用动态交换空间失败\n");
}

// 显示最终状态
printf("\n7. 最终交换空间状态:\n");
sleep(1); // 等待系统更新状态
if (get_swap_usage(&manager.monitor) == 0) {
show_swap_usage(&manager.monitor);
}

// 清理文件
printf("\n8. 清理交换文件:\n");
if (unlink(manager.swap_file) == 0) {
printf("✓ 交换文件清理成功\n");
} else {
printf("✗ 交换文件清理失败: %s\n", strerror(errno));
}

// 显示管理策略建议
printf("\n=== 动态交换空间管理策略 ===\n");
printf("1. 自动扩展策略:\n");
printf(" - 监控交换空间使用率\n");
printf(" - 使用率 > 80% 时扩展\n");
printf(" - 使用率 < 30% 时收缩\n");

printf("\n2. 性能优化:\n");
printf(" - 使用SSD作为交换空间\n");
printf(" - 设置适当的优先级\n");
printf(" - 避免频繁的启用/禁用操作\n");

printf("\n3. 安全考虑:\n");
printf(" - 交换文件权限设置为600\n");
printf(" - 定期清理临时交换文件\n");
printf(" - 监控异常的交换空间使用\n");

return 0;
}

int main() {
return demo_dynamic_swap_management();
}

示例4:交换空间优先级管理

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include <sys/swap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

/**
* 交换优先级管理器
*/
typedef struct {
char path&#91;256];
int priority;
int is_active;
} priority_swap_t;

/**
* 创建带优先级的交换文件
*/
int create_priority_swap_file(const char *filename, size_t size_mb, int priority) {
int fd;

printf("创建优先级交换文件: %s (大小: %zu MB, 优先级: %d)\n",
filename, size_mb, priority);

// 创建文件
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0600);
if (fd == -1) {
perror("创建交换文件失败");
return -1;
}

// 设置文件大小
if (ftruncate(fd, size_mb * 1024 * 1024) == -1) {
perror("设置文件大小失败");
close(fd);
unlink(filename);
return -1;
}

close(fd);

// 创建交换空间
char cmd&#91;512];
snprintf(cmd, sizeof(cmd), "mkswap %s >/dev/null 2>&1", filename);

if (system(cmd) != 0) {
printf("初始化交换空间失败\n");
unlink(filename);
return -1;
}

printf("交换文件创建成功\n");
return 0;
}

/**
* 启用带优先级的交换空间
*/
int activate_priority_swap(const char *filename, int priority) {
printf("启用优先级交换空间: %s (优先级: %d)\n", filename, priority);

// 使用swapflags设置优先级
int swapflags = (priority << 16); // 优先级存储在高16位

int result = swapon(filename, swapflags);
if (result == 0) {
printf("✓ 交换空间启用成功\n");
return 0;
} else {
printf("✗ 交换空间启用失败: %s\n", strerror(errno));
return -1;
}
}

/**
* 演示交换空间优先级管理
*/
int demo_swap_priority_management() {
priority_swap_t swaps&#91;3];
uid_t uid = getuid();

printf("=== 交换空间优先级管理演示 ===\n");

// 权限检查
if (uid != 0) {
printf("需要root权限来管理交换空间\n");
return 0;
}

// 初始化交换空间配置
strncpy(swaps&#91;0].path, "/tmp/priority_swap_high", sizeof(swaps&#91;0].path) - 1);
swaps&#91;0].priority = 10; // 高优先级
swaps&#91;0].is_active = 0;

strncpy(swaps&#91;1].path, "/tmp/priority_swap_medium", sizeof(swaps&#91;1].path) - 1);
swaps&#91;1].priority = 5; // 中优先级
swaps&#91;1].is_active = 0;

strncpy(swaps&#91;2].path, "/tmp/priority_swap_low", sizeof(swaps&#91;2].path) - 1);
swaps&#91;2].priority = 1; // 低优先级
swaps&#91;2].is_active = 0;

printf("交换空间优先级配置:\n");
for (int i = 0; i < 3; i++) {
printf(" %s: 优先级 %d\n", swaps&#91;i].path, swaps&#91;i].priority);
}

// 显示初始交换空间状态
printf("\n1. 初始交换空间状态:\n");
system("cat /proc/swaps");

// 创建交换文件
printf("\n2. 创建交换文件:\n");
for (int i = 0; i < 3; i++) {
size_t size_mb = 32 + i * 16; // 32MB, 48MB, 64MB
if (create_priority_swap_file(swaps&#91;i].path, size_mb, swaps&#91;i].priority) != 0) {
printf("创建交换文件 %s 失败\n", swaps&#91;i].path);
// 清理已创建的文件
for (int j = 0; j < i; j++) {
unlink(swaps&#91;j].path);
}
return -1;
}
}

// 按优先级顺序启用交换空间
printf("\n3. 按优先级启用交换空间:\n");
// 注意:实际的优先级设置可能需要通过命令行参数传递
for (int i = 0; i < 3; i++) {
printf("启用交换空间 %d:\n", i + 1);
// 这里简化处理,实际应用中可能需要更复杂的优先级设置
int result = swapon(swaps&#91;i].path, 0);
if (result == 0) {
swaps&#91;i].is_active = 1;
printf(" ✓ %s 启用成功\n", swaps&#91;i].path);
} else {
printf(" ✗ %s 启用失败: %s\n", swaps&#91;i].path, strerror(errno));
}
}

// 显示启用后的状态
printf("\n4. 启用后的交换空间状态:\n");
system("cat /proc/swaps");

printf("\n交换空间优先级说明:\n");
printf(" - 数值越高优先级越高\n");
printf(" - 高优先级交换空间先被使用\n");
printf(" - 相同优先级的交换空间轮询使用\n");

// 禁用交换空间
printf("\n5. 禁用交换空间:\n");
for (int i = 0; i < 3; i++) {
if (swaps&#91;i].is_active) {
int result = swapoff(swaps&#91;i].path);
if (result == 0) {
swaps&#91;i].is_active = 0;
printf(" ✓ %s 禁用成功\n", swaps&#91;i].path);
} else {
printf(" ✗ %s 禁用失败: %s\n", swaps&#91;i].path, strerror(errno));
}
}
}

// 清理文件
printf("\n6. 清理交换文件:\n");
for (int i = 0; i < 3; i++) {
if (unlink(swaps&#91;i].path) == 0) {
printf(" ✓ %s 清理成功\n", swaps&#91;i].path);
} else {
printf(" ✗ %s 清理失败: %s\n", swaps&#91;i].path, strerror(errno));
}
}

// 显示优先级管理最佳实践
printf("\n=== 交换空间优先级管理最佳实践 ===\n");
printf("1. 优先级设置原则:\n");
printf(" - SSD交换空间设置高优先级\n");
printf(" - 高速磁盘设置中等优先级\n");
printf(" - 低速磁盘设置低优先级\n");

printf("\n2. 负载均衡:\n");
printf(" - 相同类型设备设置相同优先级\n");
printf(" - 实现轮询使用,延长设备寿命\n");

printf("\n3. 性能优化:\n");
printf(" - 避免频繁调整优先级\n");
printf(" - 根据实际性能测试设置\n");
printf(" - 考虑I/O模式和访问模式\n");

return 0;
}

int main() {
return demo_swap_priority_management();
}

示例5:交换空间性能测试

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#include <sys/swap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <sys/resource.h>

/**
* 内存压力测试器
*/
typedef struct {
size_t allocated_memory_mb;
size_t swap_used_mb;
double test_duration_seconds;
int swap_enabled;
} memory_test_result_t;

/**
* 分配内存并测试交换性能
*/
int memory_pressure_test(size_t memory_mb, memory_test_result_t *result) {
char **buffers;
size_t num_buffers = memory_mb / 10; // 每个缓冲区10MB
size_t buffer_size = 10 * 1024 * 1024; // 10MB
struct timespec start, end;

printf("开始内存压力测试: %zu MB\n", memory_mb);

// 记录开始时间
clock_gettime(CLOCK_MONOTONIC, &start);

// 分配内存缓冲区数组
buffers = malloc(num_buffers * sizeof(char*));
if (!buffers) {
printf("分配缓冲区数组失败\n");
return -1;
}

// 逐步分配内存
size_t allocated_buffers = 0;
for (size_t i = 0; i < num_buffers; i++) {
buffers&#91;i] = malloc(buffer_size);
if (buffers&#91;i]) {
// 填充数据以确保内存实际使用
memset(buffers&#91;i], i & 0xFF, buffer_size);
allocated_buffers++;

if (allocated_buffers % 10 == 0) {
printf("已分配 %zu MB 内存\n", allocated_buffers * 10);
}
} else {
printf("内存分配失败在第 %zu 次\n", i + 1);
break;
}
}

// 记录结束时间
clock_gettime(CLOCK_MONOTONIC, &end);

result->allocated_memory_mb = allocated_buffers * 10;
result->test_duration_seconds = (end.tv_sec - start.tv_sec) +
(end.tv_nsec - start.tv_nsec) / 1e9;

printf("内存分配完成: %zu MB, 耗时 %.2f 秒\n",
result->allocated_memory_mb, result->test_duration_seconds);

// 检查交换空间使用情况
FILE *fp = fopen("/proc/meminfo", "r");
if (fp) {
char line&#91;256];
long swap_total = 0, swap_free = 0;

while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "SwapTotal:", 10) == 0) {
sscanf(line + 10, "%ld", &swap_total);
} else if (strncmp(line, "SwapFree:", 9) == 0) {
sscanf(line + 9, "%ld", &swap_free);
}
}

fclose(fp);
result->swap_used_mb = (swap_total - swap_free) / 1024; // 转换为MB
result->swap_enabled = (swap_total > 0);
}

// 释放内存
for (size_t i = 0; i < allocated_buffers; i++) {
if (buffers&#91;i]) {
free(buffers&#91;i]);
}
}
free(buffers);

return 0;
}

/**
* 演示交换空间性能测试
*/
int demo_swap_performance_test() {
const char *test_swap_file = "/tmp/performance_test_swap";
uid_t uid = getuid();

printf("=== 交换空间性能测试演示 ===\n");

// 权限检查
if (uid != 0) {
printf("需要root权限来管理交换空间\n");
printf("将以普通用户权限进行内存测试\n");
}

// 显示系统内存信息
printf("1. 系统内存信息:\n");
system("free -h");

printf("\n2. 当前交换空间信息:\n");
system("cat /proc/swaps");

// 如果有root权限,创建测试交换空间
if (uid == 0) {
printf("\n3. 创建测试交换空间:\n");

// 创建128MB交换文件
int fd = open(test_swap_file, O_CREAT | O_WRONLY | O_TRUNC, 0600);
if (fd != -1) {
if (ftruncate(fd, 128 * 1024 * 1024) == 0) {
close(fd);

// 创建交换空间
char cmd&#91;256];
snprintf(cmd, sizeof(cmd), "mkswap %s >/dev/null 2>&1", test_swap_file);
if (system(cmd) == 0) {
printf(" ✓ 创建测试交换文件成功\n");

// 启用交换文件
if (swapon(test_swap_file, 0) == 0) {
printf(" ✓ 启用测试交换文件成功\n");
system("cat /proc/swaps");
} else {
printf(" ✗ 启用测试交换文件失败\n");
}
} else {
printf(" ✗ 初始化交换空间失败\n");
close(fd);
unlink(test_swap_file);
}
} else {
perror(" 设置文件大小失败");
close(fd);
unlink(test_swap_file);
}
} else {
perror(" 创建交换文件失败");
}
}

// 进行内存压力测试
printf("\n4. 内存压力测试:\n");

// 测试不同内存大小
size_t test_sizes&#91;] = {100, 200, 300, 400};
int num_tests = sizeof(test_sizes) / sizeof(test_sizes&#91;0]);

for (int i = 0; i < num_tests; i++) {
printf("\n测试 %d: 分配 %zu MB 内存\n", i + 1, test_sizes&#91;i]);

memory_test_result_t result = {0};
if (memory_pressure_test(test_sizes&#91;i], &result) == 0) {
printf(" 测试结果:\n");
printf(" 分配内存: %zu MB\n", result.allocated_memory_mb);
printf(" 测试耗时: %.2f 秒\n", result.test_duration_seconds);
printf(" 交换使用: %zu MB\n", result.swap_used_mb);
printf(" 交换启用: %s\n", result.swap_enabled ? "是" : "否");

if (result.swap_enabled && result.swap_used_mb > 0) {
printf(" ✓ 交换空间正常工作\n");
} else if (result.swap_enabled) {
printf(" 提示:交换空间已启用但未使用\n");
} else {
printf(" 提示:交换空间未启用\n");
}
} else {
printf(" 测试失败\n");
}

// 短暂休息
sleep(2);
}

// 如果创建了测试交换空间,清理它
if (uid == 0) {
printf("\n5. 清理测试交换空间:\n");

// 禁用交换文件
if (swapoff(test_swap_file) == 0) {
printf(" ✓ 禁用测试交换文件成功\n");
} else {
printf(" ✗ 禁用测试交换文件失败: %s\n", strerror(errno));
}

// 删除交换文件
if (unlink(test_swap_file) == 0) {
printf(" ✓ 删除测试交换文件成功\n");
} else {
printf(" ✗ 删除测试交换文件失败: %s\n", strerror(errno));
}
}

// 显示性能测试总结
printf("\n=== 性能测试总结 ===\n");
printf("1. 交换空间性能指标:\n");
printf(" - 交换I/O速度\n");
printf(" - 内存分配延迟\n");
printf(" - 系统响应时间\n");

printf("\n2. 性能优化建议:\n");
printf(" - 使用SSD作为交换空间\n");
printf(" - 合理设置交换空间大小\n");
printf(" - 调整交换倾向参数 (swappiness)\n");

printf("\n3. 监控要点:\n");
printf(" - 交换空间使用率\n");
printf(" - 系统负载情况\n");
printf(" - 内存使用趋势\n");

return 0;
}

int main() {
return demo_swap_performance_test();
}

swapon/swapoff 使用注意事项

系统要求:

内核版本: 支持交换空间管理的Linux内核

权限要求: 需要root权限

架构支持: 支持所有主流架构

文件要求:

权限: 交换文件权限必须为600

格式: 必须使用mkswap初始化

大小: 建议为页面大小的倍数

错误处理:

EPERM: 权限不足

EINVAL: 无效参数

EBUSY: 交换空间正在使用中

ENOMEM: 内存不足

安全考虑:

文件权限: 确保交换文件权限正确

数据安全: 交换空间可能包含敏感数据

系统稳定性: 不当的交换管理可能影响系统稳定性

最佳实践:

权限检查: 执行前检查是否具有足够权限

错误处理: 妥善处理各种错误情况

资源清理: 及时清理临时交换文件

监控管理: 监控交换空间使用情况

性能调优: 根据系统特性调整交换策略

交换空间相关常量和参数

交换标志:

1
2
3
4
// swapon标志(通常为0)
#define SWAP_FLAG_PREFER 0x8000 // 优先使用
#define SWAP_FLAG_DISCARD 0x10000 // 启用discard

系统参数:

1
2
3
4
// /proc/sys/vm/swappiness (0-100)
// 控制内核使用交换空间的倾向
echo 10 > /proc/sys/vm/swappiness // 降低交换倾向

相关文件和命令

系统文件:

  • /proc/swaps: 当前交换空间信息

  • /proc/meminfo: 内存使用信息

  • /proc/sys/vm/swappiness: 交换倾向参数

相关命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 显示交换空间信息
cat /proc/swaps
swapon --show

# 显示内存使用情况
free -h
cat /proc/meminfo

# 管理交换空间
swapon /swapfile
swapoff /swapfile
mkswap /swapfile

常见使用场景

1. 系统管理:

1
2
3
// 动态添加交换空间以应对内存不足
swapon("/tmp/emergency_swap", 0);

2. 性能调优:

1
2
3
// 为不同存储设备设置不同的交换优先级
swapon("/dev/ssd_swap", priority << 16);

3. 资源监控:

1
2
3
4
5
6
// 监控交换空间使用情况并动态调整
if (swap_usage > 90%) {
// 添加更多交换空间
swapon("/tmp/additional_swap", 0);
}

总结

swapon 和 swapoff 是Linux系统中重要的内存管理函数,提供了:

动态管理: 可以动态启用和禁用交换空间

灵活配置: 支持不同的交换文件和设备

优先级控制: 可以设置交换空间的使用优先级

系统集成: 与Linux内核深度集成

通过合理使用这些函数,可以构建灵活的内存管理系统,提高系统的稳定性和性能。在实际应用中,需要注意权限要求、错误处理和系统稳定性等关键问题。

symlink-symlinkat系统调用及示例

好的,我们来深入学习 symlink 和 symlinkat 系统调用

1. 函数介绍

在 Linux 文件系统中,一个文件可以有多个名字,这通过链接 (Link) 来实现。链接主要分为两种:

硬链接 (Hard Link): 由 link() 和 linkat() 创建。多个硬链接直接指向同一个 inode(文件的实际数据)。删除一个硬链接不会影响文件数据,只有当所有硬链接都被删除时,文件数据才会被真正删除。硬链接不能跨文件系统,也不能指向目录(除了 . 和 ..)。

符号链接 (Symbolic Link, Symlink): 由 symlink() 和 symlinkat() 创建。它更像是一个“快捷方式”或“指针”,它本身是一个特殊的文件,包含了指向另一个文件或目录的路径名(可以是绝对路径或相对路径)。删除符号链接本身不会影响目标文件,但如果删除了目标文件,符号链接就会变成“悬空链接”(dangling link),无法访问。

symlink 和 symlinkat 的作用就是创建符号链接。

  • symlink: 在指定路径创建一个符号链接,指向另一个指定的路径。

  • symlinkat: 是 symlink 的扩展版本,允许更灵活地指定符号链接的创建位置(使用文件描述符作为参考目录)。

简单来说,symlink 和 symlinkat 就是让你用程序来创建文件或目录的“快捷方式”。

典型应用场景:

  • 创建快捷方式:为一个深层目录或复杂路径的文件创建一个容易访问的别名。

  • 库版本管理:例如,libfoo.so 可能是一个指向 libfoo.so.1.2.3 的符号链接,方便程序链接。

  • 配置文件管理:将配置文件放在一个标准位置,但通过符号链接指向实际存储位置。

  • 避免文件复制:通过符号链接共享文件,而不占用额外磁盘空间。

2. 函数原型

1
2
3
4
5
6
7
8
#include <unistd.h> // 包含系统调用声明

// 创建符号链接
int symlink(const char *target, const char *linkpath);

// 创建符号链接 (扩展版,支持相对路径)
int symlinkat(const char *target, int newdirfd, const char *linkpath);

3. 功能

  • symlink: 创建一个名为 linkpath 的符号链接,其内容是字符串 target。这个 target 可以是任意路径字符串,不需要在调用时就存在。

  • symlinkat: 类似于 symlink,但 linkpath 的解释方式不同。如果 linkpath 是相对路径,它是相对于 newdirfd 文件描述符所指向的目录来解析的。如果 linkpath 是绝对路径,则 newdirfd 被忽略。特殊的 newdirfd 值 AT_FDCWD 可以用来表示当前工作目录,此时 symlinkat() 的行为与 symlink() 相同。

4. 参数

target:

  • const char * 类型。

  • 指向一个以 null 结尾的字符串,该字符串定义了符号链接的目标。这个目标路径在创建符号链接时不需要存在,可以是绝对路径(如 /usr/bin/python3)或相对路径(如 ../bin/my_script)。

linkpath:

  • const char * 类型。

  • 指向一个以 null 结尾的字符串,该字符串指定了要创建的符号链接文件的名称和路径。

  • 对于 symlink: 这个路径是相对于当前工作目录解析的。

  • 对于 symlinkat: 如果是相对路径,则相对于 newdirfd 解析;如果是绝对路径,则忽略 newdirfd。

newdirfd (仅 symlinkat):

  • int 类型。

  • 一个已打开目录的文件描述符。linkpath 如果是相对路径,将相对于这个目录进行解析。

  • 特殊值 AT_FDCWD 表示当前工作目录。

5. 返回值

  • 成功: 返回 0。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

两者共享许多相同的错误码:

  • EACCES: 写入包含 linkpath 的目录权限不足。

  • EEXIST: linkpath 已经存在。

  • EFAULT: target 或 linkpath 指向了调用进程无法访问的内存地址。

  • EIO: I/O 错误。

  • ELOOP: 解析 linkpath 时遇到符号链接循环。

  • ENAMETOOLONG: target 或 linkpath 太长。

  • ENOENT: linkpath 的某个前缀目录不存在。

  • ENOMEM: 内核内存不足。

  • ENOSPC: 设备空间不足,无法创建新的目录项。

  • ENOTDIR: linkpath 的某个前缀不是目录。

  • EPERM: 文件系统不支持符号链接,或者由于其他原因被禁止(例如挂载了 nosymfollow 选项)。

  • EROFS: linkpath 所在的文件系统是只读的。

  • EBADF: (仅 symlinkat) newdirfd 不是有效的文件描述符,且不等于 AT_FDCWD。

7. 相似函数或关联函数

  • link / linkat: 创建硬链接。

  • readlink / readlinkat: 读取符号链接本身的内容(即它指向的目标路径)。

  • unlink: 删除文件或目录的链接。对于符号链接,它删除的是符号链接本身,而不是目标文件。

  • lstat: 获取文件状态,但如果文件是符号链接,它返回的是符号链接本身的信息,而不是目标文件的信息。

  • stat: 获取文件状态,如果文件是符号链接,它会跟随链接到目标文件并返回目标文件的信息。

8. 示例代码

下面的示例演示了如何使用 symlink 和 symlinkat 来创建符号链接,并展示它们与硬链接和普通文件的区别。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h> // 包含 stat, lstat
#include <fcntl.h> // 包含 open, O_* flags
#include <string.h>
#include <errno.h>
#include <libgen.h> // 包含 dirname, basename

// 辅助函数:打印文件信息
void print_file_info(const char *path, const char* description) {
struct stat sb;
printf("--- %s: %s ---\n", description, path);

// 使用 lstat 获取符号链接本身的信息
if (lstat(path, &sb) == -1) {
perror("lstat");
return;
}

printf(" Inode: %ld\n", (long) sb.st_ino);
printf(" Links: %ld\n", (long) sb.st_nlink);
printf(" Size: %ld bytes\n", (long) sb.st_size);

if (S_ISLNK(sb.st_mode)) {
printf(" Type: Symbolic Link\n");
// 读取符号链接指向的目标
char target_path&#91;1024];
ssize_t len = readlink(path, target_path, sizeof(target_path) - 1);
if (len != -1) {
target_path&#91;len] = '\0';
printf(" -> Points to: %s\n", target_path);
} else {
perror("readlink");
}
} else if (S_ISREG(sb.st_mode)) {
printf(" Type: Regular File\n");
} else if (S_ISDIR(sb.st_mode)) {
printf(" Type: Directory\n");
} else {
printf(" Type: Other (Mode: %o)\n", sb.st_mode & S_IFMT);
}
printf("\n");
}

int main() {
const char *original_file = "original_file.txt";
const char *hard_link = "hard_link_to_original.txt";
const char *sym_link_absolute = "symlink_absolute_to_original.txt";
const char *sym_link_relative = "symlink_relative_to_original.txt";
const char *sym_link_dangling = "symlink_to_nonexistent.txt";
const char *test_dir = "test_symlink_dir";
const char *sym_link_in_dir = "symlink_in_dir.txt";
int dirfd;

printf("--- Demonstrating symlink and symlinkat ---\n");

// 1. 创建一个原始文件
FILE *f = fopen(original_file, "w");
if (!f) {
perror("fopen original_file.txt");
exit(EXIT_FAILURE);
}
fprintf(f, "This is the content of the original file.\n");
fclose(f);
printf("Created original file: %s\n", original_file);

// 2. 创建硬链接
if (link(original_file, hard_link) == -1) {
perror("link");
// 清理并退出
unlink(original_file);
exit(EXIT_FAILURE);
}
printf("Created hard link: %s -> %s\n", hard_link, original_file);

// 3. 创建符号链接 (绝对路径)
// 注意:target 是字符串,不一定需要存在
if (symlink("/full/path/to/somewhere", sym_link_absolute) == -1) {
perror("symlink absolute");
// 清理并退出
unlink(original_file);
unlink(hard_link);
exit(EXIT_FAILURE);
}
printf("Created symbolic link (absolute target): %s -> /full/path/to/somewhere\n", sym_link_absolute);

// 4. 创建符号链接 (相对路径)
if (symlink(original_file, sym_link_relative) == -1) {
perror("symlink relative");
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
exit(EXIT_FAILURE);
}
printf("Created symbolic link (relative target): %s -> %s\n", sym_link_relative, original_file);

// 5. 创建一个指向不存在文件的符号链接 (悬空链接)
if (symlink("this_file_does_not_exist.txt", sym_link_dangling) == -1) {
perror("symlink dangling");
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
exit(EXIT_FAILURE);
}
printf("Created dangling symbolic link: %s -> this_file_does_not_exist.txt\n", sym_link_dangling);

// 6. 演示 symlinkat
// 首先创建一个目录
if (mkdir(test_dir, 0755) == -1 && errno != EEXIST) {
perror("mkdir test_symlink_dir");
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
unlink(sym_link_dangling);
exit(EXIT_FAILURE);
}
printf("\nCreated directory: %s\n", test_dir);

// 打开目录以获取文件描述符
dirfd = open(test_dir, O_RDONLY | O_DIRECTORY);
if (dirfd == -1) {
perror("open test_symlink_dir");
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
unlink(sym_link_dangling);
rmdir(test_dir);
exit(EXIT_FAILURE);
}

// 使用 symlinkat 在目录中创建符号链接
// linkpath 是相对路径 "symlink_in_dir.txt",它相对于 dirfd (test_symlink_dir) 解析
// target 是 "../original_file.txt",这是符号链接存储的内容
if (symlinkat("../original_file.txt", dirfd, sym_link_in_dir) == -1) {
perror("symlinkat");
close(dirfd);
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
unlink(sym_link_dangling);
rmdir(test_dir);
exit(EXIT_FAILURE);
}
printf("Created symbolic link using symlinkat: %s/%s -> ../original_file.txt\n", test_dir, sym_link_in_dir);
close(dirfd);

// 7. 检查所有创建的文件/链接的信息
printf("\n--- File Information ---\n");
print_file_info(original_file, "Original File");
print_file_info(hard_link, "Hard Link");
print_file_info(sym_link_absolute, "Symbolic Link (Absolute)");
print_file_info(sym_link_relative, "Symbolic Link (Relative)");
print_file_info(sym_link_dangling, "Dangling Symbolic Link");
char full_sym_link_in_dir&#91;512];
snprintf(full_sym_link_in_dir, sizeof(full_sym_link_in_dir), "%s/%s", test_dir, sym_link_in_dir);
print_file_info(full_sym_link_in_dir, "Symbolic Link (Created with symlinkat)");

// 8. 演示访问符号链接
printf("--- Accessing Files ---\n");
// 访问原始文件
if (access(original_file, F_OK) == 0) {
printf("Can access original file '%s'.\n", original_file);
}
// 访问硬链接 (效果同原始文件)
if (access(hard_link, F_OK) == 0) {
printf("Can access hard link '%s'.\n", hard_link);
}
// 访问相对符号链接 (应该成功,指向存在的文件)
if (access(sym_link_relative, F_OK) == 0) {
printf("Can access symbolic link '%s' (follows to existing target).\n", sym_link_relative);
} else {
printf("Cannot access symbolic link '%s': %s\n", sym_link_relative, strerror(errno));
}
// 访问悬空符号链接 (会失败)
if (access(sym_link_dangling, F_OK) == 0) {
printf("Can access dangling symbolic link '%s' (unexpected).\n", sym_link_dangling);
} else {
printf("Cannot access dangling symbolic link '%s': %s (expected)\n", sym_link_dangling, strerror(errno));
}

// 9. 清理创建的文件和目录
printf("\n--- Cleaning Up ---\n");
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
unlink(sym_link_dangling);
unlink(full_sym_link_in_dir);
rmdir(test_dir);
printf("All files and directory cleaned up.\n");

printf("\n--- Summary ---\n");
printf("1. Hard links (link) point to the same inode as the original file.\n");
printf("2. Symbolic links (symlink) are separate files containing a path string.\n");
printf("3. Symbolic links can point to non-existent targets (dangling).\n");
printf("4. symlinkat allows creating symlinks relative to a directory file descriptor.\n");

return 0;
}

9. 编译和运行

1
2
3
4
5
6
# 假设代码保存在 symlink_example.c 中
gcc -o symlink_example symlink_example.c

# 运行程序
./symlink_example

10. 预期输出

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
--- Demonstrating symlink and symlinkat ---
Created original file: original_file.txt
Created hard link: hard_link_to_original.txt -> original_file.txt
Created symbolic link (absolute target): symlink_absolute_to_original.txt -> /full/path/to/somewhere
Created symbolic link (relative target): symlink_relative_to_original.txt -> original_file.txt
Created dangling symbolic link: symlink_to_nonexistent.txt -> this_file_does_not_exist.txt

Created directory: test_symlink_dir
Created symbolic link using symlinkat: test_symlink_dir/symlink_in_dir.txt -> ../original_file.txt

--- File Information ---
--- Original File: original_file.txt ---
Inode: 123456
Links: 2
Size: 42 bytes
Type: Regular File

--- Hard Link: hard_link_to_original.txt ---
Inode: 123456
Links: 2
Size: 42 bytes
Type: Regular File

--- Symbolic Link (Absolute): symlink_absolute_to_original.txt ---
Inode: 123457
Links: 1
Size: 23 bytes
Type: Symbolic Link
-> Points to: /full/path/to/somewhere

--- Symbolic Link (Relative): symlink_relative_to_original.txt ---
Inode: 123458
Links: 1
Size: 18 bytes
Type: Symbolic Link
-> Points to: original_file.txt

--- Dangling Symbolic Link: symlink_to_nonexistent.txt ---
Inode: 123459
Links: 1
Size: 27 bytes
Type: Symbolic Link
-> Points to: this_file_does_not_exist.txt

--- Symbolic Link (Created with symlinkat): test_symlink_dir/symlink_in_dir.txt ---
Inode: 123460
Links: 1
Size: 21 bytes
Type: Symbolic Link
-> Points to: ../original_file.txt

--- Accessing Files ---
Can access original file 'original_file.txt'.
Can access hard link 'hard_link_to_original.txt'.
Can access symbolic link 'symlink_relative_to_original.txt' (follows to existing target).
Cannot access dangling symbolic link 'symlink_to_nonexistent.txt': No such file or directory (expected)

--- Cleaning Up ---
All files and directory cleaned up.

--- Summary ---
1. Hard links (link) point to the same inode as the original file.
2. Symbolic links (symlink) are separate files containing a path string.
3. Symbolic links can point to non-existent targets (dangling).
4. symlinkat allows creating symlinks relative to a directory file descriptor.

11. 总结

symlink 和 symlinkat 是创建符号链接的标准系统调用。

  • symlink(target, linkpath): 最常用的创建符号链接的方式。linkpath 相对于当前工作目录解析。

  • symlinkat(target, newdirfd, linkpath): 提供了更灵活的路径解析方式,特别是当需要在特定目录下创建链接时非常有用。当 newdirfd 为 AT_FDCWD 时,行为与 symlink 相同。

理解符号链接与硬链接的区别至关重要:

  • 硬链接增加文件的链接计数,共享 inode。

  • 符号链接是独立的文件,内容是目标路径字符串。

符号链接是 Linux 文件系统中强大而灵活的工具,广泛用于系统管理和程序设计中。