sigaction函数

概述
信号是与一定的进程相联系的。也就是说,一个进程可以决定在进程中对哪些信号进行什么样的处理。例如,一个进程可以忽略某些信号而只处理其他一些信号;另外,一个进程还可以选择如何处理信号。总之,这些总与特定的进程相联系的。因此,首先要建立其信号和进程的对应关系,这就是信号的安装登记。

Linux主要有两个函数实现信号的安装登记:signal和sigaction。
signal在系统调用的基础上实现,是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32个非实时信号的安装;
sigaction是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与sigqueue系统调用配合使用。
当然,sigaction同样支持非实时信号的安装,sigaction优于signal主要体现在支持信号带有参数。

对于应用程序自行处理的信号来说,信号的生命周期要经过信号的安装登记、信号集操作、信号的发送和信号的处理四个阶段。
1-信号的安装登记指的是在应用程序中,安装对此信号的处理方法。
2-信号集操作的作用是用于对指定的一个或多个信号进行信号屏蔽,此阶段对有些应用程序来说并不需要。
3-信号的发送指的是发送信号,可以通过硬件(如在终端上按下Ctrl-C)发送的信号和软件(如通过kill函数)发送的信号。
4-信号的处理指的是操作系统对接收信号进程的处理,处理方法是先检查信号集操作函数是否对此信号进行屏蔽,如果没有屏蔽,操作系统将按信号安装函数中登记注册的处理函数完成对此进程的处理。

一、信号的种类
信号的名称是在头文件signal.h中定义的,信号都以SIG开头,常用的信号并不多,常用的信号如下:
SIGABRT 程序异常终止
SIGFPE 算术运算出错 如:除以0
SIGINT 中断信号 如:ctrl-C
SIGSEGV 非法访问不存在的内存单元
SIGTERM 发送给本程序的终止请求信号
SIGHUP 挂起信号
SIGQUIT 退出信号

二、sigaction函数
sigaction函数用来查询和设置信号处理方式,它是用来替换早期的signal函数。sigaction函数原型及说明如下:
函数头文件:

#include <signal.h>
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact)

函数说明:sigaction()会依参数signum指定的信号编号来设置该信号的处理函数
函数参数:
signum是指定信号的编号,除SIGKILL和SIGSTOP信号以外
act参数如下:
参数结构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);
}

① sa_handler:此参数和signal()的参数handler相同,此参数主要用来对信号旧的安装函数signal()处理形式的支持
② sa_sigaction:新的信号安装机制,处理函数被调用的时候,不但可以得到信号编号,而且可以获悉被调用的原因以及产生问题的上下文的相关信息。
③ sa_mask:用来设置在处理该信号时暂时将sa_mask指定的信号搁置
④ sa_restorer: 此参数没有使用
⑤ sa_flags:用来设置信号处理的其他相关操作,下列的数值可用,可用OR 运算(|)组合:
SA_NOCLDSTOP:如果参数signum为SIGCHLD,则当子进程暂停时并不会通知父进程
SA_ONESHOT/SA_RESETHAND:当调用新的信号处理函数前,将此信号处理方式改为系统预设的方式
SA_RESTART:被信号中断的系统调用会自行重启
SA_NOMASK/SA_NODEFER:在处理此信号未结束前不理会此信号的再次到来
SA_SIGINFO:信号处理函数是带有三个参数的sa_sigaction

oldact参数:如果参数oldact不是NULL指针,则原来的信号处理方式会由此结构sigaction返回
函数返回值:成功返回0,出错则返回-1,错误原因存于error中

三. 信号集操作函数
由于有时需要把多个信号当作一个集合进行处理,这样信号集就产生了,信号集用来描述一类信号的集合,Linux所支持的信号可以全部或部分的出现在信号集中。信号集操作函数最常用的地方就是用于信号屏蔽。比如有时候希望某个进程正确执行,而不想进程受到一些信号的影响,此时就需要用到信号集操作函数完成对这些信号的屏蔽。

信号集操作函数按照功能和使用顺序分为三类,分别为创建信号集函数,设置信号屏蔽位函数和查询被搁置(未决)的信号函数。
1-创建信号集函数只是创建一个信号的集合
2-设置信号屏蔽位函数对指定信号集中的信号进行屏蔽
3-查询被搁置的信号函数是用来查询当前“未决”的信号集。
信号集函数组并不能完成信号的安装登记工作,信号的安装登记需要通过sigaction函数或signal函数来完成。

查询被搁置的信号是信号处理的后续步骤,但不是必需的。由于有时进程在某时间段内要求阻塞一些信号,程序完成特定工作后解除对该信号阻塞,这个时间段内被阻塞的信号称为“未决”信号。这些信号已经产生,但没有被处理,sigpending函数用来检测进程的这些“未决”信号,并进一步决定对它们做何种处理(包括不处理)。

3.1 创建信号集函数
创建信号集函数有如下5个:
① sigemptyset:初始化信号集合为空。
② sigfillset:把所有信号加入到集合中,信号集中将包含Linux支持的64种信号。
③ sigaddset:将指定信号加入到信号集合中去。
④ sigdelset:将指定信号从信号集中删去。
⑤ sigismember:查询指定信号是否在信号集合之中。

图片.png

3.2.设置信号屏蔽位函数
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。调用函数sigprocmask可设定信号集内的信号阻塞或不阻塞。其函数原型及说明如下:


图片.png

3.3. 查询被搁置(未决)信号函数
sigpending函数用来查询“未决”信号。其函数原型及说明如下:


图片.png

3.4. 对信号集操作函数的使用方法
对信号集操作函数的使用方法和顺序如下:
① 使用signal或sigaction函数安装和登记信号的处理。
② 使用sigemptyset等定义信号集函数完成对信号集的定义。
③ 使用sigprocmask函数设置信号屏蔽位。
④ 使用sigpending函数检测未决信号,非必需步骤。

四. 应用
例子1:使用sigaction

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
int main()
{
    struct sigaction newact,oldact;
 
    /* 设置信号忽略 */
    newact.sa_handler = SIG_IGN; //这个地方也可以是函数
    sigemptyset(&newact.sa_mask);
    newact.sa_flags = 0;
    int count = 0;
    pid_t pid = 0;
 
    sigaction(SIGINT,&newact,&oldact);//原来的备份到oldact里面
 
    pid = fork();
    if(pid == 0)
    {
        while(1)
        {
            printf("I'm child.......pid=%d\n",getpid());
            sleep(1);
        }
        return 0;
    }
 
    while(1)
    {
        if(count++ > 2)
        {
            sigaction(SIGINT,&oldact,NULL);  //备份回来
            printf("Father will send message SIGKILL then kill child pid=%d\n",pid);
            kill(pid,SIGKILL); //父进程发信号,来杀死子进程
        }
 
        printf("I am father .......... \n");
        sleep(1);
    }
 
    return 0;
}

执行结果:

I am father .......... 
I'm child.......pid=18352
I am father .......... 
I'm child.......pid=18352
I am father .......... 
I'm child.......pid=18352
Father will send message SIGKILL then kill child pid=18352
I am father .......... 
Father will send message SIGKILL then kill child pid=18352
I am father .......... 
Father will send message SIGKILL then kill child pid=1835

例子2:使用sigaction
开启终端1,终端2 。
在终端1 中执行程序,终端2 发送 命令
source code:

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

void show_handler(int sig)
{
    printf("I got signal %d\n", sig);
    int j;
    for(j = 0; j < 5; j++) 
   {
        printf("j = %d\n", j);
        sleep(1);
    }
}
 
int main(void)
{
    int i = 0;
    struct sigaction act, oldact;
    act.sa_handler = show_handler;
    sigaddset(&act.sa_mask, SIGQUIT);                       //见注(1)
    act.sa_flags = SA_RESETHAND | SA_NODEFER; //见注(2)------>对参数修改调试
    //act.sa_flags = 0;                                                     //见注(3)
 
    sigaction(SIGINT, &act, &oldact);
    while(i <50)
    {
        sleep(1);
        printf("sleeping %d, pid=%d\n", i, getpid());
        i++;
    }
}

注:
(1)如果在信号SIGINT(Ctrl + c)的信号处理函数show_handler执行过程中,本进程收到信号SIGQUIT(Crt+),将阻塞该信号,直到show_handler执行结束才会处理信号SIGQUIT。

(2)SA_NODEFER 一般情况下, 当信号处理函数运行时,内核将阻塞<该给定信号 -- SIGINT>。但是如果设置了SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号。 SA_NODEFER是这个标记的正式的POSIX名字(还有一个名字SA_NOMASK,为了软件的可移植性,一般不用这个名字)
SA_RESETHAND 当调用信号处理函数时,将信号的处理函数重置为缺省值。 SA_RESETHAND是这个标记的正式的POSIX名字(还有一个名字SA_ONESHOT,为了软件的可移植性,一般不用这个名字)

(3)如果不需要重置该给定信号的处理函数为缺省值;并且不需要阻塞该给定信号

对参数修改调试:
上面程序使用如下code, 启用SA_RESETHAND | SA_NODEFER

 act.sa_flags = SA_RESETHAND | SA_NODEFER; //见注(2)------>对参数修改调试

调试结果1
终端一:

sleeping 5, pid=18449
I got signal 2
j = 0
j = 1
j = 2
j = 3
j = 4
sleeping 6, pid=18449
sleeping 7, pid=18449----> 之后停止运行

终端2:

kill -n SIGINT 18449  ---> main 函数捕获SIGINT ,即进入show_handler
kill -n SIGINT 18449----> 再次捕获SIGINT, 处理方式是默认方式,因为设定了SA_RESETHAND 

说明:如果终端2中的指令发送两次间隔时间很小(小于1 秒)
那么终端1中程序的运行,就是下面的打印,因为内核没有阻塞SIGINT ,因为设定了SA_NODEFER

sleeping 5, pid=18449
I got signal 2
j = 0  ---->之后停止运行

说明:
如果注掉//act.sa_flags = SA_RESETHAND | SA_NODEFER;, 那么程序采用系统模式方式,阻塞SIGINT ,这时如果终端2中的指令发送两次间隔时间很小(小于1 秒)【终端2中,指令是:kill -n SIGINT 18460】
那么终端1中程序的运行,就是下面的打印,可以看出阻塞的效果

sleeping 1, pid=18460
sleeping 2, pid=18460
sleeping 3, pid=18460
I got signal 2
j = 0
j = 1
j = 2
j = 3
j = 4
I got signal 2
j = 0
j = 1
j = 2
j = 3
j = 4
sleeping 4, pid=18460
sleeping 5, pid=18460
......
sleeping 48, pid=18460
sleeping 49, pid=18460  --->程序运行结束

other
通过 kill -l 命令查看到所有的信号
HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS

上面的信号是有顺序的,比如第1个是 HUP,第9个是 KILL,下面两种方式是等价的:
kill -1 PID 和 kill -HUP PID
kill -9 PID 和 kill -KILL PID

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容