sync/syncfs系统调用及示例

好的,我们来深入学习 sync 和 syncfs 系统调用

1. 函数介绍

在 Linux 系统中,为了提高文件操作的性能,内核广泛使用了缓存 (Caching) 机制。当你调用 write() 将数据写入文件时,数据通常首先被写入到内存中的高速缓存(页缓存 Page Cache)中,而不是直接写入物理磁盘。操作系统会在稍后(例如缓存满、定时刷新或系统关闭时)才将这些缓存中的“脏数据”(已修改但未写回磁盘的数据)真正写入到磁盘上。

这种机制虽然高效,但也带来了数据安全的风险:如果在数据从缓存写入磁盘之前,系统突然断电或崩溃,那么这部分未写入的数据就会丢失。

sync 和 syncfs 系统调用就是用来强制将缓存中的数据刷新到磁盘,以确保数据的持久化。

  • sync: 这是一个全局性的操作。它会要求内核将所有已挂载文件系统上的所有脏数据(包括文件数据和元数据,如目录结构、文件权限、修改时间等)都刷新到对应的物理存储设备上。这是一个重量级操作,可能需要一些时间。

  • syncfs: 这是一个针对性的操作。它只刷新与指定文件描述符相关联的那个文件系统上的脏数据。相比 sync,它的影响范围更小,通常也更快。

简单来说:

  • sync 就像是对整个电脑说:“请把内存里所有还没存到硬盘上的东西,现在都给我存一遍!”。

  • syncfs 就像是对某个 U 盘(或硬盘分区)说:“请把你内存里还没存到你这个盘上的东西,现在都给我存一遍!”。

2. 函数原型

1
2
3
4
5
6
7
8
9
10
11
12
#include <unistd.h>  // 包含 sync 的声明

void sync(void); // 注意:sync 没有返回值,也不会报告错误

#include <sys/syscall.h> // 对于 syncfs,有时需要 syscall.h
// #include <linux/fs.h> // 或者这个头文件
// 在某些较新的 glibc 版本中,可能直接在 unistd.h 中声明
long syscall(SYS_syncfs, int fd); // 底层调用方式

// 或者,如果 glibc 支持
// int syncfs(int fd); // 更简单的用户空间接口 (较新 glibc)

注意:syncfs 相对较新,一些旧版本的 glibc 可能没有直接提供 syncfs() 的包装函数。在这种情况下,你需要使用 syscall() 来直接调用。较新版本的 glibc (2.20+) 通常直接提供了 int syncfs(int fd);。

3. 功能

  • sync(): 将所有已挂载的文件系统的脏数据(文件数据和元数据)刷新到磁盘。

  • syncfs(fd): 将文件描述符 fd 所在的文件系统的脏数据刷新到磁盘。

4. 参数

  • sync(): 无参数。

syncfs(fd):

  • int fd: 一个已打开文件的有效文件描述符。函数会根据这个 fd 找到它所属的文件系统,并只对该文件系统执行同步操作。

5. 返回值

  • sync(): 无返回值。它不会告诉你操作是否成功或失败。这是一个设计上的特点,保证了即使在系统资源紧张时也能尽可能地执行同步。

syncfs(fd):

  • 成功: 返回 0。

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

6. 错误码 (errno) - 仅适用于 syncfs

  • EBADF: fd 不是有效的文件描述符。

  • EIO: I/O 错误。

  • ENOSPC: 设备上没有足够的空间(虽然对于同步操作不太常见)。

  • EROFS: 文件系统是只读的。

7. 相似函数或关联函数

  • fsync: 同步单个文件的所有数据和元数据。它会等待操作完成。

  • fdatasync: 类似于 fsync,但只同步文件的数据和必要的元数据(不包括访问时间等),通常更快。

  • sync_file_range: 提供对文件特定字节范围的更精细同步控制。

  • msync: 用于同步 mmap 内存映射区域到文件。

  • sync 命令: 用户空间的命令行工具,功能与 sync() 系统调用相同。

8. 示例代码

下面的示例演示了如何使用 sync 和 syncfs。

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
#define _GNU_SOURCE // 启用 GNU 扩展,可能需要用于 syncfs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h> // 包含 open, O_* flags
#include <sys/stat.h> // 包含 open modes
#include <string.h>
#include <errno.h>
#include <time.h> // 包含 clock_gettime
#include <sys/syscall.h> // 包含 SYS_syncfs
#include <linux/fs.h> // 包含 SYNCFS 等定义 (如果需要)

// 计算时间差(毫秒)
double time_diff_ms(struct timespec start, struct timespec end) {
return ((end.tv_sec - start.tv_sec) * 1000.0) + ((end.tv_nsec - start.tv_nsec) / 1000000.0);
}

// 封装 syncfs 调用,处理不同 glibc 版本的兼容性
int my_syncfs(int fd) {
// 尝试直接调用 syncfs (如果 glibc 支持)
// 如果编译报错,注释掉 #ifdef 部分,只保留 syscall 部分
#ifdef SYS_syncfs // 检查是否定义了系统调用号
return syscall(SYS_syncfs, fd);
#else
// 如果你的 glibc 版本较新,可能直接支持 syncfs 函数
// return syncfs(fd);
fprintf(stderr, "syncfs system call not available on this system.\n");
return -1;
#endif
}

int main() {
const char *file1_name = "/tmp/sync_test_file1.txt"; // /tmp 通常在根文件系统或 tmpfs
const char *file2_name = "/home/user/sync_test_file2.txt"; // 假设 /home 是另一个分区
int fd1, fd2;
struct timespec start, end;
double elapsed_time;

printf("--- Demonstrating sync and syncfs ---\n");
printf("PID: %d\n", getpid());

// 1. 创建并写入两个测试文件
printf("\n1. Creating and writing test files...\n");

// --- 文件 1 ---
fd1 = open(file1_name, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd1 == -1) {
perror("open file1");
// 如果 /home/user 不可写,跳过 file2
printf("Skipping file2 due to open error.\n");
fd2 = -1;
} else {
printf("Opened %s\n", file1_name);
if (write(fd1, "Data for file 1 on root/tmp filesystem.\n", 40) != 40) {
perror("write file1");
}
// 注意:不要 close,保持 fd1 打开用于 syncfs
}

// --- 文件 2 (可能在不同文件系统) ---
fd2 = open(file2_name, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd2 == -1) {
perror("open file2");
printf("Skipping operations on file2.\n");
} else {
printf("Opened %s\n", file2_name);
if (write(fd2, "Data for file 2 on home filesystem.\n", 37) != 37) {
perror("write file2");
}
// 注意:不要 close,保持 fd2 打开用于 syncfs
}

printf("\nData written to files. It's likely in the page cache now.\n");

// 2. 演示 syncfs: 同步特定文件系统
printf("\n2. --- Demonstrating syncfs ---\n");
if (fd1 != -1) {
printf("Calling syncfs() on the filesystem containing %s...\n", file1_name);
clock_gettime(CLOCK_MONOTONIC, &start);
if (my_syncfs(fd1) == -1) {
perror("syncfs fd1");
printf("This might be because syncfs is not supported or fd is invalid.\n");
} else {
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed_time = time_diff_ms(start, end);
printf("syncfs(fd1) completed in %.2f ms.\n", elapsed_time);
printf("This synced only the filesystem containing %s.\n", file1_name);
}
}

if (fd2 != -1) {
printf("Calling syncfs() on the filesystem containing %s...\n", file2_name);
clock_gettime(CLOCK_MONOTONIC, &start);
if (my_syncfs(fd2) == -1) {
perror("syncfs fd2");
} else {
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed_time = time_diff_ms(start, end);
printf("syncfs(fd2) completed in %.2f ms.\n", elapsed_time);
printf("This synced only the filesystem containing %s.\n", file2_name);
}
}

// 3. 演示 sync: 同步所有文件系统
printf("\n3. --- Demonstrating sync ---\n");
printf("Calling sync() to flush ALL filesystems...\n");
clock_gettime(CLOCK_MONOTONIC, &start);
sync(); // sync() 没有返回值
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed_time = time_diff_ms(start, end);
printf("sync() completed in %.2f ms.\n", elapsed_time);
printf("This synced data for ALL mounted filesystems on the system.\n");

// 4. 关闭文件描述符
if (fd1 != -1) close(fd1);
if (fd2 != -1) close(fd2);

// 5. 清理测试文件 (可选)
printf("\n4. --- Cleaning up ---\n");
if (fd1 != -1) {
if (unlink(file1_name) == 0) {
printf("Deleted %s\n", file1_name);
} else {
perror("unlink file1");
}
}
if (fd2 != -1) {
if (unlink(file2_name) == 0) {
printf("Deleted %s\n", file2_name);
} else {
perror("unlink file2");
}
}

printf("\n--- Summary ---\n");
printf("1. sync(): Flushes dirty data for ALL mounted filesystems. No return value.\n");
printf("2. syncfs(fd): Flushes dirty data for ONLY the filesystem containing the file referred to by 'fd'. Returns 0 on success.\n");
printf("3. syncfs is more targeted and usually faster than sync.\n");
printf("4. Use sync() before system shutdown. Use syncfs() or fsync() for specific data safety in applications.\n");

return 0;
}

9. 编译和运行

1
2
3
4
5
6
7
8
# 假设代码保存在 sync_example.c 中
# _GNU_SOURCE 通常由 #define 定义,显式添加也无妨
gcc -D_GNU_SOURCE -o sync_example sync_example.c

# 运行程序 (可能需要权限来写入 /home/user)
# 如果 /home/user 不可写,请修改代码中的 file2_name
./sync_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
--- Demonstrating sync and syncfs ---
PID: 12345

1. Creating and writing test files...
Opened /tmp/sync_test_file1.txt
Opened /home/user/sync_test_file2.txt

Data written to files. It's likely in the page cache now.

2. --- Demonstrating syncfs ---
Calling syncfs() on the filesystem containing /tmp/sync_test_file1.txt...
syncfs(fd1) completed in 15.23 ms.
This synced only the filesystem containing /tmp/sync_test_file1.txt.
Calling syncfs() on the filesystem containing /home/user/sync_test_file2.txt...
syncfs(fd2) completed in 8.45 ms.
This synced only the filesystem containing /home/user/sync_test_file2.txt.

3. --- Demonstrating sync ---
Calling sync() to flush ALL filesystems...
sync() completed in 45.67 ms.
This synced data for ALL mounted filesystems on the system.

4. --- Cleaning up ---
Deleted /tmp/sync_test_file1.txt
Deleted /home/user/sync_test_file2.txt

--- Summary ---
1. sync(): Flushes dirty data for ALL mounted filesystems. No return value.
2. syncfs(fd): Flushes dirty数据 for ONLY the filesystem containing the file referred to by 'fd'. Returns 0 on success.
3. syncfs is more targeted and usually faster than sync.
4. Use sync() before system shutdown. Use syncfs() or fsync() for specific data safety in applications.

11. 总结

sync 和 syncfs 是确保数据持久化的重要系统调用。

  • sync() 是一个全局操作,强制刷新系统中所有文件系统的缓存数据。它简单粗暴,但开销大。通常在系统关机脚本中调用。

  • syncfs(int fd) 是一个更精细的操作,只刷新与特定文件描述符 fd 相关联的文件系统的缓存数据。它更高效,适用于需要确保特定存储设备数据安全的场景。

与 fsync (同步单个文件) 和 sync_file_range (同步文件特定范围) 相比,sync 和 syncfs 的作用范围更大。选择哪个取决于你的具体需求:是保证一个文件的安全,还是保证一个分区或整个系统的数据安全。

sync_file_range系统调用及示例

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

1. 函数介绍

在 Linux 系统中,为了提高性能,当你调用 write() 将数据写入文件时,这些数据通常不会立即写入到物理磁盘上。相反,它们会被暂时存储在内核的页缓存 (Page Cache) 中。操作系统会在稍后的某个时间点(或者当缓存满时)自动将这些数据刷新到磁盘。这种机制大大提高了写入速度,但也带来了一个问题:如果在数据被实际写入磁盘之前系统崩溃或断电,那么这部分数据就会丢失。

fsync() 和 fdatasync() 函数可以强制将文件的所有(或部分)修改数据从内核缓存刷新到磁盘,确保数据的持久化。但是,它们通常会刷新整个文件相关的数据和元数据,这可能比你需要的更多,导致不必要的性能开销。

sync_file_range (Sync File Range) 系统调用提供了更精细的控制。它允许你指定文件的一个特定字节范围,并决定对这个范围内的数据执行什么样的同步操作。你可以选择:

  • 将数据从缓存写入磁盘(但不等待完成)。

  • 等待数据写入磁盘完成。

  • 将缓存中的脏数据(已修改但未写回)回写到磁盘。

简单来说,sync_file_range 就是让你用程序精确地告诉操作系统:“请帮我确保文件的第 X 字节到第 Y 字节的数据已经安全地保存到硬盘上了”,并且你可以控制这个过程是异步还是同步。

典型应用场景:

  • 数据库系统:数据库需要精确控制其事务日志和数据文件的刷新,以确保事务的原子性和持久性,同时最大化性能。它们可以只刷新刚刚写入的关键日志部分,而不是整个文件。

  • 大文件处理:在写入一个非常大的文件时,可以分段调用 sync_file_range,确保关键部分的数据已经落盘,而不需要等待整个大文件都写完。

  • 高性能应用:需要在数据安全性和写入性能之间取得平衡的应用。

2. 函数原型

1
2
3
4
5
#define _GNU_SOURCE // 需要定义这个宏才能使用 sync_file_range
#include <fcntl.h> // 包含系统调用声明和标志

int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags);

注意:函数名和参数类型可能因系统架构(32/64位)和内核版本而略有不同(如 sync_file_range2),但 glibc 会处理好兼容性,使用 sync_file_range 即可。

3. 功能

对文件描述符 fd 对应的文件,在从 offset 开始的 nbytes 字节范围内,根据 flags 指定的操作,执行同步操作。

4. 参数

fd:

  • int 类型。

  • 一个已打开文件的有效文件描述符。

offset:

  • off64_t 类型。

  • 指定要同步的文件范围的起始字节偏移量。必须是非负数。

nbytes:

  • off64_t 类型。

  • 指定要同步的字节数。如果 nbytes 为 0,则表示同步从 offset 开始到文件末尾的所有数据。

flags:

  • unsigned int 类型。

指定要执行的同步操作。可以是以下一个或多个标志的按位或 (|) 组合:

  • SYNC_FILE_RANGE_WAIT_BEFORE: 等待在 offset 和 nbytes 范围内之前发起的任何写入操作完成。

  • SYNC_FILE_RANGE_WRITE: 启动将指定范围内的脏页面(修改过的缓存数据)回写到磁盘的操作。这个操作通常是异步的,即函数可能在数据实际写入磁盘之前就返回。

  • SYNC_FILE_RANGE_WAIT_AFTER: 等待在 offset 和 nbytes 范围内由 SYNC_FILE_RANGE_WRITE 启动的回写操作完成。

常见的标志组合:

  • SYNC_FILE_RANGE_WRITE: 启动回写,但不等待。适用于“尽快开始保存数据,但我不等它完成”。

  • SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER: 启动回写并等待其完成。这是最常用的方式,确保指定范围的数据确实写入了磁盘。

  • SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER: 等待之前的写入完成 -> 启动回写 -> 等待回写完成。非常彻底,但可能性能较低。

5. 返回值

  • 成功: 返回 0。

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

6. 错误码 (errno)

  • EBADF: fd 不是有效的文件描述符,或者该文件不支持同步操作。

  • EINVAL: flags 参数无效,或者 offset 为负数。

  • EIO: I/O 错误。

  • ENOMEM: 内核内存不足。

  • ENOSPC: 设备上没有足够的空间。

  • ESPIPE: fd 指向的是管道、套接字或 FIFO,这些不支持 sync_file_range。

7. 相似函数或关联函数

  • fsync: 同步文件的所有数据和元数据(如修改时间、文件大小等)。它会等待所有操作完成。

  • fdatasync: 同步文件的数据和必要的元数据(不包括访问时间等非必要元数据),通常比 fsync 快一些。它也会等待完成。

  • msync: 用于同步 mmap 内存映射区域到文件。

  • sync: 同步所有已挂载文件系统上的缓冲数据到磁盘。

  • open with O_SYNC / O_DSYNC: 在打开文件时指定同步标志,使得后续的 write 操作具有同步语义。

8. 示例代码

下面的示例演示了如何使用 sync_file_range 来同步文件的特定部分。

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
#define _GNU_SOURCE // 启用 GNU 扩展以使用 sync_file_range
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h> // 包含 open, O_* flags, sync_file_range
#include <sys/stat.h> // 包含 open modes
#include <string.h>
#include <errno.h>
#include <time.h> // 包含 clock_gettime

// 计算时间差(毫秒)
double time_diff_ms(struct timespec start, struct timespec end) {
return ((end.tv_sec - start.tv_sec) * 1000.0) + ((end.tv_nsec - start.tv_nsec) / 1000000.0);
}

int main() {
const char *filename = "sync_test_file.dat";
const size_t file_size = 10 * 1024 * 1024; // 10 MiB
const size_t chunk_size = 1024 * 1024; // 1 MiB chunks
int fd;
char *data;
struct timespec start, end;
double elapsed_time;

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

// 1. 分配内存并填充数据
data = malloc(chunk_size);
if (!data) {
perror("malloc");
exit(EXIT_FAILURE);
}
memset(data, 'A', chunk_size);

// 2. 创建并打开文件 (O_DIRECT 通常不与普通 buffer 一起用,这里仅作演示文件操作)
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
free(data);
exit(EXIT_FAILURE);
}
printf("Created and opened file: %s\n", filename);

// 3. 写入数据到文件
printf("Writing %zu bytes (%.2f MiB) in %zu byte chunks...\n", file_size, file_size / (1024.0*1024.0), chunk_size);
for (size_t i = 0; i < file_size; i += chunk_size) {
if (write(fd, data, chunk_size) != (ssize_t)chunk_size) {
perror("write");
close(fd);
free(data);
unlink(filename); // 清理
exit(EXIT_FAILURE);
}
// 每写入 2MB,演示一次部分同步
if (i > 0 && (i % (2 * chunk_size)) == 0) {
off64_t sync_offset = i - (2 * chunk_size);
off64_t sync_nbytes = 2 * chunk_size;
printf(" Written up to %zu bytes. Syncing range &#91;%ld, %ld)...\n",
i, (long)sync_offset, (long)(sync_offset + sync_nbytes));

// --- 关键演示:使用 sync_file_range 同步特定范围 ---
// SYNC_FILE_RANGE_WRITE: 启动回写
// SYNC_FILE_RANGE_WAIT_AFTER: 等待回写完成
clock_gettime(CLOCK_MONOTONIC, &start);
if (sync_file_range(fd, sync_offset, sync_nbytes,
SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER) == -1) {
perror("sync_file_range");
// 不退出,继续演示
}
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed_time = time_diff_ms(start, end);
printf(" -> sync_file_range for %ld bytes took %.2f ms\n", (long)sync_nbytes, elapsed_time);
}
}
printf("Finished writing file.\n");

// 4. 演示同步整个文件的最后部分
printf("\n--- Syncing final part of the file ---\n");
off64_t last_sync_offset = (file_size / chunk_size - 2) * chunk_size; // 倒数第二块开始
off64_t last_sync_nbytes = file_size - last_sync_offset; // 到文件末尾
printf("Syncing final range &#91;%ld, EOF) using sync_file_range...\n", (long)last_sync_offset);

clock_gettime(CLOCK_MONOTONIC, &start);
if (sync_file_range(fd, last_sync_offset, last_sync_nbytes,
SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER) == -1) {
perror("sync_file_range (final part)");
}
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed_time = time_diff_ms(start, end);
printf("-> sync_file_range for final %ld bytes took %.2f ms\n", (long)last_sync_nbytes, elapsed_time);

// 5. 对比:使用 fsync 同步整个文件
printf("\n--- Comparing with fsync (syncs entire file) ---\n");
printf("Calling fsync() to sync the entire file...\n");
clock_gettime(CLOCK_MONOTONIC, &start);
if (fsync(fd) == -1) {
perror("fsync");
}
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed_time = time_diff_ms(start, end);
printf("-> fsync() took %.2f ms\n", elapsed_time);

// 6. 关闭文件
close(fd);
free(data);

// 7. 验证文件大小
struct stat sb;
if (stat(filename, &sb) == 0) {
printf("\n--- Verification ---\n");
printf("File '%s' created successfully.\n", filename);
printf("Final file size: %ld bytes (%.2f MiB)\n", (long)sb.st_size, sb.st_size / (1024.0*1024.0));
}

// 8. 清理 (注释掉这行可以保留文件用于检查)
// unlink(filename);
// printf("Deleted test file '%s'.\n", filename);

printf("\n--- Summary ---\n");
printf("1. sync_file_range allows synchronizing a specific byte range of a file.\n");
printf("2. It can be more efficient than fsync for large files where only parts need syncing.\n");
printf("3. Flags control whether to start writeback (WRITE) and/or wait for completion (WAIT_*).\n");
printf("4. It provides fine-grained control for performance-critical applications like databases.\n");

return 0;
}

9. 编译和运行

1
2
3
4
5
6
7
# 假设代码保存在 sync_file_range_example.c 中
# 注意:_GNU_SOURCE 可能已由 #define 定义,显式添加也无妨
gcc -D_GNU_SOURCE -o sync_file_range_example sync_file_range_example.c

# 运行程序
./sync_file_range_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
--- Demonstrating sync_file_range ---
Created and opened file: sync_test_file.dat
Writing 10485760 bytes (10.00 MiB) in 1048576 byte chunks...
Written up to 2097152 bytes. Syncing range &#91;0, 2097152)...
-> sync_file_range for 2097152 bytes took 5.23 ms
Written up to 4194304 bytes. Syncing range &#91;2097152, 4194304)...
-> sync_file_range for 2097152 bytes took 3.12 ms
Written up to 6291456 bytes. Syncing range &#91;4194304, 6291456)...
-> sync_file_range for 2097152 bytes took 2.87 ms
Written up to 8388608 bytes. Syncing range &#91;6291456, 8388608)...
-> sync_file_range for 2097152 bytes took 3.01 ms
Finished writing file.

--- Syncing final part of the file ---
Syncing final range &#91;8388608, EOF) using sync_file_range...
-> sync_file_range for final 2097152 bytes took 2.95 ms

--- Comparing with fsync (syncs entire file) ---
Calling fsync() to sync the entire file...
-> fsync() took 12.34 ms

--- Verification ---
File 'sync_test_file.dat' created successfully.
Final file size: 10485760 bytes (10.00 MiB)

--- Summary ---
1. sync_file_range allows synchronizing a specific byte range of a file.
2. It can be more efficient than fsync for large files where only parts need syncing.
3. Flags control whether to start writeback (WRITE) and/or wait for completion (WAIT_*).
4. It provides fine-grained control for performance-critical applications like databases.

11. 总结

sync_file_range 是一个强大的、用于精确控制文件数据同步的系统调用。它允许程序只同步文件的特定部分,而不是整个文件,从而在需要数据持久化保证的场景下提供了比 fsync 更好的性能。理解其标志位的含义(WRITE, WAIT_BEFORE, WAIT_AFTER)对于正确使用它至关重要。它是构建高性能、数据安全应用(如数据库、文件系统工具)的重要工具。

syscall系统调用及示例

syscall函数详解

syscall函数是Linux系统中访问系统调用的通用接口,它是用户程序与Linux内核之间的重要桥梁。可以将syscall想象成一个”万能钥匙”,它允许程序直接调用内核提供的各种底层功能,如文件操作、进程控制、网络通信、内存管理等。

  1. 函数介绍

syscall函数是Linux系统中访问系统调用的通用接口,它是用户程序与Linux内核之间的重要桥梁。可以将syscall想象成一个”万能钥匙”,它允许程序直接调用内核提供的各种底层功能,如文件操作、进程控制、网络通信、内存管理等。

在Linux系统中,所有的系统功能都是通过系统调用来实现的。当你调用像open()、read()、write()这样的函数时,它们实际上都是通过syscall来与内核交互的。syscall函数提供了一种直接访问这些底层功能的方式,让你可以调用那些可能还没有标准C库封装的新系统调用。

使用场景:

  • 访问新的或非标准的系统调用

  • 学习和理解系统调用机制

  • 实现系统级编程和调试工具

  • 调用特定架构的系统调用

  • 性能敏感的应用程序直接调用内核功能

  1. 函数原型
1
2
3
4
5
#include <unistd.h>
#include <sys/syscall.h>

long syscall(long number, ...);

  1. 功能

syscall函数的主要功能是直接调用Linux内核提供的系统调用。它绕过了标准C库的封装层,直接与内核交互,这使得它能够访问所有可用的系统调用,包括那些还没有被标准库函数封装的调用。

  1. 参数

number: 系统调用号

  • 类型:long

  • 含义:要调用的系统调用的唯一标识号,每个系统调用都有一个固定的编号

…: 可变参数

  • 类型:可变参数列表

  • 含义:传递给系统调用的参数,参数的数量和类型取决于具体的系统调用

  1. 返回值
  • 成功: 返回系统调用的返回值(通常是long类型)

失败: 返回-1,并设置errno错误码

  • 各种错误码取决于具体的系统调用和错误情况
  1. 相似函数或关联函数
  • 系统调用封装函数: 如open(), read(), write(), fork(), exec()等

  • syscall()的特定封装: 如tgkill(), gettid()等

  • 内联汇编: 直接使用汇编指令调用系统调用

  • ioctl(): 设备控制的系统调用

  • 系统调用表: /usr/include/asm/unistd.h中定义的系统调用号

  1. 示例代码

示例1:基础syscall使用 - 常用系统调用

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main() {
// 示例1: 使用syscall调用getpid()系统调用
printf("=== 使用syscall调用getpid ===\n");
pid_t pid = syscall(SYS_getpid);
printf("进程ID: %d\n", pid);

// 示例2: 使用syscall调用getppid()系统调用
printf("\n=== 使用syscall调用getppid ===\n");
pid_t ppid = syscall(SYS_getppid);
printf("父进程ID: %d\n", ppid);

// 示例3: 使用syscall调用getuid()系统调用
printf("\n=== 使用syscall调用getuid ===\n");
uid_t uid = syscall(SYS_getuid);
printf("用户ID: %d\n", uid);

// 示例4: 使用syscall调用geteuid()系统调用
printf("\n=== 使用syscall调用geteuid ===\n");
uid_t euid = syscall(SYS_geteuid);
printf("有效用户ID: %d\n", euid);

// 示例5: 使用syscall调用gettid()系统调用
printf("\n=== 使用syscall调用gettid ===\n");
pid_t tid = syscall(SYS_gettid);
printf("线程ID: %d\n", tid);

// 示例6: 使用syscall进行文件操作
printf("\n=== 使用syscall进行文件操作 ===\n");

// 创建或打开文件
const char* filename = "syscall_test.txt";
int fd = syscall(SYS_open, filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if(fd == -1) {
perror("打开文件失败");
return 1;
}
printf("文件描述符: %d\n", fd);

// 写入数据
const char* message = "Hello from syscall!\n";
ssize_t bytes_written = syscall(SYS_write, fd, message, strlen(message));
if(bytes_written == -1) {
perror("写入文件失败");
} else {
printf("写入字节数: %zd\n", bytes_written);
}

// 关闭文件
int close_result = syscall(SYS_close, fd);
if(close_result == -1) {
perror("关闭文件失败");
} else {
printf("文件已关闭\n");
}

// 示例7: 使用syscall读取文件
printf("\n=== 使用syscall读取文件 ===\n");
fd = syscall(SYS_open, filename, O_RDONLY);
if(fd != -1) {
char buffer&#91;256];
ssize_t bytes_read = syscall(SYS_read, fd, buffer, sizeof(buffer) - 1);
if(bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("读取内容: %s", buffer);
}
syscall(SYS_close, fd);
}

// 清理测试文件
syscall(SYS_unlink, filename);

return 0;
}

示例2:高级syscall使用 - 进程和内存管理

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

int main() {
printf("=== 高级syscall使用示例 ===\n");

// 示例1: 使用mmap系统调用
printf("\n1. 使用mmap进行内存映射:\n");
void* mapped_memory = (void*)syscall(SYS_mmap,
NULL, // 地址
4096, // 长度
PROT_READ | PROT_WRITE, // 保护
MAP_PRIVATE | MAP_ANONYMOUS, // 标志
-1, // 文件描述符
0); // 偏移

if(mapped_memory == MAP_FAILED) {
perror("mmap失败");
} else {
printf("成功映射内存地址: %p\n", mapped_memory);

// 在映射的内存中写入数据
strcpy((char*)mapped_memory, "Hello from mapped memory!");
printf("内存内容: %s\n", (char*)mapped_memory);

// 取消内存映射
syscall(SYS_munmap, mapped_memory, 4096);
printf("内存映射已取消\n");
}

// 示例2: 使用brk系统调用调整程序断点
printf("\n2. 使用brk调整程序断点:\n");
void* current_brk = (void*)syscall(SYS_brk, 0);
printf("当前程序断点: %p\n", current_brk);

// 扩展堆空间
void* new_brk = (void*)syscall(SYS_brk, (long)current_brk + 8192);
if(new_brk != (void*)-1) {
printf("新程序断点: %p\n", new_brk);
printf("堆空间扩展了 %ld 字节\n", (long)new_brk - (long)current_brk);

// 恢复原来的断点
syscall(SYS_brk, (long)current_brk);
}

// 示例3: 使用fork系统调用创建子进程
printf("\n3. 使用fork创建子进程:\n");
pid_t pid = syscall(SYS_fork);

if(pid == -1) {
perror("fork失败");
} else if(pid == 0) {
// 子进程
printf("子进程: PID=%d, PPID=%d\n",
syscall(SYS_getpid), syscall(SYS_getppid));
exit(0);
} else {
// 父进程
printf("父进程: PID=%d, 创建了子进程 PID=%d\n",
syscall(SYS_getpid), pid);
// 等待子进程结束
int status;
syscall(SYS_wait4, pid, &status, 0, NULL);
printf("子进程已结束\n");
}

// 示例4: 使用gettimeofday系统调用获取时间
printf("\n4. 使用gettimeofday获取时间:\n");
struct {
long tv_sec;
long tv_usec;
} tv;

int result = syscall(SYS_gettimeofday, &tv, NULL);
if(result == 0) {
printf("当前时间: %ld 秒 %ld 微秒\n", tv.tv_sec, tv.tv_usec);
} else {
perror("gettimeofday失败");
}

// 示例5: 使用uname系统调用获取系统信息
printf("\n5. 使用uname获取系统信息:\n");
struct {
char sysname&#91;65];
char nodename&#91;65];
char release&#91;65];
char version&#91;65];
char machine&#91;65];
} uts;

result = syscall(SYS_uname, &uts);
if(result == 0) {
printf("系统名称: %s\n", uts.sysname);
printf("节点名称: %s\n", uts.nodename);
printf("内核版本: %s\n", uts.release);
printf("系统版本: %s\n", uts.version);
printf("硬件架构: %s\n", uts.machine);
} else {
perror("uname失败");
}

return 0;
}

示例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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>

// 通用的syscall错误处理函数
void handle_syscall_error(const char* syscall_name) {
fprintf(stderr, "系统调用 %s 失败: ", syscall_name);
switch(errno) {
case EPERM:
fprintf(stderr, "操作不被允许\n");
break;
case ENOENT:
fprintf(stderr, "文件或目录不存在\n");
break;
case EACCES:
fprintf(stderr, "权限不足\n");
break;
case EFAULT:
fprintf(stderr, "地址错误\n");
break;
case EINVAL:
fprintf(stderr, "无效参数\n");
break;
case ENOMEM:
fprintf(stderr, "内存不足\n");
break;
case EBUSY:
fprintf(stderr, "资源忙\n");
break;
default:
fprintf(stderr, "%s\n", strerror(errno));
break;
}
}

// 获取系统调用名称的辅助函数
const char* get_syscall_name(long syscall_num) {
switch(syscall_num) {
case SYS_getpid: return "getpid";
case SYS_getppid: return "getppid";
case SYS_getuid: return "getuid";
case SYS_geteuid: return "geteuid";
case SYS_getgid: return "getgid";
case SYS_getegid: return "getegid";
default: return "unknown";
}
}

// 安全的syscall包装函数
long safe_syscall(long number, const char* name) {
errno = 0; // 清除之前的错误
long result = syscall(number);

if(result == -1) {
handle_syscall_error(name);
}

return result;
}

int main() {
printf("=== 自定义系统调用封装和错误处理 ===\n");

// 测试各种系统调用
struct {
long number;
const char* name;
const char* description;
} syscalls&#91;] = {
{SYS_getpid, "getpid", "获取进程ID"},
{SYS_getppid, "getppid", "获取父进程ID"},
{SYS_getuid, "getuid", "获取用户ID"},
{SYS_geteuid, "geteuid", "获取有效用户ID"},
{SYS_getgid, "getgid", "获取组ID"},
{SYS_getegid, "getegid", "获取有效组ID"}
};

int num_syscalls = sizeof(syscalls) / sizeof(syscalls&#91;0]);

printf("\n系统信息获取测试:\n");
printf("----------------------------\n");

for(int i = 0; i < num_syscalls; i++) {
printf("%-10s: ", syscalls&#91;i].description);
long result = safe_syscall(syscalls&#91;i].number, syscalls&#91;i].name);
if(result != -1) {
printf("%ld\n", result);
}
}

// 测试错误处理 - 调用不存在的系统调用
printf("\n错误处理测试:\n");
printf("----------------------------\n");
printf("调用不存在的系统调用 (编号999999):\n");
errno = 0;
long result = syscall(999999);
if(result == -1) {
handle_syscall_error("invalid_syscall");
}

// 测试权限相关的系统调用
printf("\n权限相关测试:\n");
printf("----------------------------\n");

// 获取当前工作目录 (使用getcwd系统调用)
printf("当前工作目录测试:\n");
char cwd&#91;1024];
errno = 0;
long cwd_result = syscall(SYS_getcwd, cwd, sizeof(cwd));
if(cwd_result != -1) {
printf("当前目录: %s\n", cwd);
} else {
handle_syscall_error("getcwd");
}

// 测试系统资源使用情况
printf("\n系统资源使用测试:\n");
printf("----------------------------\n");

struct {
long ru_utime_sec; // 用户CPU时间(秒)
long ru_utime_usec; // 用户CPU时间(微秒)
long ru_stime_sec; // 系统CPU时间(秒)
long ru_stime_usec; // 系统CPU时间(微秒)
long ru_maxrss; // 最大常驻集大小
long ru_ixrss; // 共享内存大小
long ru_idrss; // 非共享数据大小
long ru_isrss; // 非共享栈大小
long ru_minflt; // 页错误(次要)
long ru_majflt; // 页错误(主要)
long ru_nswap; // 交换次数
long ru_inblock; // 输入操作数
long ru_oublock; // 输出操作数
long ru_msgsnd; // 消息发送数
long ru_msgrcv; // 消息接收数
long ru_nsignals; // 信号接收数
long ru_nvcsw; // 自愿上下文切换
long ru_nivcsw; // 非自愿上下文切换
} usage;

errno = 0;
result = syscall(SYS_getrusage, RUSAGE_SELF, &usage);
if(result == 0) {
printf("用户CPU时间: %ld.%06ld 秒\n", usage.ru_utime_sec, usage.ru_utime_usec);
printf("系统CPU时间: %ld.%06ld 秒\n", usage.ru_stime_sec, usage.ru_stime_usec);
printf("最大内存使用: %ld KB\n", usage.ru_maxrss);
} else {
handle_syscall_error("getrusage");
}

// 演示syscall的优势 - 访问新系统调用
printf("\n新系统调用演示:\n");
printf("----------------------------\n");
printf("注意: 某些新系统调用可能在当前内核版本中不可用\n");

// 尝试调用eventfd系统调用(如果可用)
errno = 0;
result = syscall(SYS_eventfd, 0);
if(result != -1) {
printf("eventfd系统调用成功,文件描述符: %ld\n", result);
close(result); // 关闭文件描述符
} else {
printf("eventfd系统调用不可用或失败\n");
}

printf("\n=== 测试完成 ===\n");

return 0;
}

示例4:性能对比 - syscall vs 标准库函数

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <string.h>

// 性能测试宏
#define ITERATIONS 1000000

// 获取当前时间(微秒)
long get_time_usec() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000000 + tv.tv_usec;
}

int main() {
printf("=== syscall vs 标准库函数性能对比 ===\n");
printf("测试迭代次数: %d 次\n\n", ITERATIONS);

long start_time, end_time;
long syscall_time, library_time;

// 测试1: getpid系统调用性能
printf("1. getpid() 性能测试:\n");

// 使用syscall
start_time = get_time_usec();
for(int i = 0; i < ITERATIONS; i++) {
syscall(SYS_getpid);
}
end_time = get_time_usec();
syscall_time = end_time - start_time;

// 使用标准库函数
start_time = get_time_usec();
for(int i = 0; i < ITERATIONS; i++) {
getpid();
}
end_time = get_time_usec();
library_time = end_time - start_time;

printf(" syscall方式: %ld 微秒\n", syscall_time);
printf(" 标准库方式: %ld 微秒\n", library_time);
printf(" 性能差异: %ld 微秒 (%.2f%%)\n",
syscall_time - library_time,
((double)(syscall_time - library_time) / library_time) * 100);

// 测试2: getuid系统调用性能
printf("\n2. getuid() 性能测试:\n");

// 使用syscall
start_time = get_time_usec();
for(int i = 0; i < ITERATIONS; i++) {
syscall(SYS_getuid);
}
end_time = get_time_usec();
syscall_time = end_time - start_time;

// 使用标准库函数
start_time = get_time_usec();
for(int i = 0; i < ITERATIONS; i++) {
getuid();
}
end_time = get_time_usec();
library_time = end_time - start_time;

printf(" syscall方式: %ld 微秒\n", syscall_time);
printf(" 标准库方式: %ld 微秒\n", library_time);
printf(" 性能差异: %ld 微秒 (%.2f%%)\n",
syscall_time - library_time,
((double)(syscall_time - library_time) / library_time) * 100);

// 测试3: 文件操作性能对比
printf("\n3. 文件操作性能测试:\n");

const char* test_file = "perf_test.txt";
const char* test_data = "Performance test data\n";

// 使用syscall进行文件操作
start_time = get_time_usec();
for(int i = 0; i < 1000; i++) { // 减少迭代次数以避免文件系统压力
int fd = syscall(SYS_open, test_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if(fd != -1) {
syscall(SYS_write, fd, test_data, strlen(test_data));
syscall(SYS_close, fd);
}
}
end_time = get_time_usec();
long syscall_file_time = end_time - start_time;

// 使用标准库函数进行文件操作
start_time = get_time_usec();
for(int i = 0; i < 1000; i++) {
FILE* fp = fopen(test_file, "w");
if(fp != NULL) {
fwrite(test_data, 1, strlen(test_data), fp);
fclose(fp);
}
}
end_time = get_time_usec();
long library_file_time = end_time - start_time;

printf(" syscall方式: %ld 微秒\n", syscall_file_time);
printf(" 标准库方式: %ld 微秒\n", library_file_time);
printf(" 性能差异: %ld 微秒 (%.2f%%)\n",
syscall_file_time - library_file_time,
((double)(syscall_file_time - library_file_time) / library_file_time) * 100);

// 清理测试文件
unlink(test_file);

// 总结
printf("\n=== 性能测试总结 ===\n");
printf("1. 系统调用通常比标准库函数更快,因为避免了额外的封装开销\n");
printf("2. 但在某些情况下,标准库函数可能有优化(如缓存)\n");
printf("3. syscall更适合需要精确控制或访问新功能的场景\n");
printf("4. 标准库函数更适合日常开发,提供更好的可移植性和错误处理\n");

return 0;
}

编译和运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 编译示例1
gcc -o syscall_example1 syscall_example1.c
./syscall_example1

# 编译示例2
gcc -o syscall_example2 syscall_example2.c
./syscall_example2

# 编译示例3
gcc -o syscall_example3 syscall_example3.c
./syscall_example3

# 编译示例4
gcc -o syscall_example4 syscall_example4.c
./syscall_example4

重要注意事项

系统调用号: 不同架构的系统调用号可能不同,通常在/usr/include/asm/unistd.h中定义

错误处理: syscall失败时返回-1并设置errno,必须进行检查

参数类型: 参数类型必须与系统调用期望的类型匹配

可移植性: syscall是Linux特有的,不适用于其他操作系统

性能: syscall通常比标准库函数更快,但标准库函数更安全易用

权限: 某些系统调用需要特定权限才能执行

通过这些示例,你可以理解syscall作为系统调用接口的强大功能,它为底层系统编程提供了直接访问内核功能的能力。

create_module系统调用及示例

关于 create_module 和 delete_module 的内核版本历史

create_module 废弃时间

  • Linux 2.6 内核(2003年左右)开始逐步废弃

  • Linux 2.6.8 版本后完全移除

  • 最后支持的内核版本:Linux 2.4.x

delete_module 变化时间

  • Linux 2.6 内核开始改变行为

  • 从系统调用转变为更安全的模块管理机制

  • 现代系统中仍然存在,但行为更加受限

详细历史说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* Linux 内核模块管理演进历史:
*
* Linux 2.0-2.4 (1996-2003):
* - 使用 create_module() 分配内核内存
* - 使用 delete_module() 卸载模块
* - 相对简单的模块加载机制
*
* Linux 2.6+ (2003年至今):
* - 引入 init_module() 替代 create_module()
* - delete_module() 仍然可用但更加安全
* - 模块签名验证机制
* - 更严格的权限控制
*
* 现代 Linux (3.0+):
* - 模块加载通过 finit_module() 系统调用
* - delete_module() 保留但增加安全检查
* - 强制模块签名(某些发行版)
*/

现代替代方案

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>

int main() {
printf("=== 现代内核模块管理 ===\n");
printf("Linux内核版本演进:\n");
printf("- 2.4及以前: create_module/delete_module\n");
printf("- 2.6开始: init_module/delete_module\n");
printf("- 3.0+: fini_module/delete_module\n\n");

printf("create_module废弃时间: Linux 2.6.8 (2004年)\n");
printf("最后支持版本: Linux 2.4.37 (2009年停止维护)\n\n");

printf("现代替代方案:\n");
printf("1. 用户空间工具:\n");
printf(" - insmod: 加载模块\n");
printf(" - rmmod: 卸载模块\n");
printf(" - lsmod: 列出模块\n\n");

printf("2. 系统调用:\n");
printf(" - init_module(): 加载模块\n");
printf(" - finit_module(): 文件描述符版本的init_module\n");
printf(" - delete_module(): 卸载模块(仍在使用)\n\n");

printf("3. 程序化使用:\n");
printf(" - libkmod库提供高级API\n");
printf(" - modprobe命令处理依赖关系\n");

return 0;
}

总结

  • create_module: Linux 2.6.8 (2004年) 后完全废弃

  • delete_module: 仍在使用,但在现代内核中有更多安全限制

  • 对于现代Linux系统编程,应该使用用户空间工具或libkmod库来管理内核模块

dup-dup2系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 dup 和 dup2 函数,它们用于复制一个已存在的文件描述符 (file descriptor)。

1. 函数介绍

dup 和 dup2 是 Linux 系统调用,它们的功能是创建一个指向同一文件表项 (open file description) 的新文件描述符。

简单来说,当你调用 dup 或 dup2 时,你得到的是一个别名或副本,这个新文件描述符和原始文件描述符指向同一个打开的文件,共享文件的:

  • 文件偏移量 (file offset): 通过一个描述符读写会改变文件位置,通过另一个描述符读写会从新的位置开始。

  • 状态标志 (status flags): 如 O_APPEND, O_NONBLOCK 等。

  • 文件锁 (file locks): 通过任何一个描述符获取的锁,对另一个描述符也有效。

它们最常见的用途是重定向标准输入、标准输出或标准错误。例如,将一个程序的输出重定向到文件,而不是终端。

你可以把文件描述符想象成一个指向文件的“把手”。dup 就像是给这个“把手”又做了一个一模一样的复制品。你拿着任何一个“把手”都能操作同一个文件,而且它们的状态是同步的。

2. 函数原型

1
2
3
4
5
6
7
8
#include <unistd.h> // 必需

// 复制文件描述符 (返回新的最小可用 fd)
int dup(int oldfd);

// 复制文件描述符到指定的新 fd
int dup2(int oldfd, int newfd);

3. 功能

dup(int oldfd):

  • 复制一个已存在的文件描述符 oldfd。

  • 内核会在当前进程中选择最小的未使用的文件描述符号码作为新的描述符。

  • 新的文件描述符和 oldfd 指向同一个文件表项。

dup2(int oldfd, int newfd):

  • 复制文件描述符 oldfd,并强制使复制得到的新文件描述符的号码为 newfd。

  • 如果 newfd 已经打开(指向另一个文件),dup2 会在复制前先关闭 newfd(相当于先调用 close(newfd))。

  • 如果 oldfd 和 newfd 相同,dup2 什么都不做,直接返回 newfd。

4. 参数

dup:

  • int oldfd: 要被复制的现有有效文件描述符。

dup2:

  • int oldfd: 要被复制的现有有效文件描述符。

int newfd: 请求的新文件描述符号码。

  • 如果 newfd 已经打开,它会被关闭。

  • 如果 newfd 等于 oldfd,则不执行任何操作。

5. 返回值

成功时:

  • 返回新的文件描述符号码。

  • 对于 dup,这个号码是当前进程中最小的可用号码。

  • 对于 dup2,这个号码就是请求的 newfd。

失败时:

  • 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF oldfd 或 newfd 无效,EMFILE 进程打开的文件描述符已达上限等)。

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

  • fcntl: dup(oldfd) 等价于 fcntl(oldfd, F_DUPFD, 0)。fcntl 提供了更灵活的复制方式,例如 F_DUPFD_CLOEXEC 可以在复制时设置 FD_CLOEXEC 标志。

  • close: dup2 在复制前如果 newfd 已打开,会隐式调用 close(newfd)。

  • open: 用于获取最初的文件描述符。

  • read, write: 对复制后的文件描述符进行操作。

7. 示例代码

示例 1:使用 dup 和 dup2 重定向标准输出

这个例子演示了如何使用 dup 和 dup2 来保存原始标准输出,然后将标准输出重定向到一个文件,最后再恢复标准输出。

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

int main() {
int saved_stdout; // 用于保存原始标准输出的文件描述符
int file_fd; // 用于重定向输出的文件描述符

// 1. 保存原始的标准输出 (stdout, 文件描述符 1)
saved_stdout = dup(STDOUT_FILENO);
if (saved_stdout == -1) {
perror("dup saved_stdout");
exit(EXIT_FAILURE);
}
printf("Saved original stdout to fd %d\n", saved_stdout);

// 2. 打开一个文件用于重定向
file_fd = open("output_redirection.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (file_fd == -1) {
perror("open file for redirection");
close(saved_stdout); // 清理
exit(EXIT_FAILURE);
}
printf("Opened file 'output_redirection.txt' with fd %d\n", file_fd);

// 3. 将标准输出重定向到文件
// 方法一:使用 dup2
if (dup2(file_fd, STDOUT_FILENO) == -1) {
perror("dup2 redirect stdout");
close(file_fd);
close(saved_stdout);
exit(EXIT_FAILURE);
}
printf("Standard output redirected to file.\n");
// 注意:从现在开始,printf 的输出将写入到文件中,而不是终端!

// 4. 输出一些内容到文件
printf("This line goes to the file.\n");
printf("This line also goes to the file.\n");

// 5. 恢复标准输出
// 使用 dup2 将保存的原始 stdout 描述符复制回 STDOUT_FILENO
if (dup2(saved_stdout, STDOUT_FILENO) == -1) {
perror("dup2 restore stdout");
// 清理
close(file_fd);
close(saved_stdout);
exit(EXIT_FAILURE);
}
printf("Standard output restored to terminal.\n");
// 注意:从现在开始,printf 的输出将重新显示在终端上!

// 6. 再输出一些内容到终端
printf("This line goes back to the terminal.\n");

// 7. 关闭所有打开的文件描述符
// close(file_fd) 实际上关闭了文件表项的一个引用
// dup2 在重定向时已经关闭了原来的 STDOUT_FILENO 对应的文件表项引用
// 所以这里只需要关闭 file_fd 和我们自己保存的 saved_stdout
if (close(file_fd) == -1) {
perror("close file_fd");
}
// 关闭 saved_stdout 也会关闭它指向的文件表项(即原始的终端 stdout)
if (close(saved_stdout) == -1) {
perror("close saved_stdout");
}

printf("All file descriptors closed.\n");
return 0;
}

代码解释:

调用 dup(STDOUT_FILENO) 创建原始标准输出(文件描述符 1)的一个副本,并将其保存在 saved_stdout 中。这个副本让我们可以稍后恢复标准输出。

使用 open 创建或打开一个名为 output_redirection.txt 的文件,用于接收重定向的输出。

调用 dup2(file_fd, STDOUT_FILENO)。这会:

  • 关闭当前的 STDOUT_FILENO(文件描述符 1)。

  • 将 file_fd 复制一份,并使这个新副本的号码为 1 (即 STDOUT_FILENO)。

  • 现在,文件描述符 1 和 file_fd 都指向 output_redirection.txt 文件。

执行 printf。因为标准输出已经被重定向,所以这些内容会写入到 output_redirection.txt 文件中。

调用 dup2(saved_stdout, STDOUT_FILENO) 来恢复标准输出。这会将之前保存的原始终端输出描述符复制回文件描述符 1。

再次执行 printf。现在标准输出已经恢复,内容会显示在终端上。

最后,使用 close 关闭所有打开的文件描述符。

示例 2:dup2 自动关闭目标文件描述符

这个例子重点演示 dup2 在目标文件描述符已打开时自动关闭它的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
int fd1, fd2;
char buffer&#91;100];
ssize_t bytes_read;

// 1. 打开两个不同的文件
fd1 = open("file1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd1 == -1) {
perror("open file1.txt");
exit(EXIT_FAILURE);
}
write(fd1, "Content of file 1\n", 18);

fd2 = open("file2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd2 == -1) {
perror("open file2.txt");
close(fd1);
exit(EXIT_FAILURE);
}
write(fd2, "Content of file 2\n", 18);

printf("Initially:\n");
printf(" fd1 points to 'file1.txt' (fd=%d)\n", fd1);
printf(" fd2 points to 'file2.txt' (fd=%d)\n", fd2);

// 2. 使用 dup2 将 fd1 复制到 fd2
// 这会自动关闭 fd2 当前指向的 'file2.txt'
printf("\nCalling dup2(fd1, fd2)...\n");
if (dup2(fd1, fd2) == -1) {
perror("dup2(fd1, fd2)");
close(fd1);
close(fd2);
exit(EXIT_FAILURE);
}

printf("After dup2(fd1, fd2):\n");
printf(" fd1 still points to 'file1.txt' (fd=%d)\n", fd1);
printf(" fd2 now ALSO points to 'file1.txt' (fd=%d)\n", fd2);
printf(" 'file2.txt' has been closed automatically.\n");

// 3. 验证:向 fd2 写入数据,应该出现在 'file1.txt' 中
write(fd2, "Data written via fd2 after dup2\n", 32);

// 4. 关闭文件描述符
// 关闭 fd1 和 fd2 实际上是关闭同一个文件表项的两个引用
// 内核会在最后一个引用关闭时才真正关闭文件
close(fd1);
close(fd2); // 这个 close 实际上是关闭 fd1/fd2 共同指向的文件表项

// 5. 读取 file1.txt 来验证内容
printf("\nContents of 'file1.txt' after operations:\n");
int read_fd = open("file1.txt", O_RDONLY);
if (read_fd != -1) {
while ((bytes_read = read(read_fd, buffer, sizeof(buffer) - 1)) > 0) {
buffer&#91;bytes_read] = '\0';
printf("%s", buffer);
}
close(read_fd);
}

// 6. 检查 file2.txt 是否为空或被截断 (因为它被 dup2 自动关闭了)
printf("\nContents of 'file2.txt' (should be empty or just initial content if not truncated):\n");
read_fd = open("file2.txt", O_RDONLY);
if (read_fd != -1) {
while ((bytes_read = read(read_fd, buffer, sizeof(buffer) - 1)) > 0) {
buffer&#91;bytes_read] = '\0';
printf("%s", buffer);
}
close(read_fd);
}
if (bytes_read == 0) {
printf("(file2.txt is empty)\n");
}

return 0;
}

代码解释:

打开两个文件 file1.txt 和 file2.txt,分别得到文件描述符 fd1 和 fd2。

向两个文件写入不同的初始内容。

调用 dup2(fd1, fd2)。关键点在于:

  • fd2 当前是打开的,指向 file2.txt。

  • dup2 会自动关闭 fd2。

  • 然后,它将 fd1(指向 file1.txt)复制一份,并使这个副本的号码为 fd2。

现在,fd1 和 fd2 都指向 file1.txt。

向 fd2 写入数据,数据会出现在 file1.txt 中,证明了 fd2 现在确实指向 file1.txt。

关闭 fd1 和 fd2。由于它们指向同一个文件表项,文件只会在最后一个引用关闭时才真正关闭。

读取 file1.txt 和 file2.txt 的内容来验证操作结果。file1.txt 应该包含所有写入的内容,而 file2.txt 可能为空(因为它在 dup2 时被关闭了,如果它之前的内容没有被 O_TRUNC 重新截断,则可能还保留着)。

示例 3:dup 选择最小可用文件描述符

这个例子演示 dup 如何选择最小的可用文件描述符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
int fd, new_fd1, new_fd2;

// 1. 打开一个文件
fd = open("test_dup.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
printf("Original file descriptor: %d\n", fd);

// 2. 复制文件描述符 (使用 dup)
new_fd1 = dup(fd);
if (new_fd1 == -1) {
perror("dup 1");
close(fd);
exit(EXIT_FAILURE);
}
printf("First dup() returned new fd: %d\n", new_fd1);

// 3. 再次复制
new_fd2 = dup(fd);
if (new_fd2 == -1) {
perror("dup 2");
close(fd);
close(new_fd1);
exit(EXIT_FAILURE);
}
printf("Second dup() returned new fd: %d\n", new_fd2);

// 4. 写入数据到所有描述符,验证它们指向同一文件
write(fd, "Written via fd\n", 15);
write(new_fd1, "Written via new_fd1\n", 20);
write(new_fd2, "Written via new_fd2\n", 20);

printf("Data written via all three file descriptors.\n");

// 5. 关闭所有描述符
close(fd);
close(new_fd1);
close(new_fd2);

printf("All file descriptors closed.\n");
return 0;
}

代码解释:

打开一个文件,得到文件描述符 fd(假设为 3)。

调用 dup(fd)。因为 0, 1, 2 (stdin, stdout, stderr) 通常已被占用,所以 dup 会返回下一个最小的可用描述符,比如 4。

再次调用 dup(fd)。现在 3, 4 已被占用,所以会返回 5。

向 fd, new_fd1, new_fd2 写入数据,验证它们都写入了同一个文件。

关闭所有文件描述符。

总结:

dup 和 dup2 是用于复制文件描述符的强大工具,它们在实现输入/输出重定向、保存和恢复标准流、以及在进程管理(如 fork 后)中非常有用。理解它们的关键在于掌握新旧描述符共享文件表项的特性,以及 dup2 自动关闭目标描述符的行为。

dup3系统调用及示例

1. 函数介绍

dup3 是 Linux 系统调用,是 dup2 的扩展版本。它用于将一个已存在的文件描述符复制到指定的目标文件描述符,类似于 dup2,但提供了额外的标志参数来控制复制行为。

这个函数的主要优势是可以设置文件描述符标志,最常用的是 O_CLOEXEC 标志,该标志使得复制的文件描述符在执行 exec 系列函数时自动关闭,避免了文件描述符泄漏到新程序中。

2. 函数原型

1
2
3
4
5
6
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>

int dup3(int oldfd, int newfd, int flags);

3. 功能

  • 将文件描述符 oldfd 复制到指定的文件描述符 newfd

  • 如果 newfd 已经打开,会先将其关闭

  • 可以设置额外的文件描述符标志

  • 如果 oldfd 等于 newfd,则返回错误(与 dup2 不同)

4. 参数

  • int oldfd: 要被复制的原始文件描述符

  • int newfd: 目标文件描述符编号

int flags: 控制标志,可以是以下值的按位或组合:

  • O_CLOEXEC: 设置执行时关闭标志(FD_CLOEXEC)

  • 0: 不设置任何特殊标志(等同于 dup2 的行为)

5. 返回值

  • 成功时: 返回 newfd

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

  • EBADF: oldfd 或 newfd 不是有效的文件描述符

  • EINVAL: flags 参数无效,或 oldfd 等于 newfd

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

6. 相似函数

  • dup(): 复制文件描述符到最小可用编号

  • dup2(): 复制文件描述符到指定编号(不支持标志)

  • fcntl(): 更通用的文件描述符控制函数

7. 示例代码

示例 1:基本的 dup3 使用

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
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main() {
int fd1, fd2, fd3;

printf("=== Dup3 基本使用演示 ===\n");

// 1. 打开测试文件
fd1 = open("test_dup3.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd1 == -1) {
perror("打开文件失败");
exit(EXIT_FAILURE);
}
printf("打开文件获得描述符: %d\n", fd1);

// 2. 使用 dup3 复制文件描述符(无特殊标志)
fd2 = dup3(fd1, 10, 0);
if (fd2 == -1) {
perror("dup3 失败");
close(fd1);
exit(EXIT_FAILURE);
}
printf("使用 dup3(%d, 10, 0) 复制,获得描述符: %d\n", fd1, fd2);

// 3. 使用 dup3 复制并设置 O_CLOEXEC 标志
fd3 = dup3(fd1, 15, O_CLOEXEC);
if (fd3 == -1) {
perror("dup3 带 O_CLOEXEC 失败");
close(fd1);
close(fd2);
exit(EXIT_FAILURE);
}
printf("使用 dup3(%d, 15, O_CLOEXEC) 复制,获得描述符: %d\n", fd1, fd3);

// 4. 验证 O_CLOEXEC 标志是否设置
int flags = fcntl(fd3, F_GETFD);
if (flags != -1) {
if (flags & FD_CLOEXEC) {
printf("描述符 %d 已设置 FD_CLOEXEC 标志\n", fd3);
} else {
printf("描述符 %d 未设置 FD_CLOEXEC 标志\n", fd3);
}
}

// 5. 验证文件描述符共享性
const char *message1 = "通过 fd1 写入\n";
const char *message2 = "通过 fd2 写入\n";
const char *message3 = "通过 fd3 写入\n";

write(fd1, message1, strlen(message1));
write(fd2, message2, strlen(message2));
write(fd3, message3, strlen(message3));

// 6. 读取验证
lseek(fd1, 0, SEEK_SET);
char buffer&#91;256];
ssize_t bytes_read = read(fd1, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("\n读取到的数据:\n%s", buffer);
}

// 7. 清理资源
close(fd1);
close(fd2);
close(fd3);
unlink("test_dup3.txt");

return 0;
}

示例 2:O_CLOEXEC 标志的重要性

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
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

void demonstrate_cloexec_flag() {
printf("=== O_CLOEXEC 标志演示 ===\n");

// 创建测试文件
int fd = open("cloexec_test.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
return;
}

write(fd, "测试数据", 8);

// 使用 dup3 设置 O_CLOEXEC 标志
int fd_with_cloexec = dup3(fd, 10, O_CLOEXEC);
if (fd_with_cloexec == -1) {
perror("dup3 设置 O_CLOEXEC 失败");
close(fd);
return;
}

// 使用 dup2 不设置 O_CLOEXEC 标志
int fd_without_cloexec = dup2(fd, 15);
if (fd_without_cloexec == -1) {
perror("dup2 失败");
close(fd);
close(fd_with_cloexec);
return;
}

printf("创建了两个描述符:\n");
printf(" %d: 带 O_CLOEXEC 标志\n", fd_with_cloexec);
printf(" %d: 不带 O_CLOEXEC 标志\n", fd_without_cloexec);

// 创建子进程执行新程序
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程 PID: %d\n", getpid());

// 检查文件描述符是否仍然打开
if (fcntl(fd_with_cloexec, F_GETFD) == -1) {
printf("带 O_CLOEXEC 的描述符 %d 已自动关闭\n", fd_with_cloexec);
} else {
printf("带 O_CLOEXEC 的描述符 %d 仍然打开\n", fd_with_cloexec);
}

if (fcntl(fd_without_cloexec, F_GETFD) == -1) {
printf("不带 O_CLOEXEC 的描述符 %d 已关闭\n", fd_without_cloexec);
} else {
printf("不带 O_CLOEXEC 的描述符 %d 仍然打开\n", fd_without_cloexec);
}

// 执行新程序(这里用 ls 作为示例)
execl("/bin/ls", "ls", "-l", "cloexec_test.txt", NULL);
perror("execl 失败");
exit(EXIT_FAILURE);
} else if (pid > 0) {
// 父进程
wait(NULL);
printf("父进程继续执行\n");
} else {
perror("fork 失败");
}

// 清理
close(fd);
close(fd_with_cloexec);
close(fd_without_cloexec);
unlink("cloexec_test.txt");
}

int main() {
printf("Dup3 函数演示\n\n");

// 基本使用演示
system("gcc -o basic_dup3 basic_dup3.c");
system("./basic_dup3");

printf("\n");

// O_CLOEXEC 标志演示
demonstrate_cloexec_flag();

printf("\n=== 总结 ===\n");
printf("dup3 的优势:\n");
printf("1. 支持设置 O_CLOEXEC 标志,防止文件描述符泄漏\n");
printf("2. 原子性操作,避免了 dup2 + fcntl 的竞态条件\n");
printf("3. 当 oldfd == newfd 时返回错误,行为更明确\n\n");

printf("使用建议:\n");
printf("- 优先使用 dup3 而不是 dup2\n");
printf("- 在可能执行 exec 的场景中使用 O_CLOEXEC\n");
printf("- 注意检查返回值和错误处理\n");

return 0;
}

示例 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
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

void demonstrate_dup3_errors() {
printf("=== Dup3 错误处理演示 ===\n");

// 1. 无效的文件描述符
printf("1. 使用无效的文件描述符:\n");
int result = dup3(999, 10, 0);
if (result == -1) {
printf(" 错误: %s\n", strerror(errno));
if (errno == EBADF) {
printf(" 说明: 文件描述符 999 无效\n");
}
}

// 2. 无效的标志
printf("\n2. 使用无效的标志:\n");
int fd = open("/dev/null", O_RDWR);
if (fd != -1) {
result = dup3(fd, 10, 0x1000); // 无效标志
if (result == -1) {
printf(" 错误: %s\n", strerror(errno));
if (errno == EINVAL) {
printf(" 说明: 标志参数无效\n");
}
}
close(fd);
}

// 3. oldfd 等于 newfd
printf("\n3. oldfd 等于 newfd:\n");
fd = open("/dev/null", O_RDWR);
if (fd != -1) {
result = dup3(fd, fd, 0);
if (result == -1) {
printf(" 错误: %s\n", strerror(errno));
if (errno == EINVAL) {
printf(" 说明: dup3 不允许 oldfd 等于 newfd\n");
printf(" 对比: dup2 在这种情况下会返回 newfd\n");
}
} else {
printf(" 意外成功: %d\n", result);
}
close(fd);
}

// 4. 文件描述符数量达到上限
printf("\n4. 文件描述符数量达到上限的模拟:\n");
printf(" 这种情况很难模拟,但会返回 EMFILE 错误\n");
}

int main() {
demonstrate_dup3_errors();

printf("\n=== Dup 系列函数对比 ===\n");
printf("函数 | 目标描述符 | 支持标志 | oldfd==newfd 行为\n");
printf("---------|------------|----------|------------------\n");
printf("dup | 自动分配 | 否 | N/A\n");
printf("dup2 | 指定 | 否 | 返回 newfd\n");
printf("dup3 | 指定 | 是 | 返回错误\n");

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
# 编译示例
gcc -o dup3_basic dup3_basic.c
gcc -o dup3_cloexec dup3_cloexec.c
gcc -o dup3_errors dup3_errors.c

# 运行示例
./dup3_basic
./dup3_cloexec
./dup3_errors

重要注意事项

Linux 特定: dup3 是 Linux 特定的系统调用,在其他 Unix 系统上可能不可用

标志支持: 主要优势是支持 O_CLOEXEC 标志,提高程序安全性

原子操作: dup3 是原子操作,避免了 dup2 + fcntl 组合可能的竞态条件

错误处理: 当 oldfd 等于 newfd 时,dup3 返回错误,而 dup2 返回 newfd

兼容性: 如果需要跨平台兼容性,应该使用 dup2 或 fcntl

epoll_create系统调用及示例

epoll_create - 创建epoll实例

函数介绍

epoll_create系统调用用于创建一个epoll实例,返回一个文件描述符,用于后续的epoll操作。epoll是Linux特有的I/O多路复用机制,比传统的select和poll更高效。

函数原型

1
2
3
4
5
6
#include <sys/epoll.h>
#include <sys/syscall.h>
#include <unistd.h>

int epoll_create(int size);

功能

创建一个新的epoll实例,用于监控多个文件描述符的I/O事件。

参数

  • int size: 建议的内核为该epoll实例分配的事件数(Linux 2.6.8后被忽略)

返回值

  • 成功时返回epoll文件描述符(非负整数)

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

特殊限制

  • 需要Linux 2.5.44以上内核支持

  • 每个进程可创建的epoll实例数量受系统限制

相似函数

  • epoll_create1(): 现代版本,支持标志位

  • poll(): 传统的轮询机制

  • select(): 传统的多路复用机制

示例代码

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

// 系统调用包装
static int epoll_create_wrapper(int size) {
return syscall(__NR_epoll_create, size);
}

int main() {
int epfd;

printf("=== Epoll_create 函数示例 ===\n");

// 示例1: 基本的epoll实例创建
printf("\n示例1: 基本的epoll实例创建\n");

epfd = epoll_create_wrapper(10); // size参数在现代内核中被忽略
if (epfd == -1) {
perror("epoll_create 失败");
exit(EXIT_FAILURE);
}

printf("成功创建epoll实例,文件描述符: %d\n", epfd);

// 检查epoll文件描述符是否有效
if (fcntl(epfd, F_GETFD) != -1) {
printf("epoll文件描述符验证成功\n");
}

// 关闭epoll实例
if (close(epfd) == -1) {
perror("关闭epoll实例失败");
} else {
printf("成功关闭epoll实例\n");
}

// 示例2: 多个epoll实例创建
printf("\n示例2: 多个epoll实例创建\n");

int epfds&#91;5];
int created_count = 0;

for (int i = 0; i < 5; i++) {
epfds&#91;i] = epoll_create_wrapper(1);
if (epfds&#91;i] != -1) {
printf("创建第%d个epoll实例: %d\n", i+1, epfds&#91;i]);
created_count++;
} else {
printf("创建第%d个epoll实例失败: %s\n", i+1, strerror(errno));
break;
}
}

// 关闭所有创建的epoll实例
for (int i = 0; i < created_count; i++) {
if (close(epfds&#91;i]) == -1) {
printf("关闭epoll实例%d失败: %s\n", epfds&#91;i], strerror(errno));
} else {
printf("关闭epoll实例%d成功\n", epfds&#91;i]);
}
}

// 示例3: 错误处理演示
printf("\n示例3: 错误处理演示\n");

// 使用负数作为size参数
epfd = epoll_create_wrapper(-1);
if (epfd == -1) {
if (errno == EINVAL) {
printf("负数size参数错误处理正确: %s\n", strerror(errno));
}
}

// 检查系统资源限制
printf("\n系统epoll相关限制:\n");
FILE *fp = fopen("/proc/sys/fs/epoll/max_user_watches", "r");
if (fp != NULL) {
char line&#91;256];
if (fgets(line, sizeof(line), fp)) {
printf("最大用户监视数量: %s", line);
}
fclose(fp);
}

// 示例4: epoll vs select/poll对比说明
printf("\n示例4: epoll优势说明\n");
printf("epoll相比select/poll的优势:\n");
printf("1. 文件描述符数量无限制(受系统资源限制)\n");
printf("2. O(1)时间复杂度的事件通知\n");
printf("3. 内存使用更高效\n");
printf("4. 支持边缘触发和水平触发模式\n");
printf("5. 更好的可扩展性\n\n");

// 示例5: 实际使用场景
printf("示例5: 实际使用场景\n");
printf("epoll的典型应用场景:\n");
printf("1. 高并发网络服务器\n");
printf("2. 实时数据处理系统\n");
printf("3. 聊天服务器和即时通讯\n");
printf("4. 代理服务器和负载均衡\n");
printf("5. 监控和日志收集系统\n\n");

// 示例6: 性能考虑
printf("示例6: 性能考虑\n");
printf("epoll性能优化建议:\n");
printf("1. 合理设置epoll_wait的maxevents参数\n");
printf("2. 避免频繁添加/删除监视的文件描述符\n");
printf("3. 使用边缘触发模式提高效率\n");
printf("4. 批量处理事件\n");
printf("5. 及时关闭不需要的epoll实例\n\n");

printf("总结:\n");
printf("epoll_create是创建epoll实例的基础函数\n");
printf("虽然现代推荐使用epoll_create1,但epoll_create仍然广泛支持\n");
printf("返回的文件描述符需要妥善管理\n");
printf("epoll是Linux高性能网络编程的重要工具\n");

return 0;
}

https://www.calcguide.tech/2025/08/09/epoll-create系统调用及示例/

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

epoll_pwait系统调用及示例

epoll_pwait - 带信号掩码的epoll等待

函数介绍

epoll_pwait是epoll_wait的扩展版本,支持在等待期间设置临时的信号掩码,提供了更精确的信号控制能力。

函数原型

1
2
3
4
5
6
7
#include <sys/epoll.h>
#include <signal.h>

int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);

功能

等待epoll事件,同时可以指定在等待期间临时应用的信号掩码。

参数

  • int epfd: epoll实例的文件描述符

  • struct epoll_event *events: 用于存储就绪事件的数组

  • int maxevents: events数组的最大元素数

  • int timeout: 超时时间(毫秒)

  • const sigset_t *sigmask: 临时信号掩码(NULL表示不改变)

返回值

  • 成功时返回就绪事件的数量

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

特殊限制

  • 需要Linux 2.6.19以上内核支持

  • 原子性地设置信号掩码和等待事件

相似函数

  • epoll_wait(): 基础版本

  • pselect(): 带信号掩码的select版本

  • ppoll(): 带信号掩码的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
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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>

int main() {
int epfd, sockfd, nfds;
struct epoll_event ev, events&#91;10];
sigset_t mask, orig_mask;

printf("=== Epoll_pwait 函数示例 ===\n");

// 创建epoll实例
epfd = epoll_create1(EPOLL_CLOEXEC);
if (epfd == -1) {
perror("epoll_create1 失败");
exit(EXIT_FAILURE);
}
printf("创建epoll实例: %d\n", epfd);

// 创建测试用的socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("创建socket失败");
close(epfd);
exit(EXIT_FAILURE);
}
printf("创建测试socket: %d\n", sockfd);

// 设置socket为非阻塞模式
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

// 添加socket到epoll监视集合
ev.events = EPOLLIN | EPOLLOUT;
ev.data.fd = sockfd;

if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
perror("epoll_ctl 添加失败");
close(sockfd);
close(epfd);
exit(EXIT_FAILURE);
}
printf("添加socket到epoll监视集合\n");

// 示例1: 基本使用(不改变信号掩码)
printf("\n示例1: 基本使用(不改变信号掩码)\n");

nfds = epoll_pwait(epfd, events, 10, 1000, NULL);
if (nfds == -1) {
if (errno == EINTR) {
printf("等待被信号中断\n");
} else {
perror("epoll_pwait 失败");
}
} else {
printf("等待完成,就绪事件数: %d\n", nfds);
}

// 示例2: 设置临时信号掩码
printf("\n示例2: 设置临时信号掩码\n");

// 初始化信号掩码
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);

printf("设置临时阻塞SIGUSR1和SIGUSR2信号\n");

nfds = epoll_pwait(epfd, events, 10, 2000, &mask);
if (nfds == -1) {
if (errno == EINTR) {
printf("等待被未阻塞的信号中断\n");
} else {
perror("epoll_pwait 失败");
}
} else {
printf("等待完成,就绪事件数: %d\n", nfds);
}

// 示例3: 与普通epoll_wait对比
printf("\n示例3: 与epoll_wait对比\n");

printf("epoll_wait vs epoll_pwait的区别:\n");
printf("epoll_wait:\n");
printf(" - 使用当前进程的信号掩码\n");
printf(" - 无法在调用中临时改变信号掩码\n\n");

printf("epoll_pwait:\n");
printf(" - 可以指定临时信号掩码\n");
printf(" - 原子性地设置掩码和等待\n");
printf(" - 避免竞态条件\n\n");

// 示例4: 原子性操作演示
printf("示例4: 原子性操作演示\n");

printf("说明epoll_pwait的原子性优势:\n");
printf("传统方式(非原子性):\n");
printf(" 1. sigprocmask(SIG_BLOCK, &mask, &orig_mask);\n");
printf(" 2. epoll_wait(epfd, events, 10, -1);\n");
printf(" 3. sigprocmask(SIG_SETMASK, &orig_mask, NULL);\n");
printf(" 问题: 在步骤1和2之间可能收到信号\n\n");

printf("epoll_pwait方式(原子性):\n");
printf(" epoll_pwait(epfd, events, 10, -1, &mask);\n");
printf(" 优势: 设置掩码和等待是原子操作\n\n");

// 示例5: 错误处理演示
printf("示例5: 错误处理演示\n");

// 使用无效的信号掩码
nfds = epoll_pwait(epfd, events, 10, 1000, (sigset_t*)-1);
if (nfds == -1) {
if (errno == EINVAL) {
printf("无效信号掩码错误处理正确: %s\n", strerror(errno));
}
}

// 其他错误与epoll_wait相同
nfds = epoll_pwait(-1, events, 10, 1000, &mask);
if (nfds == -1) {
if (errno == EBADF) {
printf("无效epoll文件描述符错误处理正确\n");
}
}

// 示例6: 实际应用场景
printf("\n示例6: 实际应用场景\n");

printf("多线程服务器中的信号处理:\n");
printf("场景: 主线程处理网络事件,信号处理线程处理信号\n");
printf("要求: 主线程在epoll_wait期间不被某些信号中断\n\n");

printf("实现方案:\n");
printf("sigset_t mask;\n");
printf("sigemptyset(&mask);\n");
printf("sigaddset(&mask, SIGUSR1); // 阻塞用户信号\n");
printf("sigaddset(&mask, SIGALRM); // 阻塞定时器信号\n");
printf("epoll_pwait(epfd, events, MAX_EVENTS, -1, &mask);\n\n");

// 示例7: 信号掩码操作演示
printf("示例7: 信号掩码操作演示\n");

// 创建复杂的信号掩码
sigset_t complex_mask;
sigemptyset(&complex_mask);

// 添加多个信号到掩码
int signals&#91;] = {SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGALRM};
const char *signal_names&#91;] = {"SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2", "SIGALRM"};

printf("创建包含以下信号的掩码:\n");
for (int i = 0; i < 5; i++) {
sigaddset(&complex_mask, signals&#91;i]);
printf(" %s\n", signal_names&#91;i]);
}

printf("使用复杂信号掩码进行epoll_pwait\n");
nfds = epoll_pwait(epfd, events, 10, 1000, &complex_mask);
if (nfds != -1) {
printf("epoll_pwait成功完成\n");
}

// 示例8: 线程安全考虑
printf("\n示例8: 线程安全考虑\n");

printf("在多线程环境中的使用:\n");
printf("1. 每个线程可以有自己的epoll实例\n");
printf("2. 每个线程可以设置不同的信号掩码\n");
printf("3. 避免线程间的信号处理冲突\n");
printf("4. 提高信号处理的精确性\n\n");

printf("线程特定的信号掩码设置:\n");
printf("线程1: 阻塞SIGUSR1, SIGUSR2\n");
printf("线程2: 阻塞SIGALRM, SIGVTALRM\n");
printf("线程3: 不阻塞任何信号\n\n");

// 示例9: 性能和安全性
printf("示例9: 性能和安全性\n");

printf("epoll_pwait的优势:\n");
printf("1. 原子性操作,避免竞态条件\n");
printf("2. 更精确的信号控制\n");
printf("3. 提高程序的可靠性\n");
printf("4. 简化信号处理逻辑\n\n");

printf("使用建议:\n");
printf("1. 在需要精确信号控制时使用epoll_pwait\n");
printf("2. 合理设计信号掩码\n");
printf("3. 注意信号处理的线程安全性\n");
printf("4. 在多线程环境中谨慎使用\n\n");

// 清理资源
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
close(sockfd);
close(epfd);

printf("总结:\n");
printf("epoll_pwait是epoll_wait的增强版本\n");
printf("支持临时信号掩码设置\n");
printf("提供原子性的信号控制\n");
printf("适用于需要精确信号处理的场景\n");
printf("在现代Linux系统中推荐使用\n");

return 0;
}

https://www.calcguide.tech/2025/08/09/epoll-pwait系统调用及示例

epoll_wait系统调用及示例

epoll_wait - 等待epoll事件

函数介绍

epoll_wait系统调用用于等待epoll实例中的文件描述符就绪事件。它是epoll机制的核心函数,用于高效地等待多个文件描述符的I/O事件。

函数原型

1
2
3
4
5
#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);

功能

等待epoll实例中的事件就绪,返回就绪的事件数组。

参数

  • int epfd: epoll实例的文件描述符

  • struct epoll_event *events: 用于存储就绪事件的数组

  • int maxevents: events数组的最大元素数(必须大于0)

int timeout: 超时时间(毫秒)

  • -1: 永久等待

  • 0: 立即返回(轮询)

  • 0: 等待指定毫秒数

返回值

  • 成功时返回就绪事件的数量(0表示超时)

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

特殊限制

  • maxevents必须大于0

  • 需要有效的epoll文件描述符

  • 可能被信号中断

相似函数

  • epoll_pwait(): 支持信号掩码的版本

  • poll(): 传统轮询等待

  • select(): 传统多路复用等待

示例代码

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>

// 信号处理标志
volatile sig_atomic_t signal_received = 0;

void signal_handler(int sig) {
signal_received = 1;
printf("接收到信号 %d\n", sig);
}

int main() {
int epfd, sockfd, nfds;
struct epoll_event ev, events&#91;10];

printf("=== Epoll_wait 函数示例 ===\n");

// 设置信号处理
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);

// 创建epoll实例
epfd = epoll_create1(EPOLL_CLOEXEC);
if (epfd == -1) {
perror("epoll_create1 失败");
exit(EXIT_FAILURE);
}
printf("创建epoll实例: %d\n", epfd);

// 创建测试用的socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("创建socket失败");
close(epfd);
exit(EXIT_FAILURE);
}
printf("创建测试socket: %d\n", sockfd);

// 设置socket为非阻塞模式
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

// 添加socket到epoll监视集合
ev.events = EPOLLIN | EPOLLOUT;
ev.data.fd = sockfd;

if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
perror("epoll_ctl 添加失败");
close(sockfd);
close(epfd);
exit(EXIT_FAILURE);
}
printf("添加socket到epoll监视集合\n");

// 示例1: 带超时的等待
printf("\n示例1: 带超时的等待\n");

printf("等待1秒...\n");
nfds = epoll_wait(epfd, events, 10, 1000); // 1秒超时

if (nfds == -1) {
if (errno == EINTR) {
printf("等待被信号中断\n");
} else {
perror("epoll_wait 失败");
}
} else if (nfds == 0) {
printf("等待超时(1秒内无事件)\n");
} else {
printf("就绪事件数量: %d\n", nfds);
for (int i = 0; i < nfds; i++) {
printf(" 事件 %d: fd=%d, events=0x%x\n",
i, events&#91;i].data.fd, events&#91;i].events);
}
}

// 示例2: 立即返回的轮询
printf("\n示例2: 立即返回的轮询\n");

nfds = epoll_wait(epfd, events, 10, 0); // 立即返回

if (nfds == -1) {
perror("epoll_wait 轮询失败");
} else {
printf("轮询结果: %d 个事件就绪\n", nfds);
}

// 示例3: 永久等待(演示信号中断)
printf("\n示例3: 永久等待(演示信号中断)\n");
printf("请在5秒内按Ctrl+C发送SIGINT信号\n");

alarm(5); // 5秒后发送SIGALRM
nfds = epoll_wait(epfd, events, 10, -1); // 永久等待

if (nfds == -1) {
if (errno == EINTR) {
printf("永久等待被信号中断\n");
} else {
perror("epoll_wait 失败");
}
} else {
printf("永久等待返回: %d 个事件\n", nfds);
}
alarm(0); // 取消alarm

// 示例4: 错误处理演示
printf("\n示例4: 错误处理演示\n");

// 使用无效的epoll文件描述符
nfds = epoll_wait(-1, events, 10, 1000);
if (nfds == -1) {
if (errno == EBADF) {
printf("无效epoll文件描述符错误处理正确: %s\n", strerror(errno));
}
}

// 使用无效的maxevents
nfds = epoll_wait(epfd, events, 0, 1000);
if (nfds == -1) {
if (errno == EINVAL) {
printf("无效maxevents错误处理正确: %s\n", strerror(errno));
}
}

// 使用NULL事件数组
nfds = epoll_wait(epfd, NULL, 10, 1000);
if (nfds == -1) {
if (errno == EFAULT) {
printf("NULL事件数组错误处理正确: %s\n", strerror(errno));
}
}

// 示例5: 事件处理演示
printf("\n示例5: 事件处理演示\n");

// 模拟不同类型的事件
printf("常见事件类型处理:\n");

for (int i = 0; i < 10; i++) {
events&#91;i].events = 0;
events&#91;i].data.fd = i;
}

// 设置一些模拟事件
events&#91;0].events = EPOLLIN;
events&#91;1].events = EPOLLOUT;
events&#91;2].events = EPOLLIN | EPOLLOUT;
events&#91;3].events = EPOLLERR;
events&#91;4].events = EPOLLHUP;

printf("模拟事件处理:\n");
for (int i = 0; i < 5; i++) {
printf(" fd %d: ", events&#91;i].data.fd);
if (events&#91;i].events & EPOLLIN) printf("可读 ");
if (events&#91;i].events & EPOLLOUT) printf("可写 ");
if (events&#91;i].events & EPOLLERR) printf("错误 ");
if (events&#91;i].events & EPOLLHUP) printf("挂起 ");
printf("\n");
}

// 示例6: 性能考虑
printf("\n示例6: 性能考虑\n");

printf("epoll_wait性能优化建议:\n");
printf("1. 合理设置maxevents参数\n");
printf(" - 不要过大浪费内存\n");
printf(" - 不要过小频繁调用\n");
printf("2. 使用适当的超时时间\n");
printf(" - 根据应用需求选择\n");
printf("3. 批量处理就绪事件\n");
printf(" - 减少系统调用次数\n");
printf("4. 避免在事件处理中阻塞\n");
printf(" - 保持事件循环响应性\n\n");

// 示例7: 实际服务器循环演示
printf("示例7: 实际服务器循环演示\n");

printf("典型的服务器事件循环:\n");
printf("while (running) {\n");
printf(" int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);\n");
printf(" if (nfds == -1) {\n");
printf(" if (errno == EINTR) continue; // 被信号中断\n");
printf(" else break; // 真正的错误\n");
printf(" }\n");
printf(" \n");
printf(" for (int i = 0; i < nfds; i++) {\n");
printf(" if (events&#91;i].data.fd == listen_sock) {\n");
printf(" // 处理新连接\n");
printf(" } else {\n");
printf(" // 处理已连接socket的数据\n");
printf(" }\n");
printf(" }\n");
printf("}\n\n");

// 示例8: 超时时间说明
printf("示例8: 超时时间说明\n");
printf("timeout参数说明:\n");
printf(" -1: 永久等待,直到有事件或被信号中断\n");
printf(" 0: 立即返回,不阻塞(轮询模式)\n");
printf(" >0: 等待指定毫秒数\n\n");

printf("超时时间选择建议:\n");
printf("实时应用: 较小的超时值(1-100ms)\n");
printf("批处理应用: 较大的超时值(1000ms以上)\n");
printf("交互应用: 中等超时值(100-500ms)\n\n");

// 清理资源
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
close(sockfd);
close(epfd);

printf("总结:\n");
printf("epoll_wait是epoll机制的核心等待函数\n");
printf("支持灵活的超时控制\n");
printf("正确处理信号中断很重要\n");
printf("合理的maxevents和timeout设置影响性能\n");
printf("是构建高性能网络服务器的基础\n");

return 0;
}

epoll_create1系统调用及示例

epoll_create1 - 创建epoll实例(扩展版)

函数介绍

epoll_create1是epoll_create的扩展版本,支持额外的标志位参数,提供了更多的控制选项。

函数原型

1
2
3
4
#include <sys/epoll.h>

int epoll_create1(int flags);

功能

创建一个新的epoll实例,支持额外的标志位控制。

参数

int flags: 控制标志

  • 0: 与epoll_create(size)相同

  • EPOLL_CLOEXEC: 设置文件描述符在exec时自动关闭

返回值

  • 成功时返回epoll文件描述符

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

特殊限制

  • 需要Linux 2.6.27以上内核支持

相似函数

  • epoll_create(): 基础版本

  • poll(), select(): 传统多路复用函数

示例代码

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main() {
int epfd1, epfd2;

printf("=== Epoll_create1 函数示例 ===\n");

// 示例1: 基本使用
printf("\n示例1: 基本使用\n");

epfd1 = epoll_create1(0);
if (epfd1 == -1) {
perror("epoll_create1(0) 失败");
} else {
printf("成功创建epoll实例(无标志): %d\n", epfd1);
close(epfd1);
}

// 示例2: 使用EPOLL_CLOEXEC标志
printf("\n示例2: 使用EPOLL_CLOEXEC标志\n");

epfd2 = epoll_create1(EPOLL_CLOEXEC);
if (epfd2 == -1) {
perror("epoll_create1(EPOLL_CLOEXEC) 失败");
} else {
printf("成功创建epoll实例(带CLOEXEC): %d\n", epfd2);

// 验证CLOEXEC标志是否设置
int flags = fcntl(epfd2, F_GETFD);
if (flags != -1) {
if (flags & FD_CLOEXEC) {
printf("FD_CLOEXEC标志已正确设置\n");
} else {
printf("FD_CLOEXEC标志未设置\n");
}
}

close(epfd2);
}

// 示例3: 错误处理
printf("\n示例3: 错误处理\n");

int invalid_fd = epoll_create1(-1);
if (invalid_fd == -1) {
if (errno == EINVAL) {
printf("无效标志错误处理正确: %s\n", strerror(errno));
}
}

// 示例4: 与epoll_create对比
printf("\n示例4: 与epoll_create对比\n");

int fd1 = epoll_create(10);
int fd2 = epoll_create1(0);

if (fd1 != -1 && fd2 != -1) {
printf("epoll_create返回: %d\n", fd1);
printf("epoll_create1(0)返回: %d\n", fd2);
printf("两者功能基本相同\n");

close(fd1);
close(fd2);
}

// 示例5: 实际应用演示
printf("\n示例5: 实际应用演示\n");

// 推荐的现代用法
int epfd = epoll_create1(EPOLL_CLOEXEC);
if (epfd != -1) {
printf("推荐用法: epoll_create1(EPOLL_CLOEXEC) = %d\n", epfd);
printf("优点: 避免文件描述符泄漏到子进程\n");
close(epfd);
}

printf("\nEPOLL_CLOEXEC的优势:\n");
printf("1. 原子性设置标志,避免竞态条件\n");
printf("2. 防止文件描述符泄漏到exec的程序\n");
printf("3. 提高程序安全性\n");
printf("4. 减少系统调用次数\n\n");

printf("总结:\n");
printf("epoll_create1是现代Linux编程推荐的epoll创建函数\n");
printf("EPOLL_CLOEXEC标志提供了更好的安全性\n");
printf("在支持的系统上应优先使用epoll_create1\n");

return 0;
}