• 深度剖析Linux信号机制


    信号的概念

    Linux中的信号是进程异步通信的一种方式。

    当某个信号发送到一个进程上,那么该进程就会分析该信号是什么信号,进而做出不同的处理动作。

    信号的分类

    信号分为两类:不可靠信号与可靠信号。

    不可靠信号(1-31):也称为非实时信号,不支持排队,可能会丢失(当相同信号发送多次时,效果相当于发送一次);

    可靠信号(34-64):也称为实时信号,支持排队,不会丢失。

    本章节只讨论不可靠信号。

    image-20230914215256257

    信号的产生方式

    从键盘获取

    比如:

    Ctrl + c向进程发送2号信号SIGINT

    Ctrl + \向进程发送3号信号SIGQUIT

    Ctrl + z向进程发送20号信号SIGTSTP

    通过系统调用

    比如:

    kill系统调用可以向指定进程发送指定信号

    kill命令可以向指定进程发送指定信号(kill命令内部就是调用了kill系统调用)

    raise函数可以向调用进程发送指定信号(就相当于kill(getpid(), signo)

    abort函数可以向调用进程发送6号信号SIGABRT(就相当于kill(getpid(), 6)

    硬件异常

    比如在代码中进行了除零运算或者野指针访问,那么OS会自动向该进程发送8号信号SIGPIPE或11号信号SIGSEGV

    OS是如何知道该进程进行了除零运算或野指针访问的呢?其实CPU中有一个状态寄存器,该寄存器有很多标志位,能够存储当前运算结果的某些特定状态信息(进位、溢出、零值等),OS通过访问该状态寄存器里的标志位,就能够知道当前进程发生了哪些错误进而发送对应的信号。

    软件条件

    像alarm函数,可以设定一个闹钟,在铃响的时候由OS向调用进程发送14号信号SIGALRM

    如何处理信号的到来

    当信号到来时,会有三种动作:

    1. 默认处理动作(信号到来时,进行由官方人员写好的处理动作,绝大部分是中止进程)
    2. 忽略动作(信号到来时,对该信号不进行任何处理动作)
    3. 自定义动作(信号到来时,进行自己编写好的处理动作)

    只有第三种动作,我们才能让指定信号到来时进行自己想要的动作,那么此时我们就必须有一个函数,能够让我们进行“注册”(对某种信号注册自定义动作)。signal函数就是用来进行注册的。

    void handler(int signo) {                                                                            
         /*自定义动作*/
    }
    int main() {
        signal(SIGINT, handler); /*运行注册函数,完成对SIGINT信号的自定义动作的注册*/
        // 下面就是正常要写的代码
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    但是有些信号还是无法进行注册的,比如9号信号SIGKILL

    sigaction函数也可以完成上面的操作。

    信号的更深入剖析

    • 实际执行信号的处理动作叫做信号递达
    • 信号从产生到递达之间的状态叫做信号未决
    • 一个信号是可以被阻塞的,当该信号被阻塞时,该信号不会进行信号递达,只能保持在信号未决,当阻塞被解除时才会进行信号递达

    在进程PCB中有两个位图(pending位图和block位图)

    • pending位图(32位整形):每一个bit位标识了一种信号,bit位上的内容表示该信号是否到来(是否处于信号未决状态)
    • block位图(32位整形):每一个bit位标识了一种信号,bit位上的内容表示该信号是否被阻塞

    还有一个函数指针数组:大小为32,每个指针都是对应信号的处理动作

    image-20230915084830143

    信号的处理动作是何时进行的?

    是在内核态转变为用户态的时候!

    image-20230915091247721

    当我们每次从内核态切换的时候,都会进行一次信号检测,检测pending位图中有没有需要处理的信号,当这些信号的动作是默认动作或者忽略动作时,直接就又返回用户态了。而当这些信号的动作是自定义动作时,会返回用户态执行handler自定义动作,执行完之后,返回内核态,然后再返回用户态。

    当有一大批同种信号到来时会怎样?

    首先pending位图该标志位置1,然后当信号检测时发现该信号到来了,那么就把该标志位置0同时block的该标志位置1,再去执行该信号的动作,假设该动作持续时间很长(比如handler中一直在循环),然后又有同种信号来了,此时pengding位图此标志位仍然置1,但此时该信号已经被阻塞(屏蔽)了,此后如果一直来同种信号,那么该标志位一直置1。当handler执行完了后,block上的该位会置0(解除该信号的屏蔽),由于在内核态返回用户态的过程中又会进行一次信号检测,此时由于pengding位图中的该标志位置1,block置0,所以又会进行一次该信号的handler动作。

    所以,当一大批同种信号到来时,一般会按顺序执行两次该信号的处理动作。

    Linux也提供了一批信号相关的系统调用

    处理信号集

    #include 
    int sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);
    int sigaddset (sigset_t *set, int signo);
    int sigdelset(sigset_t *set, int signo);
    int sigismember(const sigset_t *set, int signo);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    设置block信号集

    #include 
    int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
    
    • 1
    • 2

    获取pending信号集

    #include 
    int sigpending(sigset_t *set);
    
    • 1
    • 2
  • 相关阅读:
    Django(18):中间件原理和使用
    ChatGPT及GIS、生物、地球、农业、气象、生态、环境科学领域案例
    VC++创建windows服务程序
    Bootstrap的列表组相关知识
    腾讯二面——程序崩溃问题连问
    MySQL 连接出现 Authentication plugin ‘caching_sha2_password的处理方法(使用第二种)
    阶段五-Day03-Ajax
    经典面试题 之 JVM调优
    获取操作系统信息服务器信息JVM信息cpu内存磁盘信息
    vue开发h5页面不能滑动的坑
  • 原文地址:https://blog.csdn.net/qq_67569905/article/details/132895704