Linux訊號解析和心得
1. 什麼是訊號?
網上給出的定義是:在電腦科學中,訊號(英語:Signals)是Unix、類Unix以及其他POSIX相容的作業系統中程序間通訊的一種有限制的方式。它是一種非同步的通知機制,用來提醒程序一個事件已經發生。當一個訊號傳送給一個程序,作業系統中斷了程序正常的控制流程,此時,任何非原子操作都將被中斷。如果程序定義了訊號的處理函式,那麼它將被執行,否則就執行預設的處理函式。
簡單的理解訊號就是一種Linux環境下程序通訊的一種機制。
2. 訊號怎麼產生的?
訊號一般是有以下3個方面產生:
- 通過終端按鍵產生訊號
- 呼叫系統函式產生訊號
- 軟體產生的訊號
1>終端按鍵就是我們在Linux中寫程式是想要終止某個程式,就會按ctrl+c按鍵,還有類似ctrl+d、ctrl+\、ctrl+z等。
2>呼叫系統函式可以產生訊號,比如
這裡我們將一個死迴圈的程序放在後臺,而我們可以使用一個kill 命令向該程序傳送訊號。
int kill(pid_t pid, int sig);
int raise(int sig);
//返回值:成功為0,失敗為-1;
在上面的圖上我們可以看到一個程序可以呼叫系統函式來發送訊號。
3>有軟體產生的訊號,我們在管道的時候,有一個管道產生的訊號SIGPIPE,這個就是軟體訊號。
還有我們的alarm函式,相當於定一個鬧鐘,時間到了會發送一個SIGALRM訊號。
unsigned int alarm(unsigned int seconds);
//返回值:0或者是當前還剩餘的秒數。
如圖:
擴充套件:Core Dump(核心轉儲)
定義為:核心轉儲(core dump),在漢語中有時戲稱為吐核,是作業系統在程序收到某些訊號而終止執行時,將此時程序地址空間的內容以及有關程序狀態的其他資訊寫出的一個磁碟檔案。這種資訊往往用於除錯。
當程式執行的過程中異常終止或崩潰,作業系統會將程式當時的記憶體狀態記錄下來,儲存在一個檔案中,這種行為就叫做Core Dump(中文有的翻譯成“核心轉儲”)。我們可以認為 core dump 是“記憶體快照”,但實際上,除了記憶體資訊之外,還有些關鍵的程式執行狀態也會同時 dump 下來,例如暫存器資訊(包括程式指標、棧指標等)、記憶體管理資訊、其他處理器和作業系統狀態和資訊。core dump 對於程式設計人員診斷和除錯程式是非常有幫助的,因為對於有些程式錯誤是很難重現的,例如指標異常,而 core dump 檔案可以再現程式出錯時的情景。
而Linux作業系統預設不會產生core檔案的,因為core檔案有可能會包括使用者的資訊,不安全。而我們如果是在測試間斷可以使用ulimie命令改變這個命令,使其產生core檔案
ulimit -c 1024//建立1024大小的core檔案,你以後生成的core大小就是這麼大。
3. 訊號有哪些?
在命令列中輸入kill -l就可看到訊號了:
我們可以看到共有1-31 34-64共62個訊號,其中1-31是普通訊號,34-64為實時訊號。
4. 訊號的阻塞
- 訊號相關的其他概念
實際執行訊號的動作叫“遞達”
訊號從產生到遞達之間叫“未決”
訊號可以被阻塞起來
這個流程示意圖如下:
- 訊號在核心中的示意圖
- sigset_t訊號集
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
我們從上面的圖可以看到阻塞,未決都是由0 1 來控制他們是否被響應。
- 訊號集操作函式
int sigemptyset(sigset_t *set);//初始化set指向的訊號集,使所有的bit為清0,
//表示該訊號集不包含任何有效訊號。
int sigfillset(sigset_t *set);//初始化set指向的訊號集,使所有的bit置位
//表示該訊號集的有效訊號包含所有訊號。
int sigaddset(sigset_t *set, int signum);//修改pending表
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);//得到訊號集
//返回值,前四個函式成功為0。失敗為-1;
//sigismember是一個布林函式,用來判斷某個訊號集中是否包含某個訊號,
//包含為1,不包含為0,出錯為-1;
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//這個函式可以讀取或者更改程序的訊號遮蔽字(阻塞訊號集)
//返回值:成功為0,失敗為-1;
int sigpending(sigset_t *set);
//讀取該程序的未決訊號集,通過set引數傳出
//返回值:成功為0;失敗為-1;
通過上述我們可以試著寫個程式驗證一下:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void printsigset(sigset_t* set)
{
int i = 0;
for(;i<32;i++)
{
if(sigismember(set,i))//判斷指定訊號是否在目標集合中
{
putchar('1');
}
else
{
putchar('0');
}
}
puts("");
}
int main()
{
sigset_t i,j;
sigemptyset(&i);//定義訊號集物件,並且清空初始化
sigaddset(&i,SIGINT);//將ctrl+c定義成未決
sigprocmask(SIG_BLOCK,&i,NULL);//將ctrl+c阻塞
while(1)
{
sigpending(&j);//獲取未決訊號集
printsigset(&j);
sleep(1);
}
return 0;
}
效果圖:
- 訊號的捕捉
如上圖所示,訊號捕捉是在核心態返回使用者態時的自定義處理。
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
//signum是指定訊號的編號
//act是指向你所需要修改的訊號處理動作
//oldact是用來傳出你之前的訊號處理動作
//返回值:成功為0,出錯為-1
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
int pause(void);
//只有EINTR 有訊號到達中斷此函式的執行。
//pause()會令目前的程序暫停(進入睡眠狀態), 直到被訊號(signal)所中斷.
//返回值:只錯誤返回-1,成功不返回
.接下來我們自己寫一個測試驗證一下:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void my_sig(int signum)
{
;
}
unsigned int my_sleep(int num)
{
struct sigaction act,oldact;//定義新舊訊號結構體
unsigned int unslept = 0;//
act.sa_handler = my_sig;//修改alarm訊號自定義函式
sigemptyset(&(act.sa_mask));//將act訊號集初始化
act.sa_flags = 0;//訊號標誌位
sigaction(SIGALRM,&act,&oldact);//修改鬧鐘函式的訊號處理函式
alarm(num);//定鬧鐘
pause();//掛起
unslept = alarm(0);
sigaction(SIGALRM,&oldact,NULL);//將alarm的訊號出庫函式改回來
return unslept;
}
int main()
{
while(1)
{
my_sleep(2);
printf("2秒時間到\n");
}
return 0;
}