新聞中心
在 Linux C 程序開發(fā)的過程中,信號的處理是一個非常重要的話題。信號的正確處理能夠保證程序的正常運行,也能幫助開發(fā)者找出程序中的一些潛在問題。在信號處理中,信號屏蔽是一個非常重要的概念。它能夠保護程序在信號處理期間不被打斷,從而保證程序的穩(wěn)定運行。

大姚網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),大姚網(wǎng)站設(shè)計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為大姚超過千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)網(wǎng)站制作要多少錢,請找那個售后服務(wù)好的大姚做網(wǎng)站的公司定做!
一、信號的基本概念
在 Linux 系統(tǒng)中,信號是進程之間通信的一種手段。當(dāng)一個進程需要通知另一個進程某個事件已經(jīng)發(fā)生時,就可以通過發(fā)送信號的方式來通知。另外,信號也是操作系統(tǒng)向進程通知系統(tǒng)事件的一種方式。
當(dāng)一個進程收到一個信號時,它需要對該信號進行處理。通常,進程可以選擇忽略該信號,使用默認(rèn)方式處理該信號,或者將信號交給處理程序進行處理。處理程序需要在接收到信號時執(zhí)行一些指定的操作,例如關(guān)閉文件,釋放資源等。
在 Linux C 中,信號可以通過調(diào)用 signal() 系列函數(shù)來設(shè)置。例如,可以通過下面的代碼將 SIGINT 信號指向處理函數(shù):
“`
#include
void handler(int sig)
{
printf(“Received signal %d\n”, sig);
}
int mn()
{
signal(SIGINT, handler);
printf(“Press Ctrl+C to send SIGINT signal\n”);
while(1);
return 0;
}
“`
該程序設(shè)置了 SIGINT 的處理程序為 handler() 函數(shù)。當(dāng)程序運行時,如果用戶按下 Ctrl+C,就會發(fā)送一個 SIGINT 信號,程序?qū)?zhí)行 handler() 函數(shù)。
二、信號的屏蔽
在信號處理期間,有些信號可能會中斷當(dāng)前任務(wù)的執(zhí)行。為了確保程序的穩(wěn)定運行,我們需要將一些信號屏蔽掉,這將會使它們在信號處理期間不會打斷當(dāng)前任務(wù)的執(zhí)行。
在 Linux C 中,通過 sigprocmask() 函數(shù)來設(shè)置信號屏蔽。該函數(shù)的原型為:
“`
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
“`
該函數(shù)有三個參數(shù):
1. how:表示信號屏蔽方式,可以是以下三種取值:
– SIG_BLOCK:將 set 指向信號集中的信號添加到進程的信號屏蔽字中;
– SIG_UNBLOCK:將 set 指向信號集中的信號從進程的信號屏蔽字中刪除;
– SIG_SETMASK:用 set 指向信號集中的信號替換進程的信號屏蔽字;
2. set:指向要設(shè)置的信號;
3. oldset:用來保存之前屏蔽的信號的。
該函數(shù)的具體用法可以參考下面的例子:
“`
#include
int mn()
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigprocmask(SIG_BLOCK, &mask, NULL);
printf(“SIGINT signal is blocked.\n”);
while (1);
return 0;
}
“`
該程序?qū)⑵帘?SIGINT 信號。運行該程序后,按下 Ctrl+C 并不會觸發(fā) SIGINT 信號的處理,因為該信號已經(jīng)被屏蔽了。
三、信號的處理函數(shù)中的信號屏蔽
在信號處理函數(shù)中,除了需要對信號進行處理外,還需要考慮信號的屏蔽問題。一些信號,在信號處理期間可能會被再次觸發(fā)。如果在信號處理函數(shù)中沒有進行信號屏蔽,就可能會導(dǎo)致棧溢出和無限循環(huán)等問題。
解決該問題的方法是在信號處理函數(shù)的開頭對信號進行屏蔽操作,在函數(shù)結(jié)束時取消屏蔽。這個過程可以通過 sigaction() 函數(shù)中的 sa_mask 字段來實現(xiàn)。例如:
“`
#include
void handler(int sig)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, sig);
sigprocmask(SIG_BLOCK, &mask, NULL);
printf(“Received signal %d\n”, sig);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
}
int mn()
{
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGINT);
sa.sa_flags = 0;
sa.sa_handler = handler;
sigaction(SIGINT, &sa, NULL);
while(1);
return 0;
}
“`
該程序中,handler() 函數(shù)在開頭將 SIGINT 信號屏蔽,然后進行信號處理,最后再將 SIGINT 信號取消屏蔽。這樣就可以保證在信號處理中不會再次收到該信號。
四、
相關(guān)問題拓展閱讀:
- linux 內(nèi)核中斷 用什么鎖
- LINUX軟中斷通信
linux 內(nèi)核中斷 用什么鎖
首先我闡明一下,用鎖的情況只有兩種:
線程
文件
內(nèi)核程序在使用的時候也脫離不了這兩種鎖的概念。
中斷,是信號,是否要處理中斷信號喚茄?或者產(chǎn)生中斷信號仔核?
對信號來說只有:
信號屏蔽、信號捕捉、信號排隊、可重如函數(shù)等概念。
你想問的念鏈掘問題,我沒猜測,在處理某個信號時,不想讓其他信號中斷,那么使用信號屏蔽字:
先設(shè)置要屏蔽的信號集,要保存的信號集,初始信號集,可供協(xié)調(diào)使用的函數(shù)有幾個:
#include
signal(這個不建議使用,應(yīng)為有些老的實現(xiàn)是有問題的),設(shè)置信號處理程序
sig_atomic_t 數(shù)據(jù)類型
sigprocmask,設(shè)置信號屏蔽字
sigaction,設(shè)置信號處理程序,功能跟強悍,可控性更好
sigsuspend,以原子性方式,等待某些信號發(fā)生,然后返回
你具體要做啥不清楚,但使用上面的信號相關(guān)的函數(shù),肯定能實現(xiàn)你的功能。參考APUE的論述。
LINUX軟中斷通信
我驗證下阿…一不小心就fork多了..
剛開始我把kill的參數(shù)弄反了,信號和pid位置弄錯了,調(diào)了半個小時,很郁悶..
你只是忽略了一點…,我也給忽略了。。。后來才想起來
你按下ctrl+C的時候,另外兩個fork出來的進程,他們也會接到SIGINT。伍搭橡。。就退出了。。所以你要先在子進程里面忽略這個SIGINT信號,用枝或signal(SIGINT,SIG_IGN)就OK了….
程序如下…
有解釋,你可以自己看腔旁看…
#include”stdio.h”
#include”unistd.h”
#include”signal.h”
#include”sys/types.h”
#include”stdlib.h”
int k=0;
pid_t child1=0,child2=0;
void func_main(int sig);
void func_child1(int sig);
void func_child2(int sig);
int main()
{
while((child1=fork())==-1);
if(child1==0)
{
printf(“child1 OK\n”);
signal(SIGINT,SIG_IGN);
signal(SIGUSR1,func_child1);
sleep(60);
}
else if(child1 >0)
{
while((child2=fork())==-1);
if(child2==0)
{
printf(“child 2 OK\n”);
signal(SIGINT,SIG_IGN);//你按下ctrl+C,子進程也會接受到ctrl的信號…所以,子進程忽略
//所提子進程要忽略掉這個SIGINT信號
signal(SIGUSR2,func_child2);
sleep(60); //這里為了驗證,如果進程沒退出,40妙之后自動會退出的
//不然就得手動在終端里面kill掉這個進程了…
//有時候成了僵尸進程需要kill -9 才能殺死
}
else if(child2 >0)
{
signal(SIGINT,func_main);
printf(“children forked OK…\n”);
wait(0);
printf(“child return…\n”);
sleep(100);
return 0;
}
}
}
void func_main(int sig)
{
k++;
printf(“to send signal\n”);
//printf(“child1=%d,child2=%d\n”,child1,child2);
//if(k==1)
kill(child1,SIGUSR1);
//if(k==2)加上這句,再按一次ctrl C,子進程2才會退出
就是你想要的效果了
kill(child2,SIGUSR2);
signal(SIGINT,SIG_DFL); //這里恢復(fù)ctrl+C的效果
//子進程退出之后,我們再按一次ctrl+C,當(dāng)前的父進程就會像平常一樣,退出。
}
void func_child1(int sig)
{
printf(“child1 is killed by parent!\n”);
exit(0);
}
void func_child2(int sig)
{
printf(“child2 is killed by parent!\n”);
exit(0);
}
我也是初學(xué)者,這里抄一段《Linux設(shè)備驅(qū)動程序》書上的給你:
Linux的中斷宏觀分為兩種:軟中斷和硬中斷。聲明一橘啟數(shù)下,這里的軟和硬的意思是指和軟件相關(guān)以及和硬件相關(guān),而不是軟件實現(xiàn)的中斷或硬件實現(xiàn)的中斷。軟中斷就是“信號機制”。軟中斷不是軟件中斷。Linux通過信號來產(chǎn)生對進程的各種中斷操作,我們現(xiàn)在知道的信號共有31個,其具體內(nèi)容這里略過。
一般來說,軟中斷是由內(nèi)核機制的觸發(fā)事件引起的(例如進程運行超時),但是不可忽視有大量的軟中斷也是由于和硬件有關(guān)的中斷引起的,例如當(dāng)打印機端口產(chǎn)生一個硬件中斷時,會通知和硬件相關(guān)的硬中斷,硬中斷就會產(chǎn)生一個軟中斷并送到操作系統(tǒng)內(nèi)核里,這樣內(nèi)核就會根據(jù)這個軟中斷喚醒睡眠在打印機任務(wù)隊列中的處理進程。
硬中斷就是通常意義上的“中斷處理程序”,它是直接處理由硬件發(fā)過來的中斷信號的。當(dāng)硬中斷收到它應(yīng)當(dāng)處理的中斷信號以后,就回去自己驅(qū)動的設(shè)備上去看看設(shè)備的狀態(tài)寄存器以了解發(fā)生了什么事情,并進行相應(yīng)的操作。
對于軟中斷,我們不做討論,那是進程調(diào)度里要考慮的事情。由于我們討論的是設(shè)備驅(qū)動程序的中斷問題,所以焦點集中在硬中斷里。我們這里討論的是硬中斷,即和硬件相關(guān)的中斷。
要中斷,是因為外設(shè)需要通知操作系統(tǒng)她那里發(fā)生了一些事情,但是中斷的功能僅僅是一個設(shè)備報警燈,當(dāng)燈亮的時候中斷處理程序只知道有事情發(fā)生了,但發(fā)生了什么事情還要親自到設(shè)備那里去看才行。也就是說,當(dāng)中斷處理程序得知設(shè)備發(fā)生了一個中斷的時候,它并不知道設(shè)備發(fā)生了什么事情,只有當(dāng)它訪問了設(shè)備上的一些狀態(tài)寄存器以后,才能知道具體發(fā)生了什么,要怎么去處理。
設(shè)備通過中斷線向中斷控制器發(fā)送高電平告訴操作系統(tǒng)它產(chǎn)生了一個中斷,而操作系統(tǒng)會從中斷控制器的狀態(tài)位知道是哪條中斷線上產(chǎn)生了中斷。PC機上使用的中斷控制器是8259,這種控制器每一個可以管理8條中斷線,當(dāng)兩個8259級聯(lián)的時候共可以控制15條中斷線。這里的中斷線是實實在在的電路,他們通過硬件接口連接到CPU外的設(shè)備控制器上。
并不是每個設(shè)備都可以向中斷線上發(fā)中斷信號的,只有對某一條確定的中斷線勇有了控制權(quán),才可以向這條中斷線上發(fā)送信號。由于計算機的外部設(shè)備越來越多,所以15條中斷線已經(jīng)不夠用了,中斷線是非常寶貴的資源。要使用中斷線,就得進行中斷線的申請,就是IRQ(Interrupt Requirement),我們也常把申請一條中斷線成為申請一個IRQ或者是申請一個中圓首斷號。
IRQ是非常寶貴的,所以我們建議只有當(dāng)設(shè)備需要中斷的時候才申請占用一個IRQ,或者是在申請IRQ時采用共享中斷的方式,這樣可以讓更多的設(shè)備使用中斷。無論對IRQ的使用方式是獨占還是共享,申請IRQ的過程都是一樣的,分為3步:
1.將所有的中斷線探測一遍,看看哪些中斷還沒有被占用。從這些還沒有被占用的中斷中選一個作為該設(shè)備的IRQ。
2.通過中斷申請函數(shù)申請選定的IRQ,這是要指定申請的方式是獨占還是共享。
3.根據(jù)中斷申請函數(shù)的返回值決定怎么做:如果成功了萬事大吉,如果沒成功則或者重新申請或者放棄申請并返回錯誤。
Linux中的中斷處理程序很有特色,它的一個中斷處理程序分為兩個部分:上半部(top half)和下半部(bottom half)。之所以會有上半部和下半部之分,完全是考慮到中斷處理的效率。
上半部的功能是“登記中斷”。當(dāng)一個中斷發(fā)生時,他就把設(shè)備驅(qū)動程序中中斷例程的下半部掛到該設(shè)備的下半部執(zhí)行隊列中去,然后就沒事情了–等待新的中斷的到來。這樣旁薯一來,上半部執(zhí)行的速度就會很快,他就可以接受更多她負(fù)責(zé)的設(shè)備產(chǎn)生的中斷了。上半部之所以要快,是因為它是完全屏蔽中斷的,如果她不執(zhí)行完,其它的中斷就不能被及時的處理,只能等到這個中斷處理程序執(zhí)行完畢以后。所以,要盡可能多得對設(shè)備產(chǎn)生的中斷進行服務(wù)和處理,中斷處理程序就一定要快。
但是,有些中斷事件的處理是比較復(fù)雜的,所以中斷處理程序必須多花一點時間才能夠把事情做完??稍趺礃踊庠诙虝r間內(nèi)完成復(fù)雜處理的矛盾呢,這時候 Linux引入了下半部的概念。下半部和上半部更大的不同是下半部是可中斷的,而上半部是不可中斷的。下半部幾乎做了中斷處理程序所有的事情,因為上半部只是將下半部排到了他們所負(fù)責(zé)的設(shè)備的中斷處理隊列中去,然后就什么都不管了。下半部一般所負(fù)責(zé)的工作是察看設(shè)備以獲得產(chǎn)生中斷的事件信息,并根據(jù)這些信息(一般通過讀設(shè)備上的寄存器得來)進行相應(yīng)的處理。如果有些時間下半部不知道怎么去做,他就使用著名的鴕鳥算法來解決問題–說白了就是忽略這個事件。
由于下半部是可中斷的,所以在它運行期間,如果其它的設(shè)備產(chǎn)生了中斷,這個下半部可以暫時的中斷掉,等到那個設(shè)備的上半部運行完了,再回頭來運行它。但是有一點一定要注意,那就是如果一個設(shè)備中斷處理程序正在運行,無論她是運行上半部還是運行下半部,只要中斷處理程序還沒有處理完畢,在這期間設(shè)備產(chǎn)生的新的中斷都將被忽略掉。因為中斷處理程序是不可重入的,同一個中斷處理程序是不能并行的。
在Linux Kernel 2.0以前,中斷分為快中斷和慢中斷(偽中斷我們這里不談),其中快中斷的下半部也是不可中斷的,這樣可以保證它執(zhí)行的快一點。但是由于現(xiàn)在硬件水平不斷上升,快中斷和慢中斷的運行速度已經(jīng)沒有什么差別了,所以為了提高中斷例程事務(wù)處理的效率,從Linux kernel 2.0以后,中斷處理程序全部都是慢中斷的形式了–他們的下半部是可以被中斷的。
但是,在下半部中,你也可以進行中斷屏蔽–如果某一段代碼不能被中斷的話。你可以使用cti、sti或者是save_flag、restore_flag來實現(xiàn)你的想法。
在處理中斷的時候,中斷控制器會屏蔽掉原先發(fā)送中斷的那個設(shè)備,直到她發(fā)送的上一個中斷被處理完了為止。因此如果發(fā)送中斷的那個設(shè)備載中斷處理期間又發(fā)送了一個中斷,那么這個中斷就被永遠的丟失了。
之所以發(fā)生這種事情,是因為中斷控制器并不能緩沖中斷信息,所以當(dāng)前一個中斷沒有處理完以前又有新的中斷到達,他肯定會丟掉新的中斷的。但是這種缺陷可以通過設(shè)置主處理器(CPU)上的“置中斷標(biāo)志位”(sti)來解決,因為主處理器具有緩沖中斷的功能。如果使用了“置中斷標(biāo)志位”,那么在處理完中斷以后使用sti函數(shù)就可以使先前被屏蔽的中斷得到服務(wù)。
有時候需要屏蔽中斷,可是為什么要將這個中斷屏蔽掉呢?這并不是因為技術(shù)上實現(xiàn)不了同一中斷例程的并行,而是出于管理上的考慮。之所以在中斷處理的過程中要屏蔽同一IRQ來的新中斷,是因為中斷處理程序是不可重入的,所以不能并行執(zhí)行同一個中斷處理程序。在這里我們舉一個例子,從這里子例中可以看出如果一個中斷處理程序是可以并行的話,那么很有可能會發(fā)生驅(qū)動程序鎖死的情況。當(dāng)驅(qū)動程序鎖死的時候,你的操作系統(tǒng)并不一定會崩潰,但是鎖死的驅(qū)動程序所支持的那個設(shè)備是不能再使用了–設(shè)備驅(qū)動程序死了,設(shè)備也就死了。
A是一段代碼,B是操作設(shè)備寄存器R1的代碼,C是操作設(shè)備寄存器R2的代碼。其中激發(fā)PS1的事件會使A1產(chǎn)生一個中斷,然后B1去讀R1中已有的數(shù)據(jù),然后代碼C1向R2中寫數(shù)據(jù)。而激發(fā)PS2的事件會使A2產(chǎn)生一個中斷,然后B2刪除R1中的數(shù)據(jù),然后C2讀去R2中的數(shù)據(jù)。
如果PS1先產(chǎn)生,且當(dāng)他執(zhí)行到A1和B1之間的時候,如果PS2產(chǎn)生了,這是A2會產(chǎn)生一個中斷,將PS2中斷掉(掛到任務(wù)隊列的尾部),然后刪除了 R1的內(nèi)容。當(dāng)PS2運行到C2時,由于C1還沒有向R2中寫數(shù)據(jù),所以C2將會在這里被掛起,PS2就睡眠在代碼C2上,直到有數(shù)據(jù)可讀的時候被信號喚醒。這是由于PS1中的B2原先要讀的R1中的數(shù)據(jù)被PS2中的B2刪除了,所以PS1頁會睡眠在B1上,直到有數(shù)據(jù)可讀的時候被信號喚醒。這樣一來,喚醒PS1和PS2的事件就永遠不會發(fā)生了,因此PS1和PS2之間就鎖死了。
由于設(shè)備驅(qū)動程序要和設(shè)備的寄存器打交道,所以很難寫出可以重入的代碼來,因為設(shè)備寄存器就是全局變量。因此,最簡潔的辦法就是禁止同一設(shè)備的中斷處理程序并行,即設(shè)備的中斷處理程序是不可重入的。
有一點一定要清楚:在2.0版本以后的Linux kernel中,所有的上半部都是不可中斷的(上半部的操作是原子性的);不同設(shè)備的下半部可以互相中斷,但一個特定的下半部不能被它自己所中斷(即同一個下半部不能并)。
由于中斷處理程序要求不可重入,所以程序員也不必為編寫可重入的代碼而頭痛了。編寫可重入的設(shè)備驅(qū)動程序是可以的,編寫可重入的中斷處理程序是非常難得,幾乎不可能。
我們都知道,一旦競爭條件出現(xiàn)了,就有可能會發(fā)生死鎖的情況,嚴(yán)重時可能會將整個系統(tǒng)鎖死。所以一定要避免競爭條件的出現(xiàn)。只要注意一點:絕大多數(shù)由于中斷產(chǎn)生的競爭條件,都是在帶有中斷的
內(nèi)核進程被睡眠造成的。所以在實現(xiàn)中斷的時候,一定要相信謹(jǐn)慎的讓進程睡眠,必要的時候可以使用cli、sti或者save_flag、restore_flag。
linux c 信號屏蔽的介紹就聊到這里吧,感謝你花時間閱讀本站內(nèi)容,更多關(guān)于linux c 信號屏蔽,Linux C 下信號屏蔽:保障穩(wěn)定運行的重要手段,linux 內(nèi)核中斷 用什么鎖,LINUX軟中斷通信的信息別忘了在本站進行查找喔。
香港服務(wù)器選創(chuàng)新互聯(lián),2H2G首月10元開通。
創(chuàng)新互聯(lián)(www.cdcxhl.com)互聯(lián)網(wǎng)服務(wù)提供商,擁有超過10年的服務(wù)器租用、服務(wù)器托管、云服務(wù)器、虛擬主機、網(wǎng)站系統(tǒng)開發(fā)經(jīng)驗。專業(yè)提供云主機、虛擬主機、域名注冊、VPS主機、云服務(wù)器、香港云服務(wù)器、免備案服務(wù)器等。
當(dāng)前標(biāo)題:LinuxC下信號屏蔽:保障穩(wěn)定運行的重要手段(linuxc信號屏蔽)
分享URL:http://fisionsoft.com.cn/article/djicdjh.html


咨詢
建站咨詢
