嵌入式Linux:進(jìn)程如何處理信號
在Linux系統中,當進(jìn)程接收到信號后,可以通過(guò)設置信號處理方式來(lái)決定如何響應信號。
通常,信號的處理方式可以是以下三種之一:
忽略信號:進(jìn)程對該信號不做任何處理,直接忽略。
捕獲信號:為該信號設置一個(gè)處理函數,當信號到達時(shí)執行該函數。
執行系統默認操作:采用系統預定義的信號處理方式。
本篇文章主要講解進(jìn)程如何處理信號。Linux 系統提供了兩個(gè)主要的函數 signal() 和 sigaction() 用于設置信號的處理方式。
1
signal()函數
signal()函數的原型如下:
#include <signal.h> typedef void (*sig_t)(int); sig_t signal(int signum, sig_t handler);
函數參數和含義:
signum:指定需要進(jìn)行設置的信號。你可以使用信號的名稱(chēng)(如SIGINT)或者其對應的數字編號。不過(guò),建議使用信號名稱(chēng),因為這樣可讀性更強。
handler:這是一個(gè)sig_t類(lèi)型的函數指針,用于指向信號的處理函數。
handler可以設置為以下幾種:
用戶(hù)自定義函數:這是一個(gè)處理函數,在接收到信號時(shí)會(huì )自動(dòng)調用這個(gè)函數。該函數的參數是一個(gè)int類(lèi)型的值,表示觸發(fā)該函數的信號編號。通過(guò)這個(gè)參數,你可以在一個(gè)函數中處理多個(gè)信號。
SIG_IGN:表示忽略該信號,進(jìn)程在接收到該信號時(shí)不會(huì )進(jìn)行任何處理。
SIG_DFL:表示采用系統的默認處理方式,系統會(huì )對信號進(jìn)行其預定義的操作。
返回值:signal()函數的返回值是一個(gè)sig_t類(lèi)型的函數指針。成功調用時(shí),返回指向之前信號處理函數的指針,這意味著(zhù)你可以保存這個(gè)指針,以便在將來(lái)恢復原來(lái)的信號處理方式。如果調用失敗,則返回SIG_ERR,并設置errno以指示錯誤原因。
以下是一個(gè)簡(jiǎn)單的示例代碼,展示如何使用signal()函數來(lái)捕獲SIGINT信號,并執行自定義的信號處理函數:
#include <stdio.h>#include <signal.h>#include <unistd.h> // 自定義信號處理函數void handle_signal(int signal) { printf("Caught signal %dn", signal);} int main() { // 將 SIGINT 信號處理方式設置為自定義的 handle_signal 函數 signal(SIGINT, handle_signal); // 無(wú)限循環(huán),等待信號 while(1) { printf("Running...n"); sleep(1); } return 0;}
在上述代碼中,當用戶(hù)按下CTRL+C(觸發(fā)SIGINT信號)時(shí),自定義的handle_signal()函數會(huì )被調用,并輸出捕獲的信號編號。程序會(huì )繼續運行,而不會(huì )終止。如果要忽略SIGINT信號,可以將signal(SIGINT, handle_signal);改為signal(SIGINT, SIG_IGN);。
2
sigaction() 函數
sigaction() 函數是 Linux 系統中用于設置信號處理方式的一個(gè)更強大且靈活的系統調用。與 signal() 函數相比,sigaction() 提供了更詳細的控制和更高的移植性,因此更推薦在實(shí)際開(kāi)發(fā)中使用它。
sigaction() 函數原型如下:
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
函數參數:
signum:指定要設置處理方式的信號編號。可以為除 SIGKILL 和 SIGSTOP 以外的任何信號。
act:指向 struct sigaction 結構體的指針,用于指定信號的新的處理方式。如果 act 為 NULL,則不改變信號的處理方式。
oldact:指向 struct sigaction 結構體的指針,用于存儲信號先前的處理方式。如果不需要獲取原來(lái)的處理方式,可將其設置為 NULL。
返回值:成功返回 0;失敗返回 -1,并設置 errno。
struct sigaction 結構體用于描述信號的處理方式,定義如下:
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void);};
成員變量如下:
sa_handler:信號處理函數指針,與 signal() 函數中的 handler 參數相同。可設置為自定義函數、SIG_IGN(忽略信號)或 SIG_DFL(系統默認處理)。
sa_sigaction:另一個(gè)信號處理函數指針,用于處理帶有更多信息的信號。與 sa_handler 互斥,通常使用 sa_handler。選擇使用 sa_sigaction 需設置 SA_SIGINFO 標志。
sa_mask:定義在執行信號處理函數期間要阻塞的信號集合,以避免信號之間的競爭條件。
sa_flags:標志位,用于控制信號的處理行為。常用標志包括:
SA_NOCLDSTOP:阻止當子進(jìn)程停止或恢復時(shí)發(fā)送 SIGCHLD 信號。
SA_NOCLDWAIT:子進(jìn)程終止時(shí)不變?yōu)榻┦M(jìn)程。
SA_NODEFER:不阻塞自身的信號。
SA_RESETHAND:執行完信號處理后將信號恢復為默認處理方式。
SA_RESTART:被信號中斷的系統調用在信號處理完成后重新發(fā)起。
SA_SIGINFO:使用 sa_sigaction 代替 sa_handler。
sa_restorer:已過(guò)時(shí),通常不使用。
siginfo_t 結構體用于在 sa_sigaction 處理信號時(shí)傳遞更多的上下文信息,結構體定義如下:
typedef struct siginfo { int si_signo; /* Signal number */ int si_errno; /* An errno value */ int si_code; /* Signal code */ pid_t si_pid; /* Sending process ID */ uid_t si_uid; /* Real user ID of sending process */ void *si_addr; /* Memory location which caused fault */ int si_status; /* Exit value or signal */ int si_band; /* Band event */ // ... 其他成員} siginfo_t;
下面是一個(gè)使用 sigaction() 捕獲 SIGINT 信號的示例代碼:
#include <stdio.h>#include <signal.h>#include <unistd.h> void handle_signal(int signal, siginfo_t *info, void *ucontext) { printf("Caught signal %dn", signal); printf("Signal sent by process %dn", info->si_pid);} int main() { struct sigaction act; act.sa_sigaction = handle_signal; act.sa_flags = SA_SIGINFO; // 使用 sa_sigaction 而不是 sa_handler sigemptyset(&act.sa_mask); sigaction(SIGINT, &act, NULL); // 無(wú)限循環(huán),等待信號 while(1) { printf("Running...n"); sleep(1); } return 0;}
在這段代碼中,sigaction() 用來(lái)設置 SIGINT 信號的處理方式。當用戶(hù)按下 CTRL+C 發(fā)送 SIGINT 信號時(shí),程序會(huì )調用 handle_signal() 函數,該函數可以通過(guò) siginfo_t 結構體獲取信號的更多信息,比如發(fā)送信號的進(jìn)程 ID。
3
注意事項
當一個(gè)應用程序剛啟動(dòng)時(shí),或在程序中未調用 signal() 或 sigaction() 來(lái)顯式設置信號處理方式時(shí),進(jìn)程對所有信號的處理方式通常為系統默認操作。這意味著(zhù)大多數信號在未被特殊處理的情況下,都會(huì )執行默認的處理動(dòng)作。
當一個(gè)進(jìn)程使用 fork() 系統調用創(chuàng )建一個(gè)子進(jìn)程時(shí),子進(jìn)程會(huì )繼承父進(jìn)程的信號處理方式。由于子進(jìn)程是通過(guò)復制父進(jìn)程的內存映像而創(chuàng )建的,所以信號捕獲函數的地址在子進(jìn)程中同樣有效。這意味著(zhù)子進(jìn)程將會(huì )繼承父進(jìn)程的信號處理函數和其他相關(guān)的信號處理狀態(tài)。
這種繼承機制確保了子進(jìn)程在初始狀態(tài)下能夠正確處理信號,避免因為未定義的信號處理而導致不可預測的行為。如果需要,子進(jìn)程可以在運行過(guò)程中修改其信號處理方式,從而實(shí)現特定的行為需求。
在設計信號處理函數時(shí),通常建議保持其簡(jiǎn)單性。這與設計中斷處理函數的原則相似:處理函數應盡可能簡(jiǎn)短和高效,避免執行大量耗費 CPU 時(shí)間的操作。
主要原因如下:
減少信號競爭條件:信號競爭條件(Race Condition)指的是在多線(xiàn)程或多進(jìn)程環(huán)境中,不同信號可能在不合適的時(shí)間內打斷正在處理的代碼,導致不可預測的結果。如果信號處理函數復雜且耗時(shí)較長(cháng),進(jìn)程在執行處理函數時(shí),可能會(huì )接收到相同或其他信號,增加競爭條件發(fā)生的風(fēng)險。
保證系統響應性:信號處理函數應快速完成,以確保系統能夠及時(shí)響應其他事件或信號。如果處理函數占用了大量的 CPU 時(shí)間,系統響應速度可能會(huì )受到影響,尤其是在實(shí)時(shí)性要求較高的系統中。
減少對系統狀態(tài)的影響:復雜的信號處理函數可能會(huì )改變進(jìn)程的全局狀態(tài)(如修改全局變量),這可能會(huì )導致進(jìn)程在信號處理完成后進(jìn)入不一致的狀態(tài)。因此,簡(jiǎn)單的處理函數可以減少這些副作用。
最佳實(shí)踐:
在信號處理函數中,只執行必要的操作,如設置一個(gè)標志或記錄一個(gè)簡(jiǎn)單的狀態(tài)。
如果需要執行復雜的邏輯,可以在信號處理函數中設置一個(gè)標志,然后在主程序的主循環(huán)中檢查該標志,并執行相應的復雜邏輯。
這種方式可以有效分離信號處理與復雜邏輯,降低風(fēng)險。
通過(guò)保持信號處理函數的簡(jiǎn)單性,你可以有效提高程序的穩定性和可靠性,減少潛在的問(wèn)題和復雜的調試過(guò)程。
*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。