Unix訊號機制 下

2021-07-26 22:40:18 字數 4763 閱讀 4152

(一)時序競態

在訊號(上)裡面講解了訊號基礎的用法。但是考慮一下這樣的場景,比如我用alarm函式定時3秒,但是在定時完成後,cpu排程去執行其它的程序了,過了4秒,才回到之前執行到的地方,但是alarm定時的時間已經過了,那麼還沒等到執行下一條語句,訊號就先被處理了,可能導致程式的邏輯出現問題。這裡用乙個例子說明。

首先介紹乙個函式

函式原型:int pause(void)

返回值:只有執行了乙個訊號處理程式並從其返回時,pause才返回。並且只會返回-1,並設定errno為eintr。也就是說,沒有失敗的情況。

引數:無

標頭檔案:#include

這下我們就可以借助alarm()pause()完成乙個我們自己編寫的sleep函式了。

#include #include #include typedef void (*sighandler_t)(int);

void sig_catch(int signo) //處理函式

unsigned int mysleep(unsigned int seconds)

alarm(seconds); //定時

pause(); //掛起等待

signal(sigalrm, reback); //恢復signal原來的預設處理動作

return alarm(0);

}int main()

這段**,在乙個負載不是很嚴重的系統上執行一般不會出現問題。但是請注意之前說到的乙個現象,當執行到定時那一行**,準備執行pause()函式時,cpu排程到了另乙個程序,用了6s,那麼此時的定時期alarm定時的時間已經過了,訊號一傳送,就馬上被處理掉,這個時候回到本程序,pause()函式就再也等不到alarm()函式傳送的sigalrm訊號了,就會一直處於掛起狀態。

既然訊號是提前傳送並處理了,那們我們可以用遮蔽訊號的方法來阻止訊號被預先處理,意思就是在執行pause函式之前把sigalrm訊號遮蔽了,然後在即將執行pause函式時,再解除遮蔽。

#include #include #include #include void sig_catch(int signo)       //處理函式

int main()

sigset_t mask, old_mask; //old_mask用來記錄程序之前的訊號遮蔽字

sigemptyset(&mask); //清空masl

sigaddset(&mask, sigalrm); //將sigalrm訊號加入mask中

sigprocmask(sig_block, &mask, &old_mask); //設定遮蔽sigalrm

alarm(5); //定時開始

sigprocmask(sig_unblock, &mask, null); //解除遮蔽

pause();

sigaction(sigalrm, &old_act, null);

sigprocmask(sig_setmask, &old_mask, null); //恢復程序之前的遮蔽字

return 0;

}

仔細一看會發現乙個問題,在解除遮蔽sigalrm訊號完成後,然後cpu就排程到了其它程序,這樣的結果和之前一樣,都會導致pause()接收不到任何訊號,造成一直掛起。問題就出在解除遮蔽和掛起之間,只要能把這兩個操作合成乙個操作,那就不會導致有這樣的現象出現了。而unix中就有這樣的乙個函式。

函式原型:int sigsuspend(const sigset_t *mask)//掛起等待訊號

返回值:總是返回-1,如果正常返回,會設定errno為eintr

引數:mask是乙個訊號遮蔽字,裡面遮蔽了需要等待的訊號

#include #include #include #include void sig_catch(int signo)       //處理函式

int main()

/* 和上段**開始出現不同 */

sigset_t mask, old_mask, sus_mask; //sus_mask是乙個臨時的訊號遮蔽字,只在sigsuspend函式期間生效,並且該函式呼叫完之後,訊號遮蔽字恢復為呼叫之前的值

sigemptyset(&mask);

sigaddset(&mask, sigalrm);

sigprocmask(sig_block, &mask, &old_mask); //遮蔽sigalrm訊號

alarm(5);

sus_mask = old_mask; //將舊的訊號遮蔽字賦給sus_mask

sigdelset(&sus_mask, sigalrm); //為了防止之前的訊號遮蔽字已經遮蔽了sigalrm,所以保險起見,刪掉sigalrm

sigsuspend(&sus_mask); //傳入sus_mask,等待訊號

sigaction(sigalrm, &old_act, null); //恢復為預設的動作

sigprocmask(sig_setmask, &old_mask, null); //恢復為之前的訊號遮蔽字

return 0;

}

這樣就有效的避免了由於時序競態造成的問題,要切記使用sigsuspend函式時傳入的訊號遮蔽字只是臨時的,呼叫完畢後程序的訊號遮蔽字會自動恢復成呼叫之前的樣子。

(二).可/不可重入函式

在《unix環境高階程式設計》中,解釋的比較清楚。以下是原話

程序捕捉到訊號並對其進行處理時,程序正在執行的正常指令序列就被訊號處理程式臨時中斷,它首先執行該訊號處理程式中的指令。如果從訊號處理程式返回(例如沒有呼叫exit或longjmp),則繼續執行在捕捉到訊號時程序正在執行的正常指令序列(這類似於發生硬體中斷時所做的)。但在訊號處理程式中,不能判斷捕捉到訊號時程序執行到何處。如果程序正在執行malloc在其堆中分配另外的儲存空間,而此時由於捕捉到訊號而插入執行該訊號處理程式,其中又呼叫malloc,這時會發生什麼。。。。。。。。在malloc例子中,可能會對程序造成破壞,因為malloc通常為它所分配的儲存區維護乙個鍊錶,而插入執行訊號處理程式時,程序可能正在更改此鍊錶。

也就是說,可重入函式要求在執行該函式的過程中,即使被中斷了之後,再次回來繼續執行,也不會造成異常,這就叫可重入函式。書中還列出了大部分可重入函式,並提出了三點是不可重入函式的特徵:

1.使用了靜態資料結構

2.呼叫了malloc或free

3.是標準i/o函式(標準i/o函式的很多實現都以不可重入方式使用全域性資料結構)

所以我們需要避免捕捉到訊號呼叫的函式是不可重入函式。

(三).sigchld訊號

這個訊號會在1).子程序終止時。2).子程序接收到sigstop訊號停止時。3).子程序處在停止態,接收到sigcont後喚醒時。這幾種情況產生。

而在子程序結束時,它的父程序會收到sigchld訊號,而該訊號的預設處理動作就是忽略,所以我們可以利用這一點,對該訊號進行捕捉,然後完成子程序的**。

#include #include #include #include #include #include #include void sys_err(char *str)

void do_sig_child(int signo) //處理函式

}int main(void)

if(pid == 0) //子程序執行**

return i+1;

}else if(pid > 0) //父程序執行**

}return 0;

}

(四).訊號傳參

函式原型:int sigqueue(pid_t pid, int sig, const union sigval value)

返回值:成功返回0.失敗返回-1,並設定errno

引數:pid:代表要傳送給哪個程序,填程序號;sig:要傳送的訊號;value:攜帶的資料

union sigval
需要注意,不同的程序擁有不同的虛擬空間,所以傳位址是沒有意義的。

如果我們想要接收到傳遞的引數,那就應該使用sigaction函式中struct sigaction結構體中的另外乙個成員,也就是void (*sa_sigaction)(int, siginfo_t *, void *),並且此時的sa_flags的值應為sa_siginfo而不是預設屬性。

(五).中斷系統呼叫

系統呼叫分為兩類:慢速系統呼叫和其它系統呼叫

1.慢速系統呼叫:可能會使程序永久阻塞。例如wait read pause等

2.其它系統呼叫:比如fork getpid

慢速系統呼叫被中斷的行為就如同之前測試的pause一樣,必須接收到訊號,並且該訊號是**捉,而不是預設或者忽略。並且中斷後返回-1,設定errno為eintr。

linux下的訊號機制

訊號機制的詳細講解 訊號機制的生動講解 訊號的本質是在軟體層次上對中斷的模擬,所以又稱軟中斷。乙個程序收到乙個訊號就像處理器收到中斷請求一樣。訊號的作用,使程序可對外界做出 非實時 反應,也可向外界傳送自己的一些資訊。這樣可以通過訊號實現程序間的非同步通訊機制。訊號的實現原理 從cpu是否 正在執行...

驅動 訊號機制

一 實驗平台 開發板fs2410,採用三星s3c2410的cpu,linux作業系統。二 實現功能 主程式讓四個led燈形成流水燈,當按下開關k1時,熄滅所有燈,並推出程序。三 實驗原理 阻塞和poll機制都是應用程式進行查詢,應用程式是主動的,而裝置時是被動的。訊號機制可以讓裝置主動向應用程式發訊...

Linux 信 號 機 制

前面介紹了訊號的基本概念,在這一節中,我們將介紹核心如何實現訊號機制。即核心如何向乙個程序傳送訊號 程序如何接收乙個訊號 程序怎樣控制自己對訊號的反應 核心在什麼時機處理和怎樣處理程序收到的訊號。還要介紹一下setjmp和longjmp在訊號中起到的作用。1 核心對訊號的基本處理方法 核心給乙個程序...