信号
对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。
信号的概述
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIINT”、“SIGUP”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以 使用kill -l来查看信号 的名字以及序号,
信号是 从1开始编号的,不存在0号信号。kill对于信号0有特殊的应用。
信号的处理:
忽略信号:大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程。
捕捉信号:需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
系统默认动作:对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。
其实对于常用的 kill 命令就是一个发送信号的工具,kill -9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是“9-SIGKILL”,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。
kill -9 进程PID
kill -SIGKILL 进程PID
对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯的手段。
常用API
信号处理函数的注册
入门版:函数signal
高级版:函数sigaction
信号处理发送函数
1.入门版:kill
2.高级版:sigqueue
对于入门版的信号处理API的重点在于动作,但kill 函数发送的信号是无法携带数据。
对于高级版的信号处理API的重点是信号携带的信息
signal
函数原型:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数原型由两部分组成,一个是真实处理信号的函数,另一个是注册函数
注册函数:
sighandler_t signal(int signum, sighandler_t handler);
参数
signum :指出要设置处理方法的信号
handler :第二个参数handler是一个处理函数(一个函数地址),或者是
SIG_IGN:忽略参数signum所指的信号。
SIG_DFL:恢复参数signum所指信号的处理方法为默认值。
handler函数:
真实处理信号的函数:
typedef void (*sighandler_t)(int);
传中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。
捕捉信号 示例
键盘上敲ctrl c 的时候,会发出SIGINT的信号,被代码中的signal(SIGINT,handler)捕获了,这时候会进入handler进行函数处理,因为SIGINT对应的整数是2,会把2传到handler函数里面的signum参数里,进而在handler函数里面进行处理。
signalDemo1.c 捕获信息(使用kill里面的指令发送信号,供程序捕获信号)
//真实处理信号的函数
void handler(int signum)
{
printf("get signum=%d\n",signum);
switch(signum){
case 2:
printf("SIGINT\n");
break;
case 10:
printf("SIGUSR1\n");
break;
}
}
int main()
{
signal(SIGINT,handler);//信号处理函数的注册
signal(SIGUSR1,handler);//信号处理函数的注册
while(1);
return 0;
}
对于已注册的信号,使用 kill 发送都可以正常接收到,但是如果发送了未注册的信号,则会使得应用程序终止进程(结束程序 ,大部分信号默认都是使进程终止)
signalDemo1Ctrl.c (用程序来执行kill命令)
主要是将kill信号处理发送函数置于程序中,同时也可以使用system来实现kill
kill补充
函数原型
int kill(pid_t pid, int sig);
参数
pid: 指定的进程pid
sig:信号的编号
返回值:
成功返回0 ,出错返回-1
int main(int argc,char *argv[])
{
int signum=0;
int pid=0;
signum= atoi(argv[1]);//转为整型
pid= atoi(argv[2]);//转为整型
printf("pid=%d signum=%d \n",pid,signum);
kill(pid,signum);
// char cmd[128]={0};//设置system函数处理命令的大小
// sprintf(cmd,"kill -%d %d",signum,pid);
// system(cmd);
printf("send signal ok\n");
return 0;
}
注:
1、atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数。
2、sprintf指的是字符串格式化命令,
函数声明为 int sprintf(char *buffer, const char *format, [argument]…)
参数:
(1)buffer:是char类型的指针,指向写入的字符串指针;
(2)format:格式化字符串,即在程序中想要的格式;
(3)argument:可选参数,可以为任意类型的数据;
忽略信号 示例
#include<stdio.h>
#include <signal.h>
int main()
{
signal(SIGINT,SIG_IGN);//将SIGINT信号(ctrl+C、kill-2)忽略
signal(SIGUSR1,SIG_IGN);//将SIGUSR1信号(kill-10)忽略
while(1);
return 0;
}
注:SIG_IGN(忽略信号):为系统自带的宏
ctrl c 和 kill-10都被忽略 不起作用
sigaction
sigaction 是一个系统调用,可以用来查询或设置信号处理方式。
函数原型:
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
参数
signum:参数指出要捕获的信号类型(信号编号)。
act:参数指定新的信号处理方式,struct sigaction类型如果不为空说明需要对该信号有新的配置。
oldact:备份,如果不为空,那么可以对之前的信号配置进行备份。
struct 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:如果sa_flags中存在SA_SIGINFO标志,那么sa_sigaction将作为signum信号的处理函数。否则用sa_handler。
sa_handler
sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
sa_sigaction
void (*sa_sigaction)(int, siginfo_t *, void *);
参数说明
int 为信号值
void* 通常不使用这个参数
而struct siginfo_t这个结构体主要适用于记录接收信号的一些相关信息。
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; /* 存放信号携带的信息,si_value为共用体与发送信号时的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_pid 发送进程pid(可以知道是谁发的)
sigval_t si_value:si_value为共用体,与发送信号时的value共用体一致 (如下图在siginfo.h的定义)
sa_mask
sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
sa_mask 成员是一个信号集,在调用该信号捕捉函数之前,将需要堵塞的信号加入这个sa_mask,仅当信号捕捉函数正在执行时,才阻塞sa_mask中的信号,当从信号捕捉函数返回时进程的信号屏蔽字复位为原先值。
设置sa_mask的目的?
示例
// void (*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum,siginfo_t *info,void *context)
{
if(context!=NULL){//这个判断可有可无
int i;
for(i=0;i<5;i++){
printf("get signum %d \n",signum);
sleep(1);
}
}
}
int main()
{
pid_t pid;
pid=getpid();
printf("pro pid is :%d\n",pid);
struct sigaction act;
act.sa_sigaction=handler;
sigemptyset(&act.sa_mask);//sa_mask是一个临时信号集,将其清零(初始化)
sigaddset(&act.sa_mask,SIGUSR2);//将信号添加到信号集
sigaddset(&act.sa_mask,SIGINT);
sigaddset(&act.sa_mask,SIGUSR1);
act.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
sigaction(SIGINT,&act,NULL);
sigaction(SIGUSR2,&act,NULL);
while(1);
return 0;
}
设置sa_mask的时候
如果没有设置(没有添加信号集)
在调用信号处理程序时就能阻塞某些信号。注意仅仅是在信号处理程序正在执行时才能阻塞某些信号,如果信号处理程序执行完了,那么依然能接收到这些信号。
在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号,也就是说自己也被阻塞,除非设置了SA_NODEFER。
因此保证了在处理一个给定的信号时,如果这种信号再次发生,通常并不将它们排队,所以如果在某种信号被阻塞时它发生了5次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。
sa_flags
sa_flags 用来设置信号处理的其他相关操作:
SA_NODEFER: 当信号处理函数正在进行时,不堵塞对于信号处理函数自身信号功能。
SA_RESETHAND:当用户注册的信号处理函数被执行过一次后,该信号的处理函数被设为系统默认的处理函数。
SA_SIGINFO 提供附加信息,调用信号处理器程序时携带了额外参数,其中提供了关于信号的深入信息(如果设置,那么采用第二个参数sa_sigaction)
示例
sigqueue
在队列中向指定进程发送一个信号和数据
函数原型
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
参数
pid:发给谁,是目标进程的进程号
sig:发的是什么信号,是信号编号
value:发送的消息(int或者char*),是一个联合体,表示信号附带的数据,附带数据可以是一个整数也可以是一个指针,有如下形式:
union sigval value;//定义一个名字为value的sigval类型的联合体
注意事项
1、使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。
2、sigaction 结构体中的sa_sigaction成员提供了信号捕捉函数。如果实现的sa_handler成员,那么将无法获取额外携带的数据。
3、sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。
示例1(传整形数)
发送函数
发送函数
int main(int argc,char *argv[])
{
int pid;
int signum;
union sigval value;
printf("send pid is %d\n",getpid());
pid=atoi(argv[1]);
signum=atoi(argv[2]);
value.sival_int=99;
sigqueue(pid,signum,value);
return 0;
}
接受函数
接受函数
void handler(int signum,siginfo_t *info,void *context)
{
printf("get signum %d \n",signum);
if(context!=NULL){
printf("send pid=%d\n",info->si_pid);
printf("get data=%d \n",info->si_int);
printf("get data=%d \n",info->si_value.sival_int);
//info->si_int和info->si_value.sival_int是一样的--针对额外数据是int的时候
}
}
int main()
{
pid_t pid;
pid=getpid();
printf("pro pid is :%d\n",pid);
struct sigaction act;
act.sa_sigaction=handler;
act.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
sigaction(SIGINT,&act,NULL);
while(1);
return 0;
}
示例2 (传字符串)
在使用sigqueue函数与sigaction函数时,可以实现携带信息的信号的发送与接收。但是在实验的过程中发现,在父子进程,或者同一进程中发送一个带有字符串的信号是可以完成打印字符串的工作的,但是如果在不同进程之间打印传递的字符串便会出现段错误的情况,为了解决这个问题 可以使用共享内存的方法,即在发送信号时,将我们需要传递的字符串放入共享内存中,在接收信号时,再将我们传递的数据从共享内存中将字符串读取出来。
发送函数
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include<string.h>
#include<stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
int main(int argc,char *argv[])
{
int pid;
int signum;
union sigval value;
key_t key;
key=ftok(".",2);//获取键值
int shmid=shmget(key,1024*4,IPC_CREAT|0666);
//打开创建共享内存,并授予可读可写的权限
char Msg[]="abc!!!!";
value.sival_ptr=(void*)shmat(shmid,0,0);
//连接共享内存 ,sival_ptr是void类型,所以要进行强制转换
printf("send pid is %d\n",getpid());
pid=atoi(argv[1]);
signum=atoi(argv[2]);
strcpy((char*)value.sival_ptr,Msg);
//把数据写到共享内存
sigqueue(pid,signum,value);
sleep(3);
shmdt(value.sival_ptr);//断开连接
return 0;
}
接受函数
void handler(int signum,siginfo_t *info,void *context)
{
printf("get signum %d \n",signum);
if(context!=NULL){
key_t key;
key=ftok(".",2);//获取键值
int shmid=shmget(key,1024*4,0);//打开共享内存
info->si_value.sival_ptr=(void*)shmat(shmid,0,0);
//连接共享内存
printf("send pid is:%d\n",info->si_pid);
printf("get data=%s \n",(char*)info->si_value.sival_ptr);
shmdt(info->si_value.sival_ptr);//断开连接
shmctl(shmid,IPC_RMID,0);//关闭共享内存
}
}
int main()
{
pid_t pid;
pid=getpid();
printf("pro pid is :%d\n",pid);
struct sigaction act;
act.sa_sigaction=handler;
act.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
sigaction(SIGINT,&act,NULL);
while(1);
return 0;
}