LINUX中的訊息傳遞函式分析
LINUX中的訊息傳遞函式分析
sigset_t
號集及訊號集操作函式:訊號集被定義為一種資料型別:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
未決和阻塞標誌可以用相同的資料型別sigset_t來儲存,sigset_t稱為訊號集,這個型別可以表示每個訊號的“有效”或“無效”狀態,在阻塞訊號集中“有效”和“無效”的含義是該訊號是否被阻塞,而在未決訊號集中“有效”和“無效”的含義是該訊號是否處於未決狀態。阻塞訊號集也叫做當前程序的訊號遮蔽字(Signal Mask),這裡的“遮蔽”應該理解為阻塞而不是忽略
訊號集用來描述訊號的集合,linux所支援的所有訊號可以全部或部分的出現在訊號集中,主要與訊號阻塞相關函式配合使用。下面是為訊號集操作定義的相關函式:
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);
標頭檔案
#include <signal.h>
sigemptyset(sigset_t *set)初始化由set指定的訊號集,訊號集裡面的所有訊號被清空;
sigfillset(sigset_t *set)呼叫該函式後,set指向的訊號集中將包含linux支援的64種訊號;
sigaddset(sigset_t *set, int signum)在set指向的訊號集中加入signum訊號;
sigdelset(sigset_t *set, int signum)在set指向的訊號集中刪除signum訊號;
sigismember(const sigset_t *set, int signum)判定訊號signum是否在set指向的訊號集中。
訊號產生方式
- 通過終端按鍵產生。 如ctrl+c 終止程序
- 通過系統函式向程序傳送訊號。如
kill()
函式,給指定程序傳送訊號。 - 有軟體條件產生訊號。如
alarm()
函式,設定一個鬧鐘訊號。 - 硬體異常。如記憶體越界,除0異常。
利用kill()函式實現自己的kill 命令
// 傳送訊號給程序
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
先寫一個死迴圈程式test並在後臺跑起來
//test.c
#include <stdio.h>
int main()
{
while(1);
return 0;
}
這是利用kill 函式實現的mykill:
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main( int argc, char** argv)
{
if(argc != 1)
{
printf("parameter error.\n");
exit(1);
}
// 獲取pid
pid_t pid = atoi(argv[1]);
kill(pid, SIGKILL);
return 0;
}
然後執行jobs ,可以看到後臺作業test已經存在。
此時執行mykill 並 輸入test程式的pid
第一次執行jobs發現test狀態已經從Running 變為 Killed,再次執行就會看到已經被幹掉。
認識alarm() 函式
#include <unistd.h>
unsigned int alarm( unsigned int seconds);
alarm函式可以設定一個鬧鐘,告訴核心在seconds秒之後給當前程序傳送SIGALRM訊號,該訊號的預設處理動作是終止當前程序。 函式返回值是0,或者是上次鬧鐘剩餘時間。
比如先設定鬧鐘10s, 然後呼叫alarm(0) 表示取消鬧鐘, 如果返回0, 說明鬧鐘是在10後響的,如果大於0,則說明該鬧鐘提前響了。
下面是一個使用alarm函式的小栗子, 測試一秒對可以執行多少次++,並在每次++後列印值:
#include <stdio.h>
#include <unistd.h>
int main()
{
int count = 0;
alarm(1);
while(1)
{
count++;
printf("count is %d .\n", count);
}
return 0;
}
訊號的處理方式
一個程序在收到一個訊號後,有三種方式處理方式:
1. 忽略此訊號
2. 執行預設動作
3. 提供一個訊號處理函式,要求核心在處理該訊號時切換到使用者態,這種方式也叫捕捉訊號。
一個捕捉訊號的小例子:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sigact(int num)
{
printf("\n %d號 訊號 被我吃了. \n", num);
}
int main()
{
printf("catch start ... \n");
signal(SIGINT, sigact); // 捕捉 SIGINT 訊號,提供自定義動作
while(1)
{
sleep(1);
printf("你殺不掉我 hhh \n");
}
return 0;
}
在程式碼中,我們捕捉了SIGINT 訊號,也就是2號訊號(kill -l 可以看到全部訊號列表), 作業系統收到ctrl + c ,傳送該訊號給當前前臺作業。發現執行後它每個一秒列印一條“你殺不掉我 hhh”, 當我們按ctrl+c 也無濟於事,因為我們為該程序提供了2號訊號的自定義函式“訊號被我吃掉了”。最後我們只能以ctrl + \幹掉它。
訊號在記憶體中的表示
以上討論了訊號產生的各種原因,而實際執行訊號的動作稱為遞達(Delivery),訊號從產生到遞達之間的狀態,稱為訊號未決(Pending)。程序可以設定阻塞(Block)某個訊號。
如果一個程序阻塞了某個訊號,那麼在它收到被阻塞的訊號時,該訊號會處於未決狀態。直到對該訊號解除阻塞,才會執行抵達的動作。
需要注意的是,訊號阻塞和訊號忽略是不同的。訊號忽略是在該訊號被遞達後執行的動作,而阻塞說明該該訊號在解除阻塞之前不可能遞達。
我們知道在系統中執行的每一個程序都有一個 PCB, 而一個程序對應的訊號資訊也會被作業系統記錄在該程序的 PCB 上。在task_struct 結構體會有對應的欄位來記錄程序當前是否有待處理的訊號, 還有記錄當前要阻塞的訊號,以及訊號對應的處理函式。下面用一張圖來說明一下關係:
我們可以這樣理解:以看做在PCB中有三張表格,分別是block表,pending表,handler表。
block表中1 表示該訊號被阻塞,當有訊號產生時不會被抵達,會處於未決狀態。
而pending表記錄未被處理的訊號。handler 對應每個訊號的處理方式,有預設和忽略,以及一個函式指標,指向我們提供的處理函式。
一大波訊號集處理函式襲來
通過上面的表格可以發現,阻塞和未決狀態,每個訊號只需要對應一個bit位即可解決1表示有效,0表示無效,所以系統為我們提供了訊號量集 sigset_t 來儲存阻塞和未決的狀態。
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
以上兩個函式用來將訊號機全部置0或全部置1,在使用之前,務必呼叫對應初始化函式,讓訊號機處於確定狀態。
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
上面兩個函式用來給,指定訊號集,新增或刪除signum訊號。
int sigismember(sigset_t *set, int );
用來判斷訊號集中是否有該訊號,有則返回1,無返回0,執行失敗返回-1。
sigprocmask函式
用來讀取或者更改程序的訊號遮蔽集。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
如果 set 為空 oldset 不為空, 則會將程序原來的訊號遮蔽集傳出;
如果 set 不為空,oldset 為空, 則會根據 how 引數的指示修改程序的訊號遮蔽集;
如果兩個指標都不為空, 則先備份原來的訊號遮蔽字到 oldset, 然後根據 how引數修改。
how引數有如下函式
值 | 含義 |
---|---|
SIG_BLOCK | 將當前程序遮蔽字mask 增加我們希望通過引數set的遮蔽訊號,相當於mask與set執行按位或 |
SIG_UNBLOCK | 將當前程序遮蔽字mask 刪除我們希望通過引數set解除遮蔽的訊號 |
SIG_SETMASK | 設定當前訊號遮蔽字為set,相當於 mask = set |
sigpending
用來讀取當前程序的未決訊號集。
#include <signal.h>
int sigpending(sigset_t *set);
下面運用上面介紹的訊號集函式寫一個小實驗。
程式執行時,每秒列印一此未決狀態訊號集,初始全為0,當輸出ctrl-c時, 由於我們阻塞了SIGINT訊號, 會使該訊號處於未決狀態。
#include <stdio.h>
#include <signal.h>
// 列印訊號集
void printsigset(const sigset_t *set)
{
int i = 0;
for(; i<32; ++i)
{
if(sigismember(set, i) == 1)
printf("1");
else
printf("0");
}
printf("\n");
}
int main()
{
sigset_t s;
sigemptyset(&s); // 初始化
sigaddset(&s, SIGINT);
sigprocmask(SIG_BLOCK, &s, NULL );
while(1)
{
sigpending(&s);
printsigset(&s);
sleep(1);
}
return 0;
}
此篇文章是對大佬們的講解整理的,感覺還不錯!