欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux下的信号

程序员文章站 2022-07-12 11:17:19
...

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

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

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

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

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

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

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

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

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

Linux下的信号

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

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

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

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

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

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

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

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

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

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

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

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

Linux下的信号

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

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

捕捉信号:

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

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;
}