linux信号(一)--unix环境高级编程读书笔记

时间:2021-07-15 04:42:25

     1.信号的概念

     在这里要给出一个信号的准确概念感觉很困难,可以这么说,信号就是进程之间或者内核与进程间异步通信的一种机制,有点类似于中断的性质。在  linux  系统中有  31  种信号,每一种信号都以  SIG  三个字母开头,例如  SIGABRT  是夭折信号,就是调用  abort  函数产生的信号,SIGALRM  是调用  alarm  函数定时时间溢出后产生的信号。

     对每一个信号,系统有三种处理方式:

1.忽略这种信号,就是对这种信号不做任何处理。但是,对于  SIGKILL  和  SIGSTOP  信号,却不能忽略。因为这两种信号,通常被操作系统用来终止失去控制的进程。
2.捕捉该信号。当一个进程收到该信号时,就调用相应的信号处理函数,来对该信号做出相应的动作。
3.按系统默认方式处理信号,一般的默认动作是终止程序。

     2.signal函数

     当需要捕捉某种信号时,需要用到  signal  函数来注册处理该信号的函数。这个  signal  函数比较特殊,它的返回值是一个函数指针,这个函数指针指向之前处理该信号的函数。它有两个参数,第一个参数用来指定信号,第二个参数用来指定处理该信号的函数。它的函数原型如下:

       #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

举一个简单的  signal  的使用例子如下:
#include <signal.h>
#include <stdio.h>

static void sig_usr(int signo);

int main(void)
{
        if(signal(SIGUSR1,sig_usr)==SIG_ERR)
        {
                printf("signal(SIGUSR1) error\n");
                return -1;
        }

        if(signal(SIGUSR2,sig_usr)==SIG_ERR)
        {
                printf("signal(SIGUSR2) error\n");
                return -1;
        }

        for(;;)
        {
                pause();
        }

        return 0;
}

static void sig_usr(int signo)
{
        if(signo==SIGUSR1)
        {
                printf("received SIGUSR1\n");
        }
        else if(signo==SIGUSR2)
        {
                printf("received SIGUSR2\n");
        }
        else
        {
                printf("received signal:%d\n",signo);
        }
}
将程序编译为  a.out  之后,用下面的命令在后台运行:
./a.out &

在终端上输出程序的PID如下:

[1] 6212
我们输入如下命令:
kill -SIGUSR1 6212

可以看到程序输出
received SIGUSR1

     3.可再入函数

     在信号处理的过程中会产生一些问题,例如,当在  main  函数中正在调用  malloc  函数在堆上动态分配内存空间的时候,产生了一个信号,需要去执行这个信号的信号处理函数。而在这个信号处理函数中,也需要调用  malloc  函数,这时候就有可能发生问题。因为  malloc  为它分配的存储区保存一个链接表,而如果在main函数处理这张链接表时,在信号处理函数调用了  malloc,就使进程遭到了破坏。

     再举一个例子,如果在  main  函数中,刚刚调用完  getpwnam  函数,这时候来了一个信号,需要去执行信号处理函数,在这个函数中也许要调用  getpwnam  函数,这就造成了  main  函数调用  getpwnam  得到的信息丢失。

     通过上面的两个例子产生的问题,linux  规定了一个可再入函数表,这个表中列举了在信号处理函数中可以调用的函数。当然这张表中肯定没有包括  malloc  函数和  getpwnam  函数,这些函数有另一个名字,叫不可再入函数。

     就算对于可再入函数,我们知道每一个进程只有一个  errno ,如果在  main  函数中设定了 errno  的值以后,如果在信号处理程序中调用的某个可再入函数也可能会修改了  errno  的值。所以要求我们在信号处理程序前保存现场,在信号处理程序要结束时,恢复现场。

     4.kill和raise以及几个术语

     kill函数用来向指定的进程或者进程组发送信号,raise函数用来向进程自己发送信号。进程只能向和它所有者相同的进程发送信号,当然超级用户进程可以向各个进程发送信号。

     信号的产生:当造成信号的事件发生时,为进程产生一个信号。

     当产生了信号以后,内核会在进程表中设置某种形式的标记,这个过程称为信号递送。

     在信号产生和信号递送这个时间,称为信号未决。

     5.alarm函数和pause函数

     alarm  函数用来定时一段时间,当定时时间溢出时,会产生  SIGALRM  信号。pause  函数用来使进程挂起,直到捕捉到一个信号,并且等待执行完信号处理函数之后,pause  函数才会返回  -1  。它们的函数原型如下:

       #include <unistd.h>

       unsigned int alarm(unsigned int seconds);
       int pause(void);
alarm  函数的参数单位是秒,它的返回值是上一次调用  alarm  时剩余的时间。当参数  seconds  为  0  时,如果上次的定时时间还未到,则取消上次的定时,这是一个很常用的功能。

     alarm  和  pause  函数可以用来实现sleep函数,如下:

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

static void sig_alrm(int signo)
{
        return;
}

unsigned int sleep1(unsigned int nsecs)
{
        if(signal(SIGALRM,sig_alrm)==SIG_ERR)
        {
                printf("signal(SIGALRM) error\n");
                return nsecs;
        }
        alarm(nsecs);
        pause();
        return (alarm(0));
}

int main(void)
{
        printf("before sleep1\n");
        sleep1(2);
        printf("after sleep1\n");

        return 0;
}

在这个实现的函数中存在着一个竞争条件,当执行完  alarm(nsecs)  之后,可能还没有执行  pause  时,定时时间已经到了。这时执行  sig_alrm  信号处理函数,之后再执行  pause,如果系统中再没有信号产生,这就会使进程一直挂起。虽然这种情况极少发生,但是这总是一个  bug,当它发生错误时,会很难查找。可以用  setjmp  和  longjmp  来消除这个潜在的bug:
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>

static jmp_buf env_buf;

static void sig_alrm(int signo)
{
        longjmp(env_buf,1);
}

unsigned int sleep2(unsigned int nsecs)
{
        if(signal(SIGALRM,sig_alrm)==SIG_ERR)
        {
                printf("signal(SIGALRM) error\n");
                return nsecs;
        }

        if(setjmp(env_buf)==0)
        {
                alarm(nsecs);
                pause();
        }

        return (alarm(0));
}

int main(void)
{

        printf("before sleep1\n");
        sleep2(2);
        printf("after sleep1\n");

        return 0;
}


这样,即使在还没有执行  pause  时,时间就已经溢出了,也不会发上上面的情况。alarm  函数还用来为会阻塞的操作定时,防止它们进入永久阻塞的状态。这个功能类似于看门狗的功能,主要防止程序跑飞。