getmsg系统调用及示例

getmsg 函数详解

  1. 函数介绍

getmsg 是 System V STREAMS 接口中的一个函数,用于从 STREAMS 设备或管道中接收消息。可以把 STREAMS 想象成一个”消息传送带系统”——数据以消息的形式在系统中流动,getmsg 就是从这个传送带上取下消息的工具。

STREAMS 是 Unix System V 中的一种模块化 I/O 框架,它允许在数据流中插入处理模块,实现复杂的数据处理。虽然在现代 Linux 系统中 STREAMS 使用较少,但在一些 Unix 系统(如 Solaris)中仍然重要。

getmsg 允许你接收包含控制信息和数据信息的消息,提供了比普通 read 更精细的控制。

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

  1. 函数原型
1
2
3
4
#include <stropts.h>

int getmsg(int fildes, struct strbuf *ctlptr, struct strbuf *dataptr, int *flagsp);

  1. 功能

getmsg 函数用于从 STREAMS 文件描述符中接收消息。它可以分别接收消息的控制部分和数据部分,提供了对消息结构的精细控制。

  1. 参数
  • fildes: STREAMS 设备或管道的文件描述符

  • ctlptr: 指向 strbuf 结构体的指针,用于接收控制信息

  • dataptr: 指向 strbuf 结构体的指针,用于接收数据信息

  • flagsp: 指向标志的指针,用于指定接收模式和返回消息类型

  1. strbuf 结构体
1
2
3
4
5
6
struct strbuf {
int maxlen; /* 缓冲区最大长度 */
int len; /* 实际数据长度 */
char *buf; /* 指向缓冲区的指针 */
};

  1. flags 参数说明

输入标志(指定要接收的消息类型):

  • 0: 接收下一条消息(按优先级顺序)

  • RS_HIPRI: 接收下一条高优先级消息

输出标志(返回实际接收到的消息类型):

  • 0: 普通优先级消息

  • RS_HIPRI: 高优先级消息

  1. 返回值
  • 成功: 返回 0 或非负值

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

特殊返回值:

  • MORECTL: 控制部分还有更多数据

  • MOREDATA: 数据部分还有更多数据

常见错误码:

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

  • EINVAL: 参数无效

  • EIO: I/O 错误

  • ENOSTR: fildes 不是 STREAMS 设备

  • ENOSR: 没有足够的 STREAMS 资源

  • EAGAIN: 非阻塞模式下无数据可读

  1. 相似函数或关联函数
  • putmsg: 发送消息到 STREAMS 设备

  • getpmsg: 获取带优先级的消息(更高级的版本)

  • putpmsg: 发送带优先级的消息

  • ioctl: 控制 STREAMS 设备

  • read/write: 普通的文件读写操作

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

// 注意:这个示例在大多数 Linux 系统上可能无法运行
// 因为 Linux 不完全支持 STREAMS

int main() {
int fd;
struct strbuf ctlbuf, databuf;
char ctl_data&#91;256], data_buf&#91;1024];
int flags;

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

// 初始化缓冲区结构
ctlbuf.maxlen = sizeof(ctl_data);
ctlbuf.buf = ctl_data;
ctlbuf.len = 0;

databuf.maxlen = sizeof(data_buf);
databuf.buf = data_buf;
databuf.len = 0;

flags = 0; // 接收普通消息

printf("注意: getmsg 主要用于 STREAMS 系统\n");
printf("在大多数 Linux 系统上可能不可用\n\n");

// 尝试打开一个 STREAMS 设备(示例)
// fd = open("/dev/stream_device", O_RDONLY);
// 由于大多数系统没有 STREAMS 设备,这里只演示结构

printf("控制缓冲区设置:\n");
printf(" 最大长度: %d\n", ctlbuf.maxlen);
printf(" 缓冲区地址: %p\n", (void*)ctlbuf.buf);

printf("数据缓冲区设置:\n");
printf(" 最大长度: %d\n", databuf.maxlen);
printf(" 缓冲区地址: %p\n", (void*)databuf.buf);

printf("标志设置: %d\n", flags);

printf("\n如果在支持 STREAMS 的系统上,可以这样调用:\n");
printf("result = getmsg(fd, &ctlbuf, &databuf, &flags);\n");

return 0;
}

示例2:模拟 STREAMS 消息处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 模拟 STREAMS 消息结构
struct simulated_msg {
int priority; // 消息优先级
int control_len; // 控制数据长度
char control_data&#91;256]; // 控制数据
int data_len; // 数据长度
char data&#91;1024]; // 实际数据
};

// 模拟的 strbuf 结构
struct simulated_strbuf {
int maxlen;
int len;
char *buf;
};

// 模拟的 getmsg 函数
int simulated_getmsg(struct simulated_msg *msg,
struct simulated_strbuf *ctlptr,
struct simulated_strbuf *dataptr,
int *flagsp) {

printf("=== 模拟 getmsg 操作 ===\n");

// 复制控制数据
if (ctlptr && ctlptr->buf) {
int copy_len = (msg->control_len < ctlptr->maxlen) ?
msg->control_len : ctlptr->maxlen;
memcpy(ctlptr->buf, msg->control_data, copy_len);
ctlptr->len = copy_len;
printf("复制控制数据: %d 字节\n", copy_len);
}

// 复制数据
if (dataptr && dataptr->buf) {
int copy_len = (msg->data_len < dataptr->maxlen) ?
msg->data_len : dataptr->maxlen;
memcpy(dataptr->buf, msg->data, copy_len);
dataptr->len = copy_len;
printf("复制数据: %d 字节\n", copy_len);
}

// 设置标志
if (flagsp) {
*flagsp = (msg->priority > 0) ? 1 : 0; // 模拟高优先级标志
}

printf("消息优先级: %s\n",
(msg->priority > 0) ? "高" : "普通");
printf("控制数据长度: %d\n", msg->control_len);
printf("数据长度: %d\n", msg->data_len);

return 0; // 成功
}

// 创建测试消息
struct simulated_msg* create_test_message() {
static struct simulated_msg msg;

msg.priority = 1; // 高优先级
msg.control_len = strlen("CONTROL_INFO") + 1;
strcpy(msg.control_data, "CONTROL_INFO");
msg.data_len = strlen("Hello, STREAMS World!") + 1;
strcpy(msg.data, "Hello, STREAMS World!");

return &msg;
}

int main() {
struct simulated_msg *test_msg;
struct simulated_strbuf ctlbuf, databuf;
char ctl_buffer&#91;256], data_buffer&#91;1024];
int flags;
int result;

printf("=== STREAMS 消息处理模拟 ===\n\n");

// 创建测试消息
test_msg = create_test_message();
printf("创建测试消息完成\n\n");

// 初始化缓冲区
ctlbuf.maxlen = sizeof(ctl_buffer);
ctlbuf.buf = ctl_buffer;
ctlbuf.len = 0;

databuf.maxlen = sizeof(data_buffer);
databuf.buf = data_buffer;
databuf.len = 0;

flags = 0;

// 调用模拟的 getmsg
result = simulated_getmsg(test_msg, &ctlbuf, &databuf, &flags);

if (result == 0) {
printf("\n=== 接收结果 ===\n");
printf("控制数据 (%d 字节): %s\n", ctlbuf.len, ctlbuf.buf);
printf("数据 (%d 字节): %s\n", databuf.len, databuf.buf);
printf("消息标志: %s\n", (flags > 0) ? "高优先级" : "普通优先级");
} else {
printf("接收消息失败\n");
}

return 0;
}

示例3:完整的 STREAMS 消息系统模拟

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

// 消息类型定义
#define MSG_NORMAL 0
#define MSG_HIGH_PRIORITY 1
#define MSG_CONTROL 2
#define MSG_DATA 3

// 模拟的 STREAMS 消息队列
struct message_queue {
int count;
struct {
int priority;
int type;
time_t timestamp;
int control_len;
char control_data&#91;128];
int data_len;
char data&#91;512];
} messages&#91;10];
};

// 模拟的 strbuf 结构
struct my_strbuf {
int maxlen;
int len;
char *buf;
};

// 全局消息队列
struct message_queue global_queue = {0};

// 向队列添加消息
int add_message(int priority, int type,
const char *control, const char *data) {
if (global_queue.count >= 10) {
printf("消息队列已满\n");
return -1;
}

int index = global_queue.count++;
global_queue.messages&#91;index].priority = priority;
global_queue.messages&#91;index].type = type;
global_queue.messages&#91;index].timestamp = time(NULL);

if (control) {
global_queue.messages&#91;index].control_len = strlen(control) + 1;
strncpy(global_queue.messages&#91;index].control_data, control, 127);
global_queue.messages&#91;index].control_data&#91;127] = '\0';
} else {
global_queue.messages&#91;index].control_len = 0;
global_queue.messages&#91;index].control_data&#91;0] = '\0';
}

if (data) {
global_queue.messages&#91;index].data_len = strlen(data) + 1;
strncpy(global_queue.messages&#91;index].data, data, 511);
global_queue.messages&#91;index].data&#91;511] = '\0';
} else {
global_queue.messages&#91;index].data_len = 0;
global_queue.messages&#91;index].data&#91;0] = '\0';
}

printf("添加消息: 优先级=%d, 类型=%d, 控制=%s, 数据=%s\n",
priority, type, control ? control : "无", data ? data : "无");

return 0;
}

// 模拟的 getmsg 实现
int my_getmsg(struct my_strbuf *ctlptr,
struct my_strbuf *dataptr,
int *flagsp) {

if (global_queue.count == 0) {
printf("消息队列为空\n");
return -1;
}

// 查找高优先级消息(如果请求)
int msg_index = 0;
if (flagsp && (*flagsp & 1)) { // 模拟 RS_HIPRI
for (int i = 0; i < global_queue.count; i++) {
if (global_queue.messages&#91;i].priority > 0) {
msg_index = i;
break;
}
}
}

// 获取消息
struct message_queue *msg = &global_queue.messages&#91;msg_index];

// 复制控制数据
if (ctlptr && ctlptr->buf) {
int copy_len = (msg->control_len < ctlptr->maxlen) ?
msg->control_len : ctlptr->maxlen;
memcpy(ctlptr->buf, msg->control_data, copy_len);
ctlptr->len = copy_len;
}

// 复制数据
if (dataptr && dataptr->buf) {
int copy_len = (msg->data_len < dataptr->maxlen) ?
msg->data_len : dataptr->maxlen;
memcpy(dataptr->buf, msg->data, copy_len);
dataptr->len = copy_len;
}

// 设置返回标志
if (flagsp) {
*flagsp = (msg->priority > 0) ? 1 : 0; // 高优先级标志
}

// 从队列中移除消息
for (int i = msg_index; i < global_queue.count - 1; i++) {
global_queue.messages&#91;i] = global_queue.messages&#91;i + 1];
}
global_queue.count--;

return 0;
}

// 显示队列状态
void show_queue_status() {
printf("\n=== 消息队列状态 ===\n");
printf("队列中消息数量: %d\n", global_queue.count);

for (int i = 0; i < global_queue.count; i++) {
printf("消息 %d: 优先级=%d, 类型=%d, 时间=%s",
i, global_queue.messages&#91;i].priority,
global_queue.messages&#91;i].type,
ctime(&global_queue.messages&#91;i].timestamp));
printf(" 控制: %s\n", global_queue.messages&#91;i].control_data);
printf(" 数据: %s\n", global_queue.messages&#91;i].data);
}
}

int main() {
struct my_strbuf ctlbuf, databuf;
char ctl_buffer&#91;256], data_buffer&#91;1024];
int flags;
int result;

printf("=== 完整的 STREAMS 消息系统模拟 ===\n\n");

// 初始化缓冲区
ctlbuf.maxlen = sizeof(ctl_buffer);
ctlbuf.buf = ctl_buffer;
ctlbuf.len = 0;

databuf.maxlen = sizeof(data_buffer);
databuf.buf = data_buffer;
databuf.len = 0;

// 添加测试消息
printf("添加测试消息...\n");
add_message(0, MSG_NORMAL, "NORMAL_CTL", "普通消息数据");
add_message(1, MSG_HIGH_PRIORITY, "HIGH_CTL", "高优先级消息");
add_message(0, MSG_DATA, "DATA_CTL", "另一个普通消息");
add_message(1, MSG_CONTROL, "CTRL_CTL", "高优先级控制消息");

show_queue_status();

// 测试接收普通消息
printf("\n--- 测试1: 接收普通消息 ---\n");
flags = 0; // 普通消息
result = my_getmsg(&ctlbuf, &databuf, &flags);

if (result == 0) {
printf("成功接收消息:\n");
printf(" 控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
printf(" 数据: %.*s\n", databuf.len, databuf.buf);
printf(" 优先级: %s\n", (flags > 0) ? "高" : "普通");
}

// 测试接收高优先级消息
printf("\n--- 测试2: 接收高优先级消息 ---\n");
flags = 1; // 高优先级消息
result = my_getmsg(&ctlbuf, &databuf, &flags);

if (result == 0) {
printf("成功接收高优先级消息:\n");
printf(" 控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
printf(" 数据: %.*s\n", databuf.len, databuf.buf);
printf(" 优先级: %s\n", (flags > 0) ? "高" : "普通");
}

show_queue_status();

printf("\n=== STREAMS 概念说明 ===\n");
printf("STREAMS 是 System V 中的消息传递机制\n");
printf("特点:\n");
printf("1. 消息包含控制部分和数据部分\n");
printf("2. 支持消息优先级\n");
printf("3. 模块化处理架构\n");
printf("4. 主要在 Solaris 等系统中使用\n");
printf("\n在 Linux 中,类似功能可通过以下方式实现:\n");
printf("- Unix 域套接字\n");
printf("- 管道和 FIFO\n");
printf("- netlink 套接字\n");
printf("- D-Bus 消息系统\n");

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
# 编译示例程序
gcc -o getmsg_example1 example1.c
gcc -o getmsg_example2 example2.c
gcc -o getmsg_example3 example3.c

# 运行示例
./getmsg_example1
./getmsg_example2
./getmsg_example3

STREAMS 系统检查

1
2
3
4
5
6
7
8
9
# 检查系统是否支持 STREAMS
ls /usr/include/stropts.h

# 在支持 STREAMS 的系统上编译
gcc -DSTREAMS_AVAILABLE -o getmsg_real example_real.c -lstrmi

# 查看 STREAMS 相关设备
ls /dev/* | grep stream

重要注意事项

系统支持: getmsg 主要在 System V Unix 系统中可用

Linux 限制: 大多数 Linux 系统不完全支持 STREAMS

移植性: 代码可移植性较差

替代方案: Linux 中可以使用其他 IPC 机制

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

现代 Linux 替代方案

使用 Unix 域套接字

1
2
3
4
5
6
7
8
9
10
#include <sys/socket.h>
#include <sys/un.h>

// 创建 Unix 域套接字进行消息传递
int create_unix_socket(const char *path) {
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
// ... 配置和使用套接字
return sock;
}

使用管道

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

// 创建管道进行进程间通信
int pipe_fd&#91;2];
if (pipe(pipe_fd) == 0) {
// 使用 pipe_fd&#91;0] 读取,pipe_fd&#91;1] 写入
}

实际应用场景

设备驱动: 在某些 Unix 系统中用于设备通信

网络协议: 实现复杂的网络协议栈

系统管理: 系统管理工具的消息传递

模块化处理: 数据流的模块化处理

STREAMS 架构概念

1
2
3
4
5
6
7
8
9
10
11
12
应用程序

STREAMS 接口 (putmsg/getmsg)

流首部 (Stream Head)

模块 1 → 模块 2 → 模块 3

驱动程序 (Driver)

硬件设备

虽然 getmsg 在现代 Linux 系统中使用较少,但了解这个概念有助于理解 Unix 系统的消息传递机制和 STREAMS 架构的设计思想。在实际开发中,建议使用 Linux 原生的 IPC 机制。

getpeername系统调用及示例

getpeername系统调用及示例

我们已经介绍了 getsockname,所以接下来应该介绍 getpeername。

1. 函数介绍

getpeername 是一个 Linux 系统调用,用于获取已连接套接字的对方(peer)的协议地址。这个地址包含了与本端套接字建立连接的那个远程主机的 IP 地址 和 端口号。

你可以把它想象成查看已接通电话的对方号码:

  • 你和朋友正在通话(套接字已连接)。

  • 你想知道现在和你通话的人的电话号码是多少(对方地址)。

  • getpeername 就是查看这个对方号码的功能。

这对于以下场景非常有用:

服务器: 服务器 accept 一个连接后,得到一个新的已连接套接字。服务器可以使用 getpeername 来获取是哪个客户端(IP 和端口)连接了进来。

客户端: 客户端在 connect 成功后,可以使用 getpeername 来确认它连接到了哪个服务器地址(IP 和端口),尤其是在连接时使用了域名,想确认解析后的具体 IP。

调试和日志: 在网络程序中记录连接信息时,getpeername 是获取对端地址的标准方法。

2. 函数原型

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

int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

3. 功能

  • 获取对端地址: 检索与已连接套接字 sockfd 关联的对方协议地址。

  • 填充结构体: 将获取到的地址信息填充到调用者提供的 struct sockaddr 结构体指针 addr 所指向的内存中。

  • 返回地址大小: 通过 addrlen 参数返回实际存储在 addr 中的地址结构的大小。

4. 参数

int sockfd: 这是一个有效的、已连接的套接字文件描述符。

  • 对于 TCP,这意味着已经成功调用了 connect(客户端)或 accept(服务器返回的新套接字)。

  • 对于 UDP,如果调用了 connect,也可以使用 getpeername。

  • 如果套接字未连接,调用会失败。

struct sockaddr *addr: 这是一个指向套接字地址结构的指针,用于接收对方的地址信息。

  • 调用者需要提供一个足够大的缓冲区(例如 struct sockaddr_in 或 struct sockaddr_in6)并将其地址传递给 addr。

socklen_t *addrlen: 这是一个指向 socklen_t 类型变量的指针。

  • 输入: 在调用 getpeername 时,这个变量必须被初始化为 addr 指向的缓冲区的大小(以字节为单位)。例如,如果 addr 指向 struct sockaddr_in,则 *addrlen 应初始化为 sizeof(struct sockaddr_in)。

  • 输出: getpeername 返回时,这个变量会被更新为实际存储在 addr 中的地址结构的大小。

5. 返回值

  • 成功时: 返回 0。同时,addr 指向的结构体被成功填充,*addrlen 被更新为实际地址大小。

  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF sockfd 无效,EINVAL addrlen 指针无效,ENOTSOCK sockfd 不是一个套接字,ENOTCONN 套接字未连接等)。

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

  • getsockname: 用于获取本地(本端)套接字的地址信息。

  • connect: 客户端使用此函数连接到服务器。连接成功后,可以使用 getpeername 获取服务器地址。

  • accept: 服务器使用此函数接受客户端连接,返回一个已连接的新套接字,可以使用 getpeername 获取客户端地址。

  • socket: 创建套接字。

7. 示例代码

示例 1:客户端使用 getpeername 确认服务器地址

这个例子演示了 TCP 客户端在连接到服务器后,如何使用 getpeername 来获取它所连接的服务器的地址信息。

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

#define SERVER_PORT 8093
#define SERVER_IP "127.0.0.1" // 可以换成域名测试,如 "localhost"

int main() {
int sock;
struct sockaddr_in server_addr, peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);

sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
printf("Client socket created (fd: %d)\n", sock);

// 配置服务器地址并连接
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
fprintf(stderr, "Invalid server address: %s\n", SERVER_IP);
close(sock);
exit(EXIT_FAILURE);
}

printf("Connecting to server %s:%d...\n", SERVER_IP, SERVER_PORT);
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect failed");
close(sock);
exit(EXIT_FAILURE);
}
printf("Connected to server successfully.\n");

// --- 关键: 使用 getpeername 获取服务器地址 ---
printf("\n--- Getting peer (server) name with getpeername ---\n");
// 重新初始化长度,以防被修改
peer_addr_len = sizeof(peer_addr);
if (getpeername(sock, (struct sockaddr *)&peer_addr, &peer_addr_len) == 0) {
printf("Connected to server at:\n");
printf(" Peer IP Address: %s\n", inet_ntoa(peer_addr.sin_addr));
printf(" Peer Port: %d\n", ntohs(peer_addr.sin_port));
} else {
perror("getpeername failed");
// 常见错误:ENOTCONN (如果套接字未连接)
}

// (对比) 使用 getsockname 获取自己的本地地址
struct sockaddr_in local_addr;
socklen_t local_addr_len = sizeof(local_addr);
if (getsockname(sock, (struct sockaddr *)&local_addr, &local_addr_len) == 0) {
printf("\nMy local address is:\n");
printf(" Local IP Address: %s\n", inet_ntoa(local_addr.sin_addr));
printf(" Local Port: %d\n", ntohs(local_addr.sin_port));
} else {
perror("getsockname failed");
}

printf("\nClient is connected and verified peer/local addresses.\n");

// ... 进行通信 ...

close(sock);
printf("Client socket closed.\n");
return 0;
}

代码解释:

创建 TCP 套接字。

配置服务器地址并调用 connect 连接。

关键步骤: 连接成功后,调用 getpeername(sock, (struct sockaddr *)&peer_addr, &peer_addr_len)。

  • sock: 已连接的套接字。

  • &peer_addr: 指向用于存储对端地址的 sockaddr_in 结构的指针。

  • &peer_addr_len: 指向 socklen_t 变量的指针,初始化为 sizeof(peer_addr)。

getpeername 成功后,打印出服务器的地址(IP 和端口)。

对比: 调用 getsockname 获取自己的本地地址。

最后关闭套接字。

示例 2:服务器使用 getpeername 获取客户端地址

这个例子演示了 TCP 服务器在 accept 一个连接后,如何使用 getpeername 来获取连接进来的客户端的地址信息。

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

#define PORT 8093
#define BACKLOG 10

void handle_client(int client_fd) {
struct sockaddr_in peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);

printf("Handling new client connection (fd: %d)\n", client_fd);

// --- 关键: 使用 getpeername 获取客户端地址 ---
if (getpeername(client_fd, (struct sockaddr *)&peer_addr, &peer_addr_len) == 0) {
printf(" Client connected from: %s:%d\n",
inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
} else {
perror(" getpeername on client socket failed");
}

// 为了演示,我们立即关闭连接
// 实际应用中,这里会进行数据读写
close(client_fd);
printf(" Closed connection to client.\n");
}

int main() {
int server_fd, client_fd;
struct sockaddr_in address;
socklen_t client_addr_len;
struct sockaddr_in client_address;
int opt = 1;

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

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

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

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

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

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

// 演示接受几个连接
for (int i = 0; i < 3; ++i) {
printf("\n--- Waiting for connection #%d ---\n", i + 1);
client_addr_len = sizeof(client_address);
client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}

printf("New connection accepted (fd: %d)\n", client_fd);
// accept 返回的 client_fd 已经包含了客户端地址信息,
// 我们也可以用 getpeername 再次确认
handle_client(client_fd);
}

close(server_fd);
printf("Server socket closed.\n");
return 0;
}

如何测试:

在一个终端编译并运行服务器:gcc -o getpeername_server getpeername_server.c ./getpeername_server

在另外几个终端运行客户端(可以多次运行):gcc -o getpeername_client getpeername_client.c ./getpeername_client

代码解释:

创建、绑定、监听标准的 TCP 服务器套接字。

进入一个循环,accept 三个客户端连接。

每次 accept 成功后,调用 handle_client(client_fd)。

在 handle_client 函数中:

  • 关键步骤: 调用 getpeername(client_fd, …)。

  • client_fd 是 accept 返回的、与特定客户端关联的已连接套接字。

  • getpeername 会返回该客户端的 IP 地址和端口号。

  • 打印出客户端的地址信息。

函数结束时关闭与该客户端的连接。

示例 3:对比 getsockname 和 getpeername

这个例子在一个已连接的套接字上同时使用 getsockname 和 getpeername,清晰地展示它们的区别。

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

#define SERVER_PORT 8094
#define SERVER_IP "127.0.0.1"

void print_socket_addresses(int sock, const char* role) {
struct sockaddr_in local_addr, peer_addr;
socklen_t addr_len;

printf("--- Addresses for %s socket (fd: %d) ---\n", role, sock);

// 获取本地地址
addr_len = sizeof(local_addr);
if (getsockname(sock, (struct sockaddr *)&local_addr, &addr_len) == 0) {
printf(" Local Address : %s:%d\n",
inet_ntoa(local_addr.sin_addr), ntohs(local_addr.sin_port));
} else {
perror(" getsockname failed");
}

// 获取对端地址
addr_len = sizeof(peer_addr);
if (getpeername(sock, (struct sockaddr *)&peer_addr, &addr_len) == 0) {
printf(" Peer Address : %s:%d\n",
inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
} else {
perror(" getpeername failed");
// 如果套接字未连接,会打印错误,如 "Transport endpoint is not connected"
}
printf("----------------------------------------\n");
}

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

// --- 服务器端 ---
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == 0) {
perror("server socket failed");
exit(EXIT_FAILURE);
}

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

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

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

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

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

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

memset(&client_addr, 0, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &client_addr.sin_addr) <= 0) {
fprintf(stderr, "Invalid server IP\n");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}

printf("\nClient connecting to server...\n");
if (connect(client_sock, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
perror("client connect failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}

// --- 连接建立后,检查客户端套接字 ---
print_socket_addresses(client_sock, "Client");

// --- 服务器接受连接 ---
int accepted_sock = accept(server_fd, NULL, NULL); // 忽略客户端地址
if (accepted_sock < 0) {
perror("server accept failed");
close(server_fd);
close(client_sock);
exit(EXIT_FAILURE);
}

// --- 检查服务器端的已连接套接字 ---
print_socket_addresses(accepted_sock, "Server-Accepted");

// 客户端和服务器的本地地址应该等于对方的对端地址
printf("\nNote:\n");
printf("- Client's Local Address should equal Server's Peer Address.\n");
printf("- Server's Local Address should equal Client's Peer Address.\n");

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

return 0;
}

代码解释:

程序同时扮演服务器和客户端的角色。

设置服务器套接字并开始监听。

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

定义一个 print_socket_addresses 函数,它接受一个套接字和一个角色名称(“Client” 或 “Server-Accepted”)。

在该函数内部:

  • 调用 getsockname 获取并打印本地地址。

  • 调用 getpeername 获取并打印对端地址。

在 main 函数中:

  • 客户端连接成功后,调用 print_socket_addresses(client_sock, “Client”)。

  • 服务器 accept 连接后,调用 print_socket_addresses(accepted_sock, “Server-Accepted”)。

通过打印的地址可以清楚地看到:

  • 客户端的本地地址 == 服务器 accept 套接字的对端地址。

  • 服务器的本地地址 == 客户端的对端地址。

重要提示与注意事项:

仅对已连接套接字有效: getpeername 只能用于已连接的套接字。对于未连接的套接字(如刚创建的套接字,或未 connect 的 UDP 套接字),调用会失败,errno 通常为 ENOTCONN。

区分 getsockname 和 getpeername:

  • getsockname: 获取本地地址(我的地址)。

  • getpeername: 获取对端地址(对方的地址)。

addrlen 的初始化: 与 getsockname 一样,在调用前,必须将 *addrlen 初始化为目标缓冲区的大小。调用后,它会被更新为实际的地址结构大小。

服务器常用: 服务器在 accept 后,经常使用 getpeername 来记录或显示是哪个客户端连接了进来。

客户端确认: 客户端可以用它来确认连接的服务器地址,尤其是在使用域名时。

错误处理: 始终检查返回值。最常见的错误是 ENOTCONN(套接字未连接)。

总结:

getpeername 是一个用于获取已连接套接字对端地址信息的系统调用。它与 getsockname 相辅相成,分别用于查询连接两端的地址。在服务器记录客户端信息和客户端确认服务器信息等场景中非常有用。理解其参数和使用条件对于进行有效的网络编程至关重要。

https://blog.csdn.net/zidier215/article/details/151373575?sharetype=blogdetail&sharerId=151373575&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

getppid系统调用及示例

getppid - 获取父进程ID

getppid是Linux系统调用,用于获取当前进程的父进程ID(PPID)。该函数无需参数,总是成功返回父进程ID,在进程管理和监控中非常有用。示例代码展示了基础用法、父子进程关系、孤儿进程现象以及构建进程树结构,通过getpid()、fork()等相关函数配合使用,可完整呈现Unix/Linux的进程层级关系。当父进程终止后,子进程PPID会变为1(init进程),成为孤儿进程。

1. 函数介绍

getppid 是一个 Linux 系统调用,用于获取当前进程的父进程 ID(Parent Process ID)。每个进程(除了初始进程)都有一个父进程,父进程 ID 是进程管理、进程监控和进程间通信的重要信息。

在 Unix/Linux 系统中,进程以树状结构组织,getppid 提供了访问这种父子关系的方法,对于进程监控、调试和管理工具非常有用。

2. 函数原型

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

pid_t getppid(void);

3. 功能

返回当前进程的父进程 ID。这是一个只读操作,不会修改任何系统状态。

4. 参数

  • 无参数

5. 返回值

  • 成功时:返回父进程 ID(pid_t 类型)

  • 不会失败:该系统调用总是成功返回

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

  • getpid(): 获取当前进程 ID

  • getpgid(): 获取进程组 ID

  • getpgrp(): 获取进程组 ID(当前进程)

  • getsid(): 获取会话 ID

  • fork(): 创建新进程

  • wait(), waitpid(): 等待子进程

  • kill(): 向进程发送信号

  • prctl(): 进程控制

  • /proc/[pid]/stat: 查看进程状态信息

7. 示例代码

示例1:基本使用 - 获取进程父子关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
pid_t my_pid, parent_pid;

printf("=== 进程父子关系信息 ===\n");

// 获取当前进程 ID
my_pid = getpid();
printf("当前进程 ID: %d\n", my_pid);

// 获取父进程 ID
parent_pid = getppid();
printf("父进程 ID: %d\n", parent_pid);

// 获取父进程的父进程 ID(祖父进程)
// 注意:我们无法直接获取祖父进程 ID,需要通过其他方式

printf("进程关系: %d -> %d (父 -> 子)\n", parent_pid, my_pid);

// 检查特殊情况
if (parent_pid == 1) {
printf("注意: 父进程是 init 进程 (PID 1)\n");
} else if (parent_pid == 0) {
printf("注意: 父进程是调度进程 (内核进程)\n");
}

return 0;
}

示例2:父子进程关系演示

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

void print_process_info(const char *label) {
printf("%s:\n", label);
printf(" 进程 ID: %d\n", getpid());
printf(" 父进程 ID: %d\n", getppid());
printf(" 进程组 ID: %d\n", getpgrp());
printf("\n");
}

int main() {
pid_t child_pid;

printf("=== 父子进程关系演示 ===\n");

// 显示父进程信息
print_process_info("父进程(创建子进程前)");

// 创建子进程
child_pid = fork();

if (child_pid == -1) {
perror("fork 失败");
exit(EXIT_FAILURE);
}

if (child_pid == 0) {
// 子进程
print_process_info("子进程");

// 子进程睡眠一段时间
printf("子进程睡眠 3 秒...\n");
sleep(3);

// 再次检查父进程 ID(父进程可能已经结束)
printf("子进程睡眠结束,再次检查父进程 ID:\n");
printf(" 当前进程 ID: %d\n", getpid());
printf(" 父进程 ID: %d\n", getppid());

if (getppid() == 1) {
printf(" 父进程已结束,现在由 init 进程收养\n");
}

} else {
// 父进程
printf("父进程创建了子进程,子进程 ID: %d\n", child_pid);
print_process_info("父进程(创建子进程后)");

// 父进程立即结束
printf("父进程即将结束...\n");
exit(0);
}

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

int main() {
pid_t child_pid;

printf("=== 孤儿进程演示 ===\n");
printf("父进程 ID: %d\n", getpid());

child_pid = fork();

if (child_pid == -1) {
perror("fork 失败");
exit(EXIT_FAILURE);
}

if (child_pid == 0) {
// 子进程
printf("子进程启动,PID: %d, PPID: %d\n", getpid(), getppid());

// 子进程循环检查父进程状态
for (int i = 0; i < 10; i++) {
printf("子进程 %d: 父进程 ID = %d\n", getpid(), getppid());
sleep(2);
}

printf("子进程 %d 结束\n", getpid());

} else {
// 父进程
printf("父进程创建了子进程 %d\n", child_pid);
printf("父进程即将结束,子进程将成为孤儿进程\n");

// 父进程很快结束
sleep(1);
printf("父进程 %d 结束\n", getpid());
exit(0);
}

return 0;
}

示例4:进程树构建和分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

typedef struct {
pid_t pid;
pid_t ppid;
pid_t pgid;
int generation;
char name&#91;32];
} process_info_t;

void collect_process_info(process_info_t *info, int generation, const char *name) {
info->pid = getpid();
info->ppid = getppid();
info->pgid = getpgrp();
info->generation = generation;
snprintf(info->name, sizeof(info->name), "%s", name);
}

void print_process_tree(process_info_t *processes, int count) {
printf("\n=== 进程树结构 ===\n");
printf("%-8s %-8s %-8s %-12s %s\n", "PID", "PPID", "PGID", "代数", "名称");
printf("%-8s %-8s %-8s %-12s %s\n", "---", "----", "----", "----", "----");

for (int i = 0; i < count; i++) {
printf("%-8d %-8d %-8d %-12d %s\n",
processes&#91;i].pid,
processes&#91;i].ppid,
processes&#91;i].pgid,
processes&#91;i].generation,
processes&#91;i].name);
}
}

int main() {
pid_t pid1, pid2, pid3;
process_info_t processes&#91;4];
int process_count = 0;

printf("=== 复杂进程树演示 ===\n");

// 收集根进程信息
collect_process_info(&processes&#91;process_count++], 0, "根进程");

// 创建第一层子进程
pid1 = fork();
if (pid1 == 0) {
// 第一个子进程
collect_process_info(&processes&#91;process_count++], 1, "子进程1");

// 第一个子进程创建孙子进程
pid2 = fork();
if (pid2 == 0) {
// 孙子进程1
collect_process_info(&processes&#91;process_count++], 2, "孙子进程1");
sleep(3);
exit(0);
}

// 第一个子进程再创建另一个孙子进程
pid3 = fork();
if (pid3 == 0) {
// 孙子进程2
collect_process_info(&processes&#91;process_count++], 2, "孙子进程2");
sleep(3);
exit(0);
}

// 等待孙子进程结束
wait(NULL);
wait(NULL);
exit(0);
}

// 等待第一层子进程结束
wait(NULL);

// 在实际应用中,这里需要通过 IPC 机制收集所有进程信息
// 这里为了演示,只显示当前进程信息
printf("当前进程信息:\n");
printf(" PID: %d\n", getpid());
printf(" PPID: %d\n", getppid());
printf(" PGID: %d\n", getpgrp());

return 0;
}

示例5:进程监控和管理工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

// 检查父进程是否还存在
int is_parent_alive() {
pid_t ppid = getppid();

// 特殊情况:父进程是 init (PID 1) 或内核进程 (PID 0)
if (ppid == 1 || ppid == 0) {
return 1; // 认为父进程"存在"(虽然可能已结束)
}

// 尝试向父进程发送 0 信号来检查是否存在
if (kill(ppid, 0) == 0) {
return 1; // 父进程存在
} else {
if (errno == ESRCH) {
return 0; // 父进程不存在
} else {
return 1; // 其他错误,假设父进程存在
}
}
}

// 监控父进程状态
void monitor_parent_status() {
pid_t original_ppid = getppid();
pid_t current_ppid;

printf("开始监控父进程状态...\n");
printf("原始父进程 ID: %d\n", original_ppid);

while (1) {
current_ppid = getppid();

if (current_ppid != original_ppid) {
printf("父进程 ID 发生变化: %d -> %d\n", original_ppid, current_ppid);

if (current_ppid == 1) {
printf("进程已被 init 进程收养\n");
break;
}

original_ppid = current_ppid;
}

if (!is_parent_alive()) {
printf("检测到父进程可能已结束\n");
break;
}

sleep(1);
}
}

// 守护进程检查
int is_daemon_process() {
pid_t ppid = getppid();

// 守护进程的特征
if (ppid == 1) {
return 1; // 由 init 进程管理
}

// 检查是否在后台运行
if (getpgrp() != tcgetpgrp(STDIN_FILENO)) {
return 1; // 不在前台进程组
}

return 0;
}

int main() {
printf("=== 进程状态监控工具 ===\n");

printf("当前进程信息:\n");
printf(" PID: %d\n", getpid());
printf(" PPID: %d\n", getppid());
printf(" PGID: %d\n", getpgrp());

// 检查是否为守护进程
if (is_daemon_process()) {
printf("✓ 当前进程是守护进程或后台进程\n");
} else {
printf("✗ 当前进程是前台进程\n");
}

// 检查父进程状态
if (is_parent_alive()) {
printf("✓ 父进程存在\n");
} else {
printf("✗ 父进程不存在\n");
}

// 如果需要,可以启动监控
char choice;
printf("\n是否启动父进程监控? (y/N): ");
if (scanf("%c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
monitor_parent_status();
}

return 0;
}

8. 特殊父进程 ID

1
2
3
4
5
6
7
8
9
10
11
12
// 特殊的父进程 ID 值
if (ppid == 0) {
// 内核进程或调度进程
printf("父进程是内核进程\n");
} else if (ppid == 1) {
// init 进程或 systemd
printf("父进程是 init 进程\n");
} else if (ppid == getppid()) {
// 自己是父进程(不太可能)
printf("异常:自己是自己的父进程\n");
}

9. 实际应用场景

场景1:守护进程实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void create_daemon() {
pid_t pid = fork();
if (pid == 0) {
// 第一次 fork 的子进程
setsid(); // 创建新会话

pid = fork();
if (pid == 0) {
// 第二次 fork 的子进程(真正的守护进程)
// 检查父进程是否为 init
if (getppid() == 1) {
printf("成功创建守护进程\n");
}
} else {
exit(0); // 第一次 fork 的子进程退出
}
} else {
wait(NULL); // 等待第一次 fork 的子进程退出
exit(0); // 父进程退出
}
}

场景2:进程监控

1
2
3
4
5
6
7
int monitor_process_tree(pid_t target_pid) {
// 实现进程树监控逻辑
pid_t current_ppid = getppid();
// ... 监控逻辑
return 0;
}

场景3:安全检查

1
2
3
4
5
6
7
8
9
10
11
12
int check_parent_process() {
pid_t ppid = getppid();

// 检查父进程是否为预期的进程
if (ppid != expected_parent_pid) {
syslog(LOG_WARNING, "父进程异常: %d", ppid);
return -1;
}

return 0;
}

10. 注意事项

使用 getppid 时需要注意:

孤儿进程: 父进程结束后,子进程被 init 进程收养(PPID 变为 1)

竞态条件: 父进程可能在检查期间结束

权限问题: 通常可以访问父进程信息,但在某些安全环境中可能受限

跨平台兼容: 在所有 Unix-like 系统中都可用

性能考虑: 调用成本很低,可以频繁使用

11. 进程生命周期中的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 进程生命周期中 PPID 的变化示例
void demonstrate_ppid_changes() {
printf("进程生命周期 PPID 变化:\n");

pid_t original_ppid = getppid();
printf("1. 启动时 PPID: %d\n", original_ppid);

// fork 后子进程的 PPID
pid_t child = fork();
if (child == 0) {
printf("2. 子进程 PPID: %d\n", getppid());
// 如果父进程结束,PPID 会变为 1
}
}

总结

getppid 是一个简单但重要的系统调用,用于获取当前进程的父进程 ID。关键要点:

  1. 总是成功: 不会失败,总是返回父进程 ID2. 进程管理: 是进程管理和监控的基础信息3. 孤儿处理: 理解父进程结束后的孤儿进程处理机制4. 安全相关: 可用于进程身份验证和安全检查5. 系统工具: 广泛用于 ps、top 等系统工具

在编写需要了解进程关系的程序时,getppid 是必不可少的工具函数,它为程序提供了进程层次结构的重要信息。

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

getpmsg系统调用及示例

getpmsg 函数详解

  1. 函数介绍

getpmsg 是 System V STREAMS 接口中的一个函数,用于从 STREAMS 设备或管道中接收带优先级的消息。可以把 STREAMS 想象成一个”智能分拣系统”——消息根据优先级被分类处理,getpmsg 就是从这个系统中按指定优先级取下消息的工具。

与 getmsg 不同,getpmsg 提供了更精细的优先级控制,允许你指定要接收的消息类型(普通优先级、高优先级等),就像邮件分拣系统可以按紧急程度分拣邮件一样。

getpmsg 是 System V STREAMS 接口中的函数,用于从 STREAMS 设备接收带优先级的消息。该函数允许按优先级(band)接收消息,支持多种接收模式(MSG_HIPRI/MSG_ANY/MSG_BAND)。参数包括文件描述符、控制/数据缓冲区结构(strbuf)以及优先级/标志指针。函数返回0表示成功,-1表示失败并设置errno。示例代码展示了基础用法和模拟实现,但需注意Linux系统对STREAMS的支持有限。关联函数包括putpmsg、getmsg等,常用于消息优先级处理场景。

  1. 函数原型
1
2
3
4
5
#include <stropts.h>

int getpmsg(int fildes, struct strbuf *ctlptr, struct strbuf *dataptr,
int *bandp, int *flagsp);

  1. 功能

getpmsg 函数用于从 STREAMS 文件描述符中接收带优先级的消息。它可以接收指定优先级(band)的消息,并且可以控制接收行为。

  1. 参数
  • fildes: STREAMS 设备或管道的文件描述符

  • ctlptr: 指向 strbuf 结构体的指针,用于接收控制信息

  • dataptr: 指向 strbuf 结构体的指针,用于接收数据信息

  • bandp: 指向优先级(band)的指针

  • flagsp: 指向标志的指针,用于指定接收模式和返回消息类型

  1. strbuf 结构体
1
2
3
4
5
6
struct strbuf {
int maxlen; /* 缓冲区最大长度 */
int len; /* 实际数据长度 */
char *buf; /* 指向缓冲区的指针 */
};

  1. band 和 flags 参数说明

band 参数(优先级)

  • 0-255: 消息优先级级别,数值越高优先级越高

  • 用于区分不同类型的消息

flags 参数(输入标志)

  • MSG_HIPRI: 接收高优先级消息

  • MSG_ANY: 接收任何优先级的消息

  • MSG_BAND: 接收指定优先级的消息

flags 参数(输出标志)

  • MSG_HIPRI: 接收到高优先级消息

  • MSG_BAND: 接收到指定优先级消息

  • MSG_MORECTL: 控制部分还有更多数据

  • MSG_MOREDATA: 数据部分还有更多数据

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

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

常见错误码:

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

  • EINVAL: 参数无效

  • EIO: I/O 错误

  • ENOSTR: fildes 不是 STREAMS 设备

  • ENOSR: 没有足够的 STREAMS 资源

  • EAGAIN: 非阻塞模式下无数据可读

  1. 相似函数或关联函数
  • putpmsg: 发送带优先级的消息

  • getmsg: 获取普通消息(不区分优先级)

  • putmsg: 发送普通消息

  • ioctl: 控制 STREAMS 设备

  • poll/select: 检查 STREAMS 文件描述符状态

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

// 注意:这个示例在大多数 Linux 系统上可能无法运行
// 因为 Linux 不完全支持 STREAMS

int main() {
int fd;
struct strbuf ctlbuf, databuf;
char ctl_data&#91;256], data_buf&#91;1024];
int band, flags;

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

// 初始化缓冲区结构
ctlbuf.maxlen = sizeof(ctl_data);
ctlbuf.buf = ctl_data;
ctlbuf.len = 0;

databuf.maxlen = sizeof(data_buf);
databuf.buf = data_buf;
databuf.len = 0;

band = 0; // 优先级
flags = 0; // 接收标志

printf("注意: getpmsg 主要用于 STREAMS 系统\n");
printf("在大多数 Linux 系统上可能不可用\n\n");

printf("参数设置:\n");
printf(" 控制缓冲区最大长度: %d\n", ctlbuf.maxlen);
printf(" 数据缓冲区最大长度: %d\n", databuf.maxlen);
printf(" 优先级 (band): %d\n", band);
printf(" 标志: %d\n", flags);

printf("\n如果在支持 STREAMS 的系统上,可以这样调用:\n");
printf("result = getpmsg(fd, &ctlbuf, &databuf, &band, &flags);\n");

return 0;
}

示例2:模拟 STREAMS 优先级消息处理

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

// 模拟的 STREAMS 消息结构
struct simulated_pmsg {
int band; // 消息优先级 (0-255)
int control_len; // 控制数据长度
char control_data&#91;256]; // 控制数据
int data_len; // 数据长度
char data&#91;1024]; // 实际数据
time_t timestamp; // 时间戳
};

// 模拟的消息队列
struct priority_message_queue {
int count;
struct simulated_pmsg messages&#91;20];
};

// 全局消息队列
struct priority_message_queue msg_queue = {0};

// 模拟的 strbuf 结构
struct simulated_strbuf {
int maxlen;
int len;
char *buf;
};

// 标志定义(模拟)
#define MSG_HIPRI 0x01
#define MSG_ANY 0x02
#define MSG_BAND 0x04
#define MSG_MORECTL 0x08
#define MSG_MOREDATA 0x10

// 向队列添加优先级消息
int add_priority_message(int band, const char *control, const char *data) {
if (msg_queue.count >= 20) {
printf("消息队列已满\n");
return -1;
}

int index = msg_queue.count++;
msg_queue.messages&#91;index].band = band;
msg_queue.messages&#91;index].timestamp = time(NULL);

if (control) {
msg_queue.messages&#91;index].control_len = strlen(control) + 1;
strncpy(msg_queue.messages&#91;index].control_data, control, 255);
msg_queue.messages&#91;index].control_data&#91;255] = '\0';
} else {
msg_queue.messages&#91;index].control_len = 0;
msg_queue.messages&#91;index].control_data&#91;0] = '\0';
}

if (data) {
msg_queue.messages&#91;index].data_len = strlen(data) + 1;
strncpy(msg_queue.messages&#91;index].data, data, 1023);
msg_queue.messages&#91;index].data&#91;1023] = '\0';
} else {
msg_queue.messages&#91;index].data_len = 0;
msg_queue.messages&#91;index].data&#91;0] = '\0';
}

printf("添加优先级消息: band=%d, 控制='%s', 数据='%s'\n",
band, control ? control : "无", data ? data : "无");

return 0;
}

// 模拟的 getpmsg 实现
int simulated_getpmsg(struct simulated_strbuf *ctlptr,
struct simulated_strbuf *dataptr,
int *bandp, int *flagsp) {

if (msg_queue.count == 0) {
printf("消息队列为空\n");
return -1;
}

int msg_index = -1;
int target_band = *bandp;
int flags = *flagsp;

printf("查找消息: band=%d, flags=0x%x\n", target_band, flags);

// 根据标志查找消息
if (flags & MSG_HIPRI) {
// 查找最高优先级消息
int max_band = -1;
for (int i = 0; i < msg_queue.count; i++) {
if (msg_queue.messages&#91;i].band > max_band) {
max_band = msg_queue.messages&#91;i].band;
msg_index = i;
}
}
printf("查找最高优先级消息: band=%d\n", max_band);
}
else if (flags & MSG_BAND) {
// 查找指定优先级消息
for (int i = 0; i < msg_queue.count; i++) {
if (msg_queue.messages&#91;i].band == target_band) {
msg_index = i;
break;
}
}
printf("查找指定优先级消息: band=%d\n", target_band);
}
else if (flags & MSG_ANY) {
// 查找任何消息(通常按顺序)
msg_index = 0;
printf("查找任意消息\n");
}
else {
// 默认行为:查找最高优先级
int max_band = -1;
for (int i = 0; i < msg_queue.count; i++) {
if (msg_queue.messages&#91;i].band > max_band) {
max_band = msg_queue.messages&#91;i].band;
msg_index = i;
}
}
printf("默认查找最高优先级消息: band=%d\n", max_band);
}

if (msg_index == -1) {
printf("未找到符合条件的消息\n");
return -1;
}

// 获取消息
struct simulated_pmsg *msg = &msg_queue.messages&#91;msg_index];

// 复制控制数据
if (ctlptr && ctlptr->buf) {
int copy_len = (msg->control_len < ctlptr->maxlen) ?
msg->control_len : ctlptr->maxlen;
memcpy(ctlptr->buf, msg->control_data, copy_len);
ctlptr->len = copy_len;
printf("复制控制数据: %d 字节\n", copy_len);
}

// 复制数据
if (dataptr && dataptr->buf) {
int copy_len = (msg->data_len < dataptr->maxlen) ?
msg->data_len : dataptr->maxlen;
memcpy(dataptr->buf, msg->data, copy_len);
dataptr->len = copy_len;
printf("复制数据: %d 字节\n", copy_len);
}

// 更新返回参数
*bandp = msg->band;
*flagsp = (msg->band > 100) ? MSG_HIPRI : 0; // 模拟高优先级

// 从队列中移除消息
for (int i = msg_index; i < msg_queue.count - 1; i++) {
msg_queue.messages&#91;i] = msg_queue.messages&#91;i + 1];
}
msg_queue.count--;

printf("成功接收消息: band=%d\n", *bandp);
return 0;
}

// 显示队列状态
void show_priority_queue() {
printf("\n=== 优先级消息队列状态 ===\n");
printf("消息数量: %d\n", msg_queue.count);

for (int i = 0; i < msg_queue.count; i++) {
printf("消息 %d: band=%d, 时间=%s",
i, msg_queue.messages&#91;i].band,
ctime(&msg_queue.messages&#91;i].timestamp));
printf(" 控制: %s\n", msg_queue.messages&#91;i].control_data);
printf(" 数据: %s\n", msg_queue.messages&#91;i].data);
}
}

int main() {
struct simulated_strbuf ctlbuf, databuf;
char ctl_buffer&#91;256], data_buffer&#91;1024];
int band, flags;
int result;

printf("=== STREAMS 优先级消息处理模拟 ===\n\n");

// 初始化缓冲区
ctlbuf.maxlen = sizeof(ctl_buffer);
ctlbuf.buf = ctl_buffer;
ctlbuf.len = 0;

databuf.maxlen = sizeof(data_buffer);
databuf.buf = data_buffer;
databuf.len = 0;

// 添加测试消息(不同优先级)
printf("添加测试消息...\n");
add_priority_message(50, "NORMAL_CTL", "普通优先级消息");
add_priority_message(150, "HIGH_CTL", "高优先级消息");
add_priority_message(25, "LOW_CTL", "低优先级消息");
add_priority_message(200, "CRITICAL_CTL", "关键优先级消息");
add_priority_message(75, "MEDIUM_CTL", "中等优先级消息");

show_priority_queue();

// 测试1: 接收最高优先级消息
printf("\n--- 测试1: 接收最高优先级消息 ---\n");
band = 0;
flags = MSG_HIPRI;
result = simulated_getpmsg(&ctlbuf, &databuf, &band, &flags);

if (result == 0) {
printf("成功接收最高优先级消息:\n");
printf(" 优先级: %d\n", band);
printf(" 控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
printf(" 数据: %.*s\n", databuf.len, databuf.buf);
printf(" 消息标志: 0x%x\n", flags);
}

// 测试2: 接收指定优先级消息
printf("\n--- 测试2: 接收指定优先级消息 (band=75) ---\n");
band = 75;
flags = MSG_BAND;
result = simulated_getpmsg(&ctlbuf, &databuf, &band, &flags);

if (result == 0) {
printf("成功接收指定优先级消息:\n");
printf(" 优先级: %d\n", band);
printf(" 控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
printf(" 数据: %.*s\n", databuf.len, databuf.buf);
printf(" 消息标志: 0x%x\n", flags);
}

// 测试3: 接收任意消息
printf("\n--- 测试3: 接收任意消息 ---\n");
band = 0;
flags = MSG_ANY;
result = simulated_getpmsg(&ctlbuf, &databuf, &band, &flags);

if (result == 0) {
printf("成功接收任意消息:\n");
printf(" 优先级: %d\n", band);
printf(" 控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
printf(" 数据: %.*s\n", databuf.len, databuf.buf);
printf(" 消息标志: 0x%x\n", flags);
}

show_priority_queue();

printf("\n=== 优先级消息概念说明 ===\n");
printf("STREAMS 优先级消息系统:\n");
printf("1. 消息按优先级 (band) 分类\n");
printf("2. 优先级范围: 0-255 (数值越高优先级越高)\n");
printf("3. 可以按优先级选择性接收消息\n");
printf("4. 支持高优先级消息抢占\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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// 消息类型定义
#define BAND_LOW 25 // 低优先级
#define BAND_NORMAL 50 // 普通优先级
#define BAND_MEDIUM 100 // 中等优先级
#define BAND_HIGH 150 // 高优先级
#define BAND_CRITICAL 200 // 关键优先级

// 消息队列管理器
struct message_manager {
int total_messages;
int processed_messages;
int dropped_messages;
struct {
int band;
int msg_id;
char type&#91;32];
char content&#91;256];
time_t created_time;
time_t processed_time;
} queue&#91;50];
};

// 全局消息管理器
struct message_manager msg_mgr = {0};

// 标志定义
#define MSG_HIPRI 0x01
#define MSG_ANY 0x02
#define MSG_BAND 0x04

// 模拟的 strbuf 结构
struct stream_buffer {
int maxlen;
int len;
char *buf;
};

// 添加消息到队列
int queue_message(int band, const char *type, const char *content) {
if (msg_mgr.total_messages >= 50) {
msg_mgr.dropped_messages++;
printf("警告: 消息队列已满,丢弃消息\n");
return -1;
}

int index = msg_mgr.total_messages++;
msg_mgr.queue&#91;index].band = band;
msg_mgr.queue&#91;index].msg_id = msg_mgr.total_messages;
strncpy(msg_mgr.queue&#91;index].type, type, 31);
msg_mgr.queue&#91;index].type&#91;31] = '\0';
strncpy(msg_mgr.queue&#91;index].content, content, 255);
msg_mgr.queue&#91;index].content&#91;255] = '\0';
msg_mgr.queue&#91;index].created_time = time(NULL);
msg_mgr.queue&#91;index].processed_time = 0;

printf("入队消息 #%d: band=%d, 类型=%s\n",
msg_mgr.queue&#91;index].msg_id, band, type);

return 0;
}

// 模拟的 getpmsg 实现
int advanced_getpmsg(struct stream_buffer *ctlptr,
struct stream_buffer *dataptr,
int *bandp, int *flagsp) {

if (msg_mgr.total_messages == 0) {
return -1; // 队列为空
}

int msg_index = -1;
int target_band = *bandp;
int flags = *flagsp;

// 根据标志选择消息
if (flags & MSG_HIPRI) {
// 查找最高优先级消息
int max_band = -1;
for (int i = 0; i < msg_mgr.total_messages; i++) {
if (msg_mgr.queue&#91;i].band > max_band) {
max_band = msg_mgr.queue&#91;i].band;
msg_index = i;
}
}
}
else if (flags & MSG_BAND) {
// 查找指定优先级消息
for (int i = 0; i < msg_mgr.total_messages; i++) {
if (msg_mgr.queue&#91;i].band == target_band) {
msg_index = i;
break;
}
}
}
else {
// 默认:按顺序处理
msg_index = 0;
}

if (msg_index == -1) {
return -1; // 未找到消息
}

// 处理消息
struct {
int band;
int msg_id;
char type&#91;32];
char content&#91;256];
time_t created_time;
time_t processed_time;
} *msg = &msg_mgr.queue&#91;msg_index];

msg->processed_time = time(NULL);
msg_mgr.processed_messages++;

// 准备返回数据
if (ctlptr && ctlptr->buf) {
char ctl_info&#91;256];
snprintf(ctl_info, sizeof(ctl_info),
"MSG_ID=%d,BAND=%d,TYPE=%s",
msg->msg_id, msg->band, msg->type);

int copy_len = (strlen(ctl_info) + 1 < ctlptr->maxlen) ?
strlen(ctl_info) + 1 : ctlptr->maxlen;
memcpy(ctlptr->buf, ctl_info, copy_len - 1);
ctlptr->buf&#91;copy_len - 1] = '\0';
ctlptr->len = copy_len - 1;
}

if (dataptr && dataptr->buf) {
int copy_len = (strlen(msg->content) + 1 < dataptr->maxlen) ?
strlen(msg->content) + 1 : dataptr->maxlen;
memcpy(dataptr->buf, msg->content, copy_len - 1);
dataptr->buf&#91;copy_len - 1] = '\0';
dataptr->len = copy_len - 1;
}

// 更新返回参数
*bandp = msg->band;
*flagsp = (msg->band >= BAND_HIGH) ? MSG_HIPRI : 0;

// 从队列中移除消息
for (int i = msg_index; i < msg_mgr.total_messages - 1; i++) {
msg_mgr.queue&#91;i] = msg_mgr.queue&#91;i + 1];
}
msg_mgr.total_messages--;

return 0;
}

// 显示系统统计
void show_system_stats() {
printf("\n=== 消息系统统计 ===\n");
printf("总消息数: %d\n", msg_mgr.total_messages);
printf("已处理消息: %d\n", msg_mgr.processed_messages);
printf("丢弃消息: %d\n", msg_mgr.dropped_messages);
printf("队列中消息: %d\n", msg_mgr.total_messages);
}

// 显示队列内容
void show_queue_contents() {
printf("\n=== 队列内容 ===\n");
if (msg_mgr.total_messages == 0) {
printf("队列为空\n");
return;
}

printf("优先级 ID 类型 内容\n");
printf("-------- ---- -------------- ------------------------\n");

for (int i = 0; i < msg_mgr.total_messages; i++) {
printf("%-8d %-4d %-14s %s\n",
msg_mgr.queue&#91;i].band,
msg_mgr.queue&#91;i].msg_id,
msg_mgr.queue&#91;i].type,
msg_mgr.queue&#91;i].content);
}
}

// 按优先级分类统计
void show_priority_statistics() {
int band_counts&#91;5] = {0}; // 低、普通、中等、高、关键

for (int i = 0; i < msg_mgr.total_messages; i++) {
int band = msg_mgr.queue&#91;i].band;
if (band <= BAND_LOW) band_counts&#91;0]++;
else if (band <= BAND_NORMAL) band_counts&#91;1]++;
else if (band <= BAND_MEDIUM) band_counts&#91;2]++;
else if (band <= BAND_HIGH) band_counts&#91;3]++;
else band_counts&#91;4]++;
}

printf("\n=== 优先级统计 ===\n");
printf("低优先级 (0-25): %d 条消息\n", band_counts&#91;0]);
printf("普通优先级 (26-50): %d 条消息\n", band_counts&#91;1]);
printf("中等优先级 (51-100): %d 条消息\n", band_counts&#91;2]);
printf("高优先级 (101-150): %d 条消息\n", band_counts&#91;3]);
printf("关键优先级 (151-255): %d 条消息\n", band_counts&#91;4]);
}

int main() {
struct stream_buffer ctlbuf, databuf;
char ctl_buffer&#91;256], data_buffer&#91;1024];
int band, flags;
int result;

printf("=== 高级优先级消息管理系统 ===\n\n");

// 初始化缓冲区
ctlbuf.maxlen = sizeof(ctl_buffer);
ctlbuf.buf = ctl_buffer;
ctlbuf.len = 0;

databuf.maxlen = sizeof(data_buffer);
databuf.buf = data_buffer;
databuf.len = 0;

// 添加各种优先级的消息
printf("初始化消息队列...\n");
queue_message(BAND_CRITICAL, "ALERT", "系统紧急告警:磁盘空间不足");
queue_message(BAND_HIGH, "ERROR", "应用程序错误:数据库连接失败");
queue_message(BAND_MEDIUM, "WARNING", "系统警告:CPU使用率过高");
queue_message(BAND_NORMAL, "INFO", "用户登录成功");
queue_message(BAND_LOW, "DEBUG", "调试信息:函数调用跟踪");
queue_message(BAND_CRITICAL, "ALERT", "安全告警:多次登录失败");
queue_message(BAND_HIGH, "ERROR", "网络错误:连接超时");
queue_message(BAND_MEDIUM, "NOTICE", "系统通知:配置文件已更新");

show_system_stats();
show_queue_contents();
show_priority_statistics();

// 处理消息的示例
printf("\n=== 消息处理演示 ===\n");

// 1. 处理最高优先级消息
printf("\n--- 处理最高优先级消息 ---\n");
band = 0;
flags = MSG_HIPRI;
result = advanced_getpmsg(&ctlbuf, &databuf, &band, &flags);

if (result == 0) {
printf("处理成功:\n");
printf(" 优先级: %d\n", band);
printf(" 控制信息: %s\n", ctlbuf.buf);
printf(" 消息内容: %s\n", databuf.buf);
printf(" 消息标志: %s\n", (flags & MSG_HIPRI) ? "高优先级" : "普通优先级");
}

// 2. 处理指定优先级消息
printf("\n--- 处理中等优先级消息 ---\n");
band = BAND_MEDIUM;
flags = MSG_BAND;
result = advanced_getpmsg(&ctlbuf, &databuf, &band, &flags);

if (result == 0) {
printf("处理成功:\n");
printf(" 优先级: %d\n", band);
printf(" 控制信息: %s\n", ctlbuf.buf);
printf(" 消息内容: %s\n", databuf.buf);
}

// 3. 处理任意消息
printf("\n--- 处理任意消息 ---\n");
band = 0;
flags = MSG_ANY;
result = advanced_getpmsg(&ctlbuf, &databuf, &band, &flags);

if (result == 0) {
printf("处理成功:\n");
printf(" 优先级: %d\n", band);
printf(" 控制信息: %s\n", ctlbuf.buf);
printf(" 消息内容: %s\n", databuf.buf);
}

// 显示处理后的状态
show_system_stats();
show_queue_contents();

printf("\n=== STREAMS 优先级消息系统特点 ===\n");
printf("1. 支持 0-255 级优先级\n");
printf("2. 可以按优先级选择性接收消息\n");
printf("3. 高优先级消息可以抢占处理\n");
printf("4. 适用于实时系统和关键任务应用\n");
printf("\nLinux 替代方案:\n");
printf("- 实时信号 (RT signals)\n");
printf("- D-Bus 消息系统\n");
printf("- systemd journal\n");
printf("- 自定义优先级队列\n");

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
# 编译示例程序
gcc -o getpmsg_example1 example1.c
gcc -o getpmsg_example2 example2.c
gcc -o getpmsg_example3 example3.c

# 运行示例
./getpmsg_example1
./getpmsg_example2
./getpmsg_example3

STREAMS 系统检查

1
2
3
4
5
6
7
8
9
# 检查系统是否支持 STREAMS
ls /usr/include/stropts.h

# 在 Solaris 等系统上编译
gcc -D_SOLARIS -o getpmsg_real example_real.c -lstrmi

# 查看 STREAMS 相关信息
modinfo | grep stream

重要注意事项

系统支持: getpmsg 主要在 System V Unix 系统中可用

Linux 限制: 大多数 Linux 系统不完全支持 STREAMS

移植性: 代码可移植性较差

优先级范围: band 值范围为 0-255

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

现代 Linux 替代方案

使用实时信号

1
2
3
4
5
6
7
8
9
10
#include <signal.h>
#include <sys/types.h>

// 发送带优先级的实时信号
int send_priority_signal(pid_t pid, int sig, int priority) {
union sigval value;
value.sival_int = priority;
return sigqueue(pid, sig, value);
}

使用自定义优先级队列

1
2
3
4
5
6
7
8
9
10
11
12
#include <pthread.h>
#include <sys/queue.h>

// 自定义优先级消息队列
struct priority_msg {
int priority;
void *data;
SLIST_ENTRY(priority_msg) entries;
};

SLIST_HEAD(msg_head, priority_msg) msg_queue = SLIST_HEAD_INITIALIZER(msg_queue);

实际应用场景

实时系统: 需要按优先级处理消息的实时应用

网络协议: 实现复杂的网络协议栈

设备驱动: 设备驱动中的消息处理

系统管理: 系统管理工具的优先级消息

多媒体应用: 音视频处理中的实时消息

优先级消息处理流程

1
2
3
4
5
6
7
8
消息生产者
↓ (putpmsg)
STREAMS 系统
↓ (按优先级排队)
消息消费者
↓ (getpmsg)
应用层处理

虽然 getpmsg 在现代 Linux 系统中使用较少,但它体现了优先级消息处理的重要概念。在实际开发中,可以根据需求选择合适的现代替代方案来实现类似的功能。

getpriority系统调用及示例

getpriority - 获取进程或进程组的优先级

getpriority 是 Linux 系统调用,用于查询进程、进程组或用户的调度优先级。优先级范围 -20(最高)到 +19(最低)。函数原型为 int getpriority(int which, id_t who),参数 which 指定查询类型(PRIO_PROCESS/PRIO_PGRP/PRIO_USER),who 为对应 ID。成功返回优先级值,失败返回 -1 并设置 errno。需注意返回值可能为 -1(成功时),应先清除 errno 再调用。示例代码展示了如何获取当前进程、进程组和用户的优

1. 函数介绍

getpriority 是一个 Linux 系统调用,用于获取指定进程或进程组的调度优先级。它是 Unix/Linux 系统中进程调度管理的重要组成部分,允许程序查询进程的当前优先级设置。

在 Linux 系统中,进程优先级影响 CPU 调度,优先级高的进程会获得更多 CPU 时间。getpriority 与 setpriority 配合使用,提供了完整的优先级管理功能。

2. 函数原型

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

int getpriority(int which, id_t who);

3. 功能

获取指定进程、进程组或用户的所有进程的调度优先级。优先级值范围通常为 -20 到 +19,其中 -20 表示最高优先级,+19 表示最低优先级。

4. 参数

int which: 指定查询的类型

  • PRIO_PROCESS: 查询单个进程的优先级

  • PRIO_PGRP: 查询进程组中所有进程的优先级

  • PRIO_USER: 查询指定用户的所有进程的优先级

id_t who: 根据 which 参数指定的具体 ID

  • PRIO_PROCESS: 进程 ID(0 表示当前进程)

  • PRIO_PGRP: 进程组 ID(0 表示当前进程组)

  • PRIO_USER: 用户 ID(0 表示当前用户)

5. 返回值

  • 成功时:返回优先级值(范围 -20 到 +19)

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

  • 注意:由于成功时可能返回 -1,需要先清除 errno 再调用

6. 常见 errno 错误码

  • ESRCH: 指定的进程、进程组或用户不存在

  • EINVAL: which 参数无效

  • EPERM: 权限不足(无法访问指定进程的信息)

  • EACCES: 访问被拒绝(某些安全策略下)

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

  • setpriority(): 设置进程优先级

  • nice(): 调整当前进程的优先级

  • getrlimit(), setrlimit(): 获取/设置资源限制

  • sched_getparam(), sched_setparam(): 更高级的调度参数管理

  • sched_getscheduler(), sched_setscheduler(): 获取/设置调度策略

  • getrusage(): 获取进程资源使用情况

  • /proc/[pid]/stat: 查看进程状态信息

8. 示例代码

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

int safe_getpriority(int which, id_t who) {
errno = 0; // 必须先清除 errno
int priority = getpriority(which, who);
if (priority == -1 && errno != 0) {
return -1; // 真正的错误
}
return priority;
}

int main() {
int priority;

printf("=== 进程优先级获取 ===\n");

// 获取当前进程的优先级
priority = safe_getpriority(PRIO_PROCESS, 0);
if (priority == -1) {
perror("获取当前进程优先级失败");
} else {
printf("当前进程优先级: %d\n", priority);
}

// 获取当前进程组的优先级
priority = safe_getpriority(PRIO_PGRP, 0);
if (priority == -1) {
perror("获取当前进程组优先级失败");
} else {
printf("当前进程组优先级: %d\n", priority);
}

// 获取当前用户的进程优先级
priority = safe_getpriority(PRIO_USER, 0);
if (priority == -1) {
perror("获取当前用户进程优先级失败");
} else {
printf("当前用户进程优先级: %d\n", priority);
}

// 获取 init 进程的优先级
priority = safe_getpriority(PRIO_PROCESS, 1);
if (priority == -1) {
if (errno == EPERM) {
printf("权限不足,无法获取 init 进程优先级\n");
} else {
perror("获取 init 进程优先级失败");
}
} else {
printf("init 进程优先级: %d\n", priority);
}

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

void test_getpriority(int which, id_t who, const char *description) {
printf("\n测试 %s:\n", description);

errno = 0; // 清除 errno
int priority = getpriority(which, who);

if (priority == -1 && errno != 0) {
printf(" getpriority 调用失败: %s\n", strerror(errno));
switch (errno) {
case ESRCH:
printf(" 原因: 指定的进程/组/用户不存在\n");
break;
case EINVAL:
printf(" 原因: 无效的 which 参数\n");
break;
case EPERM:
printf(" 原因: 权限不足\n");
break;
default:
printf(" 其他错误\n");
break;
}
} else {
printf(" 优先级: %d\n", priority);
}
}

int main() {
printf("=== getpriority 错误处理测试 ===\n");

// 测试正常情况
test_getpriority(PRIO_PROCESS, 0, "当前进程");
test_getpriority(PRIO_PGRP, 0, "当前进程组");
test_getpriority(PRIO_USER, getuid(), "当前用户");

// 测试无效参数
test_getpriority(999, 0, "无效的 which 参数");

// 测试不存在的进程
test_getpriority(PRIO_PROCESS, 999999, "不存在的进程");

// 测试不存在的进程组
test_getpriority(PRIO_PGRP, 999999, "不存在的进程组");

// 创建子进程进行测试
pid_t child_pid = fork();
if (child_pid == -1) {
perror("fork 失败");
exit(EXIT_FAILURE);
}

if (child_pid == 0) {
// 子进程
printf("\n=== 子进程信息 ===\n");
printf("子进程 ID: %d\n", getpid());
printf("父进程 ID: %d\n", getppid());

// 子进程设置自己的优先级
if (setpriority(PRIO_PROCESS, 0, 10) == 0) {
printf("子进程成功设置优先级为 10\n");
} else {
perror("子进程设置优先级失败");
}

// 显示子进程优先级
errno = 0;
int child_priority = getpriority(PRIO_PROCESS, 0);
if (!(child_priority == -1 && errno != 0)) {
printf("子进程当前优先级: %d\n", child_priority);
}

// 子进程睡眠一段时间
sleep(3);
exit(0);
} else {
// 父进程
printf("\n=== 父进程测试子进程 ===\n");
printf("子进程 ID: %d\n", child_pid);

// 父进程获取子进程优先级
test_getpriority(PRIO_PROCESS, child_pid, "子进程(运行中)");

// 等待子进程结束
int status;
waitpid(child_pid, &status, 0);
printf("子进程已结束\n");

// 再次测试已结束的进程
test_getpriority(PRIO_PROCESS, child_pid, "已结束的子进程");
}

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

typedef struct {
pid_t pid;
int priority;
char process_name&#91;256];
} process_priority_t;

int safe_getpriority(int which, id_t who) {
errno = 0;
int priority = getpriority(which, who);
if (priority == -1 && errno != 0) {
return -999; // 特殊错误值
}
return priority;
}

void print_priority_info(const char *label, int priority) {
if (priority == -999) {
printf("%s: 无法获取\n", label);
} else {
printf("%s: %d", label, priority);
if (priority < 0) {
printf(" (高优先级)");
} else if (priority > 0) {
printf(" (低优先级)");
} else {
printf(" (正常优先级)");
}
printf("\n");
}
}

void analyze_process_priorities() {
printf("=== 进程优先级分析 ===\n");

// 当前进程信息
printf("当前进程信息:\n");
printf(" PID: %d\n", getpid());
printf(" UID: %d", getuid());
struct passwd *pwd = getpwuid(getuid());
if (pwd) {
printf(" (%s)", pwd->pw_name);
}
printf("\n");

printf(" GID: %d", getgid());
struct group *grp = getgrgid(getgid());
if (grp) {
printf(" (%s)", grp->gr_name);
}
printf("\n");

// 各种优先级信息
int current_priority = safe_getpriority(PRIO_PROCESS, 0);
print_priority_info(" 当前进程优先级", current_priority);

int pgrp_priority = safe_getpriority(PRIO_PGRP, 0);
print_priority_info(" 当前进程组优先级", pgrp_priority);

int user_priority = safe_getpriority(PRIO_USER, getuid());
print_priority_info(" 当前用户进程优先级", user_priority);

// 系统关键进程优先级
printf("\n系统关键进程优先级:\n");

int init_priority = safe_getpriority(PRIO_PROCESS, 1);
print_priority_info(" init 进程 (PID 1)", init_priority);

// 获取 shell 进程优先级
pid_t shell_pid = getppid();
int shell_priority = safe_getpriority(PRIO_PROCESS, shell_pid);
printf(" 父进程 (shell) PID %d", shell_pid);
print_priority_info("", shell_priority);
}

void priority_statistics() {
printf("\n=== 优先级统计信息 ===\n");

// 获取当前用户的进程优先级范围
int min_priority = 20, max_priority = -20;
int sum_priority = 0, count = 0;

// 这里简化处理,实际应用中可能需要扫描 /proc
int current_priority = safe_getpriority(PRIO_PROCESS, 0);
if (current_priority != -999) {
min_priority = max_priority = sum_priority = current_priority;
count = 1;
}

printf("当前会话优先级统计:\n");
printf(" 进程数量: %d\n", count);
if (count > 0) {
printf(" 最低优先级: %d\n", min_priority);
printf(" 最高优先级: %d\n", max_priority);
printf(" 平均优先级: %.2f\n", (double)sum_priority / count);
}
}

int main() {
analyze_process_priorities();
priority_statistics();

// 交互式优先级查询
printf("\n=== 交互式查询 ===\n");
printf("输入进程 ID 查询优先级 (输入 0 退出): ");

pid_t target_pid;
while (scanf("%d", &target_pid) == 1 && target_pid != 0) {
if (target_pid > 0) {
int priority = safe_getpriority(PRIO_PROCESS, target_pid);
if (priority != -999) {
printf("进程 %d 的优先级: %d\n", target_pid, priority);
} else {
printf("无法获取进程 %d 的优先级\n", target_pid);
}
}
printf("继续输入进程 ID (输入 0 退出): ");
}

return 0;
}

示例4:优先级调整和监控

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

volatile sig_atomic_t running = 1;

void signal_handler(int sig) {
running = 0;
printf("\n收到信号 %d,停止监控\n", sig);
}

int safe_getpriority(int which, id_t who) {
errno = 0;
int priority = getpriority(which, who);
if (priority == -1 && errno != 0) {
return -999;
}
return priority;
}

void monitor_priority_changes(pid_t target_pid, int duration) {
int last_priority = -999;
time_t start_time = time(NULL);
time_t current_time;

printf("开始监控进程 %d 的优先级变化 (%d 秒)...\n", target_pid, duration);
printf("%-10s %-12s %s\n", "时间", "优先级", "状态");
printf("%-10s %-12s %s\n", "----", "----", "----");

while (running && (current_time = time(NULL)) - start_time < duration) {
int current_priority = safe_getpriority(PRIO_PROCESS, target_pid);

if (current_priority != -999) {
char status&#91;20] = "稳定";

if (last_priority != -999 && current_priority != last_priority) {
snprintf(status, sizeof(status), "变化 %d->%d",
last_priority, current_priority);
}

printf("%-10ld %-12d %s\n",
current_time - start_time,
current_priority,
status);

last_priority = current_priority;
} else {
printf("%-10ld %-12s %s\n",
current_time - start_time,
"无法获取",
"错误");
}

sleep(1);
}
}

void demonstrate_priority_adjustment() {
printf("=== 优先级调整演示 ===\n");

int original_priority = safe_getpriority(PRIO_PROCESS, 0);
printf("原始优先级: %d\n", original_priority);

// 提高优先级(需要权限)
printf("尝试提高优先级到 -5...\n");
if (setpriority(PRIO_PROCESS, 0, -5) == 0) {
int new_priority = safe_getpriority(PRIO_PROCESS, 0);
printf("优先级调整成功: %d\n", new_priority);
} else {
if (errno == EPERM) {
printf("权限不足,无法提高优先级(需要 root 权限)\n");
} else {
perror("优先级调整失败");
}
}

// 降低优先级
printf("尝试降低优先级到 10...\n");
if (setpriority(PRIO_PROCESS, 0, 10) == 0) {
int new_priority = safe_getpriority(PRIO_PROCESS, 0);
printf("优先级调整成功: %d\n", new_priority);
} else {
perror("优先级调整失败");
}

// 恢复原始优先级
if (original_priority != -999) {
if (setpriority(PRIO_PROCESS, 0, original_priority) == 0) {
printf("已恢复原始优先级: %d\n",
safe_getpriority(PRIO_PROCESS, 0));
}
}
}

int main() {
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);

demonstrate_priority_adjustment();

printf("\n当前进程优先级: %d\n", safe_getpriority(PRIO_PROCESS, 0));

// 如果需要,可以监控优先级变化
char choice;
printf("\n是否监控当前进程优先级变化? (y/N): ");
getchar(); // 清除缓冲区
if (scanf("%c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
monitor_priority_changes(getpid(), 10);
}

return 0;
}

9. 优先级值说明

Linux 进程优先级系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 优先级范围:-20 到 +19
// -20: 最高优先级
// 0: 默认优先级
// +19: 最低优先级

// 特殊优先级值
#define PRIO_MIN -20
#define PRIO_MAX 19

// 优先级类别
-20 to -1: 高优先级进程
0: 正常优先级进程
+1 to +19: 低优先级进程

10. 实际应用场景

场景1:系统监控工具

1
2
3
4
5
int is_high_priority_process(pid_t pid) {
int priority = safe_getpriority(PRIO_PROCESS, pid);
return (priority != -999 && priority < 0);
}

场景2:资源管理

1
2
3
4
5
void adjust_batch_job_priority() {
// 批处理作业使用较低优先级
setpriority(PRIO_PROCESS, 0, 15);
}

场景3:性能优化

1
2
3
4
5
void optimize_critical_process() {
// 关键进程使用较高优先级
setpriority(PRIO_PROCESS, 0, -5);
}

11. 注意事项

使用 getpriority 时的重要注意事项:

返回值检查: 必须先清除 errno,因为成功时可能返回 -1

权限问题: 访问其他进程信息可能需要适当权限

进程生命周期: 进程结束后相关信息可能不可用

实时性: 优先级可能在查询期间发生变化

系统限制: 某些优先级值可能需要特殊权限才能设置

12. 优先级与调度策略

1
2
3
4
5
6
7
8
9
10
// 不同调度策略的优先级处理
void explain_scheduling_policies() {
printf("Linux 调度策略优先级说明:\n");
printf("1. SCHED_FIFO: 实时优先级 1-99\n");
printf("2. SCHED_RR: 实时优先级 1-99\n");
printf("3. SCHED_OTHER: 静态优先级 -20 到 +19\n");
printf("4. SCHED_BATCH: 批处理优化\n");
printf("5. SCHED_IDLE: 空闲任务\n");
}

总结

getpriority 是进程优先级管理的重要函数,关键要点:

优先级查询: 获取进程、进程组或用户的调度优先级

错误处理: 特殊的返回值处理机制(需要清除 errno)

权限控制: 访问其他进程信息需要适当权限

系统管理: 在系统监控和资源管理中广泛使用

性能优化: 帮助识别和调整关键进程的优先级

正确使用 getpriority 可以帮助程序了解当前的调度状态,实现更精细的性能管理和资源控制。

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

getrandom系统调用及示例

getrandom 函数详解

getrandom是Linux系统中获取高质量随机数的系统调用,从内核熵池中提取真正的随机数据。它比传统rand()更安全可靠,适用于加密、安全令牌等场景。函数原型为ssize_t getrandom(void *buf, size_t buflen, unsigned int flags),支持GRND_RANDOM(阻塞模式)和GRND_NONBLOCK(非阻塞模式)两种标志位。文章详细介绍了函数参数、返回值、错误处理和示例代码,展示了如何生成随机数据和使用不同标志位。getrandom相比传统随机数设备更安全高效,是安全敏感应用的理想选择。

  1. 函数介绍

getrandom 是 Linux 系统中用于获取高质量随机数的系统调用。可以把这个函数想象成一个”真随机数生成器”——它从系统的熵池中获取真正的随机数据,就像从大自然的噪声中提取随机性一样。

在计算机系统中,随机数非常重要:

  • 加密操作: 生成密钥、初始化向量等

  • 安全令牌: 会话 ID、验证码等

  • 模拟和游戏: 游戏中的随机事件

  • 负载均衡: 分布式系统中的随机选择

getrandom 提供了比传统 rand() 函数更安全、更高质量的随机数,特别适合安全相关的应用。

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

ssize_t getrandom(void *buf, size_t buflen, unsigned int flags);

  1. 功能

getrandom 函数用于从内核的随机数生成器中获取随机字节,并将其存储在提供的缓冲区中。它可以直接访问系统的熵源,提供密码学安全的随机数。

  1. 参数
  • buf: 指向缓冲区的指针,用于存储生成的随机数据

  • buflen: 请求的随机数据字节数

  • flags: 控制随机数生成行为的标志位

  1. 标志位(flags 参数)

标志值说明GRND_RANDOM0x0001使用 /dev/random 而不是 /dev/urandomGRND_NONBLOCK0x0002非阻塞模式,熵不足时不等待

  1. 标志位详细说明

GRND_RANDOM

  • 默认情况下,getrandom 使用 /dev/urandom(非阻塞)

  • 设置此标志后,使用 /dev/random(阻塞模式)

  • /dev/random 在熵不足时会阻塞,直到收集到足够熵

GRND_NONBLOCK

  • 默认情况下,如果熵池未初始化,函数会阻塞等待

  • 设置此标志后,如果熵不足则立即返回错误(errno = EAGAIN)

  1. 返回值
  • 成功: 返回实际获取的随机字节数

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

常见错误码:

  • EAGAIN: 非阻塞模式下熵不足

  • EFAULT: buf 指针无效

  • EINVAL: flags 参数无效

  • EIO: 系统错误

  1. 相似函数或关联函数
  • /dev/random: 传统的阻塞随机数设备

  • /dev/urandom: 传统的非阻塞随机数设备

  • rand(): 标准库伪随机数生成器

  • random(): 更好的伪随机数生成器

  • arc4random(): BSD 风格的加密安全随机数

  • openssl RAND_bytes(): OpenSSL 库的随机数函数

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

// 将二进制数据转换为十六进制字符串
void bin_to_hex(const unsigned char *bin, size_t len, char *hex) {
for (size_t i = 0; i < len; i++) {
sprintf(hex + i * 2, "%02x", bin&#91;i]);
}
hex&#91;len * 2] = '\0';
}

// 将二进制数据转换为可打印字符(用于显示)
void bin_to_printable(const unsigned char *bin, size_t len, char *printable) {
for (size_t i = 0; i < len; i++) {
if (bin&#91;i] >= 32 && bin&#91;i] <= 126) {
printable&#91;i] = bin&#91;i];
} else {
printable&#91;i] = '.';
}
}
printable&#91;len] = '\0';
}

int main() {
unsigned char random_data&#91;32];
char hex_string&#91;65];
char printable_string&#91;33];
ssize_t bytes_read;

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

// 生成 32 字节的随机数据
printf("生成 32 字节随机数据...\n");
bytes_read = getrandom(random_data, sizeof(random_data), 0);

if (bytes_read == -1) {
perror("getrandom");
return 1;
}

printf("成功生成 %zd 字节随机数据\n", bytes_read);

// 显示十六进制格式
bin_to_hex(random_data, bytes_read, hex_string);
printf("十六进制: %s\n", hex_string);

// 显示可打印字符格式
bin_to_printable(random_data, bytes_read, printable_string);
printf("可打印: %s\n", printable_string);

// 生成不同长度的随机数据
printf("\n生成不同长度的随机数据:\n");
for (int len = 1; len <= 16; len++) {
unsigned char small_data&#91;16];
bytes_read = getrandom(small_data, len, 0);
if (bytes_read > 0) {
bin_to_hex(small_data, bytes_read, hex_string);
printf(" %2d 字节: %s\n", len, hex_string);
}
}

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

// 显示随机数据的统计信息
void show_random_stats(const unsigned char *data, size_t len) {
unsigned long sum = 0;
for (size_t i = 0; i < len; i++) {
sum += data&#91;i];
}
double average = (double)sum / len;
printf(" 平均值: %.2f\n", average);
printf(" 数据范围: 0x%02x - 0x%02x\n",
data&#91;0], data&#91;len-1]); // 简化的范围显示
}

int main() {
unsigned char buffer&#91;64];
ssize_t result;
struct timespec start, end;

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

// 1. 默认模式(推荐)
printf("1. 默认模式 (GRND_URANDOM):\n");
clock_gettime(CLOCK_MONOTONIC, &start);
result = getrandom(buffer, sizeof(buffer), 0);
clock_gettime(CLOCK_MONOTONIC, &end);

if (result > 0) {
printf(" 成功获取 %zd 字节随机数据\n", result);
printf(" 耗时: %ld 纳秒\n",
(end.tv_sec - start.tv_sec) * 1000000000L +
(end.tv_nsec - start.tv_nsec));
show_random_stats(buffer, result);
} else {
perror(" getrandom 失败");
}

// 2. 使用 GRND_RANDOM(阻塞模式)
printf("\n2. GRND_RANDOM 模式 (使用 /dev/random):\n");
printf(" 注意: 如果熵不足可能会阻塞\n");

clock_gettime(CLOCK_MONOTONIC, &start);
result = getrandom(buffer, 16, GRND_RANDOM);
clock_gettime(CLOCK_MONOTONIC, &end);

if (result > 0) {
printf(" 成功获取 %zd 字节随机数据\n", result);
printf(" 耗时: %ld 纳秒\n",
(end.tv_sec - start.tv_sec) * 1000000000L +
(end.tv_nsec - start.tv_nsec));
} else {
perror(" getrandom 失败");
}

// 3. 非阻塞模式
printf("\n3. 非阻塞模式 (GRND_NONBLOCK):\n");
result = getrandom(buffer, sizeof(buffer), GRND_NONBLOCK);

if (result > 0) {
printf(" 成功获取 %zd 字节随机数据\n", result);
} else if (result == -1 && errno == EAGAIN) {
printf(" 熵不足,非阻塞模式下立即返回\n");
} else {
perror(" getrandom 失败");
}

// 4. 组合标志
printf("\n4. 组合标志 (GRND_RANDOM | GRND_NONBLOCK):\n");
result = getrandom(buffer, 16, GRND_RANDOM | GRND_NONBLOCK);

if (result > 0) {
printf(" 成功获取 %zd 字节随机数据\n", result);
} else if (result == -1 && errno == EAGAIN) {
printf(" /dev/random 熵不足,非阻塞模式下立即返回\n");
} else {
perror(" getrandom 失败");
}

printf("\n=== 使用建议 ===\n");
printf("1. 一般情况下使用默认模式(flags=0)\n");
printf("2. 加密应用可以考虑 GRND_RANDOM\n");
printf("3. 非阻塞场景使用 GRND_NONBLOCK\n");
printf("4. 避免在循环中频繁调用\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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/random.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>

// 输出格式类型
enum output_format {
FORMAT_HEX, // 十六进制
FORMAT_BASE64, // Base64
FORMAT_BINARY, // 二进制
FORMAT_DECIMAL // 十进制
};

// 将二进制数据转换为十六进制
void to_hex(const unsigned char *data, size_t len, char *output) {
for (size_t i = 0; i < len; i++) {
sprintf(output + i * 2, "%02x", data&#91;i]);
}
output&#91;len * 2] = '\0';
}

// 简化的 Base64 编码(仅用于演示)
void to_base64(const unsigned char *data, size_t len, char *output) {
const char *base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

size_t i = 0, j = 0;
while (i < len) {
uint32_t octet_a = i < len ? data&#91;i++] : 0;
uint32_t octet_b = i < len ? data&#91;i++] : 0;
uint32_t octet_c = i < len ? data&#91;i++] : 0;

uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;

output&#91;j++] = base64_chars&#91;(triple >> 18) & 63];
output&#91;j++] = base64_chars&#91;(triple >> 12) & 63];
output&#91;j++] = base64_chars&#91;(triple >> 6) & 63];
output&#91;j++] = base64_chars&#91;triple & 63];
}

// 处理填充
int pad = len % 3;
if (pad == 1) {
output&#91;j-2] = '=';
output&#91;j-1] = '=';
} else if (pad == 2) {
output&#91;j-1] = '=';
}
output&#91;j] = '\0';
}

// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s &#91;选项]\n", program_name);
printf("\n选项:\n");
printf(" -n, --bytes=NUM 生成 NUM 字节的随机数据 (默认 32)\n");
printf(" -f, --format=TYPE 输出格式: hex, base64, binary, decimal\n");
printf(" -r, --random 使用 /dev/random (阻塞模式)\n");
printf(" -b, --non-block 非阻塞模式\n");
printf(" -h, --help 显示此帮助信息\n");
printf("\n输出格式说明:\n");
printf(" hex - 十六进制字符串\n");
printf(" base64 - Base64 编码\n");
printf(" binary - 原始二进制数据\n");
printf(" decimal - 十进制数字序列\n");
}

int main(int argc, char *argv&#91;]) {
size_t num_bytes = 32;
enum output_format format = FORMAT_HEX;
unsigned int flags = 0;
int c;

// 解析命令行参数
static struct option long_options&#91;] = {
{"bytes", required_argument, 0, 'n'},
{"format", required_argument, 0, 'f'},
{"random", no_argument, 0, 'r'},
{"non-block", no_argument, 0, 'b'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};

while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "n:f:rbh", long_options, &option_index);

if (c == -1)
break;

switch (c) {
case 'n':
num_bytes = atoi(optarg);
if (num_bytes == 0) {
fprintf(stderr, "错误: 无效的字节数\n");
return 1;
}
break;

case 'f':
if (strcmp(optarg, "hex") == 0) {
format = FORMAT_HEX;
} else if (strcmp(optarg, "base64") == 0) {
format = FORMAT_BASE64;
} else if (strcmp(optarg, "binary") == 0) {
format = FORMAT_BINARY;
} else if (strcmp(optarg, "decimal") == 0) {
format = FORMAT_DECIMAL;
} else {
fprintf(stderr, "错误: 未知的格式 '%s'\n", optarg);
return 1;
}
break;

case 'r':
flags |= GRND_RANDOM;
break;

case 'b':
flags |= GRND_NONBLOCK;
break;

case 'h':
show_help(argv&#91;0]);
return 0;

case '?':
return 1;
}
}

// 分配缓冲区
unsigned char *buffer = malloc(num_bytes);
if (!buffer) {
fprintf(stderr, "错误: 内存分配失败\n");
return 1;
}

// 生成随机数据
ssize_t result = getrandom(buffer, num_bytes, flags);
if (result == -1) {
if (errno == EAGAIN && (flags & GRND_NONBLOCK)) {
fprintf(stderr, "错误: 熵不足(非阻塞模式)\n");
} else {
perror("getrandom");
}
free(buffer);
return 1;
}

// 根据格式输出
switch (format) {
case FORMAT_HEX: {
char *hex_output = malloc(result * 2 + 1);
if (hex_output) {
to_hex(buffer, result, hex_output);
printf("%s\n", hex_output);
free(hex_output);
}
break;
}

case FORMAT_BASE64: {
char *base64_output = malloc((result * 4 / 3) + 4);
if (base64_output) {
to_base64(buffer, result, base64_output);
printf("%s\n", base64_output);
free(base64_output);
}
break;
}

case FORMAT_BINARY:
fwrite(buffer, 1, result, stdout);
break;

case FORMAT_DECIMAL:
for (size_t i = 0; i < result; i++) {
printf("%d ", buffer&#91;i]);
}
printf("\n");
break;
}

free(buffer);
return 0;
}

示例4:加密安全的随机数应用

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

// 生成加密安全的会话 ID
char* generate_session_id(size_t length) {
static const char charset&#91;] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";

char *session_id = malloc(length + 1);
if (!session_id) return NULL;

unsigned char random_bytes&#91;length];
ssize_t result = getrandom(random_bytes, length, 0);
if (result != (ssize_t)length) {
free(session_id);
return NULL;
}

for (size_t i = 0; i < length; i++) {
session_id&#91;i] = charset&#91;random_bytes&#91;i] % (sizeof(charset) - 1)];
}
session_id&#91;length] = '\0';

return session_id;
}

// 生成加密密钥
int generate_crypto_key(unsigned char *key, size_t key_size) {
ssize_t result = getrandom(key, key_size, GRND_NONBLOCK);
return (result == (ssize_t)key_size) ? 0 : -1;
}

// 生成盐值
int generate_salt(unsigned char *salt, size_t salt_size) {
return (getrandom(salt, salt_size, 0) == (ssize_t)salt_size) ? 0 : -1;
}

// 显示密钥(以十六进制格式)
void print_key(const unsigned char *key, size_t key_size, const char *label) {
printf("%s: ", label);
for (size_t i = 0; i < key_size; i++) {
printf("%02x", key&#91;i]);
}
printf("\n");
}

int main() {
printf("=== 加密安全随机数应用示例 ===\n\n");

// 1. 生成会话 ID
printf("1. 生成会话 ID:\n");
for (int i = 0; i < 5; i++) {
char *session_id = generate_session_id(32);
if (session_id) {
printf(" 会话 ID %d: %s\n", i + 1, session_id);
free(session_id);
}
}

// 2. 生成加密密钥
printf("\n2. 生成加密密钥:\n");
unsigned char aes_key&#91;32]; // 256-bit AES key
unsigned char hmac_key&#91;64]; // 512-bit HMAC key

if (generate_crypto_key(aes_key, sizeof(aes_key)) == 0) {
print_key(aes_key, sizeof(aes_key), "AES-256 密钥");
} else {
printf(" AES 密钥生成失败\n");
}

if (generate_crypto_key(hmac_key, sizeof(hmac_key)) == 0) {
print_key(hmac_key, sizeof(hmac_key), "HMAC 密钥");
} else {
printf(" HMAC 密钥生成失败\n");
}

// 3. 生成盐值
printf("\n3. 生成盐值:\n");
unsigned char salt&#91;16];
for (int i = 0; i < 3; i++) {
if (generate_salt(salt, sizeof(salt)) == 0) {
printf(" 盐值 %d: ", i + 1);
for (size_t j = 0; j < sizeof(salt); j++) {
printf("%02x", salt&#91;j]);
}
printf("\n");
}
}

// 4. 生成初始化向量 (IV)
printf("\n4. 生成初始化向量 (IV):\n");
unsigned char iv&#91;16]; // AES block size
if (getrandom(iv, sizeof(iv), 0) == sizeof(iv)) {
print_key(iv, sizeof(iv), "AES IV");
}

// 5. 性能测试
printf("\n5. 性能测试:\n");
const size_t test_size = 1024;
unsigned char *test_buffer = malloc(test_size);
if (test_buffer) {
struct timespec start, end;
const int iterations = 1000;

clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < iterations; i++) {
if (getrandom(test_buffer, test_size, GRND_NONBLOCK) != test_size) {
printf(" 测试失败\n");
break;
}
}
clock_gettime(CLOCK_MONOTONIC, &end);

long long duration = (end.tv_sec - start.tv_sec) * 1000000000LL +
(end.tv_nsec - start.tv_nsec);
double avg_time = (double)duration / iterations / 1000000.0; // 毫秒

printf(" 生成 %zu 字节随机数据 %d 次\n", test_size, iterations);
printf(" 平均耗时: %.3f 毫秒\n", avg_time);
printf(" 吞吐量: %.2f MB/s\n",
(test_size * iterations) / (duration / 1000.0) / 1024.0);

free(test_buffer);
}

printf("\n=== 安全建议 ===\n");
printf("1. 使用 getrandom 而不是 rand() 生成安全随机数\n");
printf("2. 对于加密密钥,考虑使用 GRND_RANDOM\n");
printf("3. 避免在循环中频繁调用 getrandom\n");
printf("4. 检查返回值确保成功获取随机数据\n");
printf("5. 不要将随机数据存储在可预测的位置\n");

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编译示例程序
gcc -o getrandom_example1 example1.c
gcc -o getrandom_example2 example2.c
gcc -o getrandom_example3 example3.c
gcc -o getrandom_example4 example4.c

# 运行示例
./getrandom_example1
./getrandom_example2
./getrandom_example3 --help
./getrandom_example3 -n 16 -f hex
./getrandom_example3 -n 32 -f base64
./getrandom_example4

系统要求检查

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

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

# 检查 /dev/random 和 /dev/urandom
ls -l /dev/random /dev/urandom

重要注意事项

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

glibc 版本: 需要 glibc 2.25+ 支持

性能考虑: 避免频繁调用,批量获取随机数据

安全性: 比 rand() 等伪随机数生成器更安全

阻塞行为: 默认非阻塞,GRND_RANDOM 可能阻塞

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

与其他随机数生成器的比较

特性getrandom/dev/urandomrand()random()安全性高高低低阻塞可选否否否性能中等中等高高可移植性Linux 特有Unix-like标准 CBSD 扩展加密适用是是否否

实际应用场景

加密密钥生成: 生成 AES、RSA 等加密密钥

会话管理: 生成会话 ID、CSRF 令牌

密码盐值: 为密码哈希生成随机盐值

初始化向量: 生成加密算法的 IV

临时文件名: 生成唯一的临时文件名

游戏随机数: 游戏中的随机事件生成

最佳实践

默认使用: 一般情况下使用 getrandom(buffer, size, 0)

加密场景: 重要加密操作可考虑 GRND_RANDOM

批量获取: 一次获取较多随机数据,避免频繁调用

错误检查: 始终检查返回值确保成功

内存清理: 使用后及时清理敏感的随机数据

这些示例展示了 getrandom 函数的各种使用方法,从基础的随机数据生成到完整的安全应用,帮助你全面掌握这个重要的安全随机数生成接口。

getresuid系统调用及示例

getresuid - 获取进程的真实、有效和保存的用户ID

getresuid和getresgid是Linux系统调用,用于获取进程的用户和组ID信息。getresuid获取真实用户ID、有效用户ID和保存的设置用户ID;getresgid获取对应的组ID。两者都通过指针参数返回三个ID值,成功返回0,失败返回-1并设置errno。示例代码展示了如何获取并分析这些ID信息,包括错误处理和权限状态分析。相关函数包括getuid、setuid等用于用户/组ID管理的系统调用。

1. 函数介绍

getresuid 是一个 Linux 系统调用,用于同时获取当前进程的真实用户 ID(Real User ID)、有效用户 ID(Effective User ID)和保存的设置用户 ID(Saved Set-user-ID)。这三个 ID 构成了 Unix/Linux 系统中完整的用户身份管理体系。

2. 函数原型

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

int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);

getresgid - 获取进程的真实、有效和保存的组ID

1. 函数介绍

getresgid 是一个 Linux 系统调用,用于同时获取当前进程的真实组 ID(Real Group ID)、有效组 ID(Effective Group ID)和保存的设置组 ID(Saved Set-group-ID)。

2. 函数原型

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

int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);

3. 功能对比

函数功能参数getresuid(ruid, euid, suid)获取用户 ID 三元组3个 uid_t* 指针getresgid(rgid, egid, sgid)获取组 ID 三元组3个 gid_t* 指针

4. 参数说明

getresuid 参数:

  • uid_t *ruid: 指向存储真实用户 ID 的变量的指针

  • uid_t *euid: 指向存储有效用户 ID 的变量的指针

  • uid_t *suid: 指向存储保存的设置用户 ID 的变量的指针

getresgid 参数:

  • gid_t *rgid: 指向存储真实组 ID 的变量的指针

  • gid_t *egid: 指向存储有效组 ID 的变量的指针

  • gid_t *sgid: 指向存储保存的设置组 ID 的变量的指针

5. 返回值

  • 成功时:返回 0

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

6. 常见 errno 错误码

  • EFAULT: 指针参数指向无效内存地址

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

  • getuid(), geteuid(): 分别获取真实和有效用户 ID

  • getgid(), getegid(): 分别获取真实和有效组 ID

  • setresuid(), setresgid(): 设置用户/组 ID 三元组

  • setreuid(), setregid(): 设置真实和有效 ID

  • setuid(), setgid(): 设置用户/组 ID

  • seteuid(), setegid(): 设置有效用户/组 ID

8. 示例代码

示例1:基本使用 - 获取完整的 ID 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

void print_user_info(const char *label, uid_t uid) {
printf("%-20s %d", label, uid);

struct passwd *pwd = getpwuid(uid);
if (pwd != NULL) {
printf(" (%s)", pwd->pw_name);
}
printf("\n");
}

void print_group_info(const char *label, gid_t gid) {
printf("%-20s %d", label, gid);

struct group *grp = getgrgid(gid);
if (grp != NULL) {
printf(" (%s)", grp->gr_name);
}
printf("\n");
}

int main() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
int ret;

printf("=== 进程完整身份信息 ===\n");

// 获取用户 ID 三元组
ret = getresuid(&ruid, &euid, &suid);
if (ret == -1) {
perror("getresuid 失败");
exit(EXIT_FAILURE);
}

printf("用户 ID 信息:\n");
print_user_info("真实用户 ID:", ruid);
print_user_info("有效用户 ID:", euid);
print_user_info("保存的设置 UID:", suid);

// 获取组 ID 三元组
ret = getresgid(&rgid, &egid, &sgid);
if (ret == -1) {
perror("getresgid 失败");
exit(EXIT_FAILURE);
}

printf("\n组 ID 信息:\n");
print_group_info("真实组 ID:", rgid);
print_group_info("有效组 ID:", egid);
print_group_info("保存的设置 GID:", sgid);

// 分析身份状态
printf("\n身份状态分析:\n");

if (euid == 0) {
printf("✓ 当前进程具有 root 用户权限\n");
}

if (egid == 0) {
printf("✓ 当前进程具有 root 组权限\n");
}

if (ruid != euid) {
printf("✓ 用户身份已被切换 (真实: %d, 有效: %d)\n", ruid, euid);
}

if (rgid != egid) {
printf("✓ 组身份已被切换 (真实: %d, 有效: %d)\n", rgid, egid);
}

if (suid != euid) {
printf("✓ 保存的设置 UID 可用于权限恢复 (%d -> %d)\n", suid, euid);
}

if (sgid != egid) {
printf("✓ 保存的设置 GID 可用于权限恢复 (%d -> %d)\n", sgid, egid);
}

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

void safe_getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) {
if (getresuid(ruid, euid, suid) == -1) {
printf("getresuid 失败: %s\n", strerror(errno));
*ruid = *euid = *suid = (uid_t)-1;
}
}

void safe_getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) {
if (getresgid(rgid, egid, sgid) == -1) {
printf("getresgid 失败: %s\n", strerror(errno));
*rgid = *egid = *sgid = (gid_t)-1;
}
}

void analyze_privilege_status() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;

printf("=== 权限状态详细分析 ===\n");

safe_getresuid(&ruid, &euid, &suid);
safe_getresgid(&rgid, &egid, &sgid);

printf("用户权限分析:\n");
printf(" 真实 UID: %d\n", ruid);
printf(" 有效 UID: %d", euid);
if (euid == 0) printf(" (ROOT 权限)");
printf("\n");
printf(" 保存 SUID: %d", suid);
if (suid == 0) printf(" (保存 ROOT 权限)");
printf("\n");

printf("\n组权限分析:\n");
printf(" 真实 GID: %d\n", rgid);
printf(" 有效 GID: %d", egid);
if (egid == 0) printf(" (ROOT 组权限)");
printf("\n");
printf(" 保存 SGID: %d", sgid);
if (sgid == 0) printf(" (保存 ROOT 组权限)");
printf("\n");

// 权限切换能力分析
printf("\n权限切换能力:\n");

if (suid != euid) {
printf("✓ 可以通过 setuid() 恢复到保存的 UID %d\n", suid);
}

if (sgid != egid) {
printf("✓ 可以通过 setgid() 恢复到保存的 GID %d\n", sgid);
}

// 特权状态
if (euid == 0 || egid == 0) {
printf("⚠ 当前进程具有特权权限,注意安全操作\n");
}

if ((euid != ruid || egid != rgid) && (suid == ruid && sgid == rgid)) {
printf("✓ 可以通过保存的 ID 完全恢复到原始身份\n");
}
}

void demonstrate_invalid_pointer_handling() {
printf("\n=== 无效指针处理测试 ===\n");

// 测试 NULL 指针
if (getresuid(NULL, NULL, NULL) == -1) {
if (errno == EFAULT) {
printf("✓ 正确处理了 NULL 指针 (EFAULT)\n");
} else {
printf("✗ 意外错误: %s\n", strerror(errno));
}
}

// 测试部分 NULL 指针
uid_t ruid, euid;
if (getresuid(&ruid, &euid, NULL) == -1) {
if (errno == EFAULT) {
printf("✓ 正确处理了部分 NULL 指针\n");
}
}
}

int main() {
analyze_privilege_status();
demonstrate_invalid_pointer_handling();
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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>

void print_current_ids(const char *context) {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;

if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
printf("获取 ID 信息失败\n");
return;
}

printf("%s:\n", context);
printf(" UID: %d/%d/%d (真实/有效/保存)\n", ruid, euid, suid);
printf(" GID: %d/%d/%d (真实/有效/保存)\n", rgid, egid, sgid);
printf("\n");
}

int main() {
printf("=== 权限切换和恢复演示 ===\n");

// 初始状态
print_current_ids("初始状态");

// 检查是否具有特权权限
uid_t euid;
getresuid(NULL, &euid, NULL);

if (euid != 0) {
printf("注意: 当前进程不是 root,某些权限操作可能失败\n");
printf("建议以 root 权限运行此演示\n");
}

// 演示 setuid/setgid 的效果
printf("尝试进行权限操作...\n");

// 如果是 root,可以进行权限切换演示
if (euid == 0) {
// 切换到 nobody 用户(假设 UID 65534)
uid_t nobody_uid = 65534;
gid_t nogroup_gid = 65534;

printf("尝试切换到 nobody 用户...\n");

if (setresuid(nobody_uid, nobody_uid, nobody_uid) == 0 &&
setresgid(nogroup_gid, nogroup_gid, nogroup_gid) == 0) {
print_current_ids("切换到 nobody 后");
printf("✓ 成功切换到 nobody 用户\n");
} else {
printf("✗ 切换失败: %s\n", strerror(errno));
}

// 尝试恢复权限(应该失败,因为保存的 ID 已改变)
if (setuid(0) == -1) {
printf("✓ 无法恢复到 root 权限(保存的 ID 已改变)\n");
}
} else {
printf("跳过权限切换演示(需要 root 权限)\n");
}

// 显示最终状态
print_current_ids("最终状态");

return 0;
}

示例4:安全审计和监控工具

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

typedef struct {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
time_t timestamp;
pid_t pid;
} identity_snapshot_t;

int capture_identity_snapshot(identity_snapshot_t *snapshot) {
snapshot->pid = getpid();
snapshot->timestamp = time(NULL);

if (getresuid(&snapshot->ruid, &snapshot->euid, &snapshot->suid) == -1 ||
getresgid(&snapshot->rgid, &snapshot->egid, &snapshot->sgid) == -1) {
return -1;
}

return 0;
}

void print_identity_snapshot(const identity_snapshot_t *snapshot) {
char time_str&#91;32];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S",
localtime(&snapshot->timestamp));

printf("时间: %s\n", time_str);
printf("进程: %d\n", snapshot->pid);
printf("用户 ID: %d/%d/%d (真实/有效/保存)\n",
snapshot->ruid, snapshot->euid, snapshot->suid);
printf("组 ID: %d/%d/%d (真实/有效/保存)\n",
snapshot->rgid, snapshot->egid, snapshot->sgid);

// 显示用户名和组名
struct passwd *pwd = getpwuid(snapshot->ruid);
if (pwd) printf("真实用户: %s\n", pwd->pw_name);

pwd = getpwuid(snapshot->euid);
if (pwd) printf("有效用户: %s\n", pwd->pw_name);

struct group *grp = getgrgid(snapshot->rgid);
if (grp) printf("真实组: %s\n", grp->gr_name);

grp = getgrgid(snapshot->egid);
if (grp) printf("有效组: %s\n", grp->gr_name);
}

void security_audit() {
identity_snapshot_t snapshot;

printf("=== 安全审计报告 ===\n");

if (capture_identity_snapshot(&snapshot) == -1) {
printf("获取身份信息失败\n");
return;
}

print_identity_snapshot(&snapshot);

// 安全检查
printf("\n安全检查结果:\n");

// 检查特权权限
if (snapshot.euid == 0) {
printf("⚠ 警告: 进程以 root 权限运行\n");
}

if (snapshot.egid == 0) {
printf("⚠ 警告: 进程具有 root 组权限\n");
}

// 检查权限不一致
if (snapshot.ruid != snapshot.euid) {
printf("ℹ 信息: 用户权限已被切换\n");
}

if (snapshot.rgid != snapshot.egid) {
printf("ℹ 信息: 组权限已被切换\n");
}

// 检查保存的权限
if (snapshot.suid == 0 && snapshot.euid != 0) {
printf("ℹ 信息: 保存了 root 用户权限,可用于恢复\n");
}

if (snapshot.sgid == 0 && snapshot.egid != 0) {
printf("ℹ 信息: 保存了 root 组权限,可用于恢复\n");
}

// 检查潜在安全风险
if ((snapshot.ruid != snapshot.euid || snapshot.rgid != snapshot.egid) &&
(snapshot.suid == snapshot.ruid && snapshot.sgid == snapshot.rgid)) {
printf("✓ 安全: 可以完全恢复到原始身份\n");
}
}

void monitor_privilege_changes() {
printf("\n=== 权限变化监控 ===\n");

identity_snapshot_t initial, current;

if (capture_identity_snapshot(&initial) == -1) {
printf("无法获取初始身份信息\n");
return;
}

printf("监控 5 秒钟内的权限变化...\n");

for (int i = 0; i < 5; i++) {
sleep(1);

if (capture_identity_snapshot(&current) == 0) {
// 检查是否有变化
if (current.ruid != initial.ruid ||
current.euid != initial.euid ||
current.suid != initial.suid ||
current.rgid != initial.rgid ||
current.egid != initial.egid ||
current.sgid != initial.sgid) {

printf("检测到权限变化:\n");
printf("之前: UID(%d/%d/%d) GID(%d/%d/%d)\n",
initial.ruid, initial.euid, initial.suid,
initial.rgid, initial.egid, initial.sgid);
printf("现在: UID(%d/%d/%d) GID(%d/%d/%d)\n",
current.ruid, current.euid, current.suid,
current.rgid, current.egid, current.sgid);

// 更新初始状态
initial = current;
}
}
}

printf("监控结束\n");
}

int main() {
security_audit();
monitor_privilege_changes();
return 0;
}

9. ID 类型说明

Unix/Linux 系统中的三类 ID:

1
2
3
4
5
6
7
8
9
10
// 用户 ID 三元组
ruid // Real User ID: 启动进程的用户
euid // Effective User ID: 当前权限检查使用的用户 ID
suid // Saved Set-user-ID: 保存的设置用户 ID

// 组 ID 三元组
rgid // Real Group ID: 启动进程的组
egid // Effective Group ID: 当前权限检查使用的组 ID
sgid // Saved Set-group-ID: 保存的设置组 ID

10. 实际应用场景

场景1:权限管理工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int can_drop_privileges_completely() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;

if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
return 0;
}

// 检查是否可以完全丢弃特权
return (ruid == euid && euid == suid &&
rgid == egid && egid == sgid);
}

场景2:安全审计

1
2
3
4
5
6
7
8
9
void audit_process_privileges() {
identity_snapshot_t snapshot;
if (capture_identity_snapshot(&snapshot) == 0) {
if (snapshot.euid == 0) {
syslog(LOG_WARNING, "进程 %d 以 root 权限运行", snapshot.pid);
}
}
}

场景3:权限恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
int restore_original_privileges() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;

if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
return -1;
}

// 恢复到原始身份
return setresuid(ruid, ruid, ruid) || setresgid(rgid, rgid, rgid);
}

11. 注意事项

使用 getresuid 和 getresgid 时需要注意:

指针有效性: 所有指针参数必须指向有效的内存地址

错误处理: 虽然很少失败,但仍需检查返回值

权限检查: 获取其他进程的 ID 信息可能需要权限

并发安全: 在多线程环境中注意数据一致性

系统兼容: 在所有现代 Unix/Linux 系统中都可用

总结

getresuid 和 getresgid 是管理进程身份信息的重要函数:

关键特性:

完整信息: 一次性获取所有相关的 ID 信息

原子操作: 保证获取的 ID 组是一致的

安全相关: 是权限管理和安全审计的基础

系统调用: 直接访问内核信息,性能良好

主要应用:

安全审计和监控工具

权限管理和切换程序

系统管理和调试工具

容器和虚拟化环境中的权限控制

正确理解和使用这些函数对于编写安全、可靠的 Unix/Linux 程序至关重要,特别是在需要进行权限管理和安全检查的场景中。

https://www.calcguide.tech/2025/09/09/getresuid-syscall-demo/

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

getresgid系统调用及示例

getresgid - 获取进程的真实、有效和保存的组ID

1. 函数介绍

getresgid 是一个 Linux 系统调用,用于同时获取当前进程的真实组 ID(Real Group ID)、有效组 ID(Effective Group ID)和保存的设置组 ID(Saved Set-group-ID)。

2. 函数原型

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

int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);

3. 功能对比

函数功能参数getresuid(ruid, euid, suid)获取用户 ID 三元组3个 uid_t* 指针getresgid(rgid, egid, sgid)获取组 ID 三元组3个 gid_t* 指针

4. 参数说明

getresuid 参数:

  • uid_t *ruid: 指向存储真实用户 ID 的变量的指针

  • uid_t *euid: 指向存储有效用户 ID 的变量的指针

  • uid_t *suid: 指向存储保存的设置用户 ID 的变量的指针

getresgid 参数:

  • gid_t *rgid: 指向存储真实组 ID 的变量的指针

  • gid_t *egid: 指向存储有效组 ID 的变量的指针

  • gid_t *sgid: 指向存储保存的设置组 ID 的变量的指针

5. 返回值

  • 成功时:返回 0

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

6. 常见 errno 错误码

  • EFAULT: 指针参数指向无效内存地址

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

  • getuid(), geteuid(): 分别获取真实和有效用户 ID

  • getgid(), getegid(): 分别获取真实和有效组 ID

  • setresuid(), setresgid(): 设置用户/组 ID 三元组

  • setreuid(), setregid(): 设置真实和有效 ID

  • setuid(), setgid(): 设置用户/组 ID

  • seteuid(), setegid(): 设置有效用户/组 ID

8. 示例代码

示例1:基本使用 - 获取完整的 ID 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

void print_user_info(const char *label, uid_t uid) {
printf("%-20s %d", label, uid);

struct passwd *pwd = getpwuid(uid);
if (pwd != NULL) {
printf(" (%s)", pwd->pw_name);
}
printf("\n");
}

void print_group_info(const char *label, gid_t gid) {
printf("%-20s %d", label, gid);

struct group *grp = getgrgid(gid);
if (grp != NULL) {
printf(" (%s)", grp->gr_name);
}
printf("\n");
}

int main() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
int ret;

printf("=== 进程完整身份信息 ===\n");

// 获取用户 ID 三元组
ret = getresuid(&ruid, &euid, &suid);
if (ret == -1) {
perror("getresuid 失败");
exit(EXIT_FAILURE);
}

printf("用户 ID 信息:\n");
print_user_info("真实用户 ID:", ruid);
print_user_info("有效用户 ID:", euid);
print_user_info("保存的设置 UID:", suid);

// 获取组 ID 三元组
ret = getresgid(&rgid, &egid, &sgid);
if (ret == -1) {
perror("getresgid 失败");
exit(EXIT_FAILURE);
}

printf("\n组 ID 信息:\n");
print_group_info("真实组 ID:", rgid);
print_group_info("有效组 ID:", egid);
print_group_info("保存的设置 GID:", sgid);

// 分析身份状态
printf("\n身份状态分析:\n");

if (euid == 0) {
printf("✓ 当前进程具有 root 用户权限\n");
}

if (egid == 0) {
printf("✓ 当前进程具有 root 组权限\n");
}

if (ruid != euid) {
printf("✓ 用户身份已被切换 (真实: %d, 有效: %d)\n", ruid, euid);
}

if (rgid != egid) {
printf("✓ 组身份已被切换 (真实: %d, 有效: %d)\n", rgid, egid);
}

if (suid != euid) {
printf("✓ 保存的设置 UID 可用于权限恢复 (%d -> %d)\n", suid, euid);
}

if (sgid != egid) {
printf("✓ 保存的设置 GID 可用于权限恢复 (%d -> %d)\n", sgid, egid);
}

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

void safe_getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) {
if (getresuid(ruid, euid, suid) == -1) {
printf("getresuid 失败: %s\n", strerror(errno));
*ruid = *euid = *suid = (uid_t)-1;
}
}

void safe_getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) {
if (getresgid(rgid, egid, sgid) == -1) {
printf("getresgid 失败: %s\n", strerror(errno));
*rgid = *egid = *sgid = (gid_t)-1;
}
}

void analyze_privilege_status() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;

printf("=== 权限状态详细分析 ===\n");

safe_getresuid(&ruid, &euid, &suid);
safe_getresgid(&rgid, &egid, &sgid);

printf("用户权限分析:\n");
printf(" 真实 UID: %d\n", ruid);
printf(" 有效 UID: %d", euid);
if (euid == 0) printf(" (ROOT 权限)");
printf("\n");
printf(" 保存 SUID: %d", suid);
if (suid == 0) printf(" (保存 ROOT 权限)");
printf("\n");

printf("\n组权限分析:\n");
printf(" 真实 GID: %d\n", rgid);
printf(" 有效 GID: %d", egid);
if (egid == 0) printf(" (ROOT 组权限)");
printf("\n");
printf(" 保存 SGID: %d", sgid);
if (sgid == 0) printf(" (保存 ROOT 组权限)");
printf("\n");

// 权限切换能力分析
printf("\n权限切换能力:\n");

if (suid != euid) {
printf("✓ 可以通过 setuid() 恢复到保存的 UID %d\n", suid);
}

if (sgid != egid) {
printf("✓ 可以通过 setgid() 恢复到保存的 GID %d\n", sgid);
}

// 特权状态
if (euid == 0 || egid == 0) {
printf("⚠ 当前进程具有特权权限,注意安全操作\n");
}

if ((euid != ruid || egid != rgid) && (suid == ruid && sgid == rgid)) {
printf("✓ 可以通过保存的 ID 完全恢复到原始身份\n");
}
}

void demonstrate_invalid_pointer_handling() {
printf("\n=== 无效指针处理测试 ===\n");

// 测试 NULL 指针
if (getresuid(NULL, NULL, NULL) == -1) {
if (errno == EFAULT) {
printf("✓ 正确处理了 NULL 指针 (EFAULT)\n");
} else {
printf("✗ 意外错误: %s\n", strerror(errno));
}
}

// 测试部分 NULL 指针
uid_t ruid, euid;
if (getresuid(&ruid, &euid, NULL) == -1) {
if (errno == EFAULT) {
printf("✓ 正确处理了部分 NULL 指针\n");
}
}
}

int main() {
analyze_privilege_status();
demonstrate_invalid_pointer_handling();
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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>

void print_current_ids(const char *context) {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;

if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
printf("获取 ID 信息失败\n");
return;
}

printf("%s:\n", context);
printf(" UID: %d/%d/%d (真实/有效/保存)\n", ruid, euid, suid);
printf(" GID: %d/%d/%d (真实/有效/保存)\n", rgid, egid, sgid);
printf("\n");
}

int main() {
printf("=== 权限切换和恢复演示 ===\n");

// 初始状态
print_current_ids("初始状态");

// 检查是否具有特权权限
uid_t euid;
getresuid(NULL, &euid, NULL);

if (euid != 0) {
printf("注意: 当前进程不是 root,某些权限操作可能失败\n");
printf("建议以 root 权限运行此演示\n");
}

// 演示 setuid/setgid 的效果
printf("尝试进行权限操作...\n");

// 如果是 root,可以进行权限切换演示
if (euid == 0) {
// 切换到 nobody 用户(假设 UID 65534)
uid_t nobody_uid = 65534;
gid_t nogroup_gid = 65534;

printf("尝试切换到 nobody 用户...\n");

if (setresuid(nobody_uid, nobody_uid, nobody_uid) == 0 &&
setresgid(nogroup_gid, nogroup_gid, nogroup_gid) == 0) {
print_current_ids("切换到 nobody 后");
printf("✓ 成功切换到 nobody 用户\n");
} else {
printf("✗ 切换失败: %s\n", strerror(errno));
}

// 尝试恢复权限(应该失败,因为保存的 ID 已改变)
if (setuid(0) == -1) {
printf("✓ 无法恢复到 root 权限(保存的 ID 已改变)\n");
}
} else {
printf("跳过权限切换演示(需要 root 权限)\n");
}

// 显示最终状态
print_current_ids("最终状态");

return 0;
}

示例4:安全审计和监控工具

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

typedef struct {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
time_t timestamp;
pid_t pid;
} identity_snapshot_t;

int capture_identity_snapshot(identity_snapshot_t *snapshot) {
snapshot->pid = getpid();
snapshot->timestamp = time(NULL);

if (getresuid(&snapshot->ruid, &snapshot->euid, &snapshot->suid) == -1 ||
getresgid(&snapshot->rgid, &snapshot->egid, &snapshot->sgid) == -1) {
return -1;
}

return 0;
}

void print_identity_snapshot(const identity_snapshot_t *snapshot) {
char time_str&#91;32];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S",
localtime(&snapshot->timestamp));

printf("时间: %s\n", time_str);
printf("进程: %d\n", snapshot->pid);
printf("用户 ID: %d/%d/%d (真实/有效/保存)\n",
snapshot->ruid, snapshot->euid, snapshot->suid);
printf("组 ID: %d/%d/%d (真实/有效/保存)\n",
snapshot->rgid, snapshot->egid, snapshot->sgid);

// 显示用户名和组名
struct passwd *pwd = getpwuid(snapshot->ruid);
if (pwd) printf("真实用户: %s\n", pwd->pw_name);

pwd = getpwuid(snapshot->euid);
if (pwd) printf("有效用户: %s\n", pwd->pw_name);

struct group *grp = getgrgid(snapshot->rgid);
if (grp) printf("真实组: %s\n", grp->gr_name);

grp = getgrgid(snapshot->egid);
if (grp) printf("有效组: %s\n", grp->gr_name);
}

void security_audit() {
identity_snapshot_t snapshot;

printf("=== 安全审计报告 ===\n");

if (capture_identity_snapshot(&snapshot) == -1) {
printf("获取身份信息失败\n");
return;
}

print_identity_snapshot(&snapshot);

// 安全检查
printf("\n安全检查结果:\n");

// 检查特权权限
if (snapshot.euid == 0) {
printf("⚠ 警告: 进程以 root 权限运行\n");
}

if (snapshot.egid == 0) {
printf("⚠ 警告: 进程具有 root 组权限\n");
}

// 检查权限不一致
if (snapshot.ruid != snapshot.euid) {
printf("ℹ 信息: 用户权限已被切换\n");
}

if (snapshot.rgid != snapshot.egid) {
printf("ℹ 信息: 组权限已被切换\n");
}

// 检查保存的权限
if (snapshot.suid == 0 && snapshot.euid != 0) {
printf("ℹ 信息: 保存了 root 用户权限,可用于恢复\n");
}

if (snapshot.sgid == 0 && snapshot.egid != 0) {
printf("ℹ 信息: 保存了 root 组权限,可用于恢复\n");
}

// 检查潜在安全风险
if ((snapshot.ruid != snapshot.euid || snapshot.rgid != snapshot.egid) &&
(snapshot.suid == snapshot.ruid && snapshot.sgid == snapshot.rgid)) {
printf("✓ 安全: 可以完全恢复到原始身份\n");
}
}

void monitor_privilege_changes() {
printf("\n=== 权限变化监控 ===\n");

identity_snapshot_t initial, current;

if (capture_identity_snapshot(&initial) == -1) {
printf("无法获取初始身份信息\n");
return;
}

printf("监控 5 秒钟内的权限变化...\n");

for (int i = 0; i < 5; i++) {
sleep(1);

if (capture_identity_snapshot(&current) == 0) {
// 检查是否有变化
if (current.ruid != initial.ruid ||
current.euid != initial.euid ||
current.suid != initial.suid ||
current.rgid != initial.rgid ||
current.egid != initial.egid ||
current.sgid != initial.sgid) {

printf("检测到权限变化:\n");
printf("之前: UID(%d/%d/%d) GID(%d/%d/%d)\n",
initial.ruid, initial.euid, initial.suid,
initial.rgid, initial.egid, initial.sgid);
printf("现在: UID(%d/%d/%d) GID(%d/%d/%d)\n",
current.ruid, current.euid, current.suid,
current.rgid, current.egid, current.sgid);

// 更新初始状态
initial = current;
}
}
}

printf("监控结束\n");
}

int main() {
security_audit();
monitor_privilege_changes();
return 0;
}

9. ID 类型说明

Unix/Linux 系统中的三类 ID:

1
2
3
4
5
6
7
8
9
10
// 用户 ID 三元组
ruid // Real User ID: 启动进程的用户
euid // Effective User ID: 当前权限检查使用的用户 ID
suid // Saved Set-user-ID: 保存的设置用户 ID

// 组 ID 三元组
rgid // Real Group ID: 启动进程的组
egid // Effective Group ID: 当前权限检查使用的组 ID
sgid // Saved Set-group-ID: 保存的设置组 ID

10. 实际应用场景

场景1:权限管理工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int can_drop_privileges_completely() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;

if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
return 0;
}

// 检查是否可以完全丢弃特权
return (ruid == euid && euid == suid &&
rgid == egid && egid == sgid);
}

场景2:安全审计

1
2
3
4
5
6
7
8
9
void audit_process_privileges() {
identity_snapshot_t snapshot;
if (capture_identity_snapshot(&snapshot) == 0) {
if (snapshot.euid == 0) {
syslog(LOG_WARNING, "进程 %d 以 root 权限运行", snapshot.pid);
}
}
}

场景3:权限恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
int restore_original_privileges() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;

if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
return -1;
}

// 恢复到原始身份
return setresuid(ruid, ruid, ruid) || setresgid(rgid, rgid, rgid);
}

11. 注意事项

使用 getresuid 和 getresgid 时需要注意:

指针有效性: 所有指针参数必须指向有效的内存地址

错误处理: 虽然很少失败,但仍需检查返回值

权限检查: 获取其他进程的 ID 信息可能需要权限

并发安全: 在多线程环境中注意数据一致性

系统兼容: 在所有现代 Unix/Linux 系统中都可用

总结

getresuid 和 getresgid 是管理进程身份信息的重要函数:

关键特性:

完整信息: 一次性获取所有相关的 ID 信息

原子操作: 保证获取的 ID 组是一致的

安全相关: 是权限管理和安全审计的基础

系统调用: 直接访问内核信息,性能良好

主要应用:

安全审计和监控工具

权限管理和切换程序

系统管理和调试工具

容器和虚拟化环境中的权限控制

正确理解和使用这些函数对于编写安全、可靠的 Unix/Linux 程序至关重要,特别是在需要进行权限管理和安全检查的场景中。

getresgid 是 Linux 系统调用,用于获取进程的组 ID 三元组(真实组 ID、有效组 ID 和保存的组 ID)。该函数通过三个 gid_t* 参数返回 ID 值,成功时返回 0,失败返回 -1 并设置 errno。典型用法是检查进程权限状态,常与 getresuid 配合使用。示例代码展示了如何获取并分析这些 ID,包括错误处理和权限切换能力检测。该函数在需要精细控制进程权限时非常有用,特别是在特权程序设计中。

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

getitimer系统调用及示例

getitimer系统调用及示例

继续学习 Linux 系统编程中的重要函数。这次我们介绍 getitimer 和 setitimer 函数,它们用于管理和控制进程的间隔计时器(interval timers)。这些计时器可以在指定的时间后产生信号(通常是 SIGALRM, SIGVTALRM, SIGPROF),从而实现定时执行代码、测量程序执行时间等功能。

本文介绍了 Linux 系统编程中的间隔计时器函数 getitimer 和 setitimer,它们用于管理和控制进程的定时器。这些计时器可以在指定时间后产生信号(如 SIGALRM),实现定时执行代码或测量程序执行时间等功能。setitimer 设置计时器的超时时间和重载时间,支持三种类型:ITIMER_REAL(实时)、ITIMER_VIRTUAL(用户态 CPU 时间)和 ITIMER_PROF(总 CPU 时间)。getitimer 则获取计时器的当前状态。文章还提供了示例代码,演示如何使用 ITIMER_REAL 和 SIGALRM 实现超时机制,并对比了相关函数如 alarm 和 POSIX 定时器 API。这些功能为系统编程提供了灵活的定时解决方案。

1. 函数介绍

getitimer 和 setitimer 是一对 Linux 系统调用,用于获取和设置进程的间隔计时器(interval timers)。

  • 间隔计时器 (Interval Timer): 这是内核为每个进程维护的一个或多个计时器。每个计时器都有一个类型,当计时器超时(倒计时到 0)时,内核会向进程发送一个特定的信号。

setitimer: 设置指定类型计时器的超时时间和重载时间。

  • 超时时间 (Value): 从现在开始,计时器倒计时多久后第一次超时并发送信号。

  • 重载时间 (Interval): 每次超时后,计时器自动重置并重新开始倒计时的时间。如果重载时间为 0,则计时器是一次性的;如果非 0,则计时器是周期性的。

getitimer: 获取指定类型计时器的当前剩余时间(Value)和设置的重载时间(Interval)。

这提供了一种基于信号的定时机制,不同于 sleep 或 nanosleep 的主动睡眠,也不同于 alarm 的单一秒级定时器。

你可以把间隔计时器想象成一个可以设置闹钟和重复周期的高级闹钟:

  • 你可以说:“5 秒后响一次,然后每 2 秒再响一次”(周期性)。

  • 或者:“3 秒后响一次,之后不再响”(一次性)。

2. 函数原型

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

// 获取间隔计时器的当前设置
int getitimer(int which, struct itimerval *curr_value);

// 设置间隔计时器
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

3. 功能

  • getitimer: 查询由 which 指定的计时器的当前状态(剩余时间和重载时间),并将结果存储在 curr_value 指向的 struct itimerval 结构体中。

  • setitimer: 根据 new_value 设置由 which 指定的计时器。如果 old_value 非 NULL,则在设置新值之前,将计时器的旧值(设置前的剩余时间和重载时间)存储在 old_value 指向的结构体中。

4. 参数

共同参数

int which: 指定要操作的计时器类型。Linux 上有三种主要类型:

ITIMER_REAL: 实时计时器。

  • 时钟源: 系统实时时间(墙上时钟时间)。

  • 超时信号: SIGALRM。

  • 用途: 测量真实世界流逝的时间。

ITIMER_VIRTUAL: 虚拟计时器。

  • 时钟源: 进程在用户态(执行程序代码)下花费的 CPU 时间。

  • 超时信号: SIGVTALRM。

  • 用途: 测量进程自身执行代码所用的时间,不包括内核态时间和被阻塞的时间。

ITIMER_PROF: 性能计时器(Profile Timer)。

  • 时钟源: 进程在用户态和内核态(执行系统调用等)下花费的总 CPU 时间。

  • 超时信号: SIGPROF。

  • 用途: 常用于性能分析(profiling),测量程序执行(包括系统调用)的总 CPU 时间。

setitimer 特有参数

  • const struct itimerval *new_value: 指向一个 struct itimerval 结构体的指针,该结构体定义了新的计时器设置。

  • struct itimerval *old_value: 指向一个 struct itimerval 结构体的指针,用于接收计时器的旧设置。如果不需要旧值,可以传入 NULL。

struct itimerval 结构体

这两个函数都使用 struct itimerval 来表示时间间隔:

1
2
3
4
5
struct itimerval {
struct timeval it_interval; // 重载时间 (Interval)
struct timeval it_value; // 当前值/超时时间 (Value)
};

其中 struct timeval 定义了秒和微秒:

1
2
3
4
5
struct timeval {
time_t tv_sec; // 秒
suseconds_t tv_usec; // 微秒 (0 - 999,999)
};

  • it_value: 当前计时器的剩余时间。当调用 setitimer 时,它指定了第一次超时的时间。当调用 getitimer 时,它返回计时器当前还剩多少时间。

  • it_interval: 重载时间。指定计时器超时后,自动重新开始计时的时间间隔。如果这个值为 0,则计时器是一次性的。

5. 返回值

  • 成功时: 返回 0。

  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EINVAL which 无效或时间值无效,EFAULT 指针无效等)。

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

  • alarm: 一个更简单的函数,只操作 ITIMER_REAL 计时器,且精度为秒。alarm(seconds) 大致等价于 setitimer(ITIMER_REAL, …),其中 it_value.tv_sec = seconds 且 it_interval.tv_sec = 0。

  • signal, sigaction: 用于设置当计时器超时时(收到 SIGALRM, SIGVTALRM, SIGPROF 信号)应执行的操作。

  • nanosleep, clock_nanosleep: 提供高精度的主动睡眠,不基于信号。

  • clock_gettime, clock_settime: 用于获取和设置各种系统时钟,更现代和灵活。

  • timer_create, timer_settime, timer_gettime: POSIX 定时器 API,功能更强大,支持多种时钟源和多种通知方式(包括信号和线程通知),是 setitimer 的现代替代品。

7. 示例代码

示例 1:使用 ITIMER_REAL 和 SIGALRM 实现超时

这个例子演示了如何使用 ITIMER_REAL 计时器在 3 秒后发送 SIGALRM 信号来中断一个可能长时间运行的操作。

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
#include <sys/time.h> // setitimer, getitimer, ITIMER_REAL, struct itimerval
#include <signal.h> // signal, SIGALRM
#include <unistd.h> // pause, write
#include <stdio.h> // printf, perror
#include <stdlib.h> // exit
#include <errno.h> // errno
#include <string.h> // strerror

volatile sig_atomic_t alarm_received = 0;

// SIGALRM 信号处理函数
void alarm_handler(int sig) {
// 在信号处理函数中,应只调用异步信号安全的函数
write(STDERR_FILENO, "Timeout! SIGALRM received.\n", 27);
alarm_received = 1;
}

int main() {
struct itimerval timer;
const char *msg = "Doing some work that might take a long time...\n";
const char *msg_len = "Done.\n";

// 1. 设置 SIGALRM 信号处理函数
if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
perror("signal SIGALRM");
exit(EXIT_FAILURE);
}

// 2. 配置 ITIMER_REAL 计时器
// it_value: 3 秒后超时
timer.it_value.tv_sec = 3;
timer.it_value.tv_usec = 0;
// it_interval: 0 表示一次性计时器,超时后不重载
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;

printf("Setting ITIMER_REAL to expire in 3 seconds.\n");

// 3. 启动计时器
if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
perror("setitimer");
exit(EXIT_FAILURE);
}

// 4. 模拟一个可能长时间运行的操作
write(STDOUT_FILENO, msg, 46);

// 5. 等待操作完成或超时
// 这里用 pause() 模拟等待,实际可能是 read(), write(), connect() 等阻塞调用
while (!alarm_received) {
// 在实际应用中,这里可能是真正的阻塞操作
// 为了演示,我们用 pause() 等待信号
printf("Waiting for work to complete or timeout...\n");
pause(); // 进程挂起,直到收到信号
}

write(STDOUT_FILENO, msg_len, 6);
printf("Program finished after timeout.\n");

// 6. (可选) 检查计时器状态
struct itimerval curr_timer;
if (getitimer(ITIMER_REAL, &curr_timer) == -1) {
perror("getitimer");
} else {
printf("Current ITIMER_REAL setting:\n");
printf(" it_value: %ld.%06ld seconds (should be 0)\n",
(long)curr_timer.it_value.tv_sec, (long)curr_timer.it_value.tv_usec);
printf(" it_interval: %ld.%06ld seconds\n",
(long)curr_timer.it_interval.tv_sec, (long)curr_timer.it_interval.tv_usec);
}

return 0;
}

代码解释:

定义一个全局的 volatile sig_atomic_t 变量 alarm_received 用于信号处理函数和主循环间通信。

定义 SIGALRM 的信号处理函数 alarm_handler。当计时器超时,内核会发送 SIGALRM 信号,该函数会被调用,打印消息并设置标志。

在 main 函数中,使用 signal() 注册 SIGALRM 处理函数。

定义一个 struct itimerval 变量 timer。

设置 timer.it_value 为 3 秒,表示 3 秒后第一次超时。

设置 timer.it_interval 为 0,表示这是一次性计时器。

调用 setitimer(ITIMER_REAL, &timer, NULL) 启动计时器。

模拟一个长时间运行的操作(这里只是打印一条消息)。

进入一个循环,等待操作完成或超时。这里用 pause() 模拟等待,实际应用中可能是 read, write, connect 等阻塞调用。

当 SIGALRM 信号到达,alarm_handler 被调用,设置 alarm_received 为 1,主循环退出。

程序结束。

最后,调用 getitimer 检查计时器的当前状态(超时后,it_value 应该接近 0)。

示例 2:使用 ITIMER_VIRTUAL 测量 CPU 时间

这个例子演示如何使用 ITIMER_VIRTUAL 来测量进程在用户态执行代码所花费的 CPU 时间。

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
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h> // clock()

volatile sig_atomic_t vt_alarm = 0;

void vt_alarm_handler(int sig) {
write(STDERR_FILENO, "Virtual timer expired! (SIGVTALRM)\n", 35);
vt_alarm = 1;
}

// 模拟一个消耗 CPU 的函数
void cpu_intensive_task() {
volatile unsigned long sum = 0;
for (unsigned long i = 0; i < 1000000000UL; ++i) {
sum += i;
}
// 使用 sum 防止编译器优化掉循环
if (sum % 2 == 0) {
// do nothing
}
}

int main() {
struct itimerval timer;

if (signal(SIGVTALRM, vt_alarm_handler) == SIG_ERR) {
perror("signal SIGVTALRM");
exit(EXIT_FAILURE);
}

// 设置 ITIMER_VIRTUAL 在 2 秒 CPU 用户态时间后超时
timer.it_value.tv_sec = 2;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0; // 一次性
timer.it_interval.tv_usec = 0;

printf("Setting ITIMER_VIRTUAL to expire after 2 seconds of user CPU time.\n");
printf("Starting CPU-intensive task...\n");

if (setitimer(ITIMER_VIRTUAL, &timer, NULL) == -1) {
perror("setitimer ITIMER_VIRTUAL");
exit(EXIT_FAILURE);
}

// 执行消耗 CPU 的任务
cpu_intensive_task();

if (vt_alarm) {
printf("Task was interrupted by virtual timer.\n");
} else {
printf("Task completed before virtual timer expired.\n");
}

// 检查计时器状态
if (getitimer(ITIMER_VIRTUAL, &timer) == -1) {
perror("getitimer ITIMER_VIRTUAL");
} else {
printf("ITIMER_VIRTUAL status after task:\n");
printf(" it_value: %ld.%06ld seconds\n",
(long)timer.it_value.tv_sec, (long)timer.it_value.tv_usec);
printf(" it_interval: %ld.%06ld seconds\n",
(long)timer.it_interval.tv_sec, (long)timer.it_interval.tv_usec);
}

return 0;
}

代码解释:

设置 SIGVTALRM 信号处理函数。

配置 ITIMER_VIRTUAL 计时器,在进程使用 2 秒用户态 CPU 时间后超时。

调用 setitimer 启动计时器。

执行一个消耗大量 CPU 时间的函数 cpu_intensive_task。

如果在函数执行完毕前计时器超时,vt_alarm 标志会被设置,表示任务被中断。

最后检查计时器状态。

示例 3:使用 ITIMER_REAL 实现周期性操作

这个例子演示如何使用 ITIMER_REAL 实现一个周期性的“心跳”信号,每秒触发一次。

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

volatile atomic_int heartbeat_count = 0;

void heartbeat_handler(int sig) {
// 原子性地增加计数
atomic_fetch_add(&heartbeat_count, 1);
// write 是异步信号安全的
write(STDERR_FILENO, "Heartbeat (SIGALRM)!\n", 21);
}

int main() {
struct itimerval timer;
int count_before_exit = 5;

if (signal(SIGALRM, heartbeat_handler) == SIG_ERR) {
perror("signal SIGALRM");
exit(EXIT_FAILURE);
}

// 设置周期性计时器:立即启动,每 1 秒触发一次
timer.it_value.tv_sec = 1; // 1 秒后第一次超时
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 1; // 之后每 1 秒超时一次 (周期性)
timer.it_interval.tv_usec = 0;

printf("Setting periodic ITIMER_REAL (heartbeat every 1 second)...\n");
printf("Will exit after %d heartbeats.\n", count_before_exit);

if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
perror("setitimer periodic");
exit(EXIT_FAILURE);
}

// 等待指定次数的心跳
while (atomic_load(&heartbeat_count) < count_before_exit) {
pause(); // 等待信号
}

printf("Received %d heartbeats. Exiting.\n", count_before_exit);

// 停止计时器 (设置所有时间为 0)
struct itimerval stop_timer = {{0, 0}, {0, 0}};
if (setitimer(ITIMER_REAL, &stop_timer, NULL) == -1) {
perror("setitimer stop");
}

return 0;
}

代码解释:

使用 atomic_int 和 atomic_fetch_add 来安全地在信号处理函数中增加计数。

配置 ITIMER_REAL 计时器:

  • it_value: 1 秒后第一次超时。

  • it_interval: 1 秒,使计时器周期性地每秒超时一次。

调用 setitimer 启动周期性计时器。

在主循环中使用 pause() 等待信号。

每次收到 SIGALRM 信号,信号处理函数打印消息并增加计数。

当计数达到预定值时,主循环退出。

通过设置计时器的 it_value 和 it_interval 都为 0 来停止计时器。

示例 4:使用 setitimer 的 old_value 参数

这个例子演示如何使用 old_value 参数来保存和恢复计时器的旧设置。

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

void alarm_handler(int sig) {
write(STDERR_FILENO, "SIGALRM received.\n", 18);
}

void print_timer(const char *name, const struct itimerval *timer) {
printf("%s:\n", name);
printf(" Current value: %ld.%06ld seconds\n",
(long)timer->it_value.tv_sec, (long)timer->it_value.tv_usec);
printf(" Interval: %ld.%06ld seconds\n",
(long)timer->it_interval.tv_sec, (long)timer->it_interval.tv_usec);
}

int main() {
struct itimerval old_timer, new_timer;

if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
perror("signal SIGALRM");
exit(EXIT_FAILURE);
}

// 1. 设置一个初始的计时器 (5秒后超时,不重复)
new_timer.it_value.tv_sec = 5;
new_timer.it_value.tv_usec = 0;
new_timer.it_interval.tv_sec = 0;
new_timer.it_interval.tv_usec = 0;

printf("Setting initial timer for 5 seconds.\n");
if (setitimer(ITIMER_REAL, &new_timer, NULL) == -1) {
perror("setitimer initial");
exit(EXIT_FAILURE);
}

sleep(2); // 等待 2 秒

// 2. 获取当前计时器状态 (应该剩余约 3 秒)
if (getitimer(ITIMER_REAL, &old_timer) == -1) {
perror("getitimer before override");
exit(EXIT_FAILURE);
}
print_timer("Timer status after 2 seconds", &old_timer);

// 3. 设置一个新计时器 (1秒后超时),并保存旧设置
new_timer.it_value.tv_sec = 1;
new_timer.it_value.tv_usec = 0;
new_timer.it_interval.tv_sec = 0;
new_timer.it_interval.tv_usec = 0;

printf("\nOverriding timer to expire in 1 second, saving old setting.\n");
if (setitimer(ITIMER_REAL, &new_timer, &old_timer) == -1) {
perror("setitimer override");
exit(EXIT_FAILURE);
}

print_timer("Old timer setting saved", &old_timer);

printf("Waiting for 1-second timer to expire...\n");
pause(); // 等待 1 秒计时器超时

// 4. 恢复旧的计时器设置
printf("\nRestoring old timer setting.\n");
if (setitimer(ITIMER_REAL, &old_timer, NULL) == -1) {
perror("setitimer restore");
exit(EXIT_FAILURE);
}

if (getitimer(ITIMER_REAL, &new_timer) == -1) {
perror("getitimer after restore");
exit(EXIT_FAILURE);
}
print_timer("Timer status after restore", &new_timer);

printf("Waiting for restored timer to expire...\n");
pause(); // 等待恢复的计时器超时

printf("Restored timer expired. Program finished.\n");
return 0;
}

代码解释:

设置一个 5 秒后超时的初始计时器。

等待 2 秒后,调用 getitimer 获取当前计时器状态(剩余约 3 秒)。

调用 setitimer(ITIMER_REAL, &new_timer, &old_timer) 设置一个 1 秒后超时的新计时器,并将旧计时器的设置(剩余约 3 秒)保存在 old_timer 变量中。

等待 1 秒计时器超时。

调用 setitimer(ITIMER_REAL, &old_timer, NULL) 将计时器恢复为之前保存的设置(约 3 秒)。

再次调用 getitimer 验证恢复是否成功。

等待恢复的计时器超时。

重要提示与注意事项:

精度: setitimer 的时间精度是微秒(tv_usec),而 nanosleep 和 POSIX 定时器 (timer_) 支持纳秒精度。对于高精度需求,后者是更好的选择。

信号处理: 使用 setitimer 必然涉及信号处理。必须小心编写信号处理函数,只使用异步信号安全的函数。

竞态条件: 在设置信号处理函数和启动计时器之间,或者在检查标志和调用 pause 之间,可能存在竞态条件。使用 sigsuspend 可以更安全地处理。

现代替代: timer_create, timer_settime 等 POSIX 定时器函数提供了更强大和灵活的功能,例如可以选择不同的通知方式(信号、线程特定信号、过期计数等)和不同的时钟源(CLOCK_REALTIME, CLOCK_MONOTONIC 等),是 setitimer 的推荐现代替代方案。

总结:

getitimer 和 setitimer 提供了一套基于信号的进程间隔计时器机制。它们可以用于实现超时、周期性任务和 CPU 时间测量等功能。理解三种计时器类型(ITIMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF)及其对应的信号是掌握这些函数的关键。虽然它们功能强大,但在现代编程中,timer_ 系列的 POSIX 定时器通常被认为是更优的选择。

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

https://www.calcguide.tech/2025/09/09/gettitimer-syscall-demo/

fremovexattr系统调用及示例

fremovexattr - 删除文件的扩展属性(通过文件描述符)

1. 函数介绍

fremovexattr 是一个 Linux 系统调用,用于删除指定文件的特定扩展属性(extended attribute)。与 removexattr 不同,fremovexattr 通过文件描述符而不是文件路径来操作文件,这样可以避免在多线程环境中因文件重命名或删除而导致的竞态条件。

扩展属性是文件系统提供的一种机制,允许用户为文件关联额外的元数据,这些元数据以键值对的形式存储。删除扩展属性可以清理不再需要的元数据信息。

2. 函数原型

1
2
3
4
5
#include <sys/types.h>
#include <attr/xattr.h>

int fremovexattr(int fd, const char *name);

3. 功能

删除通过文件描述符指定的文件的指定名称的扩展属性。如果该属性不存在,则返回错误。

4. 参数

  • int fd: 文件描述符,通过 open() 等函数获得

const char *name: 要删除的扩展属性的名称(包括命名空间前缀)

  • 例如:”user.my_attribute”, “security.selinux”, “trusted.my_trusted_attr”

5. 返回值

  • 成功时返回 0

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

6. 常见 errno 错误码

  • EBADF: 无效的文件描述符

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

  • EACCES: 权限不足(删除某些命名空间的属性需要特殊权限)

  • ENODATA: 指定的扩展属性不存在

  • ENOTDIR: 文件描述符指向的不是目录(在某些情况下)

  • EPERM: 操作被拒绝(如尝试删除系统保护的属性)

  • EROFS: 文件位于只读文件系统上

  • ENOMEM: 内存不足

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

  • removexattr(): 通过文件路径删除扩展属性

  • lremovexattr(): 删除符号链接本身的扩展属性(不跟随链接)

  • fgetxattr(): 通过文件描述符获取扩展属性值

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

  • flistxattr(): 通过文件描述符列出所有扩展属性名称

  • getxattr(), setxattr(), listxattr(): 对应的路径版本

8. 示例代码

示例1:基本使用 - 删除扩展属性

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

int main() {
int fd;
int ret;

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

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

// 先设置一个扩展属性
const char *attr_name = "user.test_attribute";
const char *attr_value = "This is a test value";

ret = fsetxattr(fd, attr_name, attr_value, strlen(attr_value), 0);
if (ret == -1) {
perror("设置扩展属性失败");
close(fd);
exit(EXIT_FAILURE);
}

printf("成功设置扩展属性: %s = %s\n", attr_name, attr_value);

// 验证属性是否存在
char buffer&#91;256];
ssize_t size = fgetxattr(fd, attr_name, buffer, sizeof(buffer) - 1);
if (size != -1) {
buffer&#91;size] = '\0';
printf("验证 - 属性值: %s\n", buffer);
}

// 删除扩展属性
ret = fremovexattr(fd, attr_name);
if (ret == -1) {
perror("删除扩展属性失败");
} else {
printf("成功删除扩展属性: %s\n", attr_name);
}

// 再次尝试获取已删除的属性(应该失败)
size = fgetxattr(fd, attr_name, buffer, sizeof(buffer) - 1);
if (size == -1) {
if (errno == ENODATA) {
printf("确认:扩展属性 %s 已被成功删除\n", attr_name);
} else {
perror("获取已删除属性时出现意外错误");
}
}

close(fd);
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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <attr/xattr.h>
#include <errno.h>
#include <string.h>

void test_remove_attribute(int fd, const char *attr_name) {
printf("\n尝试删除属性: %s\n", attr_name);

int ret = fremovexattr(fd, attr_name);
if (ret == -1) {
switch (errno) {
case ENODATA:
printf(" 错误: 属性 '%s' 不存在\n", attr_name);
break;
case EACCES:
printf(" 错误: 权限不足,无法删除属性 '%s'\n", attr_name);
break;
case ENOTSUP:
printf(" 错误: 文件系统不支持扩展属性\n");
break;
case EPERM:
printf(" 错误: 操作被拒绝,无法删除属性 '%s'\n", attr_name);
break;
case EROFS:
printf(" 错误: 文件系统为只读,无法删除属性\n");
break;
default:
printf(" 错误: %s (errno: %d)\n", strerror(errno), errno);
break;
}
} else {
printf(" 成功删除属性: %s\n", attr_name);
}
}

int main() {
int fd;

// 打开系统文件进行测试(需要适当权限)
fd = open("test_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
exit(EXIT_FAILURE);
}

printf("成功创建测试文件,文件描述符: %d\n", fd);

// 设置一些测试属性
const char *user_attr = "user.test_attr";
const char *value = "test value";

if (fsetxattr(fd, user_attr, value, strlen(value), 0) == -1) {
perror("设置测试属性失败");
} else {
printf("设置测试属性: %s\n", user_attr);
}

// 测试删除存在的属性
test_remove_attribute(fd, user_attr);

// 测试删除不存在的属性
test_remove_attribute(fd, "user.nonexistent_attr");

// 测试删除无效命名空间的属性
test_remove_attribute(fd, "invalid.namespace.attr");

// 测试删除空名称的属性
test_remove_attribute(fd, "");

close(fd);
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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <attr/xattr.h>
#include <errno.h>
#include <string.h>

// 列出并删除所有 user.* 命名空间的属性
int remove_user_attributes(int fd) {
ssize_t list_size;
char *list_buffer;
char *current;
int removed_count = 0;

// 获取扩展属性列表大小
list_size = flistxattr(fd, NULL, 0);
if (list_size == -1) {
perror("获取属性列表大小失败");
return -1;
}

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

// 分配缓冲区
list_buffer = malloc(list_size);
if (list_buffer == NULL) {
perror("内存分配失败");
return -1;
}

// 获取扩展属性列表
if (flistxattr(fd, list_buffer, list_size) == -1) {
perror("获取属性列表失败");
free(list_buffer);
return -1;
}

// 遍历所有属性,删除 user.* 命名空间的属性
current = list_buffer;
while (current < list_buffer + list_size) {
// 检查是否为 user. 命名空间
if (strncmp(current, "user.", 5) == 0) {
printf("删除 user 属性: %s\n", current);

if (fremovexattr(fd, current) == -1) {
fprintf(stderr, "删除属性 %s 失败: %s\n", current, strerror(errno));
} else {
removed_count++;
}
} else {
printf("跳过非 user 属性: %s\n", current);
}

current += strlen(current) + 1;
}

free(list_buffer);
return removed_count;
}

int main() {
int fd;
int result;

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

printf("创建测试文件,文件描述符: %d\n", fd);

// 设置多个不同命名空间的属性
const char *attrs&#91;]&#91;2] = {
{"user.attr1", "value1"},
{"user.attr2", "value2"},
{"user.backup_info", "backup data"},
{"trusted.admin_note", "admin only"},
{"security.context", "security label"}
};

int attr_count = sizeof(attrs) / sizeof(attrs&#91;0]);

printf("设置测试属性:\n");
for (int i = 0; i < attr_count; i++) {
if (fsetxattr(fd, attrs&#91;i]&#91;0], attrs&#91;i]&#91;1], strlen(attrs&#91;i]&#91;1]), 0) == -1) {
fprintf(stderr, "设置属性 %s 失败: %s\n", attrs&#91;i]&#91;0], strerror(errno));
} else {
printf(" %s = %s\n", attrs&#91;i]&#91;0], attrs&#91;i]&#91;1]);
}
}

// 列出所有属性
printf("\n当前所有扩展属性:\n");
ssize_t list_size = flistxattr(fd, NULL, 0);
if (list_size > 0) {
char *list_buffer = malloc(list_size);
if (list_buffer) {
flistxattr(fd, list_buffer, list_size);
char *current = list_buffer;
while (current < list_buffer + list_size) {
printf(" %s\n", current);
current += strlen(current) + 1;
}
free(list_buffer);
}
}

// 删除所有 user.* 属性
printf("\n删除 user.* 命名空间的属性:\n");
result = remove_user_attributes(fd);
if (result >= 0) {
printf("成功删除 %d 个 user.* 属性\n", result);
}

// 验证剩余属性
printf("\n删除后的扩展属性:\n");
list_size = flistxattr(fd, NULL, 0);
if (list_size > 0) {
char *list_buffer = malloc(list_size);
if (list_buffer) {
flistxattr(fd, list_buffer, list_size);
char *current = list_buffer;
while (current < list_buffer + list_size) {
printf(" %s\n", current);
current += strlen(current) + 1;
}
free(list_buffer);
}
}

close(fd);
return 0;
}

9. 扩展属性命名空间权限说明

不同命名空间的扩展属性有不同的权限要求:

  • user.*: 普通用户可以读写自己文件的属性

  • trusted.*: 需要特权权限(CAP_SYS_ADMIN)才能访问

  • system.*: 系统内部使用,通常需要特殊权限

  • security.*: 安全相关,可能需要特定安全模块的权限

10. 实际应用场景

fremovexattr 常用于以下场景:

  • 清理文件的自定义元数据

  • 移除备份工具添加的临时标记

  • 删除安全标签或访问控制信息

  • 文件管理工具的属性清理功能

  • 系统维护和清理脚本

总结

fremovexattr 是管理文件扩展属性的重要函数,通过文件描述符提供了安全的删除接口。使用时需要注意:

确保属性名称完整且正确(包括命名空间前缀)

处理属性不存在的情况(ENODATA 错误)

注意不同命名空间的权限要求

在只读文件系统上操作会失败(EROFS)

正确处理各种可能的错误情况

在多线程环境中使用文件描述符可以避免竞态条件

扩展属性的删除操作是文件元数据管理的重要组成部分,在现代 Linux 系统中被广泛应用于各种高级文件管理场景。

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

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