重点来啦!!!
对于Linux来说,信号实际上是软中断,许多重要的程序都需要处理信号。信号(Signals )是Unix系统中使用的最古老的进程间通信的方法,它为Linux提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式。
信号简介
信号名称
- 每一个信号都有自己的名字和编号,且都以SIG开头。
- 信号定义在
<signal.h>
头文件中,信号名都定义为正整数。 - 使用
kill -l
来查看信号的名字和编号,信号是从1开始编号的,不存在0号信号。 kill
对于信号0又特殊的应用。
使用kill -l查看signal
其中:
SIGHUP: 从终端上发出的结束信号;
SIGINT: 来自键盘的中断信号(Ctrl-C);
SIGQUIT:来自键盘的退出信号(Ctrl-\);
SIGFPE: 浮点异常信号(例如浮点运算溢出);
SIGKILL:该信号结束接收信号的进程;
SIGALRM:进程的定时器到期时,发送该信号;
SIGTERM:kill 命令发出的信号;
SIGCHLD:标识子进程停止或结束的信号;
SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号;
·······
信号处理
信号的处理有三种方法,分别是忽略、捕捉和默认动作
- 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程。
- 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,换句话就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
- 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
信号的产生
信号的来源分为硬件方式和软件方式。
硬件方式
-
当用户按下终端键时产生信号
-
硬件异常产生信号。这些事件通常由硬件检测到,并将其通知给Linux操作系统内核,然后内核生成相应的信号,并把信号发送给该事件发生时的正在运行的程序。
软件方式
-
用户在终端下调用kill命令向进程发送任务信号
-
进程调用kill或sigqueue函数发送信号
-
当检测到某种软件条件已经具备时发出信号,如SIGALARM信号
信号函数
信号的处理函数不止一种,这里我们介绍signal函数和sigaction函数
信号的发送函数我们这里介绍kill函数和sigqueue函数
所以kill对应signal,sigaction对应sigqueue,二者的不同之处是sigaction和sigaction能传递消息,在向进程传递信号的同时告诉进程本次发来的消息。
比如说A坐在沙发看电视,B突然敲门,向A发送中断信号,A应该放下手中的事,去处理B发来的信号,去给他开门,但是二者没有任何交流,不觉得怪怪的吗?而用sigqueue函数就会携带消息,B在敲门的同时会说:“快开门”,A收到消息,中断过来处理开门
初级函数
发送函数kill,处理函数signal
signal函数
NAME
signal - ANSI C signal handling
SYNOPSIS
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:
- signum:指定信号编号
- handler:
SIG_IGN:忽略该信号。
SIG_DFL:采用系统默认方式处理信号
自定义的信号处理函数指针
返回值:
成功:设置之前的信号处理方式
出错:‐1
signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函数;这个函数的返回值是一个函数指针。
#include <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>
void handler(int signum){
printf("signum = %d\n",signum);
}
int main(int argc, char** argv){
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
signal(SIGINT,handler);
while(1);
return 0;
}
如果系统通过 ctrl+c 产生了一个 SIGINT(中断信号),显然不是所有程序同时结束,那么,信号一定需要有一个接收者。对于处理信号的程序来说,接收者就是自己。
我们通过signal指定信号编号,自定义handler处理函数,得到参数后,我们打印传递来的SIGINT编号,前面我们说到信号名都定义为正整数,所以当我们摁下ctrl C之后,不会退出进程,而是不断地打印这个编号。
kill发送函数
NAME
kill - send signal to a process
SYNOPSIS
#include <sys/types.h>
#include <signal.h>
函数原型: int kill(pid_t pid, int sig);
参数:
-
pid
正数:要接收信号的进程的进程号
0:信号被发送到所有和pid进程在同一个进程组的进程
‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
pid通过程序getpid或者ps -aux获取 -
sig:信号编号
函数返回值:成功 0 出错 ‐1
#include <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>
int main(int argc, char** argv){
// int kill(pid_t pid, int sig);
int sig;
int pid;
if(argc != 3){
printf("\tUsage:\n");
printf("\t\t %s <Target_PID> <Signal_Number>\n", argv[0]);
}
pid = atoi(argv[1]);
sig = atoi(argv[2]);
// printf("pid = %d,sig = %d\n",pid,sig);
kill(pid,sig);
return 0;
}
这个demo传入dos命令作为main的参数,将pid和sig转为int,直接调用kill。
运行上个demo我们查看他的id号,并在kill这个demo中输入我们要杀死的进程编号。
简单的总结一下,我们通过 signal 函数注册一个信号处理函数,注册了SIGINT信号,随后主程序一直在while。
通过 kill 命令发送信号之前,我们需要先查看到接收者,通过 ps 命令查看了之前所写的程序的 PID,通过 kill 函数来发送。
对于已注册的信号,使用 kill 发送都可以正常接收到,但是如果发送了未注册的信号,则会使得应用程序终止进程。
注意:
- 当执行一个程序时,所有信号的状态都是系统默认或者忽略状态的。除非是 调用exec进程忽略了某些信号。exec 函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不会改变 。
- 当一个进程调动了 fork 函数,那么子进程会继承父进程的信号处理方式。
高级函数
发送函数sigqueue,处理函数sigaction
这里的处理和发送函数之所以高级,就是因为他们不但可以发送信号,还可以携带消息,这个条件是发送函数kill和处理函数signal满足不了的。那么下面就是简单介绍下这两个高级函数。。
sigaction处理函数
#include <signal.h> //函数头文件
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//函数原型
//struct sigaction 是sigaction函数的结构体参数
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
sigaction 是一个系统调用函数,层层嵌套,其中第一个参数是整型的signum信号编号,第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复(我们接下来的demo默认null)。
struct sigaction结构体中的 sa_mask 成员,设置在其的信号集中的信号,会在捕捉函数调用前设置为阻塞,并在捕捉函数返回时恢复默认原有设置。默认是阻塞的。在信号处理函数被调用时,操作系统会建立新的信号阻塞字,包括正在被递送的信号。因此,可以保证在处理一个给定信号时,如果这个种信号再次发生,那么他会被阻塞到对之前一个信号的处理结束为止。
flags设置为SA_SIGINFO 属性时,说明了信号处理程序带有附加信息,也就是会调用 sa_sigaction 这个函数指针所指向的信号处理函数。否则,系统会默认使用 sa_handler 所指向的信号处理函数。另外注意的是sa_sigaction 和 sa_handler 使用的是同一块内存空间,相当于 union,所以只能设置其中的一个,不能两个都同时设置。
void* 是接收到信号所携带的额外数据;而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
-
其中的成员很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。
-
发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。
sigaction 参数比较复杂,我们看看导图可能比较清晰一点。。。。
接收消息介绍完,再来看看发送消息是什么参数。
sigqueue发送函数
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
以下几点需要注意:
- 使用 sigaction 函数安装信号处理程序时,选择SA_SIGINFO 的标志。
- sigaction 结构体中的 sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据。
- sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。
sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。
但是,信号不能无限的排队,信号排队的最大值受到SIGQUEUE_MAX的限制,达到最大限制后,sigqueue 会失败,errno 会被设置为 EAGAIN。
下面我们实战看看demo是怎么接收发送消息的。。。
接收端
这是demorec.c 接收消息
#include <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
//void (*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum,siginfo_t *info,void *context){
printf("the signum is %d \n",signum);
if(context != NULL){
printf("the send pid is %d\n",info->si_pid);
printf("the data: %d\n",info->si_value);
}
}
int main(){
struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
printf("The received id is %d\n",getpid());
// int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
发送端
这是demosend.c 发送消息
#include <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv){
int sig;
int pid;
if(argc != 3){
printf("\tUsage:\n");
printf("\t\t %s <Target_PID> <Signal_Number>\n", argv[0]);
}else{
printf("the send pid is %d\n",getpid());
}
pid = atoi(argv[1]);
sig = atoi(argv[2]);
union sigval value;
/*union sigval {
int sival_int;
void *sival_ptr;
};
*/
value.sival_int = 100;
// int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid,sig,value);
printf("send signal successful\n");
return 0;
}
参考:https://www.jianshu.com/p/f445bfeea40a