用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信 号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号。
SIGINT的默认处理动作是终止进程, SIGQUIT的默认处理动作是终止进程并且Core Dump,
首先解释什么是Core Dump(核心转储)。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。
进程异常终止通常是因为有 Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做 Post-mortem Debug(事后调试)。一个进程允许产生多大的core⽂文件取决于进程的 Resource Limit(这个信息保存 在PCB中)。默认是不允许产⽣生core文件的,因为core文件中 可能包含用户密码等敏感信息,不安全。
在开发调试阶段可以用ulimit命令改变这个限制, 允许产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K:
test.c源代码:
#include <stdio.h> int main() { while(1); return 0; }因为ulimit命令改变了Shell进程的Resource Limit, test进程的PCB由Shell进程复制⽽而来,所以 也具有和Shell进程相同的Resource Limit值,这样就可以产生Core文件了。
进行调试test:
这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为 SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核 将 这个异常解释为SIGSEGV信号发送给进程。
file.c 源代码:
#include <stdio.h> void handler(int data) { printf("sig is %d\n", data); } int main() { signal(11, handler); int ret = 1/0; return 0; }首先在后台执行死循环程序,然后⽤用kill命令给它发SIGSEGV信号。
源代码:
#include <stdio.h> int main() { while(1); return 0; }2829是test进程的id。之所以要再次回车才显示Segmentation fault,是因为在2829进程终止掉 之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望 Segmentation fault信息和用 户的输入交错在一起,所以等用户输入命令之后才显示。 指定某种信号的kill命令可以有多种写法,上面的命令还可以写成kill -SIGSEGV 4568 或kill -11 4568, 11是信号SIGSEGV的编号。以往遇 到的段错误都是由非法内存访问产 生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误。
kill命令是调用kill函数实现的。 kill函数可以给一个指定的进程发送指定的信号。 raise函数可 以给当前进程发送指定的信号(自己给自己发信号)。
#include <signal.h> int kill(pid_t pid, int signo); int raise(int signo); //这两个函数都是成功返回0,错误返回-1。源代码:
#include <stdio.h> #include<signal.h> void handler(int data) { printf("sig is %d\n", data); } int main() { signal(2, handler); sleep(3); while(1) { raise(2); sleep(1); } }运行结果图:
abort函数使当前进程接收到 信号而异常终止。
#include <stdlib.h> void abort(void); //就像exit函数一样,abort函数总是会成功的,所以没有返回值。运行结果图:
pause()函数使该进程暂停让出CPU
#include <unistd.h> int pause(void);源代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> void sig_handler(int num) { printf("receive the signal %d.\n", num); alarm(2); } int main() { signal(SIGALRM, sig_handler); alarm(2); while(1){ pause(); printf("pause is over.\n"); } exit(0); }运行结果图:
可以看出程序每隔2秒就会收到信号14,也就是SIGALRM信号;并且当处理完该信号之后,直接执行pause()函数下面的语句;说明pause()是可被中断的暂停;
alarm函数 和SIGALRM信号
#include <unistd.h> unsigned int alarm(unsigned int seconds);调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发 SIGALRM信号, 该信号的默认处理动作是终止当前进程。这个函数的返回值是0或者是以前 设定的闹钟时间还余下 的秒数。
源代码:
#include <stdio.h> int main() { alarm(1); int count = 0; while(1) { printf("count is %d\n", count); count++; } } //这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终⽌止运行结果图:
源代码:
#include <stdio.h> #include <unistd.h> int count = 0; void handler(int data) { printf("count is %d\n", count); } int main() { signal(14, handler); alarm(1); while(1) { count++; } return 0; } //这个函数遇上个函数作用相同,不同的是省去了I/O的输出的切换。可以看出,一秒钟比上一个代码计数多了好多倍,说明有输出的会延缓计数。