Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

2. 产生信号

2.1. 通过终端按键产生信号

上一节讲过, 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:

$ ulimit -c 1024

然后写一个死循环程序:

#include <unistd.h>

int main(void)
{
	while(1);
	return 0;
}

前台运行这个程序,然后在终端键入 Ctrl-C 或 Ctrl-\:

$ ./a.out
(按 Ctrl-C)
$ ./a.out
(按 Ctrl-\)Quit (core dumped)
$ ls -l core*
-rw------- 1 akaedu akaedu 147456 2008-11-05 23:40 core

ulimit 命令改变了 Shell 进程的 Resource Limit,a.out 进程的 PCB 由 Shell 进程复制而来,所以也具有和 Shell 进程相同的 Resource Limit 值,这样就可以产生 Core Dump 了。

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

仍以上一节的死循环程序为例,首先在后台执行这个程序,然后用 kill 命令给它发 SIGSEGV 信号。

$ ./a.out &
[1] 7940
$ kill -SIGSEGV 7940
$(再次回车)
[1]+  Segmentation fault      (core dumped) ./a.out

7940 是 a.out 进程的 id。之所以要再次回车才显示 Segmentation fault ,是因为在 7940 进程终止掉之前已经回到了 Shell 提示符等待用户输入下一条命令,Shell 不希望 Segmentation fault 信息和用户的输入交错在一起,所以等用户输入命令之后才显示。指定某种信号的 kill 命令可以有多种写法,上面的命令还可以写成 kill -SEGV 7940kill -11 7940 ,11 是信号 SIGSEGV 的编号。以往遇到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发 SIGSEGV 也能产生段错误。

kill 命令是调用kill 函数实现的。kill 函数可以给一个指定的进程发送指定的信号。raise 函数可以给当前进程发送指定的信号(自己给自己发信号)。

#include <signal.h>

int kill(pid_t pid, int signo);
int raise(int signo);

这两个函数都是成功返回 0,错误返回-1。

abort 函数使当前进程接收到SIGABRT 信号而异常终止。

#include <stdlib.h>

void abort(void);

就像 exit 函数一样, abort 函数总是会成功的,所以没有返回值。

2.3. 由软件条件产生信号

SIGPIPE 是一种由软件条件产生的信号,在例 30.7 “管道”中已经介绍过了。本节主要介绍alarm 函数和SIGALRM 信号。

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

调用 alarm 函数可以设定一个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发 SIGALRM 信号,该信号的默认处理动作是终止当前进程。这个函数的返回值是 0 或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为 30 分钟之后响,20 分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为 15 分钟之后响,“以前设定的闹钟时间还余下的时间”就是 10 分钟。如果 seconds 值为 0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。

例 33.1. alarm

#include <unistd.h>
#include <stdio.h>

int main(void)
{
	int counter;
	alarm(1);
	for(counter=0; 1; counter++)
		printf("counter=%d ", counter);
	return 0;
}

这个程序的作用是 1 秒钟之内不停地数数,1 秒钟到了就被 SIGALRM 信号终止。