码迷,mamicode.com
首页 > 系统相关 > 详细

Linux信号详解

时间:2016-05-06 19:52:25      阅读:527      评论:0      收藏:0      [点我收藏+]

标签:linux   signal   


每个信号都有一个编号和宏定义,在signal.h中可以找到

1-31为普通信号,34-64为实时信号


可通过kill -l 命令查看所有信号


信号的产生

  1. 通过终端按键产生

    用户通过键盘按键,如ctrl+c给前台进程发送2号信号SIGINT,该信号的默认动作为终止进程,当进程收到此信号时,执行默认动作终止该进程。

  2. 调用系统函数

    int kill(pid_t pid, int signo);//这两个函数都是成功返回0,错误返-1

    int raise(int signo);

    void abort(void);//无返回值

    kill命令通过调用kill函数也可以给指定的进程发送指定的信号

    raise()给当前进程发送指定的信号(自己给自己发送信号)

    abort()使当前进程收到SIGABRT信号而异常终止

  3. 软件行为

    用户可通过调用一些函数产生信号。

    unsigned int alarm(unsigned int seconds);//这个函数的返回值是0或者是以前设定的闹钟时间                                      还余下的秒数

    通过函数设定了一个闹钟,seconds秒之后给进程发送一个SIGALRM信号,该信号的默认动作为终止进程。

  如果seconds的值i为0则取消之前设定的闹钟


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>

int main()
{
    int count=5;
    while(1){
        printf("i am runing\n");
        sleep(1);
        while(--count<=0){
            //kill(getpid(),SIGTERM);
            //kill(getpid(),SIGINT);
            //kill(getpid(),SIGQUIT);

            //raise(SIGTERM);

            abort();
        }
     }
    return 0;
}

while循环里五个函数分别运行结果:

技术分享技术分享

技术分享技术分享

技术分享

可看出该进程收到的信号不同



信号的阻塞和递达

信号在收到的时候不是立马被处理,而是在一个合适的时刻才会被处理。这个合适的时刻是当进程产生异常、中断或者有系统调用的时候才会处理。


先介绍两个概念,未决和递达

未决信号在收到之后,处理之前处于未决状态,称为信号的未决(Pending)。

递达:信号被处理的动作叫做信号的递达(Delivery)。


每个进程的PCB中都保存了一个pending表、一个block表和一个handler表,每个信号都对应两个标志位(pending和block)和一个函数指针(指向信号处理函数)

信号在内核中的表示如下图:

技术分享

当进程收到某种信号时,先保存到Pending信号集中,如果Block信号集中对应的bit位为1,说明该信号被阻塞,信号不会被递达,如果对应bit位为1,表明该信号没有被阻塞,则到一个合适的时刻该信号会被递达,递达即为信号的处理,根据Handler表做出该信号相应的动作(3种),如果为SIG_IGN表示忽略此信号(注意,忽略和阻塞是不一样的,SIG_DFL为执行默认动作,第三种是处理自定义的动作(事先对该信号注册新号处理函数)。当信号被递达,Pending信号集对应的bit位会被清除(注意,由于每个信号对应一个bit位,所以在信号处于未决状态时内无论该信号产生多少次都只会被递达一次


未决和阻塞信号集用sigset_t类型来存储


下面是对信号集操作的函数

 #include <signal.h>

  1. int sigemptyset(sigset_t *set);//成功返回0,失败返回-1

    int sigfillset(sigset_t *set);

    初始化set所指向的信号集,分别使信号集对应的比特位全部置0(sigemptyset),全部置1(sigfillset)。

    注意:在使用sigset_t类型的变量的时候一定要初始化

  2. int sigaddset(sigset_t *set,int signo);//成功返回0,失败返回-1

    int sigdelset(sigset_t *set,int signo);

    往set所指向的信号集中添加(删除)signo所对应的信号(signo是某个信号所对应的值,如#define SIGINT 2)

  3. int sigismumber(const sigset_t *set,int signo);

    判断signo该有效信号是否为set所指向信号集的成员,若包含则返回1,不包含则返回0,出错返回-1。

  4. int sigprocmask(int how,const sigset_t *set,sigset_t *oset);////成功返回0,失败返回-1

    how可取三种值  SIG_BLOCK  set所指向信号集添加到阻塞信号集中,mask=mask|set

               SIG_UNBLOCK 从阻塞信号集中取消set所指定的信号集中bit位为1的信                                              号,mask=mask&~set

               SIG_SETMASK 设置当前信号屏蔽字为set所指向的信号集的值,mask=set

    oset若非空,保存进程当前阻塞信号集中的值

  5. int sigsending(sigset_t *set);//调用成功则返回0,出错则返回-1

    获取当前进程的pending信号集,并通过set传出

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

void show_member(sigset_t *sig_set)
{
    int count=1;
    while(count<32){
        int ret=sigismember(sig_set,count);
        if(ret==1){
            printf("1");
        }
        else if(ret==0){
            printf("0");
        }
        else
            printf("error\n");
        count++;
    }
    printf("\n");
}
int main()
{
    sigset_t sig_set;
    sigemptyset(&sig_set);
    sigaddset(&sig_set,2);
    sigaddset(&sig_set,6);
    //  sigfillset(&sig_set);
    //  sigdelset(&sig_set,2);
    //  sigdelset(&sig_set,5);
    //  sigdelset(&sig_set,9);
    
    sigset_t old_sig_set;
    sigemptyset(&old_sig_set);

    sigprocmask(SIG_BLOCK,&sig_set,&old_sig_set);//设置信号屏蔽字,编号为2和6的信号被添加到阻塞信号集中
    show_member(&sig_set);//显示当前的阻塞信号集
    show_member(&old_sig_set);//显示旧的阻塞信号集
    printf("*********************************\n");
    sleep(5);

    sigaddset(&sig_set,31);
    sigaddset(&sig_set,30);
    sigaddset(&sig_set,29);
    sigdelset(&sig_set,6);
    sigprocmask(SIG_SETMASK,&sig_set,&old_sig_set);//同上
    show_member(&sig_set);
    show_member(&old_sig_set);
    printf("*********************************\n");
    sleep(3);
    
    sigset_t pending_set;
    sigpending(&pending_set);//获取当前的pending信号集
    show_member(&pending_set);
    printf("this is pending_set\n");
    
    return 0;
}

运行结果:

技术分享

可看到当前的pending信号集中编号为2的信号对应bit位为1,那是因为在获取pending信号集之前我通过键盘给进程发送了2号信号(ctrl+c),但该信号被阻塞了,所以一直保存在pending信号集中,如果我不发送该信号,则会产生如下运行结果(pending信号集对应的bit位全部为0)

技术分享


信号的捕捉

信号的处理动作可以有三种(忽略、执行默认动作、用户自定义动作)

如果信号的处理动作是用户自定义的,则该动作称为信号的捕捉,信号的捕捉过程如下:

  1. 当程序执行某条指令时发生中断、异常或系统调用的时候系统会切换到内核模式执行相应的处理

  2. 当处理完中断或异常后不会立即切换回用户态,而是先检查时候有信号可被递达

  3. 如果有信号可递达(收到该信号且未被阻塞),且该信号的处理动作为用户自定义动作,切换到用户态执行该信号事先注册的函数

  4. 自定义函数执行完并返回后自动进入内核态执行特殊的系统调用函数sigreturn

  5. 如果没有其它信号可递达,切换回用户模式,恢复上下文,执行被中断处的下一条指令

技术分享


信号捕捉有关函数

#include <signal.h>

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

    把signo所对应的信号原来的执行动作保存到oact中,并执行新的动作act

    技术分享

    sa_handler是一个函数指针,若为常数SIG_IGN则忽略该信号,若为SIG_DFL则执行默认动作,若指向一个函数则执行该函数定义的动作(该函数的返回值为void 参数为int 可通过该参数获得该信号所对应的编号 该函数是个回调函数,由系统调用,而不是main函数,参照上面的图可知)


  sa_mask设置需要额外屏蔽的信号,本次线程执行时会屏蔽,当信号处理函数返回时,自动恢复原来的信号屏蔽字。

  当某个信号的处理函数被执行时,该信号会被自动添加到该进程的信号屏蔽字,若有该信号再次到产生时,它会被阻塞知道本次函数处理完。

2.#include <unistd.h>

int pause(void);

 该函数会使当前进程挂起,直到有一个信号递达。

 若信号的处理动作为忽略时,进程会继续挂起,若为默认(一般为终止进程),则终止该进程,pause函数没机会返回,若为动作为捕捉,则执行该自定义函数,函数返回-1,error设为ENITR,表示“信号被中断”。




本文出自 “零蛋蛋” 博客,请务必保留此出处http://lingdandan.blog.51cto.com/10697032/1770773

Linux信号详解

标签:linux   signal   

原文地址:http://lingdandan.blog.51cto.com/10697032/1770773

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!