资讯专栏INFORMATION COLUMN

使用 sigprocmask 和 sigpending 在程序正文中捕获和处理信号

PAMPANG / 1284人阅读

摘要:后续有说明屏蔽信号的含义的作用,主要就是屏蔽指定的信号。在程序正文处理信号这里所说的正文,指的是不在或中指定的中处理信号事件,而是在普通的程序流程能够中捕捉信号,并且处理信号。的线程安全问题前文提及如果发现为。

最近在尝试使用 epoll 写一个类似 libevent 的库。那么,如何像 libevent 一样,在 event loop 里加入对信号事件的观测呢?
我查了一下资料,一个可行的方法,就是使用 sigprocmask() 及其相关功能来实现啦。

但是请注意,这个方法是存在缺陷的,请看官留心。
个人在继续研究之后,暂时是不打算使用此种方法来实现信号事件,而改用另一个方法。

Reference

《UNIX 环境高级编程》
sigprocmask , sigpending 和 sigsuspend函数
errno多线程安全
Linux 多线程应用中编写安全的信号处理函数

UNIX 系统主要信号

以下就只列出主要的信号了:

名称 说明 FreeBSD Linux macOS Solaris 默认动作
SIGABRT 调用了abort() Y Y Y Y 终止 + core
SIGALRM alarm()产生的 Y Y Y Y 终止
SIGBUS 硬件故障 Y Y Y Y 终止 + core
SIGCHLD 子进程状态改变 Y Y Y Y 忽略
SIGHUP 连接断开 Y Y Y Y 终止
SIGINT Ctrl + C Y Y Y Y 终止
SIGKILL 终止;不可捕获 Y Y Y Y 终止
SIGPIPE 向关闭的管道写 Y Y Y Y 终止
SIGQUIT Ctrl + Y Y Y Y 终止 + core
SIGSEGV 段错误 Y Y Y Y 终止 + core
SIGSTOP 停止 Y Y Y Y 暂停进程
SIGTERM kill(1) Y Y Y Y 终止
SIGUSR1 用户自定义1 Y Y Y Y 终止
SIGUSR2 用户自定义2 Y Y Y Y 终止
SIGPOLL 可轮训的设备发生事件 . Y . Y 终止
SIGPWR 主电源失效,电池电量不足 . Y . Y 终止或忽略

如果要在 C 里面发送一个信号的话,那么可以用 kill()raise()。其中后者是想当前进程发信号,而前者可以向任意进程发信号。kill()pid 参数可以有以下可能值:

pid > 0:发给指定进程

pid == 0:发给与当前进程属于同一进程组的所有进程,但需要权限允许

pid < 0:发给进程组 ID 等于 (0 - pid) 的所有进程,但需要权限允许

pid == -1:发给所有进程,但需要权限允许

信号集操作
    #include 

    int sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);
    int sigaddset(sigset_t *set, int signum);
    int sigdelset(sigset_t *set, int signum);
    int sigismember(const sigset_t *set, int signum);

上面的几个函数语义都很清楚了,就是在一个集合里面配置多个信号。
除了 sigismenber() 实际上返回的是 BOOL 类型之外,其他的函数均返回 0 代表成功,-1 代表失败。

sigprocmask 和 sigpending
    #include 

    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    int sigpending(sigset_t *set);

sigprocmask() 返回的是 0 或者 -1 的状态值,而 sigpending() 返回 BOOL
其中 how 可以有以下值:

SIG_BLOCK:屏蔽信号(注意,不是“忽略”信号)

SIG_UNBLOCK:解屏蔽

SIG_SETMASK:将整个表配置设置进去。这适用于 sigprocmask() 恢复阶段。后续有说明

“屏蔽” 信号的含义

sigprocmask()的作用,主要就是屏蔽指定的信号。这个 “屏蔽” 的含义需要说明清楚。
首先我们大致数一下信号在内核里的处理流程吧(不是准确的流程,只是便于说明):

内核等待信号中断

信号产生,触发内核中断

内核将信号存下来,或者说设置信号标志

内核根据用户空间的配置,处理信号。如果用户空间没有特别配置,则按照默认行为处理

处理完成后,清除信号标志

回到 1,继续等待

sigprocmask()所做的 “屏蔽”,其实就是将上述的信号处理流程,卡在了 3 和 4 之间,让内核能够将信号标志设置好,但是却到不了判断并处理的那一步。
换句话说,即便进程调用 signal() 函数,设置了 SIG_IGN 标志,但如果指定的信号被 sigprocmask() 屏蔽了的话,内核也不会去判断是否该忽略这个信号,而只是把信号标志卡在那儿,直到调用sigprocmask()执行SIG_UNBLOCK为止,才能让内核继续走到第 4 步。

在程序正文处理信号

这里所说的 “正文”,指的是:
  不在 signal()sigaction() 中指定的 handler 中处理信号事件,而是在普通的程序流程能够中捕捉信号,并且处理信号。

这么做有很多好处:

中断处理函数有很多限制,只能调用某些系统调用,否则可能导致上下文异常。但在正文中就不会有这个问题

中断处理函数和正文之间可以视为两个不同的线程,两者之间的同步比较麻烦

在正文中处理,可以实现类似于 libeventEV_SIGNAL 功能——而这也是笔者正在研究的。

基本软件流程如下:

使用 signal()sigaction() 将需要捕获的信号设置为 SIG_IGN

使用 sigprocmask() 屏蔽需要捕获的信号,同时注意将屏蔽之前的信号集保存下来(oset参数)

进行相应操作(比如 epoll()

如果发现 errnoEINTR,那么就可以用 sigpending() 获取被屏蔽的信号集,判断需要捕获的信号是否在信号集中

使用 sigprocmask() 执行一次 SIG_UNBLOCK 操作,让内核清除信号集标志

回到 2,重新屏蔽信号

缺陷

不过这个流程有一个 bug,就是信号有可能在 4 和 6 之间产生,这样的话,就捕获不到了——这还需要想想怎么处理。

sigaction 函数

这里顺便记一下 sigaction() 吧,POSIX 是建议不要再使用 signal() 了。
简单情况下,只需要使用 struct sigcation 里的 sa_handlersa_mask 就可以替代 signal() 调用了。

    #include 

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

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

errno 的线程安全问题

前文提及 “如果发现 errno 为 EINTR ...”。有同学可能会问了:“errno 是一个全局变量啊,这安全不?”
实际上,errno线程安全的……呃,这个优点,其实笔者自己也是才知道……看了一下 errno 的原理,觉得实在是很厉害啊!

但是,使用 errno 只有一点要注意,就是虽然在程序正文中,errno 是线程安全的,但是在中断处理函数中却并不是这样。其他位置的话,随意。

这里参考的资料是这个还有这个。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/9914.html

相关文章

  • 使用 pipe 程序正文捕获处理信号

    摘要:本文地址环境高级编程源码深度剖析使用和在程序正文中捕获和处理信号基本原理在设置捕获信号之前,首先创建一个通信通道。在中断处理函数中,将捕获到的信号数往这个通道内写入。大量的数据处理放在程序正文中,获取信号的方法很简单,只是。 我的上一篇文章研究了一下如何在程序的正文(而不是信号处理函数)中捕获和处理信号。当时用的方案是 sigprocmask()。但那个方法理论上是可能漏掉一些信号的。...

    NotFound 评论0 收藏0
  • 【linux】——信号详解实操代码

    摘要:每个信号都有两个标志位,一个是阻塞位图和未决位图,还有一个函数指针数组,这是信号处理的方式。信号递达执行信号处理动作。返回值成功返回,失败返回取消中所有的有效位,使其中所有信号对应的位清零,表示该信号不包含任何有效位。 目录 信号的概念 信号捕捉初始 signal函数        发送信号...

    princekin 评论0 收藏0
  • 【Nginx源码研究】Master进程浅析

    摘要:内核代表进程来执行信号处理器函数,当处理器返回时,主程序会在处理器被中断的位置恢复执行。进程信号掩码内核会为每个进程维护一个信号掩码。这个竞态条件发生在主程序和信号处理器对同一个被解除信号的竞争关系。 运营研发团队 季伟滨 一、前言 众所周如,Nginx是多进程架构。有1个master进程和N个worker进程,一般N等于cpu的核数。另外, 和文件缓存相关,还有cache mana...

    wupengyu 评论0 收藏0
  • APUE 札记

    摘要:函数有种变体出错处理返回值指向消息字符串的指针用户标识组文件将组名映射为数值的组,其中个字段依次是组名称组密码组该组用户列表以逗号分隔。第章标准库流和对象不带缓冲的函数围绕文件描述符,标准库围绕流。 以apue第三版为蓝本 我是目录 第1章 UNIX基础知识 第2章 UNIX标准及实现 第3章 文件IO 第4章 文件和目录 第5章 标准I/O库 第6章 系统数据文...

    lei___ 评论0 收藏0
  • Linux Signal 示例

    摘要:信号是系统响应某些条件而产生的一个事件,接收到该信的进程做出相应的处理。原型函数向指定进程发送指定的信号,如果信号为将执行错误检查,信号并不会发送,可以用来检查的有效性。如果在等待结束之前有其他的事件产生,那定时器请求也将被取消。 信号是系统响应某些条件而产生的一个事件,接收到该信的进程做出相应的处理。通常信是由错误产生的,如段错误(SIGSEGV)。 但信还可以作为进程间通信的一种方...

    FWHeart 评论0 收藏0

发表评论

0条评论

PAMPANG

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<