Linux信号实践(2) --信号分类
信號(hào)分類(lèi)?
不可靠信號(hào)
Linux信號(hào)機(jī)制基本上是從UNIX系統(tǒng)中繼承過(guò)來(lái)的。早期UNIX系統(tǒng)中的信號(hào)機(jī)制比較簡(jiǎn)單和原始,后來(lái)在實(shí)踐中暴露出一些問(wèn)題,它的主要問(wèn)題是:
? ?1.進(jìn)程每次處理信號(hào)后,就將對(duì)信號(hào)的響應(yīng)設(shè)置為默認(rèn)動(dòng)作。在某些情況下,將導(dǎo)致對(duì)信號(hào)的錯(cuò)誤處理;因此,用戶(hù)如果不希望這樣的操作,那么就要在信號(hào)處理函數(shù)結(jié)尾再一次調(diào)用signal(),重新安裝該信號(hào)。
? ?2.因此導(dǎo)致,?早期UNIX下的不可靠信號(hào)主要指的是進(jìn)程可能對(duì)信號(hào)做出錯(cuò)誤的反應(yīng)以及信號(hào)可能丟失。?
Linux支持不可靠信號(hào),但是對(duì)不可靠信號(hào)機(jī)制做了改進(jìn):在調(diào)用完信號(hào)處理函數(shù)后,不必重新調(diào)用該信號(hào)的安裝函數(shù)(信號(hào)安裝函數(shù)是在可靠機(jī)制上的實(shí)現(xiàn))。因此,Linux下的不可靠信號(hào)問(wèn)題主要指的是信號(hào)可能丟失。
可靠信號(hào)
隨著時(shí)間的發(fā)展,實(shí)踐證明,有必要對(duì)信號(hào)的原始機(jī)制加以改進(jìn)和擴(kuò)充。所以,后來(lái)出現(xiàn)的各種UNIX版本分別在這方面進(jìn)行了研究,力圖實(shí)現(xiàn)"可靠信號(hào)"。由于原來(lái)定義的信號(hào)已有許多應(yīng)用,不好再做改動(dòng),最終只好又新增加了一些信號(hào)(SIGRTMIN?~?SIGRTMAX),并在一開(kāi)始就把它們定義為可靠信號(hào),這些信號(hào)支持排隊(duì),不會(huì)丟失。同時(shí),信號(hào)的發(fā)送和安裝也出現(xiàn)了新版本:信號(hào)發(fā)送函數(shù)sigqueue()及信號(hào)安裝函數(shù)sigaction()。
sigaction和signal函數(shù)都是調(diào)用內(nèi)核服務(wù)do_signal函數(shù);[內(nèi)核服務(wù)函數(shù),應(yīng)用程序無(wú)法調(diào)用該函數(shù)]
早期UNIX系統(tǒng)只定義了31種信號(hào),而Linux?3.x支持64種信號(hào),編號(hào)1-64(SIGRTMIN=34,SIGRTMAX=64),將來(lái)可能進(jìn)一步增加,這需要得到內(nèi)核的支持。 前31種信號(hào)已經(jīng)有了預(yù)定義值,每個(gè)信號(hào)有了確定的用途及含義,并且每種信號(hào)都有各自的缺省動(dòng)作。如按鍵盤(pán)的CTRL+C時(shí),會(huì)產(chǎn)生SIGINT信號(hào),對(duì)該信號(hào)的默認(rèn)反應(yīng)就是進(jìn)程終止。后32個(gè)信號(hào)表示實(shí)時(shí)信號(hào),等同于可靠信號(hào)。這保證了發(fā)送的多個(gè)實(shí)時(shí)信號(hào)都被接收。實(shí)時(shí)信號(hào)是POSIX標(biāo)準(zhǔn)的一部分,可用于應(yīng)用進(jìn)程。
非實(shí)時(shí)信號(hào)都不支持排隊(duì),都是不可靠信號(hào);實(shí)時(shí)信號(hào)都支持排隊(duì),都是可靠信號(hào)。
信號(hào)API-信號(hào)發(fā)送(1)
1.kill
int kill(pid_t pid, int signo);kill既可以向自身發(fā)送信號(hào),也可以向其他進(jìn)程發(fā)送信號(hào)?
signo參數(shù)組合情況解釋:
? ?pid>0?將信號(hào)sig發(fā)給pid進(jìn)程
? ?pid=0?將信號(hào)sig發(fā)給同組進(jìn)程
? ?pid=-1?將信號(hào)sig發(fā)送給所有進(jìn)程,調(diào)用者進(jìn)程有權(quán)限發(fā)送的每一個(gè)進(jìn)程(除了1號(hào)進(jìn)程之外,還有它自身)
? ?pid<-1?將信號(hào)sig發(fā)送給進(jìn)程組是pid(絕對(duì)值)的每一個(gè)進(jìn)程
//示例 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){/**向父進(jìn)程發(fā)送信號(hào)pid_t ppid = getppid();kill(ppid,SIGUSR1);*//**向同組所有進(jìn)程發(fā)送信號(hào),子進(jìn)程也會(huì)收到該信號(hào)kill(0,SIGUSR1);*///向本組所有進(jìn)程發(fā)送信號(hào),作用同上 //getpgrp()函數(shù)獲取進(jìn)程組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之前安裝信號(hào),則子進(jìn)程可以繼承信號(hào)。
?
Sleep遇上signal,子進(jìn)程向父進(jìn)程發(fā)送信號(hào),sleep函數(shù)的幾點(diǎn)說(shuō)明
? ?1)sleep函數(shù)作用,讓進(jìn)程睡眠。
? ?2)能被信號(hào)打斷,然后處理信號(hào)函數(shù)以后,就不再睡眠了。直接向下執(zhí)行代碼
? ?3)sleep函數(shù)的返回值,是剩余的秒數(shù)
Man手冊(cè)顯示:
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加強(qiáng) int main() { //...同上 cout << "Main Start Sleeping..." << endl; //sleep加強(qiáng)版^^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);給自己發(fā)送信號(hào)。raise(sig)等價(jià)于kill(getpid(),?sig);
3.killpg
int killpg(int pgrp, int sig);給進(jìn)程組發(fā)送信號(hào)。killpg(pgrp,?sig)等價(jià)于kill(-pgrp,?sig);
4.sigqueue
int sigqueue(pid_t pid, int sig, const union sigval value);給進(jìn)程發(fā)送信號(hào),支持排隊(duì),可以附帶信息。
信號(hào)API-pause
int pause(void);
將進(jìn)程置為可中斷睡眠狀態(tài)。然后它調(diào)用內(nèi)核函數(shù)schedule(),使Linux進(jìn)程調(diào)度器找到另一個(gè)進(jìn)程來(lái)運(yùn)行。
pause使調(diào)用者進(jìn)程掛起,直到一個(gè)信號(hào)被捕獲
//示例 int main() {if (signal(SIGINT,handler)== SIG_ERR)err_exit("signal error");while(true){pause();cout << "pause return..." << endl;} }信號(hào)API-信號(hào)發(fā)送(2)
unsigned int alarm(unsigned int seconds);alarm函數(shù),設(shè)置一個(gè)鬧鐘延遲發(fā)送SIGALRM信號(hào)(告訴Linux內(nèi)核n秒中以后,發(fā)送SIGALRM信號(hào));
手冊(cè)描述-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 遞歸調(diào)用 void onSignalAction(int signalNumber) {switch(signalNumber){case SIGALRM:cout << "SIGALRM = " << signalNumber << endl;alarm(1); //繼續(xù)調(diào)用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; }可重入/不可重入函數(shù)
? 所謂可重入函數(shù)是指一個(gè)可以被多個(gè)任務(wù)調(diào)用的過(guò)程,任務(wù)在調(diào)用時(shí)不必?fù)?dān)心數(shù)據(jù)是否會(huì)出錯(cuò)。因?yàn)檫M(jìn)程在收到信號(hào)后,就將跳轉(zhuǎn)到信號(hào)處理函數(shù)去接著執(zhí)行。如果信號(hào)處理函數(shù)中使用了不可重入函數(shù),那么信號(hào)處理函數(shù)可能會(huì)修改原來(lái)進(jìn)程中不應(yīng)該被修改的數(shù)據(jù),這樣進(jìn)程從信號(hào)處理函數(shù)中返回接著執(zhí)行時(shí),可能會(huì)出現(xiàn)不可預(yù)料的后果。不可重入函數(shù)在信號(hào)處理函數(shù)中被視為不安全函數(shù)。
? 為了增強(qiáng)程序的穩(wěn)定性,在信號(hào)處理函數(shù)中應(yīng)使用可重入函數(shù)。?
//不可重入函數(shù)示例 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;} }輸出結(jié)果演示:
?
原因分析:
可以將語(yǔ)句g_teacher?=?zero分解為:
????????g_teacher.a?=?zero.a;
????????g_teacher.b?=?zero.b;
????????g_teacher.c?=?zero.c;
????????g_teacher.d?=?zero.d;
因此,?在這四條語(yǔ)句執(zhí)行的中間,?如果此時(shí)SIGALRM信號(hào)到達(dá)(中斷到達(dá)),?則g_teacher中的一些數(shù)據(jù)會(huì)是新值,?而有些卻是以前留下的臟值,?究其原始則是g_teacher?=?zero不是原子操作,?而信號(hào)處理函數(shù)onSigAlarm卻又訪(fǎng)問(wèn)了全局變量g_teacher.?
如果將兩條printf封裝成一個(gè)函數(shù)
void unsafe_function() {printf("%d %d", g_teacher.a, g_teacher.b);printf(" %d %d\n", g_teacher.c, g_teacher.d); }然后在onSigAlarm中調(diào)用,?則unsafe_function函數(shù)就成了不可重入函數(shù)(其實(shí)printf就是不可重入函數(shù)),?因此,?在信號(hào)響應(yīng)函數(shù)中,?盡量不要調(diào)用不可重入函數(shù);
?
不可重入函數(shù)
滿(mǎn)足下列條件的函數(shù)多數(shù)是不可重入的:
? (1)使用靜態(tài)數(shù)據(jù)結(jié)構(gòu),如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;
? (2)函數(shù)實(shí)現(xiàn)時(shí),調(diào)用了malloc()或者free()函數(shù);
? (3)實(shí)現(xiàn)時(shí)使用了標(biāo)準(zhǔn)I/O函數(shù)
?
附-man?7?signal可以查看那些函數(shù)是可重入和不可重入的.
總結(jié)
以上是生活随笔為你收集整理的Linux信号实践(2) --信号分类的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Spring依赖注入方式
- 下一篇: 数据库开发基本操作-SQL Server