Linux进程信号

匿名 (未验证) 提交于 2019-12-02 21:59:42

信号的基本概念


signal.h中找到,例如其中有定义#define SIGINT 2。编号34以上的是实时信号,34以下的信号是普通信号。而这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明,在命令行上输入man 7 signal:

产生信号的方式:

进程收到信号后,常见的信号处理动作有以下三种:

  1. 忽略此信号。

前台进程与后台进程

//sig.c #include <stdio.h>  int main(){     while(1);     return 0; }


2.Ctrl-C产生的信号只能发给前台进程,不能发给后台进程。
前台进程:

后台进程:

3.后台进程使Shell不必等待进程结束就可以接受新的命令。而前台进程运行时占用SHELL,它运行的时候SHELL不能接受其他命令。

4.Shell可以同时运行一个前台进程和任意多个后台进程。

异步(Asynchronous)的。后台进程不是任一进程都能做,要看实际情况。一般来说,如果某进程不需从键盘输入输出(交互少的)或者执行所需时间较长的话,就比较合适做后台进程。

产生信号

产生信号的方式在前面已经提到,简单来说可以归结为以下4点:

  1. 通过终端按键(组合键)产生信号

  2. 硬件异常产生的信号

  3. 调用系统函数向进程发信号

  4. 由软件条件产生信号

1.通过终端按键(组合键)产生信号:
Core Dump:






2.调用系统函数向进程发信号:

kill:给某个进程发送某个信号
函数原型:

#include <signal.h> int kill(pid_t pid, int signo); //成功返回0,错误返回-1。

模拟实现kill:

#include <stdio.h> #include <sys/types.h> #include <signal.h> #include <stdlib.h>  void usage(const char* proc){     printf("Usage: %s signo pid\n", proc); }  int main(int argc, char* argv[]){     if(argc != 3){         usage(argv[0]);         return 0;     }     int signo = atoi(argv[1]);     int pid = atoi(argv[2]);     kill(pid, signo);     return 0; }

raise:给自己发送某个信号
函数原型:

#include <signal.h> int raise(int signo); //成功返回0,错误返回-1。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h>  int main(){     while(1){         sleep(1);         raise(3);     }     return 0; }

执行1秒后:

也就是该进程执行1秒后,遇到raise函数,发出3号信号,导致程序异常终止。

abort:给自己发送 SIGABRT 信号
函数原型:

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h>  int main(){     while(1){         sleep(1);         abort();     }     return 0; }

执行1秒后,遇到abort函数,发出 SIGABRT 信号,导致程序异常终止

3.由软件条件产生信号:

闹钟信号:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <stdint.h> #include <signal.h>  void MyHandler(int sig, uint64_t count){     (void) sig;     printf("count = %lu\n", count);     exit(0); }  int main(){     uint64_t count = 0;     signal(SIGALRM, MyHandler);     alarm(3);     while(1){         ++count;     }     return 0; } 

捕捉信号

Linux内核实现信号捕捉流程:

特点:

  1. 整体执行顺序∞
  2. 信号处理函数与原有的main函数是两个不同的执行流
  3. 信号处理未执行完以前,原有的main函数一直挂起等待

有关捕捉信号的函数:
pause:

#include <unistd.h> int pause(void);

模拟实现sleep函数:

#include <stdio.h> #include <unistd.h> #include <signal.h>  void MyHandler(int sig){     (void) sig; }  void MySleep(int second){     //1.捕捉 SIGALRM 信号,并且要把原来的信号处理方法备份下来     __sighandler_t old_handler = signal(SIGALRM, MyHandler);     //2.通过 alarm 注册一个闹钟     alarm(second);     //3.需要借助一个函数来等待闹钟信号的到来     //  在闹钟信号到来之前,挂起等待     //  一旦闹钟信号来了,继续往后执行     pause();     //4.恢复 SIGALRM 的信号处理方式     //  如果不恢复的话,就可能影响到其他信号     signal(SIGALRM, old_handler);     return; }  int main(){     printf("before sleep...\n");     MySleep(3);     printf("after sleep...\n");     return 0; }

阻塞信号

  • 进程可以选择阻塞(Block )某个信号。

2.信号集操作函数

3.sigprocmask

#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oset); //返回值:若成功则为0,若出错则为-1


4.sigpending

利用以上函数,进行简单的测试:

#include <stdio.h> #include <unistd.h> #include <signal.h>  void PrintSigset(sigset_t* set){     int i = 1;     for(; i < 32; ++i){         if(sigismember(set, i)){   //判断指定信号是否在目标集合中             printf("1");         }         else{             printf("0");         }     }     printf("\n"); }  void MyHandler(int sig){     printf("sig = %d\n", sig); }  int main(){     //1.捕捉 SIGINT 信号     signal(SIGINT, MyHandler);     //2.把 SIGINT 信号屏蔽掉     sigset_t set;     sigset_t oset;     //  清空初始化     sigemptyset(&set);     sigaddset(&set, SIGINT);     //  设置阻塞信号集,阻塞SIGINT信号     sigprocmask(SIG_BLOCK, &set, &oset);     //3.循环读取未决信号集     int count = 0;     while(1){         ++count;         if(count >= 6){             count = 0;             //解除信号屏蔽字,再设置上             printf("解除信号屏蔽字!\n");             sigprocmask(SIG_SETMASK, &oset, NULL);             sleep(1);             printf("再次设置信号屏蔽字!\n");             sigprocmask(SIG_BLOCK, &set, &oset);         }         sigset_t pending_set;         //获取未决信号集         sigpending(&pending_set);         PrintSigset(&pending_set);         sleep(1);     }     return 0; }

实现的效果如下:

同一进程中的同一个函数被不同执行流调用称为重入。

判断依据:是否有逻辑问题
同一个函数在多个执行流中同时调用,没有逻辑问题,称为可重入。
同一个函数在多个执行流中同时调用,有逻辑问题,称为不可重入。

  • 调用了其他不可重入函数。
  • 使用了非常量的全局变量/静态变量。

如果信号处理函数为不可重入函数,就可能出现逻辑问题。

volatile

作用:防止编译器过度优化而导致变量未正常读取(从寄存器中读取)

竞态条件与sigsuspend函数

现在重新审视“mysleep”程序,设想这样的时序:

  1. 注册SIGALRM信号的处理函数。
  2. nsecs秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。
  3. 可是SIGALRM信号已经处理完了,还等待什么呢?

#include <signal.h> int sigsuspend(const sigset_t *sigmask);

#include <stdio.h> #include <unistd.h> #include <signal.h>  void MyHandler(int sig){     (void) sig; }  //这个版本的函数来解决竞态条件问题 void MySleep(int second){     //1.捕捉 SIGALRM 信号,并且要把原来的信号处理方法备份下来     __sighandler_t old_handler = signal(SIGALRM, MyHandler);      //2.屏蔽闹钟信号     sigset_t set, oset;     sigemptyset(&set);     sigaddset(&set, SIGALRM);     sigprocmask(SIG_BLOCK, &set, &oset);      //3.通过 alarm 注册一个闹钟     alarm(second);      //4.需要借助一个函数来等待闹钟信号的到来     //  在闹钟信号到来之前,挂起等待     //  一旦闹钟信号来了,继续往后执行     //  此处不能使用 pause, 而要使用 sigsuspend 来进行替换     //  sigsuspend 能够做到原子的进行解除屏蔽+等待     sigset_t tmp_set = oset;     //  此处我们基于oset备份了一份tmp_set     //  但是 tmp_set 里面可能也包含了对 SIGALRM 的屏蔽     sigdelset(&tmp_set, SIGALRM);     sigsuspend(&tmp_set);      //5.恢复 SIGALRM 的信号处理方式     //  如果不恢复的话,就可能影响到其他信号     signal(SIGALRM, old_handler);      //6.恢复信号屏蔽字     sigprocmask(SIG_SETMASK, &oset, NULL);     return; }  int main(){     printf("before sleep...\n");     MySleep(3);     printf("after sleep...\n");     return 0; }

SIGCHLD信号

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h> #include <sys/wait.h>  void MyHandler(int sig){     (void) sig;     //int ret = wait(NULL);      //若用wait,会因wait数量不匹配而导致产生僵尸进程     //printf("MyHandler:%d\n", ret);     while(1){         int ret = waitpid(-1, NULL, WNOHANG);         if(ret > 0){             printf("waitpid %d\n", ret);             continue;         }         else if(ret == 0){             //子进程还存在,没执行完,直接让信号处理函数返回             //如果后续的子进程再结束,还会再次发送 SIGCHLD 信号             break;         }         else{             //子进程都结束             break;         }     } }  int main(){     signal(SIGCHLD, MyHandler);     int i = 0;     for(; i < 20; ++i){         pid_t ret = fork();         if(ret < 0){             perror("fork");             return 1;         }         if(ret == 0){             //child             printf("child %d\n", getpid());             sleep(3);             exit(0);         }     }     while(1){         printf("father working\n");         sleep(1);     }     return 0; }

文章来源: Linux进程信号
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!