博客專欄

        EEPW首頁 > 博客 > 嵌入式Linux:進程如何處理信號

        嵌入式Linux:進程如何處理信號

        發布人:美男子玩編程 時間:2024-09-27 來源:工程師 發布文章

        在Linux系統中,當進程接收到信號后,可以通過設置信號處理方式來決定如何響應信號。


        通常,信號的處理方式可以是以下三種之一:

        • 忽略信號進程對該信號不做任何處理,直接忽略。

        • 捕獲信號為該信號設置一個處理函數,當信號到達時執行該函數。

        • 執行系統默認操作采用系統預定義的信號處理方式。


        本篇文章主要講解進程如何處理信號。Linux 系統提供了兩個主要的函數 signal() 和 sigaction() 用于設置信號的處理方式。


        1


        signal()函數

        signal()函數的原型如下:


        #include <signal.h> typedef void (*sig_t)(int); sig_t signal(int signum, sig_t handler);


        函數參數和含義:

        • signum指定需要進行設置的信號。你可以使用信號的名稱(如SIGINT)或者其對應的數字編號。不過,建議使用信號名稱,因為這樣可讀性更強。

        • handler這是一個sig_t類型的函數指針,用于指向信號的處理函數。

        • handler可以設置為以下幾種:

          • 用戶自定義函數這是一個處理函數,在接收到信號時會自動調用這個函數。該函數的參數是一個int類型的值,表示觸發該函數的信號編號。通過這個參數,你可以在一個函數中處理多個信號。

          • SIG_IGN表示忽略該信號,進程在接收到該信號時不會進行任何處理。

          • SIG_DFL表示采用系統的默認處理方式,系統會對信號進行其預定義的操作。


        返回值:signal()函數的返回值是一個sig_t類型的函數指針。成功調用時,返回指向之前信號處理函數的指針,這意味著你可以保存這個指針,以便在將來恢復原來的信號處理方式。如果調用失敗,則返回SIG_ERR,并設置errno以指示錯誤原因。


        以下是一個簡單的示例代碼,展示如何使用signal()函數來捕獲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);     // 無限循環,等待信號    while(1) {        printf("Running...n");        sleep(1);    }     return 0;}


        在上述代碼中,當用戶按下CTRL+C(觸發SIGINT信號)時,自定義的handle_signal()函數會被調用,并輸出捕獲的信號編號。程序會繼續運行,而不會終止。如果要忽略SIGINT信號,可以將signal(SIGINT, handle_signal);改為signal(SIGINT, SIG_IGN);。


        2


        sigaction() 函數

        sigaction() 函數是 Linux 系統中用于設置信號處理方式的一個更強大且靈活的系統調用。與 signal() 函數相比,sigaction() 提供了更詳細的控制和更高的移植性,因此更推薦在實際開發中使用它。


        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 結構體的指針,用于存儲信號先前的處理方式。如果不需要獲取原來的處理方式,可將其設置為 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另一個信號處理函數指針,用于處理帶有更多信息的信號。與 sa_handler 互斥,通常使用 sa_handler。選擇使用 sa_sigaction 需設置 SA_SIGINFO 標志。

        • sa_mask定義在執行信號處理函數期間要阻塞的信號集合,以避免信號之間的競爭條件。

        • sa_flags標志位,用于控制信號的處理行為。常用標志包括:

          • SA_NOCLDSTOP:阻止當子進程停止或恢復時發送 SIGCHLD 信號。

          • SA_NOCLDWAIT:子進程終止時不變為僵尸進程。

          • SA_NODEFER:不阻塞自身的信號。

          • SA_RESETHAND:執行完信號處理后將信號恢復為默認處理方式。

          • SA_RESTART:被信號中斷的系統調用在信號處理完成后重新發起。

          • SA_SIGINFO:使用 sa_sigaction 代替 sa_handler。

        • sa_restorer已過時,通常不使用。


        siginfo_t 結構體用于在 sa_sigaction 處理信號時傳遞更多的上下文信息,結構體定義如下:


        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;


        下面是一個使用 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);     // 無限循環,等待信號    while(1) {        printf("Running...n");        sleep(1);    }     return 0;}


        在這段代碼中,sigaction() 用來設置 SIGINT 信號的處理方式。當用戶按下 CTRL+C 發送 SIGINT 信號時,程序會調用 handle_signal() 函數,該函數可以通過 siginfo_t 結構體獲取信號的更多信息,比如發送信號的進程 ID。


        3


        注意事項

        當一個應用程序剛啟動時,或在程序中未調用 signal() 或 sigaction() 來顯式設置信號處理方式時,進程對所有信號的處理方式通常為系統默認操作。這意味著大多數信號在未被特殊處理的情況下,都會執行默認的處理動作。


        當一個進程使用 fork() 系統調用創建一個子進程時,子進程會繼承父進程的信號處理方式。由于子進程是通過復制父進程的內存映像而創建的,所以信號捕獲函數的地址在子進程中同樣有效。這意味著子進程將會繼承父進程的信號處理函數和其他相關的信號處理狀態。


        這種繼承機制確保了子進程在初始狀態下能夠正確處理信號,避免因為未定義的信號處理而導致不可預測的行為。如果需要,子進程可以在運行過程中修改其信號處理方式,從而實現特定的行為需求。


        在設計信號處理函數時,通常建議保持其簡單性。這與設計中斷處理函數的原則相似:處理函數應盡可能簡短和高效,避免執行大量耗費 CPU 時間的操作。

        主要原因如下:


        • 減少信號競爭條件信號競爭條件(Race Condition)指的是在多線程或多進程環境中,不同信號可能在不合適的時間內打斷正在處理的代碼,導致不可預測的結果。如果信號處理函數復雜且耗時較長,進程在執行處理函數時,可能會接收到相同或其他信號,增加競爭條件發生的風險。

        • 保證系統響應性信號處理函數應快速完成,以確保系統能夠及時響應其他事件或信號。如果處理函數占用了大量的 CPU 時間,系統響應速度可能會受到影響,尤其是在實時性要求較高的系統中。

        • 減少對系統狀態的影響復雜的信號處理函數可能會改變進程的全局狀態(如修改全局變量),這可能會導致進程在信號處理完成后進入不一致的狀態。因此,簡單的處理函數可以減少這些副作用。


        最佳實踐:

        • 在信號處理函數中,只執行必要的操作,如設置一個標志或記錄一個簡單的狀態。

        • 如果需要執行復雜的邏輯,可以在信號處理函數中設置一個標志,然后在主程序的主循環中檢查該標志,并執行相應的復雜邏輯。

          這種方式可以有效分離信號處理與復雜邏輯,降低風險。


        通過保持信號處理函數的簡單性,你可以有效提高程序的穩定性和可靠性,減少潛在的問題和復雜的調試過程。

        *博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。



        關鍵詞: 嵌入式 Linux

        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 鲁山县| 巴里| 桑植县| 咸宁市| 舞阳县| 金乡县| 通城县| 九龙城区| 梧州市| 聂荣县| 宝清县| 平邑县| 南宁市| 新兴县| 华亭县| 迭部县| 灌南县| 讷河市| 张家口市| 朝阳市| 余庆县| 大姚县| 恩平市| 蕲春县| 汝州市| 岱山县| 育儿| 汉阴县| 涟源市| 正定县| 常宁市| 平乡县| 湘阴县| 马公市| 佳木斯市| 吉隆县| 昌江| 海安县| 安义县| 凯里市| 无为县|