在早期的UNIX中信號是不可靠的,不可靠在這裡指的是:信號可能丟失,一個信號發生了,但進程卻可能一直不知道這一點。
現在Linux 在SIGRTMIN實時信號之前的都叫不可靠信號,這裡的不可靠主要是不支持信號隊列,就是當多個信號發生在進程中的時候(收到信號的速度超過進程處理的速度的時候),這些沒來的及處理的信號就會被丟掉,僅僅留下一個信號。
可靠信號是多個信號發送到進程的時候(收到信號的速度超過進程處理信號的速度的時候),這些沒來的及處理的信號就會排入進程的隊列。等進程有機會來處理的時候,依次再處理,信號不丟失。
同時,信號的發送和安裝也出現了新版本:信號發送函數sigqueue()及信號安裝函數sigaction()。sigaction和signal函數都是調用內核服務do_signal函數;[內核服務函數,應用程序無法調用該函數
非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。
關於信號發送的一些API:
1.kill
int kill(pid_t pid, int signo);
參數:
pid:可能選擇有以下四種
1. pid大於零時,pid是信號欲送往的進程的標識。
2. pid等於零時,信號將送往所有與調用kill()的那個進程屬同一個使用組的進程。
3. pid等於-1時,信號將送往所有調用進程有權給其發送信號的進程,除了進程1(init)。
4. pid小於-1時,信號將送往以-pid為組標識的進程。
sig:准備發送的信號代碼,假如其值為零則沒有任何信號送出,但是系統會執行錯誤檢查,通常會利用sig值為零來檢驗某個進程是否仍在執行。
返回值說明: 成功執行時,返回0。失敗返回-1,errno被設為以下的某個值 EINVAL:指定的信號碼無效(參數 sig 不合法) EPERM;權限不夠無法傳送信號給指定進程 ESRCH:參數 pid 所指定的進程或進程組不存在。
void handler(int sig) { printf("recvv a sig =%d\n",sig); } int main() { if(signal(SIGUSR1,handler)==SIG_ERR) ERR_EXIT("signal error!"); pid_t pid=fork(); if(pid==-1) ERR_EXIT("fork error!"); else if(pid==0) { //等於 killpg(getpgrp(),SIGUSR1); pid=getpgrp(); kill(-pid,SIGUSR1); /* kill(getppid(),SIGUSR1); */ //以上 exit(EXIT_SUCCESS); } int n=5; do{ n=sleep(n); }while(n>0);// return 0; }
注意點:
(1)sleep函數會被信號打斷,進行完信號的處理函數後不再睡眠,而是繼續執行sleep函數以後的操作。如果我們就是想要程序睡眠一段時間呢?通過man手冊發現,sleep函數的返回值是 還剩余的秒數,所以可以采用循環的形式,即:
while(n=sleep(n));(2)如果發出信號的目標是進程組,那麼子進程fork的時候會繼承信號,從而會發生兩次信號處理。
2.raise
raise()給自己發送信號,等價於raise(getpid(),sig)
3.killpg
killpg()給進程組發送信號,killpg(pgrp,sig)等於kill(pgrp,sig)
4.sigqueue
int sigqueue(pid_t pid, int sig, const union sigval value);給進程發送信號,支持排隊,可以附帶信息。
alarm函數,設置一個鬧鐘延遲發送SIGALRM信號(告訴Linux內核n秒中以後,發送SIGALRM信號);
但是alarm函數一次只能發送一個信號,所以必須遞歸調用才可以實現間歇性發送信號的功能。
void handler(int sig) { printf("recvv a sig =%d\n",sig); alarm(1); //間接遞歸,持續發送信號 } int main() { if(signal(SIGALRM,handler)==SIG_ERR) ERR_EXIT("signal error!"); alarm(1); while(1) pause(); return 0; }
主要用於多任務環境中,一個可重入的函數簡單來說就是可以被中斷的函數,也就是說,可以在這個函數執行的任何時刻中斷它,轉入OS調度下去執行另外一段代碼,而返回控制時不會出現什麼錯誤;而不可重入的函數由於使用了一些系統資源,比如全局變量區,中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函數是不能運行在多任務環境下的。
也可以這樣理解,重入即表示重復進入,首先它意味著這個函數可以被中斷,其次意味著它除了使用自己棧上的變量以外不依賴於任何環境(包括static),這樣的函數就是purecode(純代碼)可重入,可以允許有該函數的多個副本在運行,由於它們使用的是分離的棧,所以不會互相干擾。如果確實需要訪問全局變量(包括static),一定要注意實施互斥手段。可重入函數在並行運行環境中非常重要,但是一般要為訪問全局變量付出一些性能代價。
編寫可重入函數時,若使用全局變量,則應通過關中斷、信號量(即P、V操作)等手段對其加以保護。
說明:若對所使用的全局變量不加以保護,則此函數就不具有可重入性,即當多個進程調用此函數時,很有可能使有關全局變量變為不可知狀態。
為了增強程序的穩定性,在信號處理函數中應使用可重入函數。
下面給出大家一個不可重入函數在信號處理程序中可能發生的錯誤:
ypedef struct { int a; int b; int c; int d; }TEST; TEST g_data; void handler(int sig) { // printf("recvv a sig =%d\n",sig); printf("%d %d %d %d\n",g_data.a,g_data.b,g_data.c,g_data.d); alarm(1); } int main() { TEST zeros={0,0,0,0}; TEST ones={1,1,1,1}; if(signal(SIGALRM,handler)==SIG_ERR) ERR_EXIT("signal error!"); g_data=zeros; alarm(1); while(1) { g_data=zeros; g_data=ones; } return 0; }
我們可以使用man 7 signal查看常用的可重入函數:
不可重入函數
滿足下列條件的函數多數是不可重入的:
(1)使用靜態數據結構,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;
(2)函數實現時,調用了malloc()或者free()函數;
(3)實現時使用了標准I/O函數