LINUX信号处理(sigaction信号捕获函数:struct sigaction)

信号概述

何为信号:信号就是由用户、系统或进程发送给目标进程的信息,以通知目标进程中某个状态的改变或是异常。

信号产生:总体来说,其产生的条件有两种,分别是:硬件和软件原因,又称为:硬中断和软中断。可细分为如下几种原因:

①系统终端Terminal中输入特殊的字符来产生一个信号,比如按下:ctrl+\会产生SIGQUIT信号。

②系统异常。比如访问非法内存和浮点数异常。

③系统状态变化。如设置了alarm定时器,当该定时器到期时候会引起SIGVTALRM信号。

④调用了kill命令或是kill函数。

include <stdio.h>
include <stdlib.h>
include <signal.h>
include <unistd.h>
using namespace std;
void sig_handler(int signum)
{
    if(0 > signum)
    {
    fprintf(stderr,"sig_handler param err. [%d]\n",signum);
    return;
    }
    if(SIGINT == signum)
    {
        printf("Received signal [%s]\n",SIGINTsignum?"SIGINT":"Other");
    }
    if(SIGQUIT == signum)
    {
        printf("Received signal [%s]\n",SIGQUITsignum?"SIGQUIT":"Other");
    }

    return;
}

int main(int argc,char **argv)
{
    printf("Wait for the signal to arrive.\n ");

    /*登记信息*/
    signal(SIGINT,sig_handler);
    signal(SIGQUIT,sig_handler);

    pause();
    pause();

    signal(SIGINT,SIG_IGN);
    return 0;
}

程序运行后会一直等待用户的输入,当在终端按下ctrl+c时候会打印 Received signal [SIGINT]
说明捕获到了SIGINT信号,接着程序继续等待,当按下ctrl+\时候会打印Received signal [SIGQUIT],表明捕获到了SIGQUIT信号:

头文件<signal.h>

头文件<signal.h>为处理各种各样的信号声明了一个类型和两个函数,并且定义了几个宏。

1、类型:sig_atomic_t

sig_atomic_t是 int 类型,在信号处理程序中作为变量使用。它是一个对象的整数类型,该对象可以作为一个原子实体访问,即使存在异步信号时,该对象可以作为一个原子实体访。

2、宏:

SIG_DFL  默认的信号处理。

SIG_ERR  表示一个信号错误。

SIG_IGN  表示忽略信号。

有效的信号包括:

SIGABRT  异常终止,如调用abort()。

SIGFPE  算术运算出错,如除数为0或溢出。

SIGILL  非法函数映象,如非法指令。

SIGINT  交互式信号,如中断。

SIGSEGV  非法访问存储器,如访问不存在的内存单元。

SIGTERM  发送给本程序的终止请求信号。


 

一、函数结构

  1. #include <signal.h>

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

参数:

  • 参数1:要捕获的信号
  • 参数2:接收到信号之后对信号进行处理的结构体
  • 参数3:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来做备份)。如果不需要备份,此处可以填NULL

返回值:

  • 成功时:返回0
  • 出错时:返回-1,并将errno设置为指示错误

与signal的不同,有哪些新功能?

  • signal只能捕获信号,对信号进行处理。但是不能获取信号的其它信息
  • sigaction可以使用sigaction结构体的sa_handler函数对信号进行处理(此处等同于signal函数),也可以使用sa_sigaction函数查看信号的各种详细信息
  • 并且sigaction函数还可以通过sa_mask、sa_flags对信号处理时进行很多其他操作

二、struct sigaction结构体

  1. struct sigaction {

  2. void (*sa_handler)(int);

  3. void (*sa_sigaction)(int, siginfo_t *, void *);

  4.  
  5. sigset_t sa_mask;

  6. int sa_flags;

  7.  
  8. void (*sa_restorer)(void);

  9. };

sa_handler成员:

  • 对捕获的信号进行处理的函数,函数参数为sigaction函数的参数1信号(概念上等同于单独使用signal函数)
  • 也可以设置为后面两个常量:常数SIG_IGN(向内核表示忽略此信号)或是常数SIG_DFL(表示接到此信号后的动作是系统默认动作)

sa_mask成员:

  • 功能:sa_mask是一个信号集,当接收到某个信号,并且调用sa_handler函数对信号处理之前,把该信号集里面的信号加入到进程的信号屏蔽字当中,当sa_handler函数执行完之后,这个信号集中的信号又会从进程的信号屏蔽字中移除
  • 为什么这样设计??这样保证了当正在处理一个信号时,如果此种信号再次发生,信号就会阻塞。如果阻塞期间产生了多个同种类型的信号,那么当sa_handler处理完之后。进程又只接受一个这种信号
  • 即使没有信号需要屏蔽,也要初始化这个成员(sigemptyset()),不能保证sa_mask=0会做同样的事情
  • sigset_t数据类型见文章:https://blog.csdn.net/qq_41453285/article/details/89228297

sa_restorer成员:

  • 已经被抛弃了,不再使用

sa_flags成员:

  • 指定了对信号进行哪些特殊的处理
SA_INTERRUPT

由此信号中断的系统调用不自动重启动

SA_NOCLDSTOP

若signo是SIGCHLD:

当一子进程停止(暂停时)时(作业控制), 不产生此信号

当一子进程终止时,仍旧产生此信号

若已设置此标志,则当停止的进程继续运行时,作为XSI扩展,不产生SIGCHLD信号

(参照的下面的SA_NOCLDWAIT选项)

SA_NOCLDWAIT

若signo是SIGCHLD,则当调用进程的子进程终止时, 不创建僵死进程

若调用进程在后面调用wait,则阻塞到它所有子进程都终止,此时返回-1,errno设置为ECHILD

SA_NODEFER

当捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号(除非sa_mask成员包括了此信号)

注意,此种类型的操作对应于早期的不可靠信号

SA_NOMASK 同SA_NODEFER
SA_ONSTACK 若用sigaltstack已声明了一替换栈,则此信号递送给替换栈上的进程
SA_STACK 同SA_ONSTACK
SA_RESETHAND

在此信号捕捉函数的入口处,将此信号的处理方式重置为SIG_DFL,并清除SA_SIGINFO标志

注意,此种类型的信号对应于早期的不可靠信号。但是不能重置SIGILL和SIGTRAP信号的配置

设置此标志使sigaction的行为如同设置了SA_NODEFER标志

SA_ONESHOT 同SA_RESETHAND
SA_RESTART 由此信号中断的系统调用自动重启动
SA_SIGINFO 此选项对信号处理程序提供了附加信息:一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针
  • 关于SA_INTERRUPT、SA_RESTART说明:
    • 某些早期系统(如SunOS)定义了SA_INTERRUPT标志,这些系统的默认方式是重新启动被中断的系统调用,而制定此标志则使系统调用被中断后不再重新启动。Linux定义SA_INTERRUPT标志,以便与使用该标志的应用程序兼容。但是,如若信号处理程序是用sigaction设置的,那么其默认方式是不重新启动系统调用
    • Single UNIX Specifiaction的XSI扩展规定,除非说明了SA_RESTART标志,否则sigaction函数不再重启动被中断的系统调用
    • 关于系统调用中断请参阅:https://blog.csdn.net/qq_41453285/article/details/89216990

sa_sigaction成员:

  • 当sa_flags成员是SA_SIGINFO标志时,就调用此函数,可以来获取该信号的很多详细信息(而不是用来对信号进行处理)
  • 参数1:参数为sigaction函数的参数1信号
  • 参数2:一个结构体包,含了信号产生的各种详细信息
    • si_signo:信号的值
    • si_code:信号代码
    • si_pid:信号来自于哪一个进程(哪个进程发送来的)
    • si_uid:信号来自于哪个用户(发送来的进程的用户)
    • si_value(重点):传递的信息。应用程序在传递信号时,可以在si_value.sival_int传递一个整型或者si_value.sival_ptr传递一块内存(下面会有一个案例)

  • 若信号时SIGCHLD:则将设置si_pid、si_status、si_uid
  • 若信号时SIGBUS、SIGILL、SIGFPE、SIGSEGV:则si_addr包含造成故障的根源地址(该地址可能并不正确)
 
  1. siginfo_t {

  2. int si_signo; /* Signal number */

  3. int si_errno; /* An errno value */

  4. int si_code; /* Signal code */

  5. int si_trapno; /* Trap number that caused

  6. hardware-generated signal(unused on most architectures) */

  7. pid_t si_pid; /* Sending process ID */

  8. uid_t si_uid; /* Real user ID of sending process */

  9. int si_status; /* Exit value or signal */

  10. clock_t si_utime; /* User time consumed */

  11. clock_t si_stime; /* System time consumed */

  12. sigval_t si_value; /* Signal value */

  13. int si_int; /* POSIX.1b signal */

  14. void *si_ptr; /* POSIX.1b signal */

  15. int si_overrun; /* Timer overrun count; POSIX.1b timers */

  16. int si_timerid; /* Timer ID; POSIX.1b timers */

  17. void *si_addr; /* Memory location which caused fault */

  18. long si_band; /* Band event (was int inglibc 2.3.2 and earlier) */

  19. int si_fd; /* File descriptor */

  20. short si_addr_lsb; /* Least significant bit of address(since Linux 2.6.32) */

  21. }

  •  参数3:此参数为void*类型,可以自己传入一些参数进去,但是一般强转为ucontext_t结构体类型使用,该结构体标识信号传递时进程的上下文。下面是ucontext_t结构体内的一些部分成员
 
  1. ucontext_t *uc_link; /* pointer to context resumed when this context returns */

  2. sigset_t uc_sigmask; /* signals blocked when this context is active */

  3. stack_t uc_stack; /* stack used by this context */

  4. mcontext_t uc_mcontext; /* machine-specific representation of saved context */

  5.  
  6. //uc_stack字段描述了当前上下面使用的栈,至少包括下列成员

  7. void *ss_sp; /* stack base or pointer */

  8. size_t ss_size; /* stack size */

  9. int ss_flags; /* flags */

  • sa_handler和sa_sigaction函数一次只能使用其中一个
    • 如果只是想处理信号,但是不获取信号信息,就使用sa_handler函数
    • 如果想要处理信号,并且获取信号的各种信息,就使用sa_sigaction函数

三、演示案例

  • 案例一:处理SIGQUIT信号 
  1. #include<stdio.h>

  2. #include<stdlib.h>

  3. #include<signal.h>

  4. void myHandler(int sig);

  5. int main(int argc,char *argv[])

  6. {

  7. struct sigaction act, oact;

  8.  
  9. act.sa_handler = myHandler;

  10. sigemptyset(&act.sa_mask);

  11. act.sa_flags = 0;

  12. sigaction(SIGQUIT, &act, &oact);

  13. while (1)

  14. {

  15. printf("running...\n");

  16. pause();

  17. }

  18. }

  19. void myHandler(int sig)

  20. {

  21. printf("I got signal: %d.\n", sig);

  22. }

  • 按下ctrl+\之后,就会调用myHandler函数 

  • 案例二:接到SIGUSR1信号时,获取其信息
  1. #include<stdio.h>

  2. #include<stdlib.h>

  3. #include<signal.h>

  4. #include<unistd.h>

  5.  
  6. void func(int signo, siginfo_t *info, void *p)

  7. {

  8. printf("signo=%d\n",signo);

  9. printf("sender sigal pid=%d\n",info->si_pid);

  10. }

  11.  
  12. int main(int argc,char *argv[])

  13. {

  14. struct sigaction act, oact;

  15.  
  16. sigemptyset(&act.sa_mask);

  17. act.sa_flags = SA_SIGINFO; //设置此项

  18. act.sa_sigaction=func;

  19. sigaction(SIGUSR1, &act, &oact);

  20. while (1)

  21. {

  22. printf("My pid=%d\n",getpid());

  23. pause();

  24. }

  25. }

  • 我们按下ctrl+c之后,就会显示信号值和发送信号的进程

四、自定义signal函数

  • 利用sigaction函数的特性,定义自己的signal函数,使用起来与系统signal函数一样,但是功能比系统的signal函数更多

重启动版本

  • 系统的signal函数原型太复杂,如下所示
void (*signal(int signo, void (*func)(int)))(int);
  •  为了简化起见,我们定义了自己的Sigfunc类型,因此调用函数时只需要传入一个信号值即可
  1. typedef void Sigfunc(int);

  2. Sigfunc *signal(int signo, Sigfunc *func);

  3. Sigfunc *signal(int signo, Sigfunc *func)

  4. {

  5. struct sigaction act, oact;

  6. act.sa_handler = func;

  7. sigemptyset(&act.sa_mask);

  8. act.sa_flags = 0;

  9.  
  10. if (signo == SIGALRM){

  11. #ifdef SA_INTERRUPT

  12. act.sa_flags |= SA_INTERRUPT;

  13. #endif

  14. }

  15. else {

  16. #ifdef SA_RESTART

  17. act.sa_flags |= SA_RESTART;

  18. #denif

  19. }

  20.  
  21. if (sigaction(signo, &act, &oact) < 0)

  22. return(SIG_ERR);

  23. return(oact.sa_handler);

  24. }

  • 函数的使用:
    • 参数传入一个信号和一个信号处理函数指针
    • 返回值:在函数内部调用sigaction函数,并将相应信号的旧行为作为signal函数的返回值
  • 几点说明:
    • 要用sigemptyset函数初始化act结构的sa_mask成员,不能简单的设置act.sa_mask=0
    • 如果信号是SIGALEM,我们就将sa_flags设置为SA_INTERRUPT,不希望重启动由SIGALRM信号中断的系统调用,原因是:我们希望为IO操作设置超时(可以参考)
    • 如果是其他信号,sa_flags标志就加入SA_RESTART,希望重启被中断的系统调用

不重新启动版本

  • 如果想要系统调用被中断后不重新启动,则可以使用这一版本
  1. typedef void Sigfunc(int);

  2. Sigfunc *signal_intr(int signo, Sigfunc *func);

  3. Sigfunc *signal_intr(int signo, Sigfunc *func)

  4. {

  5. struct sigaction act, oact;

  6. act.sa_handler = func;

  7. sigemptyset(&act.sa_mask);

  8. act.sa_flags = 0;

  9.  
  10. #ifdef SA_INTERRUPT

  11. act.sa_flags |= SA_INTERRUPT;

  12. #endif

  13.  
  14. if (sigaction(signo, &act, &oact) < 0)

  15. return(SIG_ERR);

  16. return(oact.sa_handler);

  17. }

  • 如果系统定义了SA_INTERRUPT,那么为了提高可移植性。我们在sa_flags中增加该标志,这样就可以组织被中断的系统调用的重启动 

五、siginfo_t枚举的si_value成员验证

  • siginfo_t的应用:有时我们接收到一个信号时,希望接受一些其他数据,就可以设置这个成员
  • 例如:我们有一个用链表实现一个简单的服务器,并且想要在规定的一个时间间隔发送一些数据。就可以定一个定时器,并且在接收到SIGALRM信号时,因为操作数据需要用到链表,将链表地址传递给union sigval的sival_ptr指针。这样当捕获SIGALRM信号时,就可以调用sa_action函数,然后通过参数2的sival_ptr获取这个指针

演示案例:

  • 我们用一个进程给另外一个进程发送SIGINT信号,并且同时将一个整型100发送出去
  1. //发送进程:向一个进程发送SIGINT信号,并且将整型100也传进去

  2. #include<stdio.h>

  3. #include<signal.h>

  4. #include<unistd.h>

  5. #include<string.h>

  6.  
  7. int main(int argc,char *argv[])

  8. {

  9. if(argc!=2)

  10. {

  11. printf("arguments error!");

  12. exit(0);

  13. }

  14.  
  15. pid_t pid=atoi(argv[1]);//将进程号转化为整数

  16. union sigval v;

  17. v.sival_int=100; //发送整型100

  18.  
  19. sigqueue(pid,SIGINT,v);

  20. return 0;

  21. }

 
  1. //此进程等待接受信号的传入,并有SIGINT的信号处理函数

  2. #include<stdio.h>

  3. #include<unistd.h>

  4. #include<signal.h>

  5. #include<string.h>

  6.  
  7. void handler(int,siginfo_t *,void *);

  8.  
  9. int main(void)

  10. {

  11. struct sigaction act;

  12. act.sa_sigaction=handler;

  13. sigemptyset(&act.sa_mask);

  14. act.sa_flags=SA_SIGINFO;

  15. if(sigaction(SIGINT,&act,NULL)<0)

  16. {

  17. printf("error");

  18. exit(0);

  19. }

  20. for(;;)

  21. pause();

  22. return 0;

  23. }

  24.  
  25. void handler(int sig,siginfo_t * info,void *ctx)

  26. {

  27. //打印信息

  28. printf("recv a sid=%d data=%d data=%d\n",sig,info->si_value.sival_int,info->si_int);

  29. }

  • 演示结果:

猜你喜欢

转载自blog.csdn.net/qq_20853741/article/details/113547906