我们在终端当中最常用的就是ctrl+c 和 ctrl + z 去中断当前终端正在执行的程序,其实这些也是给我们的程序发送信号,ctrl+c发送SIGINT信号ctrl+z发送SIGTSTP信号 。因此和上面的机制类似,我们可以使用处理函数重写的方式,覆盖对应的信号的行为,比如下面的程序就是使用处理函数重写的方式进行信号处理:
#include <stdio.h>#include <signal.h>#include <string.h>#include <stdlib.h>#include <fcntl.h>#include <unistd.h>void sig(int no) {char out[128];switch(no) {case SIGINT:sprintf(out, "received SIGINT signal\n");break;case SIGTSTP:sprintf(out, "received SIGSTOP signal\n");break;}write(STDOUT_FILENO, out, strlen(out));}int main() {signal(SIGINT, sig);signal(SIGTSTP, sig);while(1) {sleep(1);}return 0;}
现在我们执行这个程序然后看看当我们输入ctrl+z和ctrl+c会出现有什么输出 。

文章插图
从上面的输出我们可以看到实现了我们想要的输出结果,说明我们的函数重写生效了 。
段错误的魔幻这里有另外一个会产生SIGSEGV信号的程序,我们看看这个程序的输出是什么:
#include <stdio.h>#include <unistd.h>#include <signal.h>void sig(int n) {write(STDOUT_FILENO, "a", 1); // 这个函数就是向标准输出输出一个字符 a}int main() {signal(SIGSEGV, sig); // 这个是注册一个 SIGSEGV 错误的处理函数 当操作系统给进程发送一个 SIGSEGV 信号之后这个函数就会被执行int* p;printf("%d\n", *p); // 解引用一个没有定义的指针 造成 segementation faultreturn 0;}
我们知道上面的程序肯定会产生 segmentation fault 错误,会收到 SIGSGEV 信号,肯定会执行到函数sig
。但是上面的程序会不断的输出a
产生死循环 。
文章插图
上面程序的结果是不是有点难以理解,如果想要了解这个程序的行为,我们就需要了解操作系统是如何处理 segmentation fault 的,了解这个处理过程之后对上面程序的输出就很容易理解了 。
信号处理函数的执行过程当我们的进程接收到信号会去执行我们重写的信号处理函数,如果在我们的信号处理函数当中没有退出程序或者转移程序的执行流(可以使用setjmp和longjmp实现),即调用函数正常返回 。信号处理函数返回之后会重新执行信号发生位置的指令,也就是说哪条指令导致操作系统给进程发送信号,那条条指令在信号处理函数返回的时候仍然会被执行,因此我们才看到了上面的输出结果,因为系统会不断的执行那条发生了 segmentation fault 的指令 。
那么我们如何修正我们的代码,让程序不进入死循环,让程序能够得到我们的接管呢 。有两种办法:
- 一种是在信号处理函数当中进行一些逻辑处理之后然后,使用系统调用_exit直接退出 。
- 另外一种使用setjmp和longjmp进行执行流的跳转 。
#include <stdio.h>#include <signal.h>#include <unistd.h>void sig(int n) {printf("直接在这里退出\n");_exit(1); // 使用系统调用直接退出}int main() {signal(SIGSEGV, sig);*(int*) NULL = 0;printf("结束\n"); // 这个打印不会输出return 0;}

文章插图
使用控制流跳转
#include <stdio.h>#include <signal.h>#include <setjmp.h>jmp_buf env;void sig(int n) {printf("准备回到主函数\n");longjmp(env, 1);}int main() {signal(SIGSEGV, sig);if(!setjmp(env)) {printf("产生段错误\n");*(int*) NULL = 0;}else {printf("回到了主函数\n");}return 0;}
经验总结扩展阅读
- VS Code For Web 深入浅出 -- 进程间通信篇
- Java程序员必会Synchronized底层原理剖析
- VS Code For Web 深入浅出 -- 导读篇
- Spring 深入——IoC 容器 01
- vue2双向绑定原理:深入响应式原理defineProperty、watcher、get、set
- vue2.x核心源码深入浅出,我还是去看源码了
- 深入理解AQS--jdk层面管程实现【管程详解的补充】
- 面对爱情总是浅尝即止,不会深入的星座
- 深入底层C源码 Redis核心设计原理
- flutter系列之:深入理解布局的基础constraints