1.信號的基本概念
1)理解什麼是信號?
-
⽤戶輸⼊命令,在Shell下啓動⼀個前臺進程。
-
⽤戶按下Ctrl-C,這個鍵盤輸⼊產⽣⼀個硬件中斷。
-
如果CPU當前正在執⾏這個進程的代碼,則該進程的⽤戶空間代碼暫停執⾏,CPU從⽤戶態 切換到內核態處理硬件中斷。
-
終端驅動程序將Ctrl-C解釋成⼀個SIGINT信號,記在該進程的PCB中(也可以說發送了⼀ 個
SIGINT
信號給該進程)。(說明PCB可記錄信號,是引用位圖記錄,位置信號的1/0表明信號的存在與否)(只有OS有資格修改目標進程的位圖信息也只有OS能發送,發信號其實更準確來說是寫信號)。 -
當某個時刻要從內核返回到該進程的⽤戶空間代碼繼續執⾏之前,⾸先處理PCB中記錄的信號,發現有⼀個SIGINT信號待處理,⽽這個信號的默認處理動作是終⽌進程,所以直接終⽌進程⽽不再返回它的⽤戶空間代碼執⾏。
注意: -
Ctrl-C產⽣的信號只能發給
前臺進程
。⼀個命令 後⾯加個&
可以放到後臺運⾏,這樣Shell不必等待進程結束就可以接受新的命令,啓動新的進程。 -
Shell可以同時運⾏⼀個前臺進程和任意多個後臺進程,只有前臺進程才能接到像Ctrl-C這種控制鍵產⽣的信號。
-
前臺進程在運⾏過程中⽤戶隨時可能按下Ctrl-C⽽產⽣⼀個信號,也就是說該進程的⽤戶空間代碼執⾏到任何地⽅都有可能收到SIGINT信號⽽終⽌,所以
信號相對於進程的控制流程來說是異步(Asynchronous)的。
總結:1.進程要處理信號必須要認識信號(位圖加規定)
2.信號產生進程收到後有可能不立刻處理(先保存)而在合適的時候處理
3.信號機制的產生對進程來說是異步的執行流並沒有關係
4.信號處理的三種方法:執行默認信號/忽略/自定義
5.如果當前信號不能被處理就先記錄。
2)信號列表
使用命令:kill -l
可以查看系統定義的信號列表。
每個信號都有⼀個編號和⼀個宏定義名稱,這些宏定義可以在signal.h中找到,例如其中有定義 #define SIGINT 2 。 1-31爲普通信號,34-64爲實時信號。這些信號各自在什麼條件下產生,默認的處理動作是什麼,在signal(7)中都有詳細說明:man 7 signal
2.信號的產生
1)產生信號的方法概述
《1》終端產生
Ctrl+c產生SIGINT信號
Ctrl+\產生SIGQUIT信號
Ctrl+Z產生SIGTSTP信號
《2》硬件異常產生
,這些條件由硬件檢測到並通知內核,然後內核向當前進程發送適當的信 號。例如當前進程執⾏了除以0的指令,CPU的運算單元會產⽣異常,內核將這個異常解釋 爲SIGFPE信號發送給進程。再⽐如當前進程訪問了⾮法內存地址,MMU會產⽣異常,內核 將這個異常解釋爲SIGSEGV信號發送給進程。
《3》kill產生
⼀個進程調⽤kill(2)函數可以發送信號給另⼀個進程。 可以⽤kill(1)命令發送信號給某個進程,kill(1)命令也是調⽤kill(2)函數實現的,如果不明確指定信號則發送SIGTERM信號,該信號的默認處理動作是終⽌進程。 當內核檢測到某種軟件條件發⽣時也可以通過信號通知進程,例如鬧鐘超時產SIGALRM信號,向讀端已關閉的管道寫數據時產⽣SIGPIPE信號。 如果不想按默認動作處理信號,⽤戶程序可以調⽤sigaction(2)函數告訴內核如何處理某種信號.
《4》軟件條件產生
2)終端產生信號
SIGINT的默認處理動作是終⽌進程,SIGQUIT的默認處理動作是終⽌進程並且Core Dump,現在我們來驗證⼀下。
Core Dump
⾸先解釋什麼是Core Dump。當⼀個進程要異常終⽌時,可以選擇把進程的⽤戶空間內存數據全部 保存到磁盤
上,⽂件名通常是core,這叫做Core Dump。進程異常終⽌通常是因爲有Bug,⽐如⾮法內存訪問導致段錯誤,事後可以⽤調試器檢查core⽂件以查清錯誤原因,這叫做Post-mortemDebug(事後調試
)。
⼀個進程允許產⽣多⼤的core⽂件取決於進程的Resource Limit
(這個信息保存 在PCB中)。默認是不允許產⽣core⽂件的,因爲core⽂件中可能包含⽤戶密碼等敏感信息,不安全。在開發調試階段可以⽤ulimit命令改變這個限制,允許產⽣core⽂件。
$ ulimit -c 1024//修改core文件允許產生最大1024k
$ ulimit -a //查看資源
事後調試:
$ ulimit -c 1024
- 寫一個死循環程序,使用ctrl + \將它終止掉,ls發現會多一個core.文件。
- 使用gdb調試,core-file core.文件。
- core文件直接幫我們定位出來錯誤的地方。
3)調用系統函數向進程發信號
《1》kill命令
kill命令是調⽤kill函數實現的。kill函數可以給⼀個指定的進程發送指定的信號。
#include<signal.h>
int kill(pid_t pid,int signo);//kill命令的調用kill函數實現的
返回值:成功返回0,失敗返回-1
⾸先在後臺執⾏死循環程序
然後⽤kill命令給它發SIGSEGV信號
之所以要再次回⻋才顯⽰ Segmentation fault ,是因爲在13972進程終⽌
掉 之前已經回到了Shell提⽰符等待⽤戶輸⼊下⼀條命令,Shell不希望Segmentation fault信息和⽤戶的輸⼊交錯在⼀起,所以等⽤戶輸⼊命令之後才顯⽰。
kill -SIGSEGV 進程PID與`kill -11 進程PID(11是SIGSEGV的編號)` 相同。
以往遇到的段錯誤都是由⾮法內存訪問產⽣的,⽽這個程序本⾝沒錯,給它發SIGSEGV也能產⽣段錯誤
《2》raise函數
給當前進程發送指定信號(自己給自己發)
#include<signal.h>
int raise(int signo);
返回值:成功返回0,失敗返回-1
《3》abort函數
使當前進程接收到信號⽽異常終⽌
#include <stdlib.h>
void abort(void);
就像exit函數⼀樣,abort函數總是會成功的,所以沒有返回值。
4)軟件條件產生信號
SIGPIPE是⼀種由軟件條件產⽣的信號,當讀端將自己的文件描述符關閉,OS會向寫端發送一個SIGPIPE信號來關閉寫端。
《1》alarm函數
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
功能:設置一個鬧鐘,告訴內核在seconds秒後給當前進程發送SIGALRM信號,該信號的默認動作是終止進程
這個函數的返回值是0或者是以前設定的鬧鐘時間還餘下的秒數。
eg:下面這個程序作用是2秒鐘之內不停的數數,2秒鐘到了就被SIGALRM信號終⽌.
#include<stdio.h>
#include<unistd.h>
int main()
{
int count = 0;
alarm(2);
for(count = 0;2;count++){
printf("count = %d\n",count);
}
return 0;
}
《2》signal函數
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//signum是信號編號,
//handler是一個函數指針,這個函數指針指向的是對默認處理替換的方法函數
3.阻塞信號
1)信號其他相關常見概念
- 實際執行信號的處理動作稱爲信號遞達(Delivery)(要遞達絕對沒有阻塞)
- 信號從產生到遞達之間的狀態,稱爲信號未決(Pending)
pending->block->阻塞
pending->未block->合適時遞達
- 進程可以選擇阻塞(Block)某個信號。(阻塞也叫做屏蔽)沒處理除非解除阻塞。
- 被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。
- 阻塞與忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之後,可選的一種處理動作。(忽略表明處理了)
2)在內核中的表達
信號在內核中的表示示意圖:(位圖存儲)
由上圖知
block的0/1表明是否被屏蔽。
pending0/1表明信號是否存在。信號產生時,內核在進程控制塊中的未決標誌直到信號遞達才清除該標誌。圖中的2號信號還沒有被處理。
handler表示處理動作:函數指針數組,先編號找到位置,再將函數指針放入。
如果在進程解除對某信號的阻塞之前這種信號產⽣過多次,將如何處理?POSIX.1允許系統遞送該信號⼀次或多次。Linux是這樣實現的:常規信號在遞達之前產⽣多次只計⼀
次,⽽實時信號在遞達之前產⽣多次可以依次放在⼀個隊列⾥。
3)sigset_t
位圖,包含block表和pending表。
每個信號只有⼀個bit的未決標誌,⾮0即1,不記錄該信號產⽣了多少次,阻塞標誌也是這樣表⽰的。 因此,未決和阻塞標誌可以⽤相同的數據類型sigset_t來存儲,sigset_t稱爲信號集,這個類型可以表⽰每個信號的“有效”或“⽆效”狀態,在阻塞信號集中“有效”和“⽆效”的含義是該信號是否被阻塞,⽽在未決信
號集中“有效”和“⽆效”的含義是該信號是否處於未決狀態。 阻塞信號集也叫做當前進程的信號屏蔽字(Signal Mask),這⾥的“屏蔽”應該理解爲阻塞⽽不是忽略。
4)信號集操作函數
sigsett類型對於每種信號⽤⼀個bit表⽰“有效”或“⽆效”狀態,⾄於這個類型內部如何存儲這些bit則依賴於系統實現,從使⽤者的⾓度是不必關⼼的,使⽤者只能調⽤以下函數來操作sigset t變量,⽽不應該對它的內部數據做任何解釋,⽐如⽤printf直接打印sigset_t變量是沒有意義的。
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
- 函數sigemptyset初始化set所指向的信號集,使其中所有信號的對應bit清零,表⽰該信號集不包含 任何有效信號。
- 函數sigfillset初始化set所指向的信號集,使其中所有信號的對應bit置位,表⽰ 該信號集的有效信號包括系統⽀持的所有信號。
- 注意,在使⽤sigset_ t類型的變量之前,⼀定要調 ⽤sigemptyset或sigfillset做初始化,使信號集處於確定的狀態。初始化sigset_t變量之後就可以在調⽤sigaddset和sigdelset在該信號集中添加或刪除某種有效信號。
這四個函數都是成功返回0,出錯返回-1。sigismember是⼀個布爾函數,⽤於判斷⼀個信號集的有效信號中是否包含某種 信號,若包含則返回1,不包含則返回0,出錯返回-1。
5)sigprocmask函數
可以讀取或更改進程的信號屏蔽字(阻塞信號集)。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功則爲0,若出錯則爲-1
如果oset是⾮空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。
如果set是⾮空指針,則 更改進程的信號屏蔽字,參數how指⽰如何更改。
如果oset和set都是⾮空指針,則先將原來的信號 屏蔽字備份到oset⾥,然後根據set和how參數更改信號屏蔽字。
假設當前的信號屏蔽字爲mask,下面說明了how參數的可選值。
SIG_BLOCK:set包含了我們希望添加到當前信號屏蔽字的信號,相當於mask=mask|set
SIG_UNBLOCK:set包含了我們希望從當前信號屏蔽字中解除阻塞的信號相當於mask=mask&~set
SIG_SETMASK:設置當前信號屏蔽字爲set所指向的值,相當於mask=set
如果調用sigprocmask解除了對當前若干個未決信號的阻塞,則在函數返回之前,至少將其中一個信號遞達。
6)sigpending函數
讀取當前進程的未決信號集,通過set參數傳出。調用成功則返回0,出錯則返回-1.
#include<signal.h>
int sigpending(sigset_t *set);
下面我們使用下剛纔學習的函數:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void printsigset(sigset_t *set)
{
int i = 0;
for(;i<32;i++)
{
if(sigismember(set,i))
{
putchar('1');
}
else{
putchar('0');
}
}
puts("");
}
int main()
{
sigset_t s,p;//定義信號集對象,並清空初始化
sigemptyset(&s);
sigaddset(&s,SIGQUIT);
sigprocmask(SIG_BLOCK,&s,NULL);//設置阻塞信號集,阻塞SIGQUIT信號
while(1){
sigpending(&p);//獲取未決信號集
printsigset(&p);
sleep(1);
}
}
運行該程序,每秒鐘把各信號的未決狀態打印一遍,由於我們阻塞了SIGQUIT信號,按Ctrl+\會使SIGQUIT處於未決狀態,如下圖所示。
使用Ctrl+C仍然可以終止程序,因爲SIGINT信號沒有阻塞。
4.捕捉信號
1)信號的捕捉
《1》首先我們先了解下內核態和用戶態的基本概念
-
用戶態:用戶執行自身代碼
-
內核態:執行內核代碼的狀態
使用系統調用接口時可以由用戶態變爲內核態,返回時再又內核態轉爲用戶態,系統調用接口的存在就是爲了保護OS,普通用戶不能訪問OS的代碼數據。切入內核的方式除了系統調用還有中斷,異常,缺陷等方式
《2》內核如何實現信號的捕捉
如果信號的處理動作是用戶自定義函數,在信號遞達時就調用這個函數,這就成爲捕捉信號。由於信號處理函數的代碼是在用戶空間的,處理過程比較複雜,舉例如下:用戶程序註冊了SIGQUIT 信號的處理函數sighandler.當前正在執行main函數,這時發生中斷或者異常切換到內核態。在中斷處理完畢要返回用戶態的main函數之前檢查到有信號SIGQUIT遞達。內核決定返回用戶態後不是恢復main函數的上下文繼續執行,而是執行sighandler函數,sighandler和main函數使用不同的堆棧空間,它們之間不存在調用和被調用的關係,如果沒有新的信號要遞達,這次再返回用戶態就是恢復main函數的上下文繼續執行了。
信號處理在內核態切換至用戶態的時候,線程間的切換,進程間的切換也發生在內核態切換用戶態的期間。
《3》sigaction函數
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
//const sigaction *act是指處理新的信號
//struct sigaction *oact是將舊的返回(輸出型參數)
struct sigaction
{
void(*) (int) sa_handler ;
sigset_t sa_mask ;
int sa_flags ;
void(*) (int, siginfo_t *, void *) sa_sigaction ;
}
sigaction函數可以讀取和修改與指定信號相關聯的處理動作。調⽤成功則返回0,出錯則返回- 1。signo是指定信號的編號。若act指針⾮空,則根據act修改該信號的處理動作。若oact指針⾮ 空,則通過oact傳出該信號原來的處理動作。act和oact指向sigaction結構體:將sahandler賦值爲常數SIGIGN傳給sigaction表⽰忽略信號,賦值爲常數SIG_DFL表⽰執⾏系統默認動作,賦值爲⼀個函數指針表⽰⽤⾃定義函數捕捉信號,或者說向內核註冊了⼀個信號處理函 數,該函數返回值爲void,可以帶⼀個int參數,通過參數可以得知當前信號的編號,這樣就可以⽤同⼀個函數處理多種信號。顯然,這也是⼀個回調函數,不是被main函數調⽤,⽽是被系統所調用的。
當某個信號的處理函數被調⽤時,內核⾃動將當前信號加⼊進程的信號屏蔽字,當信號處理函數返回時⾃動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產⽣,那麼 它會被阻塞到當前處理結束爲⽌。 如果在調⽤信號處理函數時,除了當前信號被⾃動屏蔽之外,還希望⾃動屏蔽另外⼀些信號,則⽤samask字段說明這些需要額外屏蔽的信號,當信號處理函數返回時⾃動恢復原來的信號屏蔽字。
saflags字段包含⼀些選項,saflags設爲0 ,sasigaction是實時信號的處理函數。
《4》pause
#include <unistd.h>
int pause(void);
pause函數使調⽤進程掛起直到有信號遞達。如果信號的處理動作是終⽌進程,則進程終 ⽌,pause函數沒有機會返回;如果信號的處理動作是忽略,則進程繼續處於掛起狀態,pause不返回;如果信號的處理動作是捕捉,則調⽤了信號處理函數之後pause返回-1,errno設置爲EINTR, 所以pause只有錯的返回值(只有出錯返回值含有exec函數系列,進程函數替換)。錯誤碼EINTR表 ⽰“被信號中斷”。
pause要有返回值則:1:收到信號。2:自定義捕捉信號。
下面我們用alarm和pause實現mysleep函數
思路
- main 函數調用mysleep函數,然後調用sigaction註冊SIGALRM信號的處理函數sig_alrm.
- 調用alarm(nsecs)設爲鬧鐘
- 調用pause等待,內核切換到別的進程運行
- nsecs秒後,鬧鐘超時,內核發SIGAKRM給這個進程
- 從內核態返回這個進程的用戶態之前處理未決信號,發現有SIGALRMB信號被自動屏蔽,從從sig_alrm函數返回時SIGALRM信號⾃動解除屏蔽。然後⾃動執⾏系統調⽤sigreturn再次進⼊ 內核,再返回⽤戶態繼續執⾏進程的主控制流程(main函數調⽤的mysleep函數)。
- pause函數返回-1,然後調⽤alarm(0)取消鬧鐘,調⽤sigaction恢復SIGALRM信號以前的處理動作。
實現代碼:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_alrm(int signo)
{
}
unsigned int mysleep(unsigned int nsecs)
{
struct sigaction new,old;
unsigned int unslept = 0;
new.sa_handler = sig_alrm;
sigemptyset(&new.sa_mask);
new.sa_flags = 0;
sigaction(SIGALRM,&new,&old);//註冊信號處理函數
alarm(nsecs);//設置鬧鐘
pause();
unslept = alarm(0);//清空鬧鐘
sigaction(SIGALRM,&old,NULL);//恢復信號默認動作
return unslept;
}
int main()
{
while(1){
mysleep(1);
printf("1 seconds passed\n");
}
return 0;
}
2)可重入函數
《1》引入
我們以單鏈表的頭插爲例:
main函數調用insert函數向一個鏈表head中插入結點node1,插入操作爲兩步,剛做完第一步的時候,因爲硬件中斷使進程切換到內核,再次回用戶態之前檢查到有信號待處理,於是切換到sighandler函數,sighandler也調用insert函數向同⼀個鏈表head中插⼊節點node2,插⼊操作的 兩步都做完之後從sighandler返回內核態,再次回到⽤戶態就從main函數調⽤的insert函數中繼續 往下執⾏,先前做第⼀步之後被打斷,現在繼續做完第⼆步。結果是,main函數和sighandler先後 向鏈表中插⼊兩個節點,⽽最後只有⼀個節點真正插⼊鏈表中了。導致node2內存泄漏
。
《2》重入
上例中的insert函數被不同的控制流程調用,有可能在第一次調用還沒有返回時就再次進入該函數,這稱爲重入。
《3》不可重入函數
上例中insert函數訪問一個全局鏈表,有可能因爲重入而造成錯亂,像這樣的函數稱爲不可重入函數
不可重入函數的條件:
- 調用了malloc或free,因爲malloc也是用全局鏈表來管理堆的
- 調用了標準I/O庫函數。標準I/O庫的很多實現都以不可重入的方式使用全局數據結構
《4》可重入函數
如果一個函數只訪問自己的局部變量或參數則稱爲可重入函數。
《5》多線程或多CPU編程要使用volatile關鍵字
對於程序中存在多個執⾏流程訪問同⼀全局變量的情況,volatile限定符是必要的,此外,雖然程 序只有單⼀的執⾏流程,但是變量屬於以下情況之⼀的,也需要volatile限定:
1>變量的內存單元中的數據不需要寫操作就可以⾃⼰發⽣變化,每次讀上來的值都可能不⼀樣。
2>即使多次向變量的內存單元中寫數據,只寫不讀,也並不是在做⽆⽤功,⽽是有特殊意義的 。
什麼樣的內存單元會具有這樣的特性呢?肯定不是普通的內存,⽽是映射到內存地址空
間的硬件寄存器,例如串⼝的接收寄存器屬於上述第⼀種情況,⽽發送寄存器屬於上述第⼆種情況。
3>.sig_ atomic_ t類型的變量應該總是加上volatile限定符,因爲要使⽤sigatomict類型的理由也正 是要加volatile限定符的理由。
5.競態條件與sigsuspend函數
其實我們之前寫的mysleep函數還是有缺陷的,設想這樣的時序
問題:
- 註冊SIGALRM信號的處理函數。
- 調⽤alarm(nsecs)設定鬧鐘。
- 內核調度優先級更⾼的進程取代當前進程執⾏,並且優先級更⾼的進程有很多個,每個都要 執⾏很⻓時間
- nsecs秒鐘之後鬧鐘超時了,內核發送SIGALRM信號給這個進程,處於未決狀態。
- 優先級更⾼的進程執⾏完了,內核要調度回這個進程執⾏。SIGALRM信號遞達,執⾏處理函 數sig_alrm之後再次進⼊內核。
- 返回這個進程的主控制流程,alarm(nsecs)返回,調⽤pause()掛起等待。
- 可是SIGALRM信號已經處理完了,還等待什麼呢?
出現這個問題的根本原因是系統運⾏的時序(Timing)並不像我們寫程序時所設想的那樣。雖然alarm(nsecs)緊接着的下⼀⾏就是pause(),但是⽆法保證pause()⼀定會在調⽤alarm(nsecs)之 後的nsecs秒之內被調⽤。由
於異步事件在任何時候都有可能發⽣(這⾥的異步事件指出現更⾼優 先級的進程),如果我們寫程序時考慮不周密,就可能由於時序問題⽽導致錯誤,這叫做競態條件
解決方式:
1)
1. 屏蔽SIGALRM信號
2. alarm(nsecs)
3. 解除對SIGALRM信號的屏蔽
4. pause();
上述過程相對靠譜,但是第三步在解除信號屏蔽的時候,也可能立刻對遞達。
2)
1. 屏蔽SIGALRM信號;
2. alarm(nsecs);
3. pause();
4. 解除對SIGALRM信號的屏蔽
這樣更不⾏了,還沒有解除屏蔽就調⽤pause,pause根本不可能等到SIGALRM信號。要是“解除信號屏蔽”和“掛起等待信號”這兩步能合併成⼀個原⼦操作就好了,這正是sigsuspend函數
的功 能。sigsuspend包含了pause的掛起等待功能,同時解決了競態條件的問題,在對時序要求嚴格的場合下都應該調⽤sigsuspend⽽不是pause。
sigsuspend函數
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
和pause⼀樣,sigsuspend沒有成功返回值,只有執⾏了⼀個信號處理函數之後sigsuspend才返回,返回值爲-1,errno設置爲EINTR。 調⽤sigsuspend時,進程的信號屏蔽字由sigmask參數指定,可以通過指定sigmask來臨時解除對某 個信號的屏蔽,然後掛起等待,當sigsuspend返回時,進程的信號屏蔽字恢復爲原來的值,如果原來對該信號是屏蔽的從sigsuspend返回後仍然是屏蔽的。
接下來用sigsupend重新實現mysleep函數:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_alrm(int signo)
{
}
unsigned int mysleep(unsigned int nsecs)
{
struct sigaction new,old;
sigset_t newmask,oldmask,suspmask;
unsigned int unslept = 0;
new.sa_handler = sig_alrm;
sigemptyset(&new.sa_mask);
new.sa_flags = 0;
sigaction(SIGALRM,&new,&old);//註冊信號處理函數
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
alarm(nsecs);//設置鬧鐘
suspmask = oldmask ;
sigdelset(&suspmask,SIGALRM);
sigsuspend(&suspmask);
unslept = alarm(0);//清空鬧鐘
sigaction(SIGALRM,&old,NULL);//恢復信號默認動作
sigprocmask(SIG_SETMASK,&oldmask,NULL);
return unslept;
}
int main()
{
while(1){
mysleep(1);
printf("1 seconds passed\n");
}
return 0;
}
如果在調⽤mysleep函數時SIGALRM信號沒有屏蔽:
調⽤sigprocmask(SIG_ BLOCK, &newmask, &oldmask),屏蔽SIGALRM。
調⽤sigsuspend(&suspmask);解除對SIGALRM的屏蔽,然後掛起等待待。
SIGALRM遞達後suspend返回,⾃動恢復原來的屏蔽字,也就是再次屏蔽SIGALRM。
調⽤sigprocmask(SIG_ SETMASK, &oldmask, NULL);再次解除對SIGALRM的屏蔽。
6.SIGCHLD信號
⽤wait和waitpid函數清理僵⼫進程,⽗進程可以阻塞等待⼦進程結束,也可以⾮阻 塞地查詢是否有⼦進程結束等待清理(也就是輪詢的⽅式)。採⽤第⼀種⽅式,⽗進程阻塞了就不 能處理⾃⼰的⼯作了;採⽤第⼆種⽅式,⽗進程在處理⾃⼰的⼯作的同時還要記得時不時地輪詢⼀ 下,程序實現複雜。
其實,⼦進程在終⽌時會給⽗進程發SIGCHLD信號,該信號的默認處理動作是忽略,⽗進程可以⾃ 定義SIGCHLD信號的處理函數,這樣⽗進程只需專⼼處理⾃⼰的⼯作,不必關⼼⼦進程了,⼦進程 終⽌時會通知⽗進程,⽗進程在信號處理函數中調⽤wait清理⼦進程即可。
請編寫⼀個程序完成以下功能:⽗進程fork出⼦進程,⼦進程調⽤exit(2)終⽌,⽗進程⾃定 義SIGCHLD信號的處理函數,在其中調⽤wait獲得⼦進程的退出狀態並打印。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handler(int sig)
{
pid_t id;
while( (id = waitpid(-1, NULL, WNOHANG)) > 0){
printf("wait child success: %d\n", id);
}
printf("child is quit! %d\n", getpid());
}
int main()
{
signal(SIGCHLD, handler);
pid_t cid;
if((cid = fork()) == 0){//child
printf("child : %d\n", getpid());
sleep(3);
exit(1);
}
while(1){
printf("father proc is doing some thing!\n");
sleep(1);
}
return 0;
}