首頁 > 軟體

淺析 Linux/UNIX 信號機制

2020-06-16 17:56:56

      信號常常被稱為“軟中斷”,和“中斷”類似,用來通知程式發生非同步事件。對信號的處理一般來說有三種方式:忽略,終止進程以及使用信號處理常式。信號處理常式的方式是從一處執行流斷開,轉而去執行另外的一處程式碼(信號處理),當處理常式返回時,繼續從斷開的地方繼續執行。

1、安裝信號處理常式

  在系統程式設計的層面上與信號的處理關係最直接相關的函數有兩個,他們用來安裝信號處理常式:


      sighandler_t signal(int signum, sighandler_t handler);


      int sigaction(int signum, const struct sigaction *act,,struct sigaction *oldact);

    第一個函數signal比較簡單,sighandler_t 是一個別名,其原型是 typedef void (*sighandler_t)(int),他是一個函數指標,接受一個型別為int的引數(信號的編號),返回void。例如要對SIGUSR1信號進行處理:

void handler(int sig)
{
    //strsiganl 功能是把信號的編號轉為信號說明的字串
    printf("Rcv a signal:%s",strsignal(sig));
}

int main()
{
    signal(SIGUSR1,handler);
    while(1)
        ;     
}

 (這段程式其實是有問題的,後面會說到)這段程式本來是一段死迴圈,但是對他傳送SIGUSR1信號,程式會從while中“中斷”轉去執行handler中的程式碼。在shell中使用kill命令傳送信號SIGUSR1 於是程式就答應出了一段這樣的資訊:Rcv a signal:User defined signal 1。signal()的用法幾乎就是這麼簡單。但是由於可移植的原因,參與專案開發時,應該使用下面的這個函數。

  sigaction()函數的引數中有兩個結構體,其man手冊原型如下:


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和sa_sigaction其實是在一個union中,他們都是指向信號處理常式的指標。

  sa_mask 是要遮蔽的信號,sa_flags 有多種選項。(關於這兩點後文再細說)。從sigaction()原型中可以發現引數中有兩個struct sigaction引數,其中act是要安裝的信號處理,而oldact是用來帶回原來的處理方式方便我們處理完信號後的恢復。如果不需要拿回之前的信號處理方式可以把第三個引數置為NULL,反之如果只想得到之前的處理方式而不像安裝新的信號處理,可以把第二個引數置為NULL,這點用signal()是辦不到的。用sigaction()改寫上面的例子是這樣的:

 1 void handler(int sig)
 2 {
 3    printf("Rcv a signal:%s",strsignal(sig));
 4 }
 5
 6 int main()
 7 {
 8    struct sigaction act;
 9    sigemptyset(&act.sa_mask);
10    act.sa_handler = handler;
11    act.sa_flags = 0;
12    sigaction(SIGUSR1,&act,NULL);
13    while(1)
14        ;       
15 }

2、信號阻塞、信號的未決

  sigset_t 是一種將信號型別以為位掩碼形式存在的資料型別(下文都稱之為信號集),他是多種信號的集合(可以保證容納所有的信號)。作業系統的PCB為每個進程都維護了一個這樣的資料型別,並將其內所有的信號阻塞,使他們不可以實時到達進程。當信號遮蔽解除時他們才被傳遞到進程。在這之間的狀態通常被稱為未決(pending)。而在信號阻塞期間多次到來的信號,在信號遮蔽解除時只會被報告一次。

  對sigset_t 處理有一系列函數,其中POSIX標準有5個


int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signum);

int sigdelset(sigset_t *set, int signum);

int sigismember(const sigset_t *set, int signum);

  這樣的函數基本上看引數就能知道怎麼用,不在贅述。

  glibc中還實現了3個擴充套件的函數:


int sigisemptyset(sigset_t *set);

int sigorset(sigset_t *dest, sigset_t *left, sigset_t *right);

int sigandset(sigset_t *dest, sigset_t *left, sigset_t *right);

  sigprocmask()函數可以檢測和更改信號遮蔽集。

  每個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的所有信號在遞送到進程後都將被阻塞。  


int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

how 說明
SIG_BLOCK 將set中的信號與原有的取並集,並更新進程的遮蔽字
SIG_UNBLOCK 解除原有的信號集中包含set中的信號,(set補集的交集)
SIG_SETMASK 將進程的遮蔽字設定為set

sigpending函數可以看到信號遮蔽期間那些信號來到過(不計次數的)。

更多詳情見請繼續閱讀下一頁的精彩內容http://www.linuxidc.com/Linux/2015-07/120632p2.htm


IT145.com E-mail:sddin#qq.com