专栏首页开发与安全linux系统编程之信号(四):信号的捕捉与sigaction函数

linux系统编程之信号(四):信号的捕捉与sigaction函数

一、内核如何实现信号的捕捉

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 1. 用户程序注册了SIGQUIT信号的处理函数sighandler。 2. 当前正在执行main函数,这时发生中断或异常切换到内核态。 3. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 4. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。

1 2

(By default,  the  signal  handler  is invoked on the normal process stack.  It is possible to arrange that the signal handler  uses an alternate stack; see sigaltstack(2) for a discussion of how to do this and when it might be useful.)

5. sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。

6. 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

上图出自ULK。

二、sigaction函数

#include <signal.h>

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回-1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向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赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

需要注意的是sa_restorer 参数已经废弃不用,sa_handler主要用于不可靠信号(实时信号当然也可以,只是不能带信息),sa_sigaction用于实时信号可以带信息(siginfo_t),两者不能同时出现。sa_flags有几个选项,比较重要的有两个:SA_NODEFER 和 SA_SIGINFO,当SA_NODEFER设置时在信号处理函数执行期间不会屏蔽当前信号;当SA_SIGINFO设置时与sa_sigaction 搭配出现,sa_sigaction函数的第一个参数与sa_handler一样表示当前信号的编号,第二个参数是一个siginfo_t 结构体,第三个参数一般不用。当使用sa_handler时sa_flags设置为0即可。

 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 kernel 2.6.32) */            }

需要注意的是并不是所有成员都在所有信号中存在定义,有些成员是共用体,读取的时候需要读取对某个信号来说恰当的有定义的部分。

下面用sigaction函数举个小例子:

/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#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);
}

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sigaction 

^Crev sig=2

^Crev sig=2

^Crev sig=2

...........................

即按下ctrl+c 会一直产生信号而被处理打印recv语句。

其实我们在前面文章说过的signal 函数是调用sigaction 实现的,而sigaction函数底层是调用 do_sigaction() 函数实现的。可以自己实现一个my_signal 函数,如下:

/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#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);
/* 系统调用signal()实际上调用了sigaction() */
__sighandler_t my_signal(int sig, __sighandler_t handler);

int main(int argc, char *argv[])
{
    my_signal(SIGINT, handler);

    for (; ;)
        pause();

    return 0;

}

__sighandler_t my_signal(int sig, __sighandler_t handler)
{
    struct sigaction act;
    struct sigaction oldact;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    if (sigaction(sig, &act, &oldact) < 0)
        return SIG_ERR;

    return oldact.sa_handler; // 返回先前的处理函数指针
}

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

输出测试是一样的,需要注意的是 signal函数成功返回先前的handler,失败返回SIG_ERR。而sigaction 是通过oact 参数返回先前的handler,成功返回0,失败返回-1。

下面再举个小例子说明sa_mask 的作用:

/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#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);
    sigaddset(&act.sa_mask, SIGQUIT); // 在信号处理函数执行期间屏蔽SIGQUIT信号,完毕后会抵达
    /* 注意sigprocmask中屏蔽的信号是一直不能抵达的,除非解除了阻塞*/
    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);
    sleep(5);
}

先按下ctrl+c ,然后马上ctrl+\,程序是不会马上终止的,即等到handler处理完毕SIGQUIT信号才会抵达。

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sa_mask  ^Crev sig=2 ^\

5s过后接着才输出Quit (core dumped),即在信号处理函数执行期间sa_mask集合中的信号被阻塞直到运行完毕。

sa_flags 和 sa_sigaction 参数的示例看这里

在多线程环境下,编写信号处理函数需要安全地处理,可以参考这篇文章:

tgkill()发给指定进程中的指定线程;

pthread_kill()由一个线程发给同进程中的另一个线程,实际上是通过封装tgkill()实现的;

《Linux 多线程应用中如何编写安全的信号处理函数》

http://www.ibm.com/developerworks/cn/linux/l-cn-signalsec/

参考:《APUE》、《linux c 编程一站式学习》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • muduo网络库学习之EventLoop(一):事件循环类图简介和muduo 定时器TimeQueue

    1、EventLoop、Channel、Poller 等类图如下: ? 黑色菱形:组合;白色菱形:聚合;白色三角形:继承;实线:关联; Channel是sele...

    s1mba
  • 数据结构:字符串的基本操作

    字符串(string)是由0个或多个字符组成的有限序列。一般使用顺序存储结构,末尾以'\0'表示结束,但不计入字符串的长度。 ? 示例程序:(改编自《大话数据结...

    s1mba
  • linux系统编程之管道(三):命名管道FIFO和mkfifo函数

    进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道,前面讲过的匿名管道是用打开的文件描述符来标识的。如果要互相通信的几个进程没有...

    s1mba
  • cf550D. Regular Bridge(构造)

    一篇写的非常好的博客:http://www.cnblogs.com/mangoyang/p/9302269.html

    attack
  • 教你做一个后台相机

    做过相机的同学都知道,Camera需要一个SurfaceView来承载预览的输出流,SurfaceView的特性限制了它必须要有一个可见的View才能够使用。 ...

    PhoenixZheng
  • uva514(trail)(模拟栈)

    1 //#define LOCAL 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib>...

    Gxjun
  • 视频直播技术--Android视频采集(Camera1)

    今天为大家介绍一下使用Camera1进行视频采集。之前我写过一篇文章介绍的是Camera2进行视频采集。那么有人会问,为什么有了Camera2还要介绍Camer...

    音视频_李超
  • 博客 | 度量学习总结(二) | 如何使用度量学习处理 高维数据?

    本文原载于微信公众号:磐创AI(ID:xunixs),欢迎关注磐创AI微信公众号及AI研习社博客专栏。

    AI研习社
  • 【HBUOJ】暴躁的阿生

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    韩旭051
  • c语言之带参数的宏定义

    2.在带参数的宏定义中,形参参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体值,要用它去代换形参,因此必须作类型说明。

    绝命生

扫码关注云+社区

领取腾讯云代金券