linux信號種類
1、可靠信號和不可靠信號
"不可靠信號"
Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,后來在實踐中暴露出一些問題,因此,把那些建立在 早期機制上的信號叫做"不可靠信號",信號值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信號都是不可靠信號。這就是"不可靠信號"的來源。他的主要問題是:
• 進程每次處理信號后,就將對信號的響應配置為默認動作。在某些情況下,將導致對信號的錯誤處理;因此,用戶假如不希望這樣的操作,那么就要在信號處理函數結尾再一次調用signal(),重新安裝該信號。
• 信號可能丟失,后面將對此周詳闡述。
因此,早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應連同信號可能丟失。
Linux支持不可靠信號,但是對不可靠信號機制做了改進:在調用完信號處理函數后,不必重新調用該信號的安裝函數(信號安裝函數是在可靠機制上的實現)。因此,Linux下的不可靠信號問題主要指的是信號可能丟失。
"可靠信號"
隨著時間的發展,實踐證實了有必要對信號的原始機制加以改進和擴充。所以,后來出現的各種Unix版本分別在這方面進行了研究,力圖實現"可靠信 號"。由于原來定義的信號已有許多應用,不好再做改變,最終只好又新增加了一些信號,并在一開始就把他們定義為可靠信號,這些信號支持排隊,不會丟失。同 時,信號的發送和安裝也出現了新版本:信號發送函數sigqueue()及信號安裝函數sigaction()。POSIX.4對可靠信號機制做了標準 化。但是,POSIX只對可靠信號機制應具備的功能連同信號機制的對外接口做了標準化,對信號機制的實現沒有作具體的規定。
信號值位于SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝 函數sigation()連同信號發送函數sigqueue()的同時,仍然支持早期的signal()信號安裝函數,支持信號發送函數kill()。
注:不要有這樣的誤解:由sigqueue()發送、sigaction安裝的信號就是可靠的。事實上,可靠信號是指后來添加的新信號(信號值 位于SIGRTMIN及SIGRTMAX之間);不可靠信號是信號值小于SIGRTMIN的信號。信號的可靠和不可靠只和信號值有關,和信號的發送及安裝 函數無關。現在linux中的signal()是通過sigation()函數實現的,因此,即使通過signal()安裝的信號,在信號處理函數的結尾 也不必再調用一次信號安裝函數。同時,由signal()安裝的實時信號支持排隊,同樣不會丟失。
對于現在linux的兩個信號安裝函數:signal()及sigaction()來說,他們都不能把SIGRTMIN以前的信號變成可靠信號 (都不支持排隊,仍有可能丟失,仍然是不可靠信號),而且對SIGRTMIN以后的信號都支持排隊。這兩個函數的最大區別在于,經過sigaction安 裝的信號都能傳遞信息給信號處理函數(對任何信號這一點都成立),而經過signal安裝的信號卻不能向信號處理函數傳遞信息。對于信號發送函數來說也是 相同的。
2、實時信號和非實時信號
早期Unix系統只定義了32種信號,Ret hat7.2支持64種信號,編號0-63(SIGRTMIN=31,SIGRTMAX=63),將來可能進一步增加,這需要得到內核的支持。前32種信 號已有了預定義值,每個信號有了確定的用途及含義,并且每種信號都有各自的缺省動作。如按鍵盤的CTRL ^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。后32個信號表示實時信號,等同于前面闡述的可靠信號。這確保了發送的多個實時信號都 被接收。實時信號是POSIX標準的一部分,可用于應用進程。
非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。
Linux的信號的種類有60多種。可以用kill -l命令查看所有的信號,每個信號的含義如下:
1) SIGHUP:當用戶退出shell時,由該shell啟動的所有進程將收到這個信號,默認動作為終止進程
2)SIGINT:當用戶按下了<Ctrl+C>組合鍵時,用戶終端向正在運行中的由該終端啟動的程序發出此信號。默認動作為終止里程。
3)SIGQUIT:當用戶按下<ctrl+\>組合鍵時產生該信號,用戶終端向正在運行中的由該終端啟動的程序發出些信號。默認動作為終止進程。
4)SIGILL:CPU檢測到某進程執行了非法指令。默認動作為終止進程并產生core文件
5)SIGTRAP:該信號由斷點指令或其他 trap指令產生。默認動作為終止里程 并產生core文件。
6 ) SIGABRT:調用abort函數時產生該信號。默認動作為終止進程并產生core文件。
7)SIGBUS:非法訪問內存地址,包括內存對齊出錯,默認動作為終止進程并產生core文件。
8)SIGFPE:在發生致命的運算錯誤時發出。不僅包括浮點運算錯誤,還包括溢出及除數為0等所有的算法錯誤。默認動作為終止進程并產生core文件。
9)SIGKILL:無條件終止進程。本信號不能被忽略,處理和阻塞。默認動作為終止進程。它向系統管理員提供了可以殺死任何進程的方法。
10)SIGUSE1:用戶定義 的信號。即程序員可以在程序中定義并使用該信號。默認動作為終止進程。
11)SIGSEGV:指示進程進行了無數內存訪問。默認動作為終止進程并產生core文件。
12)SIGUSR2:這是另外一個用戶自定義信號 ,程序員可以在程序中定義 并使用該信號。默認動作為終止進程。1
13)SIGPIPE:Broken pipe向一個沒有讀端的管道寫數據。默認動作為終止進程。
14 ) SIGALRM:定時器超時,超時的時間 由系統調用alarm設置。默認動作為終止進程。
15)SIGTERM:程序結束信號,與SIGKILL不同的是,該信號可以被阻塞和終止。通常用來要示程序正常退出。執行shell命令Kill時,缺省產生這個信號。默認動作為終止進程。
16)SIGCHLD:子進程結束時,父進程會收到這個信號。默認動作為忽略這個信號。
17)SIGCONT:停止進程的執行。信號不能被忽略,處理和阻塞。默認動作為終止進程。
18)SIGTTIN:停止進程的運行,但該信號可以被處理和忽略。按下<ctrl+z>組合鍵發出災個信號。默認動作為暫停進程。
19)SIGTSTP:停止進程的運行,可該信號可以被處理可忽略。按下<ctrl+z>組合鍵時發出這個信號。默認動作為暫停進程。
21)SIGTTOU:該信號類似于SIGTTIN,在后臺進程要向終端輸出數據時發生。默認動作為暫停進程。
22)SIGURG:套接字上有緊急數據時,向當前正在運行的進程發出些信號,報告有緊急數據到達。默認動作為忽略該信號。
23)SIGXFSZ:進程執行時間超過了分配給該進程的CPU時間 ,系統產生該信號并發送給該進程。默認動作為終止進程。
24)SIGXFSZ:超過文件的最大長度設置。默認動作為終止進程。
25)SIGVTALRM:虛擬時鐘超時時產生該信號。類似于SIGALRM,但是該信號只計算該進程占用CPU的使用時間。默認動作為終止進程。
26)SGIPROF:類似于SIGVTALRM,它不公包括該進程占用CPU時間還包括執行系統調用時間。默認動作為終止進程。
27)SIGWINCH:窗口變化大小時發出。默認動作為忽略該信號。
28)SIGIO:此信號向進程指示發出了一個異步IO事件。默認動作為忽略。
29)SIGPWR:關機。默認動作為終止進程。
30)SIGSYS:無效的系統調用。默認動作為終止進程并產生core文件。
31)SIGRTMIN~(64)SIGRTMAX:LINUX的實時信號,它們沒有固定的含義(可以由用戶自定義)。所有的實時信號的默認動作都為終止進程。
進程對信號的響應
進程能夠通過三種方式來響應一個信號:(1)忽略信號,即對信號不做任何處理,其中,有兩個信號不能忽略:SIGKILL及SIGSTOP; (2)捕獲信號。定義信號處理函數,當信號發生時,執行相應的處理函數;(3)執行缺省操作,Linux對每種信號都規定了默認操作,周詳情況請參考 [2]連同其他資料。注意,進程對實時信號的缺省反應是進程終止。
Linux究竟采用上述三種方式的哪一個來響應信號,取決于傳遞給相應API函數的參數。
信號的發送
發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()連同abort()。
1、kill()
#include
#include
int kill(pid_t pid,int signo)
參數pid的值 信號的接收進程
pid>0 進程ID為pid的進程
pid=0 同一個進程組的進程
pid<0 pid!=-1 進程組ID為 -pid的任何進程
pid=-1 除發送進程自身外,任何進程ID大于1的進程
Sinno是信號值,當為0時(即空信號),實際不發送任何信號,但照常進行錯誤檢查,因此,可用于檢查目標進程是否存在,連同當前進程是否具備 向目標發送信號的權限(root權限的進程能夠向任何進程發送信號,非root權限的進程只能向屬于同一個session或同一個用戶的進程發送信 號)。
Kill()最常用于pid>0時的信號發送,調用成功返回 0; 否則,返回 -1。注:對于pid<0時的情況,對于哪些進程將接受信號,各種版本說法不一,其實很簡單,參閱內核源碼kernal/signal.c即可,上 表中的規則是參考red hat 7.2。
2、raise()
#include
int raise(int signo)
3、sigqueue()
#include
#include
int sigqueue(pid_t pid, int sig, const union sigval val)
調用成功返回 0;否則,返回 -1。
sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數,和函數sigaction()配合使用。
sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是個聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4字節值。
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。假如 signo=0,將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用于檢查pid的有效性連同當前進程是否有權限向目標進程發送信號。
在調用sigqueue時,sigval_t指定的信息會拷貝到3參數信號處理函數(3參數信號處理函數指的是信號處理函數由 sigaction安裝,并設定了sa_sigaction指針,稍后將闡述)的siginfo_t結構中,這樣信號處理函數就能夠處理這些信息了。由于 sigqueue系統調用支持發送帶參數信號,所以比kill()系統調用的功能要靈活和強大得多。
注:sigqueue()發送非實時信號時,第三個參數包含的信息仍然能夠傳遞給信號處理函數; sigqueue()發送非實時信號時,仍然不支持排隊,即在信號處理函數執行過程中到來的任何相同信號,都被合并為一個信號。
4、alarm()
#include
unsigned int alarm(unsigned int seconds)
專門為SIGALRM信號而設,在指定的時間seconds秒后,將向進程本身發送SIGALRM信號,又稱為鬧鐘時間。進程調用alarm后,任何以前的alarm()調用都將無效。假如參數seconds為零,那么進程內將不再包含任何鬧鐘時間。
5、setitimer()
#include
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
setitimer()比alarm功能強大,支持3種類型的定時器:
• ITIMER_REAL: 設定絕對時間;經過指定的時間后,內核將發送SIGALRM信號給本進程;
• ITIMER_VIRTUAL 設定程式執行時間;經過指定的時間后,內核將發送SIGVTALRM信號給本進程;
• ITIMER_PROF 設定進程執行連同內核因本進程而消耗的時間和,經過指定的時間后,內核將發送ITIMER_VIRTUAL信號給本進程;
Setitimer()第一個參數which指定定時器類型(上面三種之一);第二個參數是結構itimerval的一個實例,結構itimerval形式見附錄1。第三個參數可不做處理。
Setitimer()調用成功返回0,否則返回-1。
6、abort()
#include
void abort(void);
向進程發送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。即使SIGABORT被進程配置為阻塞信號,調用abort()后,SIGABORT仍然能被進程接收。該函數無返回值。
信號安裝假如進程要處理某一信號,那么就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值的動作之間的映射關系,即進程將要處理哪個信號;該信號被傳遞給進程時,將執行何種操作。
linux主要有兩個函數實現信號的安裝:signal()、sigaction()。其中signal()在可靠信號系統調用的基礎上實現, 是庫函數。他只有兩個參數,不支持信號傳遞信息,主要是用于前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實現: sys_signal連同sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來和 sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優于signal()主要體現在支持信號帶有參數。
1、signal()
#include
void (*signal(int signum, void (*handler))(int)))(int);
假如該函數原型不容易理解的話,能夠參考下面的分解方式來理解:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,能夠忽略該信號(參數設為SIG_IGN);能夠采用系統默認方式處理信號(參數設為SIG_DFL);也能夠自己實現處理方式(參數指定一個函數地址)。
假如signal()調用成功,返回最后一次為安裝信號signum而調用signal()時的handler值;失敗則返回SIG_ERR。
2、sigaction()
#include
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
sigaction函數用于改變進程接收到特定信號后的行為。該函數的第一個參數為信號的值,能夠為除SIGKILL及SIGSTOP外的任何一 個特定有效的信號(為這兩個信號定義自己的處理函數,將導致信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例的指針,在結構 sigaction的實例中,指定了對特定信號的處理,能夠為空,進程會以缺省方式對信號處理;第三個參數oldact指向的對象用來保存原來對相應信號 的處理,可指定oldact為NULL。假如把第二、第三個參數都設為NULL,那么該函數可用于檢查信號的有效性。
第二個參數最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程中應屏蔽掉哪些函數等等。
sigaction結構定義如下:
struct sigaction {
union{
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int,struct siginfo *, void *);
}_u
sigset_t sa_mask;
unsigned long sa_flags;
void (*sa_restorer)(void);
}
其中,sa_restorer,已過時,POSIX不支持他,不應再被使用。
1、聯合數據結構中的兩個元素_sa_handler連同*_sa_sigaction指定信號關聯函數,即用戶指定的信號處理函數。除了能夠是用戶自定義的處理函數外,還能夠為SIG_DFL(采用缺省的處理方式),也能夠為SIG_IGN(忽略信號)。
2、由_sa_handler指定的處理函數只有一個參數,即信號值,所以信號不能傳遞除信號值之外的任何信息;由_sa_sigaction是 指定的信號處理函數帶有三個參數,是為實時信號而設的(當然同樣支持非實時信號),他指定一個3參數信號處理函數。第一個參數為信號值,第三個參數沒有使 用(posix沒有規范使用該參數的標準),第二個參數是指向siginfo_t結構的指針,結構中包含信號攜帶的數據值,參數所指向的結構如下:
siginfo_t {
int si_signo; /* 信號值,對任何信號有意義*/
int si_errno; /* errno值,對任何信號有意義*/
int si_code; /* 信號產生的原因,對任何信號有意義*/
union{ /* 聯合數據結構,不同成員適應不同信號 */
//確保分配足夠大的存儲空間
int _pad[SI_PAD_SIZE];
//對SIGKILL有意義的結構
struct{
...
}...
... ...
... ...
//對SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結構
struct{
...
}...
... ...
}
}
注:為了更便于閱讀,在說明問題時常把該結構表示為附錄2所表示的形式。
siginfo_t結構中的聯合數據成員確保該結構適應任何的信號,比如對于實時信號來說,則實際采用下面的結構形式:
typedef struct {
int si_signo;
int si_errno;
int si_code;
union sigval si_value;
} siginfo_t;
結構的第四個域同樣為一個聯合數據結構:
union sigval {
int sival_int;
void *sival_ptr;
}
采用聯合數據結構,說明siginfo_t結構中的si_value要么持有一個4字節的整數值,要么持有一個指針,這就構成了和信號相關的數 據。在信號的處理函數中,包含這樣的信號相關數據指針,但沒有規定具體如何對這些數據進行操作,操作方法應該由程式研發人員根據具體任務事先約定。
前面在討論系統調用sigqueue發送信號時,sigqueue的第三個參數就是sigval聯合數據結構,當調用sigqueue時,該數 據結構中的數據就將拷貝到信號處理函數的第二個參數中。這樣,在發送信號同時,就能夠讓信號傳遞一些附加信息。信號能夠傳遞信息對程式研發是很有意義 的。
信號參數的傳遞過程可圖示如下:
3、sa_mask指定在信號處理程式執行過程中,哪些信號應當被阻塞。缺省情況下當前信號本身被阻塞,防止信號的嵌套發送,除非指定SA_NODEFER或SA_NOMASK標志位。
注:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數執行過程中由sa_mask指定的信號才被阻塞。
4、sa_flags中包含了許多標志位,包括剛剛提到的SA_NODEFER及SA_NOMASK標志位。另一個比較重要的標志位是 SA_SIGINFO,當設定了該標志位時,表示信號附帶的參數能夠被傳遞到信號處理函數中,因此,應該為sigaction結構中的 sa_sigaction指定處理函數,而不應該為sa_handler指定信號處理函數,否則,配置該標志變得毫無意義。即使為 sa_sigaction指定了信號處理函數,假如不配置SA_SIGINFO,信號處理函數同樣不能得到信號傳遞過來的數據,在信號處理函數中對這些信 息的訪問都將導致段錯誤(Segmentation fault)。
注:很多文獻在闡述該標志位時都認為,假如配置了該標志位,就必須定義三參數信號處理函數。實際不是這樣的,驗證方法很簡單:自己實現一個單一 參數信號處理函數,并在程式中配置該標志位,能夠察看程式的運行結果。實際上,能夠把該標志位看成信號是否傳遞參數的開關,假如配置該位,則傳遞參數;否 則,不傳遞參數。
Linux信號阻塞和信號未決
1. 信號掩碼——被阻塞的信號集
每個進程都有一個用來描述哪些信號傳送來將被阻塞的信號集,如果某種信號在某個進程的阻塞信號集中,則傳送到該進程的此種信號將會被阻塞。當前被進程阻塞的信號集也叫信號掩碼,類型為sigset_t。每個進程都有自己 的信號掩碼,且創建子進程時,子進程會繼承父進程的信號掩碼。
2. 信號阻塞和忽略的區別
阻塞的概念與忽略信號是不同的:操作系統在信號被進程解除阻塞之前不會將信號傳遞出去,被阻塞的信號也不會影響進程的行為,信號只是暫時被阻止傳遞;當進程忽略一個信號時,信號會被傳遞出去,但進程將信號丟棄。
3. 信號集的操作
信號集可以由以下幾個函數操作:
int sigemptyset(sigset_t *set); //清空信號集
int sigfillset(sigset_t *set); //將所有信號填充進set中
int sigaddset(sigset_t *set, int signum); //往set中添加信號signum
int sigdelset(sigset_t *set, int signum); //從set中移除信號signum
int sigismember(const sigset_t *set, int signum); //判斷signnum是不是包含在set中,在返回1,不在返回0
初始化往往可以用sigemptyset()將信號集清空,再用sigaddset()向信號集中添加信號;或者可以使用sigfillset()將所有信號添加到信號集,再用sigdelset()將某信號從中刪除掉。
4. sigprocmask()介紹
可以使用函數sigprocmask()來檢查或者修改進程的信號掩碼。函數信息如下:
#include <signal.h>
int sigprocmask ( int how, const sigset_t *restrict set,
sigset_t *restrict old );
參數how 是一個整數,說明信號掩碼的修改方式:
SIG_BLOCK --- 將set指向的信號集中的信號添加到當前阻塞信號集中;
SIG_UNBLOCK --- 從當前阻塞信號集中移除set指向的信號集中的信號;
SIG_SETMASK --- 指定set所指向的信號集為當前阻塞信號集。
此外,如果參數set 為NULL, 說明不需要修改,如果old 為NULL,sigprocmask會將修改之前的信號集放在*old 之中返回。
5.sigaction()回顧
在前面有用過sigaction()函數:
include <signal.h>
int sigaction(int signum,const struct sigaction *act,
const struct sigaction *oldact);
該函數是用于注冊一個信號處理函數。參數結構體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); //保留
}
5.1 sa_handler:一個函數指針,用于指向原型為void handler(int)的信號處理函數地址(老類型的信號處理函數);
5.2 sa_sigaction:也是一個函數指針,用于指向原型為:
void handler(int (新類型的信號處理函數);
三個參數的含義為:
iSignNum:傳入的信號
pSignInfo:與該信號相關的一些信息,它是個結構體
pReserved:保留,現沒用
5.3 sa_handler和sa_sigaction只應該有一個生效,如果想采用老的信號處理機制,就應該讓sa_handler指向正確的信號處理函數; 否則應該讓sa_sigaction指向正確的信號處理函數,并且讓字段sa_flags包含SA_SIGINFO選項。
5.4 sa_mask是一個包含信號集合的結構體,該結構體內的信號表示在進行信號處理時,將要被阻塞的信號。該信號集可以用前面標題3提到的5個函數來進行操作。
5.5 字段sa_flags是一組掩碼的合成值,指示信號處理時所應該采取的一些行為,各掩碼的含義為:
(1)SA_RESETHAND ---處理完畢要捕捉的信號后,將自動撤消信號處理函數的注冊,即必須再重新注冊信號處理函數,才能繼續處理接下來產生的信號。
(2)SA_NODEFER ---在處理信號時,如果又發生了其它的信號,則立即進入其它信號的處理,等其它信號處理完畢后,再繼續處理當前的信號,即遞規地處理。如果sa_flags包含了該掩碼,則結構體sigaction的sa_mask將無效;
(3)SA_RESTART--- 如果在發生信號時,程序正阻塞在某個系統調用,例如調用read()函數,則在處理完畢信號后,接著從阻塞的系統返回。該掩碼符合普通的程序處理流程,所以一般來說,應該設置該掩碼,否則信號處理完后,阻塞的系統調用將會返回失敗;
(4)SA_SIGINFO ---指示結構體的信號處理函數指針是哪個有效,如果sa_flags包含該掩碼,則sa_sigactiion指針有效,否則是sa_handler指針有效。
需要注意的是:
函數sigprocmask是全程阻塞,在sigprocmask中設置了阻塞集合后,被阻塞的信號將不能再被信號處理函數捕捉,直到重新設置阻塞信號 集合。而在sigaction()注冊信號處理函數時,選擇阻塞的信號集只是在處理捕捉的信號時,才對指定的其他信號進行阻塞。
6、信號未決sigpending(sigset_t *set))獲得當前已遞送到進程,卻被阻塞的任何信號,在set指向的信號集中返回結果。
sigsuspend(const sigset_t *mask))用于在接收到某個信號之前, 臨時用mask替換進程的信號掩碼, 并暫停進程執行,直到收到信號為止。sigsuspend 返回后將恢復調用之前的信號掩碼。信號處理函數完成后,進程將繼續執行。該系統調用始終返回-1,并將errno配置為EINTR。
計時器與信號
睡眠函數 Linux下有兩個睡眠函數,原型為:
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
void usleep(unsigned long usec);
函數sleep讓進程睡眠seconds秒,函數usleep讓進程睡眠usec毫秒。
sleep睡眠函數內部是用信號機制進行處理的,用到的函數有:
#include <unistd.h>
unsigned int alarm(unsigned int seconds); //告知自身進程,要進程在seconds秒后自動產生一個//SIGALRM的信號,
int pause(void); //將自身進程掛起,直到有信號發生時才從pause返回
示例:模擬睡眠3秒:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void SignHandler(int iSignNo)
{
printf("signal:%d\n",iSignNo);
}
int main()
{
signal(SIGALRM,SignHandler);
alarm(3);
printf("Before pause().\n");
pause();
printf("After pause().\n");
return 0;
}
注意:因為sleep在內部是用alarm實現的,所以在程序中最好不要sleep與alarm混用,以免造成混亂。
時鐘處理 Linux為每個進程維護3個計時器,分別是真實計時器、虛擬計時器和實用計時器。
真實計時器計算的是程序運行的實際時間;
虛擬計時器計算的是程序運行在用戶態時所消耗的時間(可認為是實際時間減掉(系統調用和程序睡眠所消耗)的時間);
實用計時器計算的是程序處于用戶態和處于內核態所消耗的時間之和。
例如:有一程序運行,在用戶態運行了5秒,在內核態運行了6秒,還睡眠了7秒,則真實計算器計算的結果是18秒,虛擬計時器計算的是5秒,實用計時器計算的是11秒。
用指定的初始間隔和重復間隔時間為進程設定好一個計時器后,該計時器就會定時地向進程發送時鐘信號。3個計時器發送的時鐘信號分別為:SIGALRM,SIGVTALRM和SIGPROF。
用到的函數與數據結構:
#include <sys/time.h>
//獲取計時器的設置
//which指定哪個計時器,可選項為ITIMER_REAL(真實計時器)、ITIMER_VITUAL(虛擬計時器、ITIMER_PROF(實用計時器))
//value為一結構體的傳出參數,用于傳出該計時器的初始間隔時間和重復間隔時間
//如果成功,返回0,否則-1
int getitimer(int which, struct itimerval *value);
//設置計時器
//which指定哪個計時器,可選項為ITIMER_REAL(真實計時器)、ITIMER_VITUAL(虛擬計時器、ITIMER_PROF(實用計時器))
//value為一結構體的傳入參數,指定該計時器的初始間隔時間和重復間隔時間
//ovalue為一結構體傳出參數,用于傳出以前的計時器時間設置。
//如果成功,返回0,否則-1
int setitimer(int which, const struct itimerval *value, struct itimer val *ovalue);
struct itimerval {
struct timeval it_interval; /* next value */ //重復間隔
struct timeval it_value; /* current value */ //初始間隔
};
struct timeval {
long tv_sec; /* seconds */ //時間的秒數部分
long tv_usec; /* microseconds */ //時間的微秒部分
};
示例:啟用真實計時器的進行時鐘處理
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
void TimeInt2Obj(int imSecond,timeval *ptVal)
{
ptVal->tv_sec=imSecond/1000;
ptVal->tv_usec=(imSecond%1000)*1000;
}
void SignHandler(int SignNo)
{
printf("Clock\n");
}
int main()
{
signal(SIGALRM,SignHandler);
itimerval tval;
TimeInt2Obj(1,&tval.it_value); //設初始間隔為1毫秒,注意不要為0
TimeInt2Obj(1500,&tval.it_interval); //設置以后的重復間隔為1500毫秒
setitimer(ITIMER_REAL,&tval,NULL);
while(getchar()!=EOF);
return 0;
}
信號生命周期
從信號發送到信號處理函數的執行完畢
對于一個完整的信號生命周期(從信號發送到相應的處理函數執行完畢)來說,能夠分為三個重要的階段,這三個階段由四個重要事件來刻畫:信號誕生;信號在進程中注冊完畢;信號在進程中的注銷完畢;信號處理函數執行完畢。相鄰兩個事件的時間間隔構成信號生命周期的一個階段。
下面闡述四個事件的實際意義:
1. 信號"誕生"。信號的誕生指的是觸發信號的事件發生(如檢測到硬件異常、定時器超時連同調用信號發送函數kill()或sigqueue()等)。
2. 信號在目標進程中"注冊";進程的task_struct結構中有關于本進程中未決信號的數據成員:
3. struct sigpending pending:
4. struct sigpending{
5. struct sigqueue *head, **tail;
6. sigset_t signal;
7. };
第三個成員是進程中任何未決信號集,第一、第二個成員分別指向一個sigqueue類型的結構鏈(稱之為"未決信號信息鏈")的首尾,信息鏈中的每個sigqueue結構刻畫一個特定信號所攜帶的信息,并指向下一個sigqueue結構:
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
信號在進程中注冊指的就是信號值加入到進程的未決信號集中(sigpending結構的第二個成員sigset_t signal),并且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結構中。只要信號在進程的未決信號集中,表明進程已知道這些信號的 存在,但還沒來得及處理,或該信號被進程阻塞。
注:
當一個實時信號發送給一個進程時,不管該信號是否已在進程中注冊,都會被再注冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味著 同一個實時信號能夠在同一個進程的未決信號信息鏈中占有多個sigqueue結構(進程每收到一個實時信號,都會為他分配一個結構來登記該信號信息,并把 該結構添加在未決信號鏈尾,即任何誕生的實時信號都會在目標進程中注冊);
當一個非實時信號發送給一個進程時,假如該信號已在進程中注冊,則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。這 意味著同一個非實時信號在進程的未決信號信息鏈中,至多占有一個sigqueue結構(一個非實時信號誕生后,(1)、假如發現相同的信號已在目標結構 中注冊,則不再注冊,對于進程來說,相當于不知道本次信號發生,信號丟失;(2)、假如進程的未決信號中沒有相同信號,則在進程中注冊自己)。
8. 信號在進程中的注銷。在目標進程執行過程中,會檢測是否有信號等待處理(每次從系統空間返回到用戶空間時都做這樣的檢查)。假如存在未決信號等待處理且該 信號沒有被進程阻塞,則在運行相應的信號處理函數前,進程會把信號在未決信號鏈中占有的結構卸掉。是否將信號從進程未決信號集中刪除對于實時和非實時信號 是不同的。對于非實時信號來說,由于在未決信號信息鏈中最多只占用一個sigqueue結構,因此該結構被釋放后,應該把信號在進程未決信號集中刪除(信 號注銷完畢);而對于實時信號來說,可能在未決信號信息鏈中占用多個sigqueue結構,因此應該針對占用sigqueue結構的數目區別對待:假如只 占用一個sigqueue結構(進程只收到該信號一次),則應該把信號在進程的未決信號集中刪除(信號注銷完畢)。否則,不應該在進程的未決信號集中刪除 該信號(信號注銷完畢)。
進程在執行信號相應處理函數之前,首先要把信號在進程中注銷。
9. 信號生命終止。進程注銷信號后,立即執行相應的信號處理函數,執行完畢后,信號的本次發送對進程的影響完全結束。
注:
1)信號注冊和否,和發送信號的函數(如kill()或sigqueue()等)連同信號安裝函數(signal()及sigaction()) 無關,只和信號值有關(信號值小于SIGRTMIN的信號最多只注冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進程接收到就被 注冊)。
2)在信號被注銷到相應的信號處理函數執行完畢這段時間內,假如進程又收到同一信號多次,則對實時信號來說,每一次都會在進程中注冊;而對于非實時信號來說,無論收到多少次信號,都會視為只收到一個信號,只在進程中注冊一次。
信號編程注意事項
1. 防止不該丟失的信號丟失。假如對八中所提到的信號生命周期理解深刻的話,很容易知道信號會不會丟失,連同在哪里丟失。
2. 程式的可移植性
考慮到程式的可移植性,應該盡量采用POSIX信號函數,POSIX信號函數主要分為兩類:
o POSIX 1003.1信號函數: Kill()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、 sigismember()、sigpending()、sigprocmask()、sigsuspend()。
o POSIX 1003.1b信號函數。POSIX 1003.1b在信號的實時性方面對POSIX 1003.1做了擴展,包括以下三個函數: sigqueue()、sigtimedwait()、sigwaitinfo()。其中,sigqueue主要針對信號發送,而 sigtimedwait及sigwaitinfo()主要用于取代sigsuspend()函數,后面有相應實例。
o #include
o int sigwaitinfo(sigset_t *set, siginfo_t *info).
該函數和sigsuspend()類似,阻塞一個進程直到特定信號發生,但信號到來時不執行信號處理函數,而是返回信號值。因此為了避免執行相應的信號處理函數,必須在調用該函數前,使進程屏蔽掉set指向的信號,因此調用該函數的典型代碼是:
sigset_t newmask;
int rcvd_sig;
siginfo_t info;
sigemptyset(&newmask);
sigaddset(&newmask, SIGRTMIN);
sigprocmask(SIG_BLOCK, &newmask, NULL);
rcvd_sig = sigwaitinfo(&newmask, &info)
if (rcvd_sig == -1) {
..
}
調用成功返回信號值,否則返回-1。sigtimedwait()功能相似,只但是增加了一個進程等待的時間。
3. 程式的穩定性。
為了增強程式的穩定性,在信號處理函數中應使用可重入函數。
信號處理程式中應當使用可再入(可重入)函數(注:所謂可重入函數是指一個能夠被多個任務調用的過程,任務在調用時不必擔心數據是否會出錯)。因為進程在 收到信號后,就將跳轉到信號處理函數去接著執行。假如信號處理函數中使用了不可重入函數,那么信號處理函數可能會修改原來進程中不應該被修改的數據,這樣 進程從信號處理函數中返回接著執行時,可能會出現不可預料的后果。不可再入函數在信號處理函數中被視為不安全函數。
滿足下列條件的函數多數是不可再入的:(1)使用靜態的數據結構,如getlogin(),gmtime(),getgrgid(), getgrnam(),getpwuid()連同getpwnam()等等;(2)函數實現時,調用了malloc()或free()函數;(3)實現 時使用了標準I/O函數的。The Open Group視下列函數為可再入的:
_exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、 cfsetospeed()、chdir()、chmod()、chown()、close()、creat()、dup()、dup2()、 execle()、execve()、fcntl()、fork()、fpathconf()、fstat()、fsync()、getegid()、 geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、 kill()、link()、lseek()、mkdir()、mkfifo()、 open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、setgid ()、setpgid()、setsid()、setuid()、 sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、 sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、 stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、 tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、 umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。
即使信號處理函數使用的都是"安全函數",同樣要注意進入處理函數時,首先要保存errno的值,結束時,再恢復原值。因為,信號處理過程中,errno 值隨時可能被改變。另外,longjmp()連同siglongjmp()沒有被列為可再入函數,因為不能確保緊接著兩個函數的其他調用是安全的。
信號應用實例
信號的安裝(配置信號關聯動作)
linux下的信號應用并沒有想象的那么恐怖,程式員所要做的最多只有三件事情:
1. 安裝信號(推薦使用sigaction());
2. 實現三參數信號處理函數,handler(int signal,struct siginfo *info, void *);
3. 發送信號,推薦使用sigqueue()。
實際上,對有些信號來說,只要安裝信號就足夠了(信號處理方式采用缺省或忽略)。其他可能要做的無非是和信號集相關的幾種操作。
實例一:信號發送及處理
實現一個信號接收程式sigreceive(其中信號安裝由sigaction())。
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=new_op;
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d", signum);
sleep(5);
}
說明,命令行參數為信號值,后臺運行sigreceive signo &,可獲得該進程的ID,假設為pid,然后再另一終端上運行kill -s signo pid驗證信號的發送接收及處理。同時,可驗證信號的排隊問題。
注:能夠用sigqueue實現一個命令行信號發送程式sigqueuesend
實例二:信號傳遞附加信息
主要包括兩個實例:
1. 向進程本身發送信號,并傳遞指針參數;
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
union sigval mysigval;
int i;
int sig;
pid_t pid;
char data[10];
memset(data,0,sizeof(data));
for(i=0;i < 5;i )
data[i]='2';
mysigval.sival_ptr=data;
sig=atoi(argv[1]);
pid=getpid();
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;//三參數信號處理函數
act.sa_flags=SA_SIGINFO;//信息傳遞開關
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
sigqueue(pid,sig,mysigval);//向本進程發送信號,并傳遞附加信息
}
}
void new_op(int signum,siginfo_t *info,void *myact)//三參數信號處理函數的實現
{
int i;
for(i=0;i<10;i )
{
printf("%c\n ",(*( (char*)((*info).si_ptr) i)));
}
printf("handle signal %d over;",signum);
}
這個例子中,信號實現了附加信息的傳遞,信號究竟如何對這些信息進行處理則取決于具體的應用。
2、 不同進程間傳遞整型參數:把1中的信號發送和接收放在兩個程式中,并且在發送過程中傳遞整型參數。
信號接收程式:
#include
#include
#include
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
pid_t pid;
pid=getpid();
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;
act.sa_flags=SA_SIGINFO;
if(sigaction(sig,&act,NULL)<0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("the int value is %d \n",info->si_int);
}
信號發送程式:命令行第二個參數為信號值,第三個參數為接收進程ID。
main(int argc,char**argv)
{
pid_t pid;
int signum;
union sigval mysigval;
signum=atoi(argv[1]);
pid=(pid_t)atoi(argv[2]);
mysigval.sival_int=8;//不代表具體含義,只用于說明問題
if(sigqueue(pid,signum,mysigval)==-1)
printf("send error\n");
sleep(2);
}
實例三:信號阻塞及信號集操作
#include "signal.h"
#include "unistd.h"
static void my_op(int);
main()
{
sigset_t new_mask,old_mask,pending_mask;
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void*)my_op;
if(sigaction(SIGRTMIN 10,&act,NULL))
printf("install signal SIGRTMIN 10 error\n");
sigemptyset(&new_mask);
sigaddset(&new_mask,SIGRTMIN 10);
if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
printf("block signal SIGRTMIN 10 error\n");
sleep(10);
printf("now begin to get pending mask and unblock SIGRTMIN 10\n");
if(sigpending(&pending_mask)<0)
printf("get pending mask error\n");
if(sigismember(&pending_mask,SIGRTMIN 10))
printf("signal SIGRTMIN 10 is pending\n");
if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
printf("unblock signal error\n");
printf("signal unblocked\n");
sleep(10);
}
static void my_op(int signum)
{
printf("receive signal %d \n",signum);
}
編譯該程式,并以后臺方式運行。在另一終端向該進程發送信號(運行kill -s 42 pid,SIGRTMIN 10為42),查看結果能夠看出幾個關鍵函數的運行機制,信號集相關操作比較簡單。
注:在上面幾個實例中,使用了printf()函數,只是作為診斷工具,pringf()函數是不可重入的,不應在信號處理函數中使用。
用sigqueue實現的命令行信號發送程式sigqueuesend,命令行第二個參數是發送的信號值,第三個參數是接收該信號的進程ID,能夠配合實例一使用:
int main(int argc,char**argv)
{
pid_t pid;
int sig;
sig=atoi(argv[1]);
pid=atoi(argv[2]);
sigqueue(pid,sig,NULL);
sleep(2);
}