1. 程式人生 > >Linux訊號(一)

Linux訊號(一)

訊號的概述

    訊號是事件發生時對程序的通知機制,也成為軟體中斷,是程序之間通訊的方式之一。訊號分為兩大類,一組用於核心向程序通知事件,構成所謂的傳統或標準訊號;另一組由實時訊號構成。

    訊號因某些事件而產生,訊號產生後,會於稍後傳遞給某個程序,程序即會採取相應的措施來相應訊號。但是有時候需要保證一段程式碼不被傳遞來的訊號所中斷,即可以將訊號新增到程序的訊號掩碼中(其作用就是阻塞該訊號的到達),如產生的訊號處於該阻塞之列,那麼訊號將保持等待狀態。

    訊號到達後,程序視具體訊號執行如下預設操作之一:

  1. 忽略訊號。
  2. 終止(殺死)程序。
  3. 產生核心轉儲檔案,同時程序終止
  4. 停止程序:暫停程序的執行
  5. 與之前停止後再度恢復程序執行。

    除此之外,程式也可以改變訊號到達時候的響應行為,執行訊號處理器程式(用於為響應傳遞來的訊號而執行適當任務)。

訊號型別及預設行為

    以下給出了幾種常見的訊號及預設行為。訊號的預設行為:term表示訊號終止程序,core表示產生核心轉儲檔案並退出,ignore表示忽略訊號,stop表示訊號停止程序,cont表示訊號恢復一個已停止的程序。

SIGABRT 當程序呼叫abort()時,該訊號終止程序,併產生核心轉儲檔案。core
SIGALRM 經呼叫alarm()、setitimer()而設定定時器到期,核心產生該訊號。term
SIGBUS 產生該訊號(匯流排錯誤)即表示產生某種記憶體訪問錯誤。core
SIGCHLD 當父程序的某一個子程序終止、停止或恢復,核心向父程序傳送訊號。ignore
SIGCONT 將訊號傳送給已經停止的程序,程序將會恢復允許,若程序非停止狀態則忽略該訊號。cont
SIGINT 當用戶鍵入終端中斷字元(control-c),終端驅動程式發該訊號給前臺程序。term
SIGKILL 此訊號用於終止程序,處理器程式無法將其阻塞、忽略或捕獲,保證殺死。term
SIGPIPE 當某一程序試圖向管道、FIFO或套接字寫入資訊時,若這些裝置並無響應閱讀程序,即會產生此訊號。term
SIGSEGV 當應用程式對記憶體的引用無效時,即會產生該訊號。core
SIGTERM 用來終止程序的標準訊號。term
SIGSTOP 是一個必停訊號,從能讓程序停止。stop

訊號處理器簡介

    訊號處理器程式是當指定訊號傳遞給程序時,將會呼叫的一個函式。呼叫訊號處理器程式,可能會打斷主程式流程。核心代表程序來呼叫處理器程式,當處理器返回時,主程式會在處理器打斷的位置恢復執行。

    接下來介紹一下signal系統呼叫。由於主要推薦用sigaction,因此不多介紹。

#include <signal.h>

void (*signal(int sig,void(*handler)(int)) ) (int);//失敗返回SIG_ERR

//handler的引數int用於同一個處理程式捕捉不同型別的訊號,用此引數來判斷

kill()傳送訊號

    一個程序可以使用kill()系統呼叫向另一個程序傳送訊號。當pid>0,那麼傳送訊號會給pid指定的程序;pid=0,那麼傳送訊號給與呼叫程序同組的每個程序,包括呼叫程序自身。pid=-1,則呼叫程序有權將訊號傳送每個目標程序(除了init和自身)

#include <signal.h>

int kill(pid_t pid,int sig);

int raise(int sig);

int killpg(pid_t pgrp,int sig);

    kill()系統呼叫還可以檢查程序的存在,將引數sig設定為0(即空訊號),kill就會檢視是否可以向目標程序傳送訊號。

    除此之外,還可以使用raise()系統呼叫讓程序向自身傳送訊號。killpg()向某一格程序組所有成員傳送訊號。

訊號集

    多個訊號可使用一個稱之為訊號集的資料結構表示,用系統資料結構sigset_t表示。很多訊號相關的系統呼叫都是以組的形式執行的,以下介紹幾種常見的。

#include <signal.h>

//必須使用以下兩個初始化方式之一來初始化訊號集
int sigemptyset(sigset_t *set);//初始化一個未包含任何成員的訊號集
int sigfillset(sigset_t *set); //初始化一個訊號集,使其包含所有訊號(包括實時訊號)

int sigaddset(sigset_t *set ,int sig);//向訊號集中新增單個訊號
int sigdelset(sigset_t *set ,int sig);//向訊號集中刪除單個訊號

int sigismember(const sigset_t *set,int sig);//判斷訊號是否是訊號整合員,是返回1,否返回0

int sigandset(sigset_t *dest, sigset_t *left, sigset_t *right);//left和right交集置於dest
int sigorset(sigset_t *dest, sigset_t *left, sigset_t *right);//left和right並集置於dest

int sigisemptyset(const sigset_t *set);//set為空返回1,否則返回0

char* strssignal(int sig);//顯示某個訊號的描述

    訊號掩碼:核心會為每一個程序維護一個訊號掩碼,即一組訊號,並將阻塞其針對該程序的傳遞。系統將忽略阻塞SIGKILL和SIGSTOP的請求。向訊號掩碼中新增一個訊號三種方式:

  1. 當呼叫訊號處理器程式時,可將引發呼叫的訊號自動新增到訊號掩碼中(根據sigaction標誌而定)
  2. 使用sigaction函式建立訊號處理器程式時,可以指定一組額外訊號,當呼叫該處理器程式時會將其阻塞。
  3. 使用sigprocmask()系統呼叫,顯式地向訊號掩碼中新增或移除訊號。
#include <signal.h>

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

//how=SIG_BLOCK 將set指定訊號集內的訊號新增到訊號掩碼中
//how=SIG_UNBLOCK 將set指定訊號集內的訊號從訊號掩碼中移除
//how=SIG_SETMASK 將set指定訊號集內的訊號賦值給訊號掩碼中
//若oldset不為空,則用於儲存改變之前的訊號掩碼,若想獲得訊號掩碼而不改變其,則set置空,忽略how

int sigpending(sigset_t *set);//set返回該程序處於等待的訊號集

    若程序接受了一個該程序正在阻塞的訊號,則會將訊號新增到程序的等待訊號集中,當解除了對該訊號的鎖定,會隨之將訊號傳遞給程序。用sigpending()獲取程序處於等待狀態的訊號。等待訊號集只是一個掩碼,僅表示一個訊號是否在等待,而未表明其發生的次數,即若同一個訊號在阻塞狀態下產生多次,那麼該訊號在等待訊號集中,並在稍後僅傳遞一次(標準訊號與實時訊號差異之一,實時訊號進行排隊處理)。

sigaction()訊號處置

    sigaction()較signal()更加方便。其允許在獲取訊號處置的同時不對其進行改變,並且可以設定各種屬性對呼叫訊號處理器程式控制。

#include <signal.h>

int sigaction(int sig,const struct sigaction *act,struct sigaction *oldact);
//sig標識想要獲取或者改變的訊號編號,act指向描述訊號新處理的資料結構,oldact返回之前訊號處理的相關資訊


struct sigaction
{
    void (*sa_handler)(int); //訊號處理函式
    sigset_t sa_mask;//在呼叫訊號處理器時將阻塞該組訊號,引發處理器程式呼叫的訊號自動加入訊號掩碼
    int sa_flags;//用於控制訊號處理過程的各種選項
    void (*sa_restorer)(void);
}

sa_flags的各種選項:

SA_NOCLDSTOP:若sig為SIGCHLD訊號,則當因接受一訊號而停止或恢復某一子程序時,將不會產生此訊號
SA_NOCLDWAIT:若sig為SIGCHLD訊號,則當子程序終止時不會將其轉換為殭屍
SA_ONSTACK:針對此訊號呼叫處理器函式時,使用了由sigalstack()安裝的備選棧
SA_RESTART:自動重啟由訊號處理器函式中斷的系統呼叫
SA_SIGINFO:呼叫訊號處理器程式時可攜帶額外引數

訊號處理器函式

    設計訊號處理器函式中,並非所有的系統呼叫和庫函式均可以安全呼叫。要注意可重入函式和非同步訊號安全函式。

  • 可重入函式:如果同一個程序的多條執行緒可以同時安全地呼叫某一函式,那麼該函式是可重入地。更新全域性變數或靜態資料結構地函式,詩經靜態分配記憶體來返回資訊的函式,將靜態資料結構用於內部記賬的函式,這些都是不可重入的。
  • 非同步訊號安全函式是指當從訊號處理器函式呼叫時,可以保證其實現是安全的

    儘管可能存在可重入問題,但有時候主程式和訊號處理程式之間需共享全域性變數。這時候應該定義如下變數。volatile可防止編譯器將其優化到暫存器中,sig_atomic_t型別可保證讀寫操作的原子性:

volatile sig_atomic_t flag;

    終止訊號處理器函式的方法:

  1. 使用_exit()終止程序,不能使用exit()因為其不安全(在呼叫_exit()之前重新整理緩衝區)
  2. 使用kill()訊號來殺死程序
  3. 從訊號處理器函式中指向非本地跳轉,sigsetjmp()和siglongjmp()
  4. 使用abort()函式終止程序,併產生核心轉儲。

 

參考《TLPI》《APUE》