七、进程间通信-信号
1、信号
(1)概述
信号是软件中断,进程接收信号后做出相应响应,它提供了一种处理异步事件的方法。每个信号都有名字,这些名字以SIG开头,信号都定义在<signal.h>头文件中,并且都是正整数常量。
(2)怎么产生信号
-
硬件
- 执行非法指令
- 访问非法内存
- 驱动程序
-
软件
-
Ctrl + C:中断信号。
-
Ctrl + | :退出信号。
-
Ctrl + Z:停止信号。
-
kil命令:程序调用kill()函数。
-
(3)信号的处理方式
-
忽略:进程当信号从来没有发生过。
-
捕获:进程会调用相应的处理函数,进行相应的处理。
-
默认:使用系统默认处理方式处理信号。
(4)常用信号分析
信号名 | 信号编号 | 产生原因 | 默认处理方式 |
SIGHUP | 1 | 关闭中断 | 终止 |
SIGINT | 2 | ctrl+c | 终止 |
SIGQUIT | 3 | ctrl+\ | 终止+转储 |
SIGABRT | 6 | abort() | 终止+转储 |
SIGPE | 8 | 算术错误 | 终止 |
SIGKILL | 9 | kill -9 pid | 终止,不可捕获/忽略 |
SIGUSR1 | 10 | 自定义 | 忽略 |
SIGSEGV | 11 | 段错误 | 终止+转储 |
SIGUSR2 | 12 | 自定义 | 忽略 |
SIGALRM | 14 | alarm() | 终止 |
SIGTERM | 15 | kill pid | 终止 |
SIGCHLD | 17 | (子)状态变化 | 忽略 |
SIGSTOP | 19 | ctrl+z | 暂停,不可捕获/忽略 |
(5)设置信号处理方式
1 2 3 4 5 | #include<signal.h> typedef void (*sighandler_t)( int ); sighandler_t signal ( int signum,sighandler_t handler); |
- 参数:
-
signum: 要设置的信号
-
handler:SIG_IGN:忽略;SIG_DFL:默认
-
void (*sighandler_t)(int):自定义
-
- 返回值:
- 成功:返回上一次设置的handler
- 失败:SIG_ERR.
-
实例:CTRL+C触发SIGINT信号,处理信号回调函数
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 | #include <stdio.h> #include <signal.h> #include <stdlib.h> void signal_handler( int sig) { printf ( "\nthis signa number is %d\n" ,sig); if (sig == SIGINT) { printf ( "I have get SIGINT!\n\n" ); printf ( "The signal has been restored to the default processing mode!\n\n" ); /*恢复信号为默认情况*/ signal (SIGINT,SIG_DFL); } } int main( void ) { printf ( "\nthis is an alarm test function\n\n" ); /*设置信号处理的回调函数*/ signal (SIGINT,signal_handler); while (1) { printf ( "waiting for the SIGINT signal,please enter\"Ctrl+C\"...\n" ); sleep(1); } exit (0); return 0; } |
执行结果:
(6)kill函数:给其他进程发送特定的信号
1 2 3 4 | #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); |
- 参数:
- pid:进程id.
- sig:要发送的信号。
- 返回值:
- 成功:0
- 失败:-1
(7)raise函数:把信号发给进程自身
1 2 3 | #include <signal> int raise ( int sig); |
- 参数:
- sig:要发送的信号。
- 返回值:
- 成功:0
- 失败:非0
(8)kiill和raise使用实例
子进程使用rasie给自身进程发送SIGSTOP暂停信号,然后父进程通过kill发送SIGKILL给子进程杀死子进程。
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 | #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <sys/types.h> int main( void ) { pid_t pid; int ret; pid = fork(); if (pid<0) { printf ( "create process error\n" ); exit (1); } else if (pid == 0) { printf ( "child is waiting for SIGSTOP signal!!\n\n" ); /*子进程停在这里*/ raise (SIGSTOP); //给子进程本身发暂停信号,阻塞到这。 /*子进程没有机会运行到这里*/ printf ( "child won't run here forever" ); exit (0); } else { /*睡眠3s,让子进程先执行*/ sleep(3); /*发送SIGKILL信号杀死子进程*/ if ((ret=kill(pid,SIGKILL))==0) //kill给其他进程发信号 { printf ( "Parent kill %d!\n\n" ,pid); } /*等待子进程退出*/ wait(NULL); /*父进程退出运行*/ printf ( "parent exit!\n" ); exit (0); } return 0; } |
2、信号集处理函数
内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。综上:自定义信号集set(也为一个字,64位)通过信号集操作函数来改变信号屏蔽字mask,然后mask进一步来影响未决信号集,从而控制信号是否应该被屏蔽。后面我们只是研究常规信号,即1~31号!
(1)屏蔽信号集
- 手动
- 自动
(2)未处理信号集
信号如果被屏蔽,则记录在未处理信号集中。
- 非实时信号(1~31):不排队,只留一个。
- 实时信号(34~64):排队,保留全部。
(3)信号集相关
1 2 3 4 5 6 7 8 9 10 11 12 13 | sigset_t set; // typedef unsigned long sigset_t; 声明一个信号集变量set,信号集类型为sigset_t,为unsigned long(无符号长整型),64位。 int sigemptyset(sigset_t *set); //将set所指信号集清0 成功:0;失败:-1 int sigfillset(sigset_t *set); //将set所指信号集置1 成功:0;失败:-1 int sigaddset(sigset_t *set, int signum); //将某个信号加入信号集(即将其置1) 成功:0;失败:-1 int sigdelset(sigset_t *set, int signum); //将某个信号清出信号集(即将其置0) 成功:0;失败:-1 int sigismember( const sigset_t *set, int signum); 判断某个信号是否在信号集中(是否为1)返回值:在集合:1;不在:0;出错:-1 sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。 |
1 | int sigprocmask( int how, const sigset_t *set,sigset_t *oldset); |
- how:
- SIG_BLOCK:屏蔽某个信号(屏蔽集 | set)
- SIG_UNBLOCK:打开某个信号(屏蔽集 & (~set))
- SIG_SETMASK:屏蔽集 = set
- oldset:保存就的屏蔽集的值,NULL表示不保存
(4)实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <stdio.h> #include <signal.h> #include <stdlib.h> void signal_handler( int sig) { printf ( "hello\n" ); sleep(5); printf ( "world\n" ); } int main( void ) { /*设置信号处理的回调函数*/ signal (SIGINT,signal_handler); while (1); return 0; } |
执行结果:
由上面的结果,可知我们发送了一个SIGINT信号后,在回调函数里面打印helloworld,我们按Ctrl+C后执行hello,在这期间,多次按ctrl+C发送SIGINT信号,signal并没有每个信号都进行处理,如果他第一个信号没有处理完,就不会再响应其他信号。
由于SIGINT信号编号为2,是一种非实时信号,所以只保留一个信号响应。
我们发送编号为35的信号,此信号为实时信号,系统排队响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <stdio.h> #include <signal.h> #include <stdlib.h> void signal_handler( int sig) { printf ( "hello\n" ); sleep(5); printf ( "world\n" ); } int main( void ) { /*设置信号处理的回调函数*/ signal (35,signal_handler); while (1); return 0; } |
在一个终端执行程序,在另一个终端通过kill发送编号为35的信号给signalsets的进程,然后就打印出了hello world.
由于编号为35的信号是实时信号,排队处理,所以每个信号都被排队响应
如果我们想实时响应非实时信号,可将信号集的这一信号置为1,如SIGINT信号,修改代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <stdio.h> #include <signal.h> #include <stdlib.h> void signal_handler( int sig) { sigset_t set; sigemptyset(&set); //信号集清0 sigaddset(&set,SIGINT); //将SIGINT加入信号集 sigprocmask(SIG_UNBLOCK,&set,NULL); //使用新设置的信号集去修改信号屏蔽集 printf ( "hello\n" ); sleep(5); printf ( "world\n" ); } int main( void ) { /*设置信号处理的回调函数*/ signal (SIGINT,signal_handler); while (1); return 0; } |
执行结果如下:每个SIGINT都会被响应
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)