信号也是IPC中的一种,是和管道,消息队列,共享内存并列的概念。
本文参考:
Linux中的信号_linux中信号_wolf鬼刀的博客-CSDN博客
Linux系统编程(信号处理 sigacation函数和sigqueue函数 )_花落已飘的博客-CSDN博客
Linux的sigqueue函数_linux sigqueue_QtHalcon的博客-CSDN博客
概念
信号是进程之间异步通知的一种方式,属于软中断;
可以使用“kill -l”来查看系统定义的信号列表:
所以,当在终端输入“crtl + c”时,其实就是调用了2号SIGINT,使用信号机制停止了一个程序。
- 从图中可以看到每个信号都有一个编号和宏定义的名称,这些宏都可以在signal.h中找到
- 注意并不是一共有64个信号,自己仔细看,共有62种信号
- 31号信号之前都是不可靠信号,也是非实时信号
- 编号34以上的是实时信号,可靠信号,各种信号各自在什么条件下产生什么默认的动作都可以在signal(7)中查看
同时kill命令还可以用来执行命令,例如一个进程的PID号为1234,则可以使用“kill 9 1234”或“kill SIGKILL 1234” 来杀死这个进程。
另外,kill不仅可以使用在命令窗口,kill还可以作为一个API
kill函数 : 给一个指定的进程发送一个指定的信号
包含的头文件:
#include <sys/types.h> #include <signal.h>
函数原型:
int kill(pid_t pid, int sig);
函数参数:
- pid:进程号
- sig:信号编号
信号的处理方式
- 忽略信号(SIGKILL和SIGSTOP无法被忽略)
- 执行给信号的默认动作
- 提供一个信号处理函数,要求用户在处理该信号时切换到用户态去执行处理函数,即捕捉信号
其中,对于用户来说,信号更多的意义是实现异步操作,也就是捕捉信号,其核心就是编写“信号处理函数”
信号处理的API函数及应用
signal函数
用来自定义信号处理方式
需要包含的库
#include <signal.h>
函数原型
typedef void (*sighandler_t)(int); //一个指向“传入参数是int 返回值是void的函数”,名为“sighandler_t”的指针sighandler_t signal(int signum, sighandler_t handler);
函数参数
- signum:信号编号
- handler:函数指针,指向信号处理函数;也可以使用宏,比如使用“SIG_IGN”,则忽略信号
使用举例1
signal1.c:
#include <signal.h>
#include <stdio.h>void sighandler(int sig)
{printf("get signal:%d\n",sig);}int main()
{signal(2,sighandler);while(1);return 0;
}
可见,当捕获了信号2之后,在键盘输入“ctrl + c” 不再会执行默认动作来结束进程,而是被程序捕获,并执行了信号处理函数。
那么此时如何退出进程呢?可以使用刚刚提到的kill指令,先搜索pid号然后直接杀死进程:
使用举例2
同样,也可以在程序中直接调用kill函数来发送信号
signal2.c:
编写一个C程序实现“向自己发送指定信号”的功能
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>void sighandler(int sig)
{printf("get signal:%d\n",sig);}int main(int argc,char **argv)
{int signum;pid_t pid;signum = atoi(argv[1]); //由于从键盘接收的是字符串,所以要使用“atoi”函数将ASCII码转为整型数pid = getpid();signal(2,sighandler); //为了实现效果,一定要先执行signal,再执行killkill(pid,signum);while(1);return 0;
}
编译运行代码并指定第二个参数为2:
可见,虽然此时没有在键盘打出 “ctrl + c”,但是内部的代码调用了kill对自己发送了2号信号,所以同样实现了刚刚的效果。
学到了这里,一定会产生疑问:为什么信号会作为IPC中的一员,如果只是发送信号的话,不应该算是一种很有效的进程通讯方式,所以信号的处理还有更高级的方法,也就是带消息的高级信号操作,既然要带消息,那么就需要使用更高级的API函数来实现收发:
- 发送信号:使用sigqueue函数
- 接收信号:使用sigaction函数
sigaction函数
实现带消息的高级信号操作,可以接收带消息的信号
需要包含的库
#include <signal.h>
函数原型
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
函数参数
- signum:信号编号
- act:指定新的信号处理方式
- oldact:输出先前信号的处理方式(如果不为NULL的话)
struct sigaction结构体介绍(sigaction函数的第二个参数)
struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
}
- sa_handler:此参数和signal()的参数handler相同,代表新的信号处理函数,如果使用这个成员参数,那就和signal函数无异了
- sa_sigaction:带消息的信号处理函数
int:第一个参数是信号编号
siginfo_t *:第二个参数是一个结构体,包括si_signo,si_code,si_int,si_value等等各种成员,其中si_signo和si_code必须实现
void *:第三个参数是一个指针,用来指示是否有消息存在,如果为NULL则无消息,否则就有消息
- sa_mask:用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置,可以理解为处理信号的时候释放阻塞(默认阻塞)
- sa_flags:用来设置信号处理的其他相关操作,可以是以下值的“按位或”组合:
◆ SA_RESTART:使被信号打断的系统调用自动重新发起
◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号
◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程
◆ SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号
◆ SA_RESETHAND:信号处理之后重新设置为默认的处理方式
◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数(重要)
- re_restorer:是一个已经废弃的数据域,不使用
sigqueue函数
发送带消息的信号
成功使用sigqueue这个函数需要有两个前提:
- sigaction函数的第二个参数结构体中的sa_flags成员必须有“SA_SIGINFO”
- sigaction函数的第二个参数结构体中实现的是成员sa_sigaction函数而不是成员sa_handler函数
需要包含的库
#include <signal.h>
函数原型
int sigqueue(pid_t pid, int sig, const union sigval value);
函数参数
- pid:目标进程的进程号
- sig:要发送的信号的编号
- value:要附带发送的消息
value的类型是一个名为sigval的联合体,如果需要附带的消息是整型,则将数据存入成员“sival_int”;而如果消息类型是字符串,则存入成员“sival_ptr"
union sigval {int sival_int;void *sival_ptr; };
使用sigaction和sigqueue的实操演示
需求:编写两个C程序,一个使用sigaction来接收,一个使用sigqueue来发送,实现带消息的信号的通讯
signal3.c:(接收带消息的信号)
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>void sighandler(int sig, siginfo_t *info, void *context)
{printf("get signal:%d\n",sig);if(context != NULL){printf("get data = %d\n",info->si_int);//printf"(get data = %d\n",info->si_value.sival_int);}}int main()
{struct sigaction act;act.sa_sigaction = sighandler;act.sa_flags = SA_SIGINFO;sigaction(2,&act,NULL);while(1);return 0;
}
signal4.c:(发送带消息的信号)
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc,char **argv)
{int signum;pid_t pid;int data;pid = atoi(argv[1]);signum = atoi(argv[2]);data = atoi(argv[3]);union sigval value;value.sival_int = data; sigqueue(pid,signum,value);printf("signal no.%d has been sent to pid %d, context:%d\n",signum,pid,data);return 0;
}
实现效果:
首先编译并运行signal3.c
可见,此时没有接收到任何信号,一直阻塞
此时新开一个窗口先查询到这个signal3.c的进程号
然后根据进程号,编译并运行signal4.c,将编号为2的信号发送到signal3.c对应的进程,并附带55的整型消息
此时再回看signal3.c的运行界面
可见,此时不仅收到了来自signal4.c的信号,还收到了附带的整型数据消息