Linux信号处理函数可中断么,linux信号处理

1. 信号概念

信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件)。信号是硬件中断的软件模拟(软中断)。每个信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD、SIGINT等,它们在系统头文件中定义,也可以通过在shell下键入kill –l查看信号列表,或者键入man 7 signal查看更详细的说明。

信号的生成来自内核,让内核生成信号的请求来自3个地方:

l         用户:用户能够通过输入CTRL+c、Ctrl+\,或者是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号;

l         内核:当进程执行出错时,内核会给进程发送一个信号,例如非法段存取(内存访问违规)、浮点数溢出等;

l         进程:一个进程可以通过系统调用kill给另一个进程发送信号,一个进程可以通过信号和另外一个进程进行通信。信号(signal)是一种进程间通信机制,

它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号)。

由进程的某个操作产生的信号称为同步信号(synchronous signals),例如除0;由象用户击键这样的进程外部事件产生的信号叫做异步信号。(asynchronous signals)。

进程接收到信号以后,可以有如下3种选择进行处理:

l         接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如连接到终端的进程,用户按下CTRL+c,将导致内核向进程发送一个SIGINT的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号,即终止进程的执行;

l         忽略信号:进程可以通过代码,显示地忽略某个信号的处理,例如:signal(SIGINT,SIGDEF);但是某些信号是不能被忽略的,

l         捕捉信号并处理:进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并且处理信号。

有两个信号既不能被忽略也不能被捕捉,它们是SIGKILL和SIGSTOP。即进程接收到这两个信号后,只能接受系统的默认处理,即终止线程。

2. signal信号处理机制

可以用函数signal注册一个信号捕捉函数。原型为:

#include

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

signal的第1个参数signum表示要捕捉的信号,第2个参数是个函数指针,表示要对该信号进行捕捉的函数,该参数也可以是SIG_DEF(表示交由系统缺省处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任何处理)。signal如果调用成功,返回以前该信号的处理函数的地址,否则返回SIG_ERR。

sighandler_t是信号捕捉函数,由signal函数注册,注册以后,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号捕捉函数。该函数只有一个参数,表示信号值。

示例:

1、  捕捉终端CTRL+c产生的SIGINT信号:

#include

#include

#include

#include

void SignHandler(int iSignNo)

{

printf("Capture sign no:%d\n",iSignNo);

}

int main()

{

signal(SIGINT,SignHandler);

while(true)

sleep(1);

return 0;

}

该程序运行起来以后,通过按 CTRL+c将不再终止程序的运行。应为CTRL+c产生的SIGINT信号已经由进程中注册的SignHandler函数捕捉了。该程序可以通过 Ctrl+\终止,因为组合键Ctrl+\能够产生SIGQUIT信号,而该信号的捕捉函数尚未在程序中注册。

2、  忽略掉终端CTRL+c产生的SIGINT信号:

#include

#include

#include

#include

int main()

{

signal(SIGINT,SIG_IGN);

while(true)

sleep(1);

return 0;

}

该程序运行起来以后,将CTRL+C产生的SIGINT信号忽略掉了,所以CTRL+C将不再能是该进程终止,要终止该进程,可以向进程发送SIGQUIT信号,即组合键CTRL+\

3、  接受信号的默认处理,接受默认处理就相当于没有写信号处理程序:

#include

#include

#include

#include

int main()

{

signal(SIGINT,DEF);

while(true)

sleep(1);

return 0;

}

3. sigaction信号处理机制

3.1. 信号处理情况分析

在signal处理机制下,还有许多特殊情况需要考虑:

1、  册一个信号处理函数,并且处理完毕一个信号之后,是否需要重新注册,才能够捕捉下一个信号;

2、  如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个同类型的信号,这时该怎么处理;

3、  如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个不同类型的信号,这时该怎么处理;

4、  如果程序阻塞在一个系统调用(如read(...))时,发生了一个信号,这时是让系统调用返回错误再接着进入信号处理函数,还是先跳转到信号处理函数,等信号处理完毕后,系统调用再返回。

示例:

#include

#include

#include

#include

int g_iSeq=0;

void SignHandler(int iSignNo)

{

int iSeq=g_iSeq++;

printf("%d Enter SignHandler,signo:%d.\n",iSeq,iSignNo);

sleep(3);

printf("%d Leave SignHandler,signo:%d\n",iSeq,iSignNo);

}

int main()

{

char szBuf[8];

int iRet;

signal(SIGINT,SignHandler);

signal(SIGQUIT,SignHandler);

do{

iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);

if(iRet<0){

perror("read fail.");

break;

}

szBuf[iRet]=0;

printf("Get: %s",szBuf);

}while(strcmp(szBuf,"quit\n")!=0);

return 0;

}

程序运行时,针对于如下几种输入情况(要输入得快),看输出结果:

1、  CTRL+c] [CTRL+c] [CTRL+c]

2、  [CTRL+c] [CTRL+\]

3、  hello [CTRL+\] [Enter]

4、  [CTRL+\] hello [Enter]

5、  hel [CTRL+\] lo[Enter]

针对于上面各种情况,不同版本OS可能有不同的响应结果。

3.2. sigaction信号处理注册

如果要想用程序控制上述各种情况的响应结果,就必须采用新的信号捕获机制,即使用sigaction信号处理机制。

函数原型:

#include

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

sigaction也用于注册一个信号处理函数。

参数signum为需要捕捉的信号;

参数 act是一个结构体,里面包含信号处理函数地址、处理方式等信息。

参数oldact是一个传出参数,sigaction函数调用成功后,oldact里面包含以前对signum的处理方式的信息。

如果函数调用成功,将返回0,否则返回-1

结构体 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);     // 保留,不要使用。

}

该结构体的各字段含义及使用方式:

1、字段sa_handler是一个函数指针,用于指向原型为void handler(int)的信号处理函数地址,       即老类型       的信号处理函数;

2、字段sa_sigaction也是一个函数指针,用于指向原型为:

void handler(int iSignNum,siginfo_t *pSignInfo,void *pReserved);

的信号处理函数,即新类型的信号处理函数。

该函数的三个参数含义为:

iSignNum :传入的信号

pSignInfo :与该信号相关的一些信息,它是个结构体

pReserved :保留,现没用

3、字段sa_handler和sa_sigaction只应该有一个生效,如果想采用老的信号处理机制,就应该让sa_handler指向正确的信号处理函数;否则应该让sa_sigaction指向正确的信号处理函数,并且让字段 sa_flags包含SA_SIGINFO选项。

4、字段sa_mask是一个包含信号集合的结构体,该结构体内的信号表示在进行信号处理时,将要被阻塞的信号。针对sigset_t结构体,有一组专门的函数对它进行处理,它们是:

#include

int sigemptyset(sigset_t *set);                                   // 清空信号集合set

int sigfillset(sigset_t *set);                                 // 将所有信号填充进set中

int sigaddset(sigset_t *set, int signum);               // 往set中添加信号signum

int sigdelset(sigset_t *set, int signum);                // 从set中移除信号signum

int sigismember(const sigset_t *set, int signum); // 判断signnum是不是包含在set中

例如,如果打算在处理信号SIGINT时,只阻塞对SIGQUIT信号的处理,可以用如下种方法:

struct sigaction act;

sigemptyset(&act.sa_mask);

sigaddset(&act_sa_mask,SIGQUIT);

sigaction(SIGINT,&act,NULL);

5、  字段sa_flags是一组掩码的合成值,指示信号处理时所应该采取的一些行为,各掩码的含义为:

掩码

描述

SA_RESETHAND

处理完毕要捕捉的信号后,将自动撤消信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号。该选项不符合一般的信号处理流程,现已经被废弃。

SA_NODEFER

在处理信号时,如果又发生了其它的信号,则立即进入其它信号的处理,等其它信号处理完毕后,再继续处理当前的信号,即递规地处理。如果 sa_flags包含了该掩码,则结构体sigaction的sa_mask将无效!

SA_RESTART

如果在发生信号时,程序正阻塞在某个系统调用,例如调用read()函数,则在处理完毕信号后,接着返回阻塞的系统调用。该掩码符合普通的程序处理流程,所以一般来说,应该设置该掩码,否则信号处理完后,阻塞的系统调用将会返回失败!

SA_SIGINFO

指示结构体的信号处理函数指针是哪个有效,如果sa_flags包含该掩码,则sa_sigactiion指针有效,否则是 sa_handler指针有效。

练习与验证:

针对于先前的5种输入情况,给下面代码再添加一些代码,使之能够进行如下各种形式的响应:

1 、[CTRL+c] [CTRL+c]时,第1个信号处理阻塞第2个信号处理;

2 、[CTRL+c] [CTRL+c]时,第1个信号处理时,允许递规地第2个信号处理;

3 、[CTRL+c] [CTRL+\]时,第1个信号阻塞第2个信号处理;

4 、read不要因为信号处理而返回失败结果。

#include

#include

#include

#include

int g_iSeq=0;

void SignHandlerNew(int iSignNo,siginfo_t *pInfo,void *pReserved)

{

int iSeq=g_iSeq++;

printf("%d Enter SignHandlerNew,signo:%d.\n",iSeq,iSignNo);

sleep(3);

printf("%d Leave SignHandlerNew,signo:%d\n",iSeq,iSignNo);

}

int main()

{

char szBuf[8];

int iRet;

struct sigaction act;

act.sa_sigaction=SignHandlerNew;

act.sa_flags=SA_SIGINFO;

//

sigemptyset(&act.sa_mask);

sigaction(SIGINT,&act,NULL);

sigaction(SIGQUIT,&act,NULL);

do{

iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);

if(iRet<0){

perror("read fail.");

break;

}

szBuf[iRet]=0;

printf("Get: %s",szBuf);

}while(strcmp(szBuf,"quit\n")!=0);

return 0;

}

3.3 中断与自动重启动

前面说过,信号是一种软件中断机制,这就产生了一个问题:如果信号到来时进城正在执行某个低速系统调用,系统应该怎么处理?是暂时阻塞系统调用返回,在信号处理程序完成后继续没完成的系统调用呢,还是让系统调用出错返回,同时把errno设置为EINTR。

对由signal()函数安装的信号处理程序,系统默认会自动重启动被中断的系统调用,而不是让它出错返回,所以应用程序不必针对慢速系统调用的errno,做EINTR检查,这就是自动重启动机制。

对由sigaction()函数安装的信号处理程序,系统的默认动作是不自动重启动被中断的系统调用。因此我们在使用sigaction()时需要自动重启动被中断的系统调用,就需要使用sigaction的SA_RESTART选项。

3.4. sigprocmask信号阻塞

函数sigaction中设置的被阻塞信号集合只是针对于要处理的信号,例如

struct sigaction act;

sigemptyset(&act.sa_mask);

sigaddset(&act.sa_mask,SIGQUIT);

sigaction(SIGINT,&act,NULL);

表示只有在处理信号SIGINT时,才阻塞信号SIGQUIT;

函数sigprocmask是全程阻塞,在sigprocmask中设置了阻塞集合后,被阻塞的信号将不能再被信号处理函数捕捉,直到重新设置阻塞信号集合。

原型为:

#include

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数how的值为如下3者之一:

a :SIG_BLOCK ,将参数2的信号集合添加到进程原有的阻塞信号集合中

b :SIG_UNBLOCK ,从进程原有的阻塞信号集合移除参数2中包含的信号

c :SIG_SET,重新设置进程的阻塞信号集为参数2的信号集

参数set为阻塞信号集。如果set为NULL,则不修改当前信号屏蔽字,而将当前信号集通过oldset返回;

参数oldset是传出参数,存放进程原有的信号集。

示例:

#include

#include

#include

#include

int g_iSeq=0;

void SignHandlerNew(int iSignNo,siginfo_t *pInfo,void *pReserved)

{

int iSeq=g_iSeq++;

printf("%d Enter SignHandlerNew,signo:%d.\n",iSeq,iSignNo);

sleep(3);

printf("%d Leave SignHandlerNew,signo:%d\n",iSeq,iSignNo);

}

int main()

{

char szBuf[8];

int iRet;

struct sigaction act;

act.sa_sigaction=SignHandlerNew;

act.sa_flags=SA_SIGINFO;

// 屏蔽掉SIGINT 信号,SigHandlerNew 将不能再捕捉SIGINT

sigset_t sigSet;

sigemptyset(&sigSet);

sigaddset(&sigSet,SIGINT);

sigprocmask(SIG_BLOCK,&sigSet,NULL);

//

sigemptyset(&act.sa_mask);

sigaction(SIGINT,&act,NULL);

sigaction(SIGQUIT,&act,NULL);

do{

iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);

if(iRet<0){

perror("read fail.");

break;

}

szBuf[iRet]=0;

printf("Get: %s",szBuf);

}while(strcmp(szBuf,"quit\n")!=0);

return 0;

}

4. 用程序发送信号

4.1. kill信号发送函数

原型为:

#include

#include

int kill(pid_t pid, int sig);

参数pid为将要接受信号的进程的pid。对kill()的pid,有如下描述:

pid > 0 将信号发送给ID为pid的进程

pid == 0 将信号发送给与发送进程属于同意个进程组的所有进程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值