zoukankan      html  css  js  c++  java
  • 2.2 linux中的信号分析

    信号:

      信号是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。

      信号是因为某些错误条件而产生的,比如内存段冲突、浮点处理器错误或者非法指令等。

      信号是在软件层次上对中断的一种模拟,所以通常把它称为是软中断。

      信号和中断的区别:  

        相似点:

          采用了相同的异步通信方式。

          当检测出有信号或者中断请求时,都暂停正在执行的程序而转去执行相应的处理程序。

          都在处理完毕后返回到原来的断点。

          对信号和中断都可以进行屏蔽。

        区别:

          中断有优先级,而信号没有优先级,所有的信号都是平等的。

          信号处理程序都是在用户态下运行的,而中断处理程序是在核心态下运行。

          中断响应是及时的,而信号响应通常都有较大的延迟。

      常用的信号如下所示:

    进程对信号的三种响应如下:

    man 7 signal可以查看信号的默认动作和信号的含义。

      signal用于安装一个信号处理函数,原型如下:

      __sighandler_t  signal(int signum, __sighandler_t  handler)

      signal是一个带signum和handler两个参数的函数,准备捕捉或者屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数由handler给出。

      handler这个函数必须有一个int型参数(即接收到的信号代码),它本身的类型是void。

      handler也可以是下面两个特殊值:

        SIG_IGN  忽略该信号

        SIG_DFL  恢复默认行为

    示例程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <signal.h>
     4 
     5 
     6 void handler(int num)
     7 {
     8     printf("recv signal num = %d
    ", num);
     9 }
    10 
    11 
    12 int main()
    13 {
    14     pid_t pid;
    15     
    16     signal(SIGCHLD, SIG_IGN);
    17     
    18     pid = fork();
    19     
    20     if(pid == -1)
    21     {
    22         perror("fork error");
    23         exit(0);
    24     }
    25     
    26     if(pid == 0)
    27     {
    28         printf("child ... 
    ");
    29         exit(0);
    30     }
    31     while(1)
    32     {
    33         pause();    
    34     }
    35     
    36     return 0;
    37 }

    程序中,我们注册信号时,表示父进程忽略子进程的退出信号,因此,执行结果如下:

    默认情况下(当我们不使用signal信号时),父进程会在退出的时候给子进程收尸(前提是子进程先死),可用如下程序证明:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 
     7 void handler(int num)
     8 {
     9     printf("recv signal num = %d
    ", num);
    10 }
    11 
    12 
    13 int main()
    14 {
    15     pid_t pid;
    16     
    17     //signal(SIGCHLD, SIG_IGN);
    18     
    19     pid = fork();
    20     
    21     if(pid == -1)
    22     {
    23         perror("fork error");
    24         exit(0);
    25     }
    26     
    27     if(pid == 0)
    28     {
    29         printf("child ... 
    ");
    30         exit(0);
    31     }
    32     
    33     sleep(5);
    34     //while(1)
    35     //{
    36     //    pause();    
    37     //}
    38     
    39     return 0;
    40 }

    我们让父进程睡眠5秒,保证子进程先死,当父进程还在睡眠时,我们在另一个中断执行ps -ef,结果如下:

    可以看到,父进程睡眠期间,子进程已经死了,而且处于僵尸状态,但是当父进程也结束时,子进程的僵尸状态消失了,说明父进程给它收尸了。当把17行的注释打开时,就是告诉内核,子进程死的时候让内核给他收尸,因此,我们看不到子进程处于僵尸状态的现象了(内核收尸太快了)。

      下面我们演示注册一个真正的信号处理函数和恢复默认行为的程序:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 
     7 void handler(int num)
     8 {
     9     printf("recv signal num = %d
    ", num);
    10 }
    11 
    12 
    13 int main()
    14 {
    15     pid_t pid;
    16     char c;
    17     
    18     signal(SIGINT, handler);
    19     
    20     while( (c = getchar()) != 'a')
    21     {
    22         pause();
    23     }
    24     
    25     signal(SIGINT, SIG_DFL);
    26     while(1)
    27     {
    28         pause();    
    29     }
    30     
    31     return 0;
    32 }

    SIGINT代表ctrl+c信号,18行将这个信号的处理函数注册为handler,当程序停在20行时,我们按下ctrl+c,handler会得到执行。 输入a使程序执行到26行,这时候SIGINT信号的行为恢复到了默认行为(25行中的SIG_DFL表示恢复默认行为),我们再按下ctrl+c,程序直接退出了(这就是默认行为)。执行现象如下:

       signal函数执行成功时,返回默认的处理函数,执行失败时返回SIG_ERR。我们将上述程序改成以下方式:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 void handler(int num)
     7 {
     8     printf("recv signal num = %d
    ", num);
     9 }
    10 
    11 int main()
    12 {
    13     pid_t pid;
    14     char c;
    15     
    16     __sighandler_t old = signal(SIGINT, handler);
    17     
    18     if(SIG_ERR == old)
    19     {
    20         perror("signal error");
    21     }
    22     
    23     while( (c = getchar()) != 'a')
    24     {
    25         pause();
    26     }
    27     
    28     signal(SIGINT, old);
    29     printf("huan yuan
    ");
    30     while(1)
    31     {
    32         pause();    
    33     }
    34     
    35     return 0;
    36 }

    使用signal注册信号处理函数时,将默认的处理函数返回到old中,并在下面进行了错误处理,28行恢复默认行为,执行结果如下:

     信号的分类:可靠与不可靠信号,实时与非实时信号。实时信号都是可靠信号,非实时信号都是不可靠信号。1-31号都是不可靠信号。不可靠信号就是向应用程序发送了多次信号,应用程序可能只接收到了一次。可靠信号就是向应用程序发几次信号都能保证全接收到。早期unix系统每接收到一个信号就将处理程序恢复到默认行为。现在的linux中的不可靠信号主要指信号可能会丢失。

    信号发送函数kill和raise,如下所示:

    kill可以向指定进程发送信号,raise向自身发送信号。 kill是示例程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 void my_handler(int num)
     7 {
     8     if(SIGINT == num)
     9     {
    10         printf("recv signal SIGINT
    ");
    11     }
    12     else if(SIGUSR1 == num)
    13     {
    14         printf("recv signal SIGUSR1
    ");
    15     }
    16     else
    17     {
    18         printf("recv signal num = %d
    ", num);
    19     }
    20 }
    21 
    22 int main()
    23 {
    24     pid_t pid;
    25     
    26     if(signal(SIGINT, my_handler) == SIG_ERR)
    27     {
    28         perror("signal error");
    29     }
    30     
    31     if(signal(SIGUSR1, my_handler) == SIG_ERR)
    32     {
    33         perror("signal error");
    34     }
    35     
    36     pid = fork();
    37     
    38     if(pid == -1)
    39     {
    40         perror("fork error");
    41         exit(0);
    42     }
    43     
    44     if(pid == 0)
    45     {
    46         pid = getppid();
    47         kill(pid, SIGUSR1);
    48         exit(0);
    49     }
    50     
    51     int nsleep = 5;
    52     
    53     do
    54     {
    55         printf("parent process begin sleep
    ");
    56         nsleep = sleep(nsleep);
    57         printf("parent process end sleep
    ");
    58     }while(nsleep > 0);
    59     
    60     return 0;
    61 }

    子进程通过kill向父进程发信号,父进程在睡眠,当收到信号时就去执行信号处理函数,执行完之后,发现nsleep不为0,也即没有睡够指定的时间,因此再次进入睡眠,直到睡完为止。执行结果如下:

    小知识:

      getpgrp()获取进程组id, kill(pid, SIGINT)向进程组发送SIGINT。父进程注册的信号处理函数也会复制给子进程(在fork之前注册信号)。sleep函数返回值是剩余的秒数。sleep能被信号打断,是可中断睡眠,处理信号函数返回以后就不再睡眠了,继续向下执行。wait使进程进入的睡眠也是可中断睡眠。

    pause函数:

      将进程置为可中断睡眠状态,然后它调用内核函数schedule(),使linux进程调度器找到另一个进程来运行。

      pause使调用者进程挂起,直到一个信号被捕获。

    alarm函数:

      设置一个闹钟,延迟发送信号,告诉linux内核n秒钟以后,给本进程发送SIGALRM信号。示例程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 
     7 void my_handler(int num)
     8 {
     9     printf("recv signal num = %d
    ", num);
    10 }
    11 
    12 int main()
    13 {
    14     if(signal(SIGALRM, my_handler) == SIG_ERR)
    15     {
    16         perror("signal error");
    17         exit(0);
    18     }
    19     
    20     alarm(3);
    21     
    22     pause();
    23     printf("return from pause  
    ");
    24     
    25     return 0;
    26 }

    执行结果如下所示:

    可重入与不可重入:

    示例程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 typedef struct _Teacher
     7 {
     8     int age;
     9     int num;
    10 }Teacher;
    11 
    12 Teacher g_t;
    13 
    14 void printGlobalTeacher()
    15 {
    16     printf("g_t.age = %d
    ", g_t.age);
    17     printf("g_t.num = %d
    ", g_t.num);
    18 }
    19 
    20 void my_handler(int num)
    21 {
    22     printf("recv signal num = %d
    ", num);
    23     printGlobalTeacher();
    24     alarm(1);
    25 }
    26 
    27 int main()
    28 {
    29     Teacher t1,t2;
    30     t1.age = 30;
    31     t1.num = 30;
    32     t2.age = 40;
    33     t2.num = 40;
    34     
    35     if(signal(SIGALRM, my_handler) == SIG_ERR)
    36     {
    37         perror("signal error");
    38         exit(0);
    39     }
    40     
    41     alarm(1);
    42     
    43     while(1)
    44     {
    45         g_t = t1;
    46         g_t = t2;
    47         //printf("return from pause  
    ");
    48     }
    49     return 0;
    50 }

    上述程序中在信号处理函数中调用printGlobalTeacher函数,这个函数是不可重入的函数,因为函数内部访问了全局变量g_t, 因此打印结果有可能出错。执行结果如下:

    可以看到出现了age和num不相等的情况,这就是出错了。

  • 相关阅读:
    计算机硕士工资一览表 08年最新各大IT公司薪水行
    VS2010单元测试
    windows操作系统的快捷键
    关于
    [美国代购] Nexus 6 与 Moto X 询价聊天记录整理
    nginxのerror_logはformat指定できない.
    Give $20/month and provide 480 hours of free education
    如何才可能将一件事情做到最高的效率
    Palindrome Number
    Reverse Integer
  • 原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9369048.html
Copyright © 2011-2022 走看看