setrlimit系统调用及示例

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

1. 函数介绍

在 Linux 系统中,为了保证系统的稳定性和公平性,防止某个程序因为 bug 或恶意行为而耗尽系统资源(如内存、CPU 时间、打开的文件数量等),内核提供了一种资源限制 (Resource Limits) 机制。

setrlimit (Set Resource Limit) 系统调用的作用就是设置调用进程(及其未来创建的子进程)对某一类系统资源的使用上限。

你可以把它想象成你给一个程序分配一个“资源使用预算”或“配额”。比如,你可以告诉内核:“这个程序最多只能使用 100MB 的内存”、“最多只能打开 10 个文件”、“最多只能运行 10 秒钟”等等。当程序试图超出这个限制时,内核会根据资源类型采取不同措施,通常是拒绝其请求(例如 malloc 失败)或发送一个信号(例如 SIGXCPU)来终止它。

简单来说,setrlimit 就是让你用程序来给另一个程序(或自己)“上规矩”,限制它能用多少系统资源。

2. 函数原型

1
2
3
4
#include <sys/resource.h> // 包含系统调用声明和常量

int setrlimit(int resource, const struct rlimit *rlim);

3. 功能

为调用进程设置指定资源 resource 的软限制 (soft limit) 和硬限制 (hard limit)。

4. 参数

resource:

  • int 类型。

指定要设置限制的资源类型。常见的资源类型定义在 <sys/resource.h> 中,例如:

  • RLIMIT_AS: 进程虚拟地址空间的最大总大小(字节)。限制进程能分配的总内存。

  • RLIMIT_CORE: 程序崩溃时创建的核心转储文件 (core dump) 的最大字节数。设置为 0 可以禁用 core dump。

  • RLIMIT_CPU: 进程可以使用的 CPU 时间(秒)。达到软限制会收到 SIGXCPU 信号,达到硬限制会被 SIGKILL 终止。

  • RLIMIT_DATA: 进程数据段的最大字节大小(通过 brk/sbrk 分配的内存)。

  • RLIMIT_FSIZE: 进程可以创建的文件的最大字节大小。超出限制时写操作会失败,并可能收到 SIGXFSZ 信号。

  • RLIMIT_NOFILE: 进程可以同时打开的文件描述符(File Descriptor)的最大数量。

  • RLIMIT_NPROC: 调用用户 ID (Real User ID) 可以拥有的最大进程/线程数量。

  • RLIMIT_STACK: 进程栈的最大字节大小。

  • RLIMIT_MEMLOCK: 可以使用 mlock 锁定在内存中的最大字节数。

  • RLIMIT_RSS: 进程在物理内存中驻留的最大字节数(Resident Set Size)。(在 Linux 上可能不强制执行)。

  • RLIMIT_NICE: nice 值的上限(影响进程调度优先级)。

  • … 还有其他一些资源类型。

rlim:

  • const struct rlimit * 类型。

一个指向 rlimit 结构体的指针,该结构体定义了资源的限制。rlimit 结构体定义如下:struct rlimit { rlim_t rlim_cur; // Soft limit (软限制) rlim_t rlim_max; // Hard limit (硬限制) };

rlim_cur (软限制):

  • 这是内核实际执行强制限制的值。

  • 进程可以随时将其修改为小于或等于当前硬限制 (rlim_max) 的任何值。

  • 超过软限制通常会导致内核发送一个信号(如 SIGXCPU)来警告进程。

rlim_max (硬限制):

  • 这是软限制可以被设置的上限。

  • 普通用户只能降低硬限制,不能提高它。

  • 只有特权用户 (root) 才能提高硬限制。

  • 进程可以在任何时候将硬限制降低到等于或低于当前硬限制的值。

5. 返回值

  • 成功: 返回 0。

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

6. 错误码 (errno)

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

  • EINVAL: resource 参数无效,或者指定的限制值无效(例如,负数,或者对于某些资源类型不合适)。

  • EPERM: 调用进程没有权限设置指定的限制。最常见的原因是普通用户试图提高硬限制 (rlim_max)。

7. 相似函数或关联函数

  • getrlimit: 用于获取当前进程对某类资源的限制设置。

  • prlimit: 一个更现代的系统调用,可以同时设置和获取任意进程的资源限制(需要 CAP_SYS_RESOURCE 能力)。

  • ulimit: 命令行工具(在 shell 中),用于设置当前 shell 及其子进程的资源限制。它在底层调用 setrlimit 和 getrlimit。

  • struct rlimit: 定义限制值的数据结构。

8. 示例代码

下面的示例演示了如何使用 setrlimit 来设置几种常见的资源限制。

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
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h> // 包含 setrlimit, getrlimit, struct rlimit
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h> // 包含 timeval, 用于 CPU 时间限制
#include <fcntl.h> // 包含 open

// 信号处理函数,用于捕获因资源限制而产生的信号
void signal_handler(int sig) {
printf("\nCaught signal %d\n", sig);
if (sig == SIGXCPU) {
printf("CPU time limit (soft) reached. Exiting gracefully.\n");
exit(EXIT_FAILURE);
} else if (sig == SIGXFSZ) {
printf("File size limit reached. Write operation failed.\n");
// 可以选择继续运行或退出
}
}

// 打印特定资源的当前限制
void print_resource_limit(const char* resource_name, int resource) {
struct rlimit rl;
if (getrlimit(resource, &rl) == 0) {
printf("%-15s: Soft = ", resource_name);
if (rl.rlim_cur == RLIM_INFINITY) printf("unlimited");
else printf("%ld", (long)rl.rlim_cur);

printf(", Hard = ");
if (rl.rlim_max == RLIM_INFINITY) printf("unlimited");
else printf("%ld", (long)rl.rlim_max);
printf("\n");
} else {
perror("getrlimit");
}
}

int main() {
struct rlimit rl;
struct sigaction sa;

printf("--- Demonstrating setrlimit ---\n");
printf("PID: %d\n", getpid());

// 1. 显示初始的一些资源限制
printf("\n--- Initial Resource Limits ---\n");
print_resource_limit("CPU Time", RLIMIT_CPU);
print_resource_limit("File Size", RLIMIT_FSIZE);
print_resource_limit("Data Segment", RLIMIT_DATA);
print_resource_limit("Stack Size", RLIMIT_STACK);
print_resource_limit("Virtual Memory", RLIMIT_AS);
print_resource_limit("Open Files", RLIMIT_NOFILE);
print_resource_limit("Max Processes", RLIMIT_NPROC);
print_resource_limit("Core File Size", RLIMIT_CORE);

// 2. 设置 CPU 时间限制
printf("\n--- Setting CPU Time Limit ---\n");
rl.rlim_cur = 5; // 软限制:5 秒
rl.rlim_max = 10; // 硬限制:10 秒
if (setrlimit(RLIMIT_CPU, &rl) == 0) {
printf("Set CPU time limit: Soft = %lds, Hard = %lds\n", (long)rl.rlim_cur, (long)rl.rlim_max);
// 设置信号处理函数以捕获 SIGXCPU
memset(&sa, 0, sizeof(sa));
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGXCPU, &sa, NULL) == -1) {
perror("sigaction SIGXCPU");
}
} else {
perror("setrlimit RLIMIT_CPU");
}

// 3. 设置最大打开文件数限制
printf("\n--- Setting Open File Descriptor Limit ---\n");
rl.rlim_cur = 10; // 软限制:最多 10 个文件描述符
rl.rlim_max = 20; // 硬限制:最多 20 个文件描述符
if (setrlimit(RLIMIT_NOFILE, &rl) == 0) {
printf("Set open file limit: Soft = %ld, Hard = %ld\n", (long)rl.rlim_cur, (long)rl.rlim_max);
} else {
perror("setrlimit RLIMIT_NOFILE");
}

// 4. 设置最大文件大小限制
printf("\n--- Setting File Size Limit ---\n");
rl.rlim_cur = 1024 * 1024; // 软限制:1MB
rl.rlim_max = 2 * 1024 * 1024; // 硬限制:2MB
if (setrlimit(RLIMIT_FSIZE, &rl) == 0) {
printf("Set file size limit: Soft = %ld bytes, Hard = %ld bytes\n", (long)rl.rlim_cur, (long)rl.rlim_max);
// 设置信号处理函数以捕获 SIGXFSZ
sa.sa_handler = signal_handler;
if (sigaction(SIGXFSZ, &sa, NULL) == -1) {
perror("sigaction SIGXFSZ");
}
} else {
perror("setrlimit RLIMIT_FSIZE");
}

// 5. 设置虚拟内存限制 (RLIMIT_AS)
printf("\n--- Setting Virtual Memory Limit ---\n");
rl.rlim_cur = 50 * 1024 * 1024; // 软限制:50 MB
rl.rlim_max = 100 * 1024 * 1024; // 硬限制:100 MB
if (setrlimit(RLIMIT_AS, &rl) == 0) {
printf("Set virtual memory limit: Soft = %ld bytes (%.2f MB), Hard = %ld bytes (%.2f MB)\n",
(long)rl.rlim_cur, (double)rl.rlim_cur / (1024*1024),
(long)rl.rlim_max, (double)rl.rlim_max / (1024*1024));
} else {
perror("setrlimit RLIMIT_AS");
}

// 6. 设置 core dump 大小为 0,禁用它
printf("\n--- Disabling Core Dump ---\n");
rl.rlim_cur = 0;
rl.rlim_max = 0;
if (setrlimit(RLIMIT_CORE, &rl) == 0) {
printf("Disabled core dump generation.\n");
} else {
perror("setrlimit RLIMIT_CORE");
}

// 7. 验证设置后的限制
printf("\n--- Resource Limits After setrlimit ---\n");
print_resource_limit("CPU Time", RLIMIT_CPU);
print_resource_limit("File Size", RLIMIT_FSIZE);
print_resource_limit("Virtual Memory", RLIMIT_AS);
print_resource_limit("Open Files", RLIMIT_NOFILE);
print_resource_limit("Core File Size", RLIMIT_CORE);

// 8. 演示资源限制的效果

// --- 演示 RLIMIT_FSIZE ---
printf("\n--- Testing RLIMIT_FSIZE (File Size Limit) ---\n");
const char* test_filename = "test_limited_file.txt";
int fd = open(test_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("open test file");
} else {
char data&#91;1024];
memset(data, 'A', sizeof(data));
ssize_t written;
long total_written = 0;
// 尝试写入超过 1MB 的数据
while (total_written < 2 * 1024 * 1024) {
written = write(fd, data, sizeof(data));
if (written == -1) {
perror("write");
printf("Write failed after writing approximately %ld bytes. File size limit likely reached.\n", total_written);
break;
}
total_written += written;
}
close(fd);
printf("Finished writing (or failed) to file.\n");
// 清理测试文件
unlink(test_filename);
}

// --- 演示 RLIMIT_AS ---
printf("\n--- Testing RLIMIT_AS (Virtual Memory Limit) ---\n");
printf("Attempting to allocate large chunks of memory until limit is hit...\n");
size_t chunk_size = 10 * 1024 * 1024; // 10MB
long allocated_mb = 0;
char *ptr;
while (1) {
ptr = malloc(chunk_size);
if (ptr == NULL) {
printf("malloc failed after allocating approximately %ld MB. Memory limit likely reached.\n", allocated_mb);
break;
}
// Touch the memory to ensure it's actually allocated
memset(ptr, 0, chunk_size);
allocated_mb += chunk_size / (1024 * 1024);
printf("Allocated %ld MB so far...\n", allocated_mb);
// 添加一点延迟,方便观察
sleep(1);
}

// --- 演示 RLIMIT_CPU (放在最后,因为它会终止程序) ---
printf("\n--- Testing RLIMIT_CPU (CPU Time Limit) ---\n");
printf("Entering infinite loop. Should be killed by SIGKILL after 10 seconds (hard limit).\n");
printf("You might see 'CPU time limit (soft) reached' message first (after 5s), then termination.\n");
while(1) {
// 空循环,消耗 CPU 时间
}

// 程序通常不会执行到这里,因为 RLIMIT_CPU 会终止它
printf("Program finished normally (unexpected).\n");
return 0;
}

9. 编译和运行

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

# 运行程序
./setrlimit_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
--- Demonstrating setrlimit ---
PID: 12345

--- Initial Resource Limits ---
CPU Time : Soft = unlimited, Hard = unlimited
File Size : Soft = unlimited, Hard = unlimited
Data Segment : Soft = unlimited, Hard = unlimited
Stack Size : Soft = 8388608, Hard = unlimited
Virtual Memory : Soft = unlimited, Hard = unlimited
Open Files : Soft = 1024, Hard = 1048576
Max Processes : Soft = 62545, Hard = 62545
Core File Size : Soft = 0, Hard = unlimited

--- Setting CPU Time Limit ---
Set CPU time limit: Soft = 5s, Hard = 10s

--- Setting Open File Descriptor Limit ---
Set open file limit: Soft = 10, Hard = 20

--- Setting File Size Limit ---
Set file size limit: Soft = 1048576 bytes, Hard = 2097152 bytes

--- Setting Virtual Memory Limit ---
Set virtual memory limit: Soft = 52428800 bytes (50.00 MB), Hard = 104857600 bytes (100.00 MB)

--- Disabling Core Dump ---
Disabled core dump generation.

--- Resource Limits After setrlimit ---
CPU Time : Soft = 5, Hard = 10
File Size : Soft = 1048576, Hard = 2097152
Virtual Memory : Soft = 52428800, Hard = 104857600
Open Files : Soft = 10, Hard = 20
Core File Size : Soft = 0, Hard = 0

--- Testing RLIMIT_FSIZE (File Size Limit) ---
Finished writing (or failed) to file.

--- Testing RLIMIT_AS (Virtual Memory Limit) ---
Attempting to allocate large chunks of memory until limit is hit...
Allocated 10 MB so far...
Allocated 20 MB so far...
...
Allocated 50 MB so far...
malloc failed after allocating approximately 50 MB. Memory limit likely reached.

--- Testing RLIMIT_CPU (CPU Time Limit) ---
Entering infinite loop. Should be killed by SIGKILL after 10 seconds (hard limit).
You might see 'CPU time limit (soft) reached' message first (after 5s), then termination.

Caught signal 24
CPU time limit (soft) reached. Exiting gracefully.
&#91;程序被终止]

11. 总结

setrlimit 是一个非常有用的系统调用,用于管理和控制进程对系统资源的消耗。它对于编写健壮、安全的系统程序和服务至关重要,可以防止资源耗尽导致的系统不稳定。理解软限制和硬限制的区别,以及不同资源类型的行为,是掌握 Linux 进程管理的基础。

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

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

setsockopt系统调用及示例

1. 函数介绍

setsockopt (Set Socket Options) 是一个 Linux 系统调用,用于在已创建的套接字上设置各种选项(options)或参数(parameters)。这些选项控制着套接字的行为和底层协议的特定方面。

你可以把 setsockopt 想象成调整收音机或电视机的设置:

  • 你有一个设备(套接字)。

  • 这个设备有很多可以调整的旋钮和开关(选项),比如音量(SO_RCVBUF)、音质(SO_REUSEADDR)、电源(SO_KEEPALIVE)等。

  • setsockopt 就是让你转动这些旋钮、切换这些开关,来改变设备的工作方式。

这些选项可以影响套接字的方方面面,例如:

  • 地址重用: 允许绑定到一个处于 TIME_WAIT 状态的地址(SO_REUSEADDR)。

  • 缓冲区大小: 调整发送和接收缓冲区的大小(SO_SNDBUF, SO_RCVBUF)。

  • 连接保活: 启用 TCP 的 keep-alive 机制来检测死连接(SO_KEEPALIVE)。

  • ** linger**: 控制 close 调用在有未发送数据时的行为(SO_LINGER)。

  • 广播: 允许在 UDP 套接字上发送广播数据报(SO_BROADCAST)。

  • 错误: 控制是否接收带外数据的错误指示(SO_OOBINLINE)。

  • 超时: 设置发送和接收的超时时间(SO_SNDTIMEO, SO_RCVTIMEO)。

  • 以及其他许多高级选项。

2. 函数原型

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

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

3. 功能

  • 设置选项: 为套接字 sockfd 设置由 level 和 optname 共同指定的选项。

  • 传递参数: 通过 optval 指针将选项的具体值传递给内核。这个值的类型和含义取决于具体的选项。

  • 指定长度: 通过 optlen 指定 optval 指向的数据的大小(以字节为单位)。

4. 参数

  • int sockfd: 这是一个已创建(通过 socket())的有效套接字文件描述符。

int level: 指定选项定义的协议层(level)。

  • SOL_SOCKET: 套接字层本身。这是最常用的级别,用于设置与具体网络协议(如 TCP/IP)无关的通用套接字选项。

  • IPPROTO_IP: IP 层。用于设置 IP 协议特有的选项(如 IP_TTL)。

  • IPPROTO_TCP: TCP 层。用于设置 TCP 协议特有的选项(如 TCP_NODELAY)。

  • IPPROTO_IPV6: IPv6 层。用于设置 IPv6 协议特有的选项。

int optname: 指定在 level 层要设置的具体选项名称。
例如,在 SOL_SOCKET 级别,常见的 optname 有:

  • SO_REUSEADDR: 允许重用本地地址。

  • SO_KEEPALIVE: 启用 keep-alive 机制。

  • SO_LINGER: 设置 linger 选项。

  • SO_BROADCAST: 允许发送广播数据报。

  • SO_SNDBUF: 设置发送缓冲区大小。

  • SO_RCVBUF: 设置接收缓冲区大小。

  • SO_SNDTIMEO: 设置发送超时。

  • SO_RCVTIMEO: 设置接收超时。

在 IPPROTO_TCP 级别,常见的 optname 有:

  • TCP_NODELAY: 禁用 Nagle 算法(发送小包时立即发送,不等待)。

const void *optval: 这是一个指向选项值的指针。

  • 选项值的类型取决于 optname。

  • 对于布尔型选项(如 SO_REUSEADDR),通常是一个指向 int 的指针,非 0 表示启用,0 表示禁用。

  • 对于整型选项(如 SO_SNDBUF),通常是一个指向 int 的指针,值为所需的大小。

  • 对于结构体选项(如 SO_LINGER),则是一个指向相应结构体的指针。

socklen_t optlen: 指定 optval 指向的数据的大小(以字节为单位)。

  • 例如,如果 optval 指向一个 int,则 optlen 应为 sizeof(int)。

  • 如果 optval 指向一个 struct linger,则 optlen 应为 sizeof(struct linger)。

5. 返回值

  • 成功时: 返回 0。套接字选项已成功设置。

  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF sockfd 无效,EINVAL level 或 optname 无效或 optval/optlen 不匹配,ENOPROTOOPT 协议不支持该选项等)。

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

  • getsockopt: 用于获取套接字的当前选项值。与 setsockopt 相对应。

  • socket: 创建套接字,是 setsockopt 操作的对象。

  • bind / listen / connect: 其他套接字操作函数,通常与 setsockopt 结合使用来配置套接字。

7. 示例代码

示例 1:设置 SO_REUSEADDR 选项

这个例子演示了如何在服务器套接字上设置 SO_REUSEADDR 选项,这是服务器编程中的一个常见且重要的实践。

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

#define PORT 8095
#define BACKLOG 10

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

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

// --- 关键: 设置 SO_REUSEADDR 选项 ---
// 这允许服务器在重启时立即绑定到同一个地址,
// 即使旧的连接可能处于 TIME_WAIT 状态。
printf("Setting SO_REUSEADDR option...\n");
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt SO_REUSEADDR failed");
// 注意:即使失败,程序也可以继续,但这不是好习惯
// 最佳实践是处理错误并决定是否继续
close(server_fd);
exit(EXIT_FAILURE);
}
printf("SO_REUSEADDR option set successfully.\n");

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

// 3. 绑定套接字
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");

// 4. 监听
if (listen(server_fd, BACKLOG) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server is listening.\n");

printf("Server setup complete. Press Ctrl+C to exit.\n");
pause(); // 挂起等待信号

close(server_fd);
return 0;
}

代码解释:

创建 TCP 套接字。

关键步骤: 调用 setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))。

  • server_fd: 要设置选项的套接字。

  • SOL_SOCKET: 选项所在的协议层(套接字层)。

  • SO_REUSEADDR: 要设置的选项名称。

  • &opt: 指向选项值的指针。这里 opt 是一个 int 变量,值为 1,表示启用该选项。

  • sizeof(opt): 选项值的大小。

如果调用成功,SO_REUSEADDR 选项就被设置为启用了。

继续进行 bind 和 listen。

设置 SO_REUSEADDR 的好处是,当服务器进程因某种原因(如崩溃或重启)终止后,它之前绑定的地址和端口可能会在内核中处于 TIME_WAIT 状态一段时间。如果没有设置 SO_REUSEADDR,立即重启服务器并尝试绑定同一个地址端口会失败(EADDRINUSE)。设置了这个选项后,就可以立即重用该地址。

示例 2:设置 SO_RCVBUF 和 SO_SNDBUF 选项

这个例子演示了如何调整套接字的接收和发送缓冲区大小。

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

#define PORT 8096
#define CUSTOM_BUFFER_SIZE 64 * 1024 // 64 KB

void print_buffer_sizes(int sock, const char* role) {
int rcv_buf_size, snd_buf_size;
socklen_t len = sizeof(rcv_buf_size);

if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, &len) == 0) {
printf("%s SO_RCVBUF: %d bytes\n", role, rcv_buf_size);
} else {
perror("getsockopt SO_RCVBUF failed");
}

len = sizeof(snd_buf_size); // Reset len
if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, &len) == 0) {
printf("%s SO_SNDBUF: %d bytes\n", role, snd_buf_size);
} else {
perror("getsockopt SO_SNDBUF failed");
}
}

int main() {
int server_fd, client_sock;
struct sockaddr_in address, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int opt = 1;

server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

// 设置 SO_REUSEADDR
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt SO_REUSEADDR failed");
close(server_fd);
exit(EXIT_FAILURE);
}

// --- 设置服务器套接字的缓冲区大小 ---
printf("--- Before setting buffer sizes ---\n");
print_buffer_sizes(server_fd, "Server (before)");

int rcv_buf_size = CUSTOM_BUFFER_SIZE;
int snd_buf_size = CUSTOM_BUFFER_SIZE;

printf("\nSetting custom buffer sizes to %d bytes...\n", CUSTOM_BUFFER_SIZE);
if (setsockopt(server_fd, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, sizeof(rcv_buf_size))) {
perror("setsockopt SO_RCVBUF failed");
// 注意:内核可能会调整这个值到一个合理的范围
}
if (setsockopt(server_fd, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, sizeof(snd_buf_size))) {
perror("setsockopt SO_SNDBUF failed");
}

printf("--- After setting buffer sizes ---\n");
print_buffer_sizes(server_fd, "Server (after)");

// 绑定和监听
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}

if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}

printf("\nServer listening on port %d\n", PORT);

// --- 客户端连接 ---
client_sock = socket(AF_INET, SOCK_STREAM, 0);
if (client_sock < 0) {
perror("client socket failed");
close(server_fd);
exit(EXIT_FAILURE);
}

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (connect(client_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("client connect failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}

// --- 检查客户端套接字的缓冲区大小 ---
printf("\n--- Client socket buffer sizes ---\n");
print_buffer_sizes(client_sock, "Client");

// --- 服务器 accept 连接并检查其套接字 ---
int accepted_sock = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (accepted_sock < 0) {
perror("accept failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}

printf("\n--- Server accepted socket buffer sizes ---\n");
print_buffer_sizes(accepted_sock, "Server-Accepted");

// 简单通信后关闭
close(client_sock);
close(accepted_sock);
close(server_fd);

return 0;
}

代码解释:

定义了一个 print_buffer_sizes 函数,它使用 getsockopt 来获取并打印套接字的当前接收和发送缓冲区大小。

创建服务器套接字。

设置 SO_REUSEADDR。

关键: 在 bind 之前,调用 setsockopt 来设置服务器套接字的 SO_RCVBUF 和 SO_SNDBUF。

  • 传递一个 int 变量的指针和其大小。

打印设置前后的缓冲区大小。注意,内核可能会将请求的大小调整为一个内部支持的值。

继续设置服务器监听。

创建客户端套接字并连接。

打印客户端套接字的缓冲区大小。

服务器 accept 连接,得到一个新的已连接套接字。

打印这个新套接字的缓冲区大小。通常,accept 返回的套接字会继承监听套接字的一些属性,包括缓冲区大小。

示例 3:设置 TCP_NODELAY 选项

这个例子演示了如何在 TCP 套接字上设置 TCP_NODELAY 选项,以禁用 Nagle 算法。

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
// setsockopt_tcp_nodelay.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h> // For TCP_NODELAY
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8097

int main() {
int server_fd, client_sock;
struct sockaddr_in address, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int opt = 1;

server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt SO_REUSEADDR failed");
close(server_fd);
exit(EXIT_FAILURE);
}

memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}

if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}

printf("Server listening on port %d. Waiting for connection...\n", PORT);

// --- 客户端连接 ---
client_sock = socket(AF_INET, SOCK_STREAM, 0);
if (client_sock < 0) {
perror("client socket failed");
close(server_fd);
exit(EXIT_FAILURE);
}

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (connect(client_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("client connect failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}
printf("Client connected.\n");

// --- 服务器 accept 连接 ---
int accepted_sock = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (accepted_sock < 0) {
perror("accept failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}
printf("Server accepted connection.\n");

// --- 关键: 在已连接的套接字上设置 TCP_NODELAY ---
// 这会禁用 Nagle 算法,使得小的数据包能够立即发送,而不必等待 ACK 或积累更多数据。
// 这对于实时性要求高的应用(如游戏、交互式应用)很有用。
printf("\nSetting TCP_NODELAY on server's accepted socket...\n");
int flag = 1; // Enable TCP_NODELAY
if (setsockopt(accepted_sock, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) < 0) {
perror("setsockopt TCP_NODELAY on server socket failed");
// 不是致命错误,可以继续
} else {
printf("TCP_NODELAY enabled on server's accepted socket (fd: %d).\n", accepted_sock);
}

printf("\nSetting TCP_NODELAY on client socket...\n");
if (setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) < 0) {
perror("setsockopt TCP_NODELAY on client socket failed");
} else {
printf("TCP_NODELAY enabled on client socket (fd: %d).\n", client_sock);
}

// ... 这里可以进行数据传输 ...

close(client_sock);
close(accepted_sock);
close(server_fd);

printf("Sockets closed.\n");
return 0;
}

代码解释:

设置标准的服务器和客户端连接。

服务器 accept 连接后,得到 accepted_sock。

关键: 在 accepted_sock 上调用 setsockopt(accepted_sock, IPPROTO_TCP, TCP_NODELAY, …)。

  • IPPROTO_TCP: 选项所在的协议层(TCP 层)。

  • TCP_NODELAY: 要设置的选项名称。

  • &flag (flag=1): 选项值,1 表示启用(禁用 Nagle 算法)。

  • sizeof(int): 选项值大小。

同样地,在客户端套接字 client_sock 上也设置 TCP_NODELAY。

启用 TCP_NODELAY 后,TCP 连接将立即发送小的数据包,而不会等待将多个小包合并成一个更大的包(Nagle 算法的默认行为)或等待前一个包的 ACK。这可以减少延迟,但可能会增加网络上的小包数量。

重要提示与注意事项:

调用时机: setsockopt 可以在 socket() 之后、bind()/connect()/listen() 之前或之后调用,具体取决于选项。有些选项(如 SO_REUSEADDR)必须在 bind 之前设置才有效。

level 和 optname 的匹配: 确保 level 和 optname 正确匹配。例如,TCP_NODELAY 必须在 IPPROTO_TCP 级别设置。

optval 和 optlen: 确保传递给 optval 的数据类型和大小与 optname 要求的完全一致。

内核调整: 对于某些选项(如缓冲区大小),内核可能会将你请求的值调整为一个它认为更合适的值。

错误处理: 始终检查返回值。虽然某些选项设置失败可能不会导致程序无法运行,但最好处理错误并了解原因。

常见用途: SO_REUSEADDR、TCP_NODELAY、SO_KEEPALIVE、SO_LINGER 是网络编程中非常常见的选项。

总结:

setsockopt 是一个功能强大的函数,允许程序员精细地调整套接字的行为。理解其参数(特别是 level 和 optname 的组合)以及各种常用选项的作用,对于编写高效、健壮的网络应用程序至关重要。它是网络编程中不可或缺的工具之一。

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

settimeofday系统调用及示例

settimeofday 函数详解

  1. 函数介绍

settimeofday 是Linux系统调用,settimeofday系统调用及示例,用于设置系统的日期和时间。它允许特权进程修改系统时钟,是系统管理和时间同步的重要工具。这个函数通常由系统管理员或时间同步服务(如NTP)使用。

  1. 函数原型
1
2
3
#include <sys/time.h>
int settimeofday(const struct timeval *tv, const struct timezone *tz);

  1. 功能

settimeofday 设置系统的当前日期和时间。它可以精确到微秒级别,用于系统时钟校准和时间同步。

  1. 参数
  • *const struct timeval tv: 指向时间值结构的指针(NULL表示不设置时间)

  • *const struct timezone tz: 指向时区结构的指针(通常为NULL,已被废弃)

  1. 返回值
  • 成功: 返回0

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

  1. 相似函数,或关联函数
  • gettimeofday: 获取当前时间

  • clock_settime: 更现代的时间设置函数

  • adjtime: 逐步调整系统时间

  • ntp_adjtime: NTP时间调整

  1. 示例代码

示例1:基础settimeofday使用

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

/**
* 显示当前系统时间
*/
void show_current_time() {
struct timeval tv;
struct tm *tm_info;
char time_str&#91;64];

if (gettimeofday(&tv, NULL) == 0) {
tm_info = localtime(&tv.tv_sec);
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
printf("当前系统时间: %s.%06ld\n", time_str, tv.tv_usec);
} else {
printf("获取当前时间失败: %s\n", strerror(errno));
}
}

/**
* 演示基础settimeofday使用方法
*/
int demo_settimeofday_basic() {
struct timeval current_time, new_time;
struct timezone tz = {0, 0};
time_t original_time;
int result;

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

// 显示原始时间
printf("1. 原始系统时间:\n");
show_current_time();

// 获取当前时间作为参考
if (gettimeofday(&current_time, NULL) != 0) {
printf("获取当前时间失败: %s\n", strerror(errno));
return -1;
}

original_time = current_time.tv_sec;

// 设置新的时间(当前时间+10秒)
new_time.tv_sec = current_time.tv_sec + 10;
new_time.tv_usec = current_time.tv_usec;

printf("\n2. 尝试设置新时间:\n");
printf(" 目标时间: %ld.%06ld\n", new_time.tv_sec, new_time.tv_usec);

// 尝试设置时间(需要root权限)
result = settimeofday(&new_time, &tz);
if (result == 0) {
printf(" ✓ 成功设置系统时间\n");

// 验证设置结果
printf(" 设置后的时间:\n");
show_current_time();

// 恢复原始时间
printf("\n3. 恢复原始时间:\n");
struct timeval restore_time;
restore_time.tv_sec = original_time;
restore_time.tv_usec = current_time.tv_usec;

result = settimeofday(&restore_time, &tz);
if (result == 0) {
printf(" ✓ 成功恢复原始时间\n");
show_current_time();
} else {
printf(" ✗ 恢复原始时间失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 原因:需要root权限\n");
}
}
} else {
printf(" ✗ 设置系统时间失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 原因:需要root权限来设置系统时间\n");
} else if (errno == EINVAL) {
printf(" 原因:时间值无效\n");
}

printf(" 注意:普通用户通常无法修改系统时间\n");
}

return 0;
}

int main() {
return demo_settimeofday_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
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>

/**
* 时间同步服务模拟
*/
typedef struct {
time_t reference_time;
int sync_interval;
int max_adjustment;
} time_sync_service_t;

/**
* 初始化时间同步服务
*/
int init_time_sync_service(time_sync_service_t *service) {
struct timeval tv;

if (gettimeofday(&tv, NULL) != 0) {
printf("获取当前时间失败\n");
return -1;
}

service->reference_time = tv.tv_sec;
service->sync_interval = 60; // 60秒同步间隔
service->max_adjustment = 30; // 最大调整30秒

printf("时间同步服务初始化完成\n");
printf(" 参考时间: %ld\n", service->reference_time);
printf(" 同步间隔: %d 秒\n", service->sync_interval);
printf(" 最大调整: %d 秒\n", service->max_adjustment);

return 0;
}

/**
* 计算时间差
*/
long calculate_time_difference(time_t current_time, time_t reference_time) {
return current_time - reference_time;
}

/**
* 模拟时间同步
*/
int simulate_time_synchronization(time_sync_service_t *service) {
struct timeval current_time, adjusted_time;
struct timezone tz = {0, 0};
long time_diff;
int result;

printf("=== 时间同步模拟 ===\n");

// 获取当前时间
if (gettimeofday(&current_time, NULL) != 0) {
printf("获取当前时间失败: %s\n", strerror(errno));
return -1;
}

printf("当前系统时间: %ld.%06ld\n", current_time.tv_sec, current_time.tv_usec);

// 计算时间差
time_diff = calculate_time_difference(current_time.tv_sec, service->reference_time);
printf("与参考时间差: %ld 秒\n", time_diff);

// 检查是否需要调整
if (labs(time_diff) > service->max_adjustment) {
printf("时间偏差过大,需要调整\n");

// 计算调整后的时间
adjusted_time.tv_sec = service->reference_time;
adjusted_time.tv_usec = current_time.tv_usec;

printf("调整目标时间: %ld.%06ld\n", adjusted_time.tv_sec, adjusted_time.tv_usec);

// 尝试设置时间
result = settimeofday(&adjusted_time, &tz);
if (result == 0) {
printf("✓ 时间同步成功\n");
show_current_time();
} else {
printf("✗ 时间同步失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 需要root权限来调整系统时间\n");
}
return -1;
}
} else {
printf("时间偏差在可接受范围内,无需调整\n");
}

return 0;
}

/**
* 演示时间同步
*/
int demo_time_synchronization() {
time_sync_service_t service = {0};

printf("=== 时间同步演示 ===\n");

// 检查权限
uid_t uid = getuid();
printf("当前用户ID: %d\n", uid);
if (uid == 0) {
printf("✓ 具有root权限,可以设置系统时间\n");
} else {
printf("✗ 没有root权限,时间设置操作将失败\n");
}

// 显示当前时间
printf("\n1. 当前系统时间:\n");
show_current_time();

// 初始化时间同步服务
if (init_time_sync_service(&service) != 0) {
printf("初始化时间同步服务失败\n");
return -1;
}

// 模拟时间同步
printf("\n2. 模拟时间同步:\n");
if (simulate_time_synchronization(&service) != 0) {
printf("时间同步模拟失败\n");
}

// 显示同步后的时间
printf("\n3. 同步后的时间:\n");
show_current_time();

return 0;
}

// 辅助函数声明
void show_current_time();

int main() {
return demo_time_synchronization();
}

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

/**
* 逐步时间调整
*/
int gradual_time_adjustment(time_t target_time, int adjustment_steps) {
struct timeval current_time, target_tv;
struct timezone tz = {0, 0};
time_t current_sec, step_size;
int result;

printf("=== 逐步时间调整 ===\n");
printf("目标时间: %ld\n", target_time);
printf("调整步数: %d\n", adjustment_steps);

// 获取当前时间
if (gettimeofday(&current_time, NULL) != 0) {
printf("获取当前时间失败: %s\n", strerror(errno));
return -1;
}

current_sec = current_time.tv_sec;
printf("当前时间: %ld\n", current_sec);

// 计算步长
step_size = (target_time - current_sec) / adjustment_steps;
if (step_size == 0) {
step_size = (target_time > current_sec) ? 1 : -1;
}

printf("每步调整: %ld 秒\n", step_size);

// 逐步调整时间
for (int i = 0; i < adjustment_steps; i++) {
time_t next_time = current_sec + (i + 1) * step_size;

// 确保不超过目标时间
if ((step_size > 0 && next_time > target_time) ||
(step_size < 0 && next_time < target_time)) {
next_time = target_time;
}

target_tv.tv_sec = next_time;
target_tv.tv_usec = current_time.tv_usec;

printf("第 %d 步: 设置时间 %ld\n", i + 1, next_time);

result = settimeofday(&target_tv, &tz);
if (result == 0) {
printf(" ✓ 设置成功\n");
show_current_time();
} else {
printf(" ✗ 设置失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 需要root权限\n");
break;
}
}

// 短暂延迟
sleep(1);
}

return 0;
}

/**
* 时间跳跃检测
*/
int detect_time_jumps() {
struct timeval prev_time, current_time;
long time_diff;
static int first_call = 1;

if (gettimeofday(&current_time, NULL) != 0) {
printf("获取时间失败: %s\n", strerror(errno));
return -1;
}

if (first_call) {
prev_time = current_time;
first_call = 0;
return 0;
}

time_diff = current_time.tv_sec - prev_time.tv_sec;

if (labs(time_diff) > 5) { // 超过5秒的时间跳跃
printf("⚠ 检测到时间跳跃: %ld 秒\n", time_diff);
printf(" 之前时间: %ld.%06ld\n", prev_time.tv_sec, prev_time.tv_usec);
printf(" 当前时间: %ld.%06ld\n", current_time.tv_sec, current_time.tv_usec);
}

prev_time = current_time;
return 0;
}

/**
* 演示逐步时间调整
*/
int demo_gradual_adjustment() {
struct timeval current_time;
time_t target_time;
uid_t uid = getuid();

printf("=== 逐步时间调整演示 ===\n");

// 检查权限
printf("用户权限检查:\n");
printf(" 当前用户ID: %d\n", uid);
if (uid == 0) {
printf(" ✓ 具有root权限\n");
} else {
printf(" ✗ 没有root权限,时间设置将失败\n");
}

// 显示当前时间
printf("\n当前系统时间:\n");
show_current_time();

// 获取当前时间
if (gettimeofday(&current_time, NULL) != 0) {
printf("获取当前时间失败\n");
return -1;
}

// 设置目标时间为当前时间+30秒
target_time = current_time.tv_sec + 30;
printf("\n目标时间: %ld (当前时间+30秒)\n", target_time);

// 演示逐步调整
printf("\n执行逐步时间调整:\n");
if (gradual_time_adjustment(target_time, 5) != 0) {
printf("逐步时间调整失败\n");
}

// 演示时间跳跃检测
printf("\n时间跳跃检测演示:\n");
for (int i = 0; i < 10; i++) {
detect_time_jumps();
sleep(1);
}

return 0;
}

// 辅助函数声明
void show_current_time();

int main() {
return demo_gradual_adjustment();
}

示例4:NTP时间同步模拟

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

/**
* NTP时间同步模拟
*/
typedef struct {
time_t server_time;
double offset;
double delay;
int stratum;
char server_name&#91;64];
} ntp_server_info_t;

/**
* 模拟NTP服务器响应
*/
int simulate_ntp_server_response(ntp_server_info_t *server) {
struct timeval tv;

// 模拟NTP服务器信息
strcpy(server->server_name, "ntp.example.com");
server->stratum = 2; // 二级时间服务器
server->delay = 0.05 + (rand() / (double)RAND_MAX) * 0.1; // 50-150ms延迟

// 获取当前时间作为服务器时间
if (gettimeofday(&tv, NULL) != 0) {
return -1;
}

server->server_time = tv.tv_sec;
// 模拟时钟偏移(-1到1秒)
server->offset = (rand() / (double)RAND_MAX) * 2.0 - 1.0;

return 0;
}

/**
* 计算时间同步质量
*/
double calculate_sync_quality(ntp_server_info_t *server) {
// 简单的质量计算:基于stratum和offset
double quality = 1.0;

// stratum越高,质量越低
quality -= (server->stratum - 1) * 0.1;

// offset越大,质量越低
quality -= fabs(server->offset) * 0.5;

// delay越大,质量越低
quality -= server->delay * 2.0;

return (quality > 0) ? quality : 0.0;
}

/**
* NTP时间同步
*/
int ntp_time_synchronization(ntp_server_info_t *server) {
struct timeval current_time, sync_time;
struct timezone tz = {0, 0};
double quality;
int result;

printf("=== NTP时间同步 ===\n");
printf("服务器: %s\n", server->server_name);
printf("层级: %d\n", server->stratum);
printf("服务器时间: %ld\n", server->server_time);
printf("时钟偏移: %.3f 秒\n", server->offset);
printf("网络延迟: %.3f 秒\n", server->delay);

// 计算同步质量
quality = calculate_sync_quality(server);
printf("同步质量: %.2f\n", quality);

if (quality < 0.5) {
printf("同步质量过低,跳过同步\n");
return -1;
}

// 获取当前时间
if (gettimeofday(&current_time, NULL) != 0) {
printf("获取当前时间失败: %s\n", strerror(errno));
return -1;
}

printf("同步前时间: %ld.%06ld\n", current_time.tv_sec, current_time.tv_usec);

// 计算同步后的时间
sync_time.tv_sec = server->server_time;
sync_time.tv_usec = current_time.tv_usec;

// 应用偏移调整
if (server->offset > 0) {
sync_time.tv_sec += (time_t)server->offset;
sync_time.tv_usec += (suseconds_t)((server->offset - (time_t)server->offset) * 1000000);
} else {
sync_time.tv_sec += (time_t)server->offset;
sync_time.tv_usec += (suseconds_t)((server->offset - (time_t)server->offset) * 1000000);
}

// 确保微秒在有效范围内
if (sync_time.tv_usec >= 1000000) {
sync_time.tv_sec += 1;
sync_time.tv_usec -= 1000000;
} else if (sync_time.tv_usec < 0) {
sync_time.tv_sec -= 1;
sync_time.tv_usec += 1000000;
}

printf("同步目标时间: %ld.%06ld\n", sync_time.tv_sec, sync_time.tv_usec);

// 执行时间同步
result = settimeofday(&sync_time, &tz);
if (result == 0) {
printf("✓ NTP时间同步成功\n");
show_current_time();
} else {
printf("✗ NTP时间同步失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 需要root权限来设置系统时间\n");
}
return -1;
}

return 0;
}

/**
* 演示NTP时间同步
*/
int demo_ntp_synchronization() {
ntp_server_info_t servers&#91;3];
double best_quality = 0.0;
int best_server = -1;

printf("=== NTP时间同步演示 ===\n");

// 检查权限
uid_t uid = getuid();
printf("权限检查: ");
if (uid == 0) {
printf("✓ 具有root权限\n");
} else {
printf("✗ 没有root权限,时间设置将失败\n");
}

// 显示当前时间
printf("\n当前系统时间:\n");
show_current_time();

// 模拟多个NTP服务器
printf("\n查询NTP服务器:\n");
srand(time(NULL));

for (int i = 0; i < 3; i++) {
if (simulate_ntp_server_response(&servers&#91;i]) == 0) {
double quality = calculate_sync_quality(&servers&#91;i]);
printf("服务器 %d: %s (质量: %.2f)\n",
i + 1, servers&#91;i].server_name, quality);

if (quality > best_quality) {
best_quality = quality;
best_server = i;
}
}
}

// 选择最佳服务器进行同步
if (best_server >= 0) {
printf("\n选择最佳服务器进行同步:\n");
printf(" 服务器: %s\n", servers&#91;best_server].server_name);
printf(" 质量: %.2f\n", best_quality);

if (ntp_time_synchronization(&servers&#91;best_server]) != 0) {
printf("NTP时间同步失败\n");
}
} else {
printf("\n没有找到合适的NTP服务器\n");
}

// 显示同步后的时间
printf("\n同步后的时间:\n");
show_current_time();

return 0;
}

// 辅助函数声明
void show_current_time();

int main() {
return demo_ntp_synchronization();
}

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

/**
* 时间管理工具配置
*/
typedef struct {
int set_time;
time_t target_time;
int show_time;
int sync_time;
char time_string&#91;64];
int verbose;
} time_tool_config_t;

/**
* 显示帮助信息
*/
void show_help(const char *program_name) {
printf("用法: %s &#91;选项]\n", program_name);
printf("\n选项:\n");
printf(" -s, --set TIME 设置系统时间 (格式: YYYY-MM-DD HH:MM:SS)\n");
printf(" -g, --get 显示当前系统时间\n");
printf(" -S, --sync 同步时间\n");
printf(" -v, --verbose 详细输出\n");
printf(" -h, --help 显示此帮助信息\n");
printf("\n示例:\n");
printf(" %s -g # 显示当前时间\n", program_name);
printf(" %s -s \"2023-12-25 10:30:00\" # 设置时间\n", program_name);
printf(" %s -v -g # 详细显示当前时间\n", program_name);
}

/**
* 解析时间字符串
*/
int parse_time_string(const char *time_str, time_t *result) {
struct tm tm_time = {0};
char *endptr;

// 尝试解析 ISO 格式时间: YYYY-MM-DD HH:MM:SS
if (strptime(time_str, "%Y-%m-%d %H:%M:%S", &tm_time) != NULL) {
*result = mktime(&tm_time);
if (*result == -1) {
printf("时间转换失败\n");
return -1;
}
return 0;
}

// 尝试解析 Unix 时间戳
*result = strtol(time_str, &endptr, 10);
if (*endptr == '\0' && *result > 0) {
return 0;
}

printf("无法解析时间字符串: %s\n", time_str);
printf("支持的格式:\n");
printf(" YYYY-MM-DD HH:MM:SS\n");
printf(" Unix时间戳\n");
return -1;
}

/**
* 详细显示时间信息
*/
void show_detailed_time() {
struct timeval tv;
struct tm *tm_info;
char time_str&#91;128];

if (gettimeofday(&tv, NULL) == 0) {
// 显示多种时间格式
tm_info = localtime(&tv.tv_sec);

// 标准格式
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
printf("标准时间: %s.%06ld\n", time_str, tv.tv_usec);

// Unix时间戳
printf("Unix时间戳: %ld.%06ld\n", tv.tv_sec, tv.tv_usec);

// UTC时间
tm_info = gmtime(&tv.tv_sec);
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S UTC", tm_info);
printf("UTC时间: %s\n", time_str);

// 星期和年份信息
strftime(time_str, sizeof(time_str), "%A, %B %d, %Y", localtime(&tv.tv_sec));
printf("详细日期: %s\n", time_str);

} else {
printf("获取时间失败: %s\n", strerror(errno));
}
}

/**
* 设置系统时间
*/
int set_system_time(time_t target_time) {
struct timeval tv;
struct timezone tz = {0, 0};
int result;

printf("设置系统时间为: %ld\n", target_time);

tv.tv_sec = target_time;
tv.tv_usec = 0;

result = settimeofday(&tv, &tz);
if (result == 0) {
printf("✓ 系统时间设置成功\n");
show_current_time();
return 0;
} else {
printf("✗ 系统时间设置失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 需要root权限来设置系统时间\n");
}
return -1;
}
}

/**
* 演示时间管理工具
*/
int demo_time_management_tool() {
time_tool_config_t config = {0};
uid_t uid = getuid();

printf("=== 时间管理工具演示 ===\n");

// 显示工具信息
printf("工具功能:\n");
printf(" 1. 显示系统时间\n");
printf(" 2. 设置系统时间\n");
printf(" 3. 时间同步\n");
printf(" 4. 详细时间信息\n");

// 权限检查
printf("\n权限检查:\n");
printf(" 当前用户ID: %d\n", uid);
if (uid == 0) {
printf(" ✓ 具有root权限,可以设置系统时间\n");
} else {
printf(" ✗ 没有root权限,时间设置功能将受限\n");
}

// 演示显示时间功能
printf("\n1. 显示当前时间:\n");
show_current_time();

printf("\n2. 详细时间信息:\n");
show_detailed_time();

// 演示时间设置功能
printf("\n3. 时间设置功能演示:\n");

// 获取当前时间
struct timeval current_time;
if (gettimeofday(&current_time, NULL) == 0) {
time_t future_time = current_time.tv_sec + 60; // 1分钟后

printf(" 尝试设置时间为1分钟后: %ld\n", future_time);

if (uid == 0) {
// 有权限时尝试设置
if (set_system_time(future_time) == 0) {
printf(" ✓ 时间设置成功\n");

// 恢复原始时间
printf(" 恢复原始时间: %ld\n", current_time.tv_sec);
set_system_time(current_time.tv_sec);
}
} else {
// 无权限时模拟设置
struct timeval future_tv;
future_tv.tv_sec = future_time;
future_tv.tv_usec = current_time.tv_usec;

int result = settimeofday(&future_tv, NULL);
if (result != 0) {
printf(" ✗ 时间设置失败 (预期): %s\n", strerror(errno));
printf(" 需要root权限才能设置系统时间\n");
}
}
}

// 演示时间格式解析
printf("\n4. 时间格式解析演示:\n");
const char *time_formats&#91;] = {
"2023-12-25 10:30:00",
"1703498200", // Unix时间戳
NULL
};

for (int i = 0; time_formats&#91;i]; i++) {
time_t parsed_time;
printf(" 解析时间字符串: %s\n", time_formats&#91;i]);
if (parse_time_string(time_formats&#91;i], &parsed_time) == 0) {
printf(" ✓ 解析成功: %ld\n", parsed_time);
} else {
printf(" ✗ 解析失败\n");
}
}

// 显示工具使用建议
printf("\n=== 工具使用建议 ===\n");
printf("1. 时间设置需要root权限\n");
printf("2. 建议使用NTP服务进行时间同步\n");
printf("3. 避免频繁手动调整系统时间\n");
printf("4. 记录时间变更操作日志\n");
printf("5. 使用逐步调整避免时间跳跃\n");

return 0;
}

// 辅助函数声明
void show_current_time();

int main() {
return demo_time_management_tool();
}

settimeofday 使用注意事项

系统要求:

内核版本: 支持settimeofday的Linux内核

权限要求: 需要CAP_SYS_TIME能力或root权限

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

参数限制:

时间有效性: tv参数必须指向有效的timeval结构

时区参数: tz参数通常应为NULL(已被废弃)

时间范围: 时间值应在有效范围内

错误处理:

EPERM: 权限不足(需要CAP_SYS_TIME或root权限)

EINVAL: 时间值无效

EFAULT: 指针参数指向无效内存

安全考虑:

权限提升: 不当使用可能导致安全风险

系统稳定性: 频繁的时间调整可能影响系统稳定性

应用程序影响: 时间跳跃可能影响依赖时间的应用程序

最佳实践:

权限检查: 执行前检查是否具有足够权限

时间验证: 验证时间值的有效性和合理性

错误处理: 妥善处理各种错误情况

日志记录: 记录时间变更操作

逐步调整: 避免大的时间跳跃,使用逐步调整

时间结构体说明

struct timeval:

1
2
3
4
5
struct timeval {
time_t tv_sec; // 秒数
suseconds_t tv_usec; // 微秒数
};

struct timezone(已废弃):

1
2
3
4
5
struct timezone {
int tz_minuteswest; // 西偏分钟数
int tz_dsttime; // 夏令时标志
};

相关函数对比

1. settimeofday vs clock_settime:

1
2
3
4
5
6
// settimeofday(传统接口)
settimeofday(&tv, NULL);

// clock_settime(现代接口)
clock_settime(CLOCK_REALTIME, &timespec);

2. settimeofday vs adjtime:

1
2
3
4
5
6
// settimeofday:直接设置时间
settimeofday(&tv, NULL);

// adjtime:逐步调整时间
adjtime(&delta, NULL);

常见使用场景

1. 系统管理:

1
2
3
// 系统启动时设置初始时间
settimeofday(&initial_time, NULL);

2. NTP客户端:

1
2
3
// 时间同步服务设置系统时间
settimeofday(&ntp_time, NULL);

3. 测试环境:

1
2
3
// 测试时模拟特定时间点
settimeofday(&test_time, NULL);

时间同步策略

1. 突然跳跃 vs 逐步调整:

  • 突然跳跃: 使用settimeofday直接设置

  • 逐步调整: 使用adjtime逐步调整

2. 同步频率:

  • 高精度: 每分钟同步

  • 普通: 每小时同步

  • 低精度: 每天同步

总结

settimeofday 是Linux系统中重要的时间管理函数,提供了:

精确时间控制: 可以精确到微秒级别设置系统时间

灵活接口: 支持多种时间格式和设置方式

权限管理: 通过权限控制保证系统安全

标准兼容: 符合POSIX标准

通过合理使用 settimeofday,可以实现精确的时间管理和同步。在实际应用中,需要注意权限要求、错误处理和系统稳定性等关键问题。建议在生产环境中使用专业的NTP服务进行时间同步,避免手动频繁调整系统时间。

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

setuid系统调用及示例

我们来深入学习 setuid 系统调用

1. 函数介绍

在 Linux 系统中,每个进程都运行在一个特定的用户 (User) 上下文中。这个用户上下文决定了进程拥有哪些权限,比如能否读写某个文件、能否绑定到特权端口(端口号小于 1024)等。

每个进程通常有三类相关的用户 ID:

  • 真实用户 ID (Real User ID - RUID): 登录系统时分配给用户的 ID。它标识了“你是谁”。

  • 有效用户 ID (Effective User ID - EUID): 内核用来进行权限检查时使用的 ID。它决定了“你能做什么”。这是最重要的一个。

  • 保存的设置用户 ID (Saved Set-User-ID - SUID): 用于在有效用户 ID 和真实用户 ID 之间来回切换的一个“备份”ID。

setuid (Set User ID) 系统调用的主要作用是设置调用进程的有效用户 ID (EUID)。根据调用者的权限和当前 ID 状态,它也可能同时修改保存的设置用户 ID (SUID)。

简单来说,setuid 让你的程序可以“以某个用户的身份”去执行操作,从而获得或限制与该用户相关的权限。

一个非常常见的场景是:一个需要监听 80 端口(特权端口)的 Web 服务器程序。它通常以 root 用户(UID 0)启动,以便能绑定到 80 端口。但一旦绑定成功,为了安全起见,它会使用 setuid 将自己的有效用户 ID 切换到一个权限较低的普通用户(如 www-data),这样即使程序出现漏洞被攻击,攻击者也无法获得 root 权限。

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

2. 函数原型

1
2
3
4
5
#include <unistd.h>    // 包含系统调用声明
#include <sys/types.h> // 包含 uid_t 类型定义

int setuid(uid_t uid);

3. 功能

设置调用进程的有效用户 ID (Effective UID)。根据调用者的权限(是否为 root)和目标 uid,行为会有所不同。

4. 参数

uid:

  • uid_t 类型。

  • 指定要设置的新的有效用户 ID。

5. 返回值

  • 成功: 返回 0。

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

6. 行为规则

setuid 的具体行为取决于调用进程的权限:

如果调用者是特权用户 (超级用户, root, EUID == 0):

  • 可以将有效用户 ID (euid) 设置为任意有效的用户 ID。

  • 同时,真实用户 ID (ruid) 和保存的设置用户 ID (suid) 也会被设置为相同的 uid 值。

  • 这是特权用户的强大能力。

如果调用者是普通用户 (EUID != 0):

  • uid 参数必须是调用进程的真实用户 ID (ruid) 或 保存的设置用户 ID (suid) 之一。

  • 只能将有效用户 ID (euid) 设置为 ruid 或 suid。

  • 真实用户 ID (ruid) 和 保存的设置用户 ID (suid) 不会被修改。

  • 这是为了防止普通用户随意获取其他用户的权限。

7. 错误码 (errno)

  • EINVAL: uid 参数无效(虽然在 Linux 中通常不会返回此错误)。

  • EPERM: 调用者没有权限执行此操作。对于普通用户,这意味着 uid 既不是 ruid 也不是 suid。

8. 相似函数或关联函数

  • setgid: 设置组 ID,与 setuid 功能类似,但针对的是组而非用户。

  • seteuid: 专门用于设置有效用户 ID,行为比 setuid 更受限(普通用户只能设置为 ruid 或 suid)。

  • setreuid: 同时设置真实用户 ID 和 有效用户 ID。

  • setresuid: 同时设置 真实、有效 和 保存的设置 用户 ID,提供了最精细的控制。

  • getuid: 获取调用进程的真实用户 ID。

  • geteuid: 获取调用进程的有效用户 ID。

  • chmod / chown: 与文件权限和所有权相关的系统调用,其行为也受 setuid 影响。

9. 示例代码

下面的示例演示了 setuid 在不同权限下的行为,以及如何检查用户 ID。

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
#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresuid
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

void print_current_uids(const char* context) {
uid_t ruid, euid, suid;
printf("&#91;%s] Current UIDs - Real: %d, Effective: %d, Saved: %d\n",
context, getuid(), geteuid(), (getresuid(&ruid, &euid, &suid) == 0) ? suid : -1);
}

int main() {
uid_t original_ruid, original_euid, original_suid;
uid_t target_uid;
int result;

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

// 1. 获取并打印初始 UID
if (getresuid(&original_ruid, &original_euid, &original_suid) != 0) {
perror("getresuid");
exit(EXIT_FAILURE);
}
print_current_uids("Start");

// 2. 检查是否以 root 权限运行
if (geteuid() == 0) {
printf("\nRunning as ROOT (Privileged User)\n");
// 作为 root,可以设置为任意有效的 UID
// 这里我们尝试设置为 'nobody' 用户 (通常 UID 65534, 但请检查你的系统)
target_uid = 65534;
printf("Attempting to set UID to %d (usually 'nobody' user)...\n", target_uid);

print_current_uids("Before setuid");
result = setuid(target_uid);
if (result == 0) {
printf("setuid(%d) succeeded.\n", target_uid);
print_current_uids("After setuid");
printf("Note: All UIDs (Real, Effective, Saved) are now %d.\n", target_uid);
printf("The process is now running with 'nobody' privileges.\n");
} else {
perror("setuid");
printf("Failed to set UID to %d.\n", target_uid);
}

} else {
printf("\nRunning as a REGULAR USER (UID: %d)\n", getuid());

// 作为普通用户,只能设置为自己的 ruid 或 suid
target_uid = original_ruid; // 选择设置为自己的真实 UID (这不会改变任何东西)
printf("Attempting to set UID to my Real UID (%d)...\n", target_uid);
print_current_uids("Before setuid");
result = setuid(target_uid);
if (result == 0) {
printf("setuid(%d) succeeded (as expected).\n", target_uid);
print_current_uids("After setuid");
} else {
perror("setuid");
}

// 尝试设置为一个无效的 UID (比如一个不存在的或不属于我的 UID)
// 这通常会失败
target_uid = 9999; // 假设这是一个无效的或不属于当前用户的 UID
printf("\nAttempting to set UID to an invalid/different UID (%d)...\n", target_uid);
result = setuid(target_uid);
if (result == -1) {
if (errno == EPERM) {
printf("setuid(%d) failed with EPERM (Operation not permitted) - as expected for a regular user.\n", target_uid);
printf("This is because %d is not my Real UID (%d) or Saved Set-UID (%d).\n",
target_uid, original_ruid, original_suid);
} else {
perror("setuid");
}
print_current_uids("After failed setuid");
} else {
printf("setuid(%d) unexpectedly succeeded.\n", target_uid);
print_current_uids("After unexpected setuid");
}
}

printf("\n--- Summary ---\n");
printf("The setuid() function changes the Effective UID of the process.\n");
printf("For root: It can change to any UID, and also changes Real and Saved UID.\n");
printf("For regular users: It can only change Effective UID to Real or Saved UID.\n");
printf("This is crucial for security, especially in programs like setuid binaries.\n");

return 0;
}

10. 编译和运行

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

# 1. 作为普通用户运行
./setuid_example

# 2. 作为 root 用户运行 (需要 sudo 权限)
# 注意:切换到 root 权限执行程序有风险,请小心!
sudo ./setuid_example

11. 预期输出 (作为普通用户运行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
--- Demonstrating setuid ---
&#91;Start] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000

Running as a REGULAR USER (UID: 1000)
Attempting to set UID to my Real UID (1000)...
&#91;Before setuid] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000
setuid(1000) succeeded (as expected).
&#91;After setuid] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000

Attempting to set UID to an invalid/different UID (9999)...
setuid(9999) failed with EPERM (Operation not permitted) - as expected for a regular user.
This is because 9999 is not my Real UID (1000) or Saved Set-UID (1000).
&#91;After failed setuid] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000

--- Summary ---
The setuid() function changes the Effective UID of the process.
For root: It can change to any UID, and also changes Real and Saved UID.
For regular users: It can only change Effective UID to Real or Saved UID.
This is crucial for security, especially in programs like setuid binaries.

12. 预期输出 (使用 sudo 以 root 权限运行)

1
2
3
4
5
6
7
8
9
10
11
--- Demonstrating setuid ---
&#91;Start] Current UIDs - Real: 0, Effective: 0, Saved: 0

Running as ROOT (Privileged User)
Attempting to set UID to 65534 (usually 'nobody' user)...
&#91;Before setuid] Current UIDs - Real: 0, Effective: 0, Saved: 0
setuid(65534) succeeded.
&#91;After setuid] Current UIDs - Real: 65534, Effective: 65534, Saved: 65534
Note: All UIDs (Real, Effective, Saved) are now 65534.
The process is now running with 'nobody' privileges.

13. 关于 Set-UID (SUID) 位的说明

虽然上面的示例是在运行时调用 setuid,但 setuid 系统调用的强大之处还体现在可执行文件的 SUID 位上。

当你将一个可执行文件的 SUID 位设置为 1 时(例如使用 chmod u+s myprogram),会发生以下情况:

无论哪个用户执行这个文件,该进程启动时的有效用户 ID (EUID) 都会被设置为该文件所有者的用户 ID。

这使得普通用户可以运行一个具有文件所有者权限的程序。

例如:

Root 用户创建一个程序 read_etc_shadow,其功能是读取 /etc/shadow 文件(普通用户无权读取)。

Root 将此程序的所有者设为 root,并设置 SUID 位:sudo chown root:root read_etc_shadow && sudo chmod u+s read_etc_shadow。

普通用户 alice 执行 ./read_etc_shadow。

程序启动时,其 euid 是 0 (root),因此它可以成功读取 /etc/shadow。

安全警告:SUID 程序是系统安全的关键点,因为它们允许普通用户临时获得更高的权限。编写 SUID 程序时必须极其小心,避免任何可能导致权限提升的安全漏洞。

14. 总结

setuid 是一个基础且重要的系统调用,用于管理进程的用户权限。理解它的行为规则(尤其是特权用户和普通用户的区别)对于编写安全的 Linux 程序至关重要。它常用于守护进程降权、SUID 程序以及需要特定用户权限的系统管理任务中。

shutdown系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 shutdown 函数,它用于部分或完全地关闭一个面向连接的套接字(如 TCP 套接字)的数据传输。

1. 函数介绍

shutdown 是一个 Linux 系统调用,专门用于更精细地控制已连接套接字的关闭过程。与 close 函数不同(close 会完全关闭套接字,释放其文件描述符),shutdown 允许你:

关闭数据流的一个方向:例如,告诉对方“我不会再发送数据了”(但仍可以接收数据)。

关闭数据流的两个方向:完全禁止在此套接字上进行任何发送和接收操作(但仍保持文件描述符打开,直到调用 close)。

你可以把 shutdown 想象成电话通话中的话筒控制:

  • 全双工通话:你可以说话(发送),也可以听对方说话(接收)。

  • shutdown(SHUT_WR):相当于你按下了“禁麦”按钮。你不能再说话(发送数据),但你仍然可以听到对方说话(接收数据)。

  • shutdown(SHUT_RD):相当于你戴上了耳塞。你听不到对方说话(接收数据),但(理论上)你还可以说话(发送数据)——不过对方可能听不到或会收到错误。

  • shutdown(SHUT_RDWR):相当于你挂断了电话的通话功能。你既不能说也不能听,但电话线(套接字文件描述符)本身可能还没被物理拔掉(close)。

这对于实现优雅的连接关闭(如 TCP 的四次挥手)和单向通信非常有用。

2. 函数原型

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

int shutdown(int sockfd, int how);

3. 功能

  • 部分关闭: 根据 how 参数,关闭套接字 sockfd 的发送能力、接收能力或两者。

  • 发送信号: 对于 TCP 套接字,shutdown 会触发相应的 TCP 连接终止序列(如发送 FIN 包)来通知对端。

  • 状态改变: 改变套接字的内部状态,使其无法再执行被禁止的操作。

4. 参数

  • int sockfd: 这是一个已连接(对于 TCP)或已绑定/连接(对于 UDP,如果使用了 connect)的有效套接字文件描述符。

int how: 这个参数指定了要执行的关闭操作类型。它必须是以下值之一:

  • SHUT_RD: 关闭接收方向。套接字不再接收数据。任何传入的数据都可能被丢弃,后续的 read 或 recv 调用将返回 0(表示 EOF)。

  • SHUT_WR: 关闭发送方向。套接字不再发送数据。对于 TCP,这会发送一个 FIN 包给对方,表示本端不再发送数据。后续的 write 或 send 调用将失败(通常返回错误 EPIPE 或导致 SIGPIPE 信号)。

  • SHUT_RDWR: 关闭接收和发送方向。这相当于同时执行 SHUT_RD 和 SHUT_WR。对于 TCP,这会关闭两个方向的数据流。

5. 返回值

  • 成功时: 返回 0。

  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF sockfd 不是有效的文件描述符,EINVAL how 参数无效,ENOTCONN 套接字未连接等)。

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

  • close: 完全关闭套接字,释放其文件描述符。如果套接字的引用计数变为 0,其效果类似于 shutdown(SHUT_RDWR) 后再释放资源。通常在 shutdown 之后调用 close。

  • read / write / send / recv: shutdown 会影响这些函数的行为。例如,shutdown(SHUT_RD) 后 read 会立即返回 0。

  • TCP 协议: shutdown 的行为与 TCP 连接的状态转换密切相关,特别是 FIN 包的发送和接收。

7. 示例代码

示例 1:TCP 客户端使用 shutdown 实现半关闭

这个例子演示了 TCP 客户端如何在发送完所有数据后,使用 shutdown(SHUT_WR) 告诉服务器它不会再发送更多数据,然后继续接收服务器的回复。

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
// shutdown_client.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8084
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024

int main() {
int sock;
struct sockaddr_in serv_addr;
char *message = "Here is the complete message from client.";
char buffer&#91;BUFFER_SIZE];
ssize_t bytes_sent, bytes_received;

sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}

memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
fprintf(stderr, "Invalid address\n");
close(sock);
exit(EXIT_FAILURE);
}

if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("connection failed");
close(sock);
exit(EXIT_FAILURE);
}

printf("Connected to server.\n");

// 1. 发送数据到服务器
bytes_sent = write(sock, message, strlen(message));
if (bytes_sent < 0) {
perror("write failed");
close(sock);
exit(EXIT_FAILURE);
} else {
printf("Sent %zd bytes to server.\n", bytes_sent);
}

// 2. 关闭发送方向 (SHUT_WR)
// 这告诉服务器:'我的数据发完了,不会再发了'
// 但客户端仍然可以接收服务器发送的数据
printf("Shutting down write direction (SHUT_WR)...\n");
if (shutdown(sock, SHUT_WR) < 0) {
perror("shutdown SHUT_WR failed");
// 即使 shutdown 失败,也应尝试关闭套接字
} else {
printf("Write direction shut down successfully.\n");
}

// 3. 继续接收服务器的回复
printf("Now reading server's response...\n");
while ((bytes_received = read(sock, buffer, BUFFER_SIZE - 1)) > 0) {
buffer&#91;bytes_received] = '\0';
printf("Received from server: %s", buffer);
}

if (bytes_received < 0) {
perror("read failed");
} else {
printf("Server closed connection (EOF received).\n");
}

// 4. 最后关闭套接字文件描述符
close(sock);
printf("Client socket closed.\n");

return 0;
}

代码解释:

客户端创建 TCP 套接字并连接到服务器。

使用 write 向服务器发送一条消息。

关键步骤: 调用 shutdown(sock, SHUT_WR)。

  • 这会向服务器发送一个 TCP FIN 包,表明客户端不会再发送数据。

  • 服务器的 read 调用在收到这个 FIN 后会返回 0(EOF)。

  • 但是,客户端的套接字仍然打开,并且仍然可以接收数据。

客户端进入一个 while 循环,使用 read 继续接收服务器可能发送的任何回复数据,直到服务器也关闭连接(read 返回 0)。

最后,调用 close(sock) 完全关闭套接字文件描述符。

示例 2:TCP 服务器使用 shutdown 响应客户端

这个例子演示了 TCP 服务器如何在收到客户端的 FIN(即 read 返回 0)后,使用 shutdown 和 close 来优雅地关闭连接。

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

#define PORT 8084
#define BACKLOG 10
#define BUFFER_SIZE 1024

int main() {
int server_fd, client_fd;
struct sockaddr_in address, client_address;
socklen_t client_addr_len = sizeof(client_address);
char buffer&#91;BUFFER_SIZE];
char *reply = "Server received your message. Here is the server's final reply.";
ssize_t bytes_received;
int opt = 1;

server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt failed");
close(server_fd);
exit(EXIT_FAILURE);
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}

if (listen(server_fd, BACKLOG) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}

printf("Server listening on port %d\n", PORT);

client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len);
if (client_fd < 0) {
perror("accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}

printf("Client connected.\n");

// 1. 从客户端接收数据
printf("Receiving data from client...\n");
while ((bytes_received = read(client_fd, buffer, BUFFER_SIZE - 1)) > 0) {
buffer&#91;bytes_received] = '\0';
printf("Received from client: %s", buffer);
}

if (bytes_received < 0) {
perror("read failed");
close(client_fd);
close(server_fd);
exit(EXIT_FAILURE);
} else {
// bytes_received == 0, 表示客户端已关闭发送方向 (SHUT_WR)
printf("Client has shut down its write direction (EOF received).\n");
}

// 2. 向客户端发送最终回复
printf("Sending final reply to client...\n");
if (write(client_fd, reply, strlen(reply)) != (ssize_t)strlen(reply)) {
perror("write reply failed");
} else {
printf("Final reply sent.\n");
}

// 3. 关闭服务器的写方向
// 这会向客户端发送 FIN,表明服务器也不会再发送数据
printf("Shutting down server's write direction (SHUT_WR)...\n");
if (shutdown(client_fd, SHUT_WR) < 0) {
perror("shutdown SHUT_WR failed");
} else {
printf("Server's write direction shut down.\n");
}

// 4. (可选) 继续等待一段时间,看客户端是否也关闭
// 在这个简单例子中,我们直接关闭
printf("Closing client socket.\n");
close(client_fd);

close(server_fd);
printf("Server sockets closed.\n");

return 0;
}

代码解释:

服务器创建、绑定、监听套接字,并 accept 客户端连接。

服务器进入一个 while 循环,使用 read 从客户端接收数据。

当 read 返回 0 时,表示客户端已调用 shutdown(SHUT_WR) 或 close,其发送方向已关闭。

服务器向客户端发送一个最终的回复消息。

关键步骤: 服务器调用 shutdown(client_fd, SHUT_WR)。

  • 这会向客户端发送一个 TCP FIN 包。

  • 客户端的 read 调用在收到这个 FIN 后会返回 0。

最后,服务器调用 close(client_fd) 完全关闭与该客户端的连接。

示例 3:对比 shutdown 和 close

这个例子通过伪代码和解释来说明 shutdown 和 close 的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 假设 sock 是一个已连接的 TCP 套接字

// --- 情况一:只使用 close ---
write(sock, "Hello", 5);
close(sock); // 1. 发送 FIN (如果这是最后一个引用)
// 2. 释放文件描述符
// 3. 内核可能立即终止连接或尝试优雅关闭

// --- 情况二:使用 shutdown 后再 close (优雅关闭) ---
write(sock, "Hello", 5);
shutdown(sock, SHUT_WR); // 1. 发送 FIN,告诉对方'我发完了'
// 2. 套接字仍然打开,仍可 read

char buffer&#91;1024];
ssize_t n;
while ((n = read(sock, buffer, sizeof(buffer))) > 0) {
// 处理客户端最后发送的数据
}
// read 返回 0,表示客户端也关闭了

close(sock); // 3. 此时 close 只是释放本地文件描述符
// TCP 连接已经通过 FIN/ACK 交互优雅地关闭了

解释:

  • 仅使用 close: 这种方式简单直接。当 close 被调用且该套接字的引用计数变为 0 时,内核会尝试关闭连接。这通常涉及发送 FIN,但整个过程是隐式的。如果在发送缓冲区还有数据时立即 close,行为可能取决于系统实现(数据可能被发送,也可能被丢弃)。

使用 shutdown + close: 这是一种更优雅和明确的关闭方式。

  • 发送完数据后,调用 shutdown(SHUT_WR) 明确表示“数据发送完毕”。这会可靠地发送 FIN 给对方。

  • 程序继续使用 read 来接收对方可能在收到 FIN 后发送的剩余数据。

  • 当 read 也返回 0(收到对方的 FIN 并回复 ACK)时,双方都确认了连接的单向关闭。

  • 最后调用 close 仅仅是清理本地资源(文件描述符)。

重要提示与注意事项:

仅适用于连接型套接字: shutdown 主要用于面向连接的套接字,如 TCP (SOCK_STREAM)。对于无连接的套接字,如 UDP (SOCK_DGRAM),它的行为是未定义的或没有意义的。

不释放文件描述符: shutdown 不会关闭套接字的文件描述符。你仍然需要调用 close() 来最终释放资源。

TCP 语义: shutdown 的行为与底层 TCP 协议紧密相关。SHUT_WR 导致发送 FIN,SHUT_RD 影响接收缓冲区的行为。

优雅关闭: 在需要确保所有数据都被发送和接收的场景中(如 HTTP/1.1 Connection: close),使用 shutdown 是实现优雅关闭的标准方法。

错误处理: 始终检查 shutdown 的返回值。在套接字已经关闭或无效时调用它会失败。

SHUT_RD 的实用性: SHUT_RD 的使用场景相对较少。关闭接收通常意味着你不再关心对方的数据,直接 close 或在 read 返回 0 后 close 通常就足够了。

总结:

shutdown 是一个用于精细控制 TCP 连接关闭过程的系统调用。它允许程序在完全终止连接之前,单方面地关闭数据流的一个或两个方向。这对于实现协议规定的优雅关闭序列(如 HTTP)和处理单向数据流非常重要。理解它与 close 的区别,并在需要时正确使用它,是编写健壮网络应用程序的关键技能之一。

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

sigaltstack系统调用及示例

sigaltstack 函数详解

  1. 函数介绍

sigaltstack 是Linux系统调用,用于设置和获取信号处理程序的备用栈(alternate signal stack)。当进程收到信号时,内核通常在当前栈上执行信号处理程序。使用 sigaltstack 可以为信号处理程序指定一个独立的栈空间,这对于处理栈溢出等异常情况特别有用。

  1. 函数原型
1
2
3
#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *oss);

  1. 功能

sigaltstack 允许进程为信号处理程序设置一个备用的栈空间。当信号被递送到使用备用栈的信号处理程序时,内核会切换到备用栈执行信号处理程序,执行完毕后再切换回原来的栈。

  1. 参数
  • *const stack_t ss: 指向新栈设置的指针(NULL表示不改变当前设置)

  • *stack_t oss: 指向存储旧栈设置的指针(NULL表示不获取旧设置)

  1. 返回值
  • 成功: 返回0

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

  1. 相似函数,或关联函数
  • signal/sigaction: 设置信号处理程序

  • sigprocmask: 设置信号屏蔽字

  • setjmp/longjmp: 非局部跳转

  • getcontext/setcontext: 上下文操作

  1. 示例代码

示例1:基础sigaltstack使用

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

/**
* 信号栈结构体
*/
typedef struct {
void *stack_ptr;
size_t stack_size;
int is_active;
} signal_stack_t;

/**
* 显示当前信号栈信息
*/
void show_signal_stack_info() {
stack_t current_stack;

if (sigaltstack(NULL, &current_stack) == 0) {
printf("=== 当前信号栈信息 ===\n");
printf("栈指针: %p\n", current_stack.ss_sp);
printf("栈大小: %zu 字节\n", current_stack.ss_size);
printf("栈标志: ");
if (current_stack.ss_flags & SS_ONSTACK) {
printf("SS_ONSTACK (正在使用中)\n");
} else if (current_stack.ss_flags & SS_DISABLE) {
printf("SS_DISABLE (已禁用)\n");
} else {
printf("SS_ENABLED (已启用)\n");
}
printf("\n");
} else {
printf("获取信号栈信息失败: %s\n", strerror(errno));
}
}

/**
* 信号处理程序
*/
void signal_handler(int sig) {
stack_t current_stack;

printf("信号处理程序执行中 (信号: %d)\n", sig);

// 检查当前是否在备用栈上
if (sigaltstack(NULL, &current_stack) == 0) {
if (current_stack.ss_flags & SS_ONSTACK) {
printf(" ✓ 正在备用栈上执行\n");
} else {
printf(" ✗ 在主栈上执行\n");
}
}

printf(" 当前栈指针: %p\n", &sig);
printf(" 信号处理完成\n\n");
}

/**
* 演示基础sigaltstack使用方法
*/
int demo_sigaltstack_basic() {
stack_t new_stack, old_stack;
char *stack_buffer;
struct sigaction sa;

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

// 显示原始信号栈信息
printf("1. 原始信号栈信息:\n");
show_signal_stack_info();

// 分配备用栈空间
size_t stack_size = SIGSTKSZ; // 系统推荐的栈大小
stack_buffer = malloc(stack_size);
if (!stack_buffer) {
perror("分配栈空间失败");
return -1;
}

printf("2. 分配备用栈空间:\n");
printf(" 栈大小: %zu 字节\n", stack_size);
printf(" 栈地址: %p\n", (void*)stack_buffer);

// 设置备用栈
new_stack.ss_sp = stack_buffer;
new_stack.ss_size = stack_size;
new_stack.ss_flags = 0; // 启用栈

printf("3. 设置备用信号栈:\n");
if (sigaltstack(&new_stack, &old_stack) == 0) {
printf(" ✓ 备用栈设置成功\n");
show_signal_stack_info();
} else {
printf(" ✗ 备用栈设置失败: %s\n", strerror(errno));
free(stack_buffer);
return -1;
}

// 设置使用备用栈的信号处理程序
printf("4. 设置信号处理程序:\n");
memset(&sa, 0, sizeof(sa));
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK; // 使用备用栈

if (sigaction(SIGUSR1, &sa, NULL) == 0) {
printf(" ✓ 信号处理程序设置成功 (使用备用栈)\n");
} else {
printf(" ✗ 信号处理程序设置失败: %s\n", strerror(errno));
free(stack_buffer);
return -1;
}

// 发送信号测试
printf("5. 发送测试信号:\n");
if (kill(getpid(), SIGUSR1) == 0) {
printf(" ✓ 信号发送成功\n");
sleep(1); // 等待信号处理完成
} else {
printf(" ✗ 信号发送失败: %s\n", strerror(errno));
}

// 禁用备用栈
printf("6. 禁用备用栈:\n");
stack_t disable_stack;
disable_stack.ss_flags = SS_DISABLE;

if (sigaltstack(&disable_stack, NULL) == 0) {
printf(" ✓ 备用栈禁用成功\n");
show_signal_stack_info();
} else {
printf(" ✗ 备用栈禁用失败: %s\n", strerror(errno));
}

// 清理资源
free(stack_buffer);

return 0;
}

int main() {
return demo_sigaltstack_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
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <setjmp.h>

/**
* 栈溢出保护结构
*/
typedef struct {
stack_t alt_stack;
char *stack_buffer;
sigjmp_buf jump_buffer;
int overflow_detected;
} stack_protection_t;

/**
* 栈溢出信号处理程序
*/
void stack_overflow_handler(int sig) {
printf("⚠ 检测到栈溢出异常 (信号: %d)\n", sig);

// 检查是否在备用栈上
stack_t current_stack;
if (sigaltstack(NULL, &current_stack) == 0) {
if (current_stack.ss_flags & SS_ONSTACK) {
printf(" ✓ 在备用栈上安全处理异常\n");
} else {
printf(" ✗ 不在备用栈上 (异常情况)\n");
}
}

// 恢复到安全状态
printf(" 跳转到安全恢复点...\n");
siglongjmp(((stack_protection_t*)NULL)->jump_buffer, 1);
}

/**
* 递归函数(可能导致栈溢出)
*/
void recursive_function(int depth) {
char buffer&#91;1024]; // 消耗栈空间

// 填充缓冲区以确保栈使用
memset(buffer, depth & 0xFF, sizeof(buffer));

printf("递归深度: %d, 栈地址: %p\n", depth, (void*)&buffer);

// 深度递归可能导致栈溢出
if (depth < 1000) { // 限制递归深度
recursive_function(depth + 1);
}
}

/**
* 安全的递归执行
*/
int safe_recursive_execution(stack_protection_t *protection) {
struct sigaction sa;
int result;

printf("=== 栈溢出保护演示 ===\n");

// 设置信号处理程序
memset(&sa, 0, sizeof(sa));
sa.sa_handler = stack_overflow_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;

if (sigaction(SIGSEGV, &sa, NULL) != 0) {
printf("设置SIGSEGV处理程序失败: %s\n", strerror(errno));
return -1;
}

if (sigaction(SIGBUS, &sa, NULL) != 0) {
printf("设置SIGBUS处理程序失败: %s\n", strerror(errno));
return -1;
}

// 设置跳转点
result = sigsetjmp(protection->jump_buffer, 1);
if (result == 0) {
printf("开始安全递归执行...\n");

// 执行可能引起栈溢出的操作
recursive_function(0);

printf("递归执行正常完成\n");
return 0;
} else {
printf("✓ 从栈溢出异常中成功恢复\n");
return 1;
}
}

/**
* 演示栈溢出保护
*/
int demo_stack_overflow_protection() {
stack_protection_t protection = {0};
size_t stack_size = SIGSTKSZ * 2; // 更大的备用栈

printf("=== 栈溢出保护演示 ===\n");

// 分配备用栈
protection.stack_buffer = malloc(stack_size);
if (!protection.stack_buffer) {
perror("分配备用栈失败");
return -1;
}

printf("分配备用栈: %zu 字节\n", stack_size);

// 设置备用栈
protection.alt_stack.ss_sp = protection.stack_buffer;
protection.alt_stack.ss_size = stack_size;
protection.alt_stack.ss_flags = 0;

if (sigaltstack(&protection.alt_stack, NULL) != 0) {
printf("设置备用栈失败: %s\n", strerror(errno));
free(protection.stack_buffer);
return -1;
}

printf("备用栈设置成功\n");
show_signal_stack_info();

// 执行安全递归
int result = safe_recursive_execution(&protection);

// 清理资源
free(protection.stack_buffer);

return result;
}

// 辅助函数声明
void show_signal_stack_info();

int main() {
return demo_stack_overflow_protection();
}

示例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
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

/**
* 多栈管理器
*/
typedef struct {
stack_t stack1;
stack_t stack2;
char *buffer1;
char *buffer2;
int current_stack;
} multi_stack_manager_t;

/**
* 信号处理程序1(使用栈1)
*/
void signal_handler_1(int sig) {
printf("信号处理程序1执行 (信号: %d)\n", sig);

stack_t current_stack;
if (sigaltstack(NULL, &current_stack) == 0) {
if (current_stack.ss_flags & SS_ONSTACK) {
printf(" 使用备用栈1执行\n");
}
}

printf(" 处理程序1完成\n");
}

/**
* 信号处理程序2(使用栈2)
*/
void signal_handler_2(int sig) {
printf("信号处理程序2执行 (信号: %d)\n", sig);

stack_t current_stack;
if (sigaltstack(NULL, &current_stack) == 0) {
if (current_stack.ss_flags & SS_ONSTACK) {
printf(" 使用备用栈2执行\n");
}
}

printf(" 处理程序2完成\n");
}

/**
* 初始化多栈管理器
*/
int init_multi_stack_manager(multi_stack_manager_t *manager) {
size_t stack_size = SIGSTKSZ;

printf("=== 多栈管理器初始化 ===\n");

// 分配两个栈空间
manager->buffer1 = malloc(stack_size);
manager->buffer2 = malloc(stack_size);

if (!manager->buffer1 || !manager->buffer2) {
printf("分配栈空间失败\n");
if (manager->buffer1) free(manager->buffer1);
if (manager->buffer2) free(manager->buffer2);
return -1;
}

printf("栈1地址: %p, 大小: %zu\n", (void*)manager->buffer1, stack_size);
printf("栈2地址: %p, 大小: %zu\n", (void*)manager->buffer2, stack_size);

// 初始化栈结构
manager->stack1.ss_sp = manager->buffer1;
manager->stack1.ss_size = stack_size;
manager->stack1.ss_flags = 0;

manager->stack2.ss_sp = manager->buffer2;
manager->stack2.ss_size = stack_size;
manager->stack2.ss_flags = 0;

manager->current_stack = 0;

return 0;
}

/**
* 切换备用栈
*/
int switch_alternate_stack(multi_stack_manager_t *manager, int stack_id) {
stack_t *target_stack = (stack_id == 1) ? &manager->stack1 : &manager->stack2;

printf("切换到备用栈 %d\n", stack_id);

if (sigaltstack(target_stack, NULL) == 0) {
printf("✓ 成功切换到备用栈 %d\n", stack_id);
manager->current_stack = stack_id;
return 0;
} else {
printf("✗ 切换备用栈 %d 失败: %s\n", stack_id, strerror(errno));
return -1;
}
}

/**
* 演示多信号处理程序
*/
int demo_multi_signal_handlers() {
multi_stack_manager_t manager = {0};
struct sigaction sa1, sa2;

printf("=== 多信号处理程序演示 ===\n");

// 初始化多栈管理器
if (init_multi_stack_manager(&manager) != 0) {
printf("初始化多栈管理器失败\n");
return -1;
}

// 设置信号处理程序1(使用栈1)
printf("\n设置信号处理程序1:\n");
switch_alternate_stack(&manager, 1);

memset(&sa1, 0, sizeof(sa1));
sa1.sa_handler = signal_handler_1;
sigemptyset(&sa1.sa_mask);
sa1.sa_flags = SA_ONSTACK;

if (sigaction(SIGUSR1, &sa1, NULL) == 0) {
printf("✓ 信号处理程序1设置成功\n");
} else {
printf("✗ 信号处理程序1设置失败: %s\n", strerror(errno));
}

// 设置信号处理程序2(使用栈2)
printf("\n设置信号处理程序2:\n");
switch_alternate_stack(&manager, 2);

memset(&sa2, 0, sizeof(sa2));
sa2.sa_handler = signal_handler_2;
sigemptyset(&sa2.sa_mask);
sa2.sa_flags = SA_ONSTACK;

if (sigaction(SIGUSR2, &sa2, NULL) == 0) {
printf("✓ 信号处理程序2设置成功\n");
} else {
printf("✗ 信号处理程序2设置失败: %s\n", strerror(errno));
}

// 测试信号处理
printf("\n测试信号处理:\n");

// 发送SIGUSR1
printf("发送SIGUSR1信号:\n");
switch_alternate_stack(&manager, 1);
if (kill(getpid(), SIGUSR1) == 0) {
printf("✓ SIGUSR1发送成功\n");
sleep(1);
}

// 发送SIGUSR2
printf("发送SIGUSR2信号:\n");
switch_alternate_stack(&manager, 2);
if (kill(getpid(), SIGUSR2) == 0) {
printf("✓ SIGUSR2发送成功\n");
sleep(1);
}

// 清理资源
free(manager.buffer1);
free(manager.buffer2);

return 0;
}

int main() {
return demo_multi_signal_handlers();
}

示例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 <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

/**
* 线程信号栈管理器
*/
typedef struct {
stack_t signal_stack;
char *stack_buffer;
pthread_t thread_id;
int thread_num;
} thread_stack_manager_t;

/**
* 线程信号处理程序
*/
void thread_signal_handler(int sig) {
pthread_t current_thread = pthread_self();

printf("线程 %lu 收到信号 %d\n", (unsigned long)current_thread, sig);

stack_t current_stack;
if (sigaltstack(NULL, &current_stack) == 0) {
if (current_stack.ss_flags & SS_ONSTACK) {
printf(" 线程 %lu 在备用栈上处理信号\n", (unsigned long)current_thread);
} else {
printf(" 线程 %lu 在主栈上处理信号\n", (unsigned long)current_thread);
}
}

printf(" 线程 %lu 信号处理完成\n", (unsigned long)current_thread);
}

/**
* 初始化线程栈管理器
*/
int init_thread_stack_manager(thread_stack_manager_t *manager, int thread_num) {
size_t stack_size = SIGSTKSZ;

manager->thread_num = thread_num;
manager->thread_id = pthread_self();

// 分配栈空间
manager->stack_buffer = malloc(stack_size);
if (!manager->stack_buffer) {
printf("线程 %d: 分配栈空间失败\n", thread_num);
return -1;
}

// 初始化栈结构
manager->signal_stack.ss_sp = manager->stack_buffer;
manager->signal_stack.ss_size = stack_size;
manager->signal_stack.ss_flags = 0;

printf("线程 %d: 分配备用栈 %p, 大小 %zu\n",
thread_num, (void*)manager->stack_buffer, stack_size);

return 0;
}

/**
* 线程工作函数
*/
void* thread_worker(void *arg) {
thread_stack_manager_t *manager = (thread_stack_manager_t*)arg;
struct sigaction sa;
sigset_t set;

printf("工作线程 %d 启动 (ID: %lu)\n",
manager->thread_num, (unsigned long)manager->thread_id);

// 设置备用栈
if (sigaltstack(&manager->signal_stack, NULL) != 0) {
printf("线程 %d: 设置备用栈失败: %s\n",
manager->thread_num, strerror(errno));
return NULL;
}

printf("线程 %d: 备用栈设置成功\n", manager->thread_num);

// 设置信号处理程序
memset(&sa, 0, sizeof(sa));
sa.sa_handler = thread_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;

if (sigaction(SIGUSR1, &sa, NULL) != 0) {
printf("线程 %d: 设置信号处理程序失败: %s\n",
manager->thread_num, strerror(errno));
return NULL;
}

printf("线程 %d: 信号处理程序设置成功\n", manager->thread_num);

// 阻塞SIGUSR1以便测试
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &set, NULL);

// 执行工作
for (int i = 0; i < 5; i++) {
printf("线程 %d: 工作中... (%d/5)\n", manager->thread_num, i + 1);
sleep(2);
}

// 解除阻塞并等待信号
printf("线程 %d: 等待信号...\n", manager->thread_num);
pthread_sigmask(SIG_UNBLOCK, &set, NULL);

// 等待一段时间让信号处理完成
sleep(2);

printf("线程 %d: 工作完成\n", manager->thread_num);
return NULL;
}

/**
* 演示线程安全的信号栈管理
*/
int demo_thread_safe_signal_stacks() {
const int num_threads = 3;
pthread_t threads&#91;num_threads];
thread_stack_manager_t managers&#91;num_threads];
sigset_t set;

printf("=== 线程安全的信号栈管理演示 ===\n");

// 阻塞SIGUSR1信号
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &set, NULL);

// 创建工作线程
printf("创建 %d 个工作线程:\n", num_threads);

for (int i = 0; i < num_threads; i++) {
if (init_thread_stack_manager(&managers&#91;i], i + 1) != 0) {
printf("初始化线程 %d 失败\n", i + 1);
// 清理已分配的资源
for (int j = 0; j < i; j++) {
free(managers&#91;j].stack_buffer);
}
return -1;
}

if (pthread_create(&threads&#91;i], NULL, thread_worker, &managers&#91;i]) != 0) {
printf("创建线程 %d 失败\n", i + 1);
free(managers&#91;i].stack_buffer);
// 清理已分配的资源
for (int j = 0; j < i; j++) {
free(managers&#91;j].stack_buffer);
}
return -1;
}

managers&#91;i].thread_id = threads&#91;i];
printf("创建线程 %d: ID=%lu\n", i + 1, (unsigned long)threads&#91;i]);
}

// 等待线程启动
sleep(1);

// 向所有线程发送信号
printf("\n向所有线程发送SIGUSR1信号:\n");
pthread_sigmask(SIG_UNBLOCK, &set, NULL);

for (int i = 0; i < num_threads; i++) {
// 注意:实际应用中需要更精确的线程信号发送方法
if (kill(getpid(), SIGUSR1) == 0) {
printf("向线程 %d 发送信号成功\n", i + 1);
} else {
printf("向线程 %d 发送信号失败: %s\n", i + 1, strerror(errno));
}
sleep(1); // 间隔发送
}

// 等待所有线程完成
printf("\n等待所有线程完成:\n");
for (int i = 0; i < num_threads; i++) {
void *result;
pthread_join(threads&#91;i], &result);
printf("线程 %d 已完成\n", i + 1);

// 清理资源
free(managers&#91;i].stack_buffer);
}

return 0;
}

int main() {
return demo_thread_safe_signal_stacks();
}

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

/**
* 信号栈监控器
*/
typedef struct {
stack_t original_stack;
stack_t current_stack;
int stack_switch_count;
size_t total_stack_size;
time_t last_switch_time;
} stack_monitor_t;

/**
* 详细的栈信息显示
*/
void show_detailed_stack_info(const char *context) {
stack_t stack_info;

printf("=== %s ===\n", context);

if (sigaltstack(NULL, &stack_info) == 0) {
printf("栈指针: %p\n", stack_info.ss_sp);
printf("栈大小: %zu 字节\n", stack_info.ss_size);
printf("栈标志: 0x%x\n", stack_info.ss_flags);

if (stack_info.ss_flags & SS_ONSTACK) {
printf("状态: 正在使用备用栈\n");
} else if (stack_info.ss_flags & SS_DISABLE) {
printf("状态: 备用栈已禁用\n");
} else {
printf("状态: 备用栈已启用但未使用\n");
}

// 计算栈使用情况(简化版)
void *current_sp;
asm volatile("mov %%rsp, %0" : "=r"(current_sp));
printf("当前栈指针: %p\n", current_sp);

if (stack_info.ss_sp) {
ptrdiff_t distance = (char*)current_sp - (char*)stack_info.ss_sp;
printf("距离栈底: %td 字节\n", distance);

if (distance > 0 && (size_t)distance < stack_info.ss_size) {
double usage = (double)distance / stack_info.ss_size * 100;
printf("栈使用率: %.1f%%\n", usage);
}
}
} else {
printf("获取栈信息失败: %s\n", strerror(errno));
}

printf("\n");
}

/**
* 信号处理程序(带监控)
*/
void monitored_signal_handler(int sig) {
static int call_count = 0;
call_count++;

printf("监控信号处理程序执行 (第 %d 次, 信号: %d)\n", call_count, sig);

show_detailed_stack_info("信号处理程序中的栈状态");

// 模拟一些栈使用
char local_buffer&#91;512];
memset(local_buffer, call_count & 0xFF, sizeof(local_buffer));

printf(" 处理程序使用了 %zu 字节本地缓冲区\n", sizeof(local_buffer));
printf(" 处理程序执行完成\n\n");
}

/**
* 栈使用压力测试
*/
void stack_pressure_test(int depth) {
char buffer&#91;1024]; // 每层消耗1KB栈空间

// 填充缓冲区
memset(buffer, depth & 0xFF, sizeof(buffer));

if (depth < 50) { // 限制递归深度
printf("递归深度: %d, 使用栈空间: %d KB\n", depth, depth);
stack_pressure_test(depth + 1);
} else {
printf("达到最大递归深度: %d\n", depth);
}
}

/**
* 演示信号栈监控和调试
*/
int demo_stack_monitoring() {
stack_t alt_stack, old_stack;
char *stack_buffer;
struct sigaction sa;
struct rlimit rl;

printf("=== 信号栈监控和调试演示 ===\n");

// 显示系统栈限制
printf("1. 系统栈限制信息:\n");
if (getrlimit(RLIMIT_STACK, &rl) == 0) {
printf(" 主栈大小限制: %ld 字节", rl.rlim_cur);
if (rl.rlim_cur == RLIM_INFINITY) {
printf(" (无限制)");
}
printf("\n");
printf(" 最大栈大小: %ld 字节", rl.rlim_max);
if (rl.rlim_max == RLIM_INFINITY) {
printf(" (无限制)");
}
printf("\n");
}

// 显示初始栈状态
show_detailed_stack_info("初始栈状态");

// 分配备用栈
size_t stack_size = SIGSTKSZ * 4; // 4倍标准大小
stack_buffer = malloc(stack_size);
if (!stack_buffer) {
perror("分配备用栈失败");
return -1;
}

printf("2. 分配备用栈:\n");
printf(" 请求大小: %zu 字节 (%.1f KB)\n", stack_size, stack_size / 1024.0);
printf(" 分配地址: %p\n", (void*)stack_buffer);

// 设置备用栈
alt_stack.ss_sp = stack_buffer;
alt_stack.ss_size = stack_size;
alt_stack.ss_flags = 0;

printf("3. 设置备用栈:\n");
if (sigaltstack(&alt_stack, &old_stack) == 0) {
printf(" ✓ 备用栈设置成功\n");
show_detailed_stack_info("设置备用栈后");
} else {
printf(" ✗ 备用栈设置失败: %s\n", strerror(errno));
free(stack_buffer);
return -1;
}

// 设置监控信号处理程序
printf("4. 设置监控信号处理程序:\n");
memset(&sa, 0, sizeof(sa));
sa.sa_handler = monitored_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;

if (sigaction(SIGUSR1, &sa, NULL) == 0) {
printf(" ✓ 监控信号处理程序设置成功\n");
} else {
printf(" ✗ 监控信号处理程序设置失败: %s\n", strerror(errno));
free(stack_buffer);
return -1;
}

// 发送多个信号进行测试
printf("5. 发送测试信号:\n");
for (int i = 1; i <= 3; i++) {
printf(" 发送第 %d 个信号:\n", i);
if (kill(getpid(), SIGUSR1) == 0) {
printf(" ✓ 信号发送成功\n");
sleep(1); // 等待处理完成
} else {
printf(" ✗ 信号发送失败: %s\n", strerror(errno));
}
}

// 栈压力测试
printf("6. 栈压力测试:\n");
printf(" 开始递归栈使用测试...\n");
stack_pressure_test(0);
printf(" 栈压力测试完成\n");

show_detailed_stack_info("压力测试后栈状态");

// 禁用备用栈
printf("7. 禁用备用栈:\n");
stack_t disable_stack;
disable_stack.ss_flags = SS_DISABLE;

if (sigaltstack(&disable_stack, NULL) == 0) {
printf(" ✓ 备用栈禁用成功\n");
show_detailed_stack_info("禁用备用栈后");
} else {
printf(" ✗ 备用栈禁用失败: %s\n", strerror(errno));
}

// 清理资源
free(stack_buffer);

printf("=== 监控演示完成 ===\n");
return 0;
}

int main() {
return demo_stack_monitoring();
}

sigaltstack 使用注意事项

系统要求:

内核版本: 支持信号备用栈的Linux内核

权限要求: 通常不需要特殊权限

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

栈大小考虑:

最小大小: 至少MINSIGSTKSZ字节

推荐大小: 使用SIGSTKSZ或更大

动态分配: 建议在堆上分配栈空间

错误处理:

ENOMEM: 内存不足

EINVAL: 参数无效

EPERM: 权限不足

安全考虑:

栈溢出保护: 备用栈可以防止主栈溢出

信号安全: 确保信号处理程序的安全执行

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

最佳实践:

适当大小: 根据信号处理程序的需求分配栈大小

错误检查: 始终检查sigaltstack的返回值

资源清理: 程序结束时释放栈空间

线程安全: 多线程环境中每个线程需要独立的栈

监控调试: 监控栈使用情况以便调试

信号栈标志说明

SS_ONSTACK:

  • 含义: 当前正在使用备用栈执行信号处理程序

  • 用途: 检查信号处理程序是否在备用栈上执行

SS_DISABLE:

  • 含义: 备用栈被禁用

  • 用途: 禁用备用栈功能

SS_ENABLED:

  • 含义: 备用栈已启用但未使用

  • 用途: 正常状态,可以使用备用栈

相关常量

SIGSTKSZ:

  • 含义: 系统推荐的信号栈大小

  • 典型值: 8KB或更大

MINSIGSTKSZ:

  • 含义: 信号栈的最小大小

  • 典型值: 2KB

常见使用场景

1. 栈溢出保护:

1
2
3
4
5
6
7
// 为可能引起栈溢出的程序设置备用栈
stack_t alt_stack;
alt_stack.ss_sp = malloc(SIGSTKSZ);
alt_stack.ss_size = SIGSTKSZ;
alt_stack.ss_flags = 0;
sigaltstack(&alt_stack, NULL);

2. 信号处理程序:

1
2
3
4
5
6
// 为信号处理程序设置独立的执行环境
struct sigaction sa;
sa.sa_handler = signal_handler;
sa.sa_flags = SA_ONSTACK;
sigaction(SIGSEGV, &sa, NULL);

3. 多线程应用:

1
2
3
4
5
6
7
8
9
10
// 每个线程设置独立的信号栈
void* thread_function(void *arg) {
stack_t thread_stack;
thread_stack.ss_sp = malloc(SIGSTKSZ);
thread_stack.ss_size = SIGSTKSZ;
thread_stack.ss_flags = 0;
sigaltstack(&thread_stack, NULL);
// 线程工作...
}

总结

sigaltstack 是Linux系统中重要的信号处理机制,提供了:

栈隔离: 为信号处理程序提供独立的执行环境

异常处理: 防止栈溢出等异常情况

安全执行: 确保信号处理程序的安全执行

灵活配置: 支持动态栈管理和配置

通过合理使用 sigaltstack,可以构建更加健壮和安全的信号处理系统。在实际应用中,需要注意栈大小、错误处理和资源管理等关键问题。

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

setxattr系统调用及示例

setxattr 函数详解

  1. 函数介绍

setxattr 是 Linux 系统中用于设置文件扩展属性(Extended Attributes,简称 xattrs)的系统调用。可以把扩展属性想象成”文件的隐藏标签”或”元数据贴纸”——它们是附加在文件上的额外信息,不会影响文件内容,但可以存储各种有用的元数据。

就像你可以在文件上贴便利贴记录信息一样,扩展属性允许你为文件附加自定义的元数据,比如:

  • 安全标签(SELinux 等)

  • 访问控制列表

  • 用户自定义的注释或标记

  • 备份状态信息

  • 文件分类标签

setxattr 函数让你能够为文件设置这些”便利贴”,而不需要修改文件内容本身。

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

int setxattr(const char *path, const char *name,
const void *value, size_t size, int flags);

  1. 功能

setxattr 函数用于为指定路径的文件设置扩展属性。它允许你为文件附加自定义的元数据,这些元数据与文件内容分离存储,但与文件关联。

  1. 参数
  • path: 指向文件路径的指针

  • name: 扩展属性的名称(字符串格式)

  • value: 指向属性值的指针

  • size: 属性值的大小(以字节为单位)

  • flags: 控制操作行为的标志位

  1. 扩展属性命名规范

扩展属性名称通常采用以下格式:

  • namespace.attribute_name

常见命名空间:

  • user.*: 用户自定义属性(最常用,需要文件写权限)

  • trusted.*: 受信任的属性(只有特权用户可访问)

  • system.*: 系统属性(由内核或系统服务使用)

  • security.*: 安全相关属性(如 SELinux 标签)

  1. 标志位(flags 参数)

标志值说明00如果属性存在则替换,不存在则创建XATTR_CREATE0x1仅当属性不存在时创建(类似”新建”)XATTR_REPLACE0x2仅当属性已存在时替换(类似”更新”)

  1. 返回值
  • 成功: 返回 0

  • 失败: 返回 -1,并设置相应的 errno 错误码

  1. 常见错误码
  • EACCES: 权限不足

  • ENOTSUP: 文件系统不支持扩展属性

  • ENOSPC: 磁盘空间不足

  • EEXIST: 使用 XATTR_CREATE 时属性已存在

  • ENOATTR: 使用 XATTR_REPLACE 时属性不存在

  • ENAMETOOLONG: 属性名称过长

  • EINVAL: 参数无效

  • EROFS: 文件系统只读

  • EFAULT: 参数指针无效

  1. 相似函数或关联函数
  • lsetxattr: 通过文件路径设置符号链接本身的扩展属性(不跟随符号链接)

  • fsetxattr: 通过文件描述符设置扩展属性

  • getxattr: 获取文件扩展属性的值

  • listxattr: 列出文件的所有扩展属性名称

  • removexattr: 删除文件的扩展属性

  • attr/xattr 命令行工具: 命令行界面的扩展属性操作

  1. 示例代码

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

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

const char *content = "这是一个测试文件\n用于演示扩展属性功能\n";
write(fd, content, strlen(content));
close(fd);

printf("✓ 创建测试文件: %s\n", filename);
return 0;
}

// 获取并显示扩展属性
void show_xattr_value(const char *filename, const char *attr_name) {
char buffer&#91;1024];
ssize_t size = getxattr(filename, attr_name, buffer, sizeof(buffer) - 1);

if (size != -1) {
buffer&#91;size] = '\0';
printf(" %s = '%s'\n", attr_name, buffer);
} else {
if (errno == ENODATA) {
printf(" %s = (属性不存在)\n", attr_name);
} else {
printf(" %s = (获取失败: %s)\n", attr_name, strerror(errno));
}
}
}

int main() {
const char *filename = "setxattr_test.txt";
const char *attr_name = "user.description";
const char *attr_value = "这是一个重要的配置文件,包含系统设置信息";

printf("=== setxattr 基础示例 ===\n\n");

// 创建测试文件
if (create_test_file(filename) == -1) {
return 1;
}

// 显示初始状态
printf("1. 初始状态:\n");
show_xattr_value(filename, attr_name);
printf("\n");

// 使用 setxattr 设置扩展属性
printf("2. 使用 setxattr 设置扩展属性:\n");
printf(" 属性名称: %s\n", attr_name);
printf(" 属性值: %s\n", attr_value);
printf(" 属性大小: %zu 字节\n", strlen(attr_value));
printf(" 标志位: 0 (默认行为)\n");

if (setxattr(filename, attr_name, attr_value, strlen(attr_value), 0) == 0) {
printf(" ✓ 扩展属性设置成功\n");
} else {
printf(" ✗ 扩展属性设置失败: %s\n", strerror(errno));

// 错误处理
switch (errno) {
case EACCES:
printf(" 原因: 权限不足\n");
break;
case ENOTSUP:
printf(" 原因: 文件系统不支持扩展属性\n");
break;
case ENOSPC:
printf(" 原因: 磁盘空间不足\n");
break;
case ENAMETOOLONG:
printf(" 原因: 属性名称过长\n");
break;
case EINVAL:
printf(" 原因: 参数无效\n");
break;
case EROFS:
printf(" 原因: 文件系统只读\n");
break;
}
unlink(filename);
return 1;
}

// 验证设置结果
printf("\n3. 验证设置结果:\n");
show_xattr_value(filename, attr_name);
printf("\n");

// 修改已存在的属性
printf("4. 修改已存在的属性:\n");
const char *new_value = "更新后的配置文件描述,包含了新的设置信息";
printf(" 新属性值: %s\n", new_value);

if (setxattr(filename, attr_name, new_value, strlen(new_value), 0) == 0) {
printf(" ✓ 扩展属性更新成功\n");
} else {
printf(" ✗ 扩展属性更新失败: %s\n", strerror(errno));
}

// 验证更新结果
printf("\n5. 验证更新结果:\n");
show_xattr_value(filename, attr_name);
printf("\n");

// 清理资源
printf("6. 清理资源:\n");
if (removexattr(filename, attr_name) == 0) {
printf(" ✓ 删除扩展属性成功\n");
} else {
printf(" ✗ 删除扩展属性失败: %s\n", strerror(errno));
}

if (unlink(filename) == 0) {
printf(" ✓ 删除测试文件成功\n");
} else {
printf(" ✗ 删除测试文件失败: %s\n", strerror(errno));
}

printf("\n=== setxattr 特点 ===\n");
printf("1. 原子操作: 设置属性是原子的\n");
printf("2. 路径跟随: 会跟随符号链接\n");
printf("3. 权限控制: 需要文件写权限\n");
printf("4. 命名空间: 支持多种命名空间\n");
printf("5. 标志控制: 支持创建/替换标志\n");
printf("6. 大小限制: 属性值大小有限制\n");
printf("7. 文件系统: 依赖文件系统支持\n");

return 0;
}

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

// 显示文件扩展属性列表
void show_xattr_list(const char *filename) {
char *buffer;
ssize_t list_size = listxattr(filename, NULL, 0);

if (list_size == -1) {
if (errno == ENOTSUP) {
printf(" 文件系统不支持扩展属性\n");
} else {
printf(" 获取属性列表失败: %s\n", strerror(errno));
}
return;
}

if (list_size == 0) {
printf(" 没有扩展属性\n");
return;
}

buffer = malloc(list_size + 1);
if (!buffer) {
printf(" 内存分配失败\n");
return;
}

ssize_t result = listxattr(filename, buffer, list_size);
if (result == -1) {
printf(" 获取属性列表失败: %s\n", strerror(errno));
free(buffer);
return;
}

printf(" 扩展属性列表:\n");
char *attr_name = buffer;
int count = 0;
while (attr_name < buffer + result) {
printf(" &#91;%d] %s\n", ++count, attr_name);
attr_name += strlen(attr_name) + 1;
}

free(buffer);
}

// 安全的属性设置函数
int safe_setxattr(const char *path, const char *name,
const void *value, size_t size, int flags) {
// 参数验证
if (!path || !name || !value) {
errno = EINVAL;
return -1;
}

if (size == 0) {
errno = EINVAL;
return -1;
}

// 执行设置
int result = setxattr(path, name, value, size, flags);

// 错误处理
if (result == -1) {
switch (errno) {
case EACCES:
fprintf(stderr, "权限不足设置扩展属性 '%s'\n", name);
break;
case ENOTSUP:
fprintf(stderr, "文件系统不支持扩展属性 '%s'\n", name);
break;
case ENOSPC:
fprintf(stderr, "存储空间不足设置扩展属性 '%s'\n", name);
break;
case EEXIST:
fprintf(stderr, "属性 '%s' 已存在 (使用 XATTR_CREATE)\n", name);
break;
case ENOATTR:
fprintf(stderr, "属性 '%s' 不存在 (使用 XATTR_REPLACE)\n", name);
break;
case ENAMETOOLONG:
fprintf(stderr, "属性名称 '%s' 过长\n", name);
break;
case EINVAL:
fprintf(stderr, "无效参数设置扩展属性 '%s'\n", name);
break;
case EROFS:
fprintf(stderr, "文件系统只读,无法设置扩展属性 '%s'\n", name);
break;
default:
fprintf(stderr, "设置扩展属性 '%s' 失败: %s\n", name, strerror(errno));
break;
}
}

return result;
}

int main() {
const char *filename = "setxattr_flags_test.txt";
const char *attr_name = "user.test_flag";
const char *initial_value = "初始值";
const char *new_value = "新值";

printf("=== setxattr 标志位使用示例 ===\n\n");

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

const char *content = "测试文件内容\n";
write(fd, content, strlen(content));
close(fd);
printf("✓ 创建测试文件: %s\n\n", filename);

// 显示初始状态
printf("1. 初始状态:\n");
show_xattr_list(filename);
printf("\n");

// 2. 使用 XATTR_CREATE 标志创建属性
printf("2. 使用 XATTR_CREATE 标志创建属性:\n");
printf(" 标志: XATTR_CREATE (仅当属性不存在时创建)\n");
printf(" 属性: %s = %s\n", attr_name, initial_value);

if (setxattr(filename, attr_name, initial_value, strlen(initial_value), XATTR_CREATE) == 0) {
printf(" ✓ 成功创建属性\n");
} else {
printf(" ✗ 创建属性失败: %s\n", strerror(errno));
}

printf(" 创建后属性列表:\n");
show_xattr_list(filename);
printf("\n");

// 3. 再次使用 XATTR_CREATE 应该失败
printf("3. 再次使用 XATTR_CREATE 应该失败:\n");
printf(" 标志: XATTR_CREATE (属性已存在)\n");
printf(" 属性: %s = %s\n", attr_name, new_value);

if (setxattr(filename, attr_name, new_value, strlen(new_value), XATTR_CREATE) == -1) {
if (errno == EEXIST) {
printf(" ✓ 正确失败: 属性已存在 (EEXIST)\n");
} else {
printf(" ✗ 意外错误: %s\n", strerror(errno));
}
} else {
printf(" ✗ 应该失败但成功了\n");
}

printf(" 验证属性值未改变:\n");
show_xattr_list(filename);
printf("\n");

// 4. 使用 XATTR_REPLACE 标志更新属性
printf("4. 使用 XATTR_REPLACE 标志更新属性:\n");
printf(" 标志: XATTR_REPLACE (仅当属性已存在时替换)\n");
printf(" 属性: %s = %s\n", attr_name, new_value);

if (setxattr(filename, attr_name, new_value, strlen(new_value), XATTR_REPLACE) == 0) {
printf(" ✓ 成功更新属性\n");
} else {
printf(" ✗ 更新属性失败: %s\n", strerror(errno));
}

printf(" 更新后属性列表:\n");
show_xattr_list(filename);
printf("\n");

// 5. 使用 XATTR_REPLACE 更新不存在的属性应该失败
printf("5. 使用 XATTR_REPLACE 更新不存在的属性:\n");
const char *nonexistent_attr = "user.nonexistent";
printf(" 标志: XATTR_REPLACE (属性不存在)\n");
printf(" 属性: %s = %s\n", nonexistent_attr, "测试值");

if (setxattr(filename, nonexistent_attr, "测试值", strlen("测试值"), XATTR_REPLACE) == -1) {
if (errno == ENOATTR) {
printf(" ✓ 正确失败: 属性不存在 (ENOATTR)\n");
} else {
printf(" ✗ 意外错误: %s\n", strerror(errno));
}
} else {
printf(" ✗ 应该失败但成功了\n");
}

printf(" 验证属性列表:\n");
show_xattr_list(filename);
printf("\n");

// 6. 使用默认标志 (0) - 应该成功
printf("6. 使用默认标志 (0) - 应该成功:\n");
printf(" 标志: 0 (存在则替换,不存在则创建)\n");
printf(" 属性: %s = %s\n", attr_name, "默认标志测试值");

if (setxattr(filename, attr_name, "默认标志测试值", strlen("默认标志测试值"), 0) == 0) {
printf(" ✓ 成功设置属性 (默认行为)\n");
} else {
printf(" ✗ 设置属性失败: %s\n", strerror(errno));
}

printf(" 验证属性值已更新:\n");
show_xattr_list(filename);
printf("\n");

// 7. 清理资源
printf("7. 清理资源:\n");

// 删除测试属性
if (removexattr(filename, attr_name) == 0) {
printf(" ✓ 删除属性 '%s' 成功\n", attr_name);
} else {
printf(" ✗ 删除属性 '%s' 失败: %s\n", attr_name, strerror(errno));
}

// 删除测试文件
if (unlink(filename) == 0) {
printf(" ✓ 删除文件 '%s' 成功\n", filename);
} else {
printf(" ✗ 删除文件 '%s' 失败: %s\n", filename, strerror(errno));
}

printf("\n=== 标志位使用总结 ===\n");
printf("XATTR_CREATE (0x1):\n");
printf(" • 仅当属性不存在时创建\n");
printf(" • 属性存在时返回 EEXIST\n");
printf(" • 适合确保创建新属性\n");
printf(" • 防止意外覆盖现有属性\n\n");

printf("XATTR_REPLACE (0x2):\n");
printf(" • 仅当属性已存在时替换\n");
printf(" • 属性不存在时返回 ENOATTR\n");
printf(" • 适合更新现有属性\n");
printf(" • 防止创建意外属性\n\n");

printf("默认标志 (0):\n");
printf(" • 属性存在则替换\n");
printf(" • 属性不存在则创建\n");
printf(" • 最常用的行为\n");
printf(" • 简单直接的使用方式\n\n");

printf("使用建议:\n");
printf("1. 创建新属性时使用 XATTR_CREATE\n");
printf("2. 更新现有属性时使用 XATTR_REPLACE\n");
printf("3. 一般情况下使用默认标志 (0)\n");
printf("4. 始终检查返回值和 errno\n");
printf("5. 根据应用逻辑选择合适的标志\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
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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/xattr.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <pwd.h>
#include <grp.h>

// 配置结构体
struct xattr_config {
const char *filename;
const char *attr_name;
const char *attr_value;
int operation; // 0=set, 1=get, 2=list, 3=remove
int flags; // XATTR_CREATE, XATTR_REPLACE, 0
int verbose; // 详细输出
int recursive; // 递归操作
int follow_links; // 跟随符号链接
int show_raw; // 显示原始数据
};

// 操作类型枚举
enum {
OP_SET = 0,
OP_GET = 1,
OP_LIST = 2,
OP_REMOVE = 3,
OP_COPY = 4
};

// 显示扩展属性列表
int show_xattr_list(const char *filename, int follow_links) {
char *buffer;
ssize_t list_size;

if (follow_links) {
list_size = listxattr(filename, NULL, 0);
} else {
list_size = llistxattr(filename, NULL, 0);
}

if (list_size == -1) {
switch (errno) {
case ENOTSUP:
fprintf(stderr, "错误: 文件系统不支持扩展属性 '%s'\n", filename);
break;
case EACCES:
fprintf(stderr, "错误: 权限不足访问 '%s'\n", filename);
break;
case ENOENT:
fprintf(stderr, "错误: 文件不存在 '%s'\n", filename);
break;
default:
fprintf(stderr, "错误: 获取属性列表失败 '%s': %s\n",
filename, strerror(errno));
break;
}
return -1;
}

if (list_size == 0) {
printf("文件 '%s' 没有扩展属性\n", filename);
return 0;
}

buffer = malloc(list_size + 1);
if (!buffer) {
fprintf(stderr, "错误: 内存分配失败\n");
return -1;
}

if (follow_links) {
list_size = listxattr(filename, buffer, list_size);
} else {
list_size = llistxattr(filename, buffer, list_size);
}

if (list_size == -1) {
fprintf(stderr, "错误: 获取属性列表失败 '%s': %s\n",
filename, strerror(errno));
free(buffer);
return -1;
}

printf("文件 '%s' 的扩展属性:\n", filename);
char *attr_name = buffer;
int count = 0;
while (attr_name < buffer + list_size) {
printf(" &#91;%d] %s\n", ++count, attr_name);
attr_name += strlen(attr_name) + 1;
}

free(buffer);
return 0;
}

// 显示单个扩展属性
int show_single_xattr(const char *filename, const char *attr_name,
int follow_links, int show_raw) {
char *buffer;
ssize_t attr_size;

// 先获取属性大小
if (follow_links) {
attr_size = getxattr(filename, attr_name, NULL, 0);
} else {
attr_size = lgetxattr(filename, attr_name, NULL, 0);
}

if (attr_size == -1) {
switch (errno) {
case ENODATA:
fprintf(stderr, "错误: 属性 '%s' 不存在于文件 '%s'\n",
attr_name, filename);
break;
case ENOTSUP:
fprintf(stderr, "错误: 文件系统不支持扩展属性 '%s'\n", filename);
break;
case EACCES:
fprintf(stderr, "错误: 权限不足访问属性 '%s'\n", attr_name);
break;
case ENOENT:
fprintf(stderr, "错误: 文件不存在 '%s'\n", filename);
break;
default:
fprintf(stderr, "错误: 获取属性 '%s' 失败: %s\n",
attr_name, strerror(errno));
break;
}
return -1;
}

if (attr_size == 0) {
printf("属性 '%s': (空值)\n", attr_name);
return 0;
}

buffer = malloc(attr_size + 1);
if (!buffer) {
fprintf(stderr, "错误: 内存分配失败\n");
return -1;
}

if (follow_links) {
attr_size = getxattr(filename, attr_name, buffer, attr_size);
} else {
attr_size = lgetxattr(filename, attr_name, buffer, attr_size);
}

if (attr_size == -1) {
fprintf(stderr, "错误: 获取属性值失败 '%s': %s\n",
attr_name, strerror(errno));
free(buffer);
return -1;
}

buffer&#91;attr_size] = '\0';

if (show_raw) {
// 显示原始二进制数据
printf("属性 '%s' (原始数据):\n", attr_name);
printf(" 大小: %zd 字节\n", attr_size);
printf(" 数据: ");
for (ssize_t i = 0; i < attr_size && i < 64; i++) {
printf("%02x ", (unsigned char)buffer&#91;i]);
}
if (attr_size > 64) {
printf("...(还有 %zd 字节)", attr_size - 64);
}
printf("\n");
} else {
// 显示可读数据
int is_printable = 1;
for (ssize_t i = 0; i < attr_size; i++) {
if (buffer&#91;i] < 32 || buffer&#91;i] > 126) {
if (buffer&#91;i] != '\n' && buffer&#91;i] != '\t' && buffer&#91;i] != '\r') {
is_printable = 0;
break;
}
}
}

printf("属性 '%s': ", attr_name);
if (is_printable) {
printf("'%s'\n", buffer);
} else {
printf("(二进制数据,%zd 字节)\n", attr_size);
}
}

free(buffer);
return 0;
}

// 设置扩展属性
int set_extended_attribute(const char *filename, const char *attr_name,
const char *attr_value, int flags, int follow_links) {
ssize_t value_size = strlen(attr_value);

printf("设置扩展属性:\n");
printf(" 文件: %s\n", filename);
printf(" 属性: %s\n", attr_name);
printf(" 值: '%s'\n", attr_value);
printf(" 大小: %zd 字节\n", value_size);
printf(" 标志: ");
switch (flags) {
case XATTR_CREATE: printf("XATTR_CREATE (仅创建)\n"); break;
case XATTR_REPLACE: printf("XATTR_REPLACE (仅替换)\n"); break;
case 0: printf("默认 (创建或替换)\n"); break;
default: printf("0x%x\n", flags); break;
}
printf(" 跟随链接: %s\n", follow_links ? "是" : "否");

int result;
if (follow_links) {
result = setxattr(filename, attr_name, attr_value, value_size, flags);
} else {
result = lsetxattr(filename, attr_name, attr_value, value_size, flags);
}

if (result == 0) {
printf("✓ 扩展属性设置成功\n");
return 0;
} else {
switch (errno) {
case EACCES:
fprintf(stderr, "✗ 权限不足: 需要文件写权限\n");
break;
case ENOTSUP:
fprintf(stderr, "✗ 文件系统不支持扩展属性\n");
break;
case ENOSPC:
fprintf(stderr, "✗ 存储空间不足\n");
break;
case EEXIST:
fprintf(stderr, "✗ 属性已存在 (使用 XATTR_CREATE)\n");
break;
case ENOATTR:
fprintf(stderr, "✗ 属性不存在 (使用 XATTR_REPLACE)\n");
break;
case ENAMETOOLONG:
fprintf(stderr, "✗ 属性名称过长\n");
break;
case EINVAL:
fprintf(stderr, "✗ 无效参数\n");
break;
case EROFS:
fprintf(stderr, "✗ 文件系统只读\n");
break;
default:
fprintf(stderr, "✗ 设置失败: %s\n", strerror(errno));
break;
}
return -1;
}
}

// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s &#91;选项] 文件 &#91;属性名] &#91;属性值]\n", program_name);
printf("\n选项:\n");
printf(" -s, --set 设置扩展属性\n");
printf(" -g, --get 获取扩展属性\n");
printf(" -l, --list 列出所有扩展属性\n");
printf(" -r, --remove 删除扩展属性\n");
printf(" -c, --create 仅当属性不存在时创建 (XATTR_CREATE)\n");
printf(" -p, --replace 仅当属性存在时替换 (XATTR_REPLACE)\n");
printf(" -f, --follow-links 跟随符号链接\n");
printf(" -R, --raw 显示原始数据\n");
printf(" -v, --verbose 详细输出\n");
printf(" -h, --help 显示此帮助信息\n");
printf("\n示例:\n");
printf(" %s -s file.txt user.description \"测试文件\" # 设置属性\n", program_name);
printf(" %s -g file.txt user.description # 获取属性\n", program_name);
printf(" %s -l file.txt # 列出属性\n", program_name);
printf(" %s -r file.txt user.description # 删除属性\n", program_name);
printf(" %s -c -s file.txt user.new \"新属性\" # 创建新属性\n", program_name);
printf(" %s -p -s file.txt user.desc \"更新值\" # 更新属性\n", program_name);
}

int main(int argc, char *argv&#91;]) {
struct xattr_config config = {
.filename = NULL,
.attr_name = NULL,
.attr_value = NULL,
.operation = OP_LIST, // 默认列出属性
.flags = 0,
.verbose = 0,
.recursive = 0,
.follow_links = 1, // 默认跟随符号链接
.show_raw = 0
};

printf("=== 扩展属性管理工具 ===\n\n");

// 解析命令行参数
static struct option long_options&#91;] = {
{"set", no_argument, 0, 's'},
{"get", no_argument, 0, 'g'},
{"list", no_argument, 0, 'l'},
{"remove", no_argument, 0, 'r'},
{"create", no_argument, 0, 'c'},
{"replace", no_argument, 0, 'p'},
{"follow-links", no_argument, 0, 'f'},
{"raw", no_argument, 0, 'R'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};

int opt;
while ((opt = getopt_long(argc, argv, "sglrcpfRvh", long_options, NULL)) != -1) {
switch (opt) {
case 's':
config.operation = OP_SET;
break;
case 'g':
config.operation = OP_GET;
break;
case 'l':
config.operation = OP_LIST;
break;
case 'r':
config.operation = OP_REMOVE;
break;
case 'c':
config.flags = XATTR_CREATE;
break;
case 'p':
config.flags = XATTR_REPLACE;
break;
case 'f':
config.follow_links = 1;
break;
case 'R':
config.show_raw = 1;
break;
case 'v':
config.verbose = 1;
break;
case 'h':
show_help(argv&#91;0]);
return 0;
default:
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv&#91;0]);
return 1;
}
}

// 获取文件参数
if (optind < argc) {
config.filename = argv&#91;optind];

if (optind + 1 < argc) {
config.attr_name = argv&#91;optind + 1];

if (optind + 2 < argc) {
config.attr_value = argv&#91;optind + 2];
}
}
} else {
fprintf(stderr, "错误: 请指定文件名\n");
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv&#91;0]);
return 1;
}

// 验证参数
if (config.operation == OP_SET && !config.attr_value) {
fprintf(stderr, "错误: 设置属性需要指定属性值\n");
return 1;
}

if ((config.operation == OP_GET ||
config.operation == OP_REMOVE) && !config.attr_name) {
fprintf(stderr, "错误: 操作需要指定属性名\n");
return 1;
}

// 显示配置信息(详细模式)
if (config.verbose) {
printf("配置信息:\n");
printf(" 文件: %s\n", config.filename);
printf(" 操作: ");
switch (config.operation) {
case OP_SET: printf("设置属性\n"); break;
case OP_GET: printf("获取属性\n"); break;
case OP_LIST: printf("列出属性\n"); break;
case OP_REMOVE: printf("删除属性\n"); break;
default: printf("未知操作\n"); break;
}
if (config.attr_name) printf(" 属性名: %s\n", config.attr_name);
if (config.attr_value) printf(" 属性值: %s\n", config.attr_value);
printf(" 标志: ");
switch (config.flags) {
case XATTR_CREATE: printf("XATTR_CREATE\n"); break;
case XATTR_REPLACE: printf("XATTR_REPLACE\n"); break;
case 0: printf("默认\n"); break;
default: printf("0x%x\n", config.flags); break;
}
printf(" 跟随链接: %s\n", config.follow_links ? "是" : "否");
printf(" 显示原始数据: %s\n", config.show_raw ? "是" : "否");
printf("\n");
}

// 执行相应操作
int result = 0;

switch (config.operation) {
case OP_LIST:
result = show_xattr_list(config.filename, config.follow_links);
break;

case OP_GET:
result = show_single_xattr(config.filename, config.attr_name,
config.follow_links, config.show_raw);
break;

case OP_SET:
result = set_extended_attribute(config.filename, config.attr_name,
config.attr_value, config.flags,
config.follow_links);
break;

case OP_REMOVE:
printf("删除扩展属性:\n");
printf(" 文件: %s\n", config.filename);
printf(" 属性: %s\n", config.attr_name);
printf(" 跟随链接: %s\n", config.follow_links ? "是" : "否");

if (config.follow_links) {
result = removexattr(config.filename, config.attr_name);
} else {
result = lremovexattr(config.filename, config.attr_name);
}

if (result == 0) {
printf("✓ 扩展属性删除成功\n");
} else {
switch (errno) {
case ENODATA:
fprintf(stderr, "✗ 属性不存在\n");
break;
case EACCES:
fprintf(stderr, "✗ 权限不足\n");
break;
case ENOTSUP:
fprintf(stderr, "✗ 文件系统不支持扩展属性\n");
break;
case EROFS:
fprintf(stderr, "✗ 文件系统只读\n");
break;
default:
fprintf(stderr, "✗ 删除失败: %s\n", strerror(errno));
break;
}
result = -1;
}
break;

default:
fprintf(stderr, "未知操作类型: %d\n", config.operation);
result = -1;
break;
}

// 显示操作后状态(详细模式)
if (config.verbose && config.operation != OP_LIST && config.operation != OP_GET) {
printf("\n操作后状态:\n");
show_xattr_list(config.filename, config.follow_links);
}

printf("\n=== 扩展属性最佳实践 ===\n");
printf("使用建议:\n");
printf("1. 选择合适的命名空间 (user.*, security.*, etc.)\n");
printf("2. 合理使用 XATTR_CREATE 和 XATTR_REPLACE 标志\n");
printf("3. 始终检查返回值和 errno\n");
printf("4. 注意属性大小限制 (通常 64KB)\n");
printf("5. 考虑文件系统兼容性\n");
printf("6. 安全地处理敏感属性\n");
printf("7. 及时清理不需要的属性\n");
printf("8. 使用适当的权限控制\n");
printf("\n");

printf("常见应用场景:\n");
printf("1. 安全标签 (SELinux, AppArmor)\n");
printf("2. 文件元数据存储\n");
printf("3. 备份和同步状态\n");
printf("4. 访问控制列表\n");
printf("5. 应用程序自定义属性\n");
printf("6. 版本控制信息\n");
printf("7. 缓存管理\n");
printf("8. 审计和日志信息\n");

return (result == 0) ? 0 : 1;
}

  1. 编译和运行说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 编译示例程序
gcc -o setxattr_example1 example1.c
gcc -o setxattr_example2 example2.c
gcc -o setxattr_example3 example3.c

# 运行示例
./setxattr_example1
./setxattr_example2
./setxattr_example3 --help

# 实际使用示例
./setxattr_example3 -s test_file.txt user.description "测试文件"
./setxattr_example3 -g test_file.txt user.description
./setxattr_example3 -l test_file.txt
./setxattr_example3 -r test_file.txt user.description
./setxattr_example3 -c -s test_file.txt user.new "新属性"
./setxattr_example3 -p -s test_file.txt user.description "更新值"

  1. 系统要求检查
1
2
3
4
5
6
7
8
9
10
11
12
13
# 检查文件系统支持
grep -w xattr /boot/config-$(uname -r)

# 检查文件系统类型
df -T .

# 检查扩展属性支持
ls /usr/include/sys/xattr.h

# 测试文件系统支持
touch test_file && setfattr -n user.test -v "test" test_file 2>/dev/null && echo "支持扩展属性" || echo "不支持扩展属性"
rm -f test_file

  1. 重要注意事项

6.1 文件系统支持

不是所有文件系统都支持扩展属性:

  • 支持: ext2/3/4, XFS, Btrfs, ReiserFS

  • 不支持: FAT32, NTFS (某些版本)

6.2 权限要求

1
2
3
4
5
6
7
8
9
10
// 用户属性需要文件写权限
int check_xattr_permissions(const char *filename) {
if (access(filename, W_OK) == 0) {
return 0; // 有写权限
} else {
errno = EACCES;
return -1; // 权限不足
}
}

6.3 大小限制

1
2
3
4
5
6
7
8
9
10
11
12
13
// 检查属性大小限制
int check_xattr_size_limit(const char *value) {
if (!value) return -1;

size_t value_size = strlen(value);
if (value_size > 65536) { // 64KB 限制
errno = ENOSPC;
return -1;
}

return 0;
}

6.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
// 安全的扩展属性设置函数
int safe_setxattr(const char *path, const char *name,
const void *value, size_t size, int flags) {
// 参数验证
if (!path || !name || !value) {
errno = EINVAL;
return -1;
}

if (size == 0 || size > 65536) {
errno = EINVAL;
return -1;
}

// 权限检查
if (access(path, W_OK) != 0) {
errno = EACCES;
return -1;
}

// 执行设置
int result = setxattr(path, name, value, size, flags);

// 错误处理
if (result == -1) {
switch (errno) {
case EACCES:
fprintf(stderr, "权限不足设置扩展属性 '%s'\n", name);
break;
case ENOTSUP:
fprintf(stderr, "文件系统不支持扩展属性 '%s'\n", name);
break;
case ENOSPC:
fprintf(stderr, "存储空间不足设置扩展属性 '%s'\n", name);
break;
case EEXIST:
fprintf(stderr, "属性 '%s' 已存在 (使用 XATTR_CREATE)\n", name);
break;
case ENOATTR:
fprintf(stderr, "属性 '%s' 不存在 (使用 XATTR_REPLACE)\n", name);
break;
case ENAMETOOLONG:
fprintf(stderr, "属性名称 '%s' 过长\n", name);
break;
case EINVAL:
fprintf(stderr, "无效参数设置扩展属性 '%s'\n", name);
break;
case EROFS:
fprintf(stderr, "文件系统只读,无法设置扩展属性 '%s'\n", name);
break;
}
}

return result;
}

  1. 实际应用场景

7.1 安全标签管理

1
2
3
4
5
// 设置 SELinux 安全标签
int set_selinux_label(const char *filename, const char *label) {
return setxattr(filename, "security.selinux", label, strlen(label), 0);
}

7.2 文件元数据存储

1
2
3
4
5
6
7
8
9
10
// 存储文件版本信息
int set_file_version(const char *filename, const char *version) {
return setxattr(filename, "user.version", version, strlen(version), 0);
}

// 存储文件摘要信息
int set_file_checksum(const char *filename, const char *checksum) {
return setxattr(filename, "user.checksum", checksum, strlen(checksum), 0);
}

7.3 应用程序状态管理

1
2
3
4
5
6
7
8
9
10
11
12
// 存储备份状态
int set_backup_status(const char *filename, const char *status) {
return setxattr(filename, "user.backup_status", status, strlen(status), 0);
}

// 存储同步时间戳
int set_sync_timestamp(const char *filename, time_t timestamp) {
char timestamp_str&#91;32];
snprintf(timestamp_str, sizeof(timestamp_str), "%ld", timestamp);
return setxattr(filename, "user.last_sync", timestamp_str, strlen(timestamp_str), 0);
}

  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
// 批量设置扩展属性
int batch_set_xattrs(const char *filename,
const char **names, const char **values, int count) {
int success_count = 0;
int failed_count = 0;

for (int i = 0; i < count; i++) {
if (setxattr(filename, names&#91;i], values&#91;i], strlen(values&#91;i]), 0) == 0) {
success_count++;
} else {
failed_count++;
fprintf(stderr, "设置属性 '%s' 失败: %s\n", names&#91;i], strerror(errno));
}
}

printf("批量设置完成: 成功 %d, 失败 %d\n", success_count, failed_count);
return failed_count;
}

// 预先检查属性存在性
int check_and_set_xattr(const char *filename, const char *name,
const char *value, int flags) {
// 如果是替换操作,先检查属性是否存在
if (flags == XATTR_REPLACE) {
char dummy_buffer&#91;1];
ssize_t result = getxattr(filename, name, dummy_buffer, sizeof(dummy_buffer));
if (result == -1 && errno == ENODATA) {
errno = ENOATTR;
return -1; // 属性不存在
}
}

// 执行设置操作
return setxattr(filename, name, value, strlen(value), flags);
}

这些示例全面展示了 setxattr 及相关函数的各种使用方法,从基础的属性设置到完整的管理工具,帮助你全面掌握 Linux 系统中的扩展属性机制。

signalfd4系统调用及示例

signalfd64

1. 函数介绍

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

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

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

统一 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 应用程序的有力工具。

fallocate系统调用及示例

fallocate - 预分配文件空间

1. 函数介绍

fallocate 是 Linux 系统调用,用于为文件预分配磁盘空间。你可以把它想象成”预订”磁盘空间,就像你在餐厅预订座位一样——你告诉系统你需要多少空间,系统就为你预留出来,但此时还没有实际写入数据。

这个函数的主要优势是:

  • 提高文件系统性能:避免文件碎片

  • 确保文件有足够空间:防止写入时空间不足

  • 快速操作:比实际写入数据更快

2. 函数原型

1
2
3
4
#include <fcntl.h>

int fallocate(int fd, int mode, off_t offset, off_t len);

3. 功能

为文件预分配指定范围的磁盘空间,而不需要实际写入数据。这可以优化文件系统的存储布局,提高I/O性能。

4. 参数

  • int fd: 文件描述符,通过 open() 函数获得

int mode: 操作模式

  • 0: 默认模式,分配空间

  • FALLOC_FL_PUNCH_HOLE: 创建空洞(释放空间)

  • FALLOC_FL_COLLAPSE_RANGE: 折叠范围

  • FALLOC_FL_ZERO_RANGE: 清零范围

off_t offset: 文件中的起始偏移量(字节)

off_t len: 要分配的长度(字节)

5. 返回值

  • 成功时返回 0

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

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

  • posix_fallocate(): POSIX标准版本,可移植性更好

  • truncate(): 改变文件大小

  • ftruncate(): 改变文件描述符对应的文件大小

  • lseek(): 移动文件指针位置

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

int main() {
int fd;
int ret;

// 创建一个新文件用于测试
fd = open("test_file.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("打开文件失败");
exit(EXIT_FAILURE);
}

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

// 预分配10MB的空间
ret = fallocate(fd, 0, 0, 10 * 1024 * 1024);
if (ret == -1) {
if (errno == EOPNOTSUPP) {
printf("警告: 当前文件系统不支持 fallocate\n");
} else {
perror("fallocate 调用失败");
}
close(fd);
exit(EXIT_FAILURE);
}

printf("成功预分配10MB空间\n");

// 关闭文件
close(fd);
printf("文件已关闭\n");

return 0;
}

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

int main() {
int fd;
int ret;
char data&#91;] = "Hello, World!";

// 创建一个新文件
fd = open("sparse_file.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("打开文件失败");
exit(EXIT_FAILURE);
}

printf("创建文件,文件描述符: %d\n", fd);

// 先写入一些数据在文件开头
if (write(fd, data, strlen(data)) == -1) {
perror("写入数据失败");
close(fd);
exit(EXIT_FAILURE);
}

printf("已在文件开头写入: %s\n", data);

// 预分配10MB空间在1MB偏移处(创建空洞)
ret = fallocate(fd, 0, 1024 * 1024, 10 * 1024 * 1024);
if (ret == -1) {
perror("fallocate 调用失败");
close(fd);
exit(EXIT_FAILURE);
}

printf("在1MB偏移处预分配了10MB空间\n");
printf("此时文件大小约为11MB,但实际占用磁盘空间很小\n");

// 关闭文件
close(fd);
printf("文件已关闭\n");

return 0;
}

示例3:使用PUNCH_HOLE模式释放文件中间部分的空间

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

int main() {
int fd;
int ret;
char data&#91;1024];

// 创建测试文件并填充数据
fd = open("punch_hole_test.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("打开文件失败");
exit(EXIT_FAILURE);
}

printf("创建测试文件\n");

// 填充1MB数据
memset(data, 'A', sizeof(data));
for (int i = 0; i < 1024; i++) {
if (write(fd, data, sizeof(data)) == -1) {
perror("写入数据失败");
close(fd);
exit(EXIT_FAILURE);
}
}

printf("已写入1MB数据\n");

// 使用PUNCH_HOLE模式释放中间512KB的空间
// 注意:PUNCH_HOLE需要与FALLOC_FL_KEEP_SIZE一起使用
ret = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
256 * 1024, 512 * 1024);
if (ret == -1) {
if (errno == EOPNOTSUPP) {
printf("当前文件系统不支持 PUNCH_HOLE 操作\n");
} else {
perror("fallocate PUNCH_HOLE 操作失败");
}
} else {
printf("成功在文件中间创建了512KB的空洞\n");
printf("这部分空间已被释放,但文件大小保持不变\n");
}

// 关闭文件
close(fd);
printf("文件已关闭\n");

return 0;
}

总结

fallocate 是一个非常有用的系统调用,特别适用于以下场景:

数据库系统预分配文件空间

大文件写入前的空间预留

虚拟机磁盘映像创建

需要避免文件碎片的高性能应用

记住,不是所有文件系统都支持所有模式,使用时需要检查返回值并做好错误处理。

fadvise64系统调用及示例

fadvise64 - 文件访问建议

函数介绍

fadvise64是一个Linux系统调用,用于向内核提供关于文件访问模式的建议。它帮助内核优化文件I/O操作,提高性能。

函数原型

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

int fadvise64(int fd, off_t offset, off_t len, int advice);

功能

向内核提供文件访问模式建议,帮助内核优化缓存和预读策略。

参数

  • int fd: 文件描述符

  • off_t offset: 建议适用的文件起始偏移量

  • off_t len: 建议适用的文件长度(0表示到文件末尾)

int advice: 访问建议类型

  • POSIX_FADV_NORMAL: 普通访问模式(默认)

  • POSIX_FADV_SEQUENTIAL: 顺序访问

  • POSIX_FADV_RANDOM: 随机访问

  • POSIX_FADV_NOREUSE: 数据只访问一次

  • POSIX_FADV_WILLNEED: 数据即将被访问

  • POSIX_FADV_DONTNEED: 数据不再需要

返回值

  • 成功时返回0

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

特殊限制

  • 需要Linux 2.5.60以上内核支持

  • 某些文件系统可能不完全支持

  • 建议只是提示,内核可能忽略

相似函数

  • madvise(): 内存访问建议

  • readahead(): 文件预读

  • posix_fadvise(): POSIX标准版本

示例代码

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

// 系统调用包装
static int fadvise64_wrapper(int fd, off_t offset, off_t len, int advice) {
return syscall(__NR_fadvise64, fd, offset, len, advice);
}

// 创建测试文件
int create_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(4096);
if (buffer) {
memset(buffer, 'A', 4096);
for (size_t i = 0; i < size; i += 4096) {
size_t write_size = (size - i > 4096) ? 4096 : (size - i);
write(fd, buffer, write_size);
}
free(buffer);
}

return fd;
}

int main() {
int fd;
int result;

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

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

// 创建大文件用于测试
fd = create_test_file("test_fadvise64.dat", 1024 * 1024); // 1MB
if (fd == -1) {
exit(EXIT_FAILURE);
}
printf("创建测试文件: test_fadvise64.dat (1MB)\n");

close(fd);

// 重新打开文件进行测试
fd = open("test_fadvise64.dat", O_RDONLY);
if (fd == -1) {
perror("打开测试文件失败");
unlink("test_fadvise64.dat");
exit(EXIT_FAILURE);
}
printf("打开测试文件进行fadvise64测试\n");

// 示例2: 不同的访问建议
printf("\n示例2: 不同的访问建议\n");

// POSIX_FADV_NORMAL - 普通访问模式
result = fadvise64_wrapper(fd, 0, 0, POSIX_FADV_NORMAL);
if (result == 0) {
printf("设置POSIX_FADV_NORMAL成功\n");
} else {
printf("设置POSIX_FADV_NORMAL失败: %s\n", strerror(errno));
}

// POSIX_FADV_SEQUENTIAL - 顺序访问
result = fadvise64_wrapper(fd, 0, 1024*1024, POSIX_FADV_SEQUENTIAL);
if (result == 0) {
printf("设置POSIX_FADV_SEQUENTIAL成功\n");
printf("提示内核将进行顺序访问,优化预读策略\n");
}

// POSIX_FADV_RANDOM - 随机访问
result = fadvise64_wrapper(fd, 0, 1024*1024, POSIX_FADV_RANDOM);
if (result == 0) {
printf("设置POSIX_FADV_RANDOM成功\n");
printf("提示内核将进行随机访问,减少预读\n");
}

// POSIX_FADV_WILLNEED - 数据即将被访问
result = fadvise64_wrapper(fd, 0, 64*1024, POSIX_FADV_WILLNEED);
if (result == 0) {
printf("设置POSIX_FADV_WILLNEED成功\n");
printf("提示内核预读前64KB数据\n");
}

// POSIX_FADV_DONTNEED - 数据不再需要
result = fadvise64_wrapper(fd, 0, 64*1024, POSIX_FADV_DONTNEED);
if (result == 0) {
printf("设置POSIX_FADV_DONTNEED成功\n");
printf("提示内核可以丢弃前64KB数据的缓存\n");
}

// POSIX_FADV_NOREUSE - 数据只访问一次
result = fadvise64_wrapper(fd, 64*1024, 64*1024, POSIX_FADV_NOREUSE);
if (result == 0) {
printf("设置POSIX_FADV_NOREUSE成功\n");
printf("提示内核64KB-128KB范围的数据只访问一次\n");
}

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

// 使用无效的文件描述符
result = fadvise64_wrapper(999, 0, 1024, POSIX_FADV_NORMAL);
if (result == -1) {
if (errno == EBADF) {
printf("无效文件描述符错误处理正确: %s\n", strerror(errno));
}
}

// 使用无效的建议类型
result = fadvise64_wrapper(fd, 0, 1024, 999);
if (result == -1) {
if (errno == EINVAL) {
printf("无效建议类型错误处理正确: %s\n", strerror(errno));
}
}

// 使用负的偏移量
result = fadvise64_wrapper(fd, -1024, 1024, POSIX_FADV_NORMAL);
if (result == -1) {
printf("负偏移量处理: %s\n", strerror(errno));
}

// 示例4: 实际使用场景演示
printf("\n示例4: 实际使用场景演示\n");

// 场景1: 大文件顺序读取
printf("场景1: 大文件顺序读取优化\n");
printf("处理大日志文件的代码示例:\n");
printf("int process_large_log(const char* filename) {\n");
printf(" int fd = open(filename, O_RDONLY);\n");
printf(" if (fd == -1) return -1;\n");
printf(" \n");
printf(" // 提示内核将顺序访问整个文件\n");
printf(" posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);\n");
printf(" \n");
printf(" // 读取处理文件...\n");
printf(" char buffer&#91;8192];\n");
printf(" ssize_t bytes;\n");
printf(" while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) {\n");
printf(" // 处理数据\n");
printf(" }\n");
printf(" \n");
printf(" close(fd);\n");
printf(" return 0;\n");
printf("}\n\n");

// 场景2: 随机访问数据库文件
printf("场景2: 随机访问数据库文件\n");
printf("数据库文件访问优化:\n");
printf("int access_database_file(const char* filename) {\n");
printf(" int fd = open(filename, O_RDWR);\n");
printf(" if (fd == -1) return -1;\n");
printf(" \n");
printf(" // 提示内核将随机访问文件\n");
printf(" posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);\n");
printf(" \n");
printf(" // 根据需要预读特定区域\n");
printf(" posix_fadvise(fd, index_offset, index_size, POSIX_FADV_WILLNEED);\n");
printf(" \n");
printf(" // 访问完成后释放不需要的缓存\n");
printf(" posix_fadvise(fd, old_data_offset, old_data_size, POSIX_FADV_DONTNEED);\n");
printf(" \n");
printf(" close(fd);\n");
printf(" return 0;\n");
printf("}\n\n");

// 示例5: 不同建议类型的效果说明
printf("示例5: 不同建议类型的效果说明\n");

printf("POSIX_FADV_NORMAL:\n");
printf(" - 默认访问模式\n");
printf(" - 使用系统默认的预读和缓存策略\n");
printf(" - 适用于一般情况\n\n");

printf("POSIX_FADV_SEQUENTIAL:\n");
printf(" - 优化顺序访问\n");
printf(" - 增加预读量\n");
printf(" - 适用于大文件顺序读取\n");
printf(" - 提高顺序读取性能\n\n");

printf("POSIX_FADV_RANDOM:\n");
printf(" - 优化随机访问\n");
printf(" - 减少或禁用预读\n");
printf(" - 适用于数据库、索引文件\n");
printf(" - 减少不必要的内存占用\n\n");

printf("POSIX_FADV_NOREUSE:\n");
printf(" - 数据只访问一次\n");
printf(" - 访问后尽快释放缓存\n");
printf(" - 适用于一次性处理的大文件\n");
printf(" - 节省内存资源\n\n");

printf("POSIX_FADV_WILLNEED:\n");
printf(" - 数据即将被访问\n");
printf(" - 提前预读数据到缓存\n");
printf(" - 适用于已知访问模式的场景\n");
printf(" - 减少实际访问时的等待\n\n");

printf("POSIX_FADV_DONTNEED:\n");
printf(" - 数据不再需要\n");
printf(" - 尽快释放缓存空间\n");
printf(" - 适用于处理完成的数据\n");
printf(" - 释放系统资源\n\n");

// 示例6: 性能测试演示
printf("示例6: 性能影响演示\n");

printf("fadvise64对性能的影响:\n");
printf("1. 正确使用可显著提高I/O性能\n");
printf("2. 错误使用可能导致性能下降\n");
printf("3. 效果因文件系统和硬件而异\n");
printf("4. 大文件效果更明显\n");
printf("5. 需要根据实际访问模式选择\n\n");

// 示例7: 实际应用建议
printf("示例7: 实际应用建议\n");

printf("使用fadvise64的最佳实践:\n");
printf("1. 在文件打开后尽早设置建议\n");
printf("2. 根据实际访问模式选择合适的建议\n");
printf("3. 对于大文件效果更明显\n");
printf("4. 不要过度使用,避免增加系统负担\n");
printf("5. 在长时间运行的应用中适时调整\n");
printf("6. 测试不同建议对性能的影响\n\n");

printf("常见应用场景:\n");
printf("- 大文件处理和分析\n");
printf("- 数据库系统\n");
printf("- 日志处理系统\n");
printf("- 备份和归档工具\n");
printf("- 媒体播放器\n");
printf("- 科学计算应用\n\n");

// 示例8: 与相关函数的对比
printf("示例8: 与相关函数的对比\n");

printf("fadvise64 vs madvise:\n");
printf("fadvise64:\n");
printf(" - 针对文件I/O\n");
printf(" - 影响文件缓存策略\n");
printf(" - 在文件描述符上操作\n\n");

printf("madvise:\n");
printf(" - 针对内存映射\n");
printf(" - 影响内存管理策略\n");
printf(" - 在内存地址上操作\n\n");

printf("fadvise64 vs readahead:\n");
printf("fadvise64:\n");
printf(" - 更通用的建议机制\n");
printf(" - 支持多种访问模式\n");
printf(" - 可以指定文件区域\n\n");

printf("readahead:\n");
printf(" - 专门用于预读\n");
printf(" - 立即执行预读操作\n");
printf(" - 较为直接但不够灵活\n\n");

// 清理资源
close(fd);
unlink("test_fadvise64.dat");

printf("总结:\n");
printf("fadvise64是Linux提供的文件访问优化机制\n");
printf("通过向内核提供访问建议来优化性能\n");
printf("支持多种访问模式的优化\n");
printf("是处理大文件和特定访问模式的重要工具\n");
printf("需要根据实际应用场景合理使用\n");

return 0;
}

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

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