Linux信号专题FAQ

信号: 基本概念

可重入、线程安全以及异步信号安全的区别?

​   参考可重入、线程安全和异步信号安全,需要强调的是异步信号安全,这个概念知道的人不多,平常大家在编写代码的时候也很少考虑这个因素,也不清楚哪些函数是异步信号安全的,哪些不是,典型的像printf就不是异步信号安全的,内部会加锁,但是平时很多人都喜欢在信号处理函数中调用。大多数情况下都不会出现问题的,所以让使用者错误的认为这是正确的写法。第二个需要注意的是可重入的概念,Linux有很不少系统调用的实现都是不可重入的,会将结果保存在内部的静态数据存储中,同时这类系统调用也提供了可重入版本的实现,其函数名就是尾部添加_r来标识。

信号的内部实现是怎么样的?

这里写图片描述

​ 如上图所示一个进程/线程就是一个task_struct结构,该结构包含了属于这个进程/线程的阻塞信号集、pending的信号等,所有投递到该进程/线程的信号都会通过双向链表组织在一起,链表的元素是sigqueue,所有的信号对应的信号处理函数存放在sighand_struct中的一个类型为k_sigaction数组,每次程序由核心态切换到用户态时,内核都会发起信号处理,执行信号处理程序的时候为了避免对内核产生影响,所以使用的是用户栈,还可以自定义信号处理的备用栈。

​ 信号处理函数是每次程序从核心态切换到用户态的时候,内核才会负责发起信号处理,也就是说信号处理的时机有以下两种:

  • 进程在当前时间片用完后,获得了新的时间片时(会发生内核态到用户态的切换)
  • 系统调用执行完成时(信号的传递可能会引起正在阻塞的系统调用过早完成)

如何查看一个进程当前等待的、阻塞的、忽略的、捕获的信号?

​   通过查看/proc/PID/status文件,该文件中有几个字端的值,这些值按照十六进制的形式显示,最低的有效位表示信号1,相邻的左边一位代表信号2,依次类推,例如下面这几个数值:

SigQ:   0/3872                0是当前信号队列中的信号数,3872是信号队列的最大长度
SigPnd: 0000000000000000       当前pending的信号,也就是没有信号投递给线程
ShdPnd: 0000000000000000       当前pengding的信号,也就是没有信号投递给进程
SigBlk: 7be3c0fe28014a03       当前阻塞的信号
SigIgn: 0000000000001000       当前被忽略的信号
SigCgt: 00000001800004ec       当前被捕获的信号

信号的默认处理方式有哪些?

​   当信号到达的时候,默认情况下信号有如下几种处理方式:

  • 忽略信号,内核直接将信号丢弃,不对进程产生任何影响
  • 终止进程,是一种异常的终止方式,和调用exit而发生的终止不同
  • 产生核心存储文件,同时进程终止
  • 停止进程,暂停进程的运行
  • 恢复之前暂停的进程
  • 执行用户自定义的信号处理器

signal和sigaction区别?

​   这两者都可以用来改变信号处置,signal很原始,提供的接口也比较简单,而sigaction提供了signal所不具备的功能。为了兼容,signal系统调用仍然保存,但是glibc是使用sigaction实现了signal的功能。sigaction同时支持两种形式的信号处理,通过不用的flags区分,通过设置不同的flags可以得到不同的功能。

struct sigaction {
  // 两种handler,兼容老的signal对应的信号处理函数
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;            // 要屏蔽的信号集
    int        sa_flags;
    void     (*sa_restorer)(void); // Not for application use
};

sa_flags:
1. SA_NOCLDSTOP  当接收一个信号而停止或恢复某一个子进程时,将不会产生SIGCHLD信号
2. SA_NOCLDWAIT  子进程终止时不会将其转化为僵尸
3. SA_NODEFER    捕获信号后,不会在执行信号处理器程序的时候自动将该信号添加到进程掩码中,也就是不会被这个                信号再次打断,成为死循环。
4. SA_ONSTACK    在执行信号处理函数时,使用sigaltstack安装的备选栈
5. SA_RESETHAND  捕获信号后,会在调用处理器函数之前将信号处置为默认值
6. SA_RESTART    自动重启由信号处理器程序中断的系统调用
7. SA_SIGINFO    调用信号处理器程序时,携带额外的参数,也就是使用sa_sigaction类型的信号处理函数

通过Kill来检查进程是否存在?

​   kill系统调用可以用来向指定进程发送信号,如果指定的信号是0的时候,kill仅会进行错误的检查,查看是否可以想目标进程发送信号,而这一特点恰好可以用来检测特定进程ID所对应的进程是否存在,如果不存在那么kill调用失败,并且errno设置为ESRCH

如何打印信号枚举值对应的信号描述?

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

int main() {
  printf("signal: %s\n", strsignal(SIGKILL)); // 和strerror等同 
  psignal(SIGKILL, "signal");                // 和perror等同
  return 0;
}

信号集的内部实现?

​   信号集是一种用来表示一系列信号集合的数据结构,使用sigset_t来表示,它的底层存储类型其实只是一个unsigned long类型,如下:

typedef unsigned long sigset_t;

​   unsigned long一共是八个字节,总共是64位,每一位表示一个信号的话,最多可以表示64个信号,这个和信号的最大值是吻合的。信号集也提供了一系列用来操作信号集的方法,sigemptysetsigfillsetsigaddsetsigdelsetsigismembersigisemptyset

如何阻塞信号,阻塞的信号在解除阻塞后是否会投递?

​   阻塞信号的实现不难,通过上文中对信号内部实现的分析可知,通过将要阻塞的信号放到task_struct结构中的blocked成员中,那么在信号的投递时会先查看下要投递的信号是否在阻塞信号集中,如果在就停止投递,否则就触发对应的信号处理,通过sigprocmask可以设置当前进程的阻塞信号集,对应到内核的实现如下:

int sigprocmask(int how, sigset_t *set, sigset_t *oldset)
{
    struct task_struct *tsk = current;
    sigset_t newset;

    /* Lockless, only current can change ->blocked, never from irq */
    if (oldset)
        *oldset 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值