信号作为一种进程间通信的机制,并没有实现进程间数据的传输和交换,只是用于对多个进程访问共享资源时进行有效的控制起到了一个时间锁的功能。
1、信号概述
1.1、在终端中查看常见的信号
在终端中输入kill -l可以查看linux系统中的所有信号,如下图所示:
信号的宏定义和编号定义在signal.h头文件中。终端中可以输入命令man 7 signal查看linux系统支持的信号详细定义,如下图
1.2、信号处理
信号作为一个进程间通信的机制,主要用于处理异步事件。 通常,如果没有信号发送到正在执行的进程中,进程会有如下3中处理信号的方法:
(1)、默认信号的处理方法。系统为每个信号都设置了默认的处理方法,通常为终止进程
(2)、捕捉信号。使进程执行制定的程序代码。
(3)、忽略信号,对信号不做任何处理,进程继续执行。
2、产生信号
信号的产生多总多样,主要有如下集中方法:
(1)、通过键盘终端产生,例如使用Ctrl+c可以产生SIGINT信号,使用ctrl+\产生SIGQUIT信号,ctrl+z产生SIGTSTP信号
(2)、通过终端中的kill命令产生信号,使用格式如下:
kill - 信号类型 进程号 进程号可以通过ps -aux获取
信号类型可以是信号的编号也可以是信号的宏定义。
(3)、调用系统函数向进程发送信号
kill() raise() alarm()
2.1、kill()函数
主要用于向指定的进程或进程组发送信号,该函数定义如下
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig)
参数pid为进程号或进程组号,参数sig为要发送的信号类型的编号。参数pid的取值范围不同,发送的信号触及的事件也是不同的,其取值范围如下:
pid=0:将信号发送到当前进程所在的进程组中的没一个进程
-1:将信号发送给除了init进程外的当前进程中有权发送的所有进程
<-1:将信号发送给进程组(-pid)中的没一个进程
如果pid为一个有效的进程或进程组号,信号将发送给pid所代表的进程或进程组。 如果参数sig为0,就没有信号可以发送,但会进行错误检查。
2.2、raise()函数
主要用于将信号发送给当前进程,改函数原型为
#include<signal.h>
int raise(int sig);
参数sig为发送的信号类型的编号,调用成功返回0,失败返回~0<=>kill(getpid(),sig)
2.3、alarm()函数
主要用于为发送的信号设定一个时间警告,是系统设定的时间按之后发送信号,原型为:
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
参数seconds为设定的时间,若为0,则设置的警告时钟则无效。alarm()函数安排在seconds之后,发送一个信号SIGALARM给进程。在默认情况下进程接受SIGALARM信号会终止运行,如果不希望终止可以咋进程捕获到该信号后修改默认的处理函数。
3、捕捉信号
在linux系统中可以使用signal(),sigaction()函数岁默认的处理方法进行修改。
3.1、signal()
用于修改某个信号的处理方法,定义如下
#include<signal.h>
typedef void(*sogjamd;er_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
参数signum代表信号类型的编号,参数handler代表只想信号新的处理方法的指针,如果指针只想一个函数,那么捕捉到signum信号时,会执行这个特殊函数处理信号,参数handler还可以设置为SIG_IGN,SIG_DFL.执行成功返回先前的信号,否则返回SIG_ERR
例、结合前面介绍的信号函数,产生不同的信号,通过signal函数捕捉信号。
程序代码如下:
#include<stdio.h>
#include<signal.h>
#include<stdarg.h>
void sigint(int sig);
void sigcont(int sig);
int main(){
char a[100];
if(signal(SIGINT,&sigint)==SIG_ERR)//修改SIGINT信号的处理方法为sigint
{
perror("sigint signal error!");
}
if(signal(SIGCONT,&sigcont)==SIG_ERR)//修改SIGCONT信号的处理方法
{
perror("sigcont error!");
}
if(signal(SIGQUIT,SIG_IGN))//修改SIGquit的处理方法为SIG_IGN
{
perror("sigqiut error!");
}
printf("current process is: %d\n\n",getpid());//获取当前进程的ID
while(1)
{
printf("input a:");
fgets(a,sizeof(a),stdin);//获取键盘输入的字符串
if(strcmp(a,"terminate\n")==0)//比较字符串a与terminate字符
{
raise(SIGINT);//若相同则将SIGINT信号发送给当前进程
}
else if(strcmp(a,"continue\n")==0)
{
raise(SIGCONT);
}
else if(strcmp(a,"quit\n")==0)
{
raise(SIGQUIT);
}
else if(strcmp(a,"game over\n"))
{
raise(SIGTSTP);
}
else
{
printf("your input is: %s\n\n",a);
}
}
return 0;
}
void sigint(int sig)
{
printf("SIGINT signal %d.\n",sig);
}
void sigcont(int sig)
{
printf("SIGCONT signal %d.\n",sig);
}
运行上述代码:
3.2、sigaction()
用于读取和修改制定信号的处理动作,定义如下:
#include<signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
参数signum表示要捕获的信号类型的编号,参数act,oldact都指向sigaction结构提类型的指针,参数act指定需要修改的新的处理动作,而该信号的原有动作保存到参数oldact指向的缓冲区中。
结构体类型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);
};
例:调用sigaction()修改SIGINT信号的处理方法,修改为显示是接受到的信号编号,并累加计时,知道接受到下一个信号。
程序代码如下:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
int i=0;
void new_handler(int sig)
{
printf("receive signal number is: %d\n",sig);
for(;i<100;i++){
printf("sleep2 %d\n",i);
sleep(1);
}
}
int main(void){
struct sigaction newact,oldact;
newact.sa_handler=new_handler;//处理方法
sigaddset(&newact.sa_mask,SIGCONT);//将SIGCONT 信号加到新的处理方法的屏蔽信号中
newact.sa_flags=SA_RESETHAND | SA_NODEFER;
printf("change SIGINT(2) signal___[ctrl+c]\n");
sigaction(SIGINT,&newact,&oldact);
while(1)
{
sleep(1);
printf("sleep1 %d\n",i);
i++;
}
return 0;
}
程序的运行效果如下:
4、信号的阻塞
能够实现信号阻塞的操作有3个系统函数,sigprocmask(),sigsuspend(),sigpending()
4.1、sigprocmask()
用于检测和改变进程的信号掩码,定义如下:
#include<signal.h>
#sigprocmask(int how,const sigset_t *newset,sigset_t *oldset);
param1表示修改信号屏蔽子的方式,2表示吧这个信号及设置为新的当前信号屏蔽字,如果为空,则不改变;3表示保存进程旧的信号屏蔽子,如果为空则不保存;
how取值:
SIG_BLOCK:将newsest所指向的信号集中所包含的信号加到当前的信号掩码
SIG_UNBLOCK: 将参数newset所指向的信号集中的信号从当前的信号掩码中移除。
SIG_SETAMASK:设置当前的信号掩码为参数newset所指向的信号集中所包含的信号
4.2、sigsuspend()
主要用于实现的是等待一个信号的到来定义如下:
#include<signal.h>
int sigsuspend(const sigset_t *mask);
西安书十一个结构提指针,只想一个信号集
4.3、sigpending()
用于在调用信号屏蔽的相关函数后,被屏蔽的信号对于调用进程是阻塞的,不能发送给调用进程,定义如下:
#include<signal.h>
int sigpending(sigset_t *set);
参数为一个指针,指向一个信号集
例:调用信号函数将SIGINT 阻塞
程序代码如下:
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
static void sig_handler(int signo)
{
printf("信号SIGINT被捕捉!\n");
}
int main()
{
sigset_t new, old, pend;
if(signal(SIGINT,sig_handler)==SIG_ERR)
{
perror("signal");
exit(1);
}
if(sigemptyset(&new)<0)
{
perror("sigemptyset");
}
if(sigaddset(&new,SIGINT)<0)
perror("sigaddset");
if(sigprocmask(SIG_SETMASK,&new,&old)<0)
{
perror("sigprocmask");
exit(1);
}
printf("SIGQUIt被阻塞!\n");
printf("试着按ctrl+c,程序会暂停5秒等待处理事件!\n");
sleep(5);
if(sigpending(&pend)<0)
perror("sigpending");
if(sigismember(&pend,SIGINT))
printf("信号SIGINT未决\n");
if(sigprocmask(SIG_SETMASK,&old,NULL)){
perror("sigprocmask");
exit(1);
}
printf("SIGINT已经被解开阻塞!\n");
printf("再试着按下ctrl+c \n");
sleep(5);
return 0;
}
该程序的运行结果如下: