进程间通信——信号
進程間通信——信號
宗旨:技術的學習是有限的,分享的精神是無限的。
一、信號和中斷
1、信號基本概念
(1)發送信號:產生信號,有多種發送信號的方式【一個進程到另一個進程,內核向用戶,進程向自己】
(2)安裝信號:設置信號到來時不再執行默認操作,而是執行自定義的代碼。
(3)遞送信號:一個信號被操作系統發送到目標進程引起某段處理程序的執行。
(4)捕獲信號:被遞送的信號在目標進程引起某段處理程序的執行。
(5)屏蔽信號:進程告訴操作系統暫時不接受某些信號。
(6)忽略信號:進程被遞送到目標進程,但目標進程不處理,直接丟棄。
(7)未決信號:信號已經產生,但因目標進程暫時屏蔽該信號而不能被目標進程捕獲到的信號。
(8)可靠信號和不可靠信號:編號小于32的信號是不可靠信號,大于32的是可靠信號。
? ? ?信號的“未決”是一種狀態,指的是從信號的產生到信號被處理前的這一段過程。信號的“屏蔽”是一個開關動作,指的是暫時阻止該信號被處理。
輸入命令kill –l查看系統定義的信號列表:
$ kill-l
?1) SIGHUP??????2) SIGINT?????? 3) SIGQUIT????? 4) SIGILL?????? 5) SIGTRAP
?6) SIGABRT?????7) SIGEMT?????? 8) SIGFPE?????? 9) SIGKILL???? 10) SIGBUS
11)SIGSEGV???? 12) SIGSYS????? 13) SIGPIPE???? 14) SIGALRM???? 15) SIGTERM
16)SIGURG????? 17) SIGSTOP? ???18)SIGTSTP???? 19) SIGCONT???? 20) SIGCHLD
21)SIGTTIN???? 22) SIGTTOU???? 23) SIGIO?????? 24) SIGXCPU???? 25) SIGXFSZ
26)SIGVTALRM?? 27) SIGPROF???? 28) SIGWINCH??? 29) SIGPWR????? 30) SIGUSR1
31)SIGUSR2???? 32) SIGRTMAX
?
二、發送信號
? ? ? ? 發送信號是指一個進程向另一個進程發送某個信號值,但實際并不是直接發送的,而是由OS轉發的。產生一個信號有多種情況:
(1) 用戶按下Ctrl-C,這個鍵盤輸入產生一個硬件中斷。
(2) 硬件異常產生信號。對一個無效存儲訪問的進程產生一個SIGSEGV
(3) 終止進程信號。其他進程調用kill()函數可將信號發送給另一個進程或進程組。
(4) 軟件異常產生信號。
1、kill發送一個信號到進程
? ? ? ? ——傳遞一個信號給指定進程使用kill()函數,給當前進程使用raise(),喚醒設置定時使用alarm()
(1)函數原型
#include<signal.h>
intkill(pid_t pid, int sig);
(2)函數參數
?????? pid:要被傳遞信號的進程號(PID)
????????????? pid>0:將信號發送給進程的PID值為pid的進程
????????????? pid=0:將信號發送給和當前進程在同一進程組的所有進程
????????????? pid=-1:將信號發送給系統內的所有進程
????????????? pid<0:將信號發送給進程組號PGID為pid絕對值的所有信號
?????? sig:發送的信號值
(3)返回值
?????? 成功返回0,失敗返回-1
?????? 可向某個進程發送kill -0信號以檢測進程是否存在,因為當前進程總是存在的:
?????? kill(getpid, 0);
?
2、raise自舉一個信號
? ? ? ? ——給當前進程發送一個信號,即喚醒一個進程。
(1)函數原型
?????? #include <signal.h>
?????? int raise(int sig);
(2)函數參數
?????? sig:發送的信號值
(3)返回值
?????? 成功返回0,失敗返回-1
?????? 此函數相當于:
if(kill(getpid(),sig) == -1)
{
? perror(“raise”);
}
?
3、alarm定時
? ? ? ? ——傳遞定時信號,即在多少時間內產生SIGALRM信號,調用一次產生一個信號。
(1)函數原型
?????? #include <unistd.h>
?????? int alarm(unsigned int seconds);
(2)函數參數
?????? seconds:多少時間內發送SIGALRM信號給當前進程
?????? ?????? seconds=0:取消所有發出的報警請求
(3)返回值
?????? 在調用alarm之前沒有調用過alarm,成功返回0,失敗返回-1;
?????? 此前調用過alarm函數,則將重新設置調用進程的鬧鐘,成功,將以當前時間為基準,返回值為上次設置的alarm將在多少時間內產生SIGALRM信號。
?
4、ualarm定時
? ? ? ? ——使當前進程在指定時間內產生SIGALRM信號,然后每隔指定時間重復產生SIGALRM信號。
(1)函數原型????
?????? useconds_t ualarm(useconds_t value,useconds_t interval);
(2)函數參數
?????? value:指定時間(us)內產生SIGALRM信號
interval:每隔指定時間重復產生SIGALRM
(3)返回值
?????? 成功返回0,
三、安裝信號和捕捉信號
1、信號處理方法
(1) 忽略此信號。
(2) 執行該信號的默認處理動作。
(3) 提供一個信號處理函數,要求內核在處理該信號時切換到用戶態執行這個處理函數,這種方 式稱為捕捉(Catch)一個信號。
2、signal安裝信號
——信號都有默認的處理方式,未做特殊處理,將執行默認操作;若要做特殊處理,則要安裝信號處理函數。
(1)函數原型
?????? #include <signal.h>
?????? typedef void (*sighandler_t)(int);
?????? sighandler_t signal(int sig, sighandlerhandler);
(2)函數參數
?????? sig:接收到的信號
?????? handler:接收到此信號后的處理代碼入口
(3)返回值
?????? 成功返回指向針對此信號的上一次設置,執行失敗返回SIG_EER(-1)錯誤。
3、sigaction安裝信號
? ? ? ? ——signal只能提供簡單的信號安裝操作,并逐步并淘汰。此函數可用來檢查和更改信號處理操作。
(1)函數原型
#include <signal.h> int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);struct sigaction {void (*sa_handler)(int);/* addr of signal handler, *//* or SIG_IGN, or SIG_DFL*/sigset_t sa_mask; /*additional signals to block */int sa_flags; /* signaloptions, Figure 10.16 *//* alternate handler */void (*sa_sigaction)(int, siginfo_t *, void *); };(2)函數參數
?????? signo:指定信號的編號
? ? ? ?若act指針非空,則根據act修改該信號的處理動作。若oact指針非 空,則通過oact傳出該信號原來的處理動作。
(3)返回值
?????? 成功返回0,失敗返回-1。
? ? ? ? 將sa_handler賦值為常數SIG_IGN傳給sigaction表示忽略信號,賦值為常數SIG_DFL表示執行系統默 認動作,賦值為一個函數指針表示用自定義函數捕捉信號,或者說向內核注冊了一個信號處理函 數,該函數返回值為void,可以帶一個int參數,通過參數可以得知當前信號的編號,這樣就可以 用同一個函數處理多種信號。顯然,這也是一個回調函數,不是被main函數調用,而是被系統所調用。
? ? ? ? 當某個信號的處理函數被調用時,內核自動將當前信號加入進程的信號屏蔽字,當信號處理函數返 回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產生,那么它會被阻塞到當前處理結束為止。如果在調用信號處理函數時,除了當前信號被自動屏蔽之外,還希望自動屏蔽另外一些信號,則用sa_mask字段說明這些需要額外屏蔽的信號,當信號處理函數返 回時自動恢復原來的信號屏蔽字。 sa_flags字段包含一些選項,我把sa_flags設為0, sa_sigaction是實時信號的處理函數。?
四、信號集操作
信號忽略:系統仍然傳遞該信號,指示相應進程對該信號不做任何處理。
信號屏蔽:即使傳遞信號給該進程,該進程也不捕捉信號。
#define SIGSET_NWOEDS (1024 / (8 * sizeof(unsigned long int)))typedef struct {unsigned long int val[SIGSET_NWOEDS]; } sigset_t;1、sigprocmask
? ? ? ? ?——設置進程屏蔽信號集
(1)函數原型
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
(2)函數參數
如果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 |
?
(3)返回值
?????? 若成功則為0,若出錯則為-1
?
2、sigpending
#include <signal.h>
int sigpending(sigset_t *set);
? ? ? ? ——讀取當前進程的未決信號集,通過set參數傳出。調用成功則返回0,出錯則返回-1。
#include<signal.h> #include<stdio.h> #include<unistd.h>void printsigset(const sigset_t *set) {int i;for (i = 1; i < 32; i++)if (sigismember(set, i) == 1){putchar('1');}else{putchar('0');}puts(""); }int main(void) {sigset_t s, p;sigemptyset(&s);sigaddset(&s, SIGINT);sigprocmask(SIG_BLOCK, &s, NULL);while (1){sigpending(&p);printsigset(&p);sleep(1);}return 0; }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">?</span>3、信號集
#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。
?
五、等待信號
? ? ? ? 進程可以因等待某些特定的信號而阻塞,pause()函數用來等待除當前進程外的任意信號,而sigsuspend()用來等待指定信號以外的任意信號。
1、pause
? ? ? ? ——使調用進程掛起直到有信號遞達
(1)函數原型
#include <unistd.h>
int pause(void);
(2)參數返回值
?????? 如果信號的處理動作是終止進程,則進程終止, pause函數沒有機會返回;如果信號的處理動作是忽略,則進程繼續處于掛起狀態, pause不返回;如果信號的處理動作是捕捉,則調用了信號處理函數之后pause返回-1, errno設置為EINTR, 所以pause只有出錯的返回值。
// 用alarm和pause實現sleep(3)函數 #include <unistd.h> #include <signal.h> #include <stdio.h> void sig_alrm(int signo) {/* nothing to do */ } unsigned int mysleep(unsigned int nsecs) {struct sigaction newact, oldact;unsigned int unslept;newact.sa_handler = sig_alrm;sigemptyset(&newact.sa_mask);newact.sa_flags = 0;sigaction(SIGALRM, &newact, &oldact);alarm(nsecs);pause();unslept = alarm(0);sigaction(SIGALRM, &oldact, NULL);return unslept; } int main(void) {while(1){mysleep(2);printf("Two secondspassed\n");}return 0; }(1) main函數調用mysleep函數,后者調用sigaction注冊了SIGALRM信號的處理函數sig_alrm。
(2) 調用alarm(nsecs)設定鬧鐘。
(3) 調用pause等待,內核切換到別的進程運行。
(4) nsecs秒之后,鬧鐘超時,內核發SIGALRM給這個進程。
(5) 從內核態返回這個進程的用戶態之前處理未決信號,發現有SIGALRM信號,其處理函數 是sig_alrm。
(6) 切換到用戶態執行sig_alrm函數,進入sig_alrm函數時SIGALRM信號被自動屏蔽,從sig_alrm函數返回時SIGALRM信號自動解除屏蔽。然后自動執行系統調用sigreturn再次進入 內核,再返回用戶態繼續執行進程的主控制流程( main函數調用的mysleep函數)。
(7)pause函數返回-1,然后調用alarm(0)取消鬧鐘,調用sigaction恢復SIGALRM信號以前的處理。
?
可重入函數——如果一個函數符合以下條件之一則是不可重入的:
調用了malloc或free,因為malloc也是用全局鏈表來管理堆的。
調用了標準I/O庫函數。標準I/O庫的很多實現都以不可重入的方式使用全局數據結構。
【通俗說:使用了全局變量的都是不可重入的】? ? ? ?
總結