在Linux中,信號是進(jìn)程間通訊的一種方式,它采用的是異步機制。當(dāng)信號發(fā)送到某個進(jìn)程中時,操作系統(tǒng)會中斷該進(jìn)程的正常流程,并進(jìn)入相應(yīng)的信號處理函數(shù)執(zhí)行操作,完成后再回到中斷的地方繼續(xù)執(zhí)行。 需要說明的是,信號只是用于通知進(jìn)程發(fā)生了某個事件,除了信號本身的信息之外,并不具備傳遞用戶數(shù)據(jù)的功能。 1 信號的響應(yīng)動作每個信號都有自己的響應(yīng)動作,當(dāng)接收到信號時,進(jìn)程會根據(jù)信號的響應(yīng)動作執(zhí)行相應(yīng)的操作,信號的響應(yīng)動作有以下幾種:
用戶可以通過 2 信號類型Linux支持的信號類型可以參考下面給出的列表。 2.1 在POSIX.1-1990標(biāo)準(zhǔn)中的信號列表
注:其中 2.2 在SUSv2和POSIX.1-2001標(biāo)準(zhǔn)中的信號列表
注:在Linux 2.2版本之前, 2.3 其它信號
注意:列表中有的信號有三個值,這是因為部分信號的值和CPU架構(gòu)有關(guān),這些信號的值在不同架構(gòu)的CPU中是不同的,三個值的排列順序為:1,Alpha/Sparc;2,x86/ARM/Others;3,MIPS。 例如 3 信號機制文章的前面提到過,信號是異步的,這就涉及信號何時接收、何時處理的問題。 我們知道,函數(shù)運行在用戶態(tài),當(dāng)遇到系統(tǒng)調(diào)用、中斷或是異常的情況時,程序會進(jìn)入內(nèi)核態(tài)。信號涉及到了這兩種狀態(tài)之間的轉(zhuǎn)換,過程可以先看一下下面的示意圖: 接下來圍繞示意圖,將信號分成接收、檢測和處理三個部分,逐一講解每一步的處理流程。 3.1 信號的接收接收信號的任務(wù)是由內(nèi)核代理的,當(dāng)內(nèi)核接收到信號后,會將其放到對應(yīng)進(jìn)程的信號隊列中,同時向進(jìn)程發(fā)送一個中斷,使其陷入內(nèi)核態(tài)。 注意,此時信號還只是在隊列中,對進(jìn)程來說暫時是不知道有信號到來的。 3.2 信號的檢測進(jìn)程陷入內(nèi)核態(tài)后,有兩種場景會對信號進(jìn)行檢測:
當(dāng)發(fā)現(xiàn)有新信號時,便會進(jìn)入下一步,信號的處理。 3.3 信號的處理信號處理函數(shù)是運行在用戶態(tài)的,調(diào)用處理函數(shù)前,內(nèi)核會將當(dāng)前內(nèi)核棧的內(nèi)容備份拷貝到用戶棧上,并且修改指令寄存器(eip)將其指向信號處理函數(shù)。 接下來進(jìn)程返回到用戶態(tài)中,執(zhí)行相應(yīng)的信號處理函數(shù)。 信號處理函數(shù)執(zhí)行完成后,還需要返回內(nèi)核態(tài),檢查是否還有其它信號未處理。如果所有信號都處理完成,就會將內(nèi)核?;謴?fù)(從用戶棧的備份拷貝回來),同時恢復(fù)指令寄存器(eip)將其指向中斷前的運行位置,最后回到用戶態(tài)繼續(xù)執(zhí)行進(jìn)程。 至此,一個完整的信號處理流程便結(jié)束了,如果同時有多個信號到達(dá),上面的處理流程會在第2步和第3步驟間重復(fù)進(jìn)行。 4 信號的使用4.1 發(fā)送信號用于發(fā)送信號的函數(shù)有 raise函數(shù):向進(jìn)程本身發(fā)送信號 函數(shù)聲明如下: #include <signal.h> int raise(int sig); 函數(shù)功能是向當(dāng)前程序(自身)發(fā)送信號,其中參數(shù) kill函數(shù):向指定進(jìn)程發(fā)送信號 函數(shù)聲明如下: #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); 函數(shù)功能是向特定的進(jìn)程發(fā)送信號,其中參數(shù) 在這里的參數(shù)
另外,當(dāng) 4.2 等待信號被捕獲等待信號的過程,其實就是將當(dāng)前進(jìn)程(線程)暫停,直到有信號發(fā)到當(dāng)前進(jìn)程(線程)上并被捕獲,函數(shù)有 pause函數(shù):將進(jìn)程(或線程)轉(zhuǎn)入睡眠狀態(tài),直到接收到信號 函數(shù)聲明如下: #include <unistd.h> int pause(void); 該函數(shù)調(diào)用后,調(diào)用者(進(jìn)程或線程)會進(jìn)入睡眠(Sleep)狀態(tài),直到捕獲到(任意)信號為止。該函數(shù)的返回值始終為-1,并且調(diào)用結(jié)束后,錯誤代碼(errno)會被置為EINTR。 sigsuspend函數(shù):將進(jìn)程(或線程)轉(zhuǎn)入睡眠狀態(tài),直到接收到特定信號 函數(shù)聲明如下: #include <signal.h> int sigsuspend(const sigset_t *mask); 該函數(shù)調(diào)用后,會將進(jìn)程的信號掩碼臨時修改(參數(shù) 4.3 修改信號的響應(yīng)動作用戶可以自己重新定義某個信號的處理方式,即前面提到的修改信號的默認(rèn)響應(yīng)動作,也可以理解為對信號的注冊,可以通過 首先看一下函數(shù)聲明: #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 第一個參數(shù) 下面為示例代碼: #include <stdio.h> #include <signal.h> #include <unistd.h> /* 信號處理函數(shù) */ void sig_callback(int signum) { switch (signum) { case SIGINT: /* SIGINT: Ctrl+C 按下時觸發(fā) */ printf("Get signal SIGINT. \r\n"); break; /* 多個信號可以放到同一個函數(shù)中進(jìn)行 通過信號值來區(qū)分 */ default: /* 其它信號 */ printf("Unknown signal %d. \r\n", signum); break; } return; } /* 主函數(shù) */ int main(int argc, char *argv[]) { printf("Register SIGINT(%u) Signal Action. \r\n", SIGINT); /* 注冊SIGINT信號的處理函數(shù) */ signal(SIGINT, sig_callback); printf("Waitting for Signal ... \r\n"); /* 等待信號觸發(fā) */ pause(); printf("Process Continue. \r\n"); return 0; } 源文件下載:鏈接 例子中,將 ./linux_signal_example Register SIGINT(2) Signal Action. Waitting for Signal ... ^CGet signal SIGINT. Process Continue. 進(jìn)程收到 |
|