Fork me on GitHub

Linux信号处理——自定义信号处理函数

信号是内核和进程之间通信的一种方式,信号是由内核产生,并发送给一个或一组进程的短消息,用不同特定的数字表示不同的信号,信号的作用是表示某种事件的发生。

信号简介

分类

  • 非实时不可靠信号,值为1-31
  • 实时的可靠信号,值为32-63

信号由内核生成,信号生成和事件的发生密切相关,可将事件发生源分为以下三类:

信号事件发生源:

  • 用户,如键入CTRL+C,终端驱动程序将通知内核产生信号发送到相应的进程
  • 内核,内核执行过程中,遇到非法指令和浮点数溢出等情况
  • 进程,一个进程调用kill函数向另一个进程发送信号,进行进程间通信

通常,LInux为每个信号定义了缺省的处理方式,但是用户可根据需要,对信号的处理方式进行重新定义。

信号的缺省处理方式包括

  • A 结束进程
  • B 忽略信号
  • C 结束进程并写入内核文件
  • D 停止进程
  • E 信号不能被捕获
  • F 信号不能被忽略
  • G 非POSIX信号

自定义信号处理函数

必须重新建立信号值和处理方式之间的对应关系,才能重新定义信号的处理方式。LInux提供signal和sigaction函数来实现信号的设置。signal和sigaction的区别在于signal不支持项信号处理函数传递数据。

注:SIGKILL和SIGSTOP不能被重定义或忽略。

signal函数原型:

// __sig为需设置的信号,__handler为新信号处理函数;失败返回SIG_ERR,否则成功。
//__handler为SIG_IGN忽略信号,SIG_DEL默认信号处理
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
      __THROW;

案例:使用signal重定义SIGINT处理函数

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main()
{
    void f(int);
    int i;
    signal(SIGINT,f);
    for(i=0;i<5;i++)
    {
        printf("hello\n");
        sleep(1);
    }
    return 0;
}

void f(int signum)
{
    printf("hello Linux\n");
}

sigaction函数原型

/*
signo需要处理的信号
act指向描述信号操作的结构
oact指向被替换操作的结构
成功返回0,否则返回-1
*/
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);

/*
sa_mask指定在信号处理过程中,何种信号被阻塞。缺省情况是当前信号被阻塞,以免信号处理函数被递归调用。
*/
struct sigaction
{
    void(*sa_handler)(int);                     //信号处理函数
    void(*sa_sigaction)(int,siginfo_t*,void *); //带参数的信号处理函数
    sigset_t sa_mask;                           //信号掩码
    int sa_flags;                               //设定信号处理相关行为
}

案例:sigaction定义信号SIGINT处理函数,并屏蔽其他信号

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int num=0;

void int_handle(int signum)
{
    printf("SIGINT:%d]n",signum);
    printf("int_handle called %d times\n",++num);
}

int main(void)
{
    static struct sigaction act;
    void int_handle(int);
    act.sa_handler = int_handle;
    sigfillset(&(act.sa_mask));
    sigaction(SIGINT,&act,NULL);
    while(1)
    {
        printf("i am sleepy..\n");
        sleep(1);
        if(num >=3)
        {   
            return 0;
        }
    }
}

信号集、信号屏蔽与阻塞

信号屏蔽就是临时阻塞信号被发送到某个进程,它包含一个被阻塞的信号集。当进程屏蔽某个信号时,内核将不发送该信号至屏蔽它的进程,直至该信号的屏蔽被解除。 信号集用于描述所有信号的集合。对于sigaction中的sa_mask字段,每一位对应一个信号,若某一位被设置为1,表示该位对应信号被屏蔽。

信号集定义及其操作函数

typedef struct
{
    unsigned long sig[2];
}sigset_t

int sigemptyset(sigset_t *set);     //清空信号集中所有信号
int sigfillset(sigset_t *set);      //在set信号集中加入linux支持的所有信号
int sigaddset(sigset_t *set,int signum);    //向信号集中加入signum信号
int sigdelset(sigset_t *set,int signum);    //从信号集中删除signum信号
int sigismember(const sigset_t *set, int signum)    //判断signum信号是否在信号集set中

每个进程定义一个信号掩码,该掩码对应一个信号集,该信号集中的所有信号在发送至进程后都将被阻塞。通过更改进程的信号掩码来阻塞或解除阻塞所选择的信号。以此来保护不希望由信号中断的临界代码。

信号阻塞函数sigprocmask

/*
how 如何修改信号掩码
    SIG_BLOCK 添加信号到进程屏蔽
    SIG_UNBLOCK将信号从进程屏蔽中删除
    SIG_SETMASK将set的值设定为新的信号掩码
set 指向设置信号列表
oldset指向之前的信号掩码列表
*/
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);

案例:阻塞SIGINT信号3秒后恢复

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{   
    sigset_t set;
    int count = 3;
    sigemptyset(&set);
    sigaddset(&set,SIGINT);
    sigprocmask(SIG_BLOCK,&set,NULL);
    while(count)
    {
        printf("don't disturb me (%d)\n",count--);
        sleep(1);
    }
    sigprocmask(SIG_UNBLOCK,&set,NULL);
    printf("you did not disturb me!!\n");
    return 0;
}

Comments