信号也是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
#include 函数原型:
int kill(pid_t pid, int sig);
函数参数:
- pid:进程号
- sig:信号编号
其中,对于用户来说,信号更多的意义是实现异步操作,也就是捕捉信号,其核心就是编写“信号处理函数”
用来自定义信号处理方式
#include
typedef void (*sighandler_t)(int); //一个指向“传入参数是int 返回值是void的函数”,名为“sighandler_t”的指针
sighandler_t signal(int signum, sighandler_t handler);
#include
#include
void sighandler(int sig)
{
printf("get signal:%d\n",sig);
}
int main()
{
signal(2,sighandler);
while(1);
return 0;
}
可见,当捕获了信号2之后,在键盘输入“ctrl + c” 不再会执行默认动作来结束进程,而是被程序捕获,并执行了信号处理函数。
那么此时如何退出进程呢?可以使用刚刚提到的kill指令,先搜索pid号然后直接杀死进程:
同样,也可以在程序中直接调用kill函数来发送信号
编写一个C程序实现“向自己发送指定信号”的功能
#include
#include
#include
#include
#include
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,再执行kill
kill(pid,signum);
while(1);
return 0;
}
编译运行代码并指定第二个参数为2:
可见,虽然此时没有在键盘打出 “ctrl + c”,但是内部的代码调用了kill对自己发送了2号信号,所以同样实现了刚刚的效果。
学到了这里,一定会产生疑问:为什么信号会作为IPC中的一员,如果只是发送信号的话,不应该算是一种很有效的进程通讯方式,所以信号的处理还有更高级的方法,也就是带消息的高级信号操作,既然要带消息,那么就需要使用更高级的API函数来实现收发:
- 发送信号:使用sigqueue函数
- 接收信号:使用sigaction函数
实现带消息的高级信号操作,可以接收带消息的信号
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
int:第一个参数是信号编号
siginfo_t *:第二个参数是一个结构体,包括si_signo,si_code,si_int,si_value等等各种成员,其中si_signo和si_code必须实现
void *:第三个参数是一个指针,用来指示是否有消息存在,如果为NULL则无消息,否则就有消息
◆ SA_RESTART:使被信号打断的系统调用自动重新发起
◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号
◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程
◆ SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号
◆ SA_RESETHAND:信号处理之后重新设置为默认的处理方式
◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数(重要)
发送带消息的信号
成功使用sigqueue这个函数需要有两个前提:
- sigaction函数的第二个参数结构体中的sa_flags成员必须有“SA_SIGINFO”
- sigaction函数的第二个参数结构体中实现的是成员sa_sigaction函数而不是成员sa_handler函数
#include
int sigqueue(pid_t pid, int sig, const union sigval value);
value的类型是一个名为sigval的联合体,如果需要附带的消息是整型,则将数据存入成员“sival_int”;而如果消息类型是字符串,则存入成员“sival_ptr"
union sigval { int sival_int; void *sival_ptr; };
需求:编写两个C程序,一个使用sigaction来接收,一个使用sigqueue来发送,实现带消息的信号的通讯
#include
#include
#include
#include
#include
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;
}
#include
#include
#include
#include
#include
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
可见,此时没有接收到任何信号,一直阻塞
然后根据进程号,编译并运行signal4.c,将编号为2的信号发送到signal3.c对应的进程,并附带55的整型消息
此时再回看signal3.c的运行界面
可见,此时不仅收到了来自signal4.c的信号,还收到了附带的整型数据消息