本章函數都是定義在<signal.h>linux
1.可靠的信號和不可靠的信號
ubuntu
1.1不可靠的信號api
信號可能會丟失,可是進程並不知道此事情,這是早期信號的弊端,而且早期實現對信號的控制也是蠻差的,列如用戶但願內核阻塞某個信號,可是不能忽略他,在合適的時候進行釋放,當時不具有此種能力 網絡
1.2可靠的信號
數據結構
1.2.1遞送和未決:當一個信號產生時,內核一般在進程表中以某種形式設置一個標誌,此動做叫作遞送,在信號產生和遞送的時間間隔內,此時間段內信號是未決的,而且只有在遞送了一個信號的時候,才決定進程對信號的處理方式多線程
1.2.2在可靠的信號中,能夠對信號進行阻塞,阻塞是把信號阻塞在未決的狀態,因此很據1.2.1所述,只要阻塞在未決狀態,信號沒有進行遞送,就能夠在次狀態中改變信號的處理方式。函數
1.2.2若是信號在阻塞期間發生屢次怎麼辦性能
由於是可靠的信號,因此,在這種狀況下,應該對信號進行阻塞,可是,當解除阻塞的時候,被阻塞的信號會根據選擇,進行遞送一次或者屢次,若是是進行遞送了屢次,則說明信號發生了排隊,可是此種發生排隊的現象,必須支持POSIX.1的實時擴展spa
2.系統的中斷調用線程
定義:若是進程在執行一個低速系統調用而阻塞期間捕捉到一個信號,則系統調用就被中斷不在繼續執行,該系統調用返回出錯,error被設置成EINTR,這樣處理是由於一個信號發生了,進程捕捉到他,這意味着某個重要的事情發生了,在此時,不該該進程阻塞在低速系統調用中,而是應該喚醒阻塞進程,對信號進行處理
2.1關於全局變量errno的理解
常常在調用linux 系統api 的時候會出現一些錯誤,比方說使用open() write() creat()之類的函數有些時候會返回-1,也就是調用失敗,這個時候每每須要知道失敗的緣由。這個時候使用errno這個全局變量就至關有用了。
在程序代碼中包含 #include <errno.h>,而後每次程序調用失敗的時候,系統會自動用用錯誤代碼填充errno這個對象(下面介紹)
errno這個全局變量在<errno.h>頭文件中聲明以下:extern int errno;
errno是一個由POSIX和ISO C標準定義的符號,看(用)起來就好像是一個整形變量。當系統調用或庫函數發生錯誤的時候,好比以只讀方式打開一個不存在的文件時,它的值將會被改變,根 據errno值的不一樣,咱們就能夠知道本身的程序發生了什麼錯誤,而後進行相應的處理。
爲何,要強調errno看起來好像是一個整形變量呢?由於有的標準(如ISO C)只規定了errno的做用,而沒有規定它的實現方式,它可能被定義成一個變量,也有可能被定義成一個宏,這個具體要看編譯器本身的實現。早些時 候POSIX.1曾把errno定義成extern int errno這種形式,但如今這種方式比較少見了。由於以這種形式來實現errno,在多線程環境下errno變量是被多個線程共享的,這樣可能線程A發生 某些錯誤改變了errno的值,線程B雖然沒有發生任何錯誤,可是當它檢測errno的值的時候,線程B會覺得本身發生了錯誤。因此如今errno在 Linux中被實現成extern int * __errno_location(void): #define errno (*__errno_location()),這樣每一個線程都有本身的errno,不會再發生混亂了。
2.2低速系統調用
系統中有兩種系統調用,一種低速系統調用和其餘系統調用,低速系統調用是會讓進程永遠阻塞的一類系統調用
(1)在讀某些類型的文件時(這種系統調用是讀操做,讀操做,讀操做!!!)若是數據並不存在則可能會使
調用者永遠阻塞(管道、終端設備以及網絡設備)。
(2)在寫這些類型(指的是寫類型的系統調用)(指的是管道,終端設備,網絡設備)的文件時,若是不能當即
接受這些數據,則也可能會使調用者永遠阻塞。
(3)打開文件(一些打開文件的系統調用),在某種條件發生以前也可能會使調用者阻塞(例如,打開終端設備,
它要等待直到所鏈接的調制解調器應答)。
(4)pause(按照定義,它使調用進程睡眠直至捕捉到一個信號 )和wait
(5)某種ioctl操做。
(6)某些進程間通訊函數(見第 1 4章)
在這些低速系統調用中,有一個比較值得注意的是與磁盤IO有關的系統調用
雖然讀、寫一個磁盤文件可能暫時阻塞調用者(在磁盤驅動程序將請求排入隊列,而後在適當時間執行請求期間)
,可是除非發生硬件錯誤, I / O操做總會很快返回,並使調用者再也不處於阻塞狀態。
2.3對於如何處理read和write系統調用,當被中斷的時候處理方案(POSIX 2001)
(1)若是在read函數時發生中斷,可是沒有收到所有的信息,系統能夠認爲是失敗的,而且將errno設置爲
EINTR
(2)系統液能夠認爲調用是成功的,而且返回已經處理完的數據
2.4當被中斷的系統調用出錯返回的時候,咱們有可能但願他從新啓動
爲了幫助應用程序使其沒必要處理被中斷的系統調用, 4 . 2 B S D引進了某些被中斷的系統調
用的自動再起動。自動再起動的系統調用包括: i o c t l、r e a d、r e a d v、w r i t e、w r i t e v、w a i t和
w a i t p i d。正如前述,其中前五個函數只有對低速設備進行操做時纔會被信號中斷。而 w a i t和
w a i t p i d在捕捉到信號時老是被中斷。某些應用程序並不但願這些函數被中斷後再起動,由於這
種自動再起動的處理方式也會帶來問題,爲此 4 . 3 B S D容許進程在每一個信號各別處理的基礎上
不使用此功能。
當sigaction指定爲SA_RESTART進行中斷的系統調用從新啓動,signal,被中斷的系統調用是默認啓動的,
可是在signal中各類平臺處理不同,應該本身定義signal,能夠增強可移植性能
3.可重入函數
注意:當進程正在執行執行正常的指令的時候,若是此時有信號發生,則應該首先執行信號處理程序,執行信號處理的過程應該能返回發生信號的地方,而後按照正常的程序執行流程執行
3.1什麼叫作可重入:
好比說進程正在執行malloc函數,此時正好發生一個信號,進入信號處理,在信號處理的過程當中,再一次執行malloc函數,此時,由於在堆上就會發生錯誤,這就叫作不可重入的(在執行某一個函數的時候,信號處理程序發生,處理程序中又有執行此函數,可是此函數會對進程產生破壞)
3.2哪些函數是不可重入的
(1)使用了靜態數據結構(2)調用了malloc或者free(3)他們是標準的IO函數(由於標準的IO函數使用了全局數據結構)
3.3errno變量
要注意系統調用中的errno,爲了保證函數的可重入性,應該在調用信號處理程序的時候保存errno的值,而後在返回的時候發送
4.signal:
函數原型是 void (*signal(int signo,void (*func) (int))) (int);
此函數由於聲明過於複雜,因此使用了typedef進行簡化typedef void Sigfunc(int);
Sigfunc *signal(int ,Sigfunc*);
在ubuntu中,signal信號默認不阻塞本信號(若是在信號處理程序中,再次發生了本信號,不進行本次信號的阻塞),而且在有本次信號到來的時候恢復系統的默認動做
5.sigaction函數:
int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact)
5.1signo是信號的編號
5.2sigaction解釋
struct sigaction
{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int,siginfo_t*,void *);
}
5.2.1sa_mask信號屏蔽字,當進程在調用sa_handler以前,sa_mask加入進程的信號屏蔽字中,當從信號處理程序返回的時候,而後恢復原來的信號屏蔽字,注意在信號處理程序中,正在處理的信號被自動加入信號屏蔽字中,若在發生此信號,應該對其進行阻塞,若是一個信號發生屢次,通常不將其放入信號隊列中,最後,解阻塞的時候,屢次相同的信號,只發生一次
5.2.2sa_handler是信號的處理程序,在再次改變信號處理程序的時候,該設置一直有效
5.2.3sa_flags:
SA_NODEFER:
當捕捉到此信號時,在執行其信號捕捉函數時,系統不自動阻塞此信號。注意,此種類型的操做對應於早期的不可
靠信號
SA_RESETHAND:
若是加入此標誌,在進入信號處理程序的時候將信號的處理方式設置爲SIG_DFL,而且清除SA_SIGINFO,而且此種標誌對應之前不可靠的信號!!!對於SIGILL和SIGTRAP信號,此設置是無效的
SA_INTERRUPT:
由此信號中斷的系統調用不自動重啓動
SA_RESTART:
由此信號中斷的系統調用自動重啓動
SA_SIGINFO:
由此選項對信號處理程序提供了附加信息,一個指向siginfo結構的指針以及一個指向程序上下文標識符的指針
當設置了此標誌,調用如下信號處理程序
void handler(int signo,siginfo_t *info,void *context)
6.sigsuspend函數
1)頭文件:#include <signal.h>
2)一個保護臨界區代碼的錯誤實例:(sigprocmask()和pause()實現)
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig)
{
}
int main()
{
}
上面實例的問題是:原本指望pause()以後,來SIGINT信號,能夠結束程序;但是,若是當「取消阻塞」和「pause」之間,正好來了SIGINT信號,結果程序由於pause的緣由會一直掛起。。。
解決的方式,固然是sigsuspend()函數了。
3)使用sigsuspend()的程序
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig)
{
}
int main()
{
}
sigsuspend的原子操做是:
(1)設置新的mask阻塞當前進程(上面是用wait替換new,即阻塞SIGUSR1信號)
(2)收到SIGUSR1信號,阻塞,程序繼續掛起;收到其餘信號,繼續運行sigsupsend。
(3)調用該進程設置的信號處理函數(程序中若是先來SIGUSR1信號,而後過來SIGINT信號,則信號處理函數會調用兩次,打印不一樣的內容。第一次打印SIGINT,第二次打印SIGUSR1,由於SIGUSR1是前面阻塞的)
(4)待信號處理函數返回,sigsuspend返回了。(sigsuspend將捕捉信號和信號處理函數集成到一塊兒了)恢復原先的mask(即包含SIGINT信號的)