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信號終止。