APUE编程:80---信号处理(sigaction信号捕获函数:struct sigaction)

一、函数结构

#include <signal.h>
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结构体

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成员:

  • 对捕获的信号进行处理的函数,函数参数为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包含造成故障的根源地址(该地址可能并不正确)
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 inglibc 2.3.2 and earlier) */
    int      si_fd;       /* File descriptor */
    short    si_addr_lsb; /* Least significant bit of address(since Linux 2.6.32) */
}
  •  参数3:此参数为void*类型,可以自己传入一些参数进去,但是一般强转为ucontext_t结构体类型使用,该结构体标识信号传递时进程的上下文。下面是ucontext_t结构体内的一些部分成员
ucontext_t *uc_link;    /* pointer to context resumed when this context returns */
sigset_t uc_sigmask;    /* signals blocked when this context is active */
stack_t uc_stack;       /* stack used by this context */
mcontext_t uc_mcontext; /* machine-specific representation of saved context */

//uc_stack字段描述了当前上下面使用的栈,至少包括下列成员
void *ss_sp;    /* stack base or pointer */
size_t ss_size; /* stack size */
int ss_flags;   /* flags */
  • sa_handler和sa_sigaction函数一次只能使用其中一个
    • 如果只是想处理信号,但是不获取信号信息,就使用sa_handler函数
    • 如果想要处理信号,并且获取信号的各种信息,就使用sa_sigaction函数

三、演示案例

  • 案例一:处理SIGQUIT信号 
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void myHandler(int sig);
int main(int argc,char *argv[])
{ 
    struct sigaction act, oact;

    act.sa_handler = myHandler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGQUIT, &act, &oact); 
    while (1) 
    { 
        printf("running...\n"); 
        pause();
    }
}
void myHandler(int sig)
{
    printf("I got signal: %d.\n", sig);
}
  • 按下ctrl+\之后,就会调用myHandler函数 

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

void func(int signo, siginfo_t *info, void *p)
{
    printf("signo=%d\n",signo);
    printf("sender sigal pid=%d\n",info->si_pid);
}

int main(int argc,char *argv[])
{ 
    struct sigaction act, oact;

    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO; //设置此项
    act.sa_sigaction=func;
    sigaction(SIGUSR1, &act, &oact); 
    while (1) 
    { 
        printf("My pid=%d\n",getpid()); 
        pause();
    }
}
  • 我们按下ctrl+c之后,就会显示信号值和发送信号的进程

四、自定义signal函数

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

重启动版本

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

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

Sigfunc *signal(int signo, Sigfunc *func)
{
    struct sigaction act, oact;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    if (signo == SIGALRM){
#ifdef SA_INTERRUPT
    act.sa_flags |= SA_INTERRUPT;
#endif
    } 
    else {
#ifdef SA_RESTART
    act.sa_flags |= SA_RESTART;
#denif
    }

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

不重新启动版本

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

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

Sigfunc *signal_intr(int signo, Sigfunc *func)
{
    struct sigaction act, oact;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

#ifdef SA_INTERRUPT
    act.sa_flags |= SA_INTERRUPT;
#endif
 
    if (sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}
  • 如果系统定义了SA_INTERRUPT,那么为了提高可移植性。我们在sa_flags中增加该标志,这样就可以组织被中断的系统调用的重启动 

五、siginfo_t枚举的si_value成员验证

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

演示案例:

  • 我们用一个进程给另外一个进程发送SIGINT信号,并且同时将一个整型100发送出去
//发送进程:向一个进程发送SIGINT信号,并且将整型100也传进去
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<string.h>

int main(int argc,char *argv[])
{
    if(argc!=2)
    {
        printf("arguments error!");
        exit(0);
    }

    pid_t pid=atoi(argv[1]);//将进程号转化为整数
    union sigval v;
    v.sival_int=100; //发送整型100

    sigqueue(pid,SIGINT,v);
    return 0;
}
//此进程等待接受信号的传入,并有SIGINT的信号处理函数
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>

void handler(int,siginfo_t *,void *);

int main(void)
{
    struct sigaction act;
    act.sa_sigaction=handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags=SA_SIGINFO;
    if(sigaction(SIGINT,&act,NULL)<0)
    {
        printf("error");
        exit(0);
    }
    for(;;)
        pause();
    return 0;
}

void handler(int sig,siginfo_t * info,void *ctx)
{
    //打印信息
    printf("recv a sid=%d data=%d data=%d\n",sig,info->si_value.sival_int,info->si_int);
}
  • 演示结果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值