電子產業(yè)一站式賦能平臺

PCB聯盟網

搜索
查看: 71|回復: 0
收起左側

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

[復制鏈接]

532

主題

532

帖子

3120

積分

四級會員

Rank: 4

積分
3120
跳轉到指定樓層
樓主
發(fā)表于 2024-8-30 12:02:00 | 只看該作者 |只看大圖 回帖獎勵 |正序瀏覽 |閱讀模式

9 J7 ?/ v9 Q9 t3 }4 r' W點擊上方藍色字體,關注我們, I# c) ^' ]- @) V! k0 O! u+ C
在Linux系統(tǒng)中,當進程接收到信號后,可以通過設置信號處理方式來決定如何響應信號。
2 {5 Y$ ]# P& g6 \
$ b# E9 l; e) ]1 ]. z" D通常,信號的處理方式可以是以下三種之一:
) d8 |) ~8 a0 ]6 s
  • 忽略信號:進程對該信號不做任何處理,直接忽略。
  • 捕獲信號:為該信號設置一個處理函數,當信號到達時執(zhí)行該函數。
  • 執(zhí)行系統(tǒng)默認操作:采用系統(tǒng)預定義的信號處理方式。
    9 r' Q% o+ K0 P

    * O( x- H: ^7 a8 B: h/ q本篇文章主要講解進程如何處理信號。Linux 系統(tǒng)提供了兩個主要的函數 signal() 和 sigaction() 用于設置信號的處理方式。, H. G; T/ u* V6 v0 k' Q# S
    1" `* h6 R' V, R: J
    signal()函數
    & V2 ~) S& |. Ksignal()函數的原型如下:
    0 l( H; X# d" P
      q/ P' I2 K3 e" D1 j
  • #include  typedef void (*sig_t)(int); sig_t signal(int signum, sig_t handler);/ i$ L8 n6 Z+ x+ J
    函數參數和含義:5 Y3 c6 T9 F& y  ]  _8 [: _
  • signum:指定需要進行設置的信號。你可以使用信號的名稱(如SIGINT)或者其對應的數字編號。不過,建議使用信號名稱,因為這樣可讀性更強。
  • handler:這是一個sig_t類型的函數指針,用于指向信號的處理函數。
  • handler可以設置為以下幾種:, z7 b% ^2 D2 a
  • 用戶自定義函數:這是一個處理函數,在接收到信號時會自動調用這個函數。該函數的參數是一個int類型的值,表示觸發(fā)該函數的信號編號。通過這個參數,你可以在一個函數中處理多個信號。
  • SIG_IGN:表示忽略該信號,進程在接收到該信號時不會進行任何處理。
  • SIG_DFL:表示采用系統(tǒng)的默認處理方式,系統(tǒng)會對信號進行其預定義的操作。2 }6 t& F! K$ r/ V# @

    ; ^3 j6 O; X6 i/ C( U. b( x) }返回值:signal()函數的返回值是一個sig_t類型的函數指針。成功調用時,返回指向之前信號處理函數的指針,這意味著你可以保存這個指針,以便在將來恢復原來的信號處理方式。如果調用失敗,則返回SIG_ERR,并設置errno以指示錯誤原因。3 c) j7 |- _4 U

    % ^( n% S) O" Z5 S; I以下是一個簡單的示例代碼,展示如何使用signal()函數來捕獲SIGINT信號,并執(zhí)行自定義的信號處理函數:# T$ y0 I. A/ Q

    6 D! b1 v6 A# Z. r$ Q/ ?
  • #include #include #include // 自定義信號處理函數void handle_signal(int signal) {    printf("Caught signal %d0 k. R: D. f: L+ z+ y
    ", signal);} int main() {    // 將 SIGINT 信號處理方式設置為自定義的 handle_signal 函數    signal(SIGINT, handle_signal);     // 無限循環(huán),等待信號    while(1) {        printf("Running...
    6 v* b7 z: Q; D! K$ T( p6 c");        sleep(1);    }     return 0;}- z. q& M- u% s, \0 v( D& A
    在上述代碼中,當用戶按下CTRL+C(觸發(fā)SIGINT信號)時,自定義的handle_signal()函數會被調用,并輸出捕獲的信號編號。程序會繼續(xù)運行,而不會終止。如果要忽略SIGINT信號,可以將signal(SIGINT, handle_signal);改為signal(SIGINT, SIG_IGN);。
    & I% ?+ v0 A! x" i  b) A' L2
    ' Y( k" H  I. G8 V  ]: Asigaction() 函數
    5 a. Z- U( S" a* J7 Xsigaction() 函數是 Linux 系統(tǒng)中用于設置信號處理方式的一個更強大且靈活的系統(tǒng)調用。與 signal() 函數相比,sigaction() 提供了更詳細的控制和更高的移植性,因此更推薦在實際開發(fā)中使用它。
    ( i4 f6 ?* L- j' c$ y& O
    ( A1 g7 X9 S' E- l) }6 r7 q1 Csigaction() 函數原型如下:( c5 d" |. g+ d8 Z% N

    * V" w2 y8 ~# W( @, ?  t
  • #include  int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    8 S# s4 i0 G' ^% B函數參數:4 q. H; Y4 G0 z! H2 W' ?
  • signum:指定要設置處理方式的信號編號。可以為除 SIGKILL 和 SIGSTOP 以外的任何信號。
  • act:指向 struct sigaction 結構體的指針,用于指定信號的新的處理方式。如果 act 為 NULL,則不改變信號的處理方式。
  • oldact:指向 struct sigaction 結構體的指針,用于存儲信號先前的處理方式。如果不需要獲取原來的處理方式,可將其設置為 NULL。
    * G& y1 H; a* E

    7 I, x% F4 L% F/ r! |& j返回值:成功返回 0;失敗返回 -1,并設置 errno。
    5 u( h2 q% G! K" ?1 l, g$ }8 n
    & d7 M& {- Z. \7 j5 m# r  Kstruct sigaction 結構體用于描述信號的處理方式,定義如下:$ S( t# N) A5 B7 N  _  J

    7 Z, D/ X2 T$ a) @3 n
  • struct sigaction {    void (*sa_handler)(int);    void (*sa_sigaction)(int, siginfo_t *, void *);    sigset_t sa_mask;    int sa_flags;    void (*sa_restorer)(void);};4 R" r" ]- y5 h" q. S; W
    成員變量如下:: T  k) L  A3 g
  • sa_handler:信號處理函數指針,與 signal() 函數中的 handler 參數相同?稍O置為自定義函數、SIG_IGN(忽略信號)或 SIG_DFL(系統(tǒng)默認處理)。
  • sa_sigaction:另一個信號處理函數指針,用于處理帶有更多信息的信號。與 sa_handler 互斥,通常使用 sa_handler。選擇使用 sa_sigaction 需設置 SA_SIGINFO 標志。
  • sa_mask:定義在執(zhí)行信號處理函數期間要阻塞的信號集合,以避免信號之間的競爭條件。
  • sa_flags:標志位,用于控制信號的處理行為。常用標志包括:
      H4 r7 Y, h; c+ M: R
  • SA_NOCLDSTOP:阻止當子進程停止或恢復時發(fā)送 SIGCHLD 信號。
  • SA_NOCLDWAIT:子進程終止時不變?yōu)榻┦M程。
  • SA_NODEFER:不阻塞自身的信號。
  • SA_RESETHAND:執(zhí)行完信號處理后將信號恢復為默認處理方式。
  • SA_RESTART:被信號中斷的系統(tǒng)調用在信號處理完成后重新發(fā)起。
  • SA_SIGINFO:使用 sa_sigaction 代替 sa_handler。/ G7 r" C: ?+ o% f. c
  • sa_restorer:已過時,通常不使用。
    7 F  A. T& ^$ B& q7 t2 Q# v

    8 Y: _" T7 ~9 I9 S- Qsiginfo_t 結構體用于在 sa_sigaction 處理信號時傳遞更多的上下文信息,結構體定義如下:" b& P/ n9 t$ c. _/ _$ t
    & Z4 |- c! a& Q/ \- ?" m! q
  • 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;
    " q6 a+ D, v( M1 |0 {! z下面是一個使用 sigaction() 捕獲 SIGINT 信號的示例代碼:- g" y4 {7 |  B# Z1 W
    ( @) H. ~- L% g3 |
  • #include #include #include void handle_signal(int signal, siginfo_t *info, void *ucontext) {    printf("Caught signal %d9 p/ t7 P% P, u
    ", signal);    printf("Signal sent by process %d( J5 `5 Z2 z* A1 G0 F
    ", 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);     // 無限循環(huán),等待信號    while(1) {        printf("Running...6 Z& F' q6 N2 V1 C  x, O
    ");        sleep(1);    }     return 0;}
    " p- _- d- _7 [: `在這段代碼中,sigaction() 用來設置 SIGINT 信號的處理方式。當用戶按下 CTRL+C 發(fā)送 SIGINT 信號時,程序會調用 handle_signal() 函數,該函數可以通過 siginfo_t 結構體獲取信號的更多信息,比如發(fā)送信號的進程 ID。
    " f3 n: O8 I: R, w% \35 J1 s) f7 I5 e4 {
    注意事項3 V4 S; c: g: C' h& E6 x4 ^
    當一個應用程序剛啟動時,或在程序中未調用 signal() 或 sigaction() 來顯式設置信號處理方式時,進程對所有信號的處理方式通常為系統(tǒng)默認操作。這意味著大多數信號在未被特殊處理的情況下,都會執(zhí)行默認的處理動作。
    ( q  P$ n( Q7 K, I2 C$ E4 t1 E5 j: `$ r. K+ R, f+ U
    當一個進程使用 fork() 系統(tǒng)調用創(chuàng)建一個子進程時,子進程會繼承父進程的信號處理方式。由于子進程是通過復制父進程的內存映像而創(chuàng)建的,所以信號捕獲函數的地址在子進程中同樣有效。這意味著子進程將會繼承父進程的信號處理函數和其他相關的信號處理狀態(tài)。  [" Y9 m# V9 j$ t6 |$ H& ?" \2 Y

    7 L% ?3 S& ?8 P6 N! U, j這種繼承機制確保了子進程在初始狀態(tài)下能夠正確處理信號,避免因為未定義的信號處理而導致不可預測的行為。如果需要,子進程可以在運行過程中修改其信號處理方式,從而實現特定的行為需求。
    " f; f" Y& D- U5 l- x( R; `: [6 `7 p8 R. ?4 x
    在設計信號處理函數時,通常建議保持其簡單性。這與設計中斷處理函數的原則相似:處理函數應盡可能簡短和高效,避免執(zhí)行大量耗費 CPU 時間的操作。) o3 t( E, _& n% ]
    主要原因如下:& X# p6 K6 u) X$ Q( A

    8 C8 q: e6 R9 o* t0 C1 v
  • 減少信號競爭條件:信號競爭條件(Race Condition)指的是在多線程或多進程環(huán)境中,不同信號可能在不合適的時間內打斷正在處理的代碼,導致不可預測的結果。如果信號處理函數復雜且耗時較長,進程在執(zhí)行處理函數時,可能會接收到相同或其他信號,增加競爭條件發(fā)生的風險。
  • 保證系統(tǒng)響應性:信號處理函數應快速完成,以確保系統(tǒng)能夠及時響應其他事件或信號。如果處理函數占用了大量的 CPU 時間,系統(tǒng)響應速度可能會受到影響,尤其是在實時性要求較高的系統(tǒng)中。
  • 減少對系統(tǒng)狀態(tài)的影響:復雜的信號處理函數可能會改變進程的全局狀態(tài)(如修改全局變量),這可能會導致進程在信號處理完成后進入不一致的狀態(tài)。因此,簡單的處理函數可以減少這些副作用。+ I( u( [% ]5 l2 V2 T. k$ }

    8 v( |& o7 ^" w  z) H  q最佳實踐:
    $ K8 h/ y) |, x6 q" E$ b- g* b
  • 在信號處理函數中,只執(zhí)行必要的操作,如設置一個標志或記錄一個簡單的狀態(tài)。
  • 如果需要執(zhí)行復雜的邏輯,可以在信號處理函數中設置一個標志,然后在主程序的主循環(huán)中檢查該標志,并執(zhí)行相應的復雜邏輯。5 X9 H2 D1 D  W8 p( ?4 l7 F
    這種方式可以有效分離信號處理與復雜邏輯,降低風險。' O' i. U, U% O# b) H# h! e& J

    + b" f2 b7 {( d通過保持信號處理函數的簡單性,你可以有效提高程序的穩(wěn)定性和可靠性,減少潛在的問題和復雜的調試過程。
    9 v' K/ Y' }: a& n4 b6 `- l5 b! a+ w% `8 F; Z* z! K
    0 u3 y: X* Q3 C# x. F7 U
    5 b7 W: I7 @8 l1 z. \
    點擊閱讀原文,更精彩~
  • 回復

    使用道具 舉報

    發(fā)表回復

    您需要登錄后才可以回帖 登錄 | 立即注冊

    本版積分規(guī)則

    關閉

    站長推薦上一條 /1 下一條


    聯系客服 關注微信 下載APP 返回頂部 返回列表