poll系统调用及示例

poll系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 poll 函数,它是一种高效的 I/O 多路复用机制,允许一个进程同时监视多个文件描述符,等待其中任何一个或多个文件描述符变我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 poll 函数,它是一种高效的 I/O 多路复用机制,允许一个进程同时监视多个文件描述符,等待其中任何一个或多个文件描述符变为“就绪”状态(例如可读、可写或发生异常)。

相关文章:ppoll系统调用及示例 epoll_create1系统调用及示例 epoll_ctl系统调用及示例

1. 函数介绍

poll 是一个 Linux 系统调用,用于实现 I/O 多路复用 (I/O multiplexing)。它的核心思想是让进程能够同时检查多个文件描述符(如套接字、管道、终端等)的状态,看它们是否准备好进行 I/O 操作(例如读取、写入),而无需对每个文件描述符都进行阻塞式等待。

在没有 poll(或 select、epoll)的情况下,如果一个程序需要同时处理多个网络连接或文件,它可能需要创建多个线程或进程,或者在一个文件描述符上阻塞等待,这会非常低效或复杂。poll 允许一个线程/进程在一个调用中“监听”所有感兴趣的文件描述符,当其中任何一个准备好时,poll 返回,程序就可以处理那个就绪的文件描述符。

你可以把它想象成一个“服务员”,同时照看多张餐桌(文件描述符)。服务员不需要一直站在某一张餐桌旁等客人点菜(数据),而是可以走一圈看看哪张餐桌的客人举手了(数据就绪),然后去为那张餐桌服务。

2. 函数原型

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

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

3. 功能

  • 监视文件描述符集合: poll 会检查 fds 数组中列出的 nfds 个文件描述符的状态。

等待就绪: 调用 poll 的进程会阻塞(挂起),直到以下情况之一发生:

  • fds 数组中的至少一个文件描述符变为“就绪”状态(根据 events 字段指定的条件)。

  • 调用被信号中断(返回 -1,并设置 errno 为 EINTR)。

  • 达到指定的超时时间 timeout(如果 timeout >= 0)。

  • 返回就绪数量: 当 poll 返回时,它会报告有多少个文件描述符已就绪。

  • 更新状态: poll 会修改 fds 数组中每个元素的 revents 字段,以指示该文件描述符上实际发生的事件。

4. 参数

struct pollfd *fds: 这是一个指向 struct pollfd 类型数组的指针。这个数组包含了所有需要监视的文件描述符及其感兴趣的事件。struct pollfd 的定义如下:struct pollfd { int fd; // 要监视的文件描述符 short events; // 程序关心的事件 (输入) short revents; // 实际发生的事件 (输出) };

  • fd: 要监视的文件描述符。如果 fd 为负数,则忽略该数组元素。

events: 这是一个位掩码,指定了应用程序对这个 fd 感兴趣的事件。常用的值包括:

  • POLLIN: 数据可读(对于普通文件,通常总是可读的)。

  • POLLOUT: 数据可写(对于普通文件,通常总是可写的)。

  • POLLPRI: 高优先级数据可读(例如 TCP 带外数据)。

  • POLLERR: 发生错误(作为 revents 返回,不能在 events 中设置)。

  • POLLHUP: 挂起(例如对端套接字关闭)(作为 revents 返回)。

  • POLLNVAL: 文件描述符无效(作为 revents 返回)。

revents: 这个字段由 poll 调用填充,返回该 fd 上实际发生的事件。程序需要检查这个字段来确定 fd 是否就绪以及发生了什么事件。

nfds_t nfds: 这是 fds 数组中的元素个数,即要监视的文件描述符总数。

int timeout: 指定 poll 调用阻塞等待的超时时间(以毫秒为单位)。

  • timeout == -1: poll 会无限期阻塞,直到至少一个文件描述符就绪或被信号中断。

  • timeout == 0: poll 执行非阻塞检查,立即返回,报告当前有多少文件描述符已就绪。

  • timeout > 0: poll 最多阻塞 timeout 毫秒。如果在超时前没有文件描述符就绪,则返回 0。

5. 返回值

成功时:

  • 返回 就绪的文件描述符的数量(即 revents 非零的 fds 元素个数)。这个数字可以是 0(表示超时)。

失败时:

  • 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EFAULT fds 指针无效,EINTR 调用被信号中断,EINVAL nfds 负数等)。

超时:

  • 如果在 timeout 指定的时间内没有任何文件描述符就绪,返回 0。

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

  • select: 一个更老的 I/O 多路复用函数,功能与 poll 类似,但在处理大量文件描述符时效率较低,且文件描述符集合有大小限制 (FD_SETSIZE)。

  • epoll_wait / epoll_ctl / epoll_create: Linux 特有的、更高效的 I/O 多路复用机制,特别适合处理大量的并发连接。它使用一个内核事件表来管理监视的文件描述符,避免了 poll/select 每次调用都需要传递整个文件描述符集合的开销。

  • read, write: 在 poll 返回某个文件描述符就绪后,通常会调用 read 或 write 来执行实际的 I/O 操作。

7. 示例代码

示例 1:监视标准输入和一个管道

这个例子演示如何使用 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
#include <poll.h>     // poll, struct pollfd
#include <unistd.h> // pipe, read, write, close, STDIN_FILENO
#include <stdio.h> // perror, printf, fprintf
#include <stdlib.h> // exit
#include <string.h> // strlen

int main() {
int pipefd&#91;2];
struct pollfd fds&#91;2];
int num_fds = 2;
int timeout_ms = 5000; // 5 秒超时
int ret;
char buffer&#91;100];
ssize_t bytes_read;

// 1. 创建管道
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}

// 2. 设置要监视的文件描述符数组
// 监视标准输入 (stdin)
fds&#91;0].fd = STDIN_FILENO; // 通常是 0
fds&#91;0].events = POLLIN; // 关心可读事件
fds&#91;0].revents = 0; // 内核会填充

// 监视管道的读端
fds&#91;1].fd = pipefd&#91;0];
fds&#91;1].events = POLLIN; // 关心可读事件
fds&#91;1].revents = 0; // 内核会填充

printf("Waiting up to %d ms for input from stdin or data in pipe...\n", timeout_ms);
printf("Type something in the terminal, or run 'echo hello > /proc/%d/fd/%d' in another terminal.\n",
getpid(), pipefd&#91;1]); // 提示用户如何向管道写入

// 3. 调用 poll 进行等待
ret = poll(fds, num_fds, timeout_ms);

// 4. 检查 poll 的返回值
if (ret == -1) {
perror("poll");
close(pipefd&#91;0]);
close(pipefd&#91;1]);
exit(EXIT_FAILURE);
} else if (ret == 0) {
printf("Timeout occurred! No data within %d ms.\n", timeout_ms);
} else {
printf("%d file descriptor(s) became ready.\n", ret);

// 5. 检查哪个文件描述符就绪了
for (int i = 0; i < num_fds; ++i) {
if (fds&#91;i].revents != 0) {
printf("fd %d (originally fd %d) is ready. revents = 0x%04x\n",
fds&#91;i].fd, fds&#91;i].fd, fds&#91;i].revents);

if (fds&#91;i].revents & POLLIN) {
printf(" -> POLLIN event on fd %d\n", fds&#91;i].fd);
if (fds&#91;i].fd == STDIN_FILENO) {
printf(" -> Reading from standard input:\n");
bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0'; // 确保字符串结束
printf(" -> Read from stdin: %s", buffer); // buffer 可能已包含 \n
}
} else if (fds&#91;i].fd == pipefd&#91;0]) {
printf(" -> Reading from pipe:\n");
bytes_read = read(pipefd&#91;0], buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" -> Read from pipe: %s", buffer);
}
}
}
// 可以检查其他 revents,如 POLLERR, POLLHUP 等
if (fds&#91;i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf(" -> Error or hangup or invalid fd on fd %d\n", fds&#91;i].fd);
}
}
}
}

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

return 0;
}

代码解释:

使用 pipe() 创建一个管道,得到读端 pipefd[0] 和写端 pipefd[1]。

定义一个 struct pollfd 数组 fds,包含两个元素。

第一个元素监视 STDIN_FILENO(标准输入),关心 POLLIN 事件。

第二个元素监视管道的读端 pipefd[0],也关心 POLLIN 事件。

调用 poll(fds, 2, 5000),等待最多 5 秒钟。

检查 poll 的返回值:

  • -1:错误。

  • 0:超时。

  • 0:就绪的文件描述符数量。

如果有文件描述符就绪(ret > 0),遍历 fds 数组,检查每个元素的 revents 字段。

如果 revents 包含 POLLIN,则调用 read 从对应的文件描述符读取数据。

最后关闭管道的两端。

示例 2:简单的 TCP 服务器(非阻塞 accept 和客户端 socket)

这个例子演示如何在 TCP 服务器中使用 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
#include <poll.h>      // poll, struct pollfd
#include <sys/socket.h> // socket, bind, listen, accept, recv, send
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h> // inet_addr, inet_ntoa (简化版,非线程安全)
#include <unistd.h> // close, read, write
#include <stdio.h> // perror, printf, fprintf
#include <stdlib.h> // exit
#include <string.h> // memset, strlen

#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
struct pollfd fds&#91;MAX_CLIENTS + 1]; // +1 for the listening socket
int nfds = 1; // Initially, only the listening socket
int timeout_ms = -1; // Block indefinitely
int activity;
char buffer&#91;BUFFER_SIZE] = {0};
char *hello = "Hello from server";

// 1. 创建服务器套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

// 2. 配置服务器地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口
address.sin_port = htons(PORT);

// 3. 绑定套接字
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}

// 4. 监听连接
if (listen(server_fd, 3) < 0) { // backlog=3
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}

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

// 5. 设置 poll 监视的初始文件描述符:监听套接字
fds&#91;0].fd = server_fd;
fds&#91;0].events = POLLIN; // 关心可读事件 (有新连接)
fds&#91;0].revents = 0;

// 初始化其他客户端槽位
for(int i = 1; i < MAX_CLIENTS + 1; i++) {
fds&#91;i].fd = -1; // -1 表示槽位空闲
fds&#91;i].events = POLLIN;
fds&#91;i].revents = 0;
}

// 6. 主循环
while(1) {
// 7. 调用 poll 等待事件
activity = poll(fds, nfds, timeout_ms);

if (activity < 0) {
perror("poll error");
break; // 或 exit(EXIT_FAILURE);
}

if (activity == 0) {
// 不应该发生,因为 timeout_ms = -1
printf("poll timeout (unexpected)\n");
continue;
}

// 8. 检查监听套接字 (fds&#91;0]) 是否有活动
if (fds&#91;0].revents & POLLIN) {
// 有新的客户端连接请求
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
continue; // 继续处理其他事件
}

printf("New connection, socket fd is %d, ip is : %s, port : %d\n",
new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));

// 将新连接的套接字添加到 poll 监视集合中
int i;
for (i = 1; i < MAX_CLIENTS + 1; i++) {
if (fds&#91;i].fd == -1) {
fds&#91;i].fd = new_socket;
fds&#91;i].events = POLLIN;
fds&#91;i].revents = 0;
if (i >= nfds) nfds = i + 1; // 更新监视的 fd 数量
// 发送欢迎信息
send(new_socket, hello, strlen(hello), 0);
printf("Welcome message sent\n");
break;
}
}
if (i == MAX_CLIENTS + 1) {
printf("Too many clients, connection rejected\n");
close(new_socket);
}
}

// 9. 检查已连接的客户端套接字是否有活动
for (int i = 1; i < nfds; i++) {
if (fds&#91;i].fd != -1 && (fds&#91;i].revents & POLLIN)) {
// 有数据从客户端发来
int sd = fds&#91;i].fd;
ssize_t valread = read(sd, buffer, BUFFER_SIZE - 1);
if (valread == 0) {
// 客户端断开连接
getpeername(sd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
printf("Host disconnected, ip %s, port %d\n",
inet_ntoa(address.sin_addr), ntohs(address.sin_port));
close(sd);
fds&#91;i].fd = -1; // 标记槽位为空闲
} else {
// 处理收到的数据
buffer&#91;valread] = '\0';
printf("Received message from socket %d: %s", sd, buffer);
// Echo 回去
send(sd, buffer, strlen(buffer), 0);
}
}
// 可以检查 POLLERR, POLLHUP 等错误事件
if (fds&#91;i].fd != -1 && (fds&#91;i].revents & (POLLERR | POLLHUP))) {
printf("Error or hangup on client socket %d\n", fds&#91;i].fd);
close(fds&#91;i].fd);
fds&#91;i].fd = -1;
}
}
}

// 10. 清理 (在真实应用中,需要更优雅的退出机制)
for(int i = 0; i < nfds; i++) {
if(fds&#91;i].fd != -1) {
close(fds&#91;i].fd);
}
}
close(server_fd);
printf("Server closed.\n");
return 0;
}

代码解释:

创建、绑定、监听 TCP 套接字。

初始化 struct pollfd 数组 fds。第一个元素 (fds[0]) 用于监视监听套接字 server_fd,关心 POLLIN 事件(表示有新的连接请求)。

数组的其余元素(fds[1] 到 fds[MAX_CLIENTS])用于监视已建立连接的客户端套接字。初始时,它们的 fd 被设置为 -1,表示空闲槽位。

进入主循环,调用 poll(fds, nfds, -1)。nfds 跟踪当前需要监视的 fds 数组元素个数(通常是已使用的槽位数)。

poll 返回后,检查返回值 activity。

如果 fds[0].revents & POLLIN 为真,说明监听套接字就绪,调用 accept 接受新连接。

将新获得的客户端套接字 new_socket 放入 fds 数组的一个空闲槽位中(fd 为 -1 的位置),并更新 nfds。

遍历 fds 数组中用于客户端的槽位(从索引 1 开始),检查 revents。

如果某个客户端套接字的 revents & POLLIN 为真,说明该客户端有数据可读,调用 read 读取数据。

如果 read 返回 0,表示客户端关闭了连接,关闭该套接字,并将 fds 中对应的 fd 设置回 -1。

如果 read 返回正数,表示读到了数据,这里简单地将其 echo 回客户端。

同样检查 POLLERR 和 POLLHUP 等错误事件。

这个例子展示了 poll 如何在一个单线程服务器中高效地管理多个并发连接。与为每个连接创建一个线程或进程相比,poll(以及更高效的 epoll)是构建高性能网络服务器的基础技术之一。

理解 poll 的关键是掌握 struct pollfd 数组的使用、events 和 revents 的含义,以及如何根据返回的就绪文件描述符数量和状态来处理相应的 I/O 操作。

poll系统调用详解, poll函数使用示例, linux poll系统调用, poll函数原理与应用, linux系统编程poll函数, poll函数如何工作, poll系统调用教程, poll函数在Linux中的应用, poll系统调用实例解析, poll函数编程指南

prctl系统调用及示例

prctl 函数详解

  1. 函数介绍

prctl 是 Linux 系统中用于进程控制的万能系统调用。可以把 prctl 想象成”进程的控制面板”——它允许你对进程的各种属性进行精细控制,就像调节电视遥控器上的各种设置一样。

prctl 提供了丰富的进程控制功能,包括设置进程名称、控制进程行为、管理安全属性等。它是 Linux 进程管理的重要工具。

相关文章:prctl系统调用及示例-CSDN博客 set_thread_area系统调用及示例 pwritev2系统调用及示例

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

int prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5);

  1. 功能

prctl 函数用于控制进程的各种属性和行为。它是一个多功能的系统调用,支持数十种不同的操作选项。

  1. 主要参数
  • option: 控制选项(详见下表)

  • arg2, arg3, arg4, arg5: 根据选项而定的参数

  1. 常用选项

选项功能参数说明PR_SET_NAME设置进程名称arg2: 指向名称字符串PR_GET_NAME获取进程名称arg2: 指向缓冲区PR_SET_SECCOMP设置安全计算模式seccomp 模式控制PR_GET_SECCOMP获取安全计算模式PR_SET_DUMPABLE设置核心转储权限0=不可转储, 1=可转储PR_GET_DUMPABLE获取核心转储权限PR_SET_KEEPCAPS设置保持权能标志0=不保持, 1=保持PR_GET_KEEPCAPS获取保持权能标志PR_SET_PDEATHSIG设置父进程死亡信号arg2: 信号编号PR_GET_PDEATHSIG获取父进程死亡信号arg2: 指向信号变量

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

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

  1. 常见错误码
  • EINVAL: 参数无效

  • EPERM: 权限不足

  • EFAULT: 参数指针无效

  • EACCES: 访问被拒绝

  1. 相似函数或关联函数
  • pthread_setname_np: 设置线程名称

  • getpid: 获取进程 ID

  • kill: 发送信号

  • setuid/setgid: 设置用户/组 ID

  • capset: 设置进程权能

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

int main() {
char old_name&#91;16];
char new_name&#91;16];
char current_name&#91;16];

printf("=== prctl 进程名称控制示例 ===\n\n");

// 获取当前进程名称
if (prctl(PR_GET_NAME, old_name) == 0) {
old_name&#91;15] = '\0'; // 确保字符串结束
printf("原始进程名称: %s\n", old_name);
} else {
perror("获取进程名称失败");
}

// 设置新的进程名称
snprintf(new_name, sizeof(new_name), "MyProcess_%d", getpid() % 1000);
if (prctl(PR_SET_NAME, new_name) == 0) {
printf("设置新进程名称: %s\n", new_name);
} else {
perror("设置进程名称失败");
}

// 验证名称是否设置成功
if (prctl(PR_GET_NAME, current_name) == 0) {
current_name&#91;15] = '\0';
printf("当前进程名称: %s\n", current_name);
printf("设置%s成功\n",
strcmp(current_name, new_name) == 0 ? "" : "未完全");
}

// 恢复原始名称
if (prctl(PR_SET_NAME, old_name) == 0) {
printf("恢复原始名称: %s\n", old_name);
}

printf("\n查看进程名称的方法:\n");
printf(" ps -ef | grep %d\n", getpid());
printf(" cat /proc/%d/comm\n", getpid());

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

// 父进程死亡信号处理
void parent_death_handler(int sig) {
printf("收到父进程死亡信号 %d\n", sig);
printf("子进程即将退出...\n");
exit(0);
}

int main() {
printf("=== prctl 进程安全控制示例 ===\n\n");

// 设置父进程死亡信号
if (prctl(PR_SET_PDEATHSIG, SIGTERM) == 0) {
printf("✓ 设置父进程死亡信号为 SIGTERM\n");
} else {
printf("✗ 设置父进程死亡信号失败: %s\n", strerror(errno));
}

// 设置核心转储权限
int old_dumpable = -1;
if (prctl(PR_GET_DUMPABLE, &old_dumpable) == 0) {
printf("当前核心转储权限: %s\n",
old_dumpable ? "允许" : "禁止");
}

// 禁止核心转储
if (prctl(PR_SET_DUMPABLE, 0) == 0) {
printf("✓ 禁止核心转储\n");

// 验证设置
int new_dumpable = -1;
if (prctl(PR_GET_DUMPABLE, &new_dumpable) == 0) {
printf("验证: 核心转储权限现在是 %s\n",
new_dumpable ? "允许" : "禁止");
}
}

// 恢复核心转储权限
if (old_dumpable != -1) {
prctl(PR_SET_DUMPABLE, old_dumpable);
}

printf("\n安全控制说明:\n");
printf("1. PR_SET_PDEATHSIG: 父进程死亡时发送信号\n");
printf("2. PR_SET_DUMPABLE: 控制核心转储权限\n");
printf("3. 提高进程安全性,防止敏感信息泄露\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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>

// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s &#91;选项]\n", program_name);
printf("\n选项:\n");
printf(" -n, --name=NAME 设置进程名称\n");
printf(" -s, --show 显示当前进程信息\n");
printf(" -d, --dumpable=0|1 设置核心转储权限\n");
printf(" -p, --pdeathsig=SIG 设置父进程死亡信号\n");
printf(" -h, --help 显示此帮助信息\n");
printf("\n示例:\n");
printf(" %s -n MyNewName # 设置进程名称\n", program_name);
printf(" %s -s # 显示进程信息\n", program_name);
printf(" %s -d 0 # 禁止核心转储\n", program_name);
printf(" %s -p 15 # 设置父进程死亡信号为 SIGTERM\n", program_name);
}

// 显示当前进程信息
void show_process_info() {
char name&#91;16];

printf("=== 当前进程信息 ===\n");
printf("进程 ID: %d\n", getpid());
printf("父进程 ID: %d\n", getppid());
printf("用户 ID: %d\n", getuid());
printf("组 ID: %d\n", getgid());

// 获取进程名称
if (prctl(PR_GET_NAME, name) == 0) {
name&#91;15] = '\0';
printf("进程名称: %s\n", name);
}

// 获取核心转储权限
int dumpable = -1;
if (prctl(PR_GET_DUMPABLE, &dumpable) == 0) {
printf("核心转储: %s\n", dumpable ? "允许" : "禁止");
}

// 获取父进程死亡信号
int pdeathsig = -1;
if (prctl(PR_GET_PDEATHSIG, &pdeathsig) == 0) {
if (pdeathsig > 0) {
printf("父进程死亡信号: %d\n", pdeathsig);
} else {
printf("父进程死亡信号: 无\n");
}
}
}

int main(int argc, char *argv&#91;]) {
char *new_name = NULL;
int dumpable = -1;
int pdeathsig = -1;
int show_info = 0;

printf("=== prctl 进程控制工具 ===\n\n");

// 解析命令行参数
static struct option long_options&#91;] = {
{"name", required_argument, 0, 'n'},
{"show", no_argument, 0, 's'},
{"dumpable", required_argument, 0, 'd'},
{"pdeathsig", required_argument, 0, 'p'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};

int opt;
while ((opt = getopt_long(argc, argv, "n:sd:p:h", long_options, NULL)) != -1) {
switch (opt) {
case 'n':
new_name = optarg;
break;
case 's':
show_info = 1;
break;
case 'd':
dumpable = atoi(optarg);
break;
case 'p':
pdeathsig = atoi(optarg);
break;
case 'h':
show_help(argv&#91;0]);
return 0;
default:
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv&#91;0]);
return 1;
}
}

// 显示信息
if (show_info || (!new_name && dumpable == -1 && pdeathsig == -1)) {
show_process_info();
printf("\n");
}

// 设置进程名称
if (new_name) {
if (prctl(PR_SET_NAME, new_name) == 0) {
printf("✓ 设置进程名称为: %s\n", new_name);
} else {
printf("✗ 设置进程名称失败: %s\n", strerror(errno));
}
}

// 设置核心转储权限
if (dumpable != -1) {
if (prctl(PR_SET_DUMPABLE, dumpable) == 0) {
printf("✓ %s核心转储\n", dumpable ? "允许" : "禁止");
} else {
printf("✗ 设置核心转储权限失败: %s\n", strerror(errno));
}
}

// 设置父进程死亡信号
if (pdeathsig > 0) {
if (prctl(PR_SET_PDEATHSIG, pdeathsig) == 0) {
printf("✓ 设置父进程死亡信号为: %d\n", pdeathsig);
} else {
printf("✗ 设置父进程死亡信号失败: %s\n", strerror(errno));
}
}

// 显示使用建议
if (new_name || dumpable != -1 || pdeathsig != -1) {
printf("\n=== 使用建议 ===\n");
printf("进程名称控制:\n");
printf(" - 用于进程标识和监控\n");
printf(" - 方便系统管理工具识别\n");
printf(" - 长度限制为 15 个字符\n");
printf("\n安全控制:\n");
printf(" - 禁止核心转储保护敏感信息\n");
printf(" - 设置父进程死亡信号实现优雅退出\n");
printf(" - 提高进程的安全性和可控性\n");
}

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
# 编译示例程序
gcc -o prctl_example1 example1.c
gcc -o prctl_example2 example2.c
gcc -o prctl_example3 example3.c

# 运行示例
./prctl_example1
./prctl_example2
./prctl_example3 --help
./prctl_example3 -s
./prctl_example3 -n TestProcess
./prctl_example3 -d 0

系统要求检查

1
2
3
4
5
6
7
8
9
10
# 检查内核版本(需要 2.1.57+)
uname -r

# 检查 prctl 支持
grep -w prctl /usr/include/asm/unistd_64.h

# 查看进程信息
cat /proc/self/comm
ps -p $$ -o pid,ppid,comm,cmd

重要注意事项

权限要求: 大多数操作不需要特殊权限

名称长度: 进程名称最长 15 个字符(加上终止符共 16 字节)

错误处理: 始终检查返回值和 errno

平台相关: prctl 是 Linux 特有的系统调用

安全性: 合理使用安全控制选项

实际应用场景

进程管理: 设置有意义的进程名称

安全控制: 控制核心转储和敏感信息

容器技术: 容器化进程控制

服务管理: 系统服务进程管理

调试工具: 调试和监控工具使用

常用选项详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 进程名称控制
prctl(PR_SET_NAME, "MyProcess"); // 设置进程名称
prctl(PR_GET_NAME, name_buffer); // 获取进程名称

// 安全控制
prctl(PR_SET_DUMPABLE, 0); // 禁止核心转储
prctl(PR_GET_DUMPABLE, &dumpable); // 获取核心转储权限
prctl(PR_SET_PDEATHSIG, SIGTERM); // 设置父进程死亡信号

// 权能控制
prctl(PR_SET_KEEPCAPS, 1); // 设置保持权能标志
prctl(PR_GET_KEEPCAPS, &keepcaps); // 获取保持权能标志

// 内存保护
prctl(PR_SET_MM, PR_SET_MM_START_BRK, addr); // 设置堆起始地址

最佳实践

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
// 安全的进程名称设置
int safe_set_process_name(const char *name) {
if (!name) {
errno = EINVAL;
return -1;
}

// 验证名称长度
if (strlen(name) >= 16) {
fprintf(stderr, "进程名称过长,最多 15 个字符\n");
errno = EINVAL;
return -1;
}

// 设置进程名称
int result = prctl(PR_SET_NAME, name);
if (result == -1) {
fprintf(stderr, "设置进程名称失败: %s\n", strerror(errno));
}

return result;
}

// 获取进程信息的安全函数
int get_process_info_safe(char *name, int *dumpable, int *pdeathsig) {
int result = 0;

// 获取进程名称
if (name) {
if (prctl(PR_GET_NAME, name) == -1) {
result = -1;
} else {
name&#91;15] = '\0'; // 确保字符串结束
}
}

// 获取核心转储权限
if (dumpable) {
if (prctl(PR_GET_DUMPABLE, dumpable) == -1) {
result = -1;
}
}

// 获取父进程死亡信号
if (pdeathsig) {
if (prctl(PR_GET_PDEATHSIG, pdeathsig) == -1) {
result = -1;
}
}

return result;
}

这些示例展示了 prctl 函数的各种使用方法,从基础的进程名称设置到完整的进程控制工具,帮助你全面掌握 Linux 系统中的进程控制机制。

ppoll系统调用及示例

ppoll 函数详解

  1. 函数介绍

ppoll 是 Linux 系统中用于同时监视多个文件描述符并等待特定信号的系统调用。可以把 ppoll 想象成”多功能的等待管家”——它不仅能像 poll 一样监视文件描述符的状态变化,还能在等待期间处理特定的信号,就像一个既能看门又能接电话的管家。

与传统的 poll 函数相比,ppoll 提供了更好的信号处理机制,避免了信号处理函数中调用不可重入函数的问题。

相关文章:ppoll系统调用及示例-CSDN博客 epoll_create系统调用及示例 pselect系统调用及示例 epoll_create1系统调用及示例 poll系统调用及示例-CSDN博客

  1. 函数原型
1
2
3
4
5
6
7
8
9
#define _GNU_SOURCE
#include <poll.h>
#include <signal.h>
#include <time.h>

int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts,
const sigset_t *sigmask);

  1. 功能

ppoll 函数用于同时监视多个文件描述符的状态变化,并可以选择性地阻塞指定的信号。它结合了 poll 的文件描述符监视功能和信号屏蔽功能。

  1. 参数
  • fds: 指向 pollfd 结构体数组的指针,描述要监视的文件描述符

  • nfds: fds 数组中的元素个数

  • timeout_ts: 指向超时时间的指针(NULL 表示无限等待)

  • sigmask: 指向信号屏蔽集的指针(NULL 表示不改变信号屏蔽)

  1. pollfd 结构体
1
2
3
4
5
6
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 实际发生的事件 */
};

事件类型(events 和 revents 字段)

事件值说明POLLIN0x001有数据可读POLLPRI0x002有紧急数据可读POLLOUT0x004文件描述符可写POLLERR0x008发生错误POLLHUP0x010连接挂起POLLNVAL0x020文件描述符无效POLLRDNORM0x040有普通数据可读POLLRDBAND0x080有优先数据可读POLLWRNORM0x100可以正常写入POLLWRBAND0x200可以优先写入

  1. timespec 结构体
1
2
3
4
5
struct timespec {
time_t tv_sec; /* 秒数 */
long tv_nsec; /* 纳秒数 */
};

  1. 返回值
  • 成功: 返回准备就绪的文件描述符数量(0 表示超时)

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

  1. 常见错误码
  • EBADF: 一个或多个文件描述符无效

  • EFAULT: fds 指针无效

  • EINTR: 被未屏蔽的信号中断

  • EINVAL: 参数无效

  • ENOMEM: 内存不足

  1. 相似函数或关联函数
  • poll: 基本的文件描述符监视函数

  • select: 传统的文件描述符监视函数

  • pselect: 带信号屏蔽的 select

  • epoll_wait: epoll 接口的等待函数

  • read/write: 文件读写操作

  • signal/sigaction: 信号处理函数

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

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

// 信号处理函数
void signal_handler(int sig) {
signal_received = sig;
printf("\n收到信号 %d\n", sig);
}

// 设置信号处理
void setup_signals() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;

sigaction(SIGINT, &sa, NULL); // Ctrl+C
sigaction(SIGTERM, &sa, NULL); // 终止信号
sigaction(SIGALRM, &sa, NULL); // 定时器信号
}

// 显示 pollfd 状态
void show_pollfd_status(struct pollfd *pfd, const char *description) {
printf("%s: fd=%d", description, pfd->fd);
printf(" events=");
if (pfd->events & POLLIN) printf("POLLIN ");
if (pfd->events & POLLOUT) printf("POLLOUT ");
if (pfd->events & POLLPRI) printf("POLLPRI ");
printf("\n");

printf(" revents=");
if (pfd->revents & POLLIN) printf("POLLIN ");
if (pfd->revents & POLLOUT) printf("POLLOUT ");
if (pfd->revents & POLLPRI) printf("POLLPRI ");
if (pfd->revents & POLLERR) printf("POLLERR ");
if (pfd->revents & POLLHUP) printf("POLLHUP ");
if (pfd->revents & POLLNVAL) printf("POLLNVAL ");
printf("\n");
}

int main() {
struct pollfd fds&#91;2];
struct timespec timeout;
sigset_t sigmask;
int ready;

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

// 设置信号处理
setup_signals();

// 初始化 pollfd 结构
fds&#91;0].fd = STDIN_FILENO; // 标准输入
fds&#91;0].events = POLLIN; // 监视可读事件
fds&#91;0].revents = 0;

fds&#91;1].fd = -1; // 模拟定时器文件描述符
fds&#91;1].events = POLLIN; // 监视可读事件
fds&#91;1].revents = 0;

// 设置超时时间 (5 秒)
timeout.tv_sec = 5;
timeout.tv_nsec = 0;

// 设置信号屏蔽集 (不屏蔽任何信号)
sigemptyset(&sigmask);

printf("监视设置:\n");
show_pollfd_status(&fds&#91;0], "标准输入");
printf("超时时间: %ld 秒\n", (long)timeout.tv_sec);
printf("按 Enter 键或等待超时...\n");
printf("按 Ctrl+C 发送信号\n\n");

// 使用 ppoll 监视文件描述符
ready = ppoll(fds, 1, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
printf("ppoll 被信号中断\n");
if (signal_received) {
printf("收到信号: %d\n", signal_received);
}
} else {
perror("ppoll 失败");
}
} else if (ready == 0) {
printf("超时: 没有文件描述符准备就绪\n");
} else {
printf("准备就绪的文件描述符数量: %d\n", ready);

if (fds&#91;0].revents & POLLIN) {
printf("标准输入有数据可读\n");

// 读取输入
char buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("读取到: %s", buffer);
}
}

if (fds&#91;0].revents & POLLERR) {
printf("标准输入发生错误\n");
}

if (fds&#91;0].revents & POLLHUP) {
printf("标准输入连接挂起\n");
}
}

printf("\n=== ppoll 特点 ===\n");
printf("1. 原子操作: 等待和信号屏蔽是原子的\n");
printf("2. 精确超时: 支持纳秒级超时\n");
printf("3. 信号安全: 避免信号处理中的竞态条件\n");
printf("4. 灵活屏蔽: 可以精确控制信号屏蔽\n");
printf("5. 高效监视: 同时监视多个文件描述符\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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

// 全局变量
volatile sig_atomic_t terminate_flag = 0;
volatile sig_atomic_t alarm_count = 0;

// 信号处理函数
void termination_handler(int sig) {
printf("\n收到终止信号 %d\n", sig);
terminate_flag = 1;
}

void alarm_handler(int sig) {
alarm_count++;
printf("\n收到定时器信号 %d (计数: %d)\n", sig, alarm_count);
}

// 设置信号处理
void setup_signal_handlers() {
struct sigaction sa;

// 设置终止信号处理
sa.sa_handler = termination_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);

// 设置定时器信号处理
sa.sa_handler = alarm_handler;
sigaction(SIGALRM, &sa, NULL);
}

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

const char *content = "这是测试文件的内容\n用于演示 ppoll 功能\n";
write(fd, content, strlen(content));
lseek(fd, 0, SEEK_SET);

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

// 显示文件描述符状态
void show_fds_status(struct pollfd *fds, int nfds) {
printf("文件描述符状态:\n");
for (int i = 0; i < nfds; i++) {
printf(" &#91;%d] fd=%d events=0x%04x revents=0x%04x",
i, fds&#91;i].fd, fds&#91;i].events, fds&#91;i].revents);

if (fds&#91;i].revents != 0) {
printf(" (");
if (fds&#91;i].revents & POLLIN) printf("IN ");
if (fds&#91;i].revents & POLLOUT) printf("OUT ");
if (fds&#91;i].revents & POLLPRI) printf("PRI ");
if (fds&#91;i].revents & POLLERR) printf("ERR ");
if (fds&#91;i].revents & POLLHUP) printf("HUP ");
if (fds&#91;i].revents & POLLNVAL) printf("NVAL ");
printf(")");
}
printf("\n");
}
}

int main() {
struct pollfd fds&#91;3];
struct timespec timeout;
sigset_t sigmask;
int test_fd1, test_fd2;
int ready;
int running = 1;

printf("=== ppoll 多文件描述符监视示例 ===\n\n");

// 设置信号处理
setup_signal_handlers();

// 创建测试文件
test_fd1 = create_test_file("test1.txt");
test_fd2 = create_test_file("test2.txt");

if (test_fd1 == -1 || test_fd2 == -1) {
if (test_fd1 != -1) close(test_fd1);
if (test_fd2 != -1) close(test_fd2);
unlink("test1.txt");
unlink("test2.txt");
return 1;
}

// 初始化 pollfd 结构
fds&#91;0].fd = STDIN_FILENO; // 标准输入
fds&#91;0].events = POLLIN;
fds&#91;0].revents = 0;

fds&#91;1].fd = test_fd1; // 测试文件1
fds&#91;1].events = POLLIN;
fds&#91;1].revents = 0;

fds&#91;2].fd = test_fd2; // 测试文件2
fds&#91;2].events = POLLIN;
fds&#91;2].revents = 0;

// 设置超时时间 (3 秒)
timeout.tv_sec = 3;
timeout.tv_nsec = 0;

// 设置信号屏蔽集 (不屏蔽任何信号)
sigemptyset(&sigmask);

printf("监视设置完成:\n");
show_fds_status(fds, 3);
printf("超时时间: %ld 秒\n", (long)timeout.tv_sec);
printf("按 Enter 键测试标准输入\n");
printf("按 Ctrl+C 终止程序\n\n");

// 启动定时器
alarm(2); // 2 秒后发送 SIGALRM

// 主循环
while (running && !terminate_flag) {
printf("等待事件... (按 Ctrl+C 退出)\n");

ready = ppoll(fds, 3, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
printf("ppoll 被信号中断\n");
if (alarm_count > 0) {
printf("定时器触发次数: %d\n", alarm_count);
alarm(2); // 重新启动定时器
}
continue;
} else {
perror("ppoll 失败");
break;
}
} else if (ready == 0) {
printf("超时: 没有文件描述符准备就绪\n");
alarm(2); // 重新启动定时器
} else {
printf("准备就绪的文件描述符数量: %d\n", ready);
show_fds_status(fds, 3);

// 处理各个文件描述符
for (int i = 0; i < 3; i++) {
if (fds&#91;i].revents & POLLIN) {
switch (i) {
case 0: // 标准输入
printf("标准输入有数据:\n");
{
char buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 读取到: %s", buffer);
}
}
break;

case 1: // 测试文件1
printf("测试文件1 有数据可读\n");
lseek(test_fd1, 0, SEEK_SET);
{
char buffer&#91;128];
ssize_t bytes_read = read(test_fd1, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 文件1内容: %s", buffer);
}
}
break;

case 2: // 测试文件2
printf("测试文件2 有数据可读\n");
lseek(test_fd2, 0, SEEK_SET);
{
char buffer&#91;128];
ssize_t bytes_read = read(test_fd2, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 文件2内容: %s", buffer);
}
}
break;
}
}

if (fds&#91;i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("文件描述符 %d 发生错误或异常\n", fds&#91;i].fd);
if (fds&#91;i].revents & POLLERR) printf(" 错误\n");
if (fds&#91;i].revents & POLLHUP) printf(" 挂起\n");
if (fds&#91;i].revents & POLLNVAL) printf(" 无效\n");
}
}

printf("\n");
}

// 重置 revents
for (int i = 0; i < 3; i++) {
fds&#91;i].revents = 0;
}
}

// 清理资源
printf("\n清理资源...\n");
close(test_fd1);
close(test_fd2);
unlink("test1.txt");
unlink("test2.txt");

printf("程序正常退出\n");

printf("\n=== ppoll 高级特性 ===\n");
printf("1. 原子操作: 等待和信号处理是原子的\n");
printf("2. 精确控制: 可以精确指定信号屏蔽\n");
printf("3. 灵活超时: 支持纳秒级超时控制\n");
printf("4. 多路复用: 同时监视多个文件描述符\n");
printf("5. 事件驱动: 基于事件的通知机制\n");
printf("\n");
printf("优势对比:\n");
printf(" select: 有文件描述符数量限制\n");
printf(" poll: 无文件描述符数量限制\n");
printf(" ppoll: 增强的信号处理能力\n");
printf(" epoll: 更高的性能 (Linux 特有)\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
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <getopt.h>

// 服务器配置结构体
struct server_config {
int port;
int max_clients;
int timeout_seconds;
int verbose;
int use_signals;
char *log_file;
};

// 客户端信息结构体
struct client_info {
int fd;
int connected;
time_t connect_time;
char buffer&#91;1024];
size_t buffer_pos;
};

// 服务器状态结构体
struct server_state {
int running;
int client_count;
struct client_info *clients;
int max_clients;
FILE *log_fp;
};

// 全局变量
volatile sig_atomic_t server_terminate = 0;
volatile sig_atomic_t server_reload = 0;

// 信号处理函数
void signal_handler(int sig) {
switch (sig) {
case SIGINT:
case SIGTERM:
printf("\n收到终止信号,准备关闭服务器...\n");
server_terminate = 1;
break;
case SIGHUP:
printf("\n收到重载信号,重新加载配置...\n");
server_reload = 1;
break;
case SIGUSR1:
printf("\n收到用户信号 1\n");
break;
case SIGUSR2:
printf("\n收到用户信号 2\n");
break;
}
}

// 设置信号处理
void setup_server_signals() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;

sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
}

// 初始化服务器状态
int init_server_state(struct server_state *state, int max_clients) {
state->running = 1;
state->client_count = 0;
state->max_clients = max_clients;

state->clients = calloc(max_clients, sizeof(struct client_info));
if (!state->clients) {
perror("分配客户端数组失败");
return -1;
}

state->log_fp = stderr; // 默认输出到标准错误
return 0;
}

// 添加客户端
int add_client(struct server_state *state, int client_fd) {
if (state->client_count >= state->max_clients) {
fprintf(stderr, "客户端数量已达上限: %d\n", state->max_clients);
return -1;
}

for (int i = 0; i < state->max_clients; i++) {
if (!state->clients&#91;i].connected) {
state->clients&#91;i].fd = client_fd;
state->clients&#91;i].connected = 1;
state->clients&#91;i].connect_time = time(NULL);
state->clients&#91;i].buffer_pos = 0;
state->client_count++;

printf("添加客户端 %d (fd: %d),当前客户端数: %d\n",
i, client_fd, state->client_count);
return i;
}
}

fprintf(stderr, "找不到空闲的客户端槽位\n");
return -1;
}

// 移除客户端
void remove_client(struct server_state *state, int client_index) {
if (client_index >= 0 && client_index < state->max_clients &&
state->clients&#91;client_index].connected) {

close(state->clients&#91;client_index].fd);
memset(&state->clients&#91;client_index], 0, sizeof(struct client_info));
state->client_count--;

printf("移除客户端 %d,剩余客户端数: %d\n",
client_index, state->client_count);
}
}

// 处理客户端数据
void handle_client_data(struct server_state *state, int client_index) {
struct client_info *client = &state->clients&#91;client_index];

// 模拟读取客户端数据
char buffer&#91;256];
ssize_t bytes_read = read(client->fd, buffer, sizeof(buffer) - 1);

if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("客户端 %d 发送数据: %s", client_index, buffer);

// 回显数据
write(client->fd, "Echo: ", 6);
write(client->fd, buffer, bytes_read);

} else if (bytes_read == 0) {
printf("客户端 %d 断开连接\n", client_index);
remove_client(state, client_index);
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("读取客户端数据失败");
remove_client(state, client_index);
}
}
}

// 显示服务器状态
void show_server_status(const struct server_state *state) {
printf("=== 服务器状态 ===\n");
printf("运行状态: %s\n", state->running ? "运行中" : "已停止");
printf("客户端数量: %d/%d\n", state->client_count, state->max_clients);
printf("当前时间: %s", ctime(&(time_t){time(NULL)}));

if (state->client_count > 0) {
printf("连接的客户端:\n");
for (int i = 0; i < state->max_clients; i++) {
if (state->clients&#91;i].connected) {
printf(" &#91;%d] fd=%d 连接时间: %s",
i, state->clients&#91;i].fd,
ctime(&state->clients&#91;i].connect_time));
}
}
}
printf("\n");
}

// 模拟服务器主循环
int run_server_simulation(struct server_state *state, const struct server_config *config) {
struct pollfd *fds = NULL;
struct timespec timeout;
sigset_t sigmask;
int ready;

printf("启动服务器模拟...\n");
printf("最大客户端数: %d\n", config->max_clients);
printf("超时时间: %d 秒\n", config->timeout_seconds);
printf("使用信号处理: %s\n", config->use_signals ? "是" : "否");
printf("\n");

// 分配 pollfd 数组 (1个用于标准输入 + 最大客户端数)
fds = calloc(1 + config->max_clients, sizeof(struct pollfd));
if (!fds) {
perror("分配 pollfd 数组失败");
return -1;
}

// 初始化标准输入监视
fds&#91;0].fd = STDIN_FILENO;
fds&#91;0].events = POLLIN;
fds&#91;0].revents = 0;

// 设置超时时间
timeout.tv_sec = config->timeout_seconds;
timeout.tv_nsec = 0;

// 设置信号屏蔽集
sigemptyset(&sigmask);
if (!config->use_signals) {
// 如果不使用信号,可以屏蔽一些信号
sigaddset(&sigmask, SIGUSR1);
sigaddset(&sigmask, SIGUSR2);
}

// 显示初始状态
show_server_status(state);

// 主循环
while (state->running && !server_terminate) {
// 更新 pollfd 数组
int nfds = 1; // 至少包含标准输入

// 添加连接的客户端
for (int i = 0; i < state->max_clients; i++) {
if (state->clients&#91;i].connected) {
fds&#91;nfds].fd = state->clients&#91;i].fd;
fds&#91;nfds].events = POLLIN;
fds&#91;nfds].revents = 0;
nfds++;
}
}

if (config->verbose) {
printf("监视 %d 个文件描述符...\n", nfds);
}

// 使用 ppoll 等待事件
ready = ppoll(fds, nfds, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
if (config->verbose) {
printf("ppoll 被信号中断\n");
}

if (server_reload) {
printf("重新加载配置...\n");
server_reload = 0;
}

continue;
} else {
perror("ppoll 失败");
break;
}
} else if (ready == 0) {
if (config->verbose) {
printf("超时: 没有事件发生\n");
}
} else {
if (config->verbose) {
printf("准备就绪的文件描述符数量: %d\n", ready);
}

// 处理事件
for (int i = 0; i < nfds; i++) {
if (fds&#91;i].revents & POLLIN) {
if (i == 0) {
// 标准输入事件
char input_buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, input_buffer, sizeof(input_buffer) - 1);
if (bytes_read > 0) {
input_buffer&#91;bytes_read] = '\0';

// 处理特殊命令
if (strncmp(input_buffer, "quit", 4) == 0 ||
strncmp(input_buffer, "exit", 4) == 0) {
printf("收到退出命令\n");
state->running = 0;
server_terminate = 1;
} else if (strncmp(input_buffer, "status", 6) == 0) {
show_server_status(state);
} else if (strncmp(input_buffer, "help", 4) == 0) {
printf("可用命令:\n");
printf(" quit/exit - 退出服务器\n");
printf(" status - 显示服务器状态\n");
printf(" help - 显示帮助信息\n");
printf(" Ctrl+C - 发送终止信号\n");
} else {
printf("收到输入: %s", input_buffer);

// 模拟添加客户端
if (strncmp(input_buffer, "connect", 7) == 0) {
if (state->client_count < state->max_clients) {
// 模拟创建客户端连接
int fake_fd = 1000 + state->client_count;
int client_index = add_client(state, fake_fd);
if (client_index != -1) {
printf("模拟客户端连接成功 (fd: %d)\n", fake_fd);
}
} else {
printf("客户端数量已达上限\n");
}
}
}
}
} else {
// 客户端事件
int client_fd = fds&#91;i].fd;
int client_index = -1;

// 查找对应的客户端
for (int j = 0; j < state->max_clients; j++) {
if (state->clients&#91;j].connected &&
state->clients&#91;j].fd == client_fd) {
client_index = j;
break;
}
}

if (client_index != -1) {
handle_client_data(state, client_index);
} else {
printf("未知客户端事件 (fd: %d)\n", client_fd);
}
}
}

if (fds&#91;i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("文件描述符 %d 发生异常\n", fds&#91;i].fd);

if (i > 0) {
// 客户端异常
int client_fd = fds&#91;i].fd;
for (int j = 0; j < state->max_clients; j++) {
if (state->clients&#91;j].connected &&
state->clients&#91;j].fd == client_fd) {
remove_client(state, j);
break;
}
}
}
}
}
}

// 重置 revents
for (int i = 0; i < nfds; i++) {
fds&#91;i].revents = 0;
}
}

// 清理资源
free(fds);

// 关闭所有客户端连接
for (int i = 0; i < state->max_clients; i++) {
if (state->clients&#91;i].connected) {
remove_client(state, i);
}
}

printf("服务器模拟结束\n");
return 0;
}

// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s &#91;选项]\n", program_name);
printf("\n选项:\n");
printf(" -p, --port=PORT 监听端口 (默认 8080)\n");
printf(" -c, --clients=NUM 最大客户端数 (默认 10)\n");
printf(" -t, --timeout=SECONDS 超时时间 (默认 30 秒)\n");
printf(" -v, --verbose 详细输出\n");
printf(" -s, --signals 启用信号处理\n");
printf(" -l, --log=FILE 日志文件\n");
printf(" -h, --help 显示此帮助信息\n");
printf("\n示例:\n");
printf(" %s # 使用默认设置运行\n", program_name);
printf(" %s -c 20 -t 60 -v # 20个客户端,60秒超时,详细输出\n", program_name);
printf(" %s -s -l server.log # 启用信号处理,记录日志\n", program_name);
printf(" %s --help # 显示帮助信息\n", program_name);
}

int main(int argc, char *argv&#91;]) {
struct server_config config = {
.port = 8080,
.max_clients = 10,
.timeout_seconds = 30,
.verbose = 0,
.use_signals = 0,
.log_file = NULL
};

struct server_state server_state_struct;

printf("=== ppoll 事件驱动服务器模拟器 ===\n\n");

// 解析命令行参数
static struct option long_options&#91;] = {
{"port", required_argument, 0, 'p'},
{"clients", required_argument, 0, 'c'},
{"timeout", required_argument, 0, 't'},
{"verbose", no_argument, 0, 'v'},
{"signals", no_argument, 0, 's'},
{"log", required_argument, 0, 'l'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};

int opt;
while ((opt = getopt_long(argc, argv, "p:c:t:vsl:h", long_options, NULL)) != -1) {
switch (opt) {
case 'p':
config.port = atoi(optarg);
break;
case 'c':
config.max_clients = atoi(optarg);
if (config.max_clients <= 0) config.max_clients = 10;
break;
case 't':
config.timeout_seconds = atoi(optarg);
if (config.timeout_seconds <= 0) config.timeout_seconds = 30;
break;
case 'v':
config.verbose = 1;
break;
case 's':
config.use_signals = 1;
break;
case 'l':
config.log_file = optarg;
break;
case 'h':
show_help(argv&#91;0]);
return 0;
default:
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv&#91;0]);
return 1;
}
}

// 设置信号处理
if (config.use_signals) {
setup_server_signals();
printf("✓ 信号处理已启用\n");
}

// 初始化服务器状态
if (init_server_state(&server_state_struct, config.max_clients) == -1) {
return 1;
}

// 设置日志文件
if (config.log_file) {
server_state_struct.log_fp = fopen(config.log_file, "a");
if (server_state_struct.log_fp) {
printf("✓ 日志文件: %s\n", config.log_file);
} else {
perror("打开日志文件失败");
config.log_file = NULL;
server_state_struct.log_fp = stderr;
}
}

printf("服务器配置:\n");
printf(" 监听端口: %d\n", config.port);
printf(" 最大客户端数: %d\n", config.max_clients);
printf(" 超时时间: %d 秒\n", config.timeout_seconds);
printf(" 详细输出: %s\n", config.verbose ? "是" : "否");
printf(" 信号处理: %s\n", config.use_signals ? "是" : "否");
printf(" 日志文件: %s\n", config.log_file ? config.log_file : "标准错误");
printf("\n");

printf("启动服务器模拟...\n");
printf("可用命令:\n");
printf(" 输入 'status' 查看服务器状态\n");
printf(" 输入 'connect' 模拟客户端连接\n");
printf(" 输入 'quit' 或 'exit' 退出服务器\n");
printf(" 输入 'help' 显示帮助信息\n");
printf(" 按 Ctrl+C 发送终止信号\n");
printf("\n");

// 运行服务器模拟
int result = run_server_simulation(&server_state_struct, &config);

// 清理资源
if (server_state_struct.clients) {
free(server_state_struct.clients);
}

if (config.log_file && server_state_struct.log_fp != stderr) {
fclose(server_state_struct.log_fp);
}

printf("\n=== ppoll 服务器应用说明 ===\n");
printf("核心技术:\n");
printf("1. 多路复用: 同时监视多个文件描述符\n");
printf("2. 事件驱动: 基于事件的通知机制\n");
printf("3. 信号安全: 原子的信号处理\n");
printf("4. 超时控制: 精确的超时管理\n");
printf("5. 资源管理: 动态的客户端管理\n");
printf("\n");
printf("ppoll 优势:\n");
printf("1. 原子性: 等待和信号处理是原子操作\n");
printf("2. 灵活性: 可以精确控制信号屏蔽\n");
printf("3. 精确性: 纳秒级超时控制\n");
printf("4. 可扩展: 无文件描述符数量限制\n");
printf("5. 安全性: 避免信号处理中的竞态条件\n");
printf("\n");
printf("实际应用场景:\n");
printf("1. 网络服务器: HTTP/WebSocket 服务器\n");
printf("2. 代理服务: 反向代理、负载均衡\n");
printf("3. 实时应用: 游戏服务器、聊天应用\n");
printf("4. 监控系统: 系统监控、日志收集\n");
printf("5. 数据处理: 流数据处理、ETL 系统\n");

return result;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 编译示例程序
gcc -o ppoll_example1 example1.c
gcc -o ppoll_example2 example2.c
gcc -o ppoll_example3 example3.c

# 运行示例
./ppoll_example1
./ppoll_example2
./ppoll_example3 --help
./ppoll_example3 -c 5 -t 10 -v -s

# 测试信号处理
./ppoll_example3 -s &
PID=$!
sleep 2
kill -USR1 $PID
sleep 2
kill -TERM $PID

系统要求检查

1
2
3
4
5
6
7
8
9
10
11
12
# 检查内核版本(需要 2.6.16+)
uname -r

# 检查 glibc 版本(需要 2.4+)
ldd --version

# 检查 ppoll 系统调用支持
grep -w ppoll /usr/include/asm/unistd_64.h

# 查看系统调用表
cat /proc/kallsyms | grep ppoll

重要注意事项

内核版本: 需要 Linux 2.6.16+ 内核支持

glibc 版本: 需要 glibc 2.4+ 支持

编译标志: 需要定义 _GNU_SOURCE

错误处理: 始终检查返回值和 errno

信号安全: 正确处理 EINTR 错误

资源清理: 及时关闭文件描述符

超时处理: 合理设置超时时间

与相关函数的比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// select - 传统的文件描述符监视
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

// poll - 基本的文件描述符监视
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

// ppoll - 增强的文件描述符监视(带信号处理)
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts,
const sigset_t *sigmask);

// epoll - Linux 特有的高性能接口
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);

实际应用场景

网络服务器: HTTP、WebSocket、TCP 服务器

代理服务: 反向代理、负载均衡器

实时应用: 游戏服务器、聊天应用

监控系统: 系统监控、日志收集

数据处理: 流数据处理、ETL 系统

文件系统: 文件监控、备份系统

设备驱动: 设备事件处理、I/O 多路复用

最佳实践

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
// 安全的 ppoll 封装函数
int safe_ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts,
const sigset_t *sigmask) {
// 验证参数
if (!fds || nfds == 0) {
errno = EINVAL;
return -1;
}

// 执行 ppoll
int result = ppoll(fds, nfds, timeout_ts, sigmask);

// 处理常见错误
if (result == -1) {
switch (errno) {
case EINTR:
// 被信号中断是正常的
break;
case EBADF:
fprintf(stderr, "错误: 包含无效的文件描述符\n");
break;
case EFAULT:
fprintf(stderr, "错误: 参数指针无效\n");
break;
case EINVAL:
fprintf(stderr, "错误: 参数无效\n");
break;
}
}

return result;
}

// 事件处理循环模板
int event_loop_template(struct pollfd *fds, int nfds, int timeout_seconds) {
struct timespec timeout;
sigset_t sigmask;

// 设置超时
timeout.tv_sec = timeout_seconds;
timeout.tv_nsec = 0;

// 设置信号屏蔽
sigemptyset(&sigmask);

while (1) {
int ready = safe_ppoll(fds, nfds, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
// 被信号中断,继续循环
continue;
} else {
perror("ppoll 失败");
return -1;
}
} else if (ready == 0) {
// 超时处理
printf("超时: 没有事件发生\n");
continue;
} else {
// 处理准备就绪的文件描述符
for (int i = 0; i < nfds; i++) {
if (fds&#91;i].revents & POLLIN) {
// 处理可读事件
handle_readable_fd(fds&#91;i].fd);
}
if (fds&#91;i].revents & POLLOUT) {
// 处理可写事件
handle_writable_fd(fds&#91;i].fd);
}
if (fds&#91;i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
// 处理错误事件
handle_error_fd(fds&#91;i].fd, fds&#91;i].revents);
}
}
}

// 重置 revents
for (int i = 0; i < nfds; i++) {
fds&#91;i].revents = 0;
}
}

return 0;
}

这些示例展示了 ppoll 函数的各种使用方法,从基础的文件描述符监视到完整的事件驱动服务器模拟,帮助你全面掌握 Linux 系统中的高级 I/O 多路复用机制。

preadv2系统调用及示例

preadv2系统调用及示例

我们继续按照您的要求学习 Linux 系统编程中的重要函数。这次我们介绍 preadv2、pwritev2 和 pkey_mprotect。

函数 1: preadv2

1. 函数介绍

preadv2 (pread vector 2) 是 preadv 系统调用的扩展版本。它结合了 pread(带偏移量读取)和 readv(分散读取)的优点,并引入了一个新的 flags 参数,提供了更灵活的 I/O 控制选项。

简单来说,preadv2 允许你从文件的指定偏移量开始,将数据分散读入到多个不连续的缓冲区中,同时还能指定一些高级 I/O 行为(通过 flags)。

2. 函数原型

1
2
3
4
5
6
#define _GNU_SOURCE // 必须定义以使用 preadv2
#include <sys/uio.h> // struct iovec
#include <unistd.h> // ssize_t

ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

  • 从文件描述符 fd 指定的文件中,从绝对偏移量 offset 开始读取数据。

  • 将读取的数据分散存储到由 iov 和 iovcnt 指定的多个缓冲区中。

  • 不修改文件的当前读写位置指针(lseek 位置)。

  • 根据 flags 参数执行特定的 I/O 操作。

4. 参数

  • int fd: 有效的文件描述符。

  • const struct iovec *iov: 指向 struct iovec 数组的指针,描述了多个分散的缓冲区。

  • int iovcnt: iov 数组中元素的个数。

  • off_t offset: 在文件中开始读取的绝对偏移量(以字节为单位)。必须是非负数。

int flags: 控制 I/O 行为的标志。可以是以下值的按位或组合:

  • 0: 默认行为,等同于 preadv。

  • RWF_HIPRI: 尝试使用高优先级/实时 I/O(如果内核和设备支持)。

  • RWF_DSYNC: 要求 I/O 操作具有数据同步持久性(类似于 O_DSYNC)。

  • RWF_SYNC: 要求 I/O 操作具有文件同步持久性(类似于 O_SYNC)。

  • RWF_NOWAIT: 非阻塞。如果 I/O 无法立即完成(例如,需要从磁盘读取而数据不在页缓存中),则不等待,立即返回错误 EAGAIN。这需要内核和文件系统支持。

  • RWF_APPEND: 强制将写入追加到文件末尾(仅对 pwritev2 有效)。

5. 返回值

  • 成功时: 返回实际读取的总字节数(0 表示 EOF)。

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

函数 2: pwritev2

1. 函数介绍

pwritev2 (pwrite vector 2) 是 pwritev 系统调用的扩展版本。它结合了 pwrite(带偏移量写入)和 writev(集中写入)的优点,并同样引入了 flags 参数。

简单来说,pwritev2 允许你从多个不连续的缓冲区中收集数据,并将其写入到文件的指定偏移量处,同时还能指定一些高级 I/O 行为(通过 flags)。

2. 函数原型

1
2
3
4
5
6
#define _GNU_SOURCE
#include <sys/uio.h>
#include <unistd.h>

ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

  • 从由 iov 和 iovcnt 指定的多个缓冲区中收集数据。

  • 将收集到的数据写入到文件描述符 fd 指定的文件中,从绝对偏移量 offset 开始写入。

  • 不修改文件的当前读写位置指针(lseek 位置)。

  • 根据 flags 参数执行特定的 I/O 操作。

4. 参数

  • int fd: 有效的文件描述符。

  • const struct iovec *iov: 指向 struct iovec 数组的指针,描述了多个包含数据的缓冲区。

  • int iovcnt: iov 数组中元素的个数。

off_t offset: 在文件中开始写入的绝对偏移量(以字节为单位)。必须是非负数。

  • 如果文件以 O_APPEND 模式打开,或者 flags 中设置了 RWF_APPEND,则 offset 参数会被忽略,数据总是被写入到文件末尾。

int flags: 控制 I/O 行为的标志。可以是以下值的按位或组合:

  • 0: 默认行为,等同于 pwritev。

  • RWF_HIPRI: 尝试使用高优先级/实时 I/O。

  • RWF_DSYNC: 要求数据同步持久性。

  • RWF_SYNC: 要求文件同步持久性。

  • RWF_NOWAIT: 非阻塞。如果 I/O 无法立即完成,立即返回错误 EAGAIN。

  • RWF_APPEND: 强制将写入追加到文件末尾,即使文件没有以 O_APPEND 打开。

5. 返回值

  • 成功时: 返回实际写入的总字节数。

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

函数 3: pkey_mprotect

1. 函数介绍

pkey_mprotect 是 mprotect 系统调用的扩展,用于将一个内存区域与一个特定的内存保护键(Protection Key, pkey)相关联。

回忆一下 pkey_alloc/free:它们用于获取和释放 pkey 编号。pkey_mprotect 则是将这个编号应用到具体的内存区域上。

一旦内存区域通过 pkey_mprotect 与一个 pkey 关联,对该区域的访问权限就不仅受传统的 PROT_READ/PROT_WRITE/PROT_EXEC 控制,还受该 pkey 在 CPU 的 PKRU(Protection Key Rights User)寄存器中设置的权限控制。

2. 函数原型

1
2
3
4
5
#define _GNU_SOURCE
#include <sys/mman.h> // 包含 MPK 相关常量

int pkey_mprotect(void *addr, size_t len, int prot, int pkey);

3. 功能

  • 修改从地址 addr 开始、长度为 len 字节的内存区域的访问权限。

  • 将该内存区域与保护键 pkey(由 pkey_alloc 获得)进行关联。

  • 设置该区域的基本权限为 prot(PROT_READ, PROT_WRITE, PROT_EXEC 的组合)。

4. 参数

  • void *addr: 要修改的内存区域的起始地址。必须是页对齐的。

  • size_t len: 内存区域的长度(以字节为单位)。会向上舍入到最近的页边界。

  • int prot: 新的内存保护标志。可以是 PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC 及其按位或组合。

  • int pkey: 通过 pkey_alloc 获得的保护键编号(0-15)。

5. 返回值

  • 成功时: 返回 0。

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

示例代码

示例 1:preadv2 和 pwritev2 的基本使用

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

#define FILENAME "test_piov2.txt"

int main() {
int fd;
char buf1&#91;20], buf2&#91;30], buf3&#91;50];
struct iovec iov_w&#91;2], iov_r&#91;3];
ssize_t bytes_written, bytes_read;

// 1. 创建并写入测试文件 (使用传统 write)
fd = open(FILENAME, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open for write");
exit(EXIT_FAILURE);
}

const char *data1 = "Part One: Hello, ";
const char *data2 = "preadv2 and pwritev2 World!\n";
iov_w&#91;0].iov_base = (void*)data1;
iov_w&#91;0].iov_len = strlen(data1);
iov_w&#91;1].iov_base = (void*)data2;
iov_w&#91;1].iov_len = strlen(data2);

bytes_written = writev(fd, iov_w, 2);
if (bytes_written == -1) {
perror("writev");
close(fd);
exit(EXIT_FAILURE);
}
printf("Written %zd bytes using writev.\n", bytes_written);
close(fd);

// 2. 使用 preadv2 读取
fd = open(FILENAME, O_RDONLY);
if (fd == -1) {
perror("open for read");
exit(EXIT_FAILURE);
}

// 初始化读取缓冲区
memset(buf1, '.', sizeof(buf1) - 1); buf1&#91;sizeof(buf1)-1] = '\0';
memset(buf2, '.', sizeof(buf2) - 1); buf2&#91;sizeof(buf2)-1] = '\0';
memset(buf3, '.', sizeof(buf3) - 1); buf3&#91;sizeof(buf3)-1] = '\0';

iov_r&#91;0].iov_base = buf1;
iov_r&#91;0].iov_len = sizeof(buf1) - 1;
iov_r&#91;1].iov_base = buf2;
iov_r&#91;1].iov_len = sizeof(buf2) - 1;
iov_r&#91;2].iov_base = buf3;
iov_r&#91;2].iov_len = sizeof(buf3) - 1;

// 从偏移量 0 开始读取,使用默认标志
bytes_read = preadv2(fd, iov_r, 3, 0, 0);
if (bytes_read == -1) {
perror("preadv2");
close(fd);
exit(EXIT_FAILURE);
}
printf("\nRead %zd bytes using preadv2 from offset 0:\n", bytes_read);
printf("Buffer 1: '%s'\n", buf1);
printf("Buffer 2: '%s'\n", buf2);
printf("Buffer 3: '%s'\n", buf3);

close(fd);

// 3. 使用 pwritev2 追加写入
fd = open(FILENAME, O_WRONLY); // 不用 O_APPEND
if (fd == -1) {
perror("open for write (again)");
exit(EXIT_FAILURE);
}

const char *append1 = "Appended via ";
const char *append2 = "pwritev2 with RWF_APPEND flag.\n";
struct iovec iov_a&#91;2];
iov_a&#91;0].iov_base = (void*)append1;
iov_a&#91;0].iov_len = strlen(append1);
iov_a&#91;1].iov_base = (void*)append2;
iov_a&#91;1].iov_len = strlen(append2);

// 使用 RWF_APPEND 标志强制追加,忽略 offset
bytes_written = pwritev2(fd, iov_a, 2, 0, RWF_APPEND);
if (bytes_written == -1) {
perror("pwritev2 with RWF_APPEND");
close(fd);
exit(EXIT_FAILURE);
}
printf("\nAppended %zd bytes using pwritev2 with RWF_APPEND.\n", bytes_written);

close(fd);

// 4. 验证文件内容
printf("\n--- Final file content ---\n");
fd = open(FILENAME, O_RDONLY);
if (fd != -1) {
char final_buf&#91;200];
ssize_t n = read(fd, final_buf, sizeof(final_buf) - 1);
if (n > 0) {
final_buf&#91;n] = '\0';
printf("%s", final_buf);
}
close(fd);
}

// unlink(FILENAME); // 可选:清理文件
return 0;
}

代码解释:

创建一个测试文件,并使用 writev 写入一些初始内容。

重新打开文件进行读取。

使用 preadv2(fd, iov_r, 3, 0, 0) 从文件偏移量 0 开始,将数据分散读入三个缓冲区。flags 为 0,表示默认行为。

打开文件进行写入(非 O_APPEND 模式)。

使用 pwritev2(fd, iov_a, 2, 0, RWF_APPEND) 将数据写入文件。尽管 offset 是 0,但由于使用了 RWF_APPEND 标志,数据被追加到了文件末尾。

重新读取并打印文件内容以验证操作结果。

示例 2:pkey_mprotect 结合 pkey_alloc/free 使用

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
// pkey_mprotect_example.c
#define _GNU_SOURCE
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>

static jmp_buf jmp_env;
static volatile sig_atomic_t sigsegv_caught = 0;

void sigsegv_handler(int sig) {
sigsegv_caught = 1;
longjmp(jmp_env, 1);
}

// Conceptual PKRU manipulation (requires inline assembly in real code)
// For demonstration, we'll just print what would happen.
void set_pkey_access(int pkey, int disable_access) {
printf(" &#91;Concept] Modifying PKRU for pkey %d: %s\n",
pkey, disable_access ? "DISABLE access" : "ENABLE access");
// Real code would involve inline assembly to write to PKRU register
}

int main() {
// Check for MPK support conceptually
if (sysconf(_SC_MPKEY) <= 0) {
fprintf(stderr, "MPK not supported by sysconf.\n");
exit(EXIT_FAILURE);
}

struct sigaction sa;
sa.sa_handler = sigsegv_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}

size_t page_size = getpagesize();
size_t len = page_size;
void *addr;

// 1. Allocate memory
addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
printf("Allocated %zu bytes at %p\n", len, addr);

// 2. Write some data
strcpy((char*)addr, "This memory is protected by a pkey.");
printf("Written data: %s\n", (char*)addr);

// 3. Allocate a protection key
int pkey = pkey_alloc(0, 0);
if (pkey == -1) {
if (errno == EOPNOTSUPP) {
printf("MPK not supported on this hardware/kernel.\n");
munmap(addr, len);
exit(EXIT_FAILURE);
} else {
perror("pkey_alloc");
munmap(addr, len);
exit(EXIT_FAILURE);
}
}
printf("Allocated pkey: %d\n", pkey);

// 4. Associate memory with the pkey using pkey_mprotect
printf("\n--- Associating memory with pkey %d ---\n", pkey);
if (pkey_mprotect(addr, len, PROT_READ | PROT_WRITE, pkey) == -1) {
perror("pkey_mprotect");
pkey_free(pkey);
munmap(addr, len);
exit(EXIT_FAILURE);
}
printf("Memory successfully associated with pkey %d.\n", pkey);

// 5. Disable access via PKRU (conceptual)
printf("\n--- Disabling access to pkey %d via PKRU ---\n", pkey);
set_pkey_access(pkey, 1); // Conceptual call

// 6. Try to access protected memory (should trigger SIGSEGV)
printf("\n--- Attempting to READ from protected memory ---\n");
sigsegv_caught = 0;

if (setjmp(jmp_env) == 0) {
printf(" Trying to read from %p...\n", addr);
volatile char first_char = *((char*)addr);
printf(" ERROR: Read succeeded (first char: %c). This should not happen!\n", first_char);
} else {
if (sigsegv_caught) {
printf(" SUCCESS: SIGSEGV caught. Access correctly denied by pkey.\n");
} else {
printf(" Unexpected longjmp.\n");
}
}

// 7. Re-enable access
printf("\n--- Re-enabling access to pkey %d via PKRU ---\n", pkey);
set_pkey_access(pkey, 0); // Conceptual call

// 8. Try to access memory again (should succeed)
printf("\n--- Attempting to access memory again (should succeed now) ---\n");
printf(" Reading from %p: %.50s\n", addr, (char*)addr);

// 9. Cleanup
if (pkey_free(pkey) == -1) {
perror("pkey_free");
}
if (munmap(addr, len) == -1) {
perror("munmap");
}

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

**代码解释 **(概念性):

设置信号处理和 setjmp/longjmp 用于捕获 SIGSEGV。

使用 mmap 分配一页内存。

写入一些测试数据。

调用 pkey_alloc(0, 0) 获取一个 pkey。

关键步骤: 调用 pkey_mprotect(addr, len, PROT_READ | PROT_WRITE, pkey) 将分配的内存区域与获取的 pkey 关联起来。

概念性操作: 模拟通过修改 PKRU 寄存器来禁用对这个 pkey 的访问。

尝试读取受保护的内存,预期会触发 SIGSEGV。

概念性操作: 模拟重新启用对这个 pkey 的访问。

再次尝试读取,这次应该成功。

清理资源(释放 pkey 和内存)。

重要提示与注意事项:

内核版本:

  • preadv2/pwritev2: Linux 内核 4.6+。

  • pkey_mprotect/pkey_alloc/pkey_free: Linux 内核 4.9+ (MPK)。

glibc 版本: 需要 glibc 2.27+ 才能直接使用这些函数。

硬件支持: pkey_* 函数需要 CPU 支持(如 Intel x86_64 Skylake 及更新架构)。

_GNU_SOURCE: 必须定义此宏才能使用这些扩展函数。

flags 参数: preadv2/pwritev2 的 flags 提供了强大的 I/O 控制能力,特别是 RWF_NOWAIT(非阻塞)和 RWF_APPEND。

pkey_mprotect 是核心: 它是将 pkey 机制应用到实际内存区域的关键步骤。仅仅 pkey_alloc 是不够的。

PKRU 操作: 真正控制 pkey 权限需要直接操作 CPU 的 PKRU 寄存器,这通常需要内联汇编,比较复杂。

错误处理: 始终检查返回值,特别是 pkey_* 函数可能返回 EOPNOTSUPP。

总结:

preadv2 和 pwritev2 是对现有 I/O 系统调用的有力增强,通过引入 flags 参数,提供了更细粒度的控制,如非阻塞 I/O 和强制追加写入。

pkey_mprotect 是内存保护键(MPK)技术的核心 API 之一,它允许将特定的内存区域与一个 pkey 绑定,从而实现比传统 mprotect 更快速、更灵活的内存访问控制。结合 pkey_alloc/free 和对 PKRU 寄存器的操作,可以构建出高性能的内存安全机制。

这三个函数都代表了 Linux 系统编程向更高性能、更细粒度控制发展的趋势。

preadv2 函数

1. 函数介绍

preadv2 是 preadv 的增强版本,支持额外的标志参数,提供更多的控制选项。它是Linux 4.6引入的新特性。

2. 函数原型

1
2
3
4
#define _GNU_SOURCE
#include <sys/uio.h>
ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

与 preadv 类似,但从指定位置读取数据到多个缓冲区,并支持额外的控制标志。

4. 参数

  • int fd: 文件描述符

  • *const struct iovec iov: iovec结构体数组

  • int iovcnt: iov数组元素个数

  • off_t offset: 文件偏移量

  • int flags: 控制标志(如RWF_HIPRI, RWF_DSYNC等)

5. 返回值

  • 成功: 返回实际读取的总字节数

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

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

  • preadv: 基本版本

  • pwritev2: 对应的写入函数

  • read: 基本读取函数

7. 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#define _GNU_SOURCE
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
* 演示preadv2的基本使用
* 注意:需要Linux 4.6+内核支持
*/
int demo_preadv2_basic() {
int fd;
struct iovec iov&#91;2];
char buf1&#91;30], buf2&#91;20];
ssize_t bytes_read;

printf("=== preadv2 基本使用示例 ===\n");

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

const char *test_data = "This is test data for preadv2 function demonstration.";
write(fd, test_data, strlen(test_data));
close(fd);

// 打开文件进行读取
fd = open("test_preadv2.txt", O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
return -1;
}

// 设置iovec数组
iov&#91;0].iov_base = buf1;
iov&#91;0].iov_len = sizeof(buf1) - 1;
iov&#91;1].iov_base = buf2;
iov&#91;1].iov_len = sizeof(buf2) - 1;

// 使用preadv2读取数据(flags设为0表示默认行为)
bytes_read = preadv2(fd, iov, 2, 0, 0);
if (bytes_read == -1) {
if (errno == ENOSYS) {
printf("系统不支持 preadv2 函数\n");
close(fd);
unlink("test_preadv2.txt");
return 0;
}
perror("preadv2 失败");
close(fd);
unlink("test_preadv2.txt");
return -1;
}

printf("preadv2 成功读取 %zd 字节\n", bytes_read);

// 添加字符串结束符并显示结果
buf1&#91;iov&#91;0].iov_len] = '\0';
buf2&#91;iov&#91;1].iov_len] = '\0';

printf("缓冲区1: %s\n", buf1);
printf("缓冲区2: %s\n", buf2);

close(fd);
unlink("test_preadv2.txt");
return 0;
}

/**
* 演示preadv2的高级特性(如果系统支持)
*/
int demo_preadv2_advanced() {
int fd;
struct iovec iov&#91;1];
char buffer&#91;100];
ssize_t bytes_read;

printf("\n=== preadv2 高级特性示例 ===\n");
printf("preadv2 支持的标志包括:\n");
printf(" RWF_HIPRI: 高优先级I/O\n");
printf(" RWF_DSYNC: 数据同步写入\n");
printf(" RWF_SYNC: 同步写入\n");
printf(" RWF_NOWAIT: 非阻塞操作\n");
printf(" RWF_APPEND: 追加模式写入\n");

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

const char *test_data = "Advanced preadv2 test data for feature demonstration.";
write(fd, test_data, strlen(test_data));
close(fd);

fd = open("advanced_test.txt", O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
return -1;
}

// 设置iovec
iov&#91;0].iov_base = buffer;
iov&#91;0].iov_len = sizeof(buffer) - 1;

// 尝试使用RWF_NOWAIT标志(非阻塞读取)
bytes_read = preadv2(fd, iov, 1, 0, RWF_NOWAIT);
if (bytes_read == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("非阻塞操作:数据暂时不可用\n");
} else if (errno == ENOSYS) {
printf("系统不支持 RWF_NOWAIT 标志\n");
} else {
printf("preadv2 with RWF_NOWAIT 失败: %s\n", strerror(errno));
}
} else {
buffer&#91;bytes_read] = '\0';
printf("非阻塞读取成功: %s\n", buffer);
}

close(fd);
unlink("advanced_test.txt");
return 0;
}

int main() {
printf("preadv2 需要 Linux 4.6+ 内核支持\n");

if (demo_preadv2_basic() == 0) {
demo_preadv2_advanced();
printf("\n=== preadv2 使用总结 ===\n");
printf("优点:支持额外控制标志,更灵活的I/O控制\n");
printf("注意:需要较新内核版本支持\n");
}
return 0;
}

preadv2系统调用及示例-CSDN博客

https://www.calcguide.tech/2025/09/06/preadv2系统调用及示例-2/

pselect6系统调用及示例

pselect6系统调用及示例

相关文章:pselect6系统调用及示例-CSDN博客 pselect系统调用及示例 select系统调用及示例 Linux I/O 多路复用机制对比分析poll/ppoll/epoll/select

1. 函数介绍

pselect6 是 Linux 系统中的内部系统调用,是 pselect 的底层实现。对于应用程序开发来说,通常不需要直接使用 pselect6,而是使用标准库提供的 pselect 函数。

2. 函数原型

1
2
3
// 注意: 这是内部系统调用,应用程序不应直接使用
// 应用程序应使用标准的 pselect 函数

3. 功能

pselect6 是 pselect 系统调用的内核实现,提供与 pselect 相同的功能,但在某些架构上可能有不同的参数传递方式。

4. 参数

与 pselect 相同,但由于是系统调用层面,参数传递方式可能不同。

5. 返回值

与 pselect 相同。

6. 相似函数或关联函数

  • pselect: 应用程序应使用的标准函数

  • select: 传统的文件描述符监视函数

  • poll: 更现代的文件描述符监视函数

7. 示例代码

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

// 演示标准 pselect 的使用(推荐方式)
int main_pselect_demo() {
fd_set read_fds;
struct timespec timeout;
sigset_t sigmask;
int ready;

printf("=== 标准 pselect 使用演示 ===\n\n");

// 初始化文件描述符集合
FD_ZERO(&read_fds);
FD_SET(STDIN_FILENO, &read_fds);

// 设置超时时间
timeout.tv_sec = 5;
timeout.tv_nsec = 500000000; // 500毫秒

// 设置信号屏蔽集
sigemptyset(&sigmask);

printf("使用标准 pselect 函数:\n");
printf(" 监视文件描述符: %d (标准输入)\n", STDIN_FILENO);
printf(" 超时时间: %ld.5 秒\n", (long)timeout.tv_sec);
printf(" 请输入一些文本或等待超时...\n\n");

// 使用标准 pselect
ready = pselect(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout, &sigmask);

if (ready == -1) {
perror("pselect 失败");
} else if (ready == 0) {
printf("✓ 超时: 没有文件描述符准备就绪\n");
} else {
printf("✓ 准备就绪的文件描述符数量: %d\n", ready);

if (FD_ISSET(STDIN_FILENO, &read_fds)) {
char buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("✓ 读取到: %s", buffer);
}
}
}

printf("\n=== 重要说明 ===\n");
printf("1. 应用程序应使用标准的 pselect 函数\n");
printf("2. pselect6 是内核内部系统调用\n");
printf("3. 直接使用系统调用会降低可移植性\n");
printf("4. 标准库函数提供更好的错误处理\n");
printf("5. 使用标准函数更符合 POSIX 标准\n");

return 0;
}

// 比较 pselect 和 select
void compare_pselect_select() {
fd_set read_fds1, read_fds2;
struct timeval timeout_tv;
struct timespec timeout_ts;
int result1, result2;

printf("\n=== pselect vs select 对比 ===\n");

// 初始化
FD_ZERO(&read_fds1);
FD_SET(STDIN_FILENO, &read_fds1);
read_fds2 = read_fds1;

// 设置相同的超时时间
timeout_tv.tv_sec = 2;
timeout_tv.tv_usec = 0;

timeout_ts.tv_sec = 2;
timeout_ts.tv_nsec = 0;

printf("功能对比:\n");
printf("1. select:\n");
printf(" - 使用 timeval 结构体 (微秒精度)\n");
printf(" - 不支持原子的信号屏蔽控制\n");
printf(" - 文件描述符数量受 FD_SETSIZE 限制\n");
printf(" - 跨平台性好\n");
printf("\n");

printf("2. pselect:\n");
printf(" - 使用 timespec 结构体 (纳秒精度)\n");
printf(" - 支持原子的信号屏蔽控制\n");
printf(" - 文件描述符数量受同样限制\n");
printf(" - 提供更好的信号安全性\n");
printf("\n");

printf("使用建议:\n");
printf("1. 简单应用: 使用 select\n");
printf("2. 需要信号安全: 使用 pselect\n");
printf("3. 高性能需求: 考虑 poll 或 epoll\n");
printf("4. 跨平台应用: 使用 select\n");
}

int main() {
printf("=== pselect/pselect6 系统调用详解 ===\n\n");

// 演示标准 pselect 使用
main_pselect_demo();

// 对比分析
compare_pselect_select();

printf("\n=== 实际应用场景 ===\n");
printf("pselect 适用场景:\n");
printf("1. 网络服务器: 监视多个客户端连接\n");
printf("2. 实时应用: 精确的超时控制\n");
printf("3. 系统工具: 同时监视多个输入源\n");
printf("4. 交互式程序: 响应用户输入和文件事件\n");
printf("5. 需要信号安全的程序: 避免竞态条件\n");
printf("\n");
printf("注意事项:\n");
printf("1. 文件描述符数量受 FD_SETSIZE 限制 (通常 1024)\n");
printf("2. 每次调用都会修改文件描述符集合\n");
printf("3. 需要正确处理 EINTR 错误\n");
printf("4. 超时时间会被内核修改为剩余时间\n");
printf("5. 信号屏蔽只在等待期间有效\n");

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
# 编译示例程序
gcc -D_POSIX_C_SOURCE=200112L -o pselect_example1 example1.c
gcc -D_POSIX_C_SOURCE=200112L -o pselect_example2 example2.c
gcc -D_POSIX_C_SOURCE=200112L -o pselect_example3 example3.c
gcc -D_POSIX_C_SOURCE=200112L -o pselect_demo demo.c

# 运行示例
./pselect_example1
./pselect_example2
./pselect_example3 --help
./pselect_example3 -c 5 -t 10 -v
./pselect_demo

系统要求检查

1
2
3
4
5
6
7
8
9
10
11
12
# 检查 POSIX 支持
getconf _POSIX_C_SOURCE

# 检查 pselect 支持
grep -w pselect /usr/include/sys/select.h

# 检查系统调用
grep -w pselect /usr/include/asm/unistd_64.h

# 查看系统信息
uname -a

重要注意事项

POSIX 标准: 需要定义 _POSIX_C_SOURCE 宏

文件描述符限制: 受 FD_SETSIZE 限制(通常 1024)

原子操作: 等待和信号屏蔽是原子的

错误处理: 始终检查返回值和 errno

超时处理: 超时时间会被内核修改

信号安全: 避免信号处理中的竞态条件

实际应用场景

网络服务器: 监视多个客户端连接

实时应用: 精确的超时控制

系统工具: 同时监视多个输入源

交互式程序: 响应用户输入和文件事件

需要信号安全的程序: 避免竞态条件

最佳实践

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
// 安全的 pselect 封装函数
int safe_pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask) {
// 验证参数
if (nfds < 0 || nfds > FD_SETSIZE) {
errno = EINVAL;
return -1;
}

if (!readfds && !writefds && !exceptfds) {
errno = EINVAL;
return -1;
}

int result;
do {
result = pselect(nfds, readfds, writefds, exceptfds, timeout, sigmask);
} while (result == -1 && errno == EINTR);

return result;
}

// 事件处理循环模板
int event_loop_template() {
fd_set read_fds, master_fds;
struct timespec timeout;
sigset_t sigmask;
int max_fd = STDIN_FILENO;

// 初始化
FD_ZERO(&master_fds);
FD_SET(STDIN_FILENO, &master_fds);

timeout.tv_sec = 5;
timeout.tv_nsec = 0;

sigemptyset(&sigmask);

while (1) {
// 复制文件描述符集合
read_fds = master_fds;

// 等待事件
int ready = safe_pselect(max_fd + 1, &read_fds, NULL, NULL, &timeout, &sigmask);

if (ready == -1) {
perror("pselect 失败");
return -1;
} else if (ready == 0) {
printf("超时\n");
continue;
}

// 处理就绪的文件描述符
for (int fd = 0; fd <= max_fd; fd++) {
if (FD_ISSET(fd, &read_fds)) {
// 处理事件
handle_fd_event(fd);
}
}
}

return 0;
}

这些示例展示了 pselect 和 pselect6 函数的各种使用方法,从基础的文件描述符监视到完整的事件驱动服务器模拟,帮助你全面掌握 Linux 系统中的高级 I/O 多路复用机制。

ptrace系统调用及示例

ptrace系统调用及示例

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

1. 函数介绍

在 Linux 系统中,进程通常是独立运行的,它们有自己的内存空间和执行状态。但是,有时候我们需要一个进程能够观察甚至控制另一个进程的运行。这在很多场景下都非常有用:

  • 调试器 (Debugger):像 gdb 这样的调试器,可以让你暂停一个正在运行的程序(被调试者),查看它的内存、寄存器状态,单步执行代码,设置断点等。gdb 就是通过 ptrace 来实现这些强大功能的。

  • 系统调用跟踪 (Strace):strace 命令可以显示一个程序执行了哪些系统调用,传入了什么参数,返回了什么结果。它也是利用 ptrace 来实现的。

  • 进程监控和分析:安全软件或系统管理员工具可能需要监控某个进程的行为。

  • 沙箱 (Sandboxing):某些安全机制会使用 ptrace 来限制或监视程序可以执行的操作。

ptrace (Process Trace) 系统调用就是实现这些功能的核心工具。它允许一个进程(我们称它为跟踪者 Tracer,通常是 gdb 或 strace)对另一个进程(我们称它为被跟踪者 Tracee,是你想调试或监控的程序)进行各种操作。

简单来说,ptrace 就像是一个功能强大的“钩子”或“后门”,允许一个进程(跟踪者)介入另一个进程(被跟踪者)的执行过程,查看它的状态,甚至暂停、修改它的执行。

2. 函数原型

1
2
3
4
#include <sys/ptrace.h> // 包含 ptrace 函数声明和相关常量

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

3. 功能

根据 request 参数指定的操作类型,对进程 ID 为 pid 的目标进程执行相应的跟踪操作。

4. 参数详解

request:

  • enum __ptrace_request 类型。

这是最重要的参数,它指定了你想要执行的具体操作。常见的操作有:

  • PTRACE_TRACEME: 由被跟踪者调用。意思是“请允许我的父进程跟踪我”。这是子进程请求被其父进程跟踪的标准方式。

  • PTRACE_ATTACH: 由跟踪者调用。意思是“我要开始跟踪进程 pid”。跟踪者可以是任何有权限的进程,不一定是父进程。

  • PTRACE_DETACH: 由跟踪者调用。意思是“我要停止跟踪进程 pid”,并让它继续独立运行。

  • PTRACE_SYSCALL (PTRACE_SYSEMU): 由跟踪者调用。让被跟踪者继续运行,但在它即将进入或离开一个系统调用时暂停。

  • PTRACE_SINGLESTEP: 由跟踪者调用。让被跟踪者执行一条机器指令,然后暂停。这是实现“单步执行”的基础。

  • PTRACE_CONT: 由跟踪者调用。让被跟踪者从当前暂停状态继续运行。

  • PTRACE_PEEKDATA, PTRACE_PEEKTEXT: 由跟踪者调用。读取被跟踪者内存中的数据或代码。

  • PTRACE_POKEDATA, PTRACE_POKETEXT: 由跟踪者调用。修改被跟踪者内存中的数据或代码。

  • PTRACE_GETREGS, PTRACE_SETREGS: 由跟踪者调用。获取或设置被跟踪者的 CPU 寄存器值。

  • PTRACE_GETSIGINFO, PTRACE_SETSIGINFO: 获取或设置导致进程停止的信号信息。

  • PTRACE_SETOPTIONS: 设置跟踪选项,例如是否在系统调用入口/出口时暂停 (PTRACE_O_TRACESYSGOOD),是否自动跟踪子进程 (PTRACE_O_TRACECLONE 等)。

  • … 还有很多其他选项。

pid:

  • pid_t 类型。

  • 指定要操作的被跟踪者进程的进程 ID (PID)。

  • 对于 PTRACE_TRACEME,这个参数被忽略。

addr:

  • void * 类型。

一个内存地址。其具体含义取决于 request 的类型。

  • 对于 PTRACE_PEEK*/PTRACE_POKE*,它指定要读取/修改的被跟踪者内存地址。

  • 对于其他操作,通常被忽略或有特殊含义。

data:

  • void * 类型。

一个指向数据的指针。其具体含义也取决于 request 的类型。

  • 对于 PTRACE_POKEDATA/PTRACE_POKETEXT,它指向要写入被跟踪者内存的数据。

  • 对于 PTRACE_SET* 操作,它指向包含新值的结构体。

  • 对于 PTRACE_PEEK* 操作,结果通常通过 ptrace 的返回值给出,而不是通过 data 参数。

  • 对于其他操作,通常被忽略或有特殊含义。

5. 返回值

成功:

  • 对于大多数 PTRACE_PEEK* 操作,返回值是从被跟踪者内存中读取的数据。

  • 对于其他操作,通常返回 0。

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

6. 错误码 (errno)

ptrace 可能返回多种错误码,常见的有:

EPERM: 权限不足。例如:

  • 尝试跟踪一个你不拥有的进程。

  • 目标进程已经在被其他进程跟踪。

  • 目标进程是 init 进程 (PID 1)。

  • 受到 Yama 安全模块 (ptrace_scope) 的限制。

ESRCH: 找不到 pid 指定的进程。

EINVAL: request 参数无效,或者在当前状态下不允许该操作。

EIO: I/O 错误,或在某些非法状态下尝试操作(例如,对一个正在运行的进程执行 PTRACE_PEEKDATA)。

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

7. 被跟踪者状态的变化

当一个被跟踪的进程即将收到一个信号或即将执行系统调用/从系统调用返回时,内核会暂停该进程的执行,并发送一个 SIGCHLD 信号给它的跟踪者。此时,跟踪者可以调用 waitpid() 或 wait() 来等待并获取被跟踪者暂停的通知。

跟踪者在检查被跟踪者的状态(读取寄存器、内存等)并决定如何处理后,可以调用 ptrace(PTRACE_CONT, …) 或 ptrace(PTRACE_SYSCALL, …) 等操作让被跟踪者继续运行。

8. 示例代码

下面是一个简单的示例,演示了如何使用 ptrace 来跟踪一个子进程的系统调用(类似 strace 的简化版)。

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h> // 包含 struct user_regs_struct (不同架构可能不同)
#include <sys/syscall.h> // 包含系统调用号常量
#include <errno.h>
#include <string.h>

// 一个简单的子进程函数,用于被跟踪
void traced_process() {
// 1. 请求被父进程跟踪
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
perror("ptrace TRACEME");
exit(EXIT_FAILURE);
}

// 2. 停止自己,让父进程有机会在我们真正开始执行前进行设置
// 这会向父进程发送 SIGSTOP 信号
kill(getpid(), SIGSTOP);

// 3. 执行一些操作
printf("Child: Hello from traced process!\n");
int fd = open("/dev/null", O_WRONLY);
if (fd != -1) {
write(fd, "Test data", 9);
close(fd);
}
printf("Child: Goodbye from traced process!\n");
// 子进程结束
}

// 将系统调用号转换为名称的简单函数 (只列举几个)
const char* get_syscall_name(long syscall_num) {
switch(syscall_num) {
case SYS_write: return "write";
case SYS_open: return "open";
case SYS_close: return "close";
case SYS_read: return "read";
case SYS_mmap: return "mmap";
case SYS_mprotect: return "mprotect";
default: {
static char buf&#91;32];
snprintf(buf, sizeof(buf), "syscall_%ld", syscall_num);
return buf;
}
}
}

int main() {
pid_t child_pid;
int status;
struct user_regs_struct regs; // 用于存储寄存器值

printf("--- Demonstrating ptrace (syscall tracing) ---\n");

// 1. Fork 创建子进程
child_pid = fork();
if (child_pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}

if (child_pid == 0) {
// --- 子进程 ---
traced_process();
} else {
// --- 父进程 (跟踪者) ---
printf("Parent: Started tracing child (PID: %d)\n", child_pid);

// 2. 等待子进程因 SIGSTOP 而暂停
// 当子进程调用 kill(getpid(), SIGSTOP) 时,它会暂停并通知父进程
if (waitpid(child_pid, &status, 0) == -1) {
perror("waitpid (initial stop)");
// 杀死子进程
kill(child_pid, SIGKILL);
exit(EXIT_FAILURE);
}

if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) {
printf("Parent: Child has stopped itself with SIGSTOP. Ready to trace.\n");
} else {
fprintf(stderr, "Parent: Child did not stop with SIGSTOP as expected.\n");
kill(child_pid, SIGKILL);
exit(EXIT_FAILURE);
}

// 3. 让子进程继续运行,但在每次系统调用时暂停
// PTRACE_SYSCALL 会让子进程在进入和退出系统调用时都暂停
if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1) {
perror("ptrace SYSCALL (first)");
kill(child_pid, SIGKILL);
exit(EXIT_FAILURE);
}

// 4. 主循环:等待子进程暂停,打印系统调用信息,然后让它继续
int entering_syscall = 1; // 标志位:1表示即将进入,0表示即将退出
while (1) {
// 等待子进程暂停
if (waitpid(child_pid, &status, 0) == -1) {
if (errno == ECHILD) {
// 子进程已退出
printf("Parent: Child process has exited.\n");
break;
} else {
perror("waitpid");
kill(child_pid, SIGKILL);
break;
}
}

// 检查子进程暂停的原因
if (WIFSTOPPED(status)) {
int sig = WSTOPSIG(status);
if (sig == (SIGTRAP | 0x80)) { // 这是 PTRACE_O_TRACESYSGOOD 的效果
sig = SIGTRAP;
}

if (sig == SIGTRAP) {
// 由于 PTRACE_SYSCALL 而暂停,表示系统调用事件
// 获取寄存器值
if (ptrace(PTRACE_GETREGS, child_pid, NULL, &regs) == -1) {
perror("ptrace GETREGS");
break;
}

// 在 x86_64 上,系统调用号在 orig_rax 寄存器中
long syscall_num = regs.orig_rax;

if (entering_syscall) {
printf("Parent: &#91;Entering] Syscall: %s (%ld)\n", get_syscall_name(syscall_num), syscall_num);
// 可以在这里打印参数 regs.rdi, regs.rsi, regs.rdx 等
entering_syscall = 0;
} else {
// 在 x86_64 上,系统调用返回值在 rax 寄存器中
long retval = regs.rax;
printf("Parent: &#91;Exiting] Syscall: %s (%ld), Return: %ld\n", get_syscall_name(syscall_num), syscall_num, retval);
entering_syscall = 1;
}

// 让子进程继续,直到下一个系统调用事件
if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1) {
perror("ptrace SYSCALL (loop)");
break;
}

} else if (sig == SIGSTOP) {
// 可能是初始的 SIGSTOP,或者由其他地方发出的 SIGSTOP
printf("Parent: Child received SIGSTOP.\n");
if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1) {
perror("ptrace SYSCALL after SIGSTOP");
break;
}
} else {
// 子进程因其他信号暂停
printf("Parent: Child stopped by signal %d. Forwarding signal.\n", sig);
// 将信号传递给子进程
if (ptrace(PTRACE_SYSCALL, child_pid, NULL, (void*)(unsigned long)sig) == -1) {
perror("ptrace SYSCALL (forward signal)");
break;
}
}

} else if (WIFEXITED(status)) {
// 子进程正常退出
printf("Parent: Child exited normally with status %d.\n", WEXITSTATUS(status));
break;
} else if (WIFSIGNALED(status)) {
// 子进程被信号杀死
printf("Parent: Child was killed by signal %d.\n", WTERMSIG(status));
break;
} else {
printf("Parent: Child stopped with unexpected status: %d\n", status);
if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1) {
perror("ptrace SYSCALL (unexpected)");
break;
}
}
}

printf("Parent: Tracing finished.\n");
}

return 0;
}

9. 编译和运行

1
2
3
4
5
6
7
8
# 假设代码保存在 ptrace_example.c 中
# 注意:此代码是 x86_64 架构特定的 (因为使用了 regs.orig_rax, regs.rax 等)
# 在其他架构上需要修改寄存器名称
gcc -o ptrace_example ptrace_example.c

# 运行程序
./ptrace_example

10. 预期输出 (x86_64)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--- Demonstrating ptrace (syscall tracing) ---
Parent: Started tracing child (PID: 12345)
Parent: Child has stopped itself with SIGSTOP. Ready to trace.
Child: Hello from traced process!
Parent: &#91;Entering] Syscall: write (1)
Parent: &#91;Exiting] Syscall: write (1), Return: 35
Parent: &#91;Entering] Syscall: open (2)
Parent: &#91;Exiting] Syscall: open (2), Return: 3
Parent: &#91;Entering] Syscall: write (1)
Parent: &#91;Exiting] Syscall: write (1), Return: 9
Parent: &#91;Entering] Syscall: close (3)
Parent: &#91;Exiting] Syscall: close (3), Return: 0
Child: Goodbye from traced process!
Parent: &#91;Entering] Syscall: write (1)
Parent: &#91;Exiting] Syscall: write (1), Return: 37
Parent: Child process has exited.
Parent: Tracing finished.

11. 总结

ptrace 是一个功能极其强大但也相当复杂的系统调用,是 Linux 系统调试和监控能力的基石。

  • 核心作用:允许一个进程(跟踪者)观察和控制另一个进程(被跟踪者)的执行。

主要操作 (request):

  • PTRACE_TRACEME: 子进程请求被父进程跟踪。

  • PTRACE_ATTACH/PTRACE_DETACH: 开始/停止跟踪一个任意进程。

  • PTRACE_SYSCALL/PTRACE_SINGLESTEP: 控制被跟踪者的执行(系统调用步进/单步执行)。

  • PTRACE_CONT: 让被跟踪者继续运行。

  • PTRACE_PEEK*/PTRACE_POKE*: 读写被跟踪者的内存。

  • PTRACE_GETREGS/PTRACE_SETREGS: 读写被跟踪者的寄存器。

工作机制:被跟踪者在特定事件(如信号、系统调用)发生时暂停,内核通知跟踪者。跟踪者通过 wait 获取通知,进行检查/修改,然后通过 ptrace 命令让其继续。

典型应用:

  • 调试器 (gdb): 设置断点、单步执行、查看变量。

  • 系统调用跟踪器 (strace): 记录程序执行的所有系统调用。

  • 沙箱/安全监控: 限制或记录程序的行为。

重要限制:

  • 权限:通常需要是被跟踪者的父进程,或者具有 CAP_SYS_PTRACE 能力。

  • 安全:受 Yama LSM (ptrace_scope) 限制,防止恶意跟踪。

  • 一对一:一个进程同时只能被一个进程跟踪。

复杂性:直接使用 ptrace 非常复杂,涉及信号处理、寄存器操作、架构相关细节等。实际工具(如 gdb, strace)对其进行了大量封装。

对于 Linux 编程新手来说,理解 ptrace 的基本概念和它在 gdb/strace 等工具中的作用是非常有价值的,它揭示了操作系统底层强大的进程控制能力。

ptrace系统调用及示例-CSDN博客

pselect系统调用及示例

Linux 高级 I/O 多路复用系统调用详解

相关文章:select系统调用及示例 Linux I/O 多路复用机制对比分析poll/ppoll/epoll/select pselect系统调用及示例

  1. pselect 函数详解

1. 函数介绍

pselect 是 Linux 系统中用于同时监视多个文件描述符的系统调用。可以把 pselect 想象成”智能的交通指挥官”——它能够同时观察多个”道路”(文件描述符),当某条道路有”车辆”(数据)到达时,立即通知你进行处理。

与传统的 select 相比,pselect 提供了更好的信号处理机制,避免了信号处理函数中的竞态条件问题。

2. 函数原型

1
2
3
4
5
6
7
#define _POSIX_C_SOURCE 200112L
#include <sys/select.h>

int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);

3. 功能

pselect 函数用于同时监视多个文件描述符,等待其中任何一个变为就绪状态(可读、可写或有异常)。它能够在等待期间临时设置信号屏蔽集,提供更好的信号处理安全性。

4. 参数

  • nfds: 要监视的最大文件描述符值加1

  • readfds: 指向要监视可读事件的文件描述符集合的指针

  • writefds: 指向要监视可写事件的文件描述符集合的指针

  • exceptfds: 指向要监视异常事件的文件描述符集合的指针

  • timeout: 指向超时时间的指针(NULL 表示无限等待)

  • sigmask: 指向信号屏蔽集的指针(NULL 表示不改变信号屏蔽)

5. timespec 结构体

1
2
3
4
5
struct timespec {
time_t tv_sec; /* 秒数 */
long tv_nsec; /* 纳秒数 */
};

6. 返回值

  • 成功: 返回准备就绪的文件描述符数量(0 表示超时)

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

7. 常见错误码

  • EBADF: 一个或多个文件描述符无效

  • EINTR: 被未屏蔽的信号中断

  • EINVAL: 参数无效

  • ENOMEM: 内存不足

8. 相似函数或关联函数

  • select: 传统的文件描述符监视函数

  • poll: 更现代的文件描述符监视函数

  • ppoll: 带信号屏蔽的 poll

  • epoll_wait: epoll 接口的等待函数

  • read/write: 文件读写操作

  • signal/sigaction: 信号处理函数

9. 示例代码

示例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
#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

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

// 信号处理函数
void signal_handler(int sig) {
signal_received = sig;
printf("\n收到信号 %d\n", sig);
}

int main() {
fd_set read_fds;
struct timespec timeout;
sigset_t sigmask;
int ready;

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

// 设置信号处理
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;

if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}

if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}

// 初始化文件描述符集合
FD_ZERO(&read_fds);
FD_SET(STDIN_FILENO, &read_fds);

// 设置超时时间 (5 秒)
timeout.tv_sec = 5;
timeout.tv_nsec = 0;

// 设置信号屏蔽集 (不屏蔽任何信号)
sigemptyset(&sigmask);

printf("监视设置:\n");
printf(" 监视文件描述符: %d (标准输入)\n", STDIN_FILENO);
printf(" 超时时间: %ld 秒\n", (long)timeout.tv_sec);
printf(" 按 Enter 键或等待超时...\n");
printf(" 按 Ctrl+C 发送信号\n\n");

// 使用 pselect 监视文件描述符
ready = pselect(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
printf("pselect 被信号中断\n");
if (signal_received) {
printf("收到信号: %d\n", signal_received);
}
} else {
perror("pselect 失败");
}
} else if (ready == 0) {
printf("超时: 没有文件描述符准备就绪\n");
} else {
printf("准备就绪的文件描述符数量: %d\n", ready);

if (FD_ISSET(STDIN_FILENO, &read_fds)) {
printf("标准输入有数据可读\n");

// 读取输入
char buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("读取到: %s", buffer);
}
}
}

printf("\n=== pselect 特点 ===\n");
printf("1. 原子操作: 等待和信号屏蔽是原子的\n");
printf("2. 精确超时: 支持纳秒级超时\n");
printf("3. 信号安全: 避免信号处理中的竞态条件\n");
printf("4. 灵活屏蔽: 可以精确控制信号屏蔽\n");
printf("5. 高效监视: 同时监视多个文件描述符\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
#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

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

const char *content = "这是测试文件的内容\n用于演示 pselect 功能\n";
write(fd, content, strlen(content));
lseek(fd, 0, SEEK_SET);

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

// 显示文件描述符集合状态
void show_fd_set_status(fd_set *fds, int max_fd, const char *description) {
printf("%s:\n", description);
for (int i = 0; i <= max_fd; i++) {
if (FD_ISSET(i, fds)) {
printf(" fd %d: 就绪\n", i);
}
}
printf("\n");
}

int main() {
fd_set read_fds, master_fds;
struct timespec timeout;
sigset_t sigmask;
int test_fd1, test_fd2;
int max_fd;
int ready;

printf("=== pselect 多文件描述符监视示例 ===\n\n");

// 创建测试文件
test_fd1 = create_test_file("test1.txt");
test_fd2 = create_test_file("test2.txt");

if (test_fd1 == -1 || test_fd2 == -1) {
if (test_fd1 != -1) close(test_fd1);
if (test_fd2 != -1) close(test_fd2);
unlink("test1.txt");
unlink("test2.txt");
return 1;
}

// 初始化文件描述符集合
FD_ZERO(&master_fds);
FD_SET(STDIN_FILENO, &master_fds);
FD_SET(test_fd1, &master_fds);
FD_SET(test_fd2, &master_fds);

// 找到最大的文件描述符
max_fd = STDIN_FILENO;
if (test_fd1 > max_fd) max_fd = test_fd1;
if (test_fd2 > max_fd) max_fd = test_fd2;

// 设置超时时间 (3 秒)
timeout.tv_sec = 3;
timeout.tv_nsec = 0;

// 设置信号屏蔽集
sigemptyset(&sigmask);

printf("监视设置:\n");
printf(" 最大文件描述符: %d\n", max_fd);
printf(" 监视的文件描述符: %d(标准输入), %d(文件1), %d(文件2)\n",
STDIN_FILENO, test_fd1, test_fd2);
printf(" 超时时间: %ld 秒\n", (long)timeout.tv_sec);
printf("\n");

// 主循环
printf("开始监视... (按 Ctrl+C 退出)\n");
while (1) {
// 复制主集合到工作集合
read_fds = master_fds;

printf("等待事件...\n");
ready = pselect(max_fd + 1, &read_fds, NULL, NULL, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
printf("pselect 被信号中断\n");
break;
} else {
perror("pselect 失败");
break;
}
} else if (ready == 0) {
printf("超时: 没有文件描述符准备就绪\n");
} else {
printf("准备就绪的文件描述符数量: %d\n", ready);

// 处理标准输入
if (FD_ISSET(STDIN_FILENO, &read_fds)) {
printf("标准输入有数据:\n");
char buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 读取到: %s", buffer);

if (strncmp(buffer, "quit", 4) == 0) {
printf("收到退出命令\n");
break;
}
}
}

// 处理测试文件1
if (FD_ISSET(test_fd1, &read_fds)) {
printf("测试文件1有数据可读\n");
lseek(test_fd1, 0, SEEK_SET);
char buffer&#91;128];
ssize_t bytes_read = read(test_fd1, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 文件1内容: %s", buffer);
}
}

// 处理测试文件2
if (FD_ISSET(test_fd2, &read_fds)) {
printf("测试文件2有数据可读\n");
lseek(test_fd2, 0, SEEK_SET);
char buffer&#91;128];
ssize_t bytes_read = read(test_fd2, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 文件2内容: %s", buffer);
}
}

printf("\n");
}
}

// 清理资源
printf("清理资源...\n");
close(test_fd1);
close(test_fd2);
unlink("test1.txt");
unlink("test2.txt");

printf("程序正常退出\n");
return 0;
}

示例3:完整的事件驱动服务器模拟

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

// 服务器配置结构体
struct server_config {
int max_clients;
int timeout_seconds;
int verbose;
int use_signals;
};

// 客户端信息结构体
struct client_info {
int fd;
int connected;
time_t connect_time;
char buffer&#91;1024];
size_t buffer_pos;
};

// 服务器状态结构体
struct server_state {
int running;
int client_count;
struct client_info *clients;
int max_clients;
};

// 全局变量
volatile sig_atomic_t server_terminate = 0;

// 信号处理函数
void signal_handler(int sig) {
server_terminate = 1;
printf("\n收到终止信号 %d\n", sig);
}

// 初始化服务器状态
int init_server_state(struct server_state *state, int max_clients) {
state->running = 1;
state->client_count = 0;
state->max_clients = max_clients;

state->clients = calloc(max_clients, sizeof(struct client_info));
if (!state->clients) {
perror("分配客户端数组失败");
return -1;
}

return 0;
}

// 添加客户端
int add_client(struct server_state *state, int client_fd) {
if (state->client_count >= state->max_clients) {
fprintf(stderr, "客户端数量已达上限: %d\n", state->max_clients);
return -1;
}

for (int i = 0; i < state->max_clients; i++) {
if (!state->clients&#91;i].connected) {
state->clients&#91;i].fd = client_fd;
state->clients&#91;i].connected = 1;
state->clients&#91;i].connect_time = time(NULL);
state->clients&#91;i].buffer_pos = 0;
state->client_count++;

printf("添加客户端 %d (fd: %d),当前客户端数: %d\n",
i, client_fd, state->client_count);
return i;
}
}

fprintf(stderr, "找不到空闲的客户端槽位\n");
return -1;
}

// 移除客户端
void remove_client(struct server_state *state, int client_index) {
if (client_index >= 0 && client_index < state->max_clients &&
state->clients&#91;client_index].connected) {

close(state->clients&#91;client_index].fd);
memset(&state->clients&#91;client_index], 0, sizeof(struct client_info));
state->client_count--;

printf("移除客户端 %d,剩余客户端数: %d\n",
client_index, state->client_count);
}
}

// 显示服务器状态
void show_server_status(const struct server_state *state) {
printf("=== 服务器状态 ===\n");
printf("运行状态: %s\n", state->running ? "运行中" : "已停止");
printf("客户端数量: %d/%d\n", state->client_count, state->max_clients);
printf("当前时间: %s", ctime(&(time_t){time(NULL)}));

if (state->client_count > 0) {
printf("连接的客户端:\n");
for (int i = 0; i < state->max_clients; i++) {
if (state->clients&#91;i].connected) {
printf(" &#91;%d] fd=%d 连接时间: %s",
i, state->clients&#91;i].fd,
ctime(&state->clients&#91;i].connect_time));
}
}
}
printf("\n");
}

int main(int argc, char *argv&#91;]) {
struct server_config config = {
.max_clients = 10,
.timeout_seconds = 30,
.verbose = 0,
.use_signals = 0
};

struct server_state server_state_struct;
fd_set read_fds;
struct timespec timeout;
sigset_t sigmask;
int max_fd = STDIN_FILENO;
int ready;

printf("=== pselect 事件驱动服务器模拟器 ===\n\n");

// 解析命令行参数
static struct option long_options&#91;] = {
{"clients", required_argument, 0, 'c'},
{"timeout", required_argument, 0, 't'},
{"verbose", no_argument, 0, 'v'},
{"signals", no_argument, 0, 's'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};

int opt;
while ((opt = getopt_long(argc, argv, "c:t:vsh", long_options, NULL)) != -1) {
switch (opt) {
case 'c':
config.max_clients = atoi(optarg);
if (config.max_clients <= 0) config.max_clients = 10;
break;
case 't':
config.timeout_seconds = atoi(optarg);
if (config.timeout_seconds <= 0) config.timeout_seconds = 30;
break;
case 'v':
config.verbose = 1;
break;
case 's':
config.use_signals = 1;
break;
case 'h':
printf("用法: %s &#91;选项]\n", argv&#91;0]);
printf("选项:\n");
printf(" -c, --clients=NUM 最大客户端数 (默认 10)\n");
printf(" -t, --timeout=SECONDS 超时时间 (默认 30 秒)\n");
printf(" -v, --verbose 详细输出\n");
printf(" -s, --signals 启用信号处理\n");
printf(" -h, --help 显示此帮助信息\n");
return 0;
default:
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv&#91;0]);
return 1;
}
}

// 设置信号处理
if (config.use_signals) {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;

sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
printf("✓ 信号处理已启用\n");
}

// 初始化服务器状态
if (init_server_state(&server_state_struct, config.max_clients) == -1) {
return 1;
}

printf("服务器配置:\n");
printf(" 最大客户端数: %d\n", config.max_clients);
printf(" 超时时间: %d 秒\n", config.timeout_seconds);
printf(" 详细输出: %s\n", config.verbose ? "是" : "否");
printf(" 信号处理: %s\n", config.use_signals ? "是" : "否");
printf("\n");

// 设置超时时间
timeout.tv_sec = config.timeout_seconds;
timeout.tv_nsec = 0;

// 设置信号屏蔽集
sigemptyset(&sigmask);

printf("启动服务器模拟...\n");
printf("可用命令:\n");
printf(" 输入 'status' 查看服务器状态\n");
printf(" 输入 'connect' 模拟客户端连接\n");
printf(" 输入 'quit' 或 'exit' 退出服务器\n");
printf(" 输入 'help' 显示帮助信息\n");
printf(" 按 Ctrl+C 发送终止信号\n");
printf("\n");

// 主循环
while (server_state_struct.running && !server_terminate) {
// 初始化文件描述符集合
FD_ZERO(&read_fds);
FD_SET(STDIN_FILENO, &read_fds);
max_fd = STDIN_FILENO;

// 添加连接的客户端
for (int i = 0; i < server_state_struct.max_clients; i++) {
if (server_state_struct.clients&#91;i].connected) {
FD_SET(server_state_struct.clients&#91;i].fd, &read_fds);
if (server_state_struct.clients&#91;i].fd > max_fd) {
max_fd = server_state_struct.clients&#91;i].fd;
}
}
}

if (config.verbose) {
printf("监视 %d 个文件描述符 (最大fd: %d)...\n",
server_state_struct.client_count + 1, max_fd);
}

// 使用 pselect 等待事件
ready = pselect(max_fd + 1, &read_fds, NULL, NULL, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
if (config.verbose) {
printf("pselect 被信号中断\n");
}
continue;
} else {
perror("pselect 失败");
break;
}
} else if (ready == 0) {
if (config.verbose) {
printf("超时: 没有事件发生\n");
}
} else {
if (config.verbose) {
printf("准备就绪的文件描述符数量: %d\n", ready);
}

// 处理标准输入事件
if (FD_ISSET(STDIN_FILENO, &read_fds)) {
char input_buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, input_buffer, sizeof(input_buffer) - 1);
if (bytes_read > 0) {
input_buffer&#91;bytes_read] = '\0';

// 处理特殊命令
if (strncmp(input_buffer, "quit", 4) == 0 ||
strncmp(input_buffer, "exit", 4) == 0) {
printf("收到退出命令\n");
server_state_struct.running = 0;
server_terminate = 1;
} else if (strncmp(input_buffer, "status", 6) == 0) {
show_server_status(&server_state_struct);
} else if (strncmp(input_buffer, "help", 4) == 0) {
printf("可用命令:\n");
printf(" quit/exit - 退出服务器\n");
printf(" status - 显示服务器状态\n");
printf(" help - 显示帮助信息\n");
printf(" Ctrl+C - 发送终止信号\n");
} else if (strncmp(input_buffer, "connect", 7) == 0) {
if (server_state_struct.client_count < server_state_struct.max_clients) {
// 模拟创建客户端连接
int fake_fd = 1000 + server_state_struct.client_count;
int client_index = add_client(&server_state_struct, fake_fd);
if (client_index != -1) {
printf("模拟客户端连接成功 (fd: %d)\n", fake_fd);
}
} else {
printf("客户端数量已达上限\n");
}
} else {
printf("收到输入: %s", input_buffer);
}
}
}

// 处理客户端事件
for (int i = 0; i < server_state_struct.max_clients; i++) {
if (server_state_struct.clients&#91;i].connected &&
FD_ISSET(server_state_struct.clients&#91;i].fd, &read_fds)) {

char client_buffer&#91;256];
ssize_t bytes_read = read(server_state_struct.clients&#91;i].fd,
client_buffer, sizeof(client_buffer) - 1);

if (bytes_read > 0) {
client_buffer&#91;bytes_read] = '\0';
printf("客户端 %d 发送数据: %s", i, client_buffer);

// 回显数据
write(server_state_struct.clients&#91;i].fd, "Echo: ", 6);
write(server_state_struct.clients&#91;i].fd, client_buffer, bytes_read);

} else if (bytes_read == 0) {
printf("客户端 %d 断开连接\n", i);
remove_client(&server_state_struct, i);
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("读取客户端数据失败");
remove_client(&server_state_struct, i);
}
}
}
}
}
}

// 清理资源
printf("清理资源...\n");
for (int i = 0; i < server_state_struct.max_clients; i++) {
if (server_state_struct.clients&#91;i].connected) {
remove_client(&server_state_struct, i);
}
}
free(server_state_struct.clients);

printf("服务器模拟结束\n");

printf("\n=== pselect 服务器应用说明 ===\n");
printf("核心技术:\n");
printf("1. 多路复用: 同时监视多个文件描述符\n");
printf("2. 事件驱动: 基于事件的通知机制\n");
printf("3. 信号安全: 原子的信号处理\n");
printf("4. 超时控制: 精确的超时管理\n");
printf("5. 资源管理: 动态的客户端管理\n");
printf("\n");
printf("pselect 优势:\n");
printf("1. 原子性: 等待和信号处理是原子操作\n");
printf("2. 灵活性: 可以精确控制信号屏蔽\n");
printf("3. 精确性: 纳秒级超时控制\n");
printf("4. 可扩展: 适用于中小规模并发\n");
printf("5. 安全性: 避免信号处理中的竞态条件\n");

return 0;
}

pselect系统调用及示例-CSDN博客

pwritev2系统调用及示例

pwritev2 函数

1. 函数介绍

pwritev2 是 pwritev 的增强版本,支持额外的标志参数,提供更多的控制选项。与 preadv2 配对使用。

pwritev2系统调用及示例-CSDN博客

2. 函数原型

1
2
3
4
#define _GNU_SOURCE
#include <sys/uio.h>
ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

将多个缓冲区中的数据写入到文件的指定位置,并支持额外的控制标志。

4. 参数

  • int fd: 文件描述符

  • *const struct iovec iov: iovec结构体数组

  • int iovcnt: iov数组元素个数

  • off_t offset: 文件偏移量

  • int flags: 控制标志

5. 返回值

  • 成功: 返回实际写入的总字节数

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

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

  • pwritev: 基本版本

  • preadv2: 对应的读取函数

  • write: 基本写入函数

7. 示例代码

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

/**
* 演示pwritev2的基本使用
*/
int demo_pwritev2_basic() {
int fd;
struct iovec iov&#91;3];
ssize_t bytes_written;

printf("=== pwritev2 基本使用示例 ===\n");

// 准备要写入的数据
char part1&#91;] = "Part one of the data\n";
char part2&#91;] = "Part two of the data ";
char part3&#91;] = "and part three.\n";

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

// 设置iovec数组
iov&#91;0].iov_base = part1;
iov&#91;0].iov_len = strlen(part1);
iov&#91;1].iov_base = part2;
iov&#91;1].iov_len = strlen(part2);
iov&#91;2].iov_base = part3;
iov&#91;2].iov_len = strlen(part3);

// 使用pwritev2写入数据(flags设为0表示默认行为)
bytes_written = pwritev2(fd, iov, 3, 0, 0);
if (bytes_written == -1) {
if (errno == ENOSYS) {
printf("系统不支持 pwritev2 函数\n");
close(fd);
unlink("test_pwritev2.txt");
return 0;
}
perror("pwritev2 失败");
close(fd);
unlink("test_pwritev2.txt");
return -1;
}

printf("pwritev2 成功写入 %zd 字节\n", bytes_written);

// 读取并验证写入的数据
char buffer&#91;256];
lseek(fd, 0, SEEK_SET);
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("\n写入的文件内容:\n%s", buffer);
}

close(fd);
unlink("test_pwritev2.txt");
return 0;
}

/**
* 演示pwritev2的追加写入特性
*/
int demo_pwritev2_append() {
int fd;
struct iovec iov&#91;2];
ssize_t bytes_written;

printf("\n=== pwritev2 追加写入示例 ===\n");

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

const char *initial_data = "Initial data in the file\n";
write(fd, initial_data, strlen(initial_data));
close(fd);

// 使用pwritev2进行追加写入
fd = open("append_test.txt", O_WRONLY);
if (fd == -1) {
perror("打开文件失败");
return -1;
}

char new_data1&#91;] = "Appended data ";
char new_data2&#91;] = "using pwritev2\n";

iov&#91;0].iov_base = new_data1;
iov&#91;0].iov_len = strlen(new_data1);
iov&#91;1].iov_base = new_data2;
iov&#91;1].iov_len = strlen(new_data2);

// 注意:RWF_APPEND 标志需要内核支持
bytes_written = pwritev2(fd, iov, 2, 0, RWF_APPEND);
if (bytes_written == -1) {
if (errno == ENOSYS || errno == EINVAL) {
printf("系统不支持 RWF_APPEND 标志,使用普通写入\n");
bytes_written = pwritev2(fd, iov, 2, 0, 0);
} else {
perror("pwritev2 追加写入失败");
close(fd);
unlink("append_test.txt");
return -1;
}
}

if (bytes_written > 0) {
printf("追加写入成功,写入 %zd 字节\n", bytes_written);
}

// 显示最终文件内容
char buffer&#91;256];
lseek(fd, 0, SEEK_SET);
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("\n最终文件内容:\n%s", buffer);
}

close(fd);
unlink("append_test.txt");
return 0;
}

int main() {
printf("pwritev2 需要 Linux 4.6+ 内核支持\n");

if (demo_pwritev2_basic() == 0) {
demo_pwritev2_append();
printf("\n=== pwritev2 使用总结 ===\n");
printf("优点:支持额外控制标志,功能更强大\n");
printf("注意:需要较新内核版本支持\n");
}
return 0;
}

pwritev系统调用及示例

pwritev 函数

1. 函数介绍

pwritev 是 pwrite 的聚集写入版本,它允许一次性将多个不连续缓冲区中的数据写入到文件的指定位置。这是分散/聚集I/O操作的写入部分。

2. 函数原型

1
2
3
4
#define _GNU_SOURCE
#include <sys/uio.h>
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);

3. 功能

将由 iov 描述的多个缓冲区中的数据写入到文件描述符 fd 指定的文件中,从 offset 位置开始写入。该操作不会改变文件的当前读写位置。

4. 参数

  • int fd: 文件描述符,必须是已打开的文件

  • *const struct iovec iov: iovec结构体数组,描述多个缓冲区

  • int iovcnt: iov数组中的元素个数

  • off_t offset: 文件中的偏移量(从文件开始处计算)

5. 返回值

  • 成功: 返回实际写入的总字节数

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

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

  • writev: 基本的聚集写入函数

  • pwrite: 单缓冲区定位写入函数

  • preadv: 对应的读取函数

7. 示例代码

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

/**
* 使用pwritev进行聚集写入
*/
int demo_pwritev_basic() {
int fd;
struct iovec iov&#91;3];
ssize_t total_bytes;

// 准备要写入的数据
char data1&#91;] = "First part of the message\n";
char data2&#91;] = "Second part with more content ";
char data3&#91;] = "and final part.\n";

printf("=== pwritev 基本使用示例 ===\n");
printf("准备写入的数据:\n");
printf("数据1: %s", data1);
printf("数据2: %s", data2);
printf("数据3: %s", data3);

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

// 设置iovec数组
iov&#91;0].iov_base = data1;
iov&#91;0].iov_len = strlen(data1);
iov&#91;1].iov_base = data2;
iov&#91;1].iov_len = strlen(data2);
iov&#91;2].iov_base = data3;
iov&#91;2].iov_len = strlen(data3);

// 使用pwritev写入数据
total_bytes = pwritev(fd, iov, 3, 0);
if (total_bytes == -1) {
perror("pwritev 失败");
close(fd);
return -1;
}

printf("\npwritev 写入了 %zd 字节到文件\n", total_bytes);

// 读取并验证写入的数据
char buffer&#91;256];
lseek(fd, 0, SEEK_SET);
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("\n文件内容验证:\n%s", buffer);
}

close(fd);
unlink("test_pwritev.txt");
return 0;
}

/**
* 演示pwritev写入HTTP响应头
*/
int demo_pwritev_http_response() {
int fd;
struct iovec iov&#91;4];
ssize_t total_bytes;

printf("\n=== pwritev HTTP响应示例 ===\n");

// HTTP响应的各个部分
char status_line&#91;] = "HTTP/1.1 200 OK\r\n";
char headers&#91;] = "Content-Type: text/html\r\n"
"Content-Length: 25\r\n"
"Connection: close\r\n";
char separator&#91;] = "\r\n";
char body&#91;] = "<html><body>Hello</body></html>";

// 创建测试文件
fd = open("http_response.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建HTTP响应文件失败");
return -1;
}

// 设置iovec数组
iov&#91;0].iov_base = status_line;
iov&#91;0].iov_len = strlen(status_line);
iov&#91;1].iov_base = headers;
iov&#91;1].iov_len = strlen(headers);
iov&#91;2].iov_base = separator;
iov&#91;2].iov_len = strlen(separator);
iov&#91;3].iov_base = body;
iov&#91;3].iov_len = strlen(body);

// 一次性写入完整的HTTP响应
total_bytes = pwritev(fd, iov, 4, 0);
if (total_bytes == -1) {
perror("pwritev 写入HTTP响应失败");
close(fd);
return -1;
}

printf("成功写入HTTP响应,共 %zd 字节\n", total_bytes);

// 显示生成的HTTP响应
char buffer&#91;512];
lseek(fd, 0, SEEK_SET);
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("\n生成的HTTP响应:\n%s", buffer);
}

close(fd);
unlink("http_response.txt");
return 0;
}

int main() {
if (demo_pwritev_basic() == 0) {
demo_pwritev_http_response();
printf("\n=== pwritev 使用总结 ===\n");
printf("优点:一次系统调用写入多个缓冲区,提高效率\n");
printf("适用场景:网络协议数据发送,日志记录,配置文件更新\n");
}
return 0;
}

pwritev系统调用及示例-CSDN博客

putpmsg系统调用及示例

putpmsg 替代函数和方案详解

  1. 现代 Linux 替代方案概述

由于 putpmsg 主要在 System V STREAMS 系统中可用,而大多数 Linux 系统不完全支持 STREAMS,因此需要使用现代的替代方案。以下是主要的替代函数和方案:

putpmsg系统调用及示例-CSDN博客

  1. 替代方案分类

2.1 实时信号 (RT Signals)

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
#include <signal.h>
#include <sys/types.h>
#include <string.h>

// 使用实时信号发送优先级数据
int send_priority_data_with_signal(pid_t target_pid, int priority,
const void *data, size_t data_size) {
union sigval signal_data;

// 实时信号支持传递额外数据
if (data_size <= sizeof(signal_data)) {
memcpy(&signal_data, data, data_size);
signal_data.sival_int = priority; // 将优先级存储在整型字段中
} else {
// 对于大数据,传递指针或标识符
signal_data.sival_ptr = (void*)data; // 注意:需要共享内存
signal_data.sival_int = priority;
}

// 选择实时信号 (SIGRTMIN + 0 到 SIGRTMAX - SIGRTMIN)
int signal_num = SIGRTMIN + (priority % (SIGRTMAX - SIGRTMIN + 1));

return sigqueue(target_pid, signal_num, signal_data);
}

// 信号处理函数
void priority_signal_handler(int sig, siginfo_t *info, void *context) {
printf("收到优先级信号 %d,优先级: %d\n", sig, info->si_value.sival_int);

if (info->si_code == SI_QUEUE) {
// 处理队列中的信号
printf(" 信号来源: 进程 %d\n", info->si_pid);
if (info->si_value.sival_ptr) {
printf(" 数据指针: %p\n", info->si_value.sival_ptr);
}
}
}

2.2 Unix 域套接字 (Unix Domain Sockets)

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
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

// 消息头结构体
struct priority_message_header {
int priority; // 消息优先级
int msg_type; // 消息类型
size_t data_size; // 数据大小
time_t timestamp; // 时间戳
};

// 使用 Unix 域套接字发送优先级消息
int send_priority_message_unix(int sockfd, int priority,
const void *data, size_t data_size) {
struct priority_message_header header;

// 构造消息头
header.priority = priority;
header.msg_type = 1; // 普通消息类型
header.data_size = data_size;
header.timestamp = time(NULL);

// 发送消息头
ssize_t sent = send(sockfd, &header, sizeof(header), MSG_NOSIGNAL);
if (sent != sizeof(header)) {
return -1;
}

// 发送实际数据
if (data_size > 0) {
sent = send(sockfd, data, data_size, MSG_NOSIGNAL);
if (sent != (ssize_t)data_size) {
return -1;
}
}

return 0;
}

// 接收优先级消息
ssize_t receive_priority_message_unix(int sockfd, int *priority,
void *buffer, size_t buffer_size) {
struct priority_message_header header;

// 接收消息头
ssize_t received = recv(sockfd, &header, sizeof(header), 0);
if (received != sizeof(header)) {
return -1;
}

// 返回优先级
if (priority) {
*priority = header.priority;
}

// 接收数据
if (header.data_size > 0 && header.data_size <= buffer_size) {
received = recv(sockfd, buffer, header.data_size, 0);
if (received == (ssize_t)header.data_size) {
return received;
}
}

return -1;
}

2.3 管道和 FIFO

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

// 带优先级的消息结构体
struct fifo_message {
int priority; // 消息优先级
pid_t sender_pid; // 发送者 PID
size_t data_size; // 数据大小
char data&#91;1024]; // 消息数据
};

// 通过 FIFO 发送优先级消息
int send_priority_message_fifo(const char *fifo_path, int priority,
const void *data, size_t data_size) {
int fd;
struct fifo_message msg;

// 打开 FIFO
fd = open(fifo_path, O_WRONLY);
if (fd == -1) {
return -1;
}

// 构造消息
msg.priority = priority;
msg.sender_pid = getpid();
msg.data_size = (data_size < sizeof(msg.data)) ? data_size : sizeof(msg.data);
if (data && data_size > 0) {
memcpy(msg.data, data, msg.data_size);
}

// 发送消息
ssize_t written = write(fd, &msg, sizeof(msg));
close(fd);

return (written == sizeof(msg)) ? 0 : -1;
}

// 通过 FIFO 接收优先级消息
int receive_priority_message_fifo(const char *fifo_path,
struct fifo_message *msg) {
int fd;

// 以非阻塞模式打开 FIFO
fd = open(fifo_path, O_RDONLY | O_NONBLOCK);
if (fd == -1) {
return -1;
}

// 使用 poll 等待数据
struct pollfd pfd = {fd, POLLIN, 0};
int ready = poll(&pfd, 1, 1000); // 1秒超时

if (ready > 0 && (pfd.revents & POLLIN)) {
ssize_t bytes_read = read(fd, msg, sizeof(struct fifo_message));
close(fd);
return (bytes_read == sizeof(struct fifo_message)) ? 0 : -1;
}

close(fd);
return -1;
}

2.4 D-Bus 消息系统

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
#include <dbus/dbus.h>
#include <stdio.h>
#include <stdlib.h>

// 使用 D-Bus 发送优先级消息 (需要安装 libdbus)
#ifdef USE_DBUS

int send_dbus_priority_message(const char *bus_name,
const char *object_path,
const char *interface,
int priority,
const char *message_data) {
DBusConnection *connection;
DBusMessage *message;
DBusPendingCall *pending;
dbus_bool_t result;

// 连接到系统总线
dbus_error_t error;
dbus_error_init(&error);

connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
if (dbus_error_is_set(&error)) {
fprintf(stderr, "连接 D-Bus 失败: %s\n", error.message);
dbus_error_free(&error);
return -1;
}

// 创建消息
message = dbus_message_new_signal(object_path, interface, "PriorityMessage");
if (!message) {
dbus_connection_unref(connection);
return -1;
}

// 添加参数
dbus_message_append_args(message,
DBUS_TYPE_INT32, &priority,
DBUS_TYPE_STRING, &message_data,
DBUS_TYPE_INVALID);

// 发送消息
result = dbus_connection_send(connection, message, NULL);
dbus_message_unref(message);
dbus_connection_flush(connection);
dbus_connection_unref(connection);

return result ? 0 : -1;
}

#endif

2.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
#include <pthread.h>
#include <stdlib.h>
#include <sys/queue.h>

// 消息结构体
struct priority_message {
int priority; // 消息优先级
time_t timestamp; // 时间戳
pid_t sender_pid; // 发送者 PID
size_t data_size; // 数据大小
void *data; // 消息数据
SLIST_ENTRY(priority_message) entries;
};

// 消息队列
SLIST_HEAD(message_queue, priority_message) global_message_queue =
SLIST_HEAD_INITIALIZER(global_message_queue);

// 互斥锁和条件变量
pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;

// 发送优先级消息
int send_priority_message_queue(int priority, const void *data, size_t data_size) {
// 分配消息内存
struct priority_message *msg = malloc(sizeof(struct priority_message));
if (!msg) {
return -1;
}

// 分配数据内存
if (data_size > 0) {
msg->data = malloc(data_size);
if (!msg->data) {
free(msg);
return -1;
}
memcpy(msg->data, data, data_size);
} else {
msg->data = NULL;
}

// 初始化消息
msg->priority = priority;
msg->timestamp = time(NULL);
msg->sender_pid = getpid();
msg->data_size = data_size;

// 添加到队列
pthread_mutex_lock(&queue_mutex);

// 按优先级插入队列(简单实现:插到队首)
SLIST_INSERT_HEAD(&global_message_queue, msg, entries);

pthread_cond_signal(&queue_cond); // 通知等待的线程
pthread_mutex_unlock(&queue_mutex);

return 0;
}

// 接收优先级消息(阻塞)
struct priority_message* receive_priority_message_queue(int timeout_seconds) {
struct priority_message *msg = NULL;
struct timespec timeout;

pthread_mutex_lock(&queue_mutex);

// 等待消息
while (SLIST_EMPTY(&global_message_queue)) {
if (timeout_seconds > 0) {
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += timeout_seconds;

int result = pthread_cond_timedwait(&queue_cond, &queue_mutex, &timeout);
if (result == ETIMEDOUT) {
pthread_mutex_unlock(&queue_mutex);
return NULL;
}
} else {
pthread_cond_wait(&queue_cond, &queue_mutex);
}
}

// 取出最高优先级的消息
msg = SLIST_FIRST(&global_message_queue);
SLIST_REMOVE_HEAD(&global_message_queue, entries);

pthread_mutex_unlock(&queue_mutex);

return msg;
}

// 清理消息
void free_priority_message(struct priority_message *msg) {
if (msg) {
if (msg->data) {
free(msg->data);
}
free(msg);
}
}

2.6 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
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

// 发送优先级消息到 POSIX 消息队列
int send_priority_message_mq(mqd_t mqdes, int priority,
const void *data, size_t data_size) {
// POSIX 消息队列直接支持优先级
return mq_send(mqdes, (const char*)data, data_size, priority);
}

// 接收优先级消息
ssize_t receive_priority_message_mq(mqd_t mqdes, unsigned *priority,
void *buffer, size_t buffer_size) {
return mq_receive(mqdes, (char*)buffer, buffer_size, priority);
}

// 创建优先级消息队列
mqd_t create_priority_message_queue(const char *name, int max_messages,
size_t max_message_size) {
struct mq_attr attr;

attr.mq_flags = 0;
attr.mq_maxmsg = max_messages;
attr.mq_msgsize = max_message_size;
attr.mq_curmsgs = 0;

return mq_open(name, O_CREAT | O_WRONLY, 0644, &attr);
}

2.7 epoll + 管道实现优先级

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
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <unistd.h>

// 优先级管道结构体
struct priority_pipe {
int epoll_fd;
int event_fd; // 用于通知事件
int **pipes; // 按优先级划分的管道数组
int num_priorities;
};

// 创建优先级管道系统
struct priority_pipe* create_priority_pipe_system(int num_priorities) {
struct priority_pipe *pp = malloc(sizeof(struct priority_pipe));
if (!pp) return NULL;

pp->num_priorities = num_priorities;
pp->pipes = malloc(num_priorities * sizeof(int*));
if (!pp->pipes) {
free(pp);
return NULL;
}

// 创建 epoll 实例
pp->epoll_fd = epoll_create1(0);
if (pp->epoll_fd == -1) {
free(pp->pipes);
free(pp);
return NULL;
}

// 创建事件通知 fd
pp->event_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (pp->event_fd == -1) {
close(pp->epoll_fd);
free(pp->pipes);
free(pp);
return NULL;
}

// 添加事件 fd 到 epoll
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = pp->event_fd;
epoll_ctl(pp->epoll_fd, EPOLL_CTL_ADD, pp->event_fd, &ev);

// 创建优先级管道
for (int i = 0; i < num_priorities; i++) {
pp->pipes&#91;i] = malloc(2 * sizeof(int));
if (pp->pipes&#91;i] && pipe(pp->pipes&#91;i]) == 0) {
// 设置非阻塞模式
int flags = fcntl(pp->pipes&#91;i]&#91;0], F_GETFL, 0);
fcntl(pp->pipes&#91;i]&#91;0], F_SETFL, flags | O_NONBLOCK);
}
}

return pp;
}

// 发送优先级消息
int send_priority_message_pipe(struct priority_pipe *pp, int priority,
const void *data, size_t data_size) {
if (priority < 0 || priority >= pp->num_priorities) {
errno = EINVAL;
return -1;
}

ssize_t written = write(pp->pipes&#91;priority]&#91;1], data, data_size);
if (written == (ssize_t)data_size) {
// 通知有新消息
uint64_t notify = 1;
write(pp->event_fd, &notify, sizeof(notify));
return 0;
}

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

// 消息优先级枚举
enum message_priority {
PRIORITY_LOW = 10,
PRIORITY_NORMAL = 50,
PRIORITY_HIGH = 100,
PRIORITY_CRITICAL = 200
};

// 通用消息结构体
struct unified_message {
int priority;
time_t timestamp;
pid_t sender_pid;
char type&#91;32];
size_t data_size;
char data&#91;512];
};

// 消息处理器函数指针
typedef void (*message_handler_t)(const struct unified_message *msg);

// 消息系统接口
struct message_system {
const char *name;
int (*send_func)(const struct unified_message *msg);
int (*receive_func)(struct unified_message *msg, int timeout_ms);
int (*init_func)(void);
void (*cleanup_func)(void);
};

// 实时信号消息系统
static int rt_signal_send(const struct unified_message *msg) {
union sigval data;
data.sival_int = msg->priority;
// 使用 SIGRTMIN + 0 作为消息信号
return sigqueue(getpid(), SIGRTMIN, data);
}

static int rt_signal_receive(struct unified_message *msg, int timeout_ms) {
// 这里简化处理,实际需要信号处理机制
sleep(timeout_ms / 1000);
return -1; // 超时
}

// 自定义队列消息系统
static struct unified_message *message_queue&#91;100];
static int queue_head = 0, queue_tail = 0, queue_count = 0;
static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;

static int custom_queue_send(const struct unified_message *msg) {
pthread_mutex_lock(&queue_mutex);

if (queue_count >= 100) {
pthread_mutex_unlock(&queue_mutex);
return -1; // 队列满
}

// 分配消息内存
struct unified_message *new_msg = malloc(sizeof(struct unified_message));
if (!new_msg) {
pthread_mutex_unlock(&queue_mutex);
return -1;
}

memcpy(new_msg, msg, sizeof(struct unified_message));
new_msg->timestamp = time(NULL);

// 按优先级插入队列
int insert_pos = queue_head;
for (int i = 0; i < queue_count; i++) {
int pos = (queue_head + i) % 100;
if (message_queue&#91;pos]->priority < new_msg->priority) {
insert_pos = pos;
break;
}
}

// 移动后续消息
for (int i = queue_count; i > insert_pos; i--) {
int from_pos = (queue_head + i - 1) % 100;
int to_pos = (queue_head + i) % 100;
message_queue&#91;to_pos] = message_queue&#91;from_pos];
}

message_queue&#91;insert_pos] = new_msg;
queue_count++;

pthread_mutex_unlock(&queue_mutex);
return 0;
}

static int custom_queue_receive(struct unified_message *msg, int timeout_ms) {
time_t start_time = time(NULL);

while (1) {
pthread_mutex_lock(&queue_mutex);

if (queue_count > 0) {
// 取出最高优先级消息
struct unified_message *highest_msg = message_queue&#91;queue_head];
memcpy(msg, highest_msg, sizeof(struct unified_message));
free(highest_msg);

queue_head = (queue_head + 1) % 100;
queue_count--;

pthread_mutex_unlock(&queue_mutex);
return 0;
}

pthread_mutex_unlock(&queue_mutex);

// 检查超时
if (timeout_ms > 0) {
time_t elapsed = time(NULL) - start_time;
if (elapsed * 1000 >= timeout_ms) {
return -1; // 超时
}
}

usleep(10000); // 休眠 10ms
}

return -1;
}

// 消息系统实现
static struct message_system available_systems&#91;] = {
{
.name = "custom_queue",
.send_func = custom_queue_send,
.receive_func = custom_queue_receive,
.init_func = NULL,
.cleanup_func = NULL
},
{
.name = "rt_signal",
.send_func = rt_signal_send,
.receive_func = rt_signal_receive,
.init_func = NULL,
.cleanup_func = NULL
}
};

// 当前使用的消息系统
static struct message_system *current_system = &available_systems&#91;0];

// 发送统一消息
int send_unified_message(int priority, const char *type,
const void *data, size_t data_size) {
struct unified_message msg;

msg.priority = priority;
msg.timestamp = time(NULL);
msg.sender_pid = getpid();
strncpy(msg.type, type, sizeof(msg.type) - 1);
msg.type&#91;sizeof(msg.type) - 1] = '\0';

msg.data_size = (data_size < sizeof(msg.data)) ? data_size : sizeof(msg.data);
if (data && data_size > 0) {
memcpy(msg.data, data, msg.data_size);
}

if (current_system->send_func) {
return current_system->send_func(&msg);
}

return -1;
}

// 接收统一消息
int receive_unified_message(struct unified_message *msg, int timeout_ms) {
if (current_system->receive_func) {
return current_system->receive_func(msg, timeout_ms);
}
return -1;
}

// 演示不同优先级消息处理
void demonstrate_priority_handling() {
printf("=== 优先级消息处理演示 ===\n\n");

// 发送不同优先级的消息
printf("发送不同优先级的消息:\n");

const char *critical_msg = "系统紧急告警:磁盘空间不足";
send_unified_message(PRIORITY_CRITICAL, "ALERT", critical_msg, strlen(critical_msg));
printf(" ✓ 发送关键优先级消息 (优先级 %d)\n", PRIORITY_CRITICAL);

const char *high_msg = "应用程序错误:数据库连接失败";
send_unified_message(PRIORITY_HIGH, "ERROR", high_msg, strlen(high_msg));
printf(" ✓ 发送高优先级消息 (优先级 %d)\n", PRIORITY_HIGH);

const char *normal_msg = "用户登录成功";
send_unified_message(PRIORITY_NORMAL, "INFO", normal_msg, strlen(normal_msg));
printf(" ✓ 发送普通优先级消息 (优先级 %d)\n", PRIORITY_NORMAL);

const char *low_msg = "系统日志:定时任务执行完成";
send_unified_message(PRIORITY_LOW, "DEBUG", low_msg, strlen(low_msg));
printf(" ✓ 发送低优先级消息 (优先级 %d)\n", PRIORITY_LOW);

printf("\n接收消息 (按优先级顺序):\n");

// 接收并显示消息
struct unified_message received_msg;
for (int i = 0; i < 4; i++) {
if (receive_unified_message(&received_msg, 1000) == 0) {
printf(" &#91;%d] 优先级 %d (%s): %.*s\n",
i + 1, received_msg.priority, received_msg.type,
(int)received_msg.data_size, received_msg.data);
} else {
printf(" &#91;%d] 超时或无消息\n", i + 1);
}
}
}

int main() {
printf("=== putpmsg 现代替代方案演示 ===\n\n");

printf("putpmsg 替代方案概述:\n");
printf("1. 实时信号 (RT signals)\n");
printf("2. Unix 域套接字\n");
printf("3. 管道和 FIFO\n");
printf("4. D-Bus 消息系统\n");
printf("5. 自定义优先级队列\n");
printf("6. POSIX 消息队列\n");
printf("7. epoll + 管道\n");
printf("\n");

// 演示优先级处理
demonstrate_priority_handling();

printf("\n=== 各方案特点对比 ===\n");
printf("方案 优先级支持 跨进程 复杂度 性能\n");
printf("------------- ---------- ------- ------ ----\n");
printf("实时信号 中等 是 低 高\n");
printf("Unix套接字 无 是 中 中\n");
printf("管道/FIFO 无 是 低 中\n");
printf("D-Bus 高 是 高 中\n");
printf("自定义队列 高 否 中 高\n");
printf("POSIX消息队列 高 是 中 高\n");
printf("epoll+管道 高 是 高 高\n");
printf("\n");

printf("=== 选择建议 ===\n");
printf("简单应用: 使用实时信号或管道\n");
printf("复杂系统: 使用 POSIX 消息队列\n");
printf("高性能: 使用 epoll + 管道\n");
printf("企业级: 使用 D-Bus\n");
printf("跨语言: 使用 D-Bus 或 Unix 套接字\n");

return 0;
}

  1. 编译和运行说明
1
2
3
4
5
6
7
8
9
10
11
12
# 编译示例程序
gcc -o putpmsg_alternative alternative.c -lpthread

# 编译 D-Bus 版本 (如果使用)
gcc -o putpmsg_dbus dbus_example.c -ldbus-1

# 编译 POSIX 消息队列版本
gcc -o putpmsg_mq mq_example.c -lrt

# 运行示例
./putpmsg_alternative

  1. 各方案详细对比

5.1 功能对比表

方案优先级跨进程实时性复杂度可移植性资源消耗实时信号中等是高低中低Unix套接字无是中中高中管道/FIFO无是中低高低D-Bus高是中高中高自定义队列高否高中低低POSIX消息队列高是高中中中epoll+管道高是高高中低

5.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
// 场景1: 简单的进程间通信
// 推荐: 实时信号
int simple_ipc_with_signals() {
// 发送优先级信号
union sigval data;
data.sival_int = PRIORITY_HIGH;
return sigqueue(target_pid, SIGRTMIN, data);
}

// 场景2: 高性能本地通信
// 推荐: Unix 域套接字
int high_performance_local_communication() {
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
// 配置套接字...
return sockfd;
}

// 场景3: 系统级消息传递
// 推荐: POSIX 消息队列
int system_level_messaging() {
struct mq_attr attr = {0, 10, 1024, 0};
mqd_t mqdes = mq_open("/system_queue", O_CREAT | O_WRONLY, 0644, &attr);
return (mqdes != (mqd_t)-1) ? 0 : -1;
}

// 场景4: 复杂的企业级应用
// 推荐: D-Bus
int enterprise_application_messaging() {
// 连接到 D-Bus...
// 发送消息...
return 0;
}

  1. 最佳实践总结

6.1 选择原则

简单需求: 优先考虑实时信号或管道

高性能: 选择 epoll + 管道或 Unix 套接字

复杂需求: 使用 POSIX 消息队列或 D-Bus

跨语言: 优先考虑 D-Bus 或 Unix 套接字

系统集成: 使用系统现有的消息机制

6.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
// 统一的错误处理
#define HANDLE_ERROR(operation) \
do { \
if ((operation) == -1) { \
fprintf(stderr, "错误: %s 失败 - %s\n", #operation, strerror(errno)); \
return -1; \
} \
} while(0)

// 资源清理宏
#define CLEANUP_RESOURCE(resource, cleanup_func) \
do { \
if (resource) { \
cleanup_func(resource); \
resource = NULL; \
} \
} while(0)

// 安全的消息发送函数
int safe_send_priority_message(int priority, const void *data, size_t data_size) {
if (!data || data_size == 0) {
errno = EINVAL;
return -1;
}

if (priority < 0 || priority > 255) {
errno = EINVAL;
return -1;
}

return send_unified_message(priority, "GENERIC", data, data_size);
}

这些替代方案为 putpmsg 提供了现代化的解决方案,每种方案都有其适用场景和优势。选择合适的方案需要根据具体的需求、性能要求和系统环境来决定。