訊號是Linux程序間通訊的最古老的一種方式。訊號是軟體中斷,是一種非同步通訊的方式。訊號可以導致一個正在執行的程序被另一個正在執行的非同步程序中斷,轉而處理某個突發事件。
一旦產生訊號,就要執行訊號處理常式,處理完訊號處理常式,再回來執行主函數,這就是中斷。
一個完整的訊號週期包括三個部分:訊號的產生,訊號在程序中的註冊,訊號的程序中的銷燬,執行訊號處理常式。
注意:這裡訊號的產生、註冊、登出都是訊號的內部機制,而不是訊號的函數實現。
可以通過kill -l
命令檢視系統定義的訊號列表:
可以看到,不存在編號為0的訊號。其中1-31號訊號成為常規訊號(也叫普通訊號和標準訊號),34-64稱為實時訊號,驅動程式設計與硬體相關。名字上差別不大,而前32個名字各不相同。
如果你想了解某個訊號的產生條件和預設處理動作,可以通過指令man signal_id signal
例如: man 2 signal 就是檢視二號訊號的作用
可以看到有一些訊號具有三個「value」,第一個值通常對alpha和sparc架構有用,中間值針對x86和arm架構,最後一個應用於mips架構。
不同的作業系統定義了不同的系統訊號,這裡我們之研究linux系統中的訊號。
Action為預設動作:
當用戶按下某些終端按鍵的時候,將產生訊號
Ctrl C
可以傳送2號訊號(SIG_INT),預設處理動作是終止程序Ctrl \
,傳送3號訊號(SIG_QUIT),預設處理動作是終止程序並且Core DumpCtrl z
查安生終端訊號SIGSTOP ,預設動作是暫停程序的執行Core Dump是什麼?
當一個程序要異常終止時,可以選擇把程序的使用者空間記憶體資料全部儲存到磁碟上,檔名通常是core,這叫做Core Dump。我們可以通過使用gdb偵錯檢視core檔案檢視程序退出的原因,這也叫事後偵錯。
下面介紹三個系統函數
(1)kill函數
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);
功能:給指定程序傳送指定訊號(不一定殺死)
引數:
pid:取值有4種情況:
pid > 0:將訊號傳送給程序ID為pid的程序。
pid = 0:將訊號傳送給當前程序所在行程群組中的所有程序。
pid = -1:將訊號傳送給系統內所有的程序。
pid < -1:將訊號傳給指定行程群組的所有程序。這個行程群組號等於pid的絕對值。
sig:訊號的編號,這裡可以填數位編號,也可以填訊號的宏定義,可以通過
命令kill -l進行相應檢視。不準薦直接使數位,應使用宏名,因為不同作業系統訊號編號可能不同,但名稱一致。
返回值.
成功:0
失敗:-1
程式碼範例:
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid;
//建立一個子程序
pid = fork();
{
printf("child process do work.....\n");
sleep(1);
}
exit(0);//子程序退出
}
else
{
//父程序
sleep(3);
printf("子程序不聽話了,該退出了....\n");
kill(pid,15);
printf("父程序該結束了,已經完成了它的使命\n");
}
return 0;
}
執行結果如下:
(2)raise函數
#include<signal.h>
int raise(int sig);
功能:給當前程序傳送指定訊號(自己給自己發),等價於kill(getpid(),sig)
引數:
sig:訊號編號
返回值:成功:0
失敗:非0值
程式碼範例:
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int i = 1;
while(1)
{
printf("do working %d\n",i);
//給自己傳送一個訊號
if(i == 4)
{
//自己給自己傳送編號為15的訊號
raise(SIGTERM);
}
i ++;
sleep(1);
}
return 0;
}
執行結果如下:
(3)abort函數
#include<stdlib.h>
void abort(void);
功能:給自己傳送異常終止訊號6)SIGABRT,並且產生core檔案,等價於kill(getpid(),SIGABRT);
引數:無
返回值:無
程式碼範例:
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int i = 1;
while(1)
{
printf("do working %d\n",i);
//給自己傳送一個訊號
if(i == 4)
{
//給自己傳送一個編號為6的訊號,預設的行為就是終止程序
abort();
}
i ++;
sleep(1);
}
return 0;
}
執行結果如下:
在上一篇部落格介紹過,管道如果讀端不讀了,儲存系統會發生SIGPIPE 訊號給寫端程序,終止程序。這個訊號就是由一種軟體條件產生的,這裡再介紹一種由軟體條件產生的訊號SIGALRM(時鐘訊號)。
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
功能:
設定定時器(鬧鐘)。在指定seconds後,核心會給當前程序傳送14)SIGALRM訊號,程序收到該訊號,預設動作終止。每個程序都有且只有唯一的一個定時器。
取消定時器alarm(0),返回舊鬧鐘餘下秒數。
引數:
seconds:指定的時間,以秒為單位
返回值:
返回0或剩餘的秒數
程式碼範例:
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
unsigned int ret = 0;
//第一次設定鬧鐘5秒之後就超時 傳送對應的訊號
ret = alarm(5);
printf("上一次鬧鐘剩下的時間是%u\n",ret);
sleep(2);
//之前沒有超時的鬧鐘被新的鬧鐘給覆蓋
ret = alarm(4);
printf("上一次鬧鐘剩下的時間是%u\n",ret);
printf("按下任意鍵繼續...");
getchar();
return 0;
}
執行結果:
硬體異常被硬體以某種方式被硬體檢測到並通知核心,然後核心向當前程序傳送適當的訊號。
這裡給大家介紹兩個硬體異常:CPU產生異常 和 MMU產生異常
CPU產生異常 發生除零錯誤,CPU執行單元會產生異常,核心將這個異常解釋為訊號,最後OS傳送SIGFPE訊號給程序。
程式碼範例:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main()
{
// 由軟體條件產生訊號 alarm函數和SIGPIPE
// CPU運算單元產生異常,核心將這個例外處理為SIGFPE訊號傳送給程序
int a = 10;
int b = 0;
printf("%d", a/b);
return 0;
}
執行結果小夥伴們自己下去執行一下吧~這裡就不截圖了
MMU產生異常: 當程序存取非法地址時,mmu想通過頁表對映來將虛擬轉換為實體地址,此時發現頁表中不存在該虛擬地址,此時會產生異常,然後OS將異常解釋為SIGSEGV訊號,然後傳送給程序
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main()
{
// MMU硬體產生異常,核心將這個例外處理為SIGSEGV訊號傳送給程序
signal(11, handler);
int* p = NULL;
printf("%d\n", *p);
return 0;
}
#include<signal.h>
typedef void(*sighandler_t)(int);//它定義了一個型別sighandler_t,表示指向返回值為void型(引數為int型)的函數(的)指標
sighandler_t signal(int signum,sighandler_t handler);
功能:
註冊訊號處理常式(不可用於SIGKILL、SIGSTOP訊號),即確定收到訊號後處理常式的入口地址。此函數不會阻塞。
引數:
signum:訊號的編號,這裡可以填數位編號,也可以填訊號的宏義。
handler:取值有3種情況:
SIG_IGN:忽略該訊號
SIG_DFL:執行系統預設動作
訊號處理常式名:自定義訊號處理常式,如:func
回撥函數的定義如下:
void func(int signo)
{
//signo為觸發的訊號,為signal()第一個引數的值
}
返回值:
成功:第一次返回NULL,下一次返回此訊號上一次註冊的訊號處理常式的地址。如果需要使用此返回值,必須在前面先宣告此函數指標的型別。
失敗:返回SIG_ERR
程式碼範例:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
//訊號處理常式
void fun1(int signum)
{
printf("捕捉到訊號:%d\n",signum);
}
//訊號處理常式2
void fun2(int signum)
{
printf("捕捉到訊號:%d\n",signum);
}
//訊號註冊函數
int main()
{
//訊號註冊
//ctrl+c
signal(SIGINT,fun1);
//ctrl+\
signal(SIGQUIT,fun2);
while(1)
{
sleep(1);
}
return 0;
}
執行結果如下:
訊號先註冊,程序不要退出,然後等待訊號的到達,訊號到達之後就會執行訊號處理常式。
瞭解幾個概念:
實際執行訊號的處理動作稱為訊號遞達
訊號遞達的三種方式:預設、忽略和自定義捕捉
訊號從產生到遞達之間的狀態,稱為訊號未決(Pending)
程序可以選擇阻塞 (Block )某個訊號
被阻塞的訊號產生時將保持在未決狀態,直到程序解除對此訊號的阻塞,才執行遞達的動作
注意:
每個程序都存在一個PCB,在PCB中存在兩個集合,一個是未決訊號集,一個是阻塞訊號集。
以SIGINT為例說明未決訊號集和阻塞訊號集的關係:
注意:
未決訊號集在核心中,要對核心進行操作只能通過系統呼叫,但是沒有提供這樣的方法,所以只能對未決訊號集進行讀操作,但是可以對阻塞訊號集進行讀寫操作。
問題1:所有訊號的產生都要由OS來進行執行,這是為什麼?
訊號的產生涉及到軟硬體,且OS是軟硬體資源的管理者,還是程序的管理者。
問題2:程序在沒有收到訊號的時候,能否知道自己應該如何對合法訊號進行處理呢?
答案是能知道的。每個程序都可以通過task_struct找到表示訊號的三張表。此時該程序的未決訊號集表中哪些訊號對應的那一位位元位是為0的,且程序能夠檢視阻塞訊號集表知道如果收到該訊號是否需要阻塞,可以檢視handler表知道對該訊號的處理動作。
問題3:OS如何發生訊號?
OS給某一個程序傳送了某一個訊號後,OS會找到訊號在程序中未決訊號集表對應的那一位位元位,然後把那一位位元位由0置1,這樣OS就完成了訊號傳送的過程。
sigset_t: 未決和阻塞標誌可以用相同的資料型別sigset_t來儲存,sigset_t稱為訊號集,也被定義為一種資料型別。這個型別可以表示每個訊號狀態處於何種狀態(是否被阻塞,是否處於未決狀態)。阻塞訊號集也叫做當前程序的訊號遮蔽字,這裡的「遮蔽」應該理解為阻塞而不是忽略。
實際上兩個訊號集在都是核心使用點陣圖機制來實現的,想了解的可以自己去了解下,但是作業系統不允許我們直接對其操作。而需要自定義另外一個集合,藉助於訊號集操作函數來對PCB中的這兩個訊號集進行修改。
訊號集操作函數: sigset_t型別對於每種訊號用一個bit表示「有效」或「無效」狀態,至於這個型別內部如何儲存這些bit則依賴於系統實現,從使用者的角度是不必關心的,使用者只能呼叫以下函數來操作sigset_ t變數,而不應該對它的內部資料做任何解釋。
注意: 對應sigset型別的變數,我們不可以直接使用位元運算來進行操作,而是一個嚴格實現系統給我們提供的庫函數來對這個型別的變數進行操作。
下面是訊號集操作函數的原型:
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
注意: 在實現這些函數之前,需要使用sigemptyset 或sigfillset對訊號集進行初始化。前四個函數的返回值是成功返回0,失敗返回-1。最後一個函數的返回值是真返回1,假返回-1
阻塞訊號集操作函數——sigprocmask:
#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
功能:
檢查或修改訊號阻塞集,根據how指定的方法對程序的阻塞集合進行修改,新的訊號阻塞集由set指定,而原先的訊號阻塞集合由oldset儲存。
引數:
how:訊號阻塞集合的修改方法,有3種情況:
SIG_BLOCK:向訊號阻塞集合中新增set訊號集,新的訊號掩碼是set和舊訊號掩碼的並集。相當於mask=mask丨set。
SIG_UNBLOCK:從訊號阻塞集合中刪除set訊號集,從當前訊號掩碼中去除set中的訊號。相當於mask=mask&~set。
SIG_SETMASK:將訊號阻塞集合設為set訊號集,相當於原來訊號阻塞集的內容清空,然後按照set中的訊號重新設定訊號阻塞集。相當於mask=set。
set:要操作的訊號集地址。
若set為NULL,則不改變訊號阻塞集合,函數只把當前訊號阻塞集合儲存到oldset中。
oldset: 儲存原先訊號阻塞集地址。
返回值:成功:0 失敗:-1
未決訊號集操作函數——sigpending:
#include<signal.h>
int sigpending(sigset_t *set);
功能:讀取當前程序的未決訊號集
引數:
set:未決訊號集
返回值:
成功:0 失敗:-1
程式碼範例:
實驗一:把程序中訊號遮蔽字2號訊號進行阻塞,然後每隔1s對未決訊號集進行列印,觀察現象。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void PrintPending(sigset_t* pend)
{
int i = 0;
for (i = 1; i < 32; ++i)
{
if (sigismember(pend, i)){
printf("1");
}
else{
printf("0");
}
}
printf("\n");
}
int main()
{
sigset_t set, oset;
sigset_t pending;
// 使用系統函數對訊號集進行初始化
sigemptyset(&set);
sigemptyset(&oset);
sigemptyset(&pending);
// 阻塞2號訊號
// 先用系統函數對set訊號集進行設定
sigaddset(&set, 2);
// 使用sigprocmask函數更改程序的訊號遮蔽字
// 第一個引數,三個選項:SIG_BLOCK(mask |= set) SIG_UNBLOCK(mask &= ~set) SIG_SETMASK(mask = set)
sigprocmask(SIG_BLOCK, &set, &oset);
int flag = 1; // 表示已經阻塞2號訊號
int count = 0;
while (1){
// 使用sigpending函數獲取pending訊號集
sigpending(&pending);
// 列印pending點陣圖
PrintPending(&pending);
sleep(1);
}
return 0;
}
執行結果如下:
可以看到,程序收到2號訊號時,且該訊號被阻塞,處於未決狀態,未決訊號集中2號訊號對應的位元位由0置1
範例2: 將上面的程式碼進行修改,進行執行10s後,我們將訊號遮蔽字中2號訊號解除遮蔽
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void PrintPending(sigset_t* pend)
{
int i = 0;
for (i = 1; i < 32; ++i)
{
if (sigismember(pend, i)){
printf("1");
}
else{
printf("0");
}
}
printf("\n");
}
int main()
{
sigset_t set, oset;
sigset_t pending;
// 使用系統函數對訊號集進行初始化
sigemptyset(&set);
sigemptyset(&oset);
sigemptyset(&pending);
// 阻塞2號訊號
// 先用系統函數對set訊號集進行設定
sigaddset(&set, 2);
// 使用sigprocmask函數更改程序的訊號遮蔽字
// 第一個引數,三個選項:SIG_BLOCK(mask |= set) SIG_UNBLOCK(mask &= ~set) SIG_SETMASK(mask = set)
sigprocmask(SIG_BLOCK, &set, &oset);
int flag = 1; // 表示已經阻塞2號訊號
int count = 0;
while (1){
// 使用sigpending函數獲取pending訊號集
sigpending(&pending);
// 列印pending點陣圖
PrintPending(&pending);
if (++count == 10){
// 兩種方法都可以
sigprocmask(SIG_UNBLOCK, &set, &oset);
//sigprocmask(SIG_SETMASK, &oset, NULL);
}
sleep(1);
}
return 0;
}
執行結果如下:
訊號2被阻塞之後就變成了未決狀態,當該訊號從阻塞集合中解除的時候,該訊號就會被處理,該訊號被處理後,該訊號的未決訊號集的標誌位將從1置為0。
一個程序收到一個訊號的時候,可以用以下的方法進行處理:
(1)執行系統預設動作:對大多數訊號來說,系統預設動作就是來終止進行。
(2)忽略此訊號(丟棄):接收到此訊號後沒有任何動作
(3)執行自定義訊號處理常式(捕獲):使用者定義的訊號處理常式處理該訊號
注意:SIGKILL和SIGSTOP不能更改訊號的處理方式,因為它們向用戶提供了一種使程序終止的可靠方法。
先思考一個問題:訊號是什麼時候被程序處理的?
首先,不是立即被處理的。而是在合適的時候,這個合適的時候,具體指的是程序從使用者態切換回核心態時進行處理
這句話如何理解,什麼是使用者態?什麼是核心態?
注意: 作業系統中有一個cr暫存器來記錄當前程序處於何種狀態
程序空間分為使用者空間和核心空間。此前我們介紹的頁表都是使用者級頁表,其實還有核心級頁表。程序的使用者空間是通過使用者級頁表對映到實體記憶體上,核心空間是通過核心級頁表對映到實體記憶體上,如下面簡圖所示:
當程序執行在核心空間時就處於核心態,而程序執行在使用者空間時則處於使用者態。
最高 1G 的核心空間是被所有程序共用的!
程序有不同的使用者空間,但是隻有一個核心空間,不同程序的使用者空間的程式碼和資料是不一樣的,但是核心空間的程式碼和資料是一樣的。
上面這些主要是想說:程序處於使用者態存取的是使用者空間的程式碼和資料,程序處於核心態,存取的是核心空間的程式碼和資料。
訊號捕捉的整個過程:
從上面的圖可以看出,程序是在返回使用者態之前對訊號進行檢測,檢測pending點陣圖,根據訊號處理動作,來對訊號進行處理。這個處理動作是在核心態返回使用者態後進行執行的,所以這裡也就回答了開始提出的那一個問題了。
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:
檢查或修改指定訊號的設定(或同時執行兩種操作)
引數:
signum:要操作的函數
act:要設定的對訊號的新處理方式(傳入方式)
oldact:原來對訊號的處理方式(傳出引數)
如果act指標非空,則要改變指定訊號的處理(設定),如果oldact指標非空,則系統將此前指定訊號的處理方式入oldact
返回值:
成功:0
失敗:-1
struct sigaction結構體:
struct sigaction {
void (*sa_handler)(int);//舊的訊號處理常式指標
void (*sa_sigaction)(int, siginfo_t *, void *);//新的訊號處理常式指標
sigset_t sa_mask;//訊號阻塞集
int sa_flags;//訊號處理的方式
void (*sa_restorer)(void);//已經棄用
};
(1)sa_handler、sa_sigaction:訊號處理常式指標,和signal裡面的函數指標用法是一樣的,根據情況給兩個指標賦值。
a)SIG_IGN:忽略該訊號
b)SIG_DFL:執行系統預設的動作
c)處理常式名:自定義訊號處理常式
(2)sa_mask:訊號阻塞集,在執行訊號處理常式的時候,用來臨時的遮蔽訊號
(3)sa_flags:用於指定訊號處理的行為,通常設定為0,表示使用預設的屬性。它可以是一下值的「按位元」或「組合」:
程式碼範例:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{
printf("catch a signal: %d\n", signo);
}
int main()
{
struct sigaction act, oact;
act.sa_flags = 0;// 選項 設定為0
sigfillset(&act.sa_mask);
act.sa_handler = handler;
// 對2號訊號修改處理動作
sigaction(2, &act, &oact);
while (1){
raise(2);
sleep(1);
}
return 0;
}
執行結果如下:
程式碼範例:舊的訊號處理常式
sa_flags標誌為0代表使用的是舊的訊號處理常式
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{
printf("catch a signal: %d\n", signo);
}
int main()
{
int ret = -1;
struct sigaction act;
//標誌為0,代表使用的是舊的訊號處理常式指標
act.sa_flags = 0;
//給阻塞集初始化
sigfillset(&act.sa_mask);
act.sa_handler = handler;
// 訊號註冊
ret = sigaction(SIGINT, &act, NULL);
if(ret == -1)
{
perror("sigaction");
return 1;
}
printf("按下任意鍵退出.....\n");
getchar();
return 0;
}
執行結果如下:
程式碼範例:新的訊號處理常式
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo,siginfo_t *info,void *context)
{
printf("catch a signal: %d\n", signo);
}
int main()
{
int ret = -1;
struct sigaction act;
//使用新的訊號處理常式指標
act.sa_flags = 0;
//給阻塞集初始化
sigfillset(&act.sa_mask);
act.sa_handler = handler;
// 訊號註冊
ret = sigaction(SIGINT, &act, NULL);
if(ret == -1)
{
perror("sigaction");
return 1;
}
printf("按下任意鍵退出.....\n");
getchar();
return 0;
}
先看下面一段程式碼:
#include <stdio.h>
#include <signal.h>
int a = 10;
void SelfAdd(int n)
{
a = a + n;
a = a + n;
}
void handler(int signo)
{
SelfAdd(signo);
}
int main()
{
signal(2, handler);
SlefAdd(2);
printf("%d\n", a);
return 0;
}
上面我寫了一個比較簡單的程式碼,我們慢慢分析,當我們在主函數中執行呼叫SelfAdd時,進入該函數,執行完函數中int a = a + n這句程式碼後,a變成了12,此時收到2號訊號,發生中斷
最後列印a結果是16,其實正常呼叫該函數的話,列印的應該是18。
像上面這樣的因為重入導致結果錯亂的函數就叫做不可重入函數。其中a是一個全域性變數。如果一個函數值存取自己的區域性變數或引數,那麼這樣的函數就叫做可重入函數。
說的通俗點,不可重入的意思是,如果你定義了一個全域性變數,在函數1裡面這個變數應該是10,但是有一個函數2改變了這個變數的值,此時本來函數1用的是10 ,你把他改變了,這就是不安全的,這就是不可重入函數。
思考一個問題:為什麼兩個不同的控制流程呼叫同一個函數,存取同一個區域性變數或引數不會造成錯亂?
在多執行緒中,每個執行緒雖然是資源共用,但是他們的棧卻是獨有的,所以說區域性變數不會造成錯亂。
如果一個函數符合以下條件之一則是不可重入的:
呼叫了malloc或free,因為malloc也是用全域性連結串列來管理堆的。
呼叫了標準I/O庫函數。標準I/O庫的很多實現都以不可重入的方式使用全域性資料結構。
函數體內使用了靜態的資料結構。
保證函數可重入性的方法:
SIGCHLD訊號
產生條件:1)子程序終止時
2)子程序接收到SIGSTOP訊號停止的時候
3)子程序處於停止態,接收到SIGCONT後喚醒時
如何避免殭屍程序:
1)最簡單的方法,父程序通過wait和waitpid等待子程序函數結束,但是,這會導致父程序掛起。
2)如果父程序要處理的事情很多,不能掛起,通過signal()函數人為處理訊號SIGCHLD,只有在子程序退出自動呼叫制定好的回撥函數,因為子程序結束後,父程序會收到訊號SIGCHLD,可以在回撥函數裡面用wait或waitpid回收資源。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include<sys/types.h>
#include<sys/wait.h>
void sig_child(int signo)
{
pid_t pid;
//處理殭屍程序,-1代表等待任意一個子程序,WNOHANG代表不阻塞
while((pid=waitpid(-1,NULL,WNOHANG))>0)
{
printf("孩子程序被殺死 %d\n",pid);
}
}
int main()
{
pid_t pid;
//建立捕捉子程序退出訊號
//只要子程序退出,觸發SIGSIGCHLD,自動呼叫sig_child()
signal(SIGCHLD,sig_child());
//建立程序
pid = fork();
if(pid<0)
{
perror("fork");
exit(1);
}
else if(pid == 0)
{
//子程序
printf("我是子程序,pid id :%d.我正在退出\n",getpid());
exit(0);
}
else if(pid>0)
{
//父程序
sleep(2);//保證子程序先執行
printf("我是父親,我正在退出\n");
system("ps -ef|grep defunct");//檢視有沒有殭屍程序
}
return 0;
}
執行結果:
3)如果父程序不關心子程序時候結束,那麼可以用signal(SIGCHLD,SIG_IGN)通知核心,自己對子程序的結束不感興趣,父程序忽略此訊號,那麼子程序結束後,核心會回收,並不再給父程序傳送訊號。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid;
//忽略子程序退出訊號的訊號
//那麼子程序結束之後,核心會回收,並不再給父程序傳送訊號
signal(SIGCHLD,SIG_IGN);
//建立程序
pid = fork();
if(pid<0)
{
perror("fork");
exit(1);
}
else if(pid == 0)
{
//子程序
printf("我是子程序,pid id :%d.我正在退出\n",getpid());
exit(0);
}
else if(pid>0)
{
//父程序
sleep(2);//保證子程序先執行
printf("我是父親,我正在退出\n");
system("ps -ef|grep defunct");//檢視有沒有殭屍程序
}
return 0;
}
執行結果: