Linux信号实践(2) --信号分类
信號分類?
不可靠信號
Linux信號機制基本上是從UNIX系統中繼承過來的。早期UNIX系統中的信號機制比較簡單和原始,后來在實踐中暴露出一些問題,它的主要問題是:
? ?1.進程每次處理信號后,就將對信號的響應設置為默認動作。在某些情況下,將導致對信號的錯誤處理;因此,用戶如果不希望這樣的操作,那么就要在信號處理函數結尾再一次調用signal(),重新安裝該信號。
? ?2.因此導致,?早期UNIX下的不可靠信號主要指的是進程可能對信號做出錯誤的反應以及信號可能丟失。?
Linux支持不可靠信號,但是對不可靠信號機制做了改進:在調用完信號處理函數后,不必重新調用該信號的安裝函數(信號安裝函數是在可靠機制上的實現)。因此,Linux下的不可靠信號問題主要指的是信號可能丟失。
可靠信號
隨著時間的發展,實踐證明,有必要對信號的原始機制加以改進和擴充。所以,后來出現的各種UNIX版本分別在這方面進行了研究,力圖實現"可靠信號"。由于原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號(SIGRTMIN?~?SIGRTMAX),并在一開始就把它們定義為可靠信號,這些信號支持排隊,不會丟失。同時,信號的發送和安裝也出現了新版本:信號發送函數sigqueue()及信號安裝函數sigaction()。
sigaction和signal函數都是調用內核服務do_signal函數;[內核服務函數,應用程序無法調用該函數]
早期UNIX系統只定義了31種信號,而Linux?3.x支持64種信號,編號1-64(SIGRTMIN=34,SIGRTMAX=64),將來可能進一步增加,這需要得到內核的支持。 前31種信號已經有了預定義值,每個信號有了確定的用途及含義,并且每種信號都有各自的缺省動作。如按鍵盤的CTRL+C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。后32個信號表示實時信號,等同于可靠信號。這保證了發送的多個實時信號都被接收。實時信號是POSIX標準的一部分,可用于應用進程。
非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。
信號API-信號發送(1)
1.kill
int kill(pid_t pid, int signo);kill既可以向自身發送信號,也可以向其他進程發送信號?
signo參數組合情況解釋:
? ?pid>0?將信號sig發給pid進程
? ?pid=0?將信號sig發給同組進程
? ?pid=-1?將信號sig發送給所有進程,調用者進程有權限發送的每一個進程(除了1號進程之外,還有它自身)
? ?pid<-1?將信號sig發送給進程組是pid(絕對值)的每一個進程
//示例 void onSignalAction(int signalNumber) {switch(signalNumber){case SIGUSR1:cout << "SIGUSR1 = " << signalNumber << endl;break;default:cout << "Other Signal ..." << endl;break;} }int main() {if (signal(SIGUSR1,onSignalAction)== SIG_ERR){perror("signal error");return -1;}pid_t pid = fork();if (pid == -1){perror("fork error");return -1;}else if (pid == 0){/**向父進程發送信號pid_t ppid = getppid();kill(ppid,SIGUSR1);*//**向同組所有進程發送信號,子進程也會收到該信號kill(0,SIGUSR1);*///向本組所有進程發送信號,作用同上 //getpgrp()函數獲取進程組pidpid_t pgid = getpgrp();killpg(pgid,SIGUSR1);exit(0);}int sleepTime = 3;while (sleepTime > 0){write(STDOUT_FILENO,"Parent start Sleep...\n",sizeof("Parent start Sleep...\n"));sleepTime = sleep(sleepTime);write(STDOUT_FILENO,"Parent return from Sleep...\n",sizeof("Parent return from Sleep...\n"));}return 0; }注意:如果在fork之前安裝信號,則子進程可以繼承信號。
?
Sleep遇上signal,子進程向父進程發送信號,sleep函數的幾點說明
? ?1)sleep函數作用,讓進程睡眠。
? ?2)能被信號打斷,然后處理信號函數以后,就不再睡眠了。直接向下執行代碼
? ?3)sleep函數的返回值,是剩余的秒數
Man手冊顯示:
RETURN?VALUE
???????Zero?if?the?requested?time?has?elapsed,?or?the?number?of??seconds??left?to?sleep,?
if?the?call?was?interrupted?by?a?signal?handler.
//示例:sleep遇上signal void onSignalAction(int signalNumber) {switch(signalNumber){case SIGINT:cout << "SIGINT = " << signalNumber << endl;break;default:cout << "Other Signal ..." << endl;break;} }int main() {if (signal(SIGINT,onSignalAction)== SIG_ERR){perror("signal error");return -1;}cout << "Main Start Sleeping..." << endl;int returnValue = sleep(100); //可中斷睡眠cout << "Main End Sleeping... returnValue = " << returnValue << endl;return 0; } //示例:sleep加強 int main() { //...同上 cout << "Main Start Sleeping..." << endl; //sleep加強版^^int sleepTime = 20;do{sleepTime = sleep(sleepTime); cout << "continue..." << endl;}while (sleepTime > 0);cout << "Main End Sleeping... sleepTime = " << sleepTime << endl;return 0; }2.raise
int raise(int sig);給自己發送信號。raise(sig)等價于kill(getpid(),?sig);
3.killpg
int killpg(int pgrp, int sig);給進程組發送信號。killpg(pgrp,?sig)等價于kill(-pgrp,?sig);
4.sigqueue
int sigqueue(pid_t pid, int sig, const union sigval value);給進程發送信號,支持排隊,可以附帶信息。
信號API-pause
int pause(void);
將進程置為可中斷睡眠狀態。然后它調用內核函數schedule(),使Linux進程調度器找到另一個進程來運行。
pause使調用者進程掛起,直到一個信號被捕獲
//示例 int main() {if (signal(SIGINT,handler)== SIG_ERR)err_exit("signal error");while(true){pause();cout << "pause return..." << endl;} }信號API-信號發送(2)
unsigned int alarm(unsigned int seconds);alarm函數,設置一個鬧鐘延遲發送SIGALRM信號(告訴Linux內核n秒中以后,發送SIGALRM信號);
手冊描述-DESCRIPTION
???????alarm()?arranges?for?a?SIGALRM?signal?to?be?delivered?to?the?process?in?seconds?seconds.
???????If?seconds?is?zero,?no?new?alarm()?is?scheduled.
???????In?any?event?any?previously?set?alarm()?is?cancelled.
//alarm 遞歸調用 void onSignalAction(int signalNumber) {switch(signalNumber){case SIGALRM:cout << "SIGALRM = " << signalNumber << endl;alarm(1); //繼續調用onSignalActionbreak;default:cout << "Other Signal ..." << endl;break;} }int main() {if (signal(SIGALRM,onSignalAction)== SIG_ERR){perror("signal error");return -1;}alarm(1);while(true){pause();cout << "pause returned..." << endl;}return 0; }可重入/不可重入函數
? 所謂可重入函數是指一個可以被多個任務調用的過程,任務在調用時不必擔心數據是否會出錯。因為進程在收到信號后,就將跳轉到信號處理函數去接著執行。如果信號處理函數中使用了不可重入函數,那么信號處理函數可能會修改原來進程中不應該被修改的數據,這樣進程從信號處理函數中返回接著執行時,可能會出現不可預料的后果。不可重入函數在信號處理函數中被視為不安全函數。
? 為了增強程序的穩定性,在信號處理函數中應使用可重入函數。?
//不可重入函數示例 struct Teacher {int a;int b;int c;int d; };Teacher g_teacher; void onSigAlarm(int signo) {printf("%d %d", g_teacher.a, g_teacher.b);printf(" %d %d\n", g_teacher.c, g_teacher.d);alarm(1); }int main() {if (signal(SIGALRM,onSigAlarm)== SIG_ERR)err_exit("signal error");Teacher zero = {0, 0, 0, 0};Teacher ones = {1, 1, 1, 1};alarm(1);g_teacher = zero;while(true){g_teacher = zero;g_teacher = ones;} }輸出結果演示:
?
原因分析:
可以將語句g_teacher?=?zero分解為:
????????g_teacher.a?=?zero.a;
????????g_teacher.b?=?zero.b;
????????g_teacher.c?=?zero.c;
????????g_teacher.d?=?zero.d;
因此,?在這四條語句執行的中間,?如果此時SIGALRM信號到達(中斷到達),?則g_teacher中的一些數據會是新值,?而有些卻是以前留下的臟值,?究其原始則是g_teacher?=?zero不是原子操作,?而信號處理函數onSigAlarm卻又訪問了全局變量g_teacher.?
如果將兩條printf封裝成一個函數
void unsafe_function() {printf("%d %d", g_teacher.a, g_teacher.b);printf(" %d %d\n", g_teacher.c, g_teacher.d); }然后在onSigAlarm中調用,?則unsafe_function函數就成了不可重入函數(其實printf就是不可重入函數),?因此,?在信號響應函數中,?盡量不要調用不可重入函數;
?
不可重入函數
滿足下列條件的函數多數是不可重入的:
? (1)使用靜態數據結構,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;
? (2)函數實現時,調用了malloc()或者free()函數;
? (3)實現時使用了標準I/O函數
?
附-man?7?signal可以查看那些函數是可重入和不可重入的.
總結
以上是生活随笔為你收集整理的Linux信号实践(2) --信号分类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring依赖注入方式
- 下一篇: linux 其他常用命令