linux 捕捉组合键信号,Linux进程信号

信号的基本概念

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

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

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

产生信号的方式:

进程收到信号后,常见的信号处理动作有以下三种: 忽略此信号。前台进程与后台进程 //sig.c #include int main(){ while(1); return 0; }

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

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

前台进程:

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

后台进程:

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

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

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

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

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

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

产生信号的方式在前面已经提到,简单来说可以归结为以下4点: 通过终端按键(组合键)产生信号

硬件异常产生的信号

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

由软件条件产生信号

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

Core Dump:

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

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

kill:给某个进程发送某个信号

函数原型: #include int kill(pid_t pid, int signo); //成功返回0,错误返回-1。

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

模拟实现kill: #include #include #include #include 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; }

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

raise:给自己发送某个信号

函数原型: #include int raise(int signo); //成功返回0,错误返回-1。 #include #include #include #include int main(){ while(1){ sleep(1); raise(3); } return 0; }

执行1秒后:

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

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

abort:给自己发送 SIGABRT 信号

函数原型: #include #include #include #include int main(){ while(1){ sleep(1); abort(); } return 0; }

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

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

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

闹钟信号: #include #include #include #include #include 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内核实现信号捕捉流程:

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

特点: 整体执行顺序∞

信号处理函数与原有的main函数是两个不同的执行流

信号处理未执行完以前,原有的main函数一直挂起等待

有关捕捉信号的函数:

pause: #include int pause(void);

模拟实现sleep函数: #include #include #include 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 )某个信号。

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

2.信号集操作函数

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

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

4.sigpending

利用以上函数,进行简单的测试: #include #include #include 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; }

实现的效果如下:

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

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

判断依据:是否有逻辑问题

同一个函数在多个执行流中同时调用,没有逻辑问题,称为可重入。

同一个函数在多个执行流中同时调用,有逻辑问题,称为不可重入。

调用了其他不可重入函数。

使用了非常量的全局变量/静态变量。

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

作用:防止编译器过度优化而导致变量未正常读取(从寄存器中读取) 竞态条件与sigsuspend函数

现在重新审视“mysleep”程序,设想这样的时序: 注册SIGALRM信号的处理函数。

nsecs秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。

可是SIGALRM信号已经处理完了,还等待什么呢?

#include int sigsuspend(const sigset_t *sigmask);

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

yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值