Linux下的信号

信号是操作系统发给进程的一种信息,进程会针对接收到的信息做出相应的处理。

前面谈到一个概念,叫做信号量,这里所说的信号量和我们今天谈到的信号,除了名字相似,事实上并没有任何联系,是两个完全不相关的概念,故不可混为一谈。

信号是如何产生的呢?先来说说熟悉的场景:

用户输入命令,在前台启动一个进程,然后按下Ctri+C这个组合键,键盘产生一个硬件中断,终端驱动将Ctrl+C解释成一个SIGINT信号,写入该进程的PCB中,这个过程也可以认为操作系统发送了一个SIGINT信号给该进程。

这里提到一个概念,前台。一个命令后面加&就可以放到后台运行,这样Shell不必等待进程运行结束就可以接收新的命令,启动新进程。Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到组合键产生的信号

在前台进程运行的任意时刻都有可能接收到一个SIGINT信号,所以信号相对于进程来说是异步的。

与异步相对的还有一个概念叫做同步。

简单的可以这么理解,异步就是临时起意想要做的事情,同步就是两者提前商量好到某一时刻干某件事情。

那么如何我们操作系统中到底有多少信号呢?用kill -l命令就可以查看。

这里写图片描述

这些编号后面对应的就是每一个信号,从我们系统中的编写习惯我们可以猜测这可能是一个宏,事实上也的确是这样的,这些定义可以在signal.h中找到。

从图片中观察34-64的信号都很类似,这些都是实时信号。又被认为是可靠信号,信号不会丢失,执行完处理动作后,不会恢复成缺省处理动作。我们这里主要讨论非实时信号。

刚才只说了一种信号产生的方式,这里我们来把信号产生的方式都总结一下:

  1. 组合键:Ctrl+C产生SIGINT信号,Ctrl+\产生SIGQUIT信号,Ctrl+Z产生SIGTSTP信号。
  2. 硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。如除零异常,就会发送SIGFPE信号。
  3. 一个进程调用kill(2)函数可以发送信号给另一个进程。
  4. 软件条件产生,如管道破裂。

信号常见的处理方式有三种:

  • 忽略
  • 执行默认处理
  • 执行自定义处理

忽略就是我们收到了信号,但是处理方式就是不做任何处理。那么如果有进程在操作系统中出了异常,那是不是就无法终止了呢?并非如此,因为SIGQUIT(3)和SIGKILL(9)号信号是无法被忽略的。

默认处理就是原本在系统中内置的处理方式。

自定义处理我们在后面做出解释。

然后来谈谈其他一些相关概念:

  • 实际执行信号的处理动作被称为信号递达(忽略处理也是一种递达
  • 信号从产生到递达之间的状态,称为信号未决
  • 进程可以选择阻塞某个信号
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达动作

    每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。在系统中大概和下图类似:

这里写图片描述

在每个进程的PCB中分别都维护了这样一个数据结构,信号产生时,内核在控制块中设置该信号的未决标志,直到信号递达才清除该标志。上图SIGHUP信号未阻塞也未产生,当它递达时执行默认处理动作。SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有集合改变处理动作之后再解除阻塞。如果某一信号未产生过,但是block表中被修改为1,那么一旦产生将被阻塞,再未解除阻塞之前,这个信号将始终不能递达。如果将这个信号函数指针的指向改为自定义函数的地址,那么它的处理动作就是自定义了。

POSIX允许系统递达某一信号一次或多次,Linux下是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。

捕捉信号:

如果信号的处理动作是用户自定义函数,在信号递达时候就调用这个函数,这称之为捕捉信号。由于信号处理函数的代码是在用户空间实现的,所以执行对信号的处理时会陷入内核,其流程图大致如下:

这里写图片描述

从中我们可以看到,信号在用户和内核之间的切换可多达四次,每个用蓝色圈出来的部分就是一次上下文切换。这就是信号捕捉时在操作系统中调用的大致过程。

然后我们用一段代码来演示信号捕捉的过程。

模拟实现sleep函数

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

void sig_alrm(int s)
{
    //printf("signal:%d,兄弟,你弄不死我\n",s);
}

unsigned int mysleep(unsigned int nsecs)
{
    struct sigaction new, old;
    sigset_t newmask, oldmask, suspmask;
    unsigned int unslept = 0;
    new.sa_handler = sig_alrm;
    sigemptyset(&new.sa_mask);
    new.sa_flags = 0;
    sigaction(SIGALRM, &new, &old);
    //阻塞
    sigemptyset(&newmask);
    sigaddset(&newmask,SIGALRM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);
    alarm(nsecs);

    suspmask = oldmask;
    sigdelset(&suspmask, SIGALRM);

    sigsuspend(&suspmask);

    unslept = alarm(0);
    sigaction(SIGALRM, &old, NULL);
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
    return unslept;
}

int main()
{
    mysleep(5);
    printf("5 seconds passed\n");


    //signal(2,hander);

    //alarm(5);

   // while(1)
   // {
   //     printf("I am a Running proggram\n");
   //     sleep(1);
   // }

    return 0;
}

打印出信号中的位图:

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

void printsigset(sigset_t *set)
{
    int i = 0;
    for(; i<32; ++i)
    {
        if( sigismember( set,i ) ){
            putchar('1');
        }
        else{
            putchar('0');
        }
    }
    puts("");
}

int main()
{
    sigset_t s,p;
    sigemptyset(&s);
    sigaddset(&s, SIGINT);
    sigprocmask(SIG_BLOCK, &s, NULL);
    while(1)
    {
        sigpending(&p);
        printsigset(&p);
        sleep(1);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zym1348010959/article/details/80757547
今日推荐