sendmsg系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 recvmsg 和 sendmsg 函数,它们是功能最强大、最通用的套接字 I/O 函数,可以处理 read/write、send/recv 以及 sendto/recvfrom 的所有功能,并且还支持更高级的特性,如传输文件描述符、**发送和接收访问控制列表 **(ancillary data)。

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

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

1. 函数介绍

recvmsg 和 sendmsg 是 Linux 系统调用,它们提供了最灵活和最完整的套接字数据传输接口。它们是 read/write、send/recv、sendto/recvfrom 的超集。

sendmsg: 通过套接字发送数据。它允许你指定:

  • 要发送的数据(可以来自多个不连续的缓冲区,类似 writev)。

  • 目标地址(类似 sendto)。

  • 各种控制选项和标志。

  • 辅助数据(ancillary data),例如要通过 Unix 域套接字传递的文件描述符。

recvmsg: 通过套接字接收数据。它允许你:

  • 将数据接收存储到多个不连续的缓冲区(类似 readv)。

  • 获取数据的来源地址(类似 recvfrom)。

  • 获取各种套接字状态信息。

  • 接收辅助数据(ancillary data),例如通过 Unix 域套接字传递的文件描述符。

你可以把它们想象成一个多功能的包裹处理系统:

  • 一个包裹(消息)可以包含主货物(常规数据)和特殊附件(辅助数据,如文件描述符)。

  • 主货物可以放在一个或多个箱子(缓冲区)里。

  • 包裹上贴有收件人地址(目标地址,用于发送)或发件人地址(源地址,用于接收)。

  • 包裹上还有特殊标记(标志位)指示如何处理它。

2. 函数原型

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

// 发送消息
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

// 接收消息
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

3. 功能

  • sendmsg: 根据 struct msghdr 结构中描述的所有信息(数据缓冲区、目标地址、辅助数据、标志)来构建并发送一个消息。

  • recvmsg: 从套接字接收一个消息,并将接收到的数据、源地址、辅助数据等信息填充到 struct msghdr 结构指向的缓冲区中。

4. 参数

这两个函数都通过一个核心的 struct msghdr 结构体来传递所有必要的信息。

struct msghdr 结构体

这是两个函数的核心,定义了消息的完整属性:

1
2
3
4
5
6
7
8
9
10
struct msghdr {
void *msg_name; // 可选的地址 (struct sockaddr*)
socklen_t msg_namelen; // 地址长度
struct iovec *msg_iov; // 缓冲区向量 (scatter/gather)
size_t msg_iovlen; // 缓冲区向量的元素个数
void *msg_control; // 辅助数据 (cmsghdr*)
size_t msg_controllen; // 辅助数据缓冲区大小
int msg_flags; // 接收消息时的标志 (输出)
};

void *msg_name:

  • 发送时: 指向目标地址结构(如 sockaddr_in)的指针。对于面向连接的套接字(如 TCP),通常设为 NULL。

  • 接收时: 指向用于存储源地址结构的缓冲区的指针。

socklen_t msg_namelen:

  • 发送时: msg_name 指向的地址结构的大小。

  • 接收时: 输入时为 msg_name 缓冲区的大小;返回时为实际存储的地址结构的大小。

struct iovec *msg_iov: 这是一个 struct iovec 数组的指针,用于实现分散-聚集 I/O(scatter-gather I/O),即数据可以来自多个不连续的缓冲区(类似 readv/writev)。struct iovec 定义如下:struct iovec { void *iov_base; // 缓冲区起始地址 size_t iov_len; // 缓冲区长度 };

size_t msg_iovlen: msg_iov 数组中元素的个数。

void *msg_control: 指向用于发送或接收辅助数据(ancillary data)的缓冲区。辅助数据可以包含文件描述符、网络接口索引、IP 选项等。

size_t msg_controllen:

  • 发送时: msg_control 缓冲区的大小。

  • 接收时: 输入时为 msg_control 缓冲区的大小;返回时为实际接收到的辅助数据的大小。

int msg_flags:

  • 发送时: 传递额外的发送标志(通常与 sendto/send 的 flags 参数相同)。

  • 接收时: 返回接收操作时应用的标志(例如,如果数据包被截断,可能会设置 MSG_TRUNC)。

函数参数

  • int sockfd: 一个有效的套接字文件描述符。

  • const struct msghdr *msg (sendmsg) / struct msghdr *msg (recvmsg): 指向描述消息属性的 msghdr 结构体的指针。

int flags: 控制发送或接收行为的标志位,与 send/recv/sendto/recvfrom 的 flags 参数类似。

  • 常见标志:MSG_DONTWAIT, MSG_PEEK, MSG_WAITALL, MSG_NOSIGNAL 等。

5. 返回值

成功时:

  • 返回实际传输的字节数(即所有 msg_iov 缓冲区中数据的总和)。

失败时:

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

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

  • send / sendto / recv / recvfrom: sendmsg/recvmsg 是这些函数的通用形式。

  • writev / readv: sendmsg/recvmsg 结合 iovec 实现了类似的功能。

  • sendmmsg / recvmmsg: (Linux 特有) 可以在一次系统调用中发送或接收多个消息,性能更高。

  • CMSG_* 宏: 用于处理 msg_control 中的辅助数据(如 CMSG_FIRSTHDR, CMSG_NXTHDR, CMSG_DATA)。

7. 示例代码

示例 1:使用 sendmsg/recvmsg 替代 sendto/recvfrom (UDP)

这个例子展示了如何用 sendmsg 和 recvmsg 实现与 sendto 和 recvfrom 相同的功能(发送和接收 UDP 数据报)。

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

#define SERVER_PORT 8082
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024

int main() {
int sock;
struct sockaddr_in server_addr;
struct msghdr msg;
struct iovec iov&#91;1]; // 只使用一个缓冲区
char *message = "Hello from sendmsg/recvmsg client!";
char buffer&#91;BUFFER_SIZE];
ssize_t bytes_sent, bytes_received;

// 1. 创建 UDP 套接字
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}

// 2. 配置服务器地址
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 address\n");
close(sock);
exit(EXIT_FAILURE);
}

// --- 使用 sendmsg 发送 ---
printf("Sending message using sendmsg...\n");

// 准备 iovec
iov&#91;0].iov_base = message;
iov&#91;0].iov_len = strlen(message);

// 准备 msghdr
memset(&msg, 0, sizeof(msg));
msg.msg_name = &server_addr; // 目标地址
msg.msg_namelen = sizeof(server_addr);
msg.msg_iov = iov; // 数据缓冲区向量
msg.msg_iovlen = 1; // 向量元素个数

// 发送消息
bytes_sent = sendmsg(sock, &msg, 0);
if (bytes_sent < 0) {
perror("sendmsg failed");
close(sock);
exit(EXIT_FAILURE);
} else {
printf("sendmsg sent %zd bytes.\n", bytes_sent);
}

// --- 使用 recvmsg 接收 ---
printf("Receiving reply using recvmsg...\n");

struct sockaddr_in src_addr;
socklen_t src_addr_len = sizeof(src_addr);

// 准备 iovec for recv
iov&#91;0].iov_base = buffer;
iov&#91;0].iov_len = BUFFER_SIZE - 1;

// 准备 msghdr for recv
memset(&msg, 0, sizeof(msg));
msg.msg_name = &src_addr; // 用于存储源地址
msg.msg_namelen = src_addr_len; // 输入:缓冲区大小
msg.msg_iov = iov;
msg.msg_iovlen = 1;

// 接收消息
bytes_received = recvmsg(sock, &msg, 0);
if (bytes_received < 0) {
perror("recvmsg failed");
close(sock);
exit(EXIT_FAILURE);
} else {
buffer&#91;bytes_received] = '\0';
printf("recvmsg received %zd bytes from %s:%d: %s\n",
bytes_received,
inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port),
buffer);
// msg.msg_namelen 现在包含实际的地址大小
}

close(sock);
return 0;
}

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
// msg_udp_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 8082
#define BUFFER_SIZE 1024

int main() {
int server_fd;
struct sockaddr_in server_addr, client_addr;
struct msghdr msg;
struct iovec iov&#91;1];
char buffer&#91;BUFFER_SIZE];
char reply&#91;] = "Echo via sendmsg: ";
char reply_buffer&#91;BUFFER_SIZE];
ssize_t bytes_received, bytes_sent;

server_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_fd < 0) {
perror("socket creation failed");
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(PORT);

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

printf("UDP server using sendmsg/recvmsg listening on port %d\n", PORT);

while (1) {
printf("Waiting for datagram...\n");

socklen_t client_addr_len = sizeof(client_addr);
iov&#91;0].iov_base = buffer;
iov&#91;0].iov_len = BUFFER_SIZE - 1;

memset(&msg, 0, sizeof(msg));
msg.msg_name = &client_addr;
msg.msg_namelen = client_addr_len;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

bytes_received = recvmsg(server_fd, &msg, 0);
if (bytes_received < 0) {
perror("recvmsg failed");
continue;
}

buffer&#91;bytes_received] = '\0';
printf("Received %zd bytes from %s:%d: %s\n",
bytes_received,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),
buffer);

// 构造回复
int reply_len = snprintf(reply_buffer, BUFFER_SIZE, "%s%s", reply, buffer);
if (reply_len >= BUFFER_SIZE) reply_len = BUFFER_SIZE - 1;

// 使用 sendmsg 发送回复
iov&#91;0].iov_base = reply_buffer;
iov&#91;0].iov_len = reply_len;

memset(&msg, 0, sizeof(msg));
msg.msg_name = &client_addr; // 发送到刚才接收数据的客户端
msg.msg_namelen = sizeof(client_addr);
msg.msg_iov = iov;
msg.msg_iovlen = 1;

bytes_sent = sendmsg(server_fd, &msg, 0);
if (bytes_sent < 0) {
perror("sendmsg reply failed");
} else {
printf("Sent %zd bytes reply using sendmsg.\n", bytes_sent);
}
}

return 0;
}

代码解释:

客户端和服务器都创建 UDP 套接字并进行必要的设置(服务器需要 bind)。

发送数据:

  • 准备一个 struct iovec 数组。这里只用一个元素指向消息缓冲区。

  • 准备一个 struct msghdr 结构体 msg。

  • 设置 msg.msg_name 为目标地址,msg.msg_iov 为缓冲区向量,msg.msg_iovlen 为向量长度。

  • 调用 sendmsg(sock, &msg, 0) 发送数据。

接收数据:

  • 准备 struct iovec 和 struct msghdr。

  • 设置 msg.msg_name 为用于存储源地址的缓冲区,msg.msg_namelen 为该缓冲区大小。

  • 设置 msg.msg_iov 和 msg.msg_iovlen。

  • 调用 recvmsg(sock, &msg, 0) 接收数据。

  • 接收后,msg.msg_name 中包含了发送方的地址,msg.msg_namelen 被更新为实际地址大小。

示例 2:使用 sendmsg/recvmsg 进行 Scatter-Gather I/O

这个例子展示了如何使用 iovec 数组通过 sendmsg 发送来自多个缓冲区的数据。

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

#define SERVER_PORT 8083
#define SERVER_IP "127.0.0.1"

int main() {
int sock;
struct sockaddr_in server_addr;
struct msghdr msg;
struct iovec iov&#91;3]; // 使用 3 个缓冲区
char part1&#91;] = "Part1-";
char part2&#91;] = "Part2-";
char part3&#91;] = "Part3-END";
char recv_buffer&#91;1024];
ssize_t bytes_sent, bytes_received;

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

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 address\n");
close(sock);
exit(EXIT_FAILURE);
}

// 连接到服务器
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect failed");
close(sock);
exit(EXIT_FAILURE);
}

// --- 使用 sendmsg 发送分散的数据 ---
printf("Sending scattered data using sendmsg...\n");

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

// 准备 msghdr (TCP 不需要地址)
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 3;

bytes_sent = sendmsg(sock, &msg, 0);
if (bytes_sent < 0) {
perror("sendmsg failed");
close(sock);
exit(EXIT_FAILURE);
} else {
printf("sendmsg sent %zd bytes (should be sum of parts: %zu).\n",
bytes_sent, strlen(part1) + strlen(part2) + strlen(part3));
}

// 接收服务器的确认
bytes_received = recv(sock, recv_buffer, sizeof(recv_buffer) - 1, 0);
if (bytes_received > 0) {
recv_buffer&#91;bytes_received] = '\0';
printf("Received confirmation: %s\n", recv_buffer);
}

close(sock);
return 0;
}

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

#define PORT 8083
#define BACKLOG 10

int main() {
int server_fd, client_fd;
struct sockaddr_in address, client_address;
socklen_t client_addr_len = sizeof(client_address);
struct msghdr msg;
struct iovec iov&#91;3];
char buffer1&#91;50], buffer2&#91;50], buffer3&#91;50]; // 接收缓冲区
char confirmation&#91;] = "Data received successfully!";
int opt = 1;

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

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

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

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

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

printf("Scatter-Gather TCP server listening on port %d\n", PORT);

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

printf("Client connected. Waiting for scattered data...\n");

// --- 使用 recvmsg 接收数据到多个缓冲区 ---
iov&#91;0].iov_base = buffer1;
iov&#91;0].iov_len = sizeof(buffer1) - 1;
iov&#91;1].iov_base = buffer2;
iov&#91;1].iov_len = sizeof(buffer2) - 1;
iov&#91;2].iov_base = buffer3;
iov&#91;2].iov_len = sizeof(buffer3) - 1;

memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 3;

ssize_t bytes_received = recvmsg(client_fd, &msg, 0);
if (bytes_received < 0) {
perror("recvmsg failed");
close(client_fd);
close(server_fd);
exit(EXIT_FAILURE);
} else {
printf("recvmsg received %zd bytes.\n", bytes_received);
// 确保字符串结束 (实际应用中需要更仔细地处理)
size_t total_copied = 0;
for (int i = 0; i < 3 && total_copied < (size_t)bytes_received; ++i) {
size_t to_copy = iov&#91;i].iov_len < (size_t)(bytes_received - total_copied) ?
iov&#91;i].iov_len : (size_t)(bytes_received - total_copied);
((char*)iov&#91;i].iov_base)&#91;to_copy] = '\0';
total_copied += to_copy;
}
printf("Data in buffers:\n Buffer1: '%s'\n Buffer2: '%s'\n Buffer3: '%s'\n",
buffer1, buffer2, buffer3);
}

// 发送确认
send(client_fd, confirmation, strlen(confirmation), 0);

close(client_fd);
close(server_fd);
return 0;
}

代码解释:

客户端使用 TCP 连接到服务器。

客户端将一个消息分割成三个部分,存放在 part1, part2, part3 三个不同的缓冲区中。

发送: 使用 sendmsg 和包含三个元素的 iovec 数组,将这三个缓冲区的数据一次性发送出去。这避免了三次 send 调用。

服务器 accept 连接。

接收: 服务器使用 recvmsg 和包含三个元素的 iovec 数组,将接收到的数据分散存储到 buffer1, buffer2, buffer3 三个不同的缓冲区中。

服务器打印出存储在各个缓冲区中的数据。

示例 3:通过 Unix 域套接字传递文件描述符 (辅助数据)

这个例子展示了 sendmsg/recvmsg 最强大的功能之一:传递文件描述符。这是进程间传递打开文件的一种高级方法。

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
// fd_passing.c
// 需要编译为两个程序: sender 和 receiver
// gcc -o sender fd_passing.c -DSENDER
// gcc -o receiver fd_passing.c -DRECEIVER

#include <sys/socket.h>
#include <sys/un.h> // Unix domain sockets
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SOCKET_PATH "/tmp/fd_pass_socket"

#ifdef SENDER
int main() {
int sock, file_fd;
struct sockaddr_un addr;
struct msghdr msg;
struct cmsghdr *cmsg;
struct iovec iov&#91;1];
char ctrl_buf&#91;CMSG_SPACE(sizeof(int))]; // 为一个 int (fd) 分配控制消息空间
char data&#91;] = "FD";
int data_len = strlen(data);

// 1. 创建要传递的文件
file_fd = open("testfile.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (file_fd == -1) {
perror("open testfile.txt");
exit(EXIT_FAILURE);
}
write(file_fd, "This is data in the passed file.\n", 33);
printf("Created and wrote to 'testfile.txt' (fd: %d)\n", file_fd);

// 2. 创建 Unix 域套接字
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
perror("socket");
close(file_fd);
exit(EXIT_FAILURE);
}

// 3. 连接到接收方
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);

if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("connect");
close(file_fd);
close(sock);
exit(EXIT_FAILURE);
}
printf("Connected to receiver.\n");

// 4. 准备消息结构
iov&#91;0].iov_base = data;
iov&#91;0].iov_len = data_len;

memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = ctrl_buf;
msg.msg_controllen = sizeof(ctrl_buf);

// 5. 准备辅助数据 (控制消息)
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS; // 传递权限 (文件描述符)
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &file_fd, sizeof(int)); // 将文件描述符复制到控制消息数据部分

msg.msg_controllen = cmsg->cmsg_len; // 更新控制消息长度

// 6. 发送消息 (包含文件描述符)
if (sendmsg(sock, &msg, 0) == -1) {
perror("sendmsg");
close(file_fd);
close(sock);
exit(EXIT_FAILURE);
}

printf("Sent message with file descriptor %d.\n", file_fd);
// 注意:发送后,发送方通常应该 close 掉这个 fd
// 但在此例中我们不 close,以演示 fd 已被传递

close(sock);
// close(file_fd); // 通常在这里关闭,但我们想演示它已被传递
printf("Sender finished. Check if 'testfile.txt' is closed (lsof `pwd`/testfile.txt).\n");
return 0;
}

#elif defined(RECEIVER)
int main() {
int listen_sock, conn_sock, received_fd = -1;
struct sockaddr_un addr, client_addr;
socklen_t client_len;
struct msghdr msg;
struct cmsghdr *cmsg;
struct iovec iov&#91;1];
char ctrl_buf&#91;CMSG_SPACE(sizeof(int))];
char data&#91;10];
ssize_t data_len;

// 1. 创建 Unix 域套接字
listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (listen_sock == -1) {
perror("socket");
exit(EXIT_FAILURE);
}

// 清理可能存在的旧 socket 文件
unlink(SOCKET_PATH);

// 2. 绑定套接字
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);

if (bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
close(listen_sock);
exit(EXIT_FAILURE);
}

// 3. 监听
if (listen(listen_sock, 5) == -1) {
perror("listen");
close(listen_sock);
exit(EXIT_FAILURE);
}

printf("Receiver listening on %s\n", SOCKET_PATH);

// 4. 接受连接
client_len = sizeof(client_addr);
conn_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &client_len);
if (conn_sock == -1) {
perror("accept");
close(listen_sock);
exit(EXIT_FAILURE);
}
printf("Connection accepted.\n");

// 5. 准备接收消息
iov&#91;0].iov_base = data;
iov&#91;0].iov_len = sizeof(data) - 1;

memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = ctrl_buf;
msg.msg_controllen = sizeof(ctrl_buf);

// 6. 接收消息
data_len = recvmsg(conn_sock, &msg, 0);
if (data_len == -1) {
perror("recvmsg");
close(conn_sock);
close(listen_sock);
exit(EXIT_FAILURE);
}
data&#91;data_len] = '\0';
printf("Received data: %s\n", data);

// 7. 解析辅助数据 (控制消息) 以获取文件描述符
cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int)) &&
cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
printf("Received file descriptor: %d\n", received_fd);

// 8. 使用接收到的文件描述符
lseek(received_fd, 0, SEEK_SET); // 移到文件开头
char buf&#91;100];
ssize_t bytes_read = read(received_fd, buf, sizeof(buf) - 1);
if (bytes_read > 0) {
buf&#91;bytes_read] = '\0';
printf("Read from received fd: %s", buf);
}
printf("Closing received file descriptor.\n");
close(received_fd); // 关闭接收到的文件描述符
} else {
printf("No file descriptor received.\n");
}

close(conn_sock);
close(listen_sock);
unlink(SOCKET_PATH); // 清理 socket 文件
printf("Receiver finished.\n");
return 0;
}
#endif

编译和运行:

1
2
3
4
5
6
7
8
# Terminal 1
gcc -o receiver fd_passing.c -DRECEIVER
./receiver

# Terminal 2 (在 receiver 运行后)
gcc -o sender fd_passing.c -DSENDER
./sender

代码解释:

该代码通过宏定义 SENDER 和 RECEIVER 编译成两个不同的程序。

**发送方 **(SENDER)

  • 创建一个名为 testfile.txt 的文件并写入数据。

  • 创建一个 Unix 域套接字并连接到接收方。

  • 准备一个 struct msghdr。

  • 准备一个 iovec 来发送一些普通数据(“FD”)。

关键: 准备辅助数据(msg_control)。

  • 使用 CMSG_SPACE(sizeof(int)) 来分配足够的控制缓冲区。

  • 使用 CMSG_FIRSTHDR 获取第一个控制消息头指针。

  • 设置 cmsg_level 为 SOL_SOCKET,cmsg_type 为 SCM_RIGHTS(表示传递文件描述符)。

  • 使用 CMSG_DATA(cmsg) 获取数据部分的指针,并将 file_fd 复制进去。

调用 sendmsg 发送包含普通数据和辅助数据(文件描述符)的消息。

**接收方 **(RECEIVER)

  • 创建并监听一个 Unix 域套接字。

  • accept 连接。

  • 准备 struct msghdr 和用于接收辅助数据的控制缓冲区 ctrl_buf。

  • 调用 recvmsg 接收消息。

关键: 解析接收到的辅助数据。

  • 使用 CMSG_FIRSTHDR 获取第一个控制消息头。

  • 检查 cmsg_level, cmsg_type, cmsg_len 是否正确。

  • 如果正确,使用 CMSG_DATA(cmsg) 获取数据部分,并将文件描述符复制到 received_fd 变量中。

现在,接收方可以像使用自己打开的文件一样使用 received_fd。

最后关闭接收到的文件描述符和套接字。

重要提示与注意事项:

通用性: sendmsg/recvmsg 是最通用的套接字 I/O 函数,可以替代所有其他基本的发送和接收函数。

Scatter-Gather: 通过 iovec 数组,可以高效地处理不连续的数据缓冲区,减少系统调用次数。

地址处理: 对于面向连接的套接字(如 TCP),msg_name 通常为 NULL。对于无连接的(如 UDP),它用于指定目标或获取源地址。

辅助数据: 这是 sendmsg/recvmsg 独有的强大功能。正确处理辅助数据需要使用 CMSG_* 系列宏,这比较复杂但非常有用(如传递文件描述符、设置网络接口等)。

性能: 在需要处理大量数据或复杂消息结构时,sendmsg/recvmsg 可能比多次调用 send/recv 更高效。

sendmmsg/recvmmsg: 对于需要在一次系统调用中处理多个消息的高性能应用(如网络服务器),Linux 提供了这两个函数作为 sendmsg/recvmsg 的扩展。

总结:

sendmsg 和 recvmsg 是 Linux 套接字编程中最强大和灵活的 I/O 函数。它们不仅包含了其他所有基本套接字函数的功能,还引入了处理辅助数据的能力,这使得它们在高级进程间通信(如传递文件描述符)中不可或缺。虽然使用起来比 send/recv 复杂,但掌握它们对于编写高效、功能丰富的网络应用程序至关重要。

set_mempolicy系统调用及示例

set_mempolicy系统调用及示例

set_mempolicy 是Linux系统调用,用于设置进程或内存区域的内存分配策略。它是NUMA(Non-Uniform Memory Access)系统中重要的内存管理工具,允许应用程序指定内存分配的节点偏好,优化内存访问性能。通过合理的内存策略设置,可以显著提高NUMA系统的性能。

  1. 函数介绍

set_mempolicy 是Linux系统调用,用于设置进程或内存区域的内存分配策略。它是NUMA(Non-Uniform Memory Access)系统中重要的内存管理工具,允许应用程序指定内存分配的节点偏好,优化内存访问性能。通过合理的内存策略设置,可以显著提高NUMA系统的性能。

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

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

  1. 函数原型
1
2
3
4
#include <numaif.h>
long set_mempolicy(int mode, const unsigned long *nodemask,
unsigned long maxnode);

  1. 功能

set_mempolicy 设置当前进程的默认内存分配策略,影响后续的内存分配操作。它支持多种内存分配模式,允许指定特定的NUMA节点,帮助应用程序优化内存访问模式,减少跨节点内存访问的开销。

  1. 参数
  • int mode: 内存分配模式

  • *const unsigned long nodemask: NUMA节点掩码(可为NULL)

  • unsigned long maxnode: 节点掩码中的最大节点数

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

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

  1. 相似函数,或关联函数
  • get_mempolicy: 获取当前内存策略

  • mbind: 为特定内存区域设置策略

  • migrate_pages: 迁移进程页面到指定节点

  • numa_*: NUMA库函数

  1. 示例代码

示例1:基础set_mempolicy使用

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

/**
* 检查系统NUMA支持
*/
int check_numa_support() {
long max_nodes = sysconf(_SC_NPROCESSORS_ONLN);
printf("=== NUMA支持检查 ===\n");
printf("在线CPU数: %ld\n", max_nodes);

// 检查是否支持NUMA
int ret = get_mempolicy(NULL, NULL, 0, 0, 0);
if (ret == -1 && errno == ENOSYS) {
printf("系统不支持NUMA内存策略\n");
return -1;
}

printf("系统支持NUMA内存策略\n");
return 0;
}

/**
* 演示基础set_mempolicy使用方法
*/
int demo_set_mempolicy_basic() {
int mode;
unsigned long nodemask;
int ret;

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

// 检查NUMA支持
if (check_numa_support() != 0) {
return 0; // 不支持NUMA时正常退出
}

// 获取当前内存策略
ret = get_mempolicy(&mode, &nodemask, sizeof(nodemask) * 8, 0, 0);
if (ret == 0) {
printf("当前内存策略模式: %d\n", mode);
printf("当前节点掩码: 0x%lx\n", nodemask);
}

// 设置默认内存策略为MPOL_PREFERRED(首选节点)
printf("\n1. 设置首选节点策略:\n");
unsigned long preferred_node = 0; // 首选节点0
ret = set_mempolicy(MPOL_PREFERRED, &preferred_node, sizeof(preferred_node) * 8);
if (ret == 0) {
printf(" 成功设置首选节点策略,首选节点: %lu\n", preferred_node);
} else {
printf(" 设置首选节点策略失败: %s\n", strerror(errno));
if (errno == EINVAL) {
printf(" 可能的原因:系统不支持指定的策略或节点\n");
}
}

// 设置策略为MPOL_BIND(绑定节点)
printf("\n2. 设置绑定节点策略:\n");
unsigned long bind_nodes = 0x3; // 绑定到节点0和1
ret = set_mempolicy(MPOL_BIND, &bind_nodes, sizeof(bind_nodes) * 8);
if (ret == 0) {
printf(" 成功设置绑定节点策略,绑定节点: 0x%lx\n", bind_nodes);
} else {
printf(" 设置绑定节点策略失败: %s\n", strerror(errno));
}

// 设置策略为MPOL_INTERLEAVE(交错分配)
printf("\n3. 设置交错分配策略:\n");
unsigned long interleave_nodes = 0xF; // 交错分配到节点0-3
ret = set_mempolicy(MPOL_INTERLEAVE, &interleave_nodes, sizeof(interleave_nodes) * 8);
if (ret == 0) {
printf(" 成功设置交错分配策略,节点: 0x%lx\n", interleave_nodes);
} else {
printf(" 设置交错分配策略失败: %s\n", strerror(errno));
}

// 恢复默认策略
printf("\n4. 恢复默认策略:\n");
ret = set_mempolicy(MPOL_DEFAULT, NULL, 0);
if (ret == 0) {
printf(" 成功恢复默认内存策略\n");
} else {
printf(" 恢复默认策略失败: %s\n", strerror(errno));
}

return 0;
}

int main() {
return demo_set_mempolicy_basic();
}

示例2:不同内存策略演示

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

/**
* 显示当前内存策略
*/
void show_current_policy() {
int mode;
unsigned long nodemask = 0;
long maxnode = sizeof(nodemask) * 8;

if (get_mempolicy(&mode, &nodemask, maxnode, 0, 0) == 0) {
printf("当前策略: ");
switch (mode) {
case MPOL_DEFAULT:
printf("MPOL_DEFAULT (默认策略)\n");
break;
case MPOL_PREFERRED:
printf("MPOL_PREFERRED (首选节点)\n");
break;
case MPOL_BIND:
printf("MPOL_BIND (绑定节点)\n");
break;
case MPOL_INTERLEAVE:
printf("MPOL_INTERLEAVE (交错分配)\n");
break;
default:
printf("未知模式 (%d)\n", mode);
break;
}
if (nodemask != 0) {
printf("节点掩码: 0x%lx\n", nodemask);
}
}
}

/**
* 分配内存并检查分配位置
*/
void* allocate_and_check_memory(size_t size) {
void *ptr = malloc(size);
if (!ptr) {
printf("内存分配失败\n");
return NULL;
}

// 检查内存分配的节点位置(需要NUMA库支持)
printf(" 分配 %zu 字节内存\n", size);

return ptr;
}

/**
* 演示不同内存策略
*/
int demo_memory_policies() {
void *memory_blocks&#91;5];

printf("=== 不同内存策略演示 ===\n");

// 检查NUMA支持
if (get_mempolicy(NULL, NULL, 0, 0, 0) == -1 && errno == ENOSYS) {
printf("系统不支持NUMA,跳过演示\n");
return 0;
}

printf("系统支持NUMA内存策略\n\n");

// 1. 默认策略
printf("1. 默认策略 (MPOL_DEFAULT):\n");
show_current_policy();
memory_blocks&#91;0] = allocate_and_check_memory(1024 * 1024); // 1MB

// 2. 首选节点策略
printf("\n2. 首选节点策略 (MPOL_PREFERRED):\n");
unsigned long preferred_node = 0;
if (set_mempolicy(MPOL_PREFERRED, &preferred_node, sizeof(preferred_node) * 8) == 0) {
show_current_policy();
memory_blocks&#91;1] = allocate_and_check_memory(1024 * 1024);
} else {
printf(" 设置首选节点策略失败: %s\n", strerror(errno));
}

// 3. 绑定节点策略
printf("\n3. 绑定节点策略 (MPOL_BIND):\n");
unsigned long bind_nodes = 0x3; // 节点0和1
if (set_mempolicy(MPOL_BIND, &bind_nodes, sizeof(bind_nodes) * 8) == 0) {
show_current_policy();
memory_blocks&#91;2] = allocate_and_check_memory(1024 * 1024);
} else {
printf(" 设置绑定节点策略失败: %s\n", strerror(errno));
}

// 4. 交错分配策略
printf("\n4. 交错分配策略 (MPOL_INTERLEAVE):\n");
unsigned long interleave_nodes = 0xF; // 节点0-3
if (set_mempolicy(MPOL_INTERLEAVE, &interleave_nodes, sizeof(interleave_nodes) * 8) == 0) {
show_current_policy();
memory_blocks&#91;3] = allocate_and_check_memory(4 * 1024 * 1024); // 4MB
} else {
printf(" 设置交错分配策略失败: %s\n", strerror(errno));
}

// 5. 恢复默认策略
printf("\n5. 恢复默认策略:\n");
if (set_mempolicy(MPOL_DEFAULT, NULL, 0) == 0) {
show_current_policy();
memory_blocks&#91;4] = allocate_and_check_memory(1024 * 1024);
} else {
printf(" 恢复默认策略失败: %s\n", strerror(errno));
}

// 释放内存
for (int i = 0; i < 5; i++) {
if (memory_blocks&#91;i]) {
free(memory_blocks&#91;i]);
}
}

return 0;
}

int main() {
return demo_memory_policies();
}

示例3:NUMA感知内存分配

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

/**
* 获取系统NUMA信息
*/
void get_numa_info() {
printf("=== 系统NUMA信息 ===\n");

// 获取CPU数量
long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
printf("在线CPU数: %ld\n", nprocs);

// 获取页面大小
long page_size = sysconf(_SC_PAGESIZE);
printf("页面大小: %ld 字节\n", page_size);

// 显示当前CPU亲和性
cpu_set_t cpuset;
if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0) {
printf("当前CPU亲和性: ");
for (int i = 0; i < CPU_SETSIZE && i < 64; i++) {
if (CPU_ISSET(i, &cpuset)) {
printf("%d ", i);
}
}
printf("\n");
}
}

/**
* NUMA感知的大内存分配
*/
int demo_numa_aware_allocation() {
const size_t large_size = 100 * 1024 * 1024; // 100MB
char *large_buffer;
int ret;

printf("=== NUMA感知内存分配演示 ===\n");

// 检查NUMA支持
if (get_mempolicy(NULL, NULL, 0, 0, 0) == -1 && errno == ENOSYS) {
printf("系统不支持NUMA,使用默认分配\n");
large_buffer = malloc(large_size);
if (large_buffer) {
printf("成功分配 %zu MB 内存\n", large_size / (1024 * 1024));
free(large_buffer);
}
return 0;
}

get_numa_info();

printf("\n1. 使用默认策略分配大内存:\n");
show_current_policy();
large_buffer = malloc(large_size);
if (large_buffer) {
printf(" 成功分配 %zu MB 内存\n", large_size / (1024 * 1024));
// 初始化内存以确保实际分配
memset(large_buffer, 0, 1024);
free(large_buffer);
}

printf("\n2. 设置首选节点策略后分配:\n");
unsigned long preferred_node = 0;
ret = set_mempolicy(MPOL_PREFERRED, &preferred_node, sizeof(preferred_node) * 8);
if (ret == 0) {
show_current_policy();
large_buffer = malloc(large_size);
if (large_buffer) {
printf(" 成功分配 %zu MB 内存(倾向节点 %lu)\n",
large_size / (1024 * 1024), preferred_node);
memset(large_buffer, 0, 1024);
free(large_buffer);
}
}

printf("\n3. 设置绑定策略后分配:\n");
unsigned long bind_nodes = 0x1; // 仅绑定到节点0
ret = set_mempolicy(MPOL_BIND, &bind_nodes, sizeof(bind_nodes) * 8);
if (ret == 0) {
show_current_policy();
large_buffer = malloc(large_size);
if (large_buffer) {
printf(" 成功分配 %zu MB 内存(绑定到节点 0x%lx)\n",
large_size / (1024 * 1024), bind_nodes);
memset(large_buffer, 0, 1024);
free(large_buffer);
}
}

printf("\n4. 设置交错策略后分配:\n");
unsigned long interleave_nodes = 0x3; // 交错到节点0和1
ret = set_mempolicy(MPOL_INTERLEAVE, &interleave_nodes, sizeof(interleave_nodes) * 8);
if (ret == 0) {
show_current_policy();
large_buffer = malloc(large_size);
if (large_buffer) {
printf(" 成功分配 %zu MB 内存(交错到节点 0x%lx)\n",
large_size / (1024 * 1024), interleave_nodes);
memset(large_buffer, 0, 1024);
free(large_buffer);
}
}

// 恢复默认策略
set_mempolicy(MPOL_DEFAULT, NULL, 0);

return 0;
}

int main() {
return demo_numa_aware_allocation();
}

示例4:多线程NUMA策略

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
#include <numaif.h>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sched.h>

/**
* 线程数据结构
*/
typedef struct {
int thread_id;
int preferred_node;
size_t alloc_size;
void *memory;
int result;
} thread_data_t;

/**
* 线程函数:设置NUMA策略并分配内存
*/
void* thread_function(void *arg) {
thread_data_t *data = (thread_data_t*)arg;

printf("线程 %d 启动,设置首选节点 %d\n", data->thread_id, data->preferred_node);

// 设置线程的NUMA策略
unsigned long preferred_node = data->preferred_node;
if (set_mempolicy(MPOL_PREFERRED, &preferred_node, sizeof(preferred_node) * 8) != 0) {
printf("线程 %d: 设置策略失败: %s\n", data->thread_id, strerror(errno));
data->result = -1;
return NULL;
}

// 分配内存
data->memory = malloc(data->alloc_size);
if (data->memory) {
printf("线程 %d: 成功分配 %zu KB 内存\n",
data->thread_id, data->alloc_size / 1024);

// 初始化内存
memset(data->memory, data->thread_id, 1024);
data->result = 0;
} else {
printf("线程 %d: 内存分配失败\n", data->thread_id);
data->result = -1;
}

return NULL;
}

/**
* 演示多线程NUMA策略
*/
int demo_multithread_numa() {
const int num_threads = 4;
pthread_t threads&#91;num_threads];
thread_data_t thread_data&#91;num_threads];

printf("=== 多线程NUMA策略演示 ===\n");

// 检查NUMA支持
if (get_mempolicy(NULL, NULL, 0, 0, 0) == -1 && errno == ENOSYS) {
printf("系统不支持NUMA,跳过多线程演示\n");
return 0;
}

// 初始化线程数据
for (int i = 0; i < num_threads; i++) {
thread_data&#91;i].thread_id = i;
thread_data&#91;i].preferred_node = i % 2; // 交替使用节点0和1
thread_data&#91;i].alloc_size = (1024 + i * 512) * 1024; // 1-3MB
thread_data&#91;i].memory = NULL;
thread_data&#91;i].result = 0;
}

// 创建线程
printf("创建 %d 个线程,每个线程使用不同的NUMA节点策略\n", num_threads);

for (int i = 0; i < num_threads; i++) {
if (pthread_create(&threads&#91;i], NULL, thread_function, &thread_data&#91;i]) != 0) {
printf("创建线程 %d 失败\n", i);
return -1;
}
}

// 等待所有线程完成
for (int i = 0; i < num_threads; i++) {
pthread_join(threads&#91;i], NULL);
printf("线程 %d 完成,结果: %s\n",
i, thread_data&#91;i].result == 0 ? "成功" : "失败");
}

// 释放内存
for (int i = 0; i < num_threads; i++) {
if (thread_data&#91;i].memory) {
free(thread_data&#91;i].memory);
}
}

// 显示最终策略
printf("\n主线程最终策略:\n");
show_current_policy();

return 0;
}

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

int main() {
return demo_multithread_numa();
}

void show_current_policy() {
int mode;
unsigned long nodemask = 0;
long maxnode = sizeof(nodemask) * 8;

if (get_mempolicy(&mode, &nodemask, maxnode, 0, 0) == 0) {
printf("当前策略模式: %d", mode);
switch (mode) {
case MPOL_DEFAULT: printf(" (默认)"); break;
case MPOL_PREFERRED: printf(" (首选)"); break;
case MPOL_BIND: printf(" (绑定)"); break;
case MPOL_INTERLEAVE: printf(" (交错)"); break;
}
printf(", 节点掩码: 0x%lx\n", nodemask);
}
}

示例5:性能优化示例

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

/**
* 性能测试结构
*/
typedef struct {
const char *name;
int mode;
unsigned long nodemask;
double time_taken;
size_t memory_accessed;
} perf_test_t;

/**
* 内存访问性能测试
*/
double test_memory_access_performance(size_t size, int iterations) {
char *buffer;
struct timeval start, end;

// 分配测试内存
buffer = malloc(size);
if (!buffer) {
printf("内存分配失败\n");
return -1;
}

// 初始化内存
memset(buffer, 0, size);

// 开始性能测试
gettimeofday(&start, NULL);

// 执行内存访问测试
for (int i = 0; i < iterations; i++) {
// 顺序访问内存
for (size_t j = 0; j < size; j += 64) { // 64字节步长
buffer&#91;j] = (buffer&#91;j] + 1) % 256;
}
}

gettimeofday(&end, NULL);

// 计算耗时
double elapsed = (end.tv_sec - start.tv_sec) +
(end.tv_usec - start.tv_usec) / 1000000.0;

free(buffer);
return elapsed;
}

/**
* 演示不同策略的性能影响
*/
int demo_performance_impact() {
const size_t test_size = 50 * 1024 * 1024; // 50MB
const int iterations = 10;
perf_test_t tests&#91;4];

printf("=== NUMA策略性能影响演示 ===\n");
printf("测试内存大小: %zu MB\n", test_size / (1024 * 1024));
printf("测试迭代次数: %d\n", iterations);

// 检查NUMA支持
if (get_mempolicy(NULL, NULL, 0, 0, 0) == -1 && errno == ENOSYS) {
printf("系统不支持NUMA,跳过性能测试\n");
return 0;
}

// 初始化测试配置
tests&#91;0].name = "默认策略";
tests&#91;0].mode = MPOL_DEFAULT;
tests&#91;0].nodemask = 0;

tests&#91;1].name = "首选节点0";
tests&#91;1].mode = MPOL_PREFERRED;
tests&#91;1].nodemask = 0x1;

tests&#91;2].name = "绑定节点0";
tests&#91;2].mode = MPOL_BIND;
tests&#91;2].nodemask = 0x1;

tests&#91;3].name = "交错节点0-1";
tests&#91;3].mode = MPOL_INTERLEAVE;
tests&#91;3].nodemask = 0x3;

// 执行性能测试
for (int i = 0; i < 4; i++) {
printf("\n测试 %d: %s\n", i + 1, tests&#91;i].name);

// 设置内存策略
if (tests&#91;i].mode == MPOL_DEFAULT) {
if (set_mempolicy(MPOL_DEFAULT, NULL, 0) != 0) {
printf(" 设置策略失败: %s\n", strerror(errno));
continue;
}
} else {
if (set_mempolicy(tests&#91;i].mode, &tests&#91;i].nodemask,
sizeof(tests&#91;i].nodemask) * 8) != 0) {
printf(" 设置策略失败: %s\n", strerror(errno));
continue;
}
}

// 显示当前策略
show_current_policy();

// 执行性能测试
tests&#91;i].time_taken = test_memory_access_performance(test_size, iterations);
tests&#91;i].memory_accessed = test_size * iterations;

if (tests&#91;i].time_taken > 0) {
double bandwidth = (tests&#91;i].memory_accessed / (1024.0 * 1024.0)) /
tests&#91;i].time_taken;
printf(" 测试完成: %.3f 秒, 带宽: %.2f MB/s\n",
tests&#91;i].time_taken, bandwidth);
} else {
printf(" 测试失败\n");
}
}

// 显示性能对比结果
printf("\n=== 性能对比结果 ===\n");
double baseline_time = tests&#91;0].time_taken;

for (int i = 0; i < 4; i++) {
if (tests&#91;i].time_taken > 0) {
printf("%-15s: %.3f 秒", tests&#91;i].name, tests&#91;i].time_taken);
if (i > 0 && baseline_time > 0) {
double improvement = (baseline_time - tests&#91;i].time_taken) /
baseline_time * 100;
printf(" (%+.1f%%)", improvement);
}
printf("\n");
}
}

// 恢复默认策略
set_mempolicy(MPOL_DEFAULT, NULL, 0);

return 0;
}

void show_current_policy() {
int mode;
unsigned long nodemask = 0;
long maxnode = sizeof(nodemask) * 8;

if (get_mempolicy(&mode, &nodemask, maxnode, 0, 0) == 0) {
printf(" 当前策略: ");
switch (mode) {
case MPOL_DEFAULT: printf("默认"); break;
case MPOL_PREFERRED: printf("首选节点"); break;
case MPOL_BIND: printf("绑定节点"); break;
case MPOL_INTERLEAVE: printf("交错分配"); break;
default: printf("未知(%d)", mode); break;
}
if (nodemask != 0) {
printf(" (节点掩码: 0x%lx)", nodemask);
}
printf("\n");
}
}

int main() {
return demo_performance_impact();
}

set_mempolicy 使用注意事项

系统要求:

内核版本: 需要支持NUMA的Linux内核(2.6.7+)

硬件支持: 需要NUMA架构的硬件平台

编译选项: 需要链接NUMA库(-lnuma)

策略模式详解:

MPOL_DEFAULT: 使用系统默认策略

MPOL_PREFERRED: 首选指定节点,失败时使用其他节点

MPOL_BIND: 严格绑定到指定节点

MPOL_INTERLEAVE: 在指定节点间交错分配

参数验证:

mode有效性: 确保mode参数是有效的策略模式

nodemask合法性: 确保节点掩码指定的节点存在

maxnode范围: 确保不超过系统支持的最大节点数

错误处理:

ENOSYS: 系统不支持NUMA策略

EINVAL: 参数无效(模式或节点掩码)

ENOMEM: 内存不足

EPERM: 权限不足

性能考虑:

策略选择: 根据应用访问模式选择合适策略

节点亲和: 考虑CPU和内存节点的拓扑关系

内存局部性: 优化数据访问的局部性

最佳实践:

测试验证: 在实际硬件上测试策略效果

渐进应用: 从简单策略开始逐步优化

监控调优: 监控性能指标并调整策略

兼容处理: 处理不支持NUMA的系统环境

NUMA策略模式详解

MPOL_DEFAULT(默认模式):

  • 行为: 使用系统默认的内存分配策略

  • 特点: 不指定特定节点偏好

  • 适用: 一般应用或不确定优化方向时

MPOL_PREFERRED(首选模式):

  • 行为: 优先在指定节点分配内存

  • 特点: 分配失败时会使用其他可用节点

  • 适用: 希望优先使用特定节点但允许fallback的场景

MPOL_BIND(绑定模式):

  • 行为: 严格限制在指定节点分配内存

  • 特点: 分配失败时直接返回错误

  • 适用: 严格要求内存位置的应用

MPOL_INTERLEAVE(交错模式):

  • 行为: 在多个节点间轮询分配内存

  • 特点: 均匀分布内存负载

  • 适用: 大内存应用或需要负载均衡的场景

节点掩码操作

节点掩码设置示例:

1
2
3
4
5
6
7
8
9
// 单节点
unsigned long nodemask = 1UL << 0; // 节点0

// 多节点
unsigned long nodemask = (1UL << 0) | (1UL << 1); // 节点0和1

// 连续节点
unsigned long nodemask = (1UL << 4) - 1; // 节点0-3

节点掩码操作函数:

1
2
3
4
5
6
7
8
9
// 设置节点位
#define NODE_SET(node, mask) ((mask) |= (1UL << (node)))

// 清除节点位
#define NODE_CLR(node, mask) ((mask) &= ~(1UL << (node)))

// 检查节点位
#define NODE_ISSET(node, mask) ((mask) & (1UL << (node)))

常见使用场景

1. 数据库应用:

1
2
3
4
// 为不同数据缓冲区设置不同的节点策略
set_mempolicy(MPOL_PREFERRED, &node_0, sizeof(node_0) * 8); // 索引数据
set_mempolicy(MPOL_PREFERRED, &node_1, sizeof(node_1) * 8); // 用户数据

2. 高性能计算:

1
2
3
// 为计算密集型任务绑定到本地内存节点
set_mempolicy(MPOL_BIND, &local_node, sizeof(local_node) * 8);

3. Web服务器:

1
2
3
// 交错分配大缓存以平衡内存负载
set_mempolicy(MPOL_INTERLEAVE, &all_nodes, sizeof(all_nodes) * 8);

总结

set_mempolicy 是NUMA系统中重要的内存管理工具,提供了:

灵活的策略控制: 支持多种内存分配策略

性能优化能力: 通过节点亲和优化内存访问

应用适应性: 适用于各种NUMA应用场景

系统兼容性: 在不支持NUMA的系统上安全降级

通过合理使用 set_mempolicy,可以显著提升NUMA系统的内存访问性能,特别是在内存密集型应用中效果明显。在实际应用中,需要根据具体的工作负载特征和系统拓扑结构来选择合适的内存策略。

set_robust_list系统调用及示例

set_robust_list 函数详解

set_robust_list 是Linux系统调用,用于设置进程的健壮互斥锁(robust mutex)列表。当持有健壮互斥锁的进程异常终止时,内核会自动释放这些锁,防止死锁的发生。这个机制对于构建高可靠性多线程应用程序非常重要。

  1. 函数介绍

set_robust_list 是Linux系统调用,用于设置进程的健壮互斥锁(robust mutex)列表。当持有健壮互斥锁的进程异常终止时,内核会自动释放这些锁,防止死锁的发生。这个机制对于构建高可靠性多线程应用程序非常重要。

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

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

long set_robust_list(struct robust_list_head *head, size_t len);

  1. 功能

set_robust_list 允许进程注册一个健壮互斥锁列表,当进程异常退出时(如被信号终止),内核会自动遍历这个列表并释放所有未释放的互斥锁,确保其他等待这些锁的线程不会永久阻塞。

  1. 参数
  • *struct robust_list_head head: 指向健壮列表头的指针

  • size_t len: 列表头结构的大小(通常为sizeof(struct robust_list_head))

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

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

  1. 相似函数,或关联函数
  • get_robust_list: 获取当前健壮列表

  • pthread_mutexattr_setrobust: 设置pthread互斥锁的健壮属性

  • pthread_mutex_consistent: 标记互斥锁状态为一致

  1. 示例代码

示例1:基础set_robust_list使用

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

/**
* 健壮列表头结构
*/
struct robust_list_head {
struct robust_list *list;
long futex_offset;
unsigned long list_op_pending;
};

/**
* 健壮列表节点结构
*/
struct robust_list {
struct robust_list *next;
};

/**
* 调用set_robust_list系统调用
*/
static inline long sys_set_robust_list(struct robust_list_head *head, size_t len) {
return syscall(SYS_set_robust_list, head, len);
}

/**
* 调用get_robust_list系统调用
*/
static inline long sys_get_robust_list(int pid, struct robust_list_head **head, size_t *len) {
return syscall(SYS_get_robust_list, pid, head, len);
}

/**
* 演示基础set_robust_list使用方法
*/
int demo_set_robust_list_basic() {
struct robust_list_head head;
struct robust_list_head *get_head;
size_t len;
long result;

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

// 初始化健壮列表头
head.list = NULL;
head.futex_offset = 0;
head.list_op_pending = 0;

printf("1. 设置健壮列表:\n");
result = sys_set_robust_list(&head, sizeof(head));
if (result == 0) {
printf(" 成功设置健壮列表\n");
} else {
printf(" 设置健壮列表失败: %s\n", strerror(errno));
if (errno == ENOSYS) {
printf(" 系统不支持健壮列表功能\n");
return 0;
}
return -1;
}

printf("2. 获取当前健壮列表:\n");
result = sys_get_robust_list(0, &get_head, &len);
if (result == 0) {
printf(" 成功获取健壮列表\n");
printf(" 列表地址: %p\n", (void*)get_head);
printf(" 列表大小: %zu 字节\n", len);
} else {
printf(" 获取健壮列表失败: %s\n", strerror(errno));
}

printf("3. 验证列表设置:\n");
if ((void*)get_head == (void*)&head && len == sizeof(head)) {
printf(" ✓ 健壮列表设置正确\n");
} else {
printf(" ✗ 健壮列表设置可能有问题\n");
}

return 0;
}

int main() {
return demo_set_robust_list_basic();
}

示例2:pthread健壮互斥锁演示

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

/**
* 共享资源结构
*/
typedef struct {
pthread_mutex_t mutex;
int counter;
int active_threads;
} shared_resource_t;

/**
* 线程数据结构
*/
typedef struct {
int thread_id;
shared_resource_t *resource;
int terminate_signal;
} thread_data_t;

/**
* 初始化健壮互斥锁
*/
int init_robust_mutex(pthread_mutex_t *mutex) {
pthread_mutexattr_t attr;
int result;

// 初始化互斥锁属性
result = pthread_mutexattr_init(&attr);
if (result != 0) {
printf("初始化互斥锁属性失败: %s\n", strerror(result));
return -1;
}

// 设置互斥锁为健壮的
result = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
if (result != 0) {
printf("设置健壮属性失败: %s\n", strerror(result));
pthread_mutexattr_destroy(&attr);
return -1;
}

// 初始化互斥锁
result = pthread_mutex_init(mutex, &attr);
if (result != 0) {
printf("初始化互斥锁失败: %s\n", strerror(result));
pthread_mutexattr_destroy(&attr);
return -1;
}

pthread_mutexattr_destroy(&attr);
return 0;
}

/**
* 线程函数
*/
void* worker_thread(void *arg) {
thread_data_t *data = (thread_data_t*)arg;
int result;

printf("工作线程 %d 启动\n", data->thread_id);

// 模拟工作循环
for (int i = 0; i < 10 && !data->terminate_signal; i++) {
// 获取互斥锁
result = pthread_mutex_lock(&data->resource->mutex);
if (result == EOWNERDEAD) {
printf("线程 %d: 检测到锁持有者异常终止\n", data->thread_id);
// 标记互斥锁状态为一致
result = pthread_mutex_consistent(&data->resource->mutex);
if (result != 0) {
printf("线程 %d: 恢复互斥锁一致性失败: %s\n",
data->thread_id, strerror(result));
pthread_mutex_unlock(&data->resource->mutex);
break;
}
printf("线程 %d: 成功恢复互斥锁一致性\n", data->thread_id);
} else if (result != 0) {
printf("线程 %d: 获取互斥锁失败: %s\n", data->thread_id, strerror(result));
break;
}

// 访问共享资源
data->resource->counter++;
printf("线程 %d: 计数器 = %d\n", data->thread_id, data->resource->counter);

// 模拟工作处理时间
usleep(100000); // 100ms

// 释放互斥锁
pthread_mutex_unlock(&data->resource->mutex);

// 短暂休息
usleep(50000); // 50ms
}

// 减少活跃线程计数
pthread_mutex_lock(&data->resource->mutex);
data->resource->active_threads--;
pthread_mutex_unlock(&data->resource->mutex);

printf("工作线程 %d 结束\n", data->thread_id);
return NULL;
}

/**
* 演示pthread健壮互斥锁
*/
int demo_pthread_robust_mutex() {
shared_resource_t resource = {0};
pthread_t threads&#91;3];
thread_data_t thread_data&#91;3];
int result;

printf("=== pthread健壮互斥锁演示 ===\n");

// 初始化健壮互斥锁
if (init_robust_mutex(&resource.mutex) != 0) {
return -1;
}

printf("成功初始化健壮互斥锁\n");

// 初始化共享资源
resource.counter = 0;
resource.active_threads = 3;

// 创建工作线程
printf("创建3个工作线程...\n");

for (int i = 0; i < 3; i++) {
thread_data&#91;i].thread_id = i + 1;
thread_data&#91;i].resource = &resource;
thread_data&#91;i].terminate_signal = 0;

result = pthread_create(&threads&#91;i], NULL, worker_thread, &thread_data&#91;i]);
if (result != 0) {
printf("创建线程 %d 失败: %s\n", i + 1, strerror(result));
return -1;
}
}

// 让线程运行一段时间
printf("让线程运行5秒...\n");
sleep(5);

// 强制终止一个线程来演示健壮性
printf("强制终止线程1来演示健壮性...\n");
thread_data&#91;0].terminate_signal = 1;
pthread_kill(threads&#91;0], SIGKILL);

// 等待其他线程完成
printf("等待其他线程完成...\n");
for (int i = 1; i < 3; i++) {
pthread_join(threads&#91;i], NULL);
}

// 显示最终结果
printf("\n最终结果:\n");
printf(" 计数器值: %d\n", resource.counter);
printf(" 活跃线程: %d\n", resource.active_threads);

// 清理互斥锁
pthread_mutex_destroy(&resource.mutex);

return 0;
}

int main() {
return demo_pthread_robust_mutex();
}

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

/**
* 自定义健壮列表节点
*/
typedef struct custom_robust_node {
struct custom_robust_node *next;
pthread_mutex_t *mutex;
int node_id;
} custom_robust_node_t;

/**
* 自定义健壮列表管理器
*/
typedef struct {
struct robust_list_head head;
custom_robust_node_t *nodes;
int node_count;
int max_nodes;
} robust_list_manager_t;

/**
* 系统调用封装
*/
static inline long sys_set_robust_list(struct robust_list_head *head, size_t len) {
return syscall(SYS_set_robust_list, head, len);
}

static inline long sys_get_robust_list(int pid, struct robust_list_head **head, size_t *len) {
return syscall(SYS_get_robust_list, pid, head, len);
}

/**
* 初始化健壮列表管理器
*/
int robust_list_init(robust_list_manager_t *manager, int max_nodes) {
// 初始化列表头
manager->head.list = NULL;
manager->head.futex_offset = 0;
manager->head.list_op_pending = 0;

// 分配节点数组
manager->nodes = calloc(max_nodes, sizeof(custom_robust_node_t));
if (!manager->nodes) {
perror("分配节点数组失败");
return -1;
}

manager->node_count = 0;
manager->max_nodes = max_nodes;

// 设置健壮列表
long result = sys_set_robust_list(&manager->head, sizeof(manager->head));
if (result != 0) {
printf("设置健壮列表失败: %s\n", strerror(errno));
free(manager->nodes);
return -1;
}

printf("健壮列表管理器初始化成功,最大节点数: %d\n", max_nodes);
return 0;
}

/**
* 添加互斥锁到健壮列表
*/
int robust_list_add_mutex(robust_list_manager_t *manager, pthread_mutex_t *mutex, int node_id) {
if (manager->node_count >= manager->max_nodes) {
printf("节点数已达上限\n");
return -1;
}

int index = manager->node_count++;
manager->nodes&#91;index].mutex = mutex;
manager->nodes&#91;index].node_id = node_id;

// 连接节点到列表
if (index > 0) {
manager->nodes&#91;index-1].next = (struct robust_list*)&manager->nodes&#91;index];
}

// 更新列表头
manager->head.list = (struct robust_list*)&manager->nodes&#91;0];

printf("添加互斥锁到健壮列表,节点ID: %d\n", node_id);
return 0;
}

/**
* 演示健壮列表管理
*/
int demo_robust_list_management() {
robust_list_manager_t manager;
pthread_mutex_t mutex1, mutex2, mutex3;
pthread_mutexattr_t attr;
int result;

printf("=== 健壮列表管理演示 ===\n");

// 初始化管理器
if (robust_list_init(&manager, 10) != 0) {
return -1;
}

// 初始化互斥锁属性
result = pthread_mutexattr_init(&attr);
if (result != 0) {
printf("初始化互斥锁属性失败: %s\n", strerror(result));
return -1;
}

result = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
if (result != 0) {
printf("设置健壮属性失败: %s\n", strerror(result));
pthread_mutexattr_destroy(&attr);
return -1;
}

// 创建多个健壮互斥锁
printf("创建健壮互斥锁...\n");

result = pthread_mutex_init(&mutex1, &attr);
if (result != 0) {
printf("初始化互斥锁1失败: %s\n", strerror(result));
pthread_mutexattr_destroy(&attr);
return -1;
}

result = pthread_mutex_init(&mutex2, &attr);
if (result != 0) {
printf("初始化互斥锁2失败: %s\n", strerror(result));
pthread_mutex_destroy(&mutex1);
pthread_mutexattr_destroy(&attr);
return -1;
}

result = pthread_mutex_init(&mutex3, &attr);
if (result != 0) {
printf("初始化互斥锁3失败: %s\n", strerror(result));
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
pthread_mutexattr_destroy(&attr);
return -1;
}

pthread_mutexattr_destroy(&attr);

// 将互斥锁添加到健壮列表
robust_list_add_mutex(&manager, &mutex1, 1);
robust_list_add_mutex(&manager, &mutex2, 2);
robust_list_add_mutex(&manager, &mutex3, 3);

// 测试互斥锁操作
printf("测试互斥锁操作...\n");

for (int i = 0; i < 3; i++) {
result = pthread_mutex_lock(&mutex1);
if (result == 0) {
printf("成功获取互斥锁1\n");
usleep(100000);
pthread_mutex_unlock(&mutex1);
printf("成功释放互斥锁1\n");
} else if (result == EOWNERDEAD) {
printf("检测到锁持有者异常终止,恢复一致性...\n");
pthread_mutex_consistent(&mutex1);
pthread_mutex_unlock(&mutex1);
} else {
printf("获取互斥锁1失败: %s\n", strerror(result));
}
}

// 清理资源
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
pthread_mutex_destroy(&mutex3);
free(manager.nodes);

printf("健壮列表管理演示完成\n");
return 0;
}

int main() {
return demo_robust_list_management();
}

示例4:异常终止处理演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

/**
* 共享资源结构
*/
typedef struct {
pthread_mutex_t mutex;
int shared_data;
volatile int writer_active;
} shared_data_t;

/**
* 初始化健壮互斥锁
*/
int init_robust_mutex(pthread_mutex_t *mutex) {
pthread_mutexattr_t attr;
int result;

result = pthread_mutexattr_init(&attr);
if (result != 0) {
return -1;
}

result = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
if (result != 0) {
pthread_mutexattr_destroy(&attr);
return -1;
}

result = pthread_mutex_init(mutex, &attr);
pthread_mutexattr_destroy(&attr);

return result;
}

/**
* 写者线程函数
*/
void* writer_thread(void *arg) {
shared_data_t *data = (shared_data_t*)arg;
int result;

printf("写者线程启动 (PID: %d)\n", getpid());

// 获取互斥锁
result = pthread_mutex_lock(&data->mutex);
if (result != 0) {
printf("写者: 获取互斥锁失败: %s\n", strerror(result));
return NULL;
}

printf("写者: 成功获取互斥锁\n");
data->writer_active = 1;

// 模拟长时间写操作
printf("写者: 开始写操作...\n");
for (int i = 0; i < 10; i++) {
data->shared_data = i + 1;
printf("写者: 写入数据 %d\n", data->shared_data);
sleep(1);
}

// 正常释放互斥锁
data->writer_active = 0;
pthread_mutex_unlock(&data->mutex);
printf("写者: 释放互斥锁\n");

return NULL;
}

/**
* 读者线程函数
*/
void* reader_thread(void *arg) {
shared_data_t *data = (shared_data_t*)arg;
int result;

printf("读者线程启动 (PID: %d)\n", getpid());

// 等待写者开始工作
sleep(2);

// 尝试获取互斥锁
printf("读者: 尝试获取互斥锁...\n");
result = pthread_mutex_lock(&data->mutex);

if (result == EOWNERDEAD) {
printf("读者: 检测到写者异常终止!\n");
printf("读者: 正在恢复互斥锁一致性...\n");

// 恢复互斥锁一致性
result = pthread_mutex_consistent(&data->mutex);
if (result == 0) {
printf("读者: 成功恢复互斥锁一致性\n");
printf("读者: 当前共享数据: %d\n", data->shared_data);
} else {
printf("读者: 恢复一致性失败: %s\n", strerror(result));
}
} else if (result == 0) {
printf("读者: 成功获取互斥锁\n");
printf("读者: 读取数据: %d\n", data->shared_data);
pthread_mutex_unlock(&data->mutex);
} else {
printf("读者: 获取互斥锁失败: %s\n", strerror(result));
}

return NULL;
}

/**
* 演示异常终止处理
*/
int demo_abnormal_termination() {
shared_data_t data = {0};
pthread_t writer_tid, reader_tid;
int result;

printf("=== 异常终止处理演示 ===\n");

// 初始化健壮互斥锁
if (init_robust_mutex(&data.mutex) != 0) {
printf("初始化健壮互斥锁失败\n");
return -1;
}

printf("成功初始化健壮互斥锁\n");

// 创建写者进程
pid_t writer_pid = fork();
if (writer_pid == 0) {
// 写者进程
printf("写者进程启动\n");

// 获取互斥锁
result = pthread_mutex_lock(&data.mutex);
if (result != 0) {
printf("写者进程: 获取互斥锁失败: %s\n", strerror(result));
exit(1);
}

printf("写者进程: 成功获取互斥锁\n");
data.writer_active = 1;

// 模拟工作一段时间后异常终止
printf("写者进程: 开始工作,5秒后模拟异常终止...\n");
sleep(5);

printf("写者进程: 模拟异常终止(不释放锁)\n");
// 注意:这里不调用pthread_mutex_unlock,模拟异常终止
exit(1); // 强制退出,不释放锁
} else if (writer_pid > 0) {
// 父进程(读者)
printf("父进程(读者)启动\n");

// 等待写者进程开始工作
sleep(2);

// 创建读者线程
result = pthread_create(&reader_tid, NULL, reader_thread, &data);
if (result != 0) {
printf("创建读者线程失败: %s\n", strerror(result));
kill(writer_pid, SIGKILL);
pthread_mutex_destroy(&data.mutex);
return -1;
}

// 等待写者进程异常终止
int status;
waitpid(writer_pid, &status, 0);
printf("写者进程已终止 (状态: %d)\n", status);

// 等待读者线程完成
pthread_join(reader_tid, NULL);

// 清理资源
pthread_mutex_destroy(&data.mutex);

printf("异常终止处理演示完成\n");
} else {
perror("fork 失败");
pthread_mutex_destroy(&data.mutex);
return -1;
}

return 0;
}

int main() {
return demo_abnormal_termination();
}

示例5:健壮性测试工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <time.h>

/**
* 健壮性测试配置
*/
typedef struct {
int num_threads;
int test_duration;
int crash_probability; // 0-100,表示异常终止概率
int use_robust_mutex;
} robustness_test_config_t;

/**
* 测试统计信息
*/
typedef struct {
atomic_int lock_attempts;
atomic_int lock_success;
atomic_int lock_failures;
atomic_int owner_dead_detected;
atomic_int consistency_restored;
atomic_int normal_terminations;
atomic_int abnormal_terminations;
} test_stats_t;

/**
* 测试线程数据
*/
typedef struct {
int thread_id;
pthread_mutex_t *mutex;
test_stats_t *stats;
robustness_test_config_t *config;
volatile int *terminate_flag;
} test_thread_data_t;

/**
* 初始化健壮互斥锁
*/
int init_test_mutex(pthread_mutex_t *mutex, int robust) {
pthread_mutexattr_t attr;
int result;

result = pthread_mutexattr_init(&attr);
if (result != 0) return result;

if (robust) {
result = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
if (result != 0) {
pthread_mutexattr_destroy(&attr);
return result;
}
}

result = pthread_mutex_init(mutex, &attr);
pthread_mutexattr_destroy(&attr);

return result;
}

/**
* 测试线程函数
*/
void* test_thread(void *arg) {
test_thread_data_t *data = (test_thread_data_t*)arg;
int result;

printf("测试线程 %d 启动\n", data->thread_id);

srand(time(NULL) + data->thread_id);

while (!(*data->terminate_flag)) {
// 尝试获取互斥锁
atomic_fetch_add(&data->stats->lock_attempts, 1);
result = pthread_mutex_lock(data->mutex);

if (result == 0) {
atomic_fetch_add(&data->stats->lock_success, 1);

// 模拟持有锁的工作
usleep(10000 + (rand() % 50000)); // 10-60ms

// 随机决定是否异常终止(模拟崩溃)
if (data->config->crash_probability > 0 &&
rand() % 100 < data->config->crash_probability) {
printf("线程 %d: 模拟异常终止\n", data->thread_id);
atomic_fetch_add(&data->stats->abnormal_terminations, 1);
exit(1); // 模拟崩溃,不释放锁
}

// 正常释放锁
pthread_mutex_unlock(data->mutex);
atomic_fetch_add(&data->stats->normal_terminations, 1);

} else if (result == EOWNERDEAD) {
atomic_fetch_add(&data->stats->owner_dead_detected, 1);
printf("线程 %d: 检测到锁持有者异常终止\n", data->thread_id);

// 恢复一致性
result = pthread_mutex_consistent(data->mutex);
if (result == 0) {
atomic_fetch_add(&data->stats->consistency_restored, 1);
printf("线程 %d: 成功恢复锁一致性\n", data->thread_id);
pthread_mutex_unlock(data->mutex);
} else {
atomic_fetch_add(&data->stats->lock_failures, 1);
printf("线程 %d: 恢复一致性失败: %s\n", data->thread_id, strerror(result));
}
} else {
atomic_fetch_add(&data->stats->lock_failures, 1);
printf("线程 %d: 获取锁失败: %s\n", data->thread_id, strerror(result));
}

// 短暂休息
usleep(10000 + (rand() % 20000)); // 10-30ms
}

printf("测试线程 %d 结束\n", data->thread_id);
return NULL;
}

/**
* 显示测试统计
*/
void show_test_statistics(test_stats_t *stats) {
printf("\n=== 测试统计结果 ===\n");
printf("锁尝试次数: %d\n", atomic_load(&stats->lock_attempts));
printf("成功获取锁: %d\n", atomic_load(&stats->lock_success));
printf("获取锁失败: %d\n", atomic_load(&stats->lock_failures));
printf("检测到异常终止: %d\n", atomic_load(&stats->owner_dead_detected));
printf("恢复一致性: %d\n", atomic_load(&stats->consistency_restored));
printf("正常终止: %d\n", atomic_load(&stats->normal_terminations));
printf("异常终止: %d\n", atomic_load(&stats->abnormal_terminations));

if (atomic_load(&stats->lock_attempts) > 0) {
double success_rate = (double)atomic_load(&stats->lock_success) /
atomic_load(&stats->lock_attempts) * 100;
printf("锁获取成功率: %.2f%%\n", success_rate);
}
}

/**
* 演示健壮性测试
*/
int demo_robustness_testing() {
pthread_mutex_t mutex;
pthread_t *threads;
test_thread_data_t *thread_data;
test_stats_t stats = {0};
volatile int terminate_flag = 0;

robustness_test_config_t config = {
.num_threads = 5,
.test_duration = 30, // 30秒
.crash_probability = 5, // 5%概率异常终止
.use_robust_mutex = 1
};

printf("=== 健壮性测试工具 ===\n");
printf("测试配置:\n");
printf(" 线程数: %d\n", config.num_threads);
printf(" 测试时长: %d 秒\n", config.test_duration);
printf(" 异常终止概率: %d%%\n", config.crash_probability);
printf(" 使用健壮互斥锁: %s\n", config.use_robust_mutex ? "是" : "否");

// 初始化互斥锁
if (init_test_mutex(&mutex, config.use_robust_mutex) != 0) {
printf("初始化互斥锁失败\n");
return -1;
}

// 分配线程数组
threads = malloc(config.num_threads * sizeof(pthread_t));
thread_data = malloc(config.num_threads * sizeof(test_thread_data_t));
if (!threads || !thread_data) {
printf("分配内存失败\n");
pthread_mutex_destroy(&mutex);
free(threads);
free(thread_data);
return -1;
}

// 创建测试线程
printf("创建 %d 个测试线程...\n", config.num_threads);

for (int i = 0; i < config.num_threads; i++) {
thread_data&#91;i].thread_id = i + 1;
thread_data&#91;i].mutex = &mutex;
thread_data&#91;i].stats = &stats;
thread_data&#91;i].config = &config;
thread_data&#91;i].terminate_flag = &terminate_flag;

int result = pthread_create(&threads&#91;i], NULL, test_thread, &thread_data&#91;i]);
if (result != 0) {
printf("创建线程 %d 失败: %s\n", i + 1, strerror(result));
// 清理已创建的线程
terminate_flag = 1;
for (int j = 0; j < i; j++) {
pthread_join(threads&#91;j], NULL);
}
pthread_mutex_destroy(&mutex);
free(threads);
free(thread_data);
return -1;
}
}

// 运行测试
printf("开始测试,持续 %d 秒...\n", config.test_duration);
sleep(config.test_duration);

// 停止测试
printf("停止测试...\n");
terminate_flag = 1;

// 等待所有线程结束
for (int i = 0; i < config.num_threads; i++) {
pthread_join(threads&#91;i], NULL);
}

// 显示统计结果
show_test_statistics(&stats);

// 清理资源
pthread_mutex_destroy(&mutex);
free(threads);
free(thread_data);

return 0;
}

int main() {
return demo_robustness_testing();
}

set_robust_list 使用注意事项

系统要求:

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

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

编译环境: 需要正确的系统头文件

功能限制:

FUTEX支持: 需要内核支持FUTEX同步原语

pthread支持: 需要支持健壮互斥锁的pthread实现

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

错误处理:

ENOSYS: 系统不支持健壮列表功能

EINVAL: 参数无效

EFAULT: 指针参数无效

ENOMEM: 内存不足

性能考虑:

开销: 健壮互斥锁比普通互斥锁有额外开销

列表维护: 需要维护健壮列表结构

异常处理: 异常终止时需要额外处理时间

最佳实践:

选择性使用: 只在需要健壮性的场景使用

正确处理: 妥善处理EOWNERDEAD错误

及时清理: 正常情况下及时释放互斥锁

测试验证: 充分测试异常场景下的行为

健壮互斥锁工作原理

1. 正常操作流程:

1
2
线程A获取锁 -> 执行临界区 -> 释放锁 -> 线程B获取锁

2. 异常终止处理:

1
2
线程A获取锁 -> 异常终止(未释放锁) -> 内核检测 -> 自动释放锁 -> 线程B获取锁时得到EOWNERDEAD -> 调用pthread_mutex_consistent -> 恢复正常使用

3. 关键数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
// 健壮列表头
struct robust_list_head {
struct robust_list *list; // 列表指针
long futex_offset; // futex偏移量
unsigned long list_op_pending; // 待处理操作
};

// 列表节点
struct robust_list {
struct robust_list *next; // 下一个节点
};

常见使用场景

1. 服务器应用:

1
2
3
// 为关键资源使用健壮互斥锁,防止服务器进程崩溃导致死锁
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);

2. 数据库系统:

1
2
// 保护数据库连接和事务资源,确保异常终止时资源能被正确释放

3. 实时系统:

1
2
// 在实时应用中确保关键资源不会因为进程异常而永久锁定

错误处理指南

EOWNERDEAD处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
int result = pthread_mutex_lock(&mutex);
if (result == EOWNERDEAD) {
// 检测到锁持有者异常终止
result = pthread_mutex_consistent(&mutex);
if (result == 0) {
// 成功恢复一致性,可以正常使用锁
// 注意:锁保护的数据可能处于不一致状态
} else {
// 恢复失败,应该释放锁并处理错误
pthread_mutex_unlock(&mutex);
}
}

总结

set_robust_list 和相关的健壮互斥锁机制提供了:

故障恢复: 进程异常终止时自动释放互斥锁

死锁预防: 防止因进程崩溃导致的永久阻塞

系统稳定性: 提高多线程应用的可靠性

透明处理: 对应用层提供相对透明的健壮性保证

通过合理使用健壮互斥锁,可以构建更加可靠的多线程应用程序,特别是在需要长时间运行和高可用性的系统中表现出色。在实际应用中,需要注意性能开销和正确的错误处理。

set_tid_address系统调用及示例

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

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

在 Linux 系统中,进程和线程是程序执行的基本单位。每个线程都有一个唯一的标识符,叫做 Thread ID (TID)。对于主线程(也就是进程本身),它的 TID 通常和 Process ID (PID) 是相同的。但对于通过 clone() 或 pthread_create() 创建的子线程,它们会有自己独立的 TID。

有时候,一个线程(或进程)需要知道另一个线程何时退出。例如,在一个多线程服务器中,主线程可能需要清理已退出的工作线程的资源。

set_tid_address 系统调用提供了一种机制来实现这一点。当一个线程调用 set_tid_address 并传入一个内存地址(我们称之为“TID 地址”或 tidptr)后,内核会记住这个地址。当这个线程最终退出时,内核会执行一个非常重要的操作:将这个地址处的内存值(通常是一个 int 或 pid_t 变量)清零(设置为 0)。

这样,程序的其他部分(比如父线程)就可以通过检查这个 tidptr 指向的内存位置的值来判断线程是否已经退出:如果值是 0,说明线程退出了;如果值非 0,说明线程还在运行(或者刚好是它的 TID)。

简单来说,set_tid_address 就是告诉内核:“当我死(退出)的时候,请帮我把这个地方的数字清零。”这样别人(通常是创建我的线程)就能通过看这个数字知道我是不是挂了。

重要提示:用户空间程序通常不会直接调用 set_tid_address。当你使用 pthread_create() 创建线程时,底层的 C 库(如 glibc NPTL)会自动为你调用 set_tid_address,并管理好这个 tidptr。这个系统调用主要是供 C 库实现线程功能时使用的。

对于 Linux 编程小白:你只需要知道,当你使用标准的 POSIX 线程库(pthread)时,线程退出通知机制(例如 pthread_join 能知道线程何时结束)在底层可能就是通过 set_tid_address 实现的。直接调用它比较少见,除非你在编写自己的线程库或者进行非常底层的系统编程。

2. 函数原型

1
2
3
4
5
6
7
// 标准 C 库通常不提供直接的包装函数
// 需要通过 syscall 直接调用
#include <sys/syscall.h> // 包含系统调用号 SYS_set_tid_address
#include <unistd.h> // 包含 syscall 函数

long syscall(SYS_set_tid_address, int *tidptr);

3. 功能

设置当前线程在退出时用于清除的用户空间地址。内核会记录这个地址,当线程终止时,内核会将该地址指向的内存单元(通常是一个 int)的值设置为 0。

4. 参数

tidptr:

  • int * 类型。

  • 一个指向用户空间内存地址的指针。这个地址处通常存放一个 int 或 pid_t 类型的变量。调用线程启动时,这个变量通常被初始化为其自身的 TID。当线程退出时,内核会将这个地址处的值清零。

5. 返回值

  • 成功: 返回调用线程的 Thread ID (TID)。这使得调用者可以方便地知道自己(或被创建的线程)的 TID 是多少。

  • 失败: 理论上这个系统调用不应该失败,但如果 tidptr 指向无效内存,可能会返回错误。但在实践中,它几乎总是成功返回 TID。

6. 相似函数或关联函数

  • clone: 用于创建进程或线程的底层系统调用。clone 可以接受 CLONE_CHILD_CLEARTID 标志,该标志会使得新创建的子进程/线程在退出时自动调用类似 set_tid_address 的操作。

  • pthread_create / pthread_join: POSIX 线程库函数。pthread_create 会创建线程并可能在底层使用 set_tid_address,pthread_join 会等待线程结束,其底层实现可能依赖于 set_tid_address 提供的机制(例如通过 futex 等待 tidptr 变为 0)。

  • gettid: 获取当前线程的 TID。可以通过 syscall(SYS_gettid) 调用。

  • futex: 快速用户空间互斥锁系统调用。pthread_join 等函数可能使用 futex 来高效地等待由 set_tid_address 清零的变量。

  • wait / waitpid: 用于等待子进程结束。这是针对进程的,而 set_tid_address 是针对线程的。

7. 示例代码

由于 set_tid_address 通常由 C 库内部使用,直接调用它需要手动管理线程的创建和同步,这比较复杂。下面的示例将演示如何结合 clone 系统调用和 set_tid_address 来手动创建一个线程,并利用 set_tid_address 的机制来等待它退出。

警告:这是一个非常底层的示例,展示了 set_tid_address 和 clone 的用法。在实际编程中,强烈建议使用 pthread 库。

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
#define _GNU_SOURCE // 启用 GNU 扩展以使用 clone
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h> // 包含 syscall, SYS_set_tid_address, SYS_gettid, SYS_clone
#include <sys/wait.h>
#include <sched.h> // 包含 CLONE_* 常量
#include <errno.h>
#include <stdatomic.h>
#include <linux/futex.h> // 包含 futex 操作码
#include <sys/time.h> // 包含 timespec
#include <limits.h> // 包含 INT_MAX

// 定义线程栈大小
#define STACK_SIZE (1024 * 1024) // 1MB

// 线程函数
int thread_function(void *arg) {
long thread_num = (long)arg;
pid_t my_tid = syscall(SYS_gettid); // 获取自己的 TID
printf("Child thread %ld (TID: %d) started.\n", thread_num, my_tid);

// 模拟一些工作
for (int i = 0; i < 5; ++i) {
printf("Child thread %ld working... %d\n", thread_num, i);
sleep(1);
}

printf("Child thread %ld (TID: %d) finishing.\n", thread_num, my_tid);
// 线程函数返回时,内核会根据 set_tid_address 设置的地址,
// 将该地址处的值清零,并可能唤醒等待的 futex。
return 0;
}

// 模拟 pthread_join 的简单等待函数
// 等待 *tidptr 变为 0
void wait_for_thread_exit(int *tidptr) {
// 循环检查 tidptr 指向的值
// 在实际的库实现中,这里会使用 futex 系统调用来高效等待
while(__atomic_load_n(tidptr, __ATOMIC_ACQUIRE) != 0) {
printf("Main thread: Waiting for thread with TID %d to exit (tidptr=%d)...\n",
*tidptr, *tidptr);
// 简单的轮询等待(效率低)
// 实际库会用 syscall(SYS_futex, tidptr, FUTEX_WAIT, 0, NULL, NULL, 0);
sleep(1);
}
printf("Main thread: Detected thread has exited (tidptr is now 0).\n");
}

int main() {
char *stack; // 指向子线程栈的指针
char *stack_top; // 指向子线程栈顶的指针
pid_t child_tid; // 存储 clone 返回的子线程 TID
int tid_location = 0; // 用于 set_tid_address 的变量

printf("--- Demonstrating set_tid_address with clone ---\n");
printf("Main thread PID/TID: %d\n", getpid()); // 主线程 PID 和 TID 相同

// 1. 为子线程分配栈空间
// 注意:栈是向下增长的,所以我们需要分配后调整指针
stack = malloc(STACK_SIZE);
if (stack == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
stack_top = stack + STACK_SIZE; // 栈顶指针

printf("Allocated stack for child thread at %p (top at %p)\n", stack, stack_top);

// 2. 使用 clone 创建子线程
// CLONE_VM: 子线程与父线程共享进程的内存描述符 (虚拟内存空间)
// CLONE_FS: 共享文件系统信息
// CLONE_FILES: 共享文件描述符表
// CLONE_SIGHAND: 共享信号处理函数表
// CLONE_THREAD: 将子进程置于父进程的线程组中
// CLONE_SYSVSEM: 共享 System V 信号量 undo 信息
// CLONE_PARENT_SETTID: 将子线程的 TID 写入 ptid (我们不使用 ptid)
// CLONE_CHILD_CLEARTID: 子线程退出时,清零 ctid 指向的值 (即 tid_location)
// stack_top: 子线程的栈指针
// &tid_location: ctid 参数,指向 tid_location
child_tid = syscall(SYS_clone,
CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_THREAD | CLONE_SYSVSEM |
CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID,
stack_top, NULL, &tid_location);

if (child_tid == -1) {
perror("clone");
free(stack);
exit(EXIT_FAILURE);
}

if (child_tid == 0) {
// --- 在子线程中 ---
// 子线程的第一件事通常是调用 set_tid_address
// 但实际上,当我们使用 CLONE_CHILD_CLEARTID 标志时,
// 内核已经在 clone 时为我们处理了类似 set_tid_address 的操作
// 这里我们显式调用一次,展示其效果
pid_t my_tid = syscall(SYS_gettid);
long returned_tid = syscall(SYS_set_tid_address, &tid_location);
printf("In child thread: My TID is %d, set_tid_address returned %ld\n", my_tid, returned_tid);
// 初始化 tid_location 为自己的 TID
__atomic_store_n(&tid_location, my_tid, __ATOMIC_RELEASE);
printf("In child thread: Set tid_location to %d\n", tid_location);

// 调用线程函数
thread_function((void*)1);

// 线程函数返回,线程退出
// 内核会将 &tid_location 处的值清零
free(stack); // 子线程释放栈(简化处理)
exit(EXIT_SUCCESS); // 或者直接 return
}

// --- 回到主线程 ---
printf("Main thread: clone returned child TID: %d\n", child_tid);
printf("Main thread: tid_location variable address: %p\n", &tid_location);
printf("Main thread: Initial value of tid_location: %d\n", tid_location);

// 等待一小会儿,让子线程设置 tid_location
sleep(1);
printf("Main thread: Value of tid_location after child setup: %d\n", tid_location);

// 3. 等待子线程退出
// 我们可以轮询 tid_location,或者使用 futex (推荐)
printf("Main thread: Waiting for child thread to exit...\n");
wait_for_thread_exit(&tid_location);
// 或者使用 futex: syscall(SYS_futex, &tid_location, FUTEX_WAIT, 0, NULL, NULL, 0);

printf("Main thread: Child thread has exited.\n");
printf("Main thread: Final value of tid_location: %d\n", tid_location);

// 4. 清理 (主线程不再需要栈了,因为子线程已经退出并释放了)
// free(stack); // 子线程已释放

printf("Main thread: Program finished.\n");
return 0;
}

使用标准 pthread 的对比示例 (推荐方式):

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

void* worker_thread(void *arg) {
long thread_num = (long)arg;
printf("Worker thread %ld started.\n", thread_num);

for (int i = 0; i < 5; ++i) {
printf("Worker thread %ld working... %d\n", thread_num, i);
sleep(1);
}

printf("Worker thread %ld finishing.\n", thread_num);
return NULL;
}

int main() {
pthread_t thread1, thread2;

printf("--- Using standard pthread (Recommended) ---\n");
printf("Main thread PID/TID: %d\n", getpid());

// 创建线程
if (pthread_create(&thread1, NULL, worker_thread, (void*)1) != 0 ||
pthread_create(&thread2, NULL, worker_thread, (void*)2) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}

printf("Main thread: Created worker threads.\n");

// 等待线程结束 (这在底层可能使用了 set_tid_address 提供的机制)
printf("Main thread: Waiting for worker threads to join...\n");
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);

printf("Main thread: Both worker threads have finished.\n");
printf("Main thread: Program finished using standard pthread library.\n");

return 0;
}

编译和运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 假设代码保存在 set_tid_address_example.c 和 pthread_example.c 中
# 需要链接 pthread 库

# 编译 pthread 示例 (推荐,简单,可移植)
gcc -o pthread_example pthread_example.c -lpthread

# 编译 set_tid_address 示例 (底层,复杂)
gcc -o set_tid_address_example set_tid_address_example.c

# 运行 pthread 示例
./pthread_example

# 运行 set_tid_address 示例
./set_tid_address_example

总结:对于 Linux 编程新手,请优先学习和使用标准的 pthread 库来创建和管理线程。set_tid_address 是一个底层系统调用,主要用于 C 库实现线程功能,它提供了一种高效的线程退出通知机制。直接使用它需要深入了解系统调用、内存管理和线程同步,通常只在编写系统级代码时才会涉及。

https://www.calcguide.tech/2025/08/23/set-tid-address系统调用及示例/

set_thread_area系统调用及示例

我们来深入学习 set_thread_area 系统调用,在 Linux 系统中,尤其是在 i386 (IA-32) 架构上,CPU 提供了一种特殊的机制来存储线程本地的数据,这就是 Thread Local Storage (TLS)。这种机制允许每个线程拥有自己独立的一份变量副本,即使变量名相同,不同线程访问的也是不同的数据。

1. 函数介绍

在 Linux 系统中,尤其是在 i386 (IA-32) 架构上,CPU 提供了一种特殊的机制来存储线程本地的数据,这就是 Thread Local Storage (TLS)。这种机制允许每个线程拥有自己独立的一份变量副本,即使变量名相同,不同线程访问的也是不同的数据。

在 i386 架构上,实现 TLS 的一种方法是利用 CPU 的 段寄存器(Segment Register),特别是 gs 段寄存器。CPU 可以被配置,使得访问 gs:0, gs:4, gs:8 这样的地址时,实际上访问的是内存中特定区域的数据。这个“特定区域”对每个线程来说是不同的。

set_thread_area 系统调用(以及配套的 get_thread_area)就是 Linux 内核提供给用户空间程序的接口,用于设置或获取当前线程的这种 TLS 描述符(Thread Local Storage Descriptor)。这个描述符告诉内核和 CPU:当这个线程使用 gs 段寄存器时,它的基地址应该设置在哪里。

简单来说,set_thread_area 是 i386 架构上,用户空间程序告诉内核“请为我配置一下 gs 段寄存器,让它指向我线程本地数据的起始位置”的方式。

重要提示:这是一个非常底层且架构特定(主要是 i386)的系统调用。现代的、可移植的代码通常使用编译器和 C 库提供的标准 TLS 支持(如 __thread 关键字),这些高级工具在底层可能会(也可能不会)使用 set_thread_area。

对于 Linux 编程小白:除非你在进行非常底层的系统编程、编写 C 库本身或者需要与旧的使用此机制的代码交互,否则你不需要直接了解或使用 set_thread_area。理解它有助于了解 TLS 在 i386 上的一种实现机制。

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

2. 函数原型

1
2
3
4
5
6
7
8
9
// 这是 i386 特定的系统调用
#include <asm/ldt.h> // 包含 user_desc 结构体定义
#include <sys/syscall.h> // 包含系统调用号 SYS_set_thread_area
#include <unistd.h> // 包含 syscall 函数

// 注意:标准 C 库通常不提供直接的包装函数
long syscall(SYS_set_thread_area, struct user_desc *u_info);
long syscall(SYS_get_thread_area, struct user_desc *u_info);

3. 功能

设置或修改调用线程的 Thread Local Storage (TLS) 描述符。这个描述符定义了 TLS 段(通常由 gs 段寄存器引用)在内存中的位置和属性。

4. 参数

u_info:

  • struct user_desc * 类型。

  • 一个指向 user_desc 结构体的指针。这个结构体包含了 TLS 描述符的详细信息。调用 set_thread_area 时,你需要填充这个结构体;调用 get_thread_area 时,内核会填充它。

struct user_desc 结构体 (简化版,定义在 <asm/ldt.h>):

1
2
3
4
5
6
7
8
9
10
11
12
13
struct user_desc {
unsigned int entry_number; // 描述符在 GDT/LDT 中的索引 (输入/输出)
unsigned long base_addr; // TLS 段的基地址 (输入)
unsigned int limit; // 段的大小限制 (通常设为 0xfffff)
unsigned int seg_32bit:1; // 是否为 32 位段 (通常设为 1)
unsigned int contents:2; // 段内容类型 (通常设为 0)
unsigned int read_exec_only:1; // 是否只读/执行 (通常设为 0)
unsigned int limit_in_pages:1; // limit 是否以页为单位 (通常设为 1)
unsigned int seg_not_present:1; // 段是否存在 (通常设为 0)
unsigned int useable:1; // 是否可用 (通常设为 1)
// ... 可能还有其他字段,取决于内核版本
};

关键字段解释:

  • entry_number: 指定要设置的 GDT (Global Descriptor Table) 条目索引。如果传入 0xffffffff (或 -1),内核会选择一个可用的索引并返回给调用者。

  • base_addr: 这是最重要的字段,它指定了 TLS 数据在内存中的起始地址。线程通过 gs:offset 访问的数据就位于 base_addr + offset。

  • 其他字段是 x86 段描述符的标准属性,对于 TLS 用途,通常使用上述的典型值。

5. 返回值

  • 成功: 返回 0。

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

  • EFAULT: u_info 指向无效的内存地址。

  • EINVAL: entry_number 无效,或者 user_desc 结构体中的某些字段值无效。

  • EPERM: 进程没有权限执行此操作(在某些安全模型下)。

6. 相似函数或关联函数

  • get_thread_area: 获取当前线程的 TLS 描述符。

  • arch_prctl: 在 x86-64 (AMD64) 架构上,用于设置 TLS,因为 x86-64 不使用 set_thread_area。例如,arch_prctl(ARCH_SET_FS, tls_base_address)。

  • __thread 关键字 (GCC): C 编译器提供的标准 TLS 支持。这是编写可移植 TLS 代码的推荐方式。编译器和 C 库(如 glibc)会处理底层细节(可能使用 set_thread_area 或 arch_prctl)。

  • pthread_getspecific / pthread_setspecific: POSIX 线程库提供的另一种实现线程本地存储的方式,它不依赖于 CPU 的段寄存器机制。

  • syscall: 用于直接调用 Linux 系统调用的函数。

7. 示例代码

由于 set_thread_area 是底层且架构特定的,直接使用它编写用户程序比较复杂且不推荐。下面的示例主要用于展示其用法,但请注意这更多是教学或系统级编程的内容。

警告:此代码仅在 i386 架构上可能有效,且需要对 x86 汇编和内存布局有一定了解。

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

// 注意:直接内联汇编访问 gs 段在不同编译器和优化级别下可能行为不同
// 这只是一个概念验证

int main() {
struct user_desc u_info;
long result;
void *tls_mem;
int my_tls_var_offset = 0; // 假设我们的 TLS 变量在 TLS 块的偏移 0 处

printf("--- Demonstrating set_thread_area (i386 specific) ---\n");
printf("This is a low-level example and may not work on all systems.\n");
printf("Architecture: %s\n", __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ ? "Little Endian" : "Big Endian");
printf("Pointer size: %zu bytes\n", sizeof(void*));

// 1. 分配一块内存用于 TLS 数据
// 这块内存将包含线程本地的变量
tls_mem = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (tls_mem == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
printf("Allocated TLS memory at: %p\n", tls_mem);

// 2. 在这块内存中存放一些数据
*(int*)tls_mem = 12345;
printf("Stored value %d at TLS memory location.\n", *(int*)tls_mem);

// 3. 准备 user_desc 结构体
memset(&u_info, 0, sizeof(u_info));
u_info.entry_number = -1; // 请求内核分配一个 entry
u_info.base_addr = (unsigned long) tls_mem; // 设置 TLS 块的基地址
u_info.limit = 0xfffff; // 设置段限制 (4GB)
u_info.seg_32bit = 1; // 32位段
u_info.contents = 0; // 数据段
u_info.read_exec_only = 0; // 可读写
u_info.limit_in_pages = 1; // 限制以页为单位
u_info.seg_not_present = 0; // 段存在
u_info.useable = 1; // 可用

printf("Setting up TLS descriptor...\n");
printf(" Base Address: 0x%lx\n", u_info.base_addr);
printf(" Entry Number requested: %u\n", u_info.entry_number);

// 4. 调用 set_thread_area 系统调用
result = syscall(SYS_set_thread_area, &u_info);

if (result == -1) {
perror("set_thread_area");
munmap(tls_mem, 4096);
exit(EXIT_FAILURE);
}

printf("set_thread_area succeeded.\n");
printf(" Assigned Entry Number: %u\n", u_info.entry_number);
printf(" Base Address (returned): 0x%lx\n", u_info.base_addr);

// 5. 理论上,现在可以通过 gs 段寄存器访问 tls_mem 指向的内存
// 例如,访问偏移 my_tls_var_offset 处的 4 字节整数
// 这需要内联汇编,且行为高度依赖于编译器和系统
// 下面的代码是概念性的,实际运行可能失败或产生未定义行为
/*
int value_from_tls = 0;
asm volatile (
"movl %%gs:%1, %0"
: "=r" (value_from_tls)
: "m" (*(int*)my_tls_var_offset)
);
printf("Value read from TLS via GS register: %d\n", value_from_tls);
*/

printf("\n--- Important Notes ---\n");
printf("1. Direct use of GS register via inline assembly is complex and not portable.\n");
printf("2. Modern code should use '__thread' keyword or pthread_getspecific/setspecific.\n");
printf("3. This example is for educational purposes on i386 architecture.\n");

// 6. 清理资源
munmap(tls_mem, 4096);
printf("Cleaned up TLS memory.\n");

return 0;
}

使用标准 TLS (__thread) 的对比示例 (推荐方式):

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
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 使用 __thread 关键字声明线程本地变量
// 编译器和 C 库会处理底层 TLS 机制
__thread int my_tls_variable = 0;

void* worker_thread(void *arg) {
long thread_id = (long)arg;
// 每个线程修改自己的 my_tls_variable 副本
my_tls_variable = thread_id * 100;
printf("Thread %ld: my_tls_variable = %d\n", thread_id, my_tls_variable);
sleep(1);
printf("Thread %ld: my_tls_variable is still %d\n", thread_id, my_tls_variable);
return NULL;
}

int main() {
pthread_t t1, t2;
printf("--- Using standard TLS (__thread) ---\n");

// 主线程的 my_tls_variable
my_tls_variable = 999;
printf("Main thread: my_tls_variable = %d\n", my_tls_variable);

// 创建线程
if (pthread_create(&t1, NULL, worker_thread, (void*)1) != 0 ||
pthread_create(&t2, NULL, worker_thread, (void*)2) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}

// 等待线程结束
pthread_join(t1, NULL);
pthread_join(t2, NULL);

// 检查主线程的变量是否未受影响
printf("Main thread: my_tls_variable is still %d\n", my_tls_variable);

printf("Program finished using standard, portable TLS.\n");
return 0;
}

编译和运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 假设代码保存在 set_thread_area_example.c 和 standard_tls_example.c 中

# 编译标准 TLS 示例 (推荐,跨平台)
gcc -o standard_tls_example standard_tls_example.c -lpthread

# 编译 set_thread_area 示例 (仅适用于 i386,可能需要调整)
# 在 64 位系统上编译 32 位程序
gcc -m32 -o set_thread_area_example set_thread_area_example.c

# 运行标准示例
./standard_tls_example

# 尝试运行 i386 示例 (可能失败或需要在 32 位系统/i386 模拟器上运行)
# ./set_thread_area_example

总结:对于 Linux 编程新手,请优先学习和使用标准的 TLS 机制,如 __thread 关键字。set_thread_area 是一个底层、架构特定的工具,主要用于系统级编程或与旧代码交互。理解它有助于深入学习操作系统和 CPU 架构知识。

setgid系统调用及示例

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

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

setfsgid系统调用及示例

系统调用LinuxGuide

setpgid系统调用及示例_setpgid函数作用-CSDN博客

1. 函数介绍

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

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

  • 有效 ID (Effective ID): 内核用来进行权限检查时使用的 ID。它决定了“你能做什么”。

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

[setgid(Set Group ID)系统调用的主要作用是设置调用进程的有效组 ID (Effective GID)。根据调用者的权限和当前 ID 状态,它也可能同时修改保存的设置组 ID (Saved Set-GID)。

简单来说,setgid 让你的程序可以“以某个组的身份”去执行操作,从而获得或限制与该组相关的权限。](https://www.calcguide.tech/2025/08/23/setgid系统调用及示例/)

一个常见的场景是:一个需要访问特定组才能读写的文件或设备的程序,可以通过 setgid 来获取相应的组权限。

2. 函数原型

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

int setgid(gid_t gid);

3. 功能

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

4. 参数

gid:

  • gid_t 类型。

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

5. 返回值

  • 成功: 返回 0。

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

6. 行为规则

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

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

  • 可以将有效 GID (egid) 设置为任意有效的组 ID。

  • 同时,真实 GID (rgid) 和保存的设置 GID (sgid) 也会被设置为相同的 gid 值。

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

如果调用者是普通用户:

  • gid 参数必须是调用进程的真实 GID (rgid) 或 保存的设置 GID (sgid) 之一。

  • 只能将有效 GID (egid) 设置为 rgid 或 sgid。

  • 真实 GID (rgid) 和 保存的设置 GID (sgid) 不会被修改。

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

7. 错误码 (errno)

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

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

8. 相似函数或关联函数

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

  • setegid: 专门用于设置有效组 ID,行为比 setgid 更受限(普通用户只能设置为 rgid 或 sgid)。

  • setregid: 同时设置真实组 ID 和 有效组 ID。

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

  • getgid: 获取调用进程的真实组 ID。

  • getegid: 获取调用进程的有效组 ID。

  • getgroups: 获取调用进程所属的附加组列表。

9. 示例代码

下面的示例演示了 setgid 在不同权限下的行为,以及如何检查组 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
#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresgid
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

void print_current_gids(const char* context) {
gid_t rgid, egid, sgid;
printf("&#91;%s] Current GIDs - Real: %d, Effective: %d, Saved: %d\n",
context, getgid(), getegid(), (getresgid(&rgid, &egid, &sgid) == 0) ? sgid : -1);
}

int main() {
gid_t original_rgid, original_egid, original_sgid;
gid_t target_gid;
int result;

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

// 1. 获取并打印初始 GID
if (getresgid(&original_rgid, &original_egid, &original_sgid) != 0) {
perror("getresgid");
exit(EXIT_FAILURE);
}
print_current_gids("Start");

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

print_current_gids("Before setgid");
result = setgid(target_gid);
if (result == 0) {
printf("setgid(%d) succeeded.\n", target_gid);
print_current_gids("After setgid");
printf("Note: All GIDs (Real, Effective, Saved) are now %d.\n", target_gid);
} else {
perror("setgid");
printf("Failed to set GID to %d.\n", target_gid);
}

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

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

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

printf("\n--- Summary ---\n");
printf("The setgid() function changes the Effective GID of the process.\n");
printf("For root: It can change to any GID, and also changes Real and Saved GID.\n");
printf("For regular users: It can only change Effective GID to Real or Saved GID.\n");

return 0;
}

编译和运行:

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

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

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

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

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

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

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

--- Summary ---
The setgid() function changes the Effective GID of the process.
For root: It can change to any GID, and also changes Real and Saved GID.
For regular users: It can only change Effective GID to Real or Saved GID.

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

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

Running as ROOT (Privileged User)
Attempting to set GID to 1 (usually 'daemon' group)...
&#91;Before setgid] Current GIDs - Real: 0, Effective: 0, Saved: 0
setgid(1) succeeded.
&#91;After setgid] Current GIDs - Real: 1, Effective: 1, Saved: 1
Note: All GIDs (Real, Effective, Saved) are now 1.

总结:setgid 是一个基础且重要的系统调用,用于管理进程的组权限。理解它的行为规则(尤其是特权用户和普通用户的区别)对于编写安全的 Linux 程序至关重要。在实际应用中,它常用于守护进程(daemons)或需要特定组权限的程序中。

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

sethostname系统调用及示例

我们来深入学习 sethostname 系统调用,在 Linux 系统(以及大多数 Unix-like 系统)中,每台计算机都有一个唯一的标识符,叫做 主机名 (hostname)。这个主机名用于在网络中识别这台机器。例如,当你在命令行输入 hostname 时,它会显示当前机器的主机名。sethostname 系统调用的作用就是设置这台运行着 Linux 内核的计算机的 主机名。这是一个系统级别的设置,会影响整个机器,而不仅仅是调用它的那个进程。

1. 函数介绍

在 Linux 系统(以及大多数 Unix-like 系统)中,每台计算机都有一个唯一的标识符,叫做 主机名 (hostname)。这个主机名用于在网络中识别这台机器。例如,当你在命令行输入 hostname 时,它会显示当前机器的主机名。sethostname 系统调用的作用就是设置这台运行着 Linux 内核的计算机的 主机名。这是一个系统级别的设置,会影响整个机器,而不仅仅是调用它的那个进程。

简单来说,sethostname 就是让你用程序来给你的 Linux 电脑“改名字”。

重要提示:

需要权限:修改主机名是一个特权操作,通常只有 root 用户(超级用户)才有权限执行 sethostname。普通用户尝试调用它会失败。

影响范围:主机名是系统全局的属性。一旦通过 sethostname 修改,系统中所有查询主机名的地方(如 gethostname, uname 命令)都会返回新的名字。

持久性:通过 sethostname 设置的主机名是临时的。它只在当前的系统运行会话(直到关机或重启)中有效。系统重启后,主机会从配置文件(如 /etc/hostname)中读取并恢复原来的主机名。

2. 函数原型

1
2
3
4
5
#include <unistd.h>      // 包含系统调用声明
#include <sys/utsname.h> // 有时也需要,包含主机名长度常量

int sethostname(const char *name, size_t len);

3. 功能

设置内核维护的主机名。这个主机名可以通过 gethostname 系统调用或 uname 系统调用来查询。

4. 参数

name:

  • const char * 类型。

  • 一个指向以 null 结尾的字符串的指针,该字符串包含了新的主机名。

len:

  • size_t 类型。

  • 指定 name 字符串中实际包含的字符数(不包括末尾的 null 终止符 \0)。通常使用 strlen(name) 来获取这个长度。

5. 返回值

  • 成功: 返回 0。

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

6. 错误码 (errno)

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

  • EINVAL: len 超过了系统允许的最大主机名长度(通常是 MAXHOSTNAMELEN,在 <sys/utsname.h> 或 <limits.h> 中定义,常见值是 64 或 256)。

  • EPERM: 调用进程没有权限(不是 root 用户)来更改主机名。

7. 相似函数或关联函数

  • gethostname: 用于获取当前的主机名。

  • uname: 系统调用,可以获取包括主机名在内的系统信息(系统名、版本、机器类型等)。对应的命令行工具也叫 uname。

  • hostname: 命令行工具,用于显示或设置系统的主机名。它在底层就是调用 sethostname 和 gethostname。

  • /etc/hostname: 在许多 Linux 发行版中,系统启动时会从这个文件读取主机名并使用 sethostname 设置。

8. 示例代码

下面的示例演示了如何使用 sethostname 来修改主机名,以及如何使用 gethostname 来查询它。

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
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h> // 包含 gethostname, uname, MAXHOSTNAMELEN
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#define HOST_NAME_BUFFER_SIZE MAXHOSTNAMELEN // 通常定义在 <sys/utsname.h> 或 <limits.h>

void print_current_hostname(const char* context) {
char hostname&#91;HOST_NAME_BUFFER_SIZE];
if (gethostname(hostname, sizeof(hostname)) == 0) {
printf("&#91;%s] Current hostname is: '%s'\n", context, hostname);
} else {
perror("gethostname");
}
}

int main(int argc, char *argv&#91;]) {
char new_hostname&#91;HOST_NAME_BUFFER_SIZE];
struct utsname uname_info; // 用于 uname 系统调用

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

// 1. 显示当前主机名
print_current_hostname("Initial");

// 2. 使用 uname 系统调用获取更详细的系统信息
if (uname(&uname_info) == 0) {
printf("&#91;uname] System name: %s\n", uname_info.sysname);
printf("&#91;uname] Node name (hostname): %s\n", uname_info.nodename);
printf("&#91;uname] Release: %s\n", uname_info.release);
printf("&#91;uname] Version: %s\n", uname_info.version);
printf("&#91;uname] Machine: %s\n", uname_info.machine);
} else {
perror("uname");
}

// 3. 检查命令行参数
if (argc != 2) {
printf("Usage: %s <new_hostname>\n", argv&#91;0]);
printf("Note: You need to run this program as root to change the hostname.\n");
exit(EXIT_FAILURE);
}

strncpy(new_hostname, argv&#91;1], sizeof(new_hostname) - 1);
new_hostname&#91;sizeof(new_hostname) - 1] = '\0'; // 确保 null 终止

printf("\nAttempting to change hostname to: '%s'\n", new_hostname);

// 4. 调用 sethostname
// 注意:这需要 root 权限
if (sethostname(new_hostname, strlen(new_hostname)) == -1) {
perror("sethostname");
if (errno == EPERM) {
printf("Error: Permission denied. You must run this program as root (e.g., using sudo).\n");
}
exit(EXIT_FAILURE);
}

printf("sethostname('%s') succeeded.\n", new_hostname);

// 5. 再次显示主机名以验证更改
print_current_hostname("After sethostname");

// 6. 再次使用 uname 验证
if (uname(&uname_info) == 0) {
printf("&#91;uname after change] Node name (hostname): %s\n", uname_info.nodename);
}

printf("\n--- Important Notes ---\n");
printf("1. The hostname change is TEMPORARY and only lasts until the system is rebooted.\n");
printf("2. To make the change persistent, you need to update configuration files like /etc/hostname.\n");
printf("3. You need ROOT privileges to call sethostname.\n");

return 0;
}

编译和运行:

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

# 1. 不带参数运行 (会报错)
./sethostname_example

# 2. 带一个参数运行,但没有 root 权限 (会失败)
./sethostname_example MyNewTempHostname

# 3. 带一个参数运行,并使用 sudo 获取 root 权限 (应该成功)
# 注意:请将 MyNewTempHostname 替换为你想要的主机名
sudo ./sethostname_example MyNewTempHostname

# 4. 验证更改
hostname
uname -n

预期输出 (使用 sudo 运行):

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
# 假设原始主机名是 'old-hostname'
$ sudo ./sethostname_example NewTempName
--- Demonstrating sethostname ---
&#91;Initial] Current hostname is: 'old-hostname'
&#91;uname] System name: Linux
&#91;uname] Node name (hostname): old-hostname
&#91;uname] Release: 5.4.0-XX-generic
&#91;uname] Version: #XX-Ubuntu SMP ...
&#91;uname] Machine: x86_64

Attempting to change hostname to: 'NewTempName'
sethostname('NewTempName') succeeded.
&#91;After sethostname] Current hostname is: 'NewTempName'
&#91;uname after change] Node name (hostname): NewTempName

--- Important Notes ---
1. The hostname change is TEMPORARY and only lasts until the system is rebooted.
2. To make the change persistent, you need to update configuration files like /etc/hostname.
3. You need ROOT privileges to call sethostname.

# 验证命令
$ hostname
NewTempName
$ uname -n
NewTempName

重启后:如果你重启系统,主机会名会恢复到 /etc/hostname 文件中配置的名称。

总结:sethostname 是一个用于修改系统全局主机名的系统调用。它需要 root 权限,并且修改是临时的。理解它有助于你编写需要动态管理主机名的系统管理工具。在日常使用中,hostname 命令是更常见的设置主机名的方式。

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

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

setgroups系统调用及示例

setgroups 函数详解

  1. 函数介绍

setgroups 是Linux系统调用,用于设置进程的附加组ID列表。每个进程都可以属于多个组,除了主组ID(由setgid设置)外,还可以通过附加组ID列表拥有多个组的权限。这对于实现细粒度的权限控制和访问控制非常重要。

csdn:setgroups系统调用及示例-CSDN博客

  1. 函数原型
1
2
3
4
#include <grp.h>
#include <unistd.h>
int setgroups(size_t size, const gid_t *list);

  1. 功能

setgroups 设置调用进程的附加组ID列表,替换当前的附加组集合。这允许进程获得多个组的权限,从而可以访问属于这些组的文件和资源。

  1. 参数
  • size_t size: 附加组ID列表的大小(元素个数)

  • *const gid_t list: 指向组ID数组的指针

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

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

  1. 相似函数,或关联函数
  • getgroups: 获取当前附加组ID列表

  • setgid: 设置主组ID

  • setuid: 设置用户ID

  • initgroups: 根据用户初始化组列表

  1. 示例代码

示例1:基础setgroups使用

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

/**
* 显示当前用户和组信息
*/
void show_current_user_info() {
uid_t uid = getuid();
gid_t gid = getgid();
uid_t euid = geteuid();
gid_t egid = getegid();

printf("=== 当前用户和组信息 ===\n");
printf("真实用户ID: %d\n", uid);
printf("真实组ID: %d\n", gid);
printf("有效用户ID: %d\n", euid);
printf("有效组ID: %d\n", egid);

// 显示当前附加组
int max_groups = getgroups(0, NULL);
if (max_groups > 0) {
gid_t *groups = malloc(max_groups * sizeof(gid_t));
if (groups) {
int num_groups = getgroups(max_groups, groups);
if (num_groups > 0) {
printf("附加组ID (%d个): ", num_groups);
for (int i = 0; i < num_groups; i++) {
printf("%d ", groups&#91;i]);
}
printf("\n");

// 显示组名
printf("附加组名: ");
for (int i = 0; i < num_groups; i++) {
struct group *grp = getgrgid(groups&#91;i]);
if (grp) {
printf("%s ", grp->gr_name);
} else {
printf("%d ", groups&#91;i]);
}
}
printf("\n");
}
free(groups);
}
}
printf("\n");
}

/**
* 演示基础setgroups使用方法
*/
int demo_setgroups_basic() {
gid_t current_groups&#91;32];
gid_t new_groups&#91;] = {100, 200, 300, 400}; // 示例组ID
int num_current, num_new = 4;
int result;

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

// 显示当前用户信息
show_current_user_info();

// 获取当前附加组
num_current = getgroups(sizeof(current_groups) / sizeof(current_groups&#91;0]),
current_groups);
if (num_current == -1) {
printf("获取当前附加组失败: %s\n", strerror(errno));
return -1;
}

printf("当前附加组数量: %d\n", num_current);
if (num_current > 0) {
printf("当前附加组ID: ");
for (int i = 0; i < num_current; i++) {
printf("%d ", current_groups&#91;i]);
}
printf("\n");
}

// 尝试设置新的附加组(需要适当权限)
printf("\n尝试设置新的附加组列表:\n");
for (int i = 0; i < num_new; i++) {
printf(" 组ID: %d\n", new_groups&#91;i]);
}

result = setgroups(num_new, new_groups);
if (result == 0) {
printf("✓ 成功设置附加组列表\n");

// 验证设置结果
gid_t verify_groups&#91;32];
int verify_count = getgroups(sizeof(verify_groups) / sizeof(verify_groups&#91;0]),
verify_groups);
if (verify_count > 0) {
printf("验证附加组列表:\n");
printf(" 组数量: %d\n", verify_count);
printf(" 组ID: ");
for (int i = 0; i < verify_count; i++) {
printf("%d ", verify_groups&#91;i]);
}
printf("\n");
}
} else {
printf("✗ 设置附加组列表失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 原因:需要CAP_SETGID权限或root权限\n");
} else if (errno == EINVAL) {
printf(" 原因:参数无效\n");
}

printf("注意:普通用户通常无法修改附加组列表\n");
}

return 0;
}

int main() {
return demo_setgroups_basic();
}

示例2:权限检查和组管理

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

/**
* 检查当前权限
*/
void check_permissions() {
uid_t uid = getuid();
uid_t euid = geteuid();
gid_t gid = getgid();

printf("=== 权限检查 ===\n");
printf("真实用户ID: %d\n", uid);
printf("有效用户ID: %d\n", euid);
printf("主组ID: %d\n", gid);

if (uid == 0 || euid == 0) {
printf("✓ 当前具有root权限\n");
} else {
printf("✗ 当前没有root权限\n");
printf(" 提示:修改附加组需要CAP_SETGID权限或root权限\n");
}
printf("\n");
}

/**
* 查找系统中的组
*/
void find_system_groups() {
struct group *grp;
int group_count = 0;

printf("=== 系统中的部分组信息 ===\n");

// 查找一些常见的组
const char *common_groups&#91;] = {"root", "daemon", "sys", "adm", "tty", "disk", NULL};

for (int i = 0; common_groups&#91;i]; i++) {
grp = getgrnam(common_groups&#91;i]);
if (grp) {
printf(" 组名: %-10s ID: %d\n", grp->gr_name, grp->gr_gid);
group_count++;
}
}

printf(" 找到 %d 个常用组\n\n", group_count);
}

/**
* 演示权限检查和组管理
*/
int demo_permission_check() {
gid_t test_groups&#91;5];
int num_groups = 0;

printf("=== 权限检查和组管理演示 ===\n");

// 检查当前权限
check_permissions();

// 查找系统组
find_system_groups();

// 准备测试组列表
struct group *grp;

// 尝试获取一些系统组ID
grp = getgrnam("daemon");
if (grp) {
test_groups&#91;num_groups++] = grp->gr_gid;
printf("添加组 daemon (ID: %d) 到测试列表\n", grp->gr_gid);
}

grp = getgrnam("sys");
if (grp) {
test_groups&#91;num_groups++] = grp->gr_gid;
printf("添加组 sys (ID: %d) 到测试列表\n", grp->gr_gid);
}

grp = getgrnam("adm");
if (grp) {
test_groups&#91;num_groups++] = grp->gr_gid;
printf("添加组 adm (ID: %d) 到测试列表\n", grp->gr_gid);
}

if (num_groups == 0) {
// 如果找不到系统组,使用示例ID
test_groups&#91;0] = 100;
test_groups&#91;1] = 200;
test_groups&#91;2] = 300;
num_groups = 3;
printf("使用示例组ID: 100, 200, 300\n");
}

// 显示当前附加组
printf("\n当前附加组:\n");
int current_count = getgroups(0, NULL);
if (current_count > 0) {
gid_t *current_groups = malloc(current_count * sizeof(gid_t));
if (current_groups) {
int actual_count = getgroups(current_count, current_groups);
if (actual_count > 0) {
printf(" ");
for (int i = 0; i < actual_count; i++) {
struct group *g = getgrgid(current_groups&#91;i]);
if (g) {
printf("%s(%d) ", g->gr_name, current_groups&#91;i]);
} else {
printf("%d ", current_groups&#91;i]);
}
}
printf("\n");
}
free(current_groups);
}
}

// 尝试设置附加组
printf("\n尝试设置附加组列表:\n");
printf(" 组数量: %d\n", num_groups);
printf(" 组ID: ");
for (int i = 0; i < num_groups; i++) {
struct group *g = getgrgid(test_groups&#91;i]);
if (g) {
printf("%s(%d) ", g->gr_name, test_groups&#91;i]);
} else {
printf("%d ", test_groups&#91;i]);
}
}
printf("\n");

int result = setgroups(num_groups, test_groups);
if (result == 0) {
printf("✓ 附加组设置成功\n");
} else {
printf("✗ 附加组设置失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 需要root权限或CAP_SETGID能力\n");
}
}

return 0;
}

int main() {
return demo_permission_check();
}

示例3:initgroups替代实现

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

/**
* 自定义initgroups实现
*/
int my_initgroups(const char *user, gid_t group) {
struct group *grp;
gid_t *groups = NULL;
int ngroups = 0;
int max_groups = 32;

printf("=== 自定义initgroups实现 ===\n");
printf("用户: %s, 主组: %d\n", user, group);

// 分配初始组数组
groups = malloc(max_groups * sizeof(gid_t));
if (!groups) {
perror("分配内存失败");
return -1;
}

// 添加主组
groups&#91;ngroups++] = group;

// 查找用户所属的所有组
setgrent(); // 重新开始组文件扫描

while ((grp = getgrent()) != NULL) {
// 检查用户是否在该组的成员列表中
if (grp->gr_mem) {
for (char **member = grp->gr_mem; *member; member++) {
if (strcmp(*member, user) == 0) {
// 确保不重复添加主组
if (grp->gr_gid != group) {
// 检查是否需要扩展数组
if (ngroups >= max_groups) {
max_groups *= 2;
gid_t *new_groups = realloc(groups, max_groups * sizeof(gid_t));
if (!new_groups) {
perror("重新分配内存失败");
free(groups);
endgrent();
return -1;
}
groups = new_groups;
}

groups&#91;ngroups++] = grp->gr_gid;
printf(" 发现用户组: %s (ID: %d)\n", grp->gr_name, grp->gr_gid);
}
break;
}
}
}
}

endgrent();

printf("总共找到 %d 个组\n", ngroups);

// 设置附加组
int result = setgroups(ngroups, groups);
if (result == 0) {
printf("成功设置附加组列表\n");
} else {
printf("设置附加组失败: %s\n", strerror(errno));
}

free(groups);
return result;
}

/**
* 演示自定义initgroups
*/
int demo_custom_initgroups() {
struct passwd *pwd;
const char *username;

printf("=== 自定义initgroups演示 ===\n");

// 获取当前用户名
pwd = getpwuid(getuid());
if (pwd) {
username = pwd->pw_name;
printf("当前用户: %s (UID: %d, GID: %d)\n",
username, pwd->pw_uid, pwd->pw_gid);
} else {
printf("无法获取当前用户信息\n");
return -1;
}

// 显示用户信息
printf("\n用户详细信息:\n");
printf(" 用户名: %s\n", pwd->pw_name);
printf(" 用户ID: %d\n", pwd->pw_uid);
printf(" 主组ID: %d\n", pwd->pw_gid);
printf(" 主目录: %s\n", pwd->pw_dir);
printf(" 登录shell: %s\n", pwd->pw_shell);

// 使用自定义initgroups
printf("\n调用自定义initgroups:\n");
int result = my_initgroups(username, pwd->pw_gid);

if (result == 0) {
printf("✓ 自定义initgroups执行成功\n");

// 验证结果
printf("\n验证附加组设置:\n");
int max_groups = getgroups(0, NULL);
if (max_groups > 0) {
gid_t *groups = malloc(max_groups * sizeof(gid_t));
if (groups) {
int actual_count = getgroups(max_groups, groups);
if (actual_count > 0) {
printf(" 附加组数量: %d\n", actual_count);
printf(" 附加组列表: ");
for (int i = 0; i < actual_count; i++) {
struct group *g = getgrgid(groups&#91;i]);
if (g) {
printf("%s(%d) ", g->gr_name, groups&#91;i]);
} else {
printf("%d ", groups&#91;i]);
}
}
printf("\n");
}
free(groups);
}
}
} else {
printf("✗ 自定义initgroups执行失败\n");
}

return 0;
}

int main() {
return demo_custom_initgroups();
}

示例4:组管理工具

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

/**
* 组信息结构
*/
typedef struct {
gid_t gid;
char name&#91;256];
int is_member;
} group_info_t;

/**
* 显示组列表
*/
void show_group_list(group_info_t *groups, int count) {
printf("组列表 (%d 个组):\n", count);
printf("%-8s %-20s %-10s\n", "GID", "组名", "成员状态");
printf("----------------------------------------\n");

for (int i = 0; i < count; i++) {
printf("%-8d %-20s %-10s\n",
groups&#91;i].gid, groups&#91;i].name,
groups&#91;i].is_member ? "是" : "否");
}
printf("\n");
}

/**
* 获取当前附加组
*/
int get_current_groups(gid_t **groups) {
int count = getgroups(0, NULL);
if (count <= 0) {
return count;
}

*groups = malloc(count * sizeof(gid_t));
if (!*groups) {
return -1;
}

count = getgroups(count, *groups);
return count;
}

/**
* 检查组是否在列表中
*/
int is_group_in_list(gid_t gid, gid_t *groups, int count) {
for (int i = 0; i < count; i++) {
if (groups&#91;i] == gid) {
return 1;
}
}
return 0;
}

/**
* 演示组管理工具
*/
int demo_group_manager() {
gid_t *current_groups = NULL;
int current_count = 0;
struct group *grp;
group_info_t test_groups&#91;10];
int test_count = 0;

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

// 获取当前附加组
current_count = get_current_groups(&current_groups);
if (current_count < 0) {
printf("获取当前附加组失败: %s\n", strerror(errno));
return -1;
}

printf("当前进程附加组数量: %d\n", current_count);
if (current_count > 0) {
printf("当前附加组ID: ");
for (int i = 0; i < current_count; i++) {
printf("%d ", current_groups&#91;i]);
}
printf("\n\n");
}

// 准备测试组列表
printf("准备测试组列表:\n");

// 添加一些常见组
const char *group_names&#91;] = {"daemon", "sys", "adm", "tty", "disk", "audio", NULL};

for (int i = 0; group_names&#91;i] && test_count < 10; i++) {
grp = getgrnam(group_names&#91;i]);
if (grp) {
test_groups&#91;test_count].gid = grp->gr_gid;
strncpy(test_groups&#91;test_count].name, grp->gr_name,
sizeof(test_groups&#91;test_count].name) - 1);
test_groups&#91;test_count].name&#91;sizeof(test_groups&#91;test_count].name) - 1] = '\0';
test_groups&#91;test_count].is_member = is_group_in_list(grp->gr_gid,
current_groups, current_count);
test_count++;
printf(" 添加组: %s (ID: %d) %s\n",
grp->gr_name, grp->gr_gid,
test_groups&#91;test_count-1].is_member ? "&#91;当前成员]" : "");
}
}

// 如果没找到足够的系统组,添加示例组
if (test_count < 3) {
for (int i = test_count; i < 3 && i < 10; i++) {
test_groups&#91;i].gid = 1000 + i;
snprintf(test_groups&#91;i].name, sizeof(test_groups&#91;i].name), "group_%d", 1000 + i);
test_groups&#91;i].is_member = 0;
test_count++;
}
}

printf("\n");
show_group_list(test_groups, test_count);

// 演示设置附加组
printf("尝试设置新的附加组列表:\n");
gid_t new_groups&#91;5];
int new_count = 0;

// 选择前3个组进行设置
for (int i = 0; i < test_count && new_count < 5 && i < 3; i++) {
new_groups&#91;new_count++] = test_groups&#91;i].gid;
printf(" 添加组: %s (ID: %d)\n", test_groups&#91;i].name, test_groups&#91;i].gid);
}

int result = setgroups(new_count, new_groups);
if (result == 0) {
printf("✓ 附加组设置成功\n");

// 验证设置结果
printf("\n验证设置结果:\n");
gid_t *verify_groups = NULL;
int verify_count = get_current_groups(&verify_groups);
if (verify_count > 0) {
printf(" 验证组数量: %d\n", verify_count);
printf(" 验证组ID: ");
for (int i = 0; i < verify_count; i++) {
printf("%d ", verify_groups&#91;i]);
}
printf("\n");
}
if (verify_groups) {
free(verify_groups);
}
} else {
printf("✗ 附加组设置失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 需要root权限或CAP_SETGID能力\n");
}
}

// 清理资源
if (current_groups) {
free(current_groups);
}

return 0;
}

int main() {
return demo_group_manager();
}

示例5:安全组切换演示

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

/**
* 显示进程权限信息
*/
void show_process_capabilities() {
uid_t uid = getuid();
gid_t gid = getgid();
uid_t euid = geteuid();
gid_t egid = getegid();

printf("=== 进程权限信息 ===\n");
printf("真实用户ID: %d\n", uid);
printf("真实组ID: %d\n", gid);
printf("有效用户ID: %d\n", euid);
printf("有效组ID: %d\n", egid);

// 显示附加组数量
int ngids = getgroups(0, NULL);
if (ngids >= 0) {
printf("附加组数量: %d\n", ngids);
}

// 显示当前附加组
if (ngids > 0) {
gid_t *gids = malloc(ngids * sizeof(gid_t));
if (gids) {
int actual = getgroups(ngids, gids);
if (actual > 0) {
printf("附加组ID: ");
for (int i = 0; i < actual; i++) {
printf("%d ", gids&#91;i]);
}
printf("\n");
}
free(gids);
}
}
printf("\n");
}

/**
* 安全组切换演示
*/
int demo_secure_group_switching() {
gid_t original_groups&#91;32];
gid_t test_groups&#91;3] = {1000, 2000, 3000};
int original_count, test_count = 3;

printf("=== 安全组切换演示 ===\n");

// 显示原始权限信息
show_process_capabilities();

// 保存原始附加组
original_count = getgroups(sizeof(original_groups) / sizeof(original_groups&#91;0]),
original_groups);
if (original_count == -1) {
printf("获取原始附加组失败: %s\n", strerror(errno));
return -1;
}

printf("原始附加组数量: %d\n", original_count);
if (original_count > 0) {
printf("原始附加组ID: ");
for (int i = 0; i < original_count; i++) {
printf("%d ", original_groups&#91;i]);
}
printf("\n");
}

// 尝试设置测试组
printf("\n1. 设置测试附加组:\n");
printf(" 测试组ID: ");
for (int i = 0; i < test_count; i++) {
printf("%d ", test_groups&#91;i]);
}
printf("\n");

int result = setgroups(test_count, test_groups);
if (result == 0) {
printf(" ✓ 测试组设置成功\n");
show_process_capabilities();
} else {
printf(" ✗ 测试组设置失败: %s\n", strerror(errno));
if (errno == EPERM) {
printf(" 需要root权限或CAP_SETGID能力\n");
printf(" 演示将继续使用当前权限\n");
}
}

// 演示临时组权限使用
printf("\n2. 模拟使用组权限:\n");
if (result == 0) {
printf(" 使用新设置的附加组权限...\n");
// 这里可以进行需要特定组权限的操作
printf(" 模拟文件访问检查...\n");
} else {
printf(" 使用当前附加组权限...\n");
}

// 恢复原始附加组
printf("\n3. 恢复原始附加组:\n");
if (original_count >= 0) {
result = setgroups(original_count, original_groups);
if (result == 0) {
printf(" ✓ 原始附加组恢复成功\n");
} else {
printf(" ✗ 原始附加组恢复失败: %s\n", strerror(errno));
}
}

show_process_capabilities();

// 演示安全最佳实践
printf("\n4. 安全最佳实践:\n");
printf(" ✓ 始终保存原始权限状态\n");
printf(" ✓ 在使用特殊权限后及时恢复\n");
printf(" ✓ 检查权限操作的返回值\n");
printf(" ✓ 记录权限变更日志\n");
printf(" ✓ 最小权限原则:只请求必需的权限\n");

// 显示组相关安全信息
printf("\n5. 组安全相关信息:\n");
printf(" Linux限制:最多支持 %d 个附加组\n", NGROUPS_MAX);
printf(" 权限要求:修改附加组需要CAP_SETGID能力\n");
printf(" 安全建议:避免在生产环境中频繁切换组权限\n");

return 0;
}

int main() {
return demo_secure_group_switching();
}

setgroups 使用注意事项

系统要求:

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

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

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

参数限制:

数量限制: 系统限制附加组数量(通常NGROUPS_MAX)

组ID有效性: 组ID应该在系统中存在

空指针处理: list为NULL时size应该为0

错误处理:

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

EINVAL: 参数无效(size过大或指针无效)

ENOMEM: 内存不足

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

性能考虑:

系统调用开销: 频繁调用有性能开销

内核数据结构: 需要更新内核中的组信息

权限检查: 每次调用都需要权限验证

安全考虑:

权限提升: 可能导致权限提升风险

审计日志: 建议记录权限变更操作

最小权限: 遵循最小权限原则

状态恢复: 及时恢复原始权限状态

最佳实践:

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

参数验证: 验证参数的有效性和安全性

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

状态保存: 保存原始状态以便恢复

日志记录: 记录权限变更操作

及时恢复: 使用完特殊权限后及时恢复

常见使用场景

1. 用户登录处理:

1
2
3
4
5
6
7
8
// 用户登录时初始化组权限
struct passwd *pwd = getpwnam(username);
if (pwd) {
initgroups(username, pwd->pw_gid);
setgid(pwd->pw_gid);
setuid(pwd->pw_uid);
}

2. 服务权限管理:

1
2
3
4
// 服务启动时设置适当的组权限
gid_t service_groups&#91;] = {GROUP_DAEMON, GROUP_LOG};
setgroups(2, service_groups);

3. 安全沙箱:

1
2
3
// 创建受限环境时移除不必要的组权限
setgroups(0, NULL); // 清空附加组

组管理相关常量

系统限制:

1
2
3
4
#include <limits.h>
// NGROUPS_MAX: 系统支持的最大附加组数量
printf("最大附加组数量: %ld\n", sysconf(_SC_NGROUPS_MAX));

权限能力检查

检查CAP_SETGID能力:

1
2
3
#include <sys/capability.h>
// 检查进程是否具有设置组ID的能力

总结

setgroups 是Linux系统中重要的权限管理函数,提供了:

组权限控制: 精确控制进程的组权限

灵活配置: 支持动态设置附加组列表

安全机制: 通过权限检查保证系统安全

标准兼容: 符合POSIX标准

通过合理使用 setgroups,可以实现细粒度的权限控制,构建更加安全可靠的系统应用。在实际应用中,需要注意权限要求、错误处理和安全最佳实践。

 setns 系统调用及示例

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

1. 函数介绍

Linux 命名空间 (Namespaces) 是 Linux 内核的一个强大特性,它提供了隔离机制。通过命名空间,可以将一组进程及其资源(如网络接口、挂载点、进程 ID 等)与系统上的其他进程隔离开来,仿佛它们运行在独立的系统中一样。这是实现 容器 (Containers) 技术(如 Docker, LXC)的核心基础之一。

Linux 支持多种类型的命名空间,每种隔离不同类型的系统资源:

  • Mount (mnt): 隔离文件系统挂载点。

  • PID (pid): 隔离进程 ID 空间。

  • Network (net): 隔离网络设备、IP 地址、端口等。

  • Interprocess Communication (ipc): 隔离 System V IPC 和 POSIX 消息队列。

  • UTS (uts): 隔离主机名和域名 (nodename/domainname)。

  • User (user): 隔离用户和组 ID。

  • Cgroup (cgroup): 隔离控制组 (cgroups) 的视图。

setns (Set Namespace) 系统调用的作用是:将调用它的进程,加入到一个已经存在的命名空间中。

想象一下,你手里有一把钥匙,这把钥匙可以打开一扇通往某个“隔离房间”的门。setns 就像是你使用这把钥匙(文件描述符)进入那个特定的“隔离房间”(命名空间)的过程。一旦进入,你就能看到并使用那个房间里的东西(资源),就像你属于那个房间一样。

简单来说,setns 让一个正在运行的进程可以“穿越”到另一个隔离的环境(命名空间)中去。

重要提示:

需要权限:加入某些类型的命名空间(尤其是 User 命名空间)可能需要特殊权限或遵循复杂的规则。

文件描述符:setns 不是直接通过命名空间的名字或 ID 来操作,而是通过一个指向该命名空间的文件描述符。这个文件描述符通常是通过打开 /proc/[pid]/ns/ 目录下的特殊符号链接文件获得的。

部分加入:一个进程可以同时属于多个不同类型的命名空间。setns 每次只加入一个指定类型的命名空间。

2. 函数原型

1
2
3
4
5
6
7
// 标准 C 库通常不提供直接包装,需要通过 syscall 调用
#include <sched.h> // 包含 CLONE_* 常量,定义了命名空间类型
#include <sys/syscall.h> // 包含系统调用号 SYS_setns
#include <unistd.h> // 包含 syscall 函数

long syscall(SYS_setns, int fd, int nstype);

3. 功能

将调用进程加入由文件描述符 fd 指向的命名空间。如果 nstype 不为 0,还会检查该命名空间的类型是否与 nstype 指定的类型匹配。

4. 参数

fd:

  • int 类型。

  • 一个指向命名空间的文件描述符。这个文件描述符通常是通过打开 /proc/[pid]/ns/[namespace_name] 文件(例如 /proc/self/ns/net)获得的。[pid] 可以是目标进程的 PID,也可以是 self(代表调用进程自身)。

nstype:

  • int 类型。

指定要加入的命名空间的类型。这是一个检查机制。有效的值是 <sched.h> 中定义的 CLONE_NEW* 常量,例如:

  • CLONE_NEWIPC

  • CLONE_NEWNET

  • CLONE_NEWNS (Mount)

  • CLONE_NEWPID

  • CLONE_NEWUSER

  • CLONE_NEWUTS

  • CLONE_NEWCGROUP

如果 nstype 设置为 0,则不进行类型检查。

5. 返回值

  • 成功: 返回 0。

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

6. 错误码 (errno)

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

  • EINVAL: fd 是有效的,但它不指向一个命名空间文件,或者 nstype 指定的类型与文件描述符指向的命名空间类型不匹配。

  • ENOMEM: 内核内存不足,无法完成操作。

  • EPERM: 调用者没有权限加入该命名空间。例如,尝试加入一个 User 命名空间可能受到严格限制。

7. 相似函数或关联函数

  • unshare: 允许调用进程脱离当前的某个命名空间,并加入一个新创建的、空的同类型命名空间。

  • clone: 创建新进程时,可以通过传递 CLONE_NEW* 标志,使新进程在新的命名空间中启动。

  • /proc/[pid]/ns/: 这个目录包含了进程所处的各种命名空间的符号链接文件。通过打开这些文件可以获得命名空间的文件描述符。

  • nsenter: 命令行工具,可以在指定的命名空间中执行命令,它在底层使用了 setns。

8. 示例代码

由于命名空间涉及系统级隔离,创建和操作它们通常需要 root 权限或对 /proc 文件系统的访问。下面的示例将展示如何使用 setns 加入一个 Mount 命名空间。

警告:此示例需要 root 权限,并且会创建挂载点。请在测试环境中运行。

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
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/mount.h> // 包含 mount 函数
#include <sys/wait.h> // 包含 waitpid

// 简单的子进程函数,用于在新命名空间中执行
int child_func(void* arg) {
printf("Child process (PID: %d) started.\n", getpid());

// 在子进程中挂载一个 tmpfs 到 /tmp/my_test_mount
// 这个挂载操作只在子进程的 Mount Namespace 中可见
const char* mount_point = "/tmp/my_test_mount";
if (mkdir(mount_point, 0755) == -1 && errno != EEXIST) {
perror("mkdir (child)");
return 1;
}

if (mount("tmpfs", mount_point, "tmpfs", 0, NULL) == -1) {
perror("mount (child)");
return 1;
}

printf("Child process: Mounted tmpfs on %s\n", mount_point);

// 在挂载点创建一个文件
char file_path&#91;256];
snprintf(file_path, sizeof(file_path), "%s/test_file.txt", mount_point);
FILE *f = fopen(file_path, "w");
if (f) {
fprintf(f, "Hello from child process in its own mount namespace!\n");
fclose(f);
printf("Child process: Created file %s\n", file_path);
} else {
perror("fopen (child)");
}

printf("Child process: Sleeping for 30 seconds. Check /tmp/my_test_mount from parent and child.\n");
printf("Child process: You can run 'ls /tmp/my_test_mount' in another terminal as root.\n");
sleep(30); // 睡眠,让我们有时间从外部观察

// 清理 (可选,因为退出时会自动清理)
// umount(mount_point);
// rmdir(mount_point);

printf("Child process exiting.\n");
return 0;
}

int main() {
pid_t child_pid;
int parent_ns_fd, child_ns_fd;
char ns_path&#91;256];
const int STACK_SIZE = 1024 * 1024; // 1MB 栈
char *child_stack = malloc(STACK_SIZE);
if (!child_stack) {
perror("malloc");
exit(EXIT_FAILURE);
}

printf("--- Demonstrating setns with Mount Namespace ---\n");
printf("Main process PID: %d\n", getpid());

// 1. 获取父进程当前的 Mount Namespace 文件描述符
snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/mnt");
parent_ns_fd = open(ns_path, O_RDONLY);
if (parent_ns_fd == -1) {
perror("open parent namespace");
free(child_stack);
exit(EXIT_FAILURE);
}
printf("Opened parent's mount namespace fd: %d\n", parent_ns_fd);

// 2. 使用 clone 创建一个新进程,并让它拥有自己的 Mount Namespace
// CLONE_NEWNS: 创建新的 Mount Namespace
child_pid = clone(child_func, child_stack + STACK_SIZE,
CLONE_NEWNS | SIGCHLD, NULL);
if (child_pid == -1) {
perror("clone");
close(parent_ns_fd);
free(child_stack);
exit(EXIT_FAILURE);
}
printf("Created child process with new mount namespace. PID: %d\n", child_pid);

// 等待一小会儿,让子进程完成挂载
sleep(2);

// 3. 获取子进程的 Mount Namespace 文件描述符
snprintf(ns_path, sizeof(ns_path), "/proc/%d/ns/mnt", child_pid);
child_ns_fd = open(ns_path, O_RDONLY);
if (child_ns_fd == -1) {
perror("open child namespace");
close(parent_ns_fd);
// 杀死子进程
kill(child_pid, SIGKILL);
waitpid(child_pid, NULL, 0);
free(child_stack);
exit(EXIT_FAILURE);
}
printf("Opened child's mount namespace fd: %d\n", child_ns_fd);

// 4. 在父进程中,检查 /tmp/my_test_mount 是否存在
// (它应该不存在,因为父进程在不同的 Mount Namespace)
if (access("/tmp/my_test_mount", F_OK) == 0) {
printf("Parent process: /tmp/my_test_mount EXISTS in parent namespace (unexpected!).\n");
} else {
printf("Parent process: /tmp/my_test_mount does NOT exist in parent namespace (as expected).\n");
}

// 5. 现在,使用 setns 将父进程加入到子进程的 Mount Namespace
printf("\n--- Calling setns to join child's mount namespace ---\n");
if (syscall(SYS_setns, child_ns_fd, CLONE_NEWNS) == -1) {
perror("setns");
printf("Failed to join child's namespace. Do you have root privileges?\n");
close(parent_ns_fd);
close(child_ns_fd);
kill(child_pid, SIGKILL);
waitpid(child_pid, NULL, 0);
free(child_stack);
exit(EXIT_FAILURE);
}
printf("Parent process successfully joined child's mount namespace.\n");

// 6. 再次检查 /tmp/my_test_mount
// (现在它应该存在了,因为父进程已经加入了子进程的命名空间)
if (access("/tmp/my_test_mount", F_OK) == 0) {
printf("Parent process (after setns): /tmp/my_test_mount NOW EXISTS in current namespace.\n");
printf("Parent process (after setns): You can now see the file created by the child.\n");
// 尝试读取子进程创建的文件
char file_path&#91;256];
snprintf(file_path, sizeof(file_path), "%s/test_file.txt", "/tmp/my_test_mount");
FILE *f = fopen(file_path, "r");
if (f) {
char buffer&#91;256];
if (fgets(buffer, sizeof(buffer), f)) {
printf("Parent process (after setns): Read from file: %s", buffer);
}
fclose(f);
} else {
perror("fopen (parent after setns)");
}
} else {
printf("Parent process (after setns): /tmp/my_test_mount STILL does not exist (unexpected!).\n");
}

// 7. 清理和等待
printf("\n--- Cleaning up ---\n");
close(parent_ns_fd);
close(child_ns_fd);

// 等待子进程结束
int status;
waitpid(child_pid, &status, 0);
if (WIFEXITED(status)) {
printf("Child process exited with status %d.\n", WEXITSTATUS(status));
} else {
printf("Child process did not exit normally.\n");
}

free(child_stack);
printf("Main process finished.\n");

return 0;
}

使用 nsenter 命令行工具的对比示例:

nsenter 是一个非常方便的命令行工具,它封装了 setns 的功能,允许你在指定的命名空间中运行命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 启动一个长时间运行的进程 (例如 sleep) 并让它进入新的 PID 命名空间
# (通常与 unshare 一起使用)
unshare -p -f --mount-proc sleep 300 &
UNSHARE_PID=$!

# 等待一下让 unshare 启动
sleep 1

# 2. 查看这个新进程的 PID 命名空间 inode
ls -li /proc/1/ns/pid /proc/$UNSHARE_PID/ns/pid

# 3. 使用 nsenter 进入这个进程的 PID 和 Mount 命名空间,并运行 ps
# 这会显示在那个命名空间内部看到的进程
nsenter -t $UNSHARE_PID -p -m ps aux

# 4. 清理
kill $UNSHARE_PID

编译和运行:

1
2
3
4
5
6
7
8
9
# 假设代码保存在 setns_example.c 中
# 需要 root 权限来运行涉及 mount 和 setns 的操作

# 编译
gcc -o setns_example setns_example.c

# 运行 (必须使用 sudo)
sudo ./setns_example

预期输出 (片段):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--- Demonstrating setns with Mount Namespace ---
Main process PID: 12345
Opened parent's mount namespace fd: 3
Created child process with new mount namespace. PID: 12346
Child process (PID: 12346) started.
Child process: Mounted tmpfs on /tmp/my_test_mount
Child process: Created file /tmp/my_test_mount/test_file.txt
Child process: Sleeping for 30 seconds. Check /tmp/my_test_mount from parent and child.
Opened child's mount namespace fd: 4
Parent process: /tmp/my_test_mount does NOT exist in parent namespace (as expected).

--- Calling setns to join child's mount namespace ---
Parent process successfully joined child's mount namespace.
Parent process (after setns): /tmp/my_test_mount NOW EXISTS in current namespace.
Parent process (after setns): You can now see the file created by the child.
Parent process (after setns): Read from file: Hello from child process in its own mount namespace!

--- Cleaning up ---
Child process exited with status 0.
Main process finished.

总结:setns 是一个强大的系统调用,是 Linux 容器技术的基石之一。它允许进程动态地加入到已存在的命名空间中,从而获得该命名空间的视图和资源访问权限。对于 Linux 编程新手来说,理解命名空间的概念是第一步,setns 则是实现命名空间操作的关键工具。直接使用它进行编程比较复杂,通常在容器运行时(如 runc)或高级系统管理脚本中会用到。日常开发中,使用 nsenter 或容器管理工具(如 docker exec)是更常见的与命名空间交互的方式。

https://www.calcguide.tech/2025/08/20/awk分析nginx日志/

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

setns 系统调用及示例 - LinuxGuide setns setns,setns-系统调用及示例LinuxGuide

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

Ubuntu Download Sites and Mirror Sites Collection

Ubuntu Download Sites and Mirror Sites Collection

https://www.calcguide.tech/2025/08/23/ubuntu-download-sites-and-mirror-sites-collection/

🌐 Official Ubuntu Download Sites

Official Main Sites

Release Archive

🌍 Domestic Mirror Sites (China) - ✅ Verified

Educational Network Mirrors

Tsinghua University Mirror ✅

USTC Mirror ✅

Shanghai Jiao Tong University Mirror ✅

Huazhong University of Science and Technology Mirror ✅

Beijing Jiaotong University Mirror ✅

Commercial Mirrors

Alibaba Cloud Mirror ✅

Huawei Cloud Mirror ✅

Tencent Cloud Mirror ✅

163 Mirror ✅

Sohu Mirror ✅

🌎 International Mirror Sites - ✅ Verified

Asia Region

Japan Mirror ✅

Korea Mirror ✅

Singapore Mirror ✅

Europe Region

Germany Mirror ✅

France Mirror ✅

UK Mirror ✅

Netherlands Mirror ✅

North America Region

USA Official Mirror ✅

Canada Mirror ✅

USA West Coast Mirror ✅

📦 Ubuntu Version Download Links

Ubuntu 22.04.4 LTS (Jammy Jellyfish) ✅

Ubuntu 20.04.6 LTS (Focal Fossa) ✅

Ubuntu 23.10 (Mantic Minotaur) ✅

Alternative Architectures

⚡ Download Recommendations

Mirror Selection by Region

China Mainland: Tsinghua, Alibaba Cloud, USTC

Asia-Pacific: Japan, Singapore, Korea mirrors

Europe: Germany, France, Netherlands mirrors

North America: Official US mirrors

Other Regions: Choose geographically closest mirror

Download Methods

1
2
3
4
5
6
7
8
9
10
11
# Using wget (Recommended)
wget https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# Using aria2 for multi-threaded download
aria2c -x 16 -s 16 https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# Using curl
curl -O https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# Resume interrupted download
wget -c https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

Verify File Integrity

1
2
3
4
5
6
7
8
9
10
# Verify SHA256 checksum
sha256sum ubuntu-22.04.4-desktop-amd64.iso

# Verify with official checksum file
wget https://releases.ubuntu.com/22.04/SHA256SUMS
sha256sum -c SHA256SUMS 2>&1 | grep ubuntu-22.04.4-desktop-amd64.iso

# Verify GPG signature
wget https://releases.ubuntu.com/22.04/SHA256SUMS.gpg
gpg --verify SHA256SUMS.gpg SHA256SUMS

🔧 Related Tools

Image Writing Tools

Rufus (Windows) - https://rufus.ie/

Etcher (Cross-platform) - https://www.balena.io/etcher/

UNetbootin (Cross-platform) - https://unetbootin.github.io/

Ventoy (Multi-image management) - https://www.ventoy.net/

Startup Disk Creator (Ubuntu built-in)

Command Line Tools (Linux)

1
2
3
4
5
6
7
8
# Using dd (Linux)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/sdX bs=4M status=progress oflag=sync

# Using dd (macOS)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/rdiskX bs=1m

# Using balenaEtcher CLI
sudo etcher-cli ubuntu-22.04.4-desktop-amd64.iso --drive /dev/sdX

🔄 Repository Mirror Configuration

Using Chinese Mirrors for Package Updates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Backup original sources list
sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup

# Edit sources list
sudo nano /etc/apt/sources.list

# Replace with Tsinghua mirror (Ubuntu 22.04 example)
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse

Quick Mirror Switch Script

1
2
3
4
5
#!/bin/bash
# Switch to Tsinghua mirror
sudo sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo sed -i 's/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo apt update

⚠️ Important Notes

Security and Verification

Always verify checksums after download

Use official or trusted mirrors only

Check GPG signatures when available

Verify file size matches expected size

System Requirements

  • Desktop: 4GB RAM minimum, 25GB disk space

  • Server: 1GB RAM minimum, 5GB disk space

  • Recommended: 8GB+ RAM, 50GB+ disk space

  • Ubuntu is free and open-source

  • Complies with Ubuntu Copyright Policy

  • Commercial use permitted under Ubuntu Licensing

📊 Mirror Status Check

Quick Test Commands

1
2
3
4
5
6
7
8
9
10
11
# Test mirror connectivity
curl -s -o /dev/null -w "%{http_code}" https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/

# Test download speed (small file)
time wget -O /dev/null https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/HEADER.html

# Compare multiple mirrors
for mirror in "releases.ubuntu.com" "mirrors.tuna.tsinghua.edu.cn" "mirrors.aliyun.com"; do
echo "Testing $mirror:"
time wget -O /dev/null "https://$mirror/ubuntu-releases/HEADER.html" 2>&1 | grep real
done

🆘 Troubleshooting

Common Issues

Download interrupted: Use wget -c to resume

Checksum mismatch: Re-download from different mirror

Slow download: Try different geographic mirrors

Connection timeout: Check firewall/proxy settings

Alternative Download Methods

  • Torrent: Official Ubuntu torrents available

  • BitTorrent: P2P download option

  • CDN: Use commercial CDN mirrors

  • Local cache: University/institutional mirrors

All links marked with ✅ have been verified as working. This collection provides comprehensive options for downloading Ubuntu from the fastest and most reliable sources based on your geographic location.