产生信号的条件主要有:
A. 通过终端按键产生信号
用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如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:
B. 硬件异常产生信号
这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以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;
}
C. 调用系统函数向进程发信号
首先在后台执行死循环程序,然后⽤用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的输出的切换。
可以看出,一秒钟比上一个代码计数多了好多倍,说明有输出的会延缓计数。