Unix/Linux编程:sigaction

sigaction

理论

NAME
       sigaction - 检查或者修改与指定信号相关联的处理动作
       			 - sa_sigaction新的信号安装机制,处理函数被调用的时候,不但可以得到信号编号,而且可以获悉被
       			   调用的原因以及产生问题的上下文的相关信息。

SYNOPSIS
       #include <signal.h>

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

DESCRIPTION
		sigaction() 系统调用用于更改进程在收到特定信号后采取的操作。
		
		sig 参数标识想要获取或改变的信号编号。该参数可以是除去 SIGKILL 和 SIGSTOP 之外的任何信号。

		act 指定新的信号处理方式.  它是一枚指针,指向描述信号新处置的数据结构。如果仅对信号的现有处置感兴趣,
		那么可将该参数指定为 NULL。

		oldact 参数是指向同一结构类型的指针,用来返回之前信号处置的相关信息。如果无意获取此类信息,那么可将
		该参数指定为 NULL。

		act 和 oldact 所指向的结构类型如下所示:

		struct sigaction {
		    void (*sa_handler)(int);     
		    void (*sa_sigaction)(int, siginfo_t *, void *);    
		    sigset_t sa_mask;  
		    int sa_flags;
		    // 已过时,POSIX不支持它,不应再使用。
		    void (*sa_restorer)(void);
		}

			sa_handler参数和signal()的参数handler相同,此参数主要用来对信号旧的安装函数signal()处理形式的支持
			
			sa_handler除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为SIG_IGN(忽略信号)。
			
			仅当sa_handler 是信号处理程序的地址时,亦即 sa_handler的取值在 SIG_IGN 和 SIG_DFL之外,才会对sa_mask 和
			sa_flags 字段(稍后讨论)加以处理。
			
			sa_handler 原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息。


			sa_sigaction原型是一个带三个参数,类型分别为intstruct siginfo *void *,返回类型为void的函数指针。
					第一个参数为信号值;
					第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;
					第三个参数没有使用。

			sa_sigaction指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。

			sa_mask 字段定义了一组信号,在调用由 sa_handler 所定义的处理器程序时将阻塞该组信号。当调用信号处理器程序时,会在
			调用信号处理器之前,将该组信号中当前未处于进程掩码之列的任何信号自动添加到进程掩码中。这些信号将保留在进程掩码中,直
			至信号处理器函数返回,届时将自动删除这些信号。

			利用 sa_mask 字段可指定一组信号,不允许它们中断此处理器程序的执行。此外,引发对处理器程序调用的信号将自动添加到进程信号
			掩码中。这意味着,当正在执行处理器程序时,如果同一个信号实例第二次抵达,信号处理器程序将不会递归中断自己。由于不会对遭阻
			塞的信号进行排队处理,如果在处理器程序执行过程中重复产生这些信号中的任何信号,(稍后)对信号的传递将是一次性的

			注意:sa_mask指定的信号阻塞的前提条件,是在由sigaction()安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。

			sa_flags 字段是一个位掩码,指定用于控制信号处理过程的各种选项。该字段包含的位如下(可以相或(|))
					
					SA_NOCLDSTOP 
						若 sig 为 SIGCHLD 信号,则当因接受一信号而停止或恢复某一子进程时,将不会产生此信号。
				
					SA_NOCLDWAIT 
						(始于 Linux 2.6)若 sig 为 SIGCHLD 信号,则当子进程终止时不会将其转化为僵尸

					SA_NODEFER 
							一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该
							信号处理函数运行时,内核将不会阻塞该信号
							
							SA_NOMASK历史上曾是 SA_NODEFER 的代名词。之所以建议使用后者,是因为 SUSv3 将其纳入规范。

					SA_ONSTACK 
							针对此信号调用处理器函数时,使用了由 sigaltstack()安装的备选栈

					SA_RESETHAND :如果置位并且捕获了信号,则将信号的配置重置为SIG_DFL,并且在进入信号处理程序时不会阻塞该信号。
					
							当捕获该信号时,会在调用处理器函数之前将信号处置重置为默认值(即 SIG_DFL)(默认情况下,信号处理器函数
							保持建立状态,直至进一步调用 sigaction()将其显式解除。)

							SA_ONESHOT 历史上曾是 SA_RESETHAND 的代名词,之所以建议使用后者,是因为 SUSv3将其纳入规范

					SA_RESTART 
							如果信号中断了进程的某个系统调用,则系统自动启动该系统调用

					SA_SIGINFO  
							当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。

							即sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信
							号处理函数中对这些信息的访问都将导致段错误。
					

sa_handler是用于处理信号的函数,sa_sigaction也是类似的函数;在有些架构中,这两者使用了union结构,因此最好不要同时使用;一般使用sa_handler函数即可。sg_mask是掩码,用于声明需要屏蔽的信号集。sa_flags是一个信号的标志集合,指明了处理信号的行为

SA_SIGINFO 标志

如果在使用 sigaction()创建处理器函数时设置了 SA_SIGINFO 标志,那么在收到信号时处理器函数可以获取该信号的一些附加信息。为获取这一信息,需要将处理器函数声明如下:

void handler(int sig, siginfo_t *siginfo, void *ucontext);

SYNOPSIS
	第 1 个参数 sig 表示信号编号
	第 2 个参数 siginfo 是用于提供信号附加信息的一个结构。该结构会与最后一个参数 ucontext 一起作用

因为上述信号处理器函数的原型不同于标准处理器函数,依照 C 语言的类型规则,将无法利用 sigaction 结构的 sa_handler 字段来指定处理器函数地址。此时需要使用另一个字段:sa_sigaction。

struct sigaction{
	union{
		void (*sa_handler)(int);
		void (*sa_sigaction)(int, siginfo_t *, void *)
	}__sigaction_handler;
	sigset_t sa_mask;
	int      sa_flags;
	void     (*sa_restore)(void);
};

#define sa_handler __sigaction_handler.sa_handler
#define sa_sigaction__sigaction_handler.sa_sigaction

结构sa_sigaction使用联合体来合并sa_sigaction和sa_handler。只所以使用联合体,是因为对sigaction()的特定调用只会用到其中的一个字段。

这里是使用SA_SIGINFO 创建信号处理函数的一个例子:

struct sigaction act;

sigemptyset(&act.sa_mask);
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO ;

if(sigaction(SIGINT, &act, NULL) == -1){
	perror("sigaction");
	exit(EXIT_FAILURE);
}

在以SA_SIGINFO 标志创建的信号处理器函数中,结构siginfo_t 是其第2 个参数,格式如下

 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;    /* Signal 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 */
               long     si_band;     /* Band event (was int in
                                        glibc 2.3.2 and earlier) */
               int      si_fd;       /* File descriptor */
               short    si_addr_lsb; /* Least significant bit of address
                                        (since Linux 2.6.32) */
           }

  • si_signo :需要为所有信号设置。内含引发处理器函数调用的信号编号—与处理器函数sig 参数的值相同。
  • si_errno :如果将该字段置为非 0 值,则其所包含为一错误号(类似 errno),标志信号的产生原因。Linux 通常不使用该字段。
  • si_code :需要为所有信号设置。如下表所示
  • si_pid :对于经由 kill()或 sigqueue()发送的信号,该字段保存了发送进程的进程 ID
  • si_uid :对于经由 kill()或 sigqueue()发送的信号,该字段保存了发送进程的真实用户 ID
  • si_addr :仅针对由硬件产生的 SIGBUG、SIGSEGV、SIGILL 和 SIGFPE 信号设置该字段。
    • 对于SIGBUS 和 SIGSEGV 而言,该字段内含引发无效内存引用的地址
    • 对于 SIGILL 和 SIGFPE信号,则包含导致信号产生的程序指令地址

以下各字段均属非标准的 Linux 扩展,仅当 POSIX 定时器(23.6 节)到期而产生信号传递时设置:

  • si_timerid :内含供内核内部使用的 ID,用以标识定时器。
  • si_overrun :设置该字段为定时器的溢出次数。

仅当收到 SIGIO 信号时,才会设置下面两个字段

  • si_band :该字段包含与 I/O 事件相关的“带事件”值。
  • si_fd :该字段包含与 I/O 事件相关的文件描述符编号。

仅当收到 SIGCHLD 信号时,才会对以下各字段进行设置。

  • si_status :该字段包含子进程的退出状态(当 si_code=CLD_EXITED 时)或者发给子进程的信号编号(即终止或停止子进程的信号编号)。
  • si_utime :该字段包含子进程使用的用户 CPU 时间
  • si_stime :该字段包含了子进程使用的系统 CPU 时间

在这里插入图片描述
在这里插入图片描述

SUSv4 定义了功用与 psignal()(20.8 节)相仿的 psiginfo()函数。函数 psiginfo()带有两个参数,分别是指向 siginfo_t 结构的指针和一个消息字符串。该函数在标准错误设备上输出字符串消息,接着显示描述于 siginfo_t 结构中的信号信息。glibc 自 2.10 版开始提供psiginfo()函数。glibc 实现会显示信号的描述信息及来源(根据 si_code 字段所示),对于某些信号,还会列出 siginfo_t 结构中的其他字段。函数 psiginfo()是 SUSv4 中的新丁,并非所有系统都予以支持。

ucontext:

  • 以 SA_SIGINFO 标志所创建的信号处理器函数,其最后一个参数是 ucontext,一个指向ucontext_t 类型结构(定义于<ucontext.h>)的指针。(因为 SUSv3 并未规定该参数的任何细节,所以将其定义为 void 类型指针。)
  • 该结构提供了所谓的用户上下文信息,用于描述调用信号处理器函数前的进程状态,其中包括上一个进程信号掩码以及寄存器的保存值,例如程序计数器(cp)和栈指针寄存器(sp)
  • 信号处理器函数很少用到此类信息

使用结构 ucontext_t 的其他函数有 getcontext()、makecontext()、setcontext()和 swapcontext(),分别对应的功能是允许进程去接收、创建、改变以及交换执行上下文。(这些操作有点类似于 setjmp()和 longjmp(),但更为通用。)可以使用这些函数来实现协程(coroutines),令进程的执行线程在两个(或多个)函数之间交替。SUSv3 规定了这些函数,但将它们标记为已废止。SUSv4 则将其删去,并建议使用 POSIX 线程来重写旧有的应用程序。glibc 手册页提供了关于这些函数的深入信息。

实践

注册信号处理函数

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);


int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    if (sigaction(SIGINT, &act, NULL) < 0)
        ERR_EXIT("sigaction error");

    for (; ;)
        pause();

    return 0;

}

void handler(int sig)
{
    printf("rev sig=%d\n", sig);
}

在这里插入图片描述

kill与sigaction

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

void show_handler(int signo)
{
    printf("recieve signo =%2d\n",signo);
}
int main(int argc,char ** argv)
{
    int i;
    struct sigaction act,oldact;
    act.sa_handler=show_handler;
   // act.sa_flags = SA_NOMASK;
    sigemptyset(&act.sa_mask);
    sigaction(SIGUSR1,&act,&oldact);
    sigaction(SIGINT,&act,&oldact);
    kill(getpid(),SIGUSR1);
    kill(getpid(),SIGUSR1);
    kill(getpid(),SIGINT);
    kill(getpid(),SIGINT);
    sleep(10);
    return 0;
}

在这里插入图片描述

用sigaction实现的signal函数

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

void  handler();
void (*Signal(int signo,void(*func)(int)))(int);

int main()
{
    int i;

    Signal(SIGALRM,handler);
    alarm(5);

    for(i=1;i<8;i++){
        printf("sleep is -----%d\n",i);
        sleep(1);
    }
    return 0;
}

void  handler()
{
    printf("hello\n");
}

void (*Signal(int signo,void(*func)(int)))(int){
    struct sigaction act, oact;

    sigemptyset(&act.sa_mask);
    act.sa_handler = func;
    //对除SIGALRM以外的所有信号,我们都有意尝试设置SA_RESTART标志,于是这些信号中断的系统调用都能重启动。*/
    if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;
#endif
    } else {
#ifdef SA_RESTART
        act.sa_flags |= SA_RESTART;  //如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
#endif
    }
    if(sigaction(signo,&act,&oact) < 0) {
        return SIG_ERR ;
    }
    return oact.sa_handler;
}

带外数据杀死子进程

#include <iostream>
#include <future>
#include <mutex>
#include <thread>
#include <zconf.h>
#include <ostream>
#include <sstream>
#include <signal.h>
#include <wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>

void sig_op(int signo, siginfo_t* info, void* context)
{
    printf("recv a sig = %d data = %d data = %d\n", signo, info->si_value.sival_int, info->si_int);
    kill( info->si_value.sival_int,SIGKILL);
}

int main(int argc,char** argv)
{
    struct sigaction act;
    memset( &act, 0, sizeof(act) );
    pid_t pid;

    pid = fork();
    if(pid == 0){
        printf("the child pid is %d\n",getpid());

        while (1){
            printf("------------\n");
        }
    }else if(pid > 0){
        sleep(2);
        act.sa_sigaction = sig_op;
        sigemptyset(&act.sa_mask);
        act.sa_flags= SA_SIGINFO | SA_RESETHAND ;
        if(sigaction(SIGUSR1,&act,NULL)==-1)
            printf("%s","install error~!\n");
        union sigval v;
        v.sival_int = pid;
        sigqueue(getpid(), SIGUSR1, v);
        printf("the parent pid is %d\n",getpid());
        waitpid(pid,NULL,0);
        printf("ok......");
    }

    getchar();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值