美文网首页
Linux信号机制

Linux信号机制

作者: 捉虫大师 | 来源:发表于2015-04-07 18:12 被阅读250次

    理论部分

    简单来说

    信号是用来通知进程发生了异步事件

    比如,在终端运行程序,按下ctrl+c就产生一个中端信号,大概过程是这样的:

    • 用户从shell下启动一个进程;
    • 用户按下ctrl+c,产生一个硬件中端信号;
    • cpu从用户态切换到进程态,处理中端;(这个过程当年读书的时候在《计算机组成原理》看过)
    • 终端驱动程序将ctrl+c解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。
    • 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

    前台进程在运行过程中用户随时可能按下ctrl+c而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步的。

    kill -l 可以查看系统定义的信号,这些定义是在signal.h中。

    1.png

    也可以通过man 7 signal来查看。

    2.png

    其中Action代表默认处理动作,

    Term : 终止当前进程
    Core : 终止并且Core Dump
    Ing   : 忽略
    Stop : 停止当前进程
    Cont: 继续执行先前停止的进程
    

    注:当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。

    产生信号的条件主要是:

    • 终端的按键,如ctrl+c产生SIGINT信号,ctrl+\产生SIGQUIT信号,ctrl+z产生SIGTSTP信号。
    • 硬件异常产生信号。
    • 一个进程调用kill (man 2 kill) 可以发送信号给另一个进程。
    • 可以调用kill (man 1 kill)。
    • 当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。

    用户程序可以调用sigaction(man 2 sigaction)函数告诉内核如何处理某种信号,可选的操作有

    • 忽略
    • 执行默认动作
    • 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号。

    发送信号可以用:

    #include <signal.h> 
    int kill(pid_t pid, int signo);//向其他进程发信号
    int raise(int signo);//向自己发信号
    
    #include <stdlib.h>
    void abort(void);//使当前进程接收到SIGABRT信号而异常终止
    
    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);//内核在seconds秒之后给当前进程发SIGALRM信号
    

    信号从产生到递达之间的状态,称为信号未决。
    每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作

    信号集操作函数

    sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_t变量,而不应该对它的内部数据做任何解释。

    #include<signal.h>
    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);
    

    函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。注意,在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

    #include<signal.h>
    int sigprocmask(int how, const sigset_t *set, sigset_t *oset);//读取或更改进程的信号屏蔽字
    
    3.png
    #include<signal.h>
    int sigpending(sigset_t *set);//读取当前进程的未决信号集,通过set参数传出
    

    信号补捉过程

    4.png
    #include<signal.h>
    int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);//读取和修改与指定信号相关联的处理动作
    

    act和oact指向sigaction结构体

    struct sigaction {
         void (*sa_handler)(int); /* addr of signal handler, */ 
        /* or SIG_IGN, or SIG_DFL */ 
        sigset_t sa_mask; /* additional signals to block */ 
        int sa_flags; /* signal options, Figure 10.16 */ 
        /* alternate handler */ 
        void (*sa_sigaction)(int, siginfo_t *, void *);
    };
    

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

    #include<unistd.h>
    int pause(void);//使调用进程挂起直到有信号递达
    

    当捕捉到信号时,不论进程的主控制流程当前执行到哪儿,都会先跳到信号处理函数中执行,从信号处理函数返回后再继续执行主控制流程。信号处理函数是一个单独的控制流程,因为它和主控制流程是异步的,二者不存在调用和被调用的关系,并且使用不同的堆栈空间。引入了信号处理函数使得一个进程具有多个控制流程,如果这些控制流程访问相同的全局资源(全局变量、硬件资源等),就有可能出现冲突。

    理解部分

    关于信号,之前接触的比较多的是kill这个命令,现在看来,发送信号给进程是一种交互方式。当进程中实现了补捉信号以及处理的函数时,就可以与用户进行简单的交互,之所以简单是因为信号数量很少。所以一般用做进程的启动,重启,停止,错误处理等等。若使用其他的信息传递方式我想也是可以实现的,就是麻烦了一些。
    下面来实现一个简单的例子,后台跑一个进程,打印出发送给该进程的信号。
    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>

    void show_handler(int sig)
    {
        printf("I got signal %d\n", sig);
    }
    
    int main(void)
    {
        int i = 0;
        struct sigaction act, oldact;
        act.sa_handler = show_handler;
        sigaddset(&act.sa_mask, SIGQUIT);
        act.sa_flags = 0;
        sigaction(SIGINT, &act, &oldact);
        sigaction(SIGCONT, &act, &oldact);
        while(1) {
            sleep(1);
            i++;
        }
    }
    

    贴上运行图

    s.png

    注:sigaction 是不能接收SIGKILL 和 SIGSTOP的信号

    相关文章

      网友评论

          本文标题:Linux信号机制

          本文链接:https://www.haomeiwen.com/subject/godzxttx.html