C 语言 信号量
操作系统用信号量控制程序,当 ctrl+z
向程序发送中断信号。
信号是一条短消息,即一个整型值。当信号到来时,进程必须停止手中一切工作去处理信号。进程会查看信号映射表,表中每个信号都对应一个信号处理器函数。中断信号的默认信号处理器会调用 exit() 函数。
3.1 sigaction
sigaction
是一个函数包装器,是一个结构体,有一个函数指针。sigaction 告诉操作系统进程收到某个信号时应该调用哪个函数。如果想 在某人向进程发送中断信号时让操作系统调用 diediedie() 函 数,就需要把 diediedie() 函数包装成 sigaction。
3.2 处理器必须接收信号量参数
信号是一个整型值,如果你创建了一个自定义处理器函数,就 需要接收一个整型参数,像这样:
void diediedie(int sig) { puts("end"); exit(1); }
3.3 用 sigaction() 函数注册 sigaction
创建 sigaction 以后,需要用 sigaction() 函数来让操作系统知道它的存在:
sigaction(signal_no, &new_action, &old_action);
如果 sigaction() 函数失败,会返回 -1,并设置 errno 变量。
实例:Ctrl-C:处理程序
#include <stdio.h> #include <signal.h> // 使用这个头文件 #include <stdlib.h> //新的信号处理器 void diediedie(int sig) { // 操作系统把信号传给处理器 puts("goodbye!\n"); exit(1); //处理器返回 void } // 注册处理器函数 int catch_signal(int sig, void (*handler)(int)) { struct sigaction action; // 创建动作 action.sa_handler = handler; // 将动作处理器设置为我们传来的函数 sigemptyset(&action.sa_mask); // 使用一个空的掩码 action.sa_flags = 0; return sigaction(sig, &action, NULL); } int main() { if (catch_signal(SIGINT, diediedie) == -1) { // SIGINT:表示我们要捕获的信号量 // 将中断处理程序设为 diediedie() fprintf(stderr, "Can't map the handler"); exit(2); } char name[30]; printf("Enter your name:"); fgets(name, 30, stdin); printf("Hello, %s\n", name); return 0; }
程序要求用户输入名字,然后它会等待输入。如果用户没有输入名字而是按了 Ctrl-C
,操作系统会自动向进程发送中断信号 SIGINT
,然后用我们在 catch_signal()
函数中注册的 sigaction
来处理这个信号。sigaction 中有一个指向 diediedie()
函数的指针,程序会调用这个函数,显示消息并调用 exit()
。
3.4 发送信号
在类 Unix 操作系统中我们经常用 kill
命令,kill 向进程发送一个信号(默认: SIGTERM
),也可以用它发送其他信号,在 C 中可以用 raise()
函数让进程向自己发送信号。
raise(SIGTERM);
除了在发生错误时使用,有时进程也需要产生自己的信号,比如信号 SIGALRM。信号通常由进程的间隔定时器创建。间隔定时器就像一台:你可以定一个时间,其间程序就会去做 其他事情:
注意:不要同时使用 sleep()
和 alarm()
, 因为都使用了间隔计时器,同时使用会发生冲突
3.5 实例:计时数学运算大比拼
下面程序测试用户的数学水平,要求用户输出 a*b
的值,涉及条件:
- 控制用户的 ctrl-c
- 用户要是在 5s 后没有写上答案则程序结束并把退出状态设为 0
#include <stdio.h> #include <signal.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <time.h> #include <stdlib.h> int score=0; //新的信号处理器 void end_game(int sig){ printf("\nFinal score: %i\n", score); exit(0); } //注册处理器函数 int catch_signal(int sig, void (*handler)(int)) { struct sigaction action; action.sa_handler = handler; sigemptyset(&action.sa_mask); action.sa_flags = 0; return sigaction(sig, &action, NULL); } void time_up(int sig) { puts("\nTime's up!"); raise(SIGINT); // 引发 SIGINT 信号,让程序调用 end_game() 显示最后得分 } void error(char *msg){ fprintf(stderr, "%s, %s\n", msg, strerror(errno)); exit(1); } int main(){ catch_signal(SIGALRM, time_up); catch_signal(SIGINT, end_game); srandom(time(0)); // 保证每次得到不同的随机值 while (1) { int a = random() % 11; //a,b 是 0-10 随机数 int b = random() % 11; char txt[4]; alarm(5); // 将闹钟信号设为 5s 后触发 // 只要能在 5s 内再次回到这个地方,定时器就会重置,闹钟信号也不会触发 printf("\nWhat is %i * %i? ", a, b); fgets(txt, 4, stdin); int answer = atoi(txt); if (answer == a*b) { score++; } else printf("\nWrong!Score:%i\n", score); } return 0; }
运行:
➜ intoC git:(master) ✗ ./test_sig What is 0 * 4? 4 Wrong!Score:0 What is 1 * 3? 3 What is 9 * 10? 90 What is 0 * 2? Time's up! Final score: 2 ➜ intoC git:(master) ✗ ./test_sig What is 5 * 2? 10 What is 8 * 7? ^C Final score: 1
3.6 小结
- system 用信号控制进程
- 进程常用信号来结束
- 进程收到信号后会进行信号处理器
- 大部分错误信号的默认处理器会终止程序
- 可以用
signation()
函数替换处理器 - 可以用
raise()
向自己发送信号 - 间隔定时器发送 SIGALRM 信号
- alarm() 函数设置间隔定时器
- 每个进程只能有一个定时器
- 不要同时使用 sleep() 和 alarm()
- kill 命令可以向进程发送信号
- kill -KILL <进程号> 一定能让你的程序上西天
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: C 语言 数据流和重定向
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论