Linux訊號(一)
訊號的概述
訊號是事件發生時對程序的通知機制,也成為軟體中斷,是程序之間通訊的方式之一。訊號分為兩大類,一組用於核心向程序通知事件,構成所謂的傳統或標準訊號;另一組由實時訊號構成。
訊號因某些事件而產生,訊號產生後,會於稍後傳遞給某個程序,程序即會採取相應的措施來相應訊號。但是有時候需要保證一段程式碼不被傳遞來的訊號所中斷,即可以將訊號新增到程序的訊號掩碼中(其作用就是阻塞該訊號的到達),如產生的訊號處於該阻塞之列,那麼訊號將保持等待狀態。
訊號到達後,程序視具體訊號執行如下預設操作之一:
- 忽略訊號。
- 終止(殺死)程序。
- 產生核心轉儲檔案,同時程序終止
- 停止程序:暫停程序的執行
- 與之前停止後再度恢復程序執行。
除此之外,程式也可以改變訊號到達時候的響應行為,執行訊號處理器程式(用於為響應傳遞來的訊號而執行適當任務)。
訊號型別及預設行為
以下給出了幾種常見的訊號及預設行為。訊號的預設行為: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的請求。向訊號掩碼中新增一個訊號三種方式:
- 當呼叫訊號處理器程式時,可將引發呼叫的訊號自動新增到訊號掩碼中(根據sigaction標誌而定)
- 使用sigaction函式建立訊號處理器程式時,可以指定一組額外訊號,當呼叫該處理器程式時會將其阻塞。
- 使用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;
終止訊號處理器函式的方法:
- 使用_exit()終止程序,不能使用exit()因為其不安全(在呼叫_exit()之前重新整理緩衝區)
- 使用kill()訊號來殺死程序
- 從訊號處理器函式中指向非本地跳轉,sigsetjmp()和siglongjmp()
- 使用abort()函式終止程序,併產生核心轉儲。
參考《TLPI》《APUE》