sigaltstack系统调用及示例

sigaltstack 函数详解

  1. 函数介绍

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

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

  1. 功能

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

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

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

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

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

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

  • sigprocmask: 设置信号屏蔽字

  • setjmp/longjmp: 非局部跳转

  • getcontext/setcontext: 上下文操作

  1. 示例代码

示例1:基础sigaltstack使用

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// 清理资源
free(stack_buffer);

return 0;
}

int main() {
return demo_sigaltstack_basic();
}

示例2:栈溢出保护演示

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return result;
}

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

int main() {
return demo_stack_overflow_protection();
}

示例3:多信号处理程序管理

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

manager->current_stack = 0;

return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return 0;
}

int main() {
return demo_multi_signal_handlers();
}

示例4:线程安全的信号栈管理

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

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

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

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

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

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

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

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

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

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

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

return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return 0;
}

int main() {
return demo_thread_safe_signal_stacks();
}

示例5:信号栈监控和调试

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

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

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

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

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

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

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

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

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

printf("\n");
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// 清理资源
free(stack_buffer);

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

int main() {
return demo_stack_monitoring();
}

sigaltstack 使用注意事项

系统要求:

  1. 内核版本: 支持信号备用栈的Linux内核2. 权限要求: 通常不需要特殊权限3. 架构支持: 支持所有主流架构

栈大小考虑:

最小大小: 至少MINSIGSTKSZ字节

推荐大小: 使用SIGSTKSZ或更大

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

错误处理:

ENOMEM: 内存不足

EINVAL: 参数无效

EPERM: 权限不足

安全考虑:

  1. 栈溢出保护: 备用栈可以防止主栈溢出2. 信号安全: 确保信号处理程序的安全执行3. 资源管理: 及时释放分配的栈空间

最佳实践:

  1. 适当大小: 根据信号处理程序的需求分配栈大小2. 错误检查: 始终检查sigaltstack的返回值3. 资源清理: 程序结束时释放栈空间4. 线程安全: 多线程环境中每个线程需要独立的栈5. 监控调试: 监控栈使用情况以便调试

信号栈标志说明

SS_ONSTACK:

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

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

SS_DISABLE:

  • 含义: 备用栈被禁用

  • 用途: 禁用备用栈功能

SS_ENABLED:

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

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

相关常量

SIGSTKSZ:

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

  • 典型值: 8KB或更大

MINSIGSTKSZ:

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

  • 典型值: 2KB

常见使用场景

1. 栈溢出保护:

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

2. 信号处理程序:

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

3. 多线程应用:

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

总结

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

  1. 栈隔离: 为信号处理程序提供独立的执行环境2. 异常处理: 防止栈溢出等异常情况3. 安全执行: 确保信号处理程序的安全执行4. 灵活配置: 支持动态栈管理和配置

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

sigaltstack系统调用及示例

sigaltstack 函数详解

  1. 函数介绍

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

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

  1. 功能

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

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

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

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

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

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

  • sigprocmask: 设置信号屏蔽字

  • setjmp/longjmp: 非局部跳转

  • getcontext/setcontext: 上下文操作

  1. 示例代码

示例1:基础sigaltstack使用

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// 清理资源
free(stack_buffer);

return 0;
}

int main() {
return demo_sigaltstack_basic();
}

示例2:栈溢出保护演示

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return result;
}

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

int main() {
return demo_stack_overflow_protection();
}

示例3:多信号处理程序管理

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

manager->current_stack = 0;

return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return 0;
}

int main() {
return demo_multi_signal_handlers();
}

示例4:线程安全的信号栈管理

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

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

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

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

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

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

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

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

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

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

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

return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return 0;
}

int main() {
return demo_thread_safe_signal_stacks();
}

示例5:信号栈监控和调试

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

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

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

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

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

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

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

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

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

printf("\n");
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// 清理资源
free(stack_buffer);

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

int main() {
return demo_stack_monitoring();
}

sigaltstack 使用注意事项

系统要求:

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

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

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

栈大小考虑:

最小大小: 至少MINSIGSTKSZ字节

推荐大小: 使用SIGSTKSZ或更大

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

错误处理:

ENOMEM: 内存不足

EINVAL: 参数无效

EPERM: 权限不足

安全考虑:

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

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

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

最佳实践:

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

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

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

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

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

信号栈标志说明

SS_ONSTACK:

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

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

SS_DISABLE:

  • 含义: 备用栈被禁用

  • 用途: 禁用备用栈功能

SS_ENABLED:

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

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

相关常量

SIGSTKSZ:

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

  • 典型值: 8KB或更大

MINSIGSTKSZ:

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

  • 典型值: 2KB

常见使用场景

1. 栈溢出保护:

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

2. 信号处理程序:

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

3. 多线程应用:

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

总结

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

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

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

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

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

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

getcwd系统调用及示例

getcwd - 获取当前工作目录

getcwd - 获取当前工作目录

函数介绍

getcwd系统调用用于获取进程当前的工作目录路径。它返回一个以null结尾的字符串,表示当前目录的绝对路径名。

函数原型

1
2
3
4
#include <unistd.h>

char *getcwd(char *buf, size_t size);

功能

获取进程当前工作目录的绝对路径名。

参数

  • char *buf: 指向存储路径名的缓冲区

  • size_t size: 缓冲区大小(字节)

返回值

  • 成功时返回指向缓冲区的指针(buf)

失败时返回NULL,并设置errno:

  • EINVAL: size参数为0且buf非NULL

  • ERANGE: 缓冲区太小

  • ENOMEM: 内存不足

相似函数

  • chdir(): 改变当前工作目录

  • fchdir(): 通过文件描述符改变当前工作目录

示例代码

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

int main() {
char buffer&#91;PATH_MAX];
char *result;

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

// 示例1: 基本的获取当前目录操作
printf("\n示例1: 基本的获取当前目录操作\n");

// 方法1: 使用预分配的缓冲区
result = getcwd(buffer, sizeof(buffer));
if (result != NULL) {
printf("当前工作目录: %s\n", buffer);
} else {
perror("获取当前目录失败");
}

// 方法2: 让系统分配内存(buf为NULL,size为0)
char *dynamic_buffer = getcwd(NULL, 0);
if (dynamic_buffer != NULL) {
printf("动态分配的当前目录: %s\n", dynamic_buffer);
free(dynamic_buffer); // 需要手动释放内存
} else {
perror("动态获取当前目录失败");
}

// 示例2: 缓冲区大小处理
printf("\n示例2: 缓冲区大小处理\n");

// 测试不同大小的缓冲区
size_t sizes&#91;] = {10, 50, 100, PATH_MAX};
for (int i = 0; i < 4; i++) {
char small_buffer&#91;1000]; // 足够大的缓冲区
printf("尝试使用大小为 %zu 的缓冲区: ", sizes&#91;i]);

result = getcwd(small_buffer, sizes&#91;i]);
if (result != NULL) {
printf("成功,目录长度: %zu 字节\n", strlen(small_buffer));
} else {
if (errno == ERANGE) {
printf("失败 - 缓冲区太小\n");
} else {
printf("失败 - %s\n", strerror(errno));
}
}
}

// 示例3: 创建测试目录并切换
printf("\n示例3: 目录切换演示\n");

// 保存原始目录
char original_dir&#91;PATH_MAX];
if (getcwd(original_dir, sizeof(original_dir)) == NULL) {
perror("保存原始目录失败");
exit(EXIT_FAILURE);
}
printf("原始目录: %s\n", original_dir);

// 创建测试目录
const char *test_dir = "test_getcwd_dir";
if (mkdir(test_dir, 0755) == -1 && errno != EEXIST) {
perror("创建测试目录失败");
} else {
printf("创建测试目录: %s\n", test_dir);

// 切换到测试目录
if (chdir(test_dir) == -1) {
perror("切换目录失败");
} else {
printf("切换到测试目录\n");

// 获取当前目录
result = getcwd(buffer, sizeof(buffer));
if (result != NULL) {
printf("切换后当前目录: %s\n", buffer);

// 验证是否正确切换
if (strstr(buffer, test_dir) != NULL) {
printf("目录切换验证成功\n");
}
}

// 在测试目录中创建子目录
const char *sub_dir = "sub_directory";
if (mkdir(sub_dir, 0755) == -1 && errno != EEXIST) {
perror("创建子目录失败");
} else {
printf("创建子目录: %s\n", sub_dir);

// 切换到子目录
if (chdir(sub_dir) == -1) {
perror("切换到子目录失败");
} else {
printf("切换到子目录\n");

result = getcwd(buffer, sizeof(buffer));
if (result != NULL) {
printf("子目录路径: %s\n", buffer);
}
}
}
}

// 返回原始目录
if (chdir(original_dir) == -1) {
perror("返回原始目录失败");
} else {
printf("返回原始目录\n");
result = getcwd(buffer, sizeof(buffer));
if (result != NULL) {
printf("确认当前目录: %s\n", buffer);
}
}
}

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

// 测试缓冲区太小的情况
char tiny_buffer&#91;5];
result = getcwd(tiny_buffer, sizeof(tiny_buffer));
if (result == NULL) {
if (errno == ERANGE) {
printf("缓冲区太小错误处理正确: %s\n", strerror(errno));
} else {
printf("其他错误: %s\n", strerror(errno));
}
}

// 测试无效参数
result = getcwd(buffer, 0);
if (result == NULL) {
if (errno == EINVAL) {
printf("无效参数处理正确: %s\n", strerror(errno));
}
}

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

// 场景1: 程序配置文件定位
printf("场景1: 程序配置文件定位\n");
result = getcwd(buffer, sizeof(buffer));
if (result != NULL) {
char config_path&#91;PATH_MAX * 2];
snprintf(config_path, sizeof(config_path), "%s/.myapp/config", buffer);
printf("配置文件路径: %s\n", config_path);

// 检查配置文件是否存在
if (access(config_path, F_OK) == 0) {
printf("配置文件存在\n");
} else {
printf("配置文件不存在,将创建默认配置\n");
}
}

// 场景2: 相对路径转换为绝对路径
printf("场景2: 相对路径处理\n");
const char *relative_paths&#91;] = {"./file.txt", "../parent_dir", "subdir/file"};

for (int i = 0; i < 3; i++) {
result = getcwd(buffer, sizeof(buffer));
if (result != NULL) {
char absolute_path&#91;PATH_MAX * 2];
snprintf(absolute_path, sizeof(absolute_path), "%s/%s", buffer, relative_paths&#91;i]);
printf(" %s -> %s\n", relative_paths&#91;i], absolute_path);
}
}

// 场景3: 目录操作安全检查
printf("场景3: 目录操作安全检查\n");
char current_dir&#91;PATH_MAX];
char previous_dir&#91;PATH_MAX];

// 保存当前目录
if (getcwd(current_dir, sizeof(current_dir)) != NULL) {
printf("当前工作目录: %s\n", current_dir);

// 执行一些目录操作
// ... 目录操作代码 ...

// 检查目录是否发生变化
if (getcwd(previous_dir, sizeof(previous_dir)) != NULL) {
if (strcmp(current_dir, previous_dir) != 0) {
printf("警告: 工作目录已改变\n");
printf("原目录: %s\n", current_dir);
printf("现目录: %s\n", previous_dir);
} else {
printf("工作目录保持不变\n");
}
}
}

// 场景4: 日志记录中的目录信息
printf("场景4: 日志记录\n");
time_t current_time = time(NULL);
result = getcwd(buffer, sizeof(buffer));
if (result != NULL) {
printf("&#91;%s] INFO: 程序在目录 '%s' 中启动\n",
ctime(&current_time), buffer);
}

// 示例6: 跨平台兼容性考虑
printf("\n示例6: 跨平台兼容性\n");

// 获取系统路径限制
long path_max = pathconf(".", _PC_PATH_MAX);
if (path_max == -1) {
path_max = PATH_MAX;
printf("使用默认PATH_MAX: %ld\n", path_max);
} else {
printf("系统PATH_MAX: %ld\n", path_max);
}

// 使用动态分配确保足够空间
char *safe_buffer = getcwd(NULL, 0);
if (safe_buffer != NULL) {
printf("动态分配确保安全: %s\n", safe_buffer);
printf("路径长度: %zu 字节\n", strlen(safe_buffer));
free(safe_buffer);
}

// 示例7: 性能对比测试
printf("\n示例7: 性能对比测试\n");

// 测试预分配缓冲区的性能
clock_t start = clock();
for (int i = 0; i < 10000; i++) {
getcwd(buffer, sizeof(buffer));
}
clock_t preallocated_time = clock() - start;

// 测试动态分配的性能
start = clock();
for (int i = 0; i < 10000; i++) {
char *temp = getcwd(NULL, 0);
if (temp != NULL) {
free(temp);
}
}
clock_t dynamic_time = clock() - start;

printf("预分配缓冲区 10000 次调用耗时: %f 秒\n",
((double)preallocated_time) / CLOCKS_PER_SEC);
printf("动态分配 10000 次调用耗时: %f 秒\n",
((double)dynamic_time) / CLOCKS_PER_SEC);
printf("预分配方式通常更快\n");

// 清理测试目录
printf("\n清理测试资源...\n");
if (access(test_dir, F_OK) == 0) {
if (rmdir(test_dir) == -1) {
printf("删除测试目录失败: %s\n", strerror(errno));
} else {
printf("删除测试目录完成\n");
}
}

return 0;
}

getgroups系统调用及示例

getgroups - 获取进程的补充组列表

1. 函数介绍

getgroups - 获取进程的补充组列表

getgroups 是一个 Linux 系统调用,用于获取当前进程所属的补充组(supplementary groups)列表。除了进程的主要组 ID(由 getgid() 返回)之外,进程还可以属于多个补充组,这些组决定了进程对文件和资源的额外访问权限。

补充组机制是 Unix/Linux 系统中实现灵活访问控制的重要组成部分,允许用户同时属于多个组以获得相应的权限。

2. 函数原型

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

int getgroups(int size, gid_t list&#91;]);

3. 功能

获取当前进程所属的补充组 ID 列表。补充组是除了主要组之外,进程还属于的其他组。

4. 参数

int size: 指定 list 数组的大小(元素个数)

  • 如果为 0:函数返回补充组的数量,不填充 list 数组

  • 如果大于 0:将补充组 ID 填充到 list 数组中

gid_t list[]: 指向存储组 ID 的数组

  • 如果 size 为 0:可以为 NULL

  • 如果 size 大于 0:必须指向有效的数组

5. 返回值

成功时:

  • 如果 size 为 0:返回补充组的数量

  • 如果 size 大于 0:返回实际填充到数组中的组 ID 数量

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

6. 常见 errno 错误码

  • EINVAL: size 参数小于补充组的实际数量(缓冲区不足)

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

  • EPERM: 在某些安全限制下可能返回(较少见)

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

  • setgroups(): 设置进程的补充组列表

  • initgroups(): 根据用户信息初始化补充组列表

  • getgid(): 获取进程的真实组 ID

  • getegid(): 获取进程的有效组 ID

  • getuid(), geteuid(): 获取用户 ID 相关函数

  • setgid(), setegid(): 设置组 ID

  • getgrouplist(): 获取用户的所有组(包括主要组)

  • /etc/group: 系统组信息文件

  • /etc/passwd: 用户主要组信息文件

8. 示例代码

示例1:基本使用 - 获取补充组列表

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

int main() {
int group_count;
gid_t *group_list;
long max_groups;

printf("=== 获取进程补充组列表 ===\n");

// 首先获取补充组数量
group_count = getgroups(0, NULL);
if (group_count == -1) {
perror("获取补充组数量失败");
exit(EXIT_FAILURE);
}

printf("补充组数量: %d\n", group_count);

if (group_count == 0) {
printf("当前进程没有补充组\n");
return 0;
}

// 获取系统支持的最大组数
max_groups = sysconf(_SC_NGROUPS_MAX);
if (max_groups == -1) {
max_groups = 65536; // 设置一个较大的默认值
}

printf("系统最大支持组数: %ld\n", max_groups);

// 确保不会超出系统限制
if (group_count > max_groups) {
printf("警告: 补充组数量超出系统限制\n");
group_count = max_groups;
}

// 分配内存存储组列表
group_list = malloc(group_count * sizeof(gid_t));
if (group_list == NULL) {
perror("内存分配失败");
exit(EXIT_FAILURE);
}

// 获取补充组列表
int result = getgroups(group_count, group_list);
if (result == -1) {
perror("获取补充组列表失败");
free(group_list);
exit(EXIT_FAILURE);
}

printf("成功获取 %d 个补充组:\n", result);
printf("%-8s %-10s %s\n", "序号", "组ID", "组名");
printf("%-8s %-10s %s\n", "----", "----", "----");

// 显示每个组的信息
for (int i = 0; i < result; i++) {
printf("%-8d %-10d ", i + 1, group_list&#91;i]);

// 尝试获取组名
struct group *grp = getgrgid(group_list&#91;i]);
if (grp != NULL) {
printf("%s", grp->gr_name);
} else {
printf("(未知)");
}
printf("\n");
}

// 显示主要组信息进行对比
gid_t primary_gid = getgid();
printf("\n主要组信息:\n");
printf(" 组ID: %d\n", primary_gid);

struct group *primary_grp = getgrgid(primary_gid);
if (primary_grp != NULL) {
printf(" 组名: %s\n", primary_grp->gr_name);
}

// 检查主要组是否在补充组列表中
int found_in_supplementary = 0;
for (int i = 0; i < result; i++) {
if (group_list&#91;i] == primary_gid) {
found_in_supplementary = 1;
break;
}
}

printf("主要组是否在补充组中: %s\n",
found_in_supplementary ? "是" : "否");

free(group_list);
return 0;
}

示例2:错误处理和边界情况

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

void demonstrate_error_handling() {
int result;
gid_t small_buffer&#91;2]; // 故意使用小缓冲区

printf("=== 错误处理演示 ===\n");

// 获取实际补充组数量
int actual_count = getgroups(0, NULL);
if (actual_count == -1) {
perror("获取补充组数量失败");
return;
}

printf("实际补充组数量: %d\n", actual_count);

if (actual_count > 0) {
// 使用过小的缓冲区,应该返回错误
printf("使用过小缓冲区测试 (大小: 2):\n");
result = getgroups(2, small_buffer);

if (result == -1) {
if (errno == EINVAL) {
printf(" 错误处理正确: 缓冲区不足 (EINVAL)\n");
} else {
printf(" 其他错误: %s\n", strerror(errno));
}
} else {
printf(" 意外成功,返回数量: %d\n", result);
}

// 使用 NULL 指针但 size > 0
printf("使用 NULL 指针测试:\n");
result = getgroups(10, NULL);
if (result == -1) {
if (errno == EFAULT) {
printf(" 错误处理正确: 无效指针 (EFAULT)\n");
} else {
printf(" 其他错误: %s\n", strerror(errno));
}
}
}
}

void demonstrate_empty_groups() {
printf("\n=== 空组列表演示 ===\n");

// 获取补充组数量
int count = getgroups(0, NULL);
if (count == -1) {
perror("获取组数量失败");
return;
}

printf("当前进程补充组数量: %d\n", count);

if (count == 0) {
printf("进程没有补充组权限\n");
printf("这可能表示:\n");
printf(" 1. 进程以最小权限运行\n");
printf(" 2. 用户不属于任何补充组\n");
printf(" 3. 在容器或受限环境中运行\n");
}
}

int main() {
demonstrate_error_handling();
demonstrate_empty_groups();
return 0;
}

示例3:完整的组权限分析工具

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

typedef struct {
gid_t gid;
char group_name&#91;256];
int is_primary;
int is_effective;
int is_supplementary;
} group_info_t;

int compare_gids(const void *a, const void *b) {
gid_t gid_a = ((group_info_t*)a)->gid;
gid_t gid_b = ((group_info_t*)b)->gid;
return (gid_a > gid_b) - (gid_a < gid_b);
}

void analyze_process_groups() {
gid_t primary_gid, effective_gid;
int sup_count;
gid_t *sup_groups = NULL;
group_info_t *all_groups = NULL;
int total_groups = 0;

printf("=== 进程组权限完整分析 ===\n");

// 获取基本信息
primary_gid = getgid();
effective_gid = getegid();

printf("进程基本信息:\n");
printf(" 真实用户 ID: %d\n", getuid());
printf(" 有效用户 ID: %d\n", geteuid());
printf(" 真实组 ID: %d\n", primary_gid);
printf(" 有效组 ID: %d\n", effective_gid);

// 获取用户信息
struct passwd *pwd = getpwuid(getuid());
if (pwd != NULL) {
printf(" 用户名: %s\n", pwd->pw_name);
}

// 获取补充组
sup_count = getgroups(0, NULL);
if (sup_count > 0) {
sup_groups = malloc(sup_count * sizeof(gid_t));
if (sup_groups == NULL) {
perror("内存分配失败");
return;
}

if (getgroups(sup_count, sup_groups) == -1) {
perror("获取补充组失败");
free(sup_groups);
return;
}
}

// 创建完整的组信息列表
total_groups = 2 + sup_count; // primary + effective + supplementary
all_groups = malloc(total_groups * sizeof(group_info_t));
if (all_groups == NULL) {
perror("内存分配失败");
if (sup_groups) free(sup_groups);
return;
}

int index = 0;

// 添加主要组
all_groups&#91;index].gid = primary_gid;
all_groups&#91;index].is_primary = 1;
all_groups&#91;index].is_effective = (primary_gid == effective_gid);
all_groups&#91;index].is_supplementary = 0;
struct group *grp = getgrgid(primary_gid);
if (grp != NULL) {
strncpy(all_groups&#91;index].group_name, grp->gr_name, sizeof(all_groups&#91;index].group_name) - 1);
} else {
snprintf(all_groups&#91;index].group_name, sizeof(all_groups&#91;index].group_name), "group_%d", primary_gid);
}
index++;

// 添加有效组(如果不同于主要组)
if (effective_gid != primary_gid) {
all_groups&#91;index].gid = effective_gid;
all_groups&#91;index].is_primary = 0;
all_groups&#91;index].is_effective = 1;
all_groups&#91;index].is_supplementary = 0;
struct group *grp = getgrgid(effective_gid);
if (grp != NULL) {
strncpy(all_groups&#91;index].group_name, grp->gr_name, sizeof(all_groups&#91;index].group_name) - 1);
} else {
snprintf(all_groups&#91;index].group_name, sizeof(all_groups&#91;index].group_name), "group_%d", effective_gid);
}
index++;
}

// 添加补充组
for (int i = 0; i < sup_count; i++) {
// 检查是否已存在
int exists = 0;
for (int j = 0; j < index; j++) {
if (all_groups&#91;j].gid == sup_groups&#91;i]) {
all_groups&#91;j].is_supplementary = 1;
exists = 1;
break;
}
}

if (!exists) {
all_groups&#91;index].gid = sup_groups&#91;i];
all_groups&#91;index].is_primary = 0;
all_groups&#91;index].is_effective = (sup_groups&#91;i] == effective_gid);
all_groups&#91;index].is_supplementary = 1;
struct group *grp = getgrgid(sup_groups&#91;i]);
if (grp != NULL) {
strncpy(all_groups&#91;index].group_name, grp->gr_name, sizeof(all_groups&#91;index].group_name) - 1);
} else {
snprintf(all_groups&#91;index].group_name, sizeof(all_groups&#91;index].group_name), "group_%d", sup_groups&#91;i]);
}
index++;
}
}

total_groups = index;

// 按组 ID 排序
qsort(all_groups, total_groups, sizeof(group_info_t), compare_gids);

// 显示结果
printf("\n完整的组权限信息:\n");
printf("%-8s %-10s %-12s %-12s %-15s %s\n",
"序号", "组ID", "主要组", "有效组", "补充组", "组名");
printf("%-8s %-10s %-12s %-12s %-15s %s\n",
"----", "----", "----", "----", "----", "----");

for (int i = 0; i < total_groups; i++) {
printf("%-8d %-10d %-12s %-12s %-15s %s\n",
i + 1,
all_groups&#91;i].gid,
all_groups&#91;i].is_primary ? "是" : "否",
all_groups&#91;i].is_effective ? "是" : "否",
all_groups&#91;i].is_supplementary ? "是" : "否",
all_groups&#91;i].group_name);
}

// 统计信息
printf("\n统计信息:\n");
printf(" 总组数: %d\n", total_groups);

int primary_count = 0, effective_count = 0, supplementary_count = 0;
for (int i = 0; i < total_groups; i++) {
if (all_groups&#91;i].is_primary) primary_count++;
if (all_groups&#91;i].is_effective) effective_count++;
if (all_groups&#91;i].is_supplementary) supplementary_count++;
}

printf(" 主要组: %d\n", primary_count);
printf(" 有效组: %d\n", effective_count);
printf(" 补充组: %d\n", supplementary_count);

// 特殊权限检查
printf("\n特殊权限检查:\n");
int has_root_group = 0;
int has_admin_group = 0;

for (int i = 0; i < total_groups; i++) {
if (all_groups&#91;i].gid == 0) {
has_root_group = 1;
}
// 检查常见的管理员组
if (strcmp(all_groups&#91;i].group_name, "wheel") == 0 ||
strcmp(all_groups&#91;i].group_name, "sudo") == 0 ||
strcmp(all_groups&#91;i].group_name, "adm") == 0) {
has_admin_group = 1;
}
}

printf(" Root 组权限: %s\n", has_root_group ? "是" : "否");
printf(" 管理员组权限: %s\n", has_admin_group ? "是" : "否");

// 清理内存
if (sup_groups) free(sup_groups);
if (all_groups) free(all_groups);
}

int main() {
analyze_process_groups();
return 0;
}

示例4:组权限验证和安全检查

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

// 检查进程是否属于指定组
int is_process_member_of_group(gid_t target_gid) {
// 首先检查主要组和有效组
if (getgid() == target_gid || getegid() == target_gid) {
return 1;
}

// 检查补充组
int sup_count = getgroups(0, NULL);
if (sup_count <= 0) {
return 0;
}

gid_t *sup_groups = malloc(sup_count * sizeof(gid_t));
if (sup_groups == NULL) {
return 0;
}

if (getgroups(sup_count, sup_groups) == -1) {
free(sup_groups);
return 0;
}

for (int i = 0; i < sup_count; i++) {
if (sup_groups&#91;i] == target_gid) {
free(sup_groups);
return 1;
}
}

free(sup_groups);
return 0;
}

// 获取特定组的权限级别
typedef enum {
NO_ACCESS = 0,
SUPPLEMENTARY_ACCESS = 1,
PRIMARY_ACCESS = 2,
EFFECTIVE_ACCESS = 3,
ROOT_ACCESS = 4
} access_level_t;

access_level_t get_group_access_level(gid_t target_gid) {
if (target_gid == 0 && (getgid() == 0 || getegid() == 0)) {
return ROOT_ACCESS;
}

if (getegid() == target_gid) {
return EFFECTIVE_ACCESS;
}

if (getgid() == target_gid) {
return PRIMARY_ACCESS;
}

// 检查补充组
int sup_count = getgroups(0, NULL);
if (sup_count > 0) {
gid_t *sup_groups = malloc(sup_count * sizeof(gid_t));
if (sup_groups != NULL) {
if (getgroups(sup_count, sup_groups) != -1) {
for (int i = 0; i < sup_count; i++) {
if (sup_groups&#91;i] == target_gid) {
free(sup_groups);
return SUPPLEMENTARY_ACCESS;
}
}
}
free(sup_groups);
}
}

return NO_ACCESS;
}

void check_common_groups() {
printf("=== 常见组权限检查 ===\n");

// 检查一些常见的系统组
struct {
const char *name;
gid_t gid;
} common_groups&#91;] = {
{"root", 0},
{"wheel", 0}, // 需要查找实际 GID
{"sudo", 0},
{"adm", 0},
{"disk", 0}
};

// 获取这些组的实际 GID
for (int i = 0; i < sizeof(common_groups)/sizeof(common_groups&#91;0]); i++) {
struct group *grp = getgrnam(common_groups&#91;i].name);
if (grp != NULL) {
common_groups&#91;i].gid = grp->gr_gid;
}
}

printf("%-12s %-8s %-15s %s\n", "组名", "组ID", "访问级别", "成员状态");
printf("%-12s %-8s %-15s %s\n", "----", "----", "----", "----");

for (int i = 0; i < sizeof(common_groups)/sizeof(common_groups&#91;0]); i++) {
if (common_groups&#91;i].gid != 0) {
access_level_t level = get_group_access_level(common_groups&#91;i].gid);
int is_member = is_process_member_of_group(common_groups&#91;i].gid);

const char *level_str;
switch (level) {
case ROOT_ACCESS: level_str = "Root"; break;
case EFFECTIVE_ACCESS: level_str = "Effective"; break;
case PRIMARY_ACCESS: level_str = "Primary"; break;
case SUPPLEMENTARY_ACCESS: level_str = "Supplementary"; break;
default: level_str = "None"; break;
}

printf("%-12s %-8d %-15s %s\n",
common_groups&#91;i].name,
common_groups&#91;i].gid,
level_str,
is_member ? "是" : "否");
}
}
}

int main() {
check_common_groups();
return 0;
}

9. 实际应用场景

getgroups 在以下场景中非常有用:

场景1:权限验证

1
2
3
4
int can_access_resource(gid_t required_group) {
return is_process_member_of_group(required_group);
}

场景2:安全审计

1
2
3
4
5
void audit_process_groups() {
int count = getgroups(0, NULL);
syslog(LOG_INFO, "进程拥有 %d 个补充组", count);
}

场景3:访问控制

1
2
3
4
5
6
7
8
int check_file_group_permission(const char *filename) {
struct stat file_stat;
if (stat(filename, &file_stat) == 0) {
return is_process_member_of_group(file_stat.st_gid);
}
return 0;
}

10. 注意事项

使用 getgroups 时需要注意:

缓冲区大小: 确保缓冲区足够大,避免 EINVAL 错误

内存管理: 正确分配和释放内存

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

系统限制: 了解 NGROUPS_MAX 限制

并发安全: 在多线程环境中注意数据一致性

总结

getgroups 是管理进程组权限的重要函数,关键要点:

补充组获取: 获取进程除主要组外的所有组

双重调用: 通常需要先获取数量,再获取实际数据

权限检查: 是权限验证和访问控制的基础

安全相关: 在安全审计和权限管理中广泛使用

系统集成: 与 /etc/group 等系统文件紧密相关

正确使用 getgroups 可以帮助程序准确了解当前的组权限状态,实现更精细的访问控制和安全检查

semctl系统调用及示例

semctl - 信号量控制

函数介绍

semctl系统调用用于控制System V信号量集,可以获取和设置信号量的各种属性和状态。它是信号量管理的重要工具,用于初始化、查询、修改和删除信号量。

semctl - 信号量控制(https://www.calcguide.tech/2025/08/18/semctl系统调用及示例/)

函数介绍semctl系统调用用于控制System V信号量集,可以获取和设置信号量的各种属性和状态。它是信号量管理的重要工具,用于初始化、查询、修改和删除信号量。

函数原型

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);

功能

对信号量集执行各种控制操作,包括设置值、获取状态、删除信号量集等。

参数

  • int semid: 信号量集的标识符

  • int semnum: 信号量在集合中的索引号(某些命令需要)

int cmd: 要执行的控制命令

  • SETVAL: 设置单个信号量的值

  • GETVAL: 获取单个信号量的值

  • SETALL: 设置所有信号量的值

  • GETALL: 获取所有信号量的值

  • IPC_RMID: 删除信号量集

  • IPC_STAT: 获取信号量集状态

  • IPC_SET: 设置信号量集状态

…: 可选参数,根据命令不同而不同

返回值

根据命令不同返回不同值:

  • SETVAL, SETALL, IPC_RMID: 成功返回0,失败返回-1

  • GETVAL: 成功返回信号量值,失败返回-1

  • GETALL: 成功返回0,失败返回-1

  • IPC_STAT: 成功返回0,失败返回-1

失败时设置errno

相似函数

  • semget(): 获取信号量集标识符

  • semop(): 操作信号量集

  • ftok(): 生成System V IPC键值

示例代码

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

// 信号量操作联合体
union semun {
int val; // 用于SETVAL
struct semid_ds *buf; // 用于IPC_STAT和IPC_SET
unsigned short *array; // 用于GETALL和SETALL
};

int main() {
key_t key;
int semid;
union semun arg;
struct semid_ds sem_info;

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

// 创建信号量集
key = ftok(".", 'c');
if (key == -1) {
perror("ftok失败");
exit(EXIT_FAILURE);
}

// 先尝试删除可能存在的同名信号量集
semid = semget(key, 1, 0666);
if (semid != -1) {
semctl(semid, 0, IPC_RMID);
}

// 创建包含3个信号量的信号量集
semid = semget(key, 3, 0666 | IPC_CREAT | IPC_EXCL);
if (semid == -1) {
perror("创建信号量集失败");
exit(EXIT_FAILURE);
}
printf("成功创建信号量集,标识符: %d\n", semid);

// 示例1: 设置单个信号量的值
printf("\n示例1: 设置单个信号量的值\n");

// 设置信号量0的值为5
arg.val = 5;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("设置信号量0失败");
} else {
printf(" 成功设置信号量0的值为: %d\n", semctl(semid, 0, GETVAL));
}

// 设置信号量1的值为10
arg.val = 10;
if (semctl(semid, 1, SETVAL, arg) == -1) {
perror("设置信号量1失败");
} else {
printf(" 成功设置信号量1的值为: %d\n", semctl(semid, 1, GETVAL));
}

// 设置信号量2的值为0
arg.val = 0;
if (semctl(semid, 2, SETVAL, arg) == -1) {
perror("设置信号量2失败");
} else {
printf(" 成功设置信号量2的值为: %d\n", semctl(semid, 2, GETVAL));
}

// 示例2: 获取单个信号量的值
printf("\n示例2: 获取单个信号量的值\n");
int val0 = semctl(semid, 0, GETVAL);
int val1 = semctl(semid, 1, GETVAL);
int val2 = semctl(semid, 2, GETVAL);

if (val0 != -1 && val1 != -1 && val2 != -1) {
printf(" 信号量0的值: %d\n", val0);
printf(" 信号量1的值: %d\n", val1);
printf(" 信号量2的值: %d\n", val2);
} else {
perror("获取信号量值失败");
}

// 示例3: 批量设置所有信号量的值
printf("\n示例3: 批量设置所有信号量的值\n");
unsigned short values&#91;3] = {3, 7, 2};
arg.array = values;

if (semctl(semid, 0, SETALL, arg) == -1) {
perror("批量设置信号量值失败");
} else {
printf(" 成功批量设置信号量值\n");

// 验证设置结果
unsigned short get_values&#91;3];
arg.array = get_values;
if (semctl(semid, 0, GETALL, arg) == -1) {
perror("批量获取信号量值失败");
} else {
printf(" 当前信号量值: &#91;%d, %d, %d]\n",
get_values&#91;0], get_values&#91;1], get_values&#91;2]);
}
}

// 示例4: 获取信号量集状态信息
printf("\n示例4: 获取信号量集状态信息\n");
arg.buf = &sem_info;
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("获取信号量集状态失败");
} else {
printf(" 信号量集状态信息:\n");
printf(" 键值: %d\n", sem_info.sem_perm.__key);
printf(" 信号量数量: %ld\n", sem_info.sem_nsems);
printf(" 最后操作时间: %ld\n", sem_info.sem_otime);
printf(" 最后修改时间: %ld\n", sem_info.sem_ctime);
printf(" 创建者UID: %d\n", sem_info.sem_perm.cuid);
printf(" 创建者GID: %d\n", sem_info.sem_perm.cgid);
printf(" 所有者UID: %d\n", sem_info.sem_perm.uid);
printf(" 所有者GID: %d\n", sem_info.sem_perm.gid);
printf(" 权限: %o\n", sem_info.sem_perm.mode);
}

// 示例5: 修改信号量集权限
printf("\n示例5: 修改信号量集权限\n");
struct semid_ds new_info;
memcpy(&new_info, &sem_info, sizeof(struct semid_ds));
new_info.sem_perm.mode = 0644; // 修改为读写权限

arg.buf = &new_info;
if (semctl(semid, 0, IPC_SET, arg) == -1) {
perror("修改信号量集权限失败");
} else {
printf(" 成功修改信号量集权限为: %o\n", new_info.sem_perm.mode);
}

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

// 尝试操作不存在的信号量集
if (semctl(999999, 0, GETVAL) == -1) {
printf(" 操作不存在的信号量集: %s\n", strerror(errno));
}

// 尝试操作不存在的信号量索引
if (semctl(semid, 999, GETVAL) == -1) {
printf(" 操作不存在的信号量索引: %s\n", strerror(errno));
}

// 示例7: 删除信号量集
printf("\n示例7: 删除信号量集\n");
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("删除信号量集失败");
} else {
printf(" 成功删除信号量集 %d\n", semid);

// 验证删除结果
if (semctl(semid, 0, GETVAL) == -1) {
printf(" 验证: 信号量集已不存在\n");
}
}

return 0;
}

semget系统调用及示例

semget - 获取信号量集标识符

函数介绍

semget系统调用用于创建或访问一个System V信号量集。信号量是一种用于进程间同步的机制,主要用于控制对共享资源的访问,防止多个进程同时访问同一资源造成冲突。

函数原型

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

功能

创建新的信号量集或获取现有信号量集的标识符。

参数

key_t key: 信号量集的键值

  • IPC_PRIVATE: 创建私有信号量集

  • 正整数: 通过ftok()函数生成的键值

int nsems: 信号量集中信号量的个数(创建时使用)

int semflg: 标志位组合

  • IPC_CREAT: 如果信号量集不存在则创建

  • IPC_EXCL: 与IPC_CREAT配合使用,如果已存在则失败

  • 权限位: 如0666(读写权限)

返回值

  • 成功时返回信号量集的标识符(非负整数)

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

  • EACCES: 权限不足

  • EEXIST: 信号量集已存在(使用IPC_EXCL时)

  • EINVAL: 参数无效

  • ENOENT: 信号量集不存在且未指定IPC_CREAT

相似函数

  • semctl(): 控制信号量集

  • semop(): 操作信号量集

  • ftok(): 生成System V IPC键值

示例代码

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

// 信号量操作联合体(某些系统需要)
union semun {
int val; // 用于SETVAL
struct semid_ds *buf; // 用于IPC_STAT和IPC_SET
unsigned short *array; // 用于GETALL和SETALL
};

int main() {
key_t key;
int semid;

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

// 示例1: 使用IPC_PRIVATE创建私有信号量集
printf("\n示例1: 创建私有信号量集\n");
semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
if (semid == -1) {
perror("semget IPC_PRIVATE失败");
exit(EXIT_FAILURE);
}
printf(" 成功创建私有信号量集,标识符: %d\n", semid);

// 删除信号量集
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("删除信号量集失败");
} else {
printf(" 成功删除信号量集\n");
}

// 示例2: 使用ftok生成键值创建信号量集
printf("\n示例2: 使用ftok创建信号量集\n");

// 生成键值(基于当前文件和项目ID)
key = ftok(".", 'a');
if (key == -1) {
perror("ftok失败");
exit(EXIT_FAILURE);
}
printf(" 生成的键值: %d\n", key);

// 创建信号量集(包含3个信号量)
semid = semget(key, 3, 0666 | IPC_CREAT);
if (semid == -1) {
perror("semget创建失败");
exit(EXIT_FAILURE);
}
printf(" 成功创建信号量集,标识符: %d\n", semid);

// 示例3: 尝试创建已存在的信号量集
printf("\n示例3: 尝试重复创建信号量集\n");
int semid2 = semget(key, 3, 0666 | IPC_CREAT | IPC_EXCL);
if (semid2 == -1) {
if (errno == EEXIST) {
printf(" 信号量集已存在\n");
} else {
perror(" semget失败");
}
} else {
printf(" 意外创建了新的信号量集: %d\n", semid2);
}

// 示例4: 获取已存在的信号量集
printf("\n示例4: 获取已存在的信号量集\n");
int semid3 = semget(key, 0, 0666);
if (semid3 == -1) {
perror("获取信号量集失败");
} else {
printf(" 成功获取信号量集,标识符: %d\n", semid3);
// 验证是否为同一个信号量集
if (semid3 == semid) {
printf(" 确认为同一个信号量集\n");
}
}

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

// 尝试访问不存在的信号量集(不创建)
int semid4 = semget(12345, 1, 0666);
if (semid4 == -1) {
if (errno == ENOENT) {
printf(" 信号量集不存在: %s\n", strerror(errno));
} else {
perror(" 其他错误");
}
}

// 清理:删除创建的信号量集
printf("\n清理资源...\n");
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("清理信号量集失败");
} else {
printf(" 成功清理信号量集\n");
}

return 0;
}

semop系统调用及示例

semop - 信号量操作

函数介绍

semop系统调用用于对System V信号量集执行操作。它是信号量机制的核心操作函数,通过原子性地执行一系列信号量操作来实现进程同步。](https://www.calcguide.tech/2025/08/18/semop系统调用及示例/)

函数原型

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);

功能

对信号量集执行原子操作,用于进程间同步。

参数

  • int semid: 信号量集的标识符(由semget返回)

struct sembuf *sops: 指向sembuf结构体数组的指针struct sembuf { unsigned short sem_num; // 信号量在集合中的索引 short sem_op; // 操作类型 short sem_flg; // 操作标志 };

  • sem_op > 0: 释放资源(V操作),将sem_op加到信号量值上

  • sem_op < 0: 请求资源(P操作),从信号量值中减去sem_op的绝对值

  • sem_op = 0: 等待信号量值变为0

unsigned nsops: sops数组中操作的数量

返回值

  • 成功时返回0

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

  • EAGAIN: 操作会阻塞但设置了IPC_NOWAIT

  • EIDRM: 信号量集已被删除

  • EINTR: 系统调用被信号中断

  • EINVAL: 参数无效

  • EFBIG: sem_op绝对值过大

相似函数

  • semtimedop(): 带超时的信号量操作

  • semget(): 获取信号量集

  • semctl(): 控制信号量集

示例代码

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

// 信号量操作联合体
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};

// P操作(等待/减1)
int P(int semid, int sem_num) {
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = -1; // P操作
sb.sem_flg = 0;
return semop(semid, &sb, 1);
}

// V操作(发送/加1)
int V(int semid, int sem_num) {
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = 1; // V操作
sb.sem_flg = 0;
return semop(semid, &sb, 1);
}

// 等待信号量为0
int wait_zero(int semid, int sem_num) {
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = 0;
sb.sem_flg = 0;
return semop(semid, &sb, 1);
}

int main() {
key_t key;
int semid;
union semun arg;

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

// 创建信号量集
key = ftok(".", 'b');
if (key == -1) {
perror("ftok失败");
exit(EXIT_FAILURE);
}

semid = semget(key, 2, 0666 | IPC_CREAT | IPC_EXCL);
if (semid == -1) {
if (errno == EEXIST) {
// 如果已存在,先删除再创建
semid = semget(key, 2, 0666);
semctl(semid, 0, IPC_RMID);
semid = semget(key, 2, 0666 | IPC_CREAT | IPC_EXCL);
} else {
perror("semget失败");
exit(EXIT_FAILURE);
}
}

if (semid == -1) {
perror("创建信号量集失败");
exit(EXIT_FAILURE);
}
printf("成功创建信号量集,标识符: %d\n", semid);

// 初始化信号量
// 信号量0:用于互斥,初始值为1(二进制信号量)
// 信号量1:用于资源计数,初始值为3(表示有3个资源)
unsigned short init_values&#91;2] = {1, 3};
arg.array = init_values;
if (semctl(semid, 0, SETALL, arg) == -1) {
perror("初始化信号量失败");
semctl(semid, 0, IPC_RMID);
exit(EXIT_FAILURE);
}
printf("信号量初始化完成\n");
printf(" 信号量0(互斥): %d\n", semctl(semid, 0, GETVAL));
printf(" 信号量1(资源): %d\n", semctl(semid, 1, GETVAL));

// 示例1: 基本的P/V操作
printf("\n示例1: 基本的P/V操作\n");
printf("操作前 - 信号量0: %d, 信号量1: %d\n",
semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));

// P操作获取互斥锁
printf("执行P操作获取互斥锁...\n");
if (P(semid, 0) == -1) {
perror("P操作失败");
} else {
printf("成功获取互斥锁\n");
printf("操作后 - 信号量0: %d, 信号量1: %d\n",
semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));

// V操作释放互斥锁
printf("执行V操作释放互斥锁...\n");
if (V(semid, 0) == -1) {
perror("V操作失败");
} else {
printf("成功释放互斥锁\n");
printf("操作后 - 信号量0: %d, 信号量1: %d\n",
semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
}
}

// 示例2: 多进程同步演示
printf("\n示例2: 多进程同步演示\n");
pid_t pid = fork();

if (pid == -1) {
perror("fork失败");
semctl(semid, 0, IPC_RMID);
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程
printf("子进程 %d 开始执行\n", getpid());

// 模拟需要互斥访问的临界区
printf("子进程尝试获取互斥锁...\n");
if (P(semid, 0) == 0) {
printf("子进程 %d 进入临界区\n", getpid());
printf("子进程在临界区工作2秒...\n");
sleep(2);
printf("子进程 %d 离开临界区\n", getpid());
V(semid, 0); // 释放锁
}

exit(EXIT_SUCCESS);
} else {
// 父进程
sleep(1); // 让子进程先运行

printf("父进程 %d 开始执行\n", getpid());
printf("父进程尝试获取互斥锁...\n");

if (P(semid, 0) == 0) {
printf("父进程 %d 进入临界区\n", getpid());
printf("父进程在临界区工作2秒...\n");
sleep(2);
printf("父进程 %d 离开临界区\n", getpid());
V(semid, 0); // 释放锁
}

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

// 示例3: 资源计数信号量演示
printf("\n示例3: 资源计数信号量演示\n");
printf("当前可用资源数: %d\n", semctl(semid, 1, GETVAL));

// 模拟多个进程竞争资源
for (int i = 0; i < 4; i++) {
pid = fork();
if (pid == 0) {
// 子进程
printf("进程 %d 尝试获取资源...\n", getpid());

// P操作获取资源
if (P(semid, 1) == 0) {
printf("进程 %d 成功获取资源,剩余资源: %d\n",
getpid(), semctl(semid, 1, GETVAL));

// 使用资源
printf("进程 %d 使用资源2秒...\n", getpid());
sleep(2);

// V操作释放资源
V(semid, 1);
printf("进程 %d 释放资源,剩余资源: %d\n",
getpid(), semctl(semid, 1, GETVAL));
} else {
printf("进程 %d 获取资源失败\n", getpid());
}

exit(EXIT_SUCCESS);
}
}

// 等待所有子进程结束
for (int i = 0; i < 4; i++) {
wait(NULL);
}

// 清理资源
printf("\n清理资源...\n");
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("删除信号量集失败");
} else {
printf("成功删除信号量集\n");
}

return 0;
}

semtimedop系统调用及示例

semtimedop 函数详解

  1. 函数介绍

semtimedop 是Linux系统调用,用于对System V信号量集执行原子操作,并支持超时控制。它是 semop 函数的增强版本,允许指定操作的超时时间,避免无限期阻塞。信号量是进程间同步的重要机制,常用于控制对共享资源的访问。

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

int semtimedop(int semid, struct sembuf *sops, size_t nsops,
const struct timespec *timeout);

  1. 功能

semtimedop 对指定的信号量集执行一系列原子操作,如果操作不能立即完成且设置了阻塞标志,则在指定的超时时间内等待。该函数提供了精确的时间控制,使得程序可以在等待信号量时避免永久阻塞。

  1. 参数
  • int semid: 信号量集标识符(由semget返回)

  • *struct sembuf sops: 信号量操作数组

  • size_t nsops: 操作数组中元素的个数

  • *const struct timespec timeout: 超时时间(NULL表示无限等待)

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

  • 超时: 返回-1,并设置errno为EAGAIN

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

  1. 相似函数,或关联函数
  • semop: 不带超时的信号量操作函数

  • semget: 获取信号量集

  • semctl: 信号量控制操作

  • semctl: 信号量控制函数

  1. 示例代码

示例1:基础semtimedop使用

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

/**
* 信号量操作结构
*/
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};

/**
* 初始化信号量
*/
int init_semaphore(key_t key, int nsems, int init_val) {
int semid;
union semun arg;

// 创建或获取信号量集
semid = semget(key, nsems, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget 失败");
return -1;
}

// 初始化信号量值
arg.val = init_val;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("semctl 初始化失败");
return -1;
}

return semid;
}

/**
* 演示基础semtimedop使用方法
*/
int demo_semtimedop_basic() {
int semid;
struct sembuf sop;
struct timespec timeout;
key_t key = ftok(".", 's');

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

// 初始化信号量(初始值为1,表示资源可用)
semid = init_semaphore(key, 1, 1);
if (semid == -1) {
return -1;
}

printf("创建信号量集,ID: %d\n", semid);

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

// 定义P操作(获取信号量)
sop.sem_num = 0; // 操作第0个信号量
sop.sem_op = -1; // P操作:减1
sop.sem_flg = 0; // 阻塞操作

printf("执行P操作(获取信号量)...\n");
if (semtimedop(semid, &sop, 1, &timeout) == -1) {
if (errno == EAGAIN) {
printf("操作超时\n");
} else {
perror("semtimedop P操作失败");
}
goto cleanup;
}

printf("成功获取信号量\n");

// 模拟使用资源
printf("使用资源中...(3秒)\n");
sleep(3);

// 定义V操作(释放信号量)
sop.sem_op = 1; // V操作:加1

printf("执行V操作(释放信号量)...\n");
if (semtimedop(semid, &sop, 1, &timeout) == -1) {
perror("semtimedop V操作失败");
goto cleanup;
}

printf("成功释放信号量\n");

cleanup:
// 清理信号量集
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("清理信号量集失败");
}

return 0;
}

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

union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};

/**
* 创建二进制信号量(互斥锁)
*/
int create_binary_semaphore(key_t key) {
int semid;
union semun arg;

semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("创建信号量失败");
return -1;
}

// 初始化为1(可用状态)
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("初始化信号量失败");
semctl(semid, 0, IPC_RMID);
return -1;
}

return semid;
}

/**
* 获取互斥锁
*/
int acquire_mutex(int semid, int timeout_sec) {
struct sembuf sop;
struct timespec timeout;

sop.sem_num = 0;
sop.sem_op = -1;
sop.sem_flg = 0;

timeout.tv_sec = timeout_sec;
timeout.tv_nsec = 0;

return semtimedop(semid, &sop, 1, &timeout);
}

/**
* 释放互斥锁
*/
int release_mutex(int semid) {
struct sembuf sop;

sop.sem_num = 0;
sop.sem_op = 1;
sop.sem_flg = 0;

return semop(semid, &sop, 1);
}

/**
* 演示多进程同步
*/
int demo_multiprocess_synchronization() {
int semid;
key_t key = ftok(".", 'm');
int shared_resource = 0;

printf("=== 多进程同步演示 ===\n");

// 创建互斥信号量
semid = create_binary_semaphore(key);
if (semid == -1) {
return -1;
}

printf("创建互斥信号量,ID: %d\n", semid);

// 创建多个子进程
for (int i = 0; i < 3; i++) {
if (fork() == 0) {
// 子进程代码
pid_t pid = getpid();
printf("子进程 %d 启动\n", pid);

// 尝试获取互斥锁(超时3秒)
if (acquire_mutex(semid, 3) == -1) {
if (errno == EAGAIN) {
printf("子进程 %d: 获取锁超时\n", pid);
} else {
printf("子进程 %d: 获取锁失败: %s\n", pid, strerror(errno));
}
exit(1);
}

printf("子进程 %d: 成功获取锁\n", pid);

// 模拟临界区操作
printf("子进程 %d: 访问共享资源\n", pid);
sleep(2); // 模拟处理时间

// 释放互斥锁
if (release_mutex(semid) == -1) {
printf("子进程 %d: 释放锁失败: %s\n", pid, strerror(errno));
} else {
printf("子进程 %d: 成功释放锁\n", pid);
}

exit(0);
}
}

// 父进程等待所有子进程完成
for (int i = 0; i < 3; i++) {
int status;
wait(&status);
if (WIFEXITED(status)) {
printf("子进程 %d 退出,状态码: %d\n",
i + 1, WEXITSTATUS(status));
}
}

// 清理信号量
semctl(semid, 0, IPC_RMID);

return 0;
}

int main() {
return demo_multiprocess_synchronization();
}

示例3:生产者-消费者模型

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

#define BUFFER_SIZE 5
#define MAX_ITEMS 10

union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};

// 信号量索引定义
#define EMPTY_SEM 0 // 空槽位计数
#define FULL_SEM 1 // 满槽位计数
#define MUTEX_SEM 2 // 缓冲区互斥锁

/**
* 初始化生产者-消费者信号量
*/
int init_pc_semaphores(key_t key) {
int semid;
union semun arg;
unsigned short values&#91;3] = {BUFFER_SIZE, 0, 1}; // empty, full, mutex

// 创建包含3个信号量的信号量集
semid = semget(key, 3, IPC_CREAT | 0666);
if (semid == -1) {
perror("创建信号量集失败");
return -1;
}

// 初始化信号量值
arg.array = values;
if (semctl(semid, 0, SETALL, arg) == -1) {
perror("初始化信号量失败");
semctl(semid, 0, IPC_RMID);
return -1;
}

return semid;
}

/**
* 生产者操作
*/
int producer_operation(int semid, int item, int timeout_sec) {
struct sembuf sops&#91;2];
struct timespec timeout;

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

// 1. 等待空槽位 (P(empty))
sops&#91;0].sem_num = EMPTY_SEM;
sops&#91;0].sem_op = -1;
sops&#91;0].sem_flg = 0;

// 2. 获取缓冲区互斥锁 (P(mutex))
sops&#91;1].sem_num = MUTEX_SEM;
sops&#91;1].sem_op = -1;
sops&#91;1].sem_flg = 0;

printf("生产者: 尝试生产项目 %d\n", item);

// 执行P操作
if (semtimedop(semid, sops, 2, &timeout) == -1) {
if (errno == EAGAIN) {
printf("生产者: 等待超时,无法生产项目 %d\n", item);
} else {
printf("生产者: 操作失败: %s\n", strerror(errno));
}
return -1;
}

// 模拟生产过程
printf("生产者: 正在生产项目 %d\n", item);
sleep(1);
printf("生产者: 生产完成项目 %d\n", item);

// 释放互斥锁和增加满槽位计数
sops&#91;0].sem_num = MUTEX_SEM;
sops&#91;0].sem_op = 1; // V(mutex)
sops&#91;1].sem_num = FULL_SEM;
sops&#91;1].sem_op = 1; // V(full)

if (semop(semid, sops, 2) == -1) {
perror("生产者: 释放信号量失败");
return -1;
}

return 0;
}

/**
* 消费者操作
*/
int consumer_operation(int semid, int *item, int timeout_sec) {
struct sembuf sops&#91;2];
struct timespec timeout;

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

// 1. 等待满槽位 (P(full))
sops&#91;0].sem_num = FULL_SEM;
sops&#91;0].sem_op = -1;
sops&#91;0].sem_flg = 0;

// 2. 获取缓冲区互斥锁 (P(mutex))
sops&#91;1].sem_num = MUTEX_SEM;
sops&#91;1].sem_op = -1;
sops&#91;1].sem_flg = 0;

printf("消费者: 尝试消费项目\n");

// 执行P操作
if (semtimedop(semid, sops, 2, &timeout) == -1) {
if (errno == EAGAIN) {
printf("消费者: 等待超时,无项目可消费\n");
} else {
printf("消费者: 操作失败: %s\n", strerror(errno));
}
return -1;
}

// 模拟消费过程
*item = rand() % 100; // 模拟消费的项目
printf("消费者: 正在消费项目 %d\n", *item);
sleep(1);
printf("消费者: 消费完成项目 %d\n", *item);

// 释放互斥锁和增加空槽位计数
sops&#91;0].sem_num = MUTEX_SEM;
sops&#91;0].sem_op = 1; // V(mutex)
sops&#91;1].sem_num = EMPTY_SEM;
sops&#91;1].sem_op = 1; // V(empty)

if (semop(semid, sops, 2) == -1) {
perror("消费者: 释放信号量失败");
return -1;
}

return 0;
}

/**
* 演示生产者-消费者模型
*/
int demo_producer_consumer() {
int semid;
key_t key = ftok(".", 'p');

printf("=== 生产者-消费者模型演示 ===\n");
printf("缓冲区大小: %d\n", BUFFER_SIZE);
printf("生产/消费项目数: %d\n", MAX_ITEMS);

// 初始化信号量
semid = init_pc_semaphores(key);
if (semid == -1) {
return -1;
}

printf("创建生产者-消费者信号量集,ID: %d\n", semid);

// 创建生产者进程
if (fork() == 0) {
// 生产者进程
srand(getpid());
printf("生产者进程启动 (PID: %d)\n", getpid());

for (int i = 1; i <= MAX_ITEMS; i++) {
if (producer_operation(semid, i, 5) == -1) {
printf("生产者: 生产项目 %d 失败\n", i);
break;
}
sleep(1); // 生产间隔
}

printf("生产者进程结束\n");
exit(0);
}

// 创建消费者进程
if (fork() == 0) {
// 消费者进程
srand(getpid() + 1000);
printf("消费者进程启动 (PID: %d)\n", getpid());

for (int i = 1; i <= MAX_ITEMS; i++) {
int item;
if (consumer_operation(semid, &item, 5) == -1) {
printf("消费者: 消费项目失败\n");
break;
}
sleep(2); // 消费间隔
}

printf("消费者进程结束\n");
exit(0);
}

// 父进程等待子进程完成
int status;
wait(&status);
wait(&status);

// 显示最终信号量状态
union semun arg;
unsigned short values&#91;3];
arg.array = values;

if (semctl(semid, 0, GETALL, arg) != -1) {
printf("\n最终信号量状态:\n");
printf(" 空槽位: %d\n", values&#91;EMPTY_SEM]);
printf(" 满槽位: %d\n", values&#91;FULL_SEM]);
printf(" 互斥锁: %d\n", values&#91;MUTEX_SEM]);
}

// 清理信号量集
semctl(semid, 0, IPC_RMID);

return 0;
}

int main() {
return demo_producer_consumer();
}

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

union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};

/**
* 创建计数信号量
*/
int create_counting_semaphore(key_t key, int initial_value) {
int semid;
union semun arg;

semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("创建计数信号量失败");
return -1;
}

arg.val = initial_value;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("初始化计数信号量失败");
semctl(semid, 0, IPC_RMID);
return -1;
}

return semid;
}

/**
* 演示不同超时设置
*/
int demo_timeout_settings() {
int semid;
struct sembuf sop;
struct timespec timeout;
key_t key = ftok(".", 't');

printf("=== 超时控制演示 ===\n");

// 创建计数信号量,初始值为0(无资源可用)
semid = create_counting_semaphore(key, 0);
if (semid == -1) {
return -1;
}

printf("创建计数信号量(初始值: 0),ID: %d\n", semid);

// 定义P操作
sop.sem_num = 0;
sop.sem_op = -1; // 请求资源
sop.sem_flg = 0; // 阻塞操作

printf("\n1. 测试短超时(1秒):\n");
timeout.tv_sec = 1;
timeout.tv_nsec = 0;

clock_t start = clock();
if (semtimedop(semid, &sop, 1, &timeout) == -1) {
if (errno == EAGAIN) {
clock_t end = clock();
double elapsed = ((double)(end - start)) / CLOCKS_PER_SEC;
printf(" 操作超时,等待时间: %.2f 秒\n", elapsed);
} else {
printf(" 操作失败: %s\n", strerror(errno));
}
}

printf("\n2. 测试中等超时(3秒):\n");
timeout.tv_sec = 3;
timeout.tv_nsec = 0;

start = clock();
if (semtimedop(semid, &sop, 1, &timeout) == -1) {
if (errno == EAGAIN) {
clock_t end = clock();
double elapsed = ((double)(end - start)) / CLOCKS_PER_SEC;
printf(" 操作超时,等待时间: %.2f 秒\n", elapsed);
} else {
printf(" 操作失败: %s\n", strerror(errno));
}
}

printf("\n3. 测试精确超时(1.5秒):\n");
timeout.tv_sec = 1;
timeout.tv_nsec = 500000000; // 500毫秒

start = clock();
if (semtimedop(semid, &sop, 1, &timeout) == -1) {
if (errno == EAGAIN) {
clock_t end = clock();
double elapsed = ((double)(end - start)) / CLOCKS_PER_SEC;
printf(" 操作超时,等待时间: %.2f 秒\n", elapsed);
} else {
printf(" 操作失败: %s\n", strerror(errno));
}
}

printf("\n4. 测试零超时(立即返回):\n");
timeout.tv_sec = 0;
timeout.tv_nsec = 0;

if (semtimedop(semid, &sop, 1, &timeout) == -1) {
if (errno == EAGAIN) {
printf(" 立即返回,操作无法完成\n");
} else {
printf(" 操作失败: %s\n", strerror(errno));
}
}

printf("\n5. 测试NULL超时(无限等待):\n");
printf(" 注意:这会导致无限期等待,需要外部干预\n");
printf(" 演示跳过此测试以避免阻塞\n");

// 启动一个进程来释放信号量
if (fork() == 0) {
sleep(2); // 等待主进程开始等待

// 释放信号量
sop.sem_op = 1; // V操作
if (semop(semid, &sop, 1) == -1) {
perror("释放信号量失败");
} else {
printf(" 后台进程:信号量已释放\n");
}
exit(0);
}

// 等待一小段时间后测试
sleep(1);
timeout.tv_sec = 5;
timeout.tv_nsec = 0;

printf(" 等待信号量释放(5秒超时)...\n");
if (semtimedop(semid, &sop, 1, &timeout) == 0) {
printf(" 成功获取信号量\n");
} else {
printf(" 获取信号量失败: %s\n", strerror(errno));
}

// 等待后台进程完成
int status;
wait(&status);

// 清理
semctl(semid, 0, IPC_RMID);

return 0;
}

int main() {
return demo_timeout_settings();
}

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

union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};

/**
* 创建多个信号量的信号量集
*/
int create_multi_semaphore(key_t key, int nsems) {
int semid;

semid = semget(key, nsems, IPC_CREAT | 0666);
if (semid == -1) {
perror("创建多信号量集失败");
return -1;
}

// 初始化所有信号量为1
unsigned short *values = malloc(nsems * sizeof(unsigned short));
if (!values) {
perror("分配内存失败");
semctl(semid, 0, IPC_RMID);
return -1;
}

for (int i = 0; i < nsems; i++) {
values&#91;i] = 1;
}

union semun arg;
arg.array = values;
if (semctl(semid, 0, SETALL, arg) == -1) {
perror("初始化多信号量失败");
free(values);
semctl(semid, 0, IPC_RMID);
return -1;
}

free(values);
return semid;
}

/**
* 演示复杂的多信号量操作
*/
int demo_complex_operations() {
int semid;
struct sembuf sops&#91;5];
struct timespec timeout;
key_t key = ftok(".", 'c');
int num_semaphores = 5;

printf("=== 复杂信号量操作演示 ===\n");
printf("创建包含 %d 个信号量的信号量集\n", num_semaphores);

// 创建信号量集
semid = create_multi_semaphore(key, num_semaphores);
if (semid == -1) {
return -1;
}

printf("信号量集创建成功,ID: %d\n", semid);

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

// 演示原子性操作:同时操作多个信号量
printf("\n1. 原子性多信号量操作:\n");
printf(" 同时获取信号量 0, 1, 2\n");

// 同时获取3个信号量(P操作)
for (int i = 0; i < 3; i++) {
sops&#91;i].sem_num = i;
sops&#91;i].sem_op = -1;
sops&#91;i].sem_flg = 0;
}

if (semtimedop(semid, sops, 3, &timeout) == 0) {
printf(" 成功原子性获取3个信号量\n");

// 模拟使用资源
printf(" 使用获取的资源...\n");
sleep(2);

// 同时释放3个信号量(V操作)
for (int i = 0; i < 3; i++) {
sops&#91;i].sem_op = 1;
}

if (semop(semid, sops, 3) == 0) {
printf(" 成功原子性释放3个信号量\n");
} else {
perror(" 释放信号量失败");
}
} else {
printf(" 获取信号量失败: %s\n", strerror(errno));
}

// 演示混合操作:获取和释放不同信号量
printf("\n2. 混合信号量操作:\n");
printf(" 获取信号量0,释放信号量1\n");

sops&#91;0].sem_num = 0;
sops&#91;0].sem_op = -1; // 获取
sops&#91;0].sem_flg = 0;

sops&#91;1].sem_num = 1;
sops&#91;1].sem_op = 1; // 释放
sops&#91;1].sem_flg = 0;

if (semtimedop(semid, sops, 2, &timeout) == 0) {
printf(" 混合操作成功\n");
} else {
printf(" 混合操作失败: %s\n", strerror(errno));
}

// 演示条件操作:使用SEM_UNDO标志
printf("\n3. 带自动回滚的操作:\n");
printf(" 使用SEM_UNDO标志,进程退出时自动释放\n");

sops&#91;0].sem_num = 2;
sops&#91;0].sem_op = -1;
sops&#91;0].sem_flg = SEM_UNDO; // 自动回滚

if (semtimedop(semid, sops, 1, &timeout) == 0) {
printf(" 获取信号量2(带自动回滚)\n");
printf(" 程序退出时会自动释放该信号量\n");

// 模拟程序继续运行
printf(" 程序继续运行中...\n");
sleep(1);

// 不显式释放,依赖SEM_UNDO
} else {
printf(" 获取信号量失败: %s\n", strerror(errno));
}

// 演示NOWAIT操作
printf("\n4. 非阻塞操作:\n");
printf(" 使用IPC_NOWAIT标志\n");

sops&#91;0].sem_num = 3;
sops&#91;0].sem_op = -1;
sops&#91;0].sem_flg = IPC_NOWAIT; // 非阻塞

if (semtimedop(semid, sops, 1, NULL) == 0) {
printf(" 非阻塞获取信号量3成功\n");
// 立即释放
sops&#91;0].sem_op = 1;
sops&#91;0].sem_flg = 0;
semop(semid, sops, 1);
} else {
if (errno == EAGAIN) {
printf(" 信号量3不可用,立即返回\n");
} else {
printf(" 操作失败: %s\n", strerror(errno));
}
}

// 显示当前信号量状态
printf("\n5. 当前信号量状态:\n");
unsigned short *values = malloc(num_semaphores * sizeof(unsigned short));
if (values) {
union semun arg;
arg.array = values;

if (semctl(semid, 0, GETALL, arg) != -1) {
for (int i = 0; i < num_semaphores; i++) {
printf(" 信号量 %d: %d\n", i, values&#91;i]);
}
}
free(values);
}

// 清理
semctl(semid, 0, IPC_RMID);

return 0;
}

int main() {
return demo_complex_operations();
}

semtimedop 使用注意事项

系统要求:

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

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

编译环境: 需要正确的头文件包含

参数验证:

信号量ID有效性: 确保semid有效且未被删除

操作数组合法性: 确保sops指针有效且nsops合理

超时参数: timeout可以为NULL(无限等待)或有效时间结构

错误处理:

EAGAIN: 操作超时

EINTR: 被信号中断

EINVAL: 参数无效

EACCES: 权限不足

EIDRM: 信号量集已被删除

ENOMEM: 内存不足

性能考虑:

  1. 超时设置: 合理设置超时时间避免过度等待2. 原子性: 多信号量操作保证原子性3. 资源清理: 及时清理不再使用的信号量

最佳实践:

  1. 超时控制: 始终考虑设置合理的超时时间2. 错误恢复: 妥善处理超时和错误情况3. 资源管理: 及时清理信号量资源4. 并发安全: 确保多进程/线程环境下的正确性

信号量操作结构详解

struct sembuf 结构:

1
2
3
4
5
6
struct sembuf {
unsigned short sem_num; // 信号量编号
short sem_op; // 操作类型
short sem_flg; // 操作标志
};

操作类型 (sem_op):

  • 正值: V操作(释放资源),将值加到信号量上

  • 负值: P操作(获取资源),从信号量中减去该值

  • 零值: 等待信号量变为0

操作标志 (sem_flg):

  • SEM_UNDO: 操作完成后自动撤销(进程退出时)

  • IPC_NOWAIT: 非阻塞操作,如果不能立即完成则返回错误

  • 0: 默认阻塞操作

常见使用场景

1. 互斥锁:

1
2
3
4
5
6
7
8
// 获取锁
sop.sem_op = -1;
semtimedop(semid, &sop, 1, &timeout);

// 释放锁
sop.sem_op = 1;
semop(semid, &sop, 1);

2. 资源计数:

1
2
3
4
5
6
7
8
// 请求资源
sop.sem_op = -1;
semtimedop(semid, &sop, 1, &timeout);

// 释放资源
sop.sem_op = 1;
semop(semid, &sop, 1);

3. 条件同步:

1
2
3
4
// 等待条件满足(信号量为0)
sop.sem_op = 0;
semtimedop(semid, &sop, 1, &timeout);

总结

semtimedop 是一个功能强大的信号量操作函数,提供了:

  1. 超时控制: 避免无限期等待,提高程序健壮性2. 原子操作: 保证多信号量操作的原子性3. 灵活配置: 支持多种操作标志和模式4. 广泛应用: 适用于各种进程间同步场景

通过合理使用 semtimedop,可以构建更加可靠和高效的并发程序,特别是在需要精确时间控制的场景中表现出色。在实际应用中,需要注意超时设置、错误处理和资源管理等关键问题。

flock系统调用及示例

flock - 文件锁flock系统调用及示例

函数介绍

flock系统调用用于对文件进行加锁操作,实现进程间的文件访问同步。文件锁可以防止多个进程同时修改同一文件,保证数据的一致性。(https://www.calcguide.tech/2025/08/17/flock系统调用及示例/)

函数原型

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

int flock(int fd, int operation);

功能

对指定文件描述符对应的文件进行加锁或解锁操作。

参数

  • int fd: 文件描述符

int operation: 锁操作类型

  • LOCK_SH: 共享锁(读锁),多个进程可以同时持有

  • LOCK_EX: 排他锁(写锁),只能有一个进程持有

  • LOCK_UN: 解锁

  • LOCK_NB: 非阻塞模式(与上述操作组合使用)

返回值

  • 成功时返回0

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

  • EAGAIN/EWOULDBLOCK: 非阻塞模式下无法获取锁

  • EBADF: 文件描述符无效

  • EINTR: 系统调用被信号中断

  • EINVAL: 参数无效

  • EOPNOTSUPP: 文件系统不支持锁

相似函数

  • fcntl(): 更灵活的文件锁操作

  • lockf(): POSIX文件锁接口

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>

int main() {
int fd;
pid_t pid;

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

// 创建测试文件
fd = open("test_flock.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
exit(EXIT_FAILURE);
}
printf("成功创建测试文件,文件描述符: %d\n", fd);

// 写入初始数据
const char *initial_data = "Initial data for flock test\n";
if (write(fd, initial_data, strlen(initial_data)) == -1) {
perror("写入初始数据失败");
close(fd);
exit(EXIT_FAILURE);
}

// 示例1: 基本的排他锁操作
printf("\n示例1: 基本的排他锁操作\n");

// 获取排他锁
if (flock(fd, LOCK_EX) == -1) {
perror("获取排他锁失败");
} else {
printf(" 成功获取排他锁\n");

// 在锁保护下写入数据
const char *exclusive_data = "Data written with exclusive lock\n";
if (write(fd, exclusive_data, strlen(exclusive_data)) == -1) {
perror(" 写入数据失败");
} else {
printf(" 成功写入数据到文件\n");
}

// 释放锁
if (flock(fd, LOCK_UN) == -1) {
perror(" 释放锁失败");
} else {
printf(" 成功释放排他锁\n");
}
}

// 示例2: 共享锁操作
printf("\n示例2: 共享锁操作\n");

// 获取共享锁
if (flock(fd, LOCK_SH) == -1) {
perror("获取共享锁失败");
} else {
printf(" 成功获取共享锁\n");

// 在锁保护下读取数据
char buffer&#91;200];
lseek(fd, 0, SEEK_SET);
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 读取文件内容:\n%s", buffer);
}

// 释放锁
if (flock(fd, LOCK_UN) == -1) {
perror(" 释放共享锁失败");
} else {
printf(" 成功释放共享锁\n");
}
}

// 示例3: 非阻塞锁操作
printf("\n示例3: 非阻塞锁操作\n");

// 先获取一个排他锁
if (flock(fd, LOCK_EX) == -1) {
perror("获取排他锁失败");
} else {
printf(" 进程已持有排他锁\n");

// 尝试非阻塞获取另一个排他锁(应该失败)
if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf(" 非阻塞获取排他锁: 立即返回EAGAIN(锁被占用)\n");
} else {
perror(" 非阻塞获取锁失败");
}
} else {
printf(" 非阻塞获取排他锁成功(不应该发生)\n");
flock(fd, LOCK_UN); // 释放意外获取的锁
}

// 尝试非阻塞获取共享锁(也应该失败,因为有排他锁)
if (flock(fd, LOCK_SH | LOCK_NB) == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf(" 非阻塞获取共享锁: 立即返回EAGAIN(有排他锁)\n");
} else {
perror(" 非阻塞获取共享锁失败");
}
} else {
printf(" 非阻塞获取共享锁成功(不应该发生)\n");
flock(fd, LOCK_UN); // 释放意外获取的锁
}

// 释放锁
flock(fd, LOCK_UN);
printf(" 释放排他锁\n");
}

// 示例4: 多进程锁演示
printf("\n示例4: 多进程锁演示\n");

pid = fork();
if (pid == -1) {
perror("fork失败");
close(fd);
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程
printf(" 子进程 %d 开始执行\n", getpid());

// 子进程打开同一个文件
int child_fd = open("test_flock.txt", O_RDWR);
if (child_fd == -1) {
perror(" 子进程打开文件失败");
exit(EXIT_FAILURE);
}

printf(" 子进程尝试获取排他锁...\n");
if (flock(child_fd, LOCK_EX) == -1) {
perror(" 子进程获取排他锁失败");
} else {
printf(" 子进程成功获取排他锁\n");

// 写入子进程数据
char child_data&#91;100];
sprintf(child_data, "Data from child process %d\n", getpid());
if (write(child_fd, child_data, strlen(child_data)) == -1) {
perror(" 子进程写入数据失败");
} else {
printf(" 子进程写入数据成功\n");
}

sleep(3); // 持有锁3秒

// 释放锁
if (flock(child_fd, LOCK_UN) == -1) {
perror(" 子进程释放锁失败");
} else {
printf(" 子进程释放排他锁\n");
}
}

close(child_fd);
exit(EXIT_SUCCESS);
} else {
// 父进程
sleep(1); // 让子进程先运行

printf(" 父进程 %d 尝试获取排他锁...\n", getpid());

// 父进程尝试获取排他锁(会被阻塞直到子进程释放)
printf(" 父进程获取排他锁(会被阻塞)...\n");
if (flock(fd, LOCK_EX) == -1) {
perror(" 父进程获取排他锁失败");
} else {
printf(" 父进程成功获取排他锁(子进程已释放)\n");

// 写入父进程数据
char parent_data&#91;100];
sprintf(parent_data, "Data from parent process %d\n", getpid());
if (write(fd, parent_data, strlen(parent_data)) == -1) {
perror(" 父进程写入数据失败");
} else {
printf(" 父进程写入数据成功\n");
}

// 释放锁
if (flock(fd, LOCK_UN) == -1) {
perror(" 父进程释放锁失败");
} else {
printf(" 父进程释放排他锁\n");
}
}

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

// 示例5: 共享锁并发演示
printf("\n示例5: 共享锁并发演示\n");

// 创建多个子进程同时获取共享锁
for (int i = 0; i < 3; i++) {
pid = fork();
if (pid == 0) {
// 子进程
int child_fd = open("test_flock.txt", O_RDONLY);
if (child_fd != -1) {
printf(" 子进程 %d 尝试获取共享锁...\n", getpid());

if (flock(child_fd, LOCK_SH) == 0) {
printf(" 子进程 %d 成功获取共享锁\n", getpid());

// 读取数据
char read_buffer&#91;200];
lseek(child_fd, 0, SEEK_SET);
ssize_t bytes_read = read(child_fd, read_buffer, sizeof(read_buffer) - 1);
if (bytes_read > 0) {
read_buffer&#91;bytes_read] = '\0';
printf(" 子进程 %d 读取数据成功\n", getpid());
}

sleep(2); // 持有共享锁2秒

flock(child_fd, LOCK_UN);
printf(" 子进程 %d 释放共享锁\n", getpid());
}

close(child_fd);
}
exit(EXIT_SUCCESS);
}
}

// 等待所有子进程结束
for (int i = 0; i < 3; i++) {
wait(NULL);
}

// 示例6: 锁的继承和关闭行为
printf("\n示例6: 锁的继承和关闭行为\n");

if (flock(fd, LOCK_EX) == 0) {
printf(" 进程持有排他锁\n");

// 复制文件描述符
int dup_fd = dup(fd);
if (dup_fd != -1) {
printf(" 复制文件描述符: %d -> %d\n", fd, dup_fd);

// 使用复制的fd释放锁
if (flock(dup_fd, LOCK_UN) == 0) {
printf(" 使用复制的fd释放锁成功\n");
}

close(dup_fd);
}

// 重新获取锁
if (flock(fd, LOCK_EX) == 0) {
printf(" 重新获取锁成功\n");
flock(fd, LOCK_UN);
}
}

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

// 尝试对无效文件描述符加锁
if (flock(999, LOCK_EX) == -1) {
printf(" 对无效文件描述符加锁: %s\n", strerror(errno));
}

// 清理资源
printf("\n清理资源...\n");
if (close(fd) == -1) {
perror("关闭文件失败");
} else {
printf("成功关闭文件描述符 %d\n", fd);
}

// 删除测试文件
if (unlink("test_flock.txt") == -1) {
perror("删除测试文件失败");
} else {
printf("成功删除测试文件\n");
}

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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>

int main() {
int fd;
pid_t pid;

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

// 创建测试文件
fd = open("test_flock.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
exit(EXIT_FAILURE);
}
printf("成功创建测试文件,文件描述符: %d\n", fd);

// 写入初始数据
const char *initial_data = "Initial data for flock test\n";
if (write(fd, initial_data, strlen(initial_data)) == -1) {
perror("写入初始数据失败");
close(fd);
exit(EXIT_FAILURE);
}

// 示例1: 基本的排他锁操作
printf("\n示例1: 基本的排他锁操作\n");

// 获取排他锁
if (flock(fd, LOCK_EX) == -1) {
perror("获取排他锁失败");
} else {
printf(" 成功获取排他锁\n");

// 在锁保护下写入数据
const char *exclusive_data = "Data written with exclusive lock\n";
if (write(fd, exclusive_data, strlen(exclusive_data)) == -1) {
perror(" 写入数据失败");
} else {
printf(" 成功写入数据到文件\n");
}

// 释放锁
if (flock(fd, LOCK_UN) == -1) {
perror(" 释放锁失败");
} else {
printf(" 成功释放排他锁\n");
}
}

// 示例2: 共享锁操作
printf("\n示例2: 共享锁操作\n");

// 获取共享锁
if (flock(fd, LOCK_SH) == -1) {
perror("获取共享锁失败");
} else {
printf(" 成功获取共享锁\n");

// 在锁保护下读取数据
char buffer&#91;200];
lseek(fd, 0, SEEK_SET);
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 读取文件内容:\n%s", buffer);
}

// 释放锁
if (flock(fd, LOCK_UN) == -1) {
perror(" 释放共享锁失败");
} else {
printf(" 成功释放共享锁\n");
}
}

// 示例3: 非阻塞锁操作
printf("\n示例3: 非阻塞锁操作\n");

// 先获取一个排他锁
if (flock(fd, LOCK_EX) == -1) {
perror("获取排他锁失败");
} else {
printf(" 进程已持有排他锁\n");

// 尝试非阻塞获取另一个排他锁(应该失败)
if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf(" 非阻塞获取排他锁: 立即返回EAGAIN(锁被占用)\n");
} else {
perror(" 非阻塞获取锁失败");
}
} else {
printf(" 非阻塞获取排他锁成功(不应该发生)\n");
flock(fd, LOCK_UN); // 释放意外获取的锁
}

// 尝试非阻塞获取共享锁(也应该失败,因为有排他锁)
if (flock(fd, LOCK_SH | LOCK_NB) == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf(" 非阻塞获取共享锁: 立即返回EAGAIN(有排他锁)\n");
} else {
perror(" 非阻塞获取共享锁失败");
}
} else {
printf(" 非阻塞获取共享锁成功(不应该发生)\n");
flock(fd, LOCK_UN); // 释放意外获取的锁
}

// 释放锁
flock(fd, LOCK_UN);
printf(" 释放排他锁\n");
}

// 示例4: 多进程锁演示
printf("\n示例4: 多进程锁演示\n");

pid = fork();
if (pid == -1) {
perror("fork失败");
close(fd);
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程
printf(" 子进程 %d 开始执行\n", getpid());

// 子进程打开同一个文件
int child_fd = open("test_flock.txt", O_RDWR);
if (child_fd == -1) {
perror(" 子进程打开文件失败");
exit(EXIT_FAILURE);
}

printf(" 子进程尝试获取排他锁...\n");
if (flock(child_fd, LOCK_EX) == -1) {
perror(" 子进程获取排他锁失败");
} else {
printf(" 子进程成功获取排他锁\n");

// 写入子进程数据
char child_data&#91;100];
sprintf(child_data, "Data from child process %d\n", getpid());
if (write(child_fd, child_data, strlen(child_data)) == -1) {
perror(" 子进程写入数据失败");
} else {
printf(" 子进程写入数据成功\n");
}

sleep(3); // 持有锁3秒

// 释放锁
if (flock(child_fd, LOCK_UN) == -1) {
perror(" 子进程释放锁失败");
} else {
printf(" 子进程释放排他锁\n");
}
}

close(child_fd);
exit(EXIT_SUCCESS);
} else {
// 父进程
sleep(1); // 让子进程先运行

printf(" 父进程 %d 尝试获取排他锁...\n", getpid());

// 父进程尝试获取排他锁(会被阻塞直到子进程释放)
printf(" 父进程获取排他锁(会被阻塞)...\n");
if (flock(fd, LOCK_EX) == -1) {
perror(" 父进程获取排他锁失败");
} else {
printf(" 父进程成功获取排他锁(子进程已释放)\n");

// 写入父进程数据
char parent_data&#91;100];
sprintf(parent_data, "Data from parent process %d\n", getpid());
if (write(fd, parent_data, strlen(parent_data)) == -1) {
perror(" 父进程写入数据失败");
} else {
printf(" 父进程写入数据成功\n");
}

// 释放锁
if (flock(fd, LOCK_UN) == -1) {
perror(" 父进程释放锁失败");
} else {
printf(" 父进程释放排他锁\n");
}
}

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

// 示例5: 共享锁并发演示
printf("\n示例5: 共享锁并发演示\n");

// 创建多个子进程同时获取共享锁
for (int i = 0; i < 3; i++) {
pid = fork();
if (pid == 0) {
// 子进程
int child_fd = open("test_flock.txt", O_RDONLY);
if (child_fd != -1) {
printf(" 子进程 %d 尝试获取共享锁...\n", getpid());

if (flock(child_fd, LOCK_SH) == 0) {
printf(" 子进程 %d 成功获取共享锁\n", getpid());

// 读取数据
char read_buffer&#91;200];
lseek(child_fd, 0, SEEK_SET);
ssize_t bytes_read = read(child_fd, read_buffer, sizeof(read_buffer) - 1);
if (bytes_read > 0) {
read_buffer&#91;bytes_read] = '\0';
printf(" 子进程 %d 读取数据成功\n", getpid());
}

sleep(2); // 持有共享锁2秒

flock(child_fd, LOCK_UN);
printf(" 子进程 %d 释放共享锁\n", getpid());
}

close(child_fd);
}
exit(EXIT_SUCCESS);
}
}

// 等待所有子进程结束
for (int i = 0; i < 3; i++) {
wait(NULL);
}

// 示例6: 锁的继承和关闭行为
printf("\n示例6: 锁的继承和关闭行为\n");

if (flock(fd, LOCK_EX) == 0) {
printf(" 进程持有排他锁\n");

// 复制文件描述符
int dup_fd = dup(fd);
if (dup_fd != -1) {
printf(" 复制文件描述符: %d -> %d\n", fd, dup_fd);

// 使用复制的fd释放锁
if (flock(dup_fd, LOCK_UN) == 0) {
printf(" 使用复制的fd释放锁成功\n");
}

close(dup_fd);
}

// 重新获取锁
if (flock(fd, LOCK_EX) == 0) {
printf(" 重新获取锁成功\n");
flock(fd, LOCK_UN);
}
}

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

// 尝试对无效文件描述符加锁
if (flock(999, LOCK_EX) == -1) {
printf(" 对无效文件描述符加锁: %s\n", strerror(errno));
}

// 清理资源
printf("\n清理资源...\n");
if (close(fd) == -1) {
perror("关闭文件失败");
} else {
printf("成功关闭文件描述符 %d\n", fd);
}

// 删除测试文件
if (unlink("test_flock.txt") == -1) {
perror("删除测试文件失败");
} else {
printf("成功删除测试文件\n");
}

return 0;
}

getcpu系统调用及示例

getcpu - 获取当前 CPU 和 NUMA 节点信息]()

1. 函数介绍

getcpu 是一个 Linux 系统调用,用于获取当前线程正在运行的 CPU 核心编号和 NUMA(Non-Uniform Memory Access)节点编号。这个函数对于性能分析、负载均衡、线程亲和性设置等场景非常有用。

NUMA 是一种多处理器计算机内存设计,其中内存访问时间取决于内存相对于处理器的位置。了解当前线程在哪个 CPU 核心和 NUMA 节点上运行,有助于优化程序性能。

2. 函数原型

1
2
3
4
5
6
7
#define _GNU_SOURCE
#include <linux/getcpu.h>
#include <sys/syscall.h>
#include <unistd.h>

long getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache);

注意:该函数不是标准 C 库的一部分,需要通过 syscall() 调用或者包含 glibc 2.29+ 版本后才能直接调用。

3. 功能

获取当前线程正在运行的 CPU 核心编号和 NUMA 节点编号。这是一个轻量级的操作,通常用于性能监控和调度优化。

4. 参数

unsigned *cpu: 指向存储 CPU 核心编号的变量的指针

  • 如果为 NULL:不返回 CPU 信息

  • 如果非 NULL:存储当前 CPU 核心编号(从 0 开始)

unsigned *node: 指向存储 NUMA 节点编号的变量的指针

  • 如果为 NULL:不返回 NUMA 节点信息

  • 如果非 NULL:存储当前 NUMA 节点编号(从 0 开始)

struct getcpu_cache *tcache: 缓存结构体指针,用于优化性能

  • 通常传入 NULL(内核会使用默认缓存机制)

  • 主要供内核内部使用

5. 返回值

  • 成功时返回 0

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

6. 常见 errno 错误码

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

  • EINVAL: 无效参数

  • ENOSYS: 系统不支持该功能

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

  • sched_getcpu(): glibc 提供的获取当前 CPU 的便捷函数

  • sched_setaffinity(): 设置进程 CPU 亲和性

  • sched_getaffinity(): 获取进程 CPU 亲和性

  • pthread_setaffinity_np(): 设置线程 CPU 亲和性

  • pthread_getaffinity_np(): 获取线程 CPU 亲和性

  • /proc/cpuinfo: 查看 CPU 信息

  • /sys/devices/system/cpu/: CPU 拓扑信息

  • numactl: NUMA 控制工具

8. 示例代码

示例1:基本使用 - 获取当前 CPU 和 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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/getcpu.h>
#include <errno.h>
#include <string.h>

#ifndef SYS_getcpu
# define SYS_getcpu 309 // x86_64 架构下的系统调用号
#endif

int main() {
unsigned cpu, node;
long result;

printf("=== 获取当前 CPU 和 NUMA 节点信息 ===\n");

// 使用 syscall 调用 getcpu
result = syscall(SYS_getcpu, &cpu, &node, NULL);

if (result == -1) {
printf("getcpu 调用失败: %s\n", strerror(errno));
return 1;
}

printf("当前线程运行信息:\n");
printf(" CPU 核心编号: %u\n", cpu);
printf(" NUMA 节点编号: %u\n", node);

// 验证系统 CPU 数量
long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
printf(" 系统在线 CPU 数: %ld\n", nprocs);

if (cpu >= nprocs) {
printf(" 警告: CPU 编号超出系统 CPU 数范围\n");
}

return 0;
}

示例2:使用 glibc 提供的 sched_getcpu 函数

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

int main() {
int cpu;

printf("=== 使用 sched_getcpu 获取 CPU 信息 ===\n");

cpu = sched_getcpu();
if (cpu == -1) {
perror("sched_getcpu 调用失败");
return 1;
}

printf("当前 CPU 核心编号: %d\n", cpu);

// 获取系统信息进行验证
long nprocs_conf = sysconf(_SC_NPROCESSORS_CONF);
long nprocs_onln = sysconf(_SC_NPROCESSORS_ONLN);

printf("系统配置 CPU 数: %ld\n", nprocs_conf);
printf("系统在线 CPU 数: %ld\n", nprocs_onln);

if (cpu >= nprocs_onln) {
printf("警告: CPU 编号超出在线 CPU 范围\n");
}

return 0;
}

示例3:监控 CPU 迁移情况

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

#ifndef SYS_getcpu
# define SYS_getcpu 309
#endif

void monitor_cpu_migration(int duration_seconds) {
unsigned cpu, node;
unsigned last_cpu = (unsigned)-1;
unsigned last_node = (unsigned)-1;
int migrations = 0;
time_t start_time = time(NULL);
time_t current_time;

printf("开始监控 CPU 迁移情况 (%d 秒)...\n", duration_seconds);
printf("%-10s %-6s %-6s %-15s\n", "时间", "CPU", "Node", "状态");
printf("%-10s %-6s %-6s %-15s\n", "----", "---", "----", "----");

while ((current_time = time(NULL)) - start_time < duration_seconds) {
if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
char status&#91;20] = "运行中";

if (last_cpu != (unsigned)-1) {
if (cpu != last_cpu) {
snprintf(status, sizeof(status), "CPU迁移 %u->%u", last_cpu, cpu);
migrations++;
} else if (node != last_node) {
snprintf(status, sizeof(status), "Node迁移 %u->%u", last_node, node);
migrations++;
}
}

printf("%-10ld %-6u %-6u %-15s\n",
current_time - start_time, cpu, node, status);

last_cpu = cpu;
last_node = node;
}

usleep(500000); // 休眠 0.5 秒
}

printf("\n监控结束:\n");
printf(" 总迁移次数: %d\n", migrations);
printf(" 平均迁移间隔: %.2f 秒\n",
migrations > 0 ? (double)duration_seconds / migrations : 0.0);
}

int main() {
printf("=== CPU 迁移监控演示 ===\n");

// 获取初始信息
unsigned cpu, node;
if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
printf("初始位置: CPU %u, Node %u\n", cpu, node);
}

// 监控 10 秒钟的 CPU 迁移情况
monitor_cpu_migration(10);

return 0;
}

示例4:多线程环境下的 CPU 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <linux/getcpu.h>
#include <sched.h>

#ifndef SYS_getcpu
# define SYS_getcpu 309
#endif

void* thread_function(void *arg) {
int thread_id = *(int*)arg;
unsigned cpu, node;
cpu_set_t cpuset;
int ret;

// 获取当前线程的 CPU 信息
if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
printf("线程 %d: 初始位置 CPU %u, Node %u\n", thread_id, cpu, node);
}

// 获取当前 CPU 亲和性
ret = pthread_getaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
if (ret == 0) {
printf("线程 %d: 可运行在 CPU ", thread_id);
for (int i = 0; i < CPU_SETSIZE; i++) {
if (CPU_ISSET(i, &cpuset)) {
printf("%d ", i);
}
}
printf("\n");
}

// 执行一些工作
for (int i = 0; i < 5; i++) {
sleep(1);
if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
printf("线程 %d: 当前位置 CPU %u, Node %u\n", thread_id, cpu, node);
}
}

return NULL;
}

int main() {
pthread_t threads&#91;3];
int thread_ids&#91;3] = {1, 2, 3};

printf("=== 多线程 CPU 信息演示 ===\n");

// 创建多个线程
for (int i = 0; i < 3; i++) {
if (pthread_create(&threads&#91;i], NULL, thread_function, &thread_ids&#91;i]) != 0) {
perror("创建线程失败");
return 1;
}
}

// 等待所有线程完成
for (int i = 0; i < 3; i++) {
pthread_join(threads&#91;i], NULL);
}

printf("所有线程执行完成\n");

return 0;
}

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

void print_numa_info() {
FILE *fp;
char line&#91;256];

printf("\n=== NUMA 系统信息 ===\n");

// 检查 NUMA 支持
if (access("/proc/numa_maps", F_OK) == 0) {
printf("系统支持 NUMA\n");
} else {
printf("系统不支持 NUMA 或无访问权限\n");
return;
}

// 查看 NUMA 节点信息
fp = fopen("/sys/devices/system/node/online", "r");
if (fp) {
if (fgets(line, sizeof(line), fp)) {
printf("在线 NUMA 节点: %s", line);
}
fclose(fp);
}

// 查看 CPU 拓扑
system("lscpu | grep -E 'NUMA|CPU(s)'");
}

10. 性能优化应用场景

getcpu 在以下场景中非常有用:

场景1:线程绑定优化

1
2
3
4
5
6
7
8
9
10
#include <sched.h>
#include <pthread.h>

void bind_thread_to_cpu(int target_cpu) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(target_cpu, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
}

场景2:内存分配优化

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

void* allocate_local_memory(size_t size) {
if (numa_available() == -1) {
return malloc(size); // NUMA 不可用,使用普通分配
}
return numa_alloc_local(size); // 分配在本地 NUMA 节点
}

11. 实际系统中的应用

  1. 数据库系统: 将查询线程绑定到特定 CPU 以提高缓存命中率2. Web 服务器: 均衡分配请求处理线程到不同 CPU 核心3. 高性能计算: 优化 MPI 进程的 CPU 和 NUMA 布局4. 实时系统: 确保关键任务在指定 CPU 上运行5. 容器技术: Docker 和 Kubernetes 中的 CPU 资源管理

总结

getcpu 是一个重要的系统调用,用于获取当前线程的 CPU 和 NUMA 节点信息。关键要点:

  1. 轻量级操作: 快速获取当前执行环境信息2. NUMA 感知: 支持现代多核 NUMA 系统3. 性能优化: 为调度和内存访问优化提供基础信息4. 多线程支持: 在并发环境中准确反映线程位置5. 系统监控: 用于负载均衡和性能分析工具

在编写高性能应用程序时,了解和利用 CPU 和 NUMA 信息可以显著提升程序性能,特别是在多核和 NUMA 系统上。】

CSDN-LINK:https://blog.csdn.net/timberwolf007/article/details/150473820?sharetype=blogdetail&sharerId=150473820&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118