本文來自個人博客:https://dunkwan.cn
中斷的系統調用
早期UNIX系統的一個特性是:如果進程在執行一個低速系統調用而阻塞期間捕捉到一個信號,則該系統調用就被中斷不再繼續執行。該系統調用返回出錯,其errno
設置爲EINTR
。這樣處理是因爲一個信號發生了,進程捕捉到它,這意味着已經發生了某種事情,所以是個好機會應當喚醒阻塞的系統調用。
這裏系統調用應當與函數進行區分,當捕捉到某個信號時,被中斷的是內核中執行的系統調用。
爲了支持這種特性,將系統調用分爲兩類:低速系統調用和其他系統調用。低速系統調用是可能會使進程永遠阻塞的一類系統調用,包括:
- 如果某些類型文件的數據不存在,則讀操作可能會使調用者永遠阻塞;
- 如果這些數據不能被相同的類型文件立即接受,則寫操作可能會使調用者永遠阻塞;
- 在這種條件發生之前打開某些類型文件,可能會發生阻塞;
pause
函數和wait
函數;- 某些
ioctl
操作; - 某些進程間通信函數。
下面是幾種實現所提供的與信號有關的函數及它們的語義。
可重入函數
在信號處理程序中保證調用安全的函數,這些函數是可重入函數並被稱之爲異步信號安全的。以下是一些異步信號安全的函數。
測試示例:
#include "../../include/apue.h"
#include <pwd.h>
static void my_alarm(int signo)
{
struct passwd *rootptr;
printf("in signal handler\n");
if((rootptr = getpwnam("root")) == NULL)
err_sys("getpwnam(root) error");
alarm(1);
}
int main(void)
{
struct passwd *ptr;
signal(SIGALRM, my_alarm);
alarm(1);
for(;;){
if((ptr = getpwnam("dunk")) == NULL)
err_sys("getpwnam error");
if(strcmp(ptr->pw_name, "dunk") != 0)
printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);
}
}
在運行該程序時,結果具有隨機性。從這個示例中可以看出,如果在信號處理程序中調用一個非可重入函數,則其結果是不可知的。
SIGCLD
語義
SIGCLD
和SIGCHLD
這兩個信號很容易被混淆。SIGCLD
是system V
的一個信號名,其語義與名爲SIGCHLD
的BSD信號不同。POSIX.1採用BSD的SIGCHLD
信號。
對於SIGCLD
的早期處理方式是:
- 如果進程明確地將該信號的配置設置爲
SIG_IGN
,則調用進程的子進程將不產生殭屍進程。 - 如果將
SIGCLD
的配置設置爲捕捉,則內核立即檢查是否有子進程準備好被等待,如果是這樣,則調用SIGCLD
處理程序。
測試示例:
#include "../../include/apue.h"
#include <sys/wait.h>
static void sig_cld(int);
int main(void)
{
pid_t pid;
if(signal(SIGCHLD, sig_cld) == SIG_ERR)
perror("signal error");
if((pid = fork()) < 0){
perror("fork error");
}else if(pid == 0){
sleep(2);
_exit(0);
}
pause();
return 0;
}
static void sig_cld(int signo)
{
pid_t pid;
int status;
printf("SIGCHLD received\n");
if(signal(SIGCHLD, sig_cld) == SIG_ERR)
perror("signal error");
if((pid = wait(&status)) < 0)
perror("wait error");
printf("pid = %d\n", pid);
}
結果如下:
可靠信號術語和語義
- 造成信號的事件:硬件異常(除數爲0)、軟件條件(alarm定時器超時)、終端產生的信號或調用
kill
函數。- 當一個信號產生時,內核通常在進程表中以某種形式設置一個標誌,這種行爲稱之爲向進程遞送了一個信號。在信號產生和遞送之間的時間間隔內,稱信號是未決的(pending)。
- POSIX.1允許系統遞送該信號一次或多次,如果信號被遞送多次,則稱信號進行了排隊。
- 每個進程都有一個信號屏蔽字,它規定了當前要阻塞遞送到該進程的信號集。
- 信號編號可能會超過一個整型所包含的二進制位數,因此POSIX.1定義了一個新數據類型
sigset_t
,它可容納一個信號集。