【Linux系统编程学习】信号、信号集以其相关函数
此為牛客Linux C++和黑馬Linux系統編程課程筆記。
文章目錄
- 0. 信號的概念
- 1. Linux信號一覽表
- 2. 信號相關函數
- 3. kill函數
- 4. raise函數
- 5. abort函數
- 6. alarm函數
- 7. setitimer函數
- 8. signal函數
- 9. 信號集
- 10. 自定義信號集相關函數
- 11. sigprocmask函數
- 12. sigpending函數
- 13. sigaction函數
- 14. 內核實現信號捕捉過程
0. 信號的概念
A給B發送信號,B收到信號之前執行自己的代碼,收到信號后,不管執行到程序的什么位置,都要暫停運行,去處理信號,處理完畢再繼續執行。與硬件中斷類似——異步模式。但信號是軟件層面上實現的中斷,早期常被稱為“軟中斷”。
信號的特質:由于信號是通過軟件方法實現,其實現手段導致信號有很強的延時性。但對于用戶來說,這個延遲時間非常短,不易察覺。
每個進程收到的所有信號,都是由內核負責發送的,內核處理。
1. Linux信號一覽表
紅色為重點掌握的信號
2. 信號相關函數
3. kill函數
#include <sys/types.h> #include <signal.h>int kill(pid_t pid, int sig);功能:給任何的進程或者進程組pid, 發送任何的信號 sig
參數:
- pid :
< 0 : 將信號發送給指定的進程
= 0 : 將信號發送給當前的進程組
= -1 : 將信號發送給每一個有權限接收這個信號的進程
< -1 : 這個pid=某個進程組的ID取反 (-12345)
- sig : 需要發送的信號的編號或者是宏值,0表示不發送任何信號
如kill(getppid(), 9);能夠殺死父進程;kill(getpid(), 9);能夠殺死當前進程。
4. raise函數
#include <sys/types.h> #include <signal.h>int raise(int sig);功能:給當前進程發送信號;
參數:sig : 要發送的信號;
返回值:成功 0, 失敗 非0。
相當于kill(getpid(), sig);
5. abort函數
#include <sys/types.h> #include <signal.h>void abort(void);功能: 發送SIGABRT(編號為6)信號給當前的進程,殺死當前進程;
相當于kill(getpid(), SIGABRT);或raise(SIGBRT);。
6. alarm函數
設置定時器(鬧鐘)。在指定seconds后,內核會給當前進程發送14)SIGALRM信號。進程收到該信號,默認動作終止。
#include <unistd.h> unsigned int alarm(unsigned int seconds);功能:設置定時器(鬧鐘)。函數調用,開始倒計時,當倒計時為0的時候,函數會給當前的進程發送一個信號:SIGALARM。
參數:seconds: 倒計時的時長,單位:秒。如果參數為0,定時器無效(不進行倒計時,不發信號)。
返回值:
- 之前沒有定時器,返回0
- 之前有定時器,返回之前的定時器剩余的時間
常用:使用alarm(0)取消定時器,返回舊鬧鐘余下秒數。
每個進程都有且只有唯一個定時器。 比如:進程先執行了alarm(10),2秒后又執行了一個alarm(5),alarm(5)的返回值是8,因為之前有定時器,返回的是之前定時器的剩余時間。然后從現在起該進程還是只有一個定時器,定時5秒,因為后來的定時器會刷新之前的定時器。
注意,alarm定時是與與進程狀態無關(自然定時法)!就緒、運行、掛起(阻塞、暫停)、終止、僵尸…無論進程處于何種狀態,alarm都計時。
看以下示例程序:
#include <unistd.h> #include <stdio.h> int main() {int i;alarm(1);for(i = 0; ; i++) {printf("%d\n", i);}return 0; }用定時器讓程序執行1s后停止。
我們用time ./alarm來查看該程序的運行時間:
可以看到實際運行時間幾乎是1秒,但是發現用戶時間和系統時間加起來與總的運行時間不同,這是為什么呢。
實際執行時間 = 系統時間 + 用戶時間 + 等待時間。程序的很多時間浪費在printf上了。
7. setitimer函數
#include <sys/time.h> int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);功能:設置定時器(鬧鐘)。可以替代alarm函數。精度微妙us,可以實現周期性定時。
參數:
- which : 定時器以什么時間計時,有以下三種參數,一般用第一種自然定時。
ITIMER_REAL: 真實時間(自然定時),時間到達發送 SIGALRM 常用
ITIMER_VIRTUAL: 用戶時間,時間到達發送 SIGVTALRM
ITIMER_PROF: 以該進程在用戶態和內核態下所消耗的時間來計算,時間到達發送 SIGPROF - new_value: 設置定時器的屬性
- old_value :記錄上一次的定時的時間參數,一般不使用,指定NULL
如以下示例程序能夠實現延遲3秒,每2秒發送一次信號。
#include <sys/time.h> #include <stdio.h> #include <stdlib.h>// 過3秒以后,每隔2秒鐘定時一次 int main() {struct itimerval new_value;// 設置間隔的時間new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 設置延遲的時間,3秒之后開始第一次定時new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定時器開始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0; }由于還沒有介紹signal信號捕捉函數,setitimer發出的信號讓程序終止,所以無法演示其周期性發送信號的功能,接下來介紹signal函數。
8. signal函數
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);功能:設置某個信號的捕捉行為
注意:并不是該函數來捕捉信號,該函數只是向內核注冊對某個信號的捕捉行為。
參數:
- signum: 要捕捉的信號
- handler: 捕捉到信號要如何處理,可以有以下三種參數:
- SIG_IGN : 忽略信號
- SIG_DFL : 使用信號默認的行為
- 回調函數 : 這個函數是內核調用,程序員只負責寫,捕捉到信號后如何去處理信號。
回調函數:
- 需要程序員實現,提前準備好的,函數的類型根據實際需求,看函數指針的定義
- 不是程序員調用,而是當信號產生,由內核調用
- 函數指針是實現回調的手段,函數實現之后,將函數名放到函數指針的位置就可以了。
返回值:
- 成功,返回上一次注冊的信號處理函數的地址。第一次調用返回NULL
- 失敗,返回SIG_ERR,設置錯誤號
注意:SIGKILL 和 SIGSTOP不能被捕捉,不能被忽略。
在setitimer的示例代碼中加入signal后,示例代碼如下:
void myfunc(int num) {printf("捕捉到了信號的編號是:%d\n", num);printf("xxxxxxx\n"); }// 過3秒以后,每隔2秒鐘定時一次 int main() {// 注冊信號捕捉// signal(SIGALRM, SIG_IGN);// signal(SIGALRM, SIG_DFL);// void (*sighandler_t)(int); 函數指針,int類型的參數表示捕捉到的信號的值。signal(SIGALRM, myfunc);struct itimerval new_value;// 設置間隔的時間new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 設置延遲的時間,3秒之后開始第一次定時new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定時器開始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0; }signal的第二個參數傳入函數地址,當當前進程捕捉到SIGALRM信號時,將執行程序員自定義的myfunc函數,myfun函數的int類型參數是捕捉到的信號的值(編號)。
程序運行結果如下:
程序運行3秒后第一次發出信號,程序輸出一次,然后每隔2秒發出一次信號。
9. 信號集
一個進程的PCB中除了包含進程id,狀態,工作目錄,用戶id,組id,文件描述符表,還包含了信號相關的信息,主要指阻塞信號集和未決信號集。
阻塞信號集(信號屏蔽字): 將某些信號加入集合,對他們設置屏蔽,當屏蔽x信號后,再收到該信號,該信號的處理將推后(解除屏蔽后)
未決信號集:
- 信號產生,未決信號集中描述該信號的位立刻翻轉為1,表信號處于未決狀態。當信號被處理對應位翻轉回為0。這一時刻往往非常短暫。
- 信號產生后由于某些原因(主要是阻塞)不能抵達。這類信號的集合稱之為未決信號集。在屏蔽解除前,信號一直處于未決狀態。
信號集本質上是一個64位的二進制數,其每一位的0或1代表著該位序號對應的信號的狀態。
當信號產生時,PCB中未決信號集中的該位立即置為1,然后去阻塞信號集的同樣位置查看是否為1,如果阻塞信號集的對應位置也為1,說明該信號要阻塞,未決信號集的該位置保持1不變;直到阻塞解除,這個信號就被處理。
10. 自定義信號集相關函數
以下信號集相關的函數都是對自定義的信號集進行操作。
int sigemptyset(sigset_t *set);- 功能:清空信號集中的數據,將信號集中的所有的標志位置為0
- 參數:set,傳出參數,需要操作的信號集
- 返回值:成功返回0, 失敗返回-1
- 功能:將信號集中的所有的標志位置為1
- 參數:set:傳出參數,需要操作的信號集
- 返回值:成功返回0, 失敗返回-1
- 功能:設置信號集中的某一個信號對應的標志位為1,表示阻塞這個信號
- 參數:
- set:傳出參數,需要操作的信號集
- signum:需要設置阻塞的那個信號 - 返回值:成功返回0, 失敗返回-1
- 功能:設置信號集中的某一個信號對應的標志位為0,表示不阻塞這個信號
- 參數:
- set:傳出參數,需要操作的信號集
- signum:需要設置不阻塞的那個信號 - 返回值:成功返回0, 失敗返回-1
- 功能:判斷某個信號是否阻塞
- 參數:
- set:需要操作的信號集
- signum:需要判斷的那個信號 - 返回值:
1 : signum被阻塞
0 : signum不阻塞
-1 : 失敗
一個用到以上函數的示例程序如下:
#include <signal.h> #include <stdio.h>int main() {// 創建一個信號集sigset_t set;// 清空信號集的內容sigemptyset(&set);// 判斷 SIGINT 是否在信號集 set 里int ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 添加幾個信號到信號集中sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 判斷SIGINT是否在信號集中ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 判斷SIGQUIT是否在信號集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}// 從信號集中刪除一個信號sigdelset(&set, SIGQUIT);// 判斷SIGQUIT是否在信號集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}return 0; }11. sigprocmask函數
之前的信號集函數均是對自定義的信號集進行操作,那如何修改內核中的阻塞信號集呢?可以使用sigprocmask函數,用自定義的信號集設置內核阻塞信號集。
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);功能:將自定義信號集中的數據設置到內核中(設置阻塞,解除阻塞,替換)。
參數:
- how : 如何對內核阻塞信號集進行處理,有以下可選參數:
SIG_BLOCK: 將用戶設置的阻塞信號集添加到內核中,內核中原來的數據不變(假設內核中默認的阻塞信號集是mask, mask | set)。
SIG_UNBLOCK: 根據用戶設置的數據,對內核中的數據進行解除阻塞(相當于 mask = mask & ~set)。
SIG_SETMASK: 用set覆蓋內核中原來的值。
- set :已經初始化好的用戶自定義的信號集
- oldset : 保存設置之前的內核中的阻塞信號集的狀態,可以是 NULL。
返回值: 成功:0 ;失敗:-1,并設置錯誤號。
12. sigpending函數
#include <signal.h> int sigpending(sigset_t *set);功能:獲取內核中的未決信號集。
參數:set,傳出參數,保存的是內核中的未決信號集中的信息。
13. sigaction函數
sigaction函數通常用于替代signal函數,用來捕捉信號,同時自定義信號的處理動作。
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);功能:檢查或者改變信號的處理。信號捕捉。
參數:
- signum : 需要捕捉的信號的編號或者宏值(信號的名稱)
- act :捕捉到信號之后的處理動作
- oldact : 上一次對信號捕捉相關的設置,一般不使用,傳NULL即可
返回值: 成功 0 失敗 -1
其中參數act的類型sigaction結構體定義如下:
struct sigaction {// 函數指針,指向的函數就是信號捕捉到之后的處理函數void (*sa_handler)(int);// 不常用void (*sa_sigaction)(int, siginfo_t *, void *);// 臨時阻塞信號集,在信號捕捉函數執行過程中,臨時阻塞某些信號。sigset_t sa_mask;// 使用哪一個信號處理對捕捉到的信號進行處理// 這個值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigactionint sa_flags;// 被廢棄掉了void (*sa_restorer)(void);};其中sa_sigaction和sa_restorer我們基本用不到,所以掌握以下三個即可:
① sa_handler:指定信號捕捉后的處理函數名(即注冊函數)。也可賦值為SIG_IGN表忽略 或 SIG_DFL表執行默認動作。
② sa_mask: 調用信號處理函數時,所要屏蔽的信號集合(信號屏蔽字)。注意:僅在處理函數被調用期間屏蔽生效,是臨時性設置。
③ sa_flags:通常設置為0,表使用默認屬性。
該函數與signal函數最大的區別就在于sa_mask上,sa_mask是程序員自定義的一個信號集,該信號集充當調用信號處理函數時的一個臨時的阻塞信號集,也就是說:
進程正常運行時,默認PCB中有一個信號屏蔽字(阻塞信號集),假定為☆,它決定了進程自動屏蔽哪些信號。當注冊了某個信號捕捉函數,捕捉到該信號以后,要調用該函數。而該函數有可能執行很長時間,在這期間所屏蔽的信號不由☆來指定。而是用sa_mask來指定。調用完信號處理函數,再恢復為☆。
示例程序如下:
#include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <signal.h>void catchFunc(int signo) {printf("捕捉到了信號:%d \n", signo); }int main() {struct sigaction act;act.sa_handler = catchFunc;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGQUIT); // 想要在捕捉函數中屏蔽SIGQUIT信號int res = sigaction(SIGINT, &act, NULL);if(res == -1) {perror("sigaction error");exit(1);}while(1);return 0; }執行結果如下:
每次在終端輸入ctrl+c(產生SIGINT信號)時,輸出:捕捉到了信號2。
當在鍵盤中輸入ctrl+\(產生SIGQUIT)時, 程序退出。那么有個問題,程序中不是已經設置了sigaddset(&act.sa_mask, SIGQUIT);來屏蔽信號了嗎?為什么輸入ctrl+\時, 程序依然會退出?是因為sigaction函數設置的sa_mask只在信號處理函數執行中生效,輸出語句后信號處理函數以及執行完畢。
再看下面示例程序:該程序讓信號處理函數睡眠10秒。
#include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h>void catchFunc(int signo) {printf("捕捉到了信號:%d \n", signo);sleep(10);printf("-----finish-----"); }int main() {struct sigaction act;act.sa_handler = catchFunc;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGQUIT); // 想要在捕捉函數中屏蔽SIGQUIT信號int res = sigaction(SIGINT, &act, NULL);if(res == -1) {perror("sigaction error");exit(1);}return 0; }運行程序后,輸入ctrl+c,終端輸出如下:
此時10秒以內,依然在執行信號捕捉函數catchFunc,也就是說當前sa_mask是生效的。此時我們輸入crtl+\:
程序并沒有退出,因為此時sa_mask中屏蔽了SIGQUIT信號。等待10秒過后:
發現程序自動退出,這是因為10秒過后信號捕捉函數catchFunc執行完畢,臨時的阻塞信號集(sa_mask)失效,此時生效的是原PCB中的阻塞信號集,未決信號集(SIGQUIT處的值為1)查詢到后阻塞信號集中SIGQUIT處的值是0后,SIGQUIT信號遞達,程序退出。
這里還有一個值得注意的細節:
當信號捕捉函數catchFunc執行時,我輸入了多個ctrl+c后,信號捕捉函數執行完畢后,只輸出了一個“捕捉到了信號:2”,這是因為我們無論向當前進程發出多少個相同信號,未決信號集的對應位都是1,無法記錄相同信號的數量,所以當臨時阻塞信號集被取消后,只輸出了一個“捕捉到了信號:2”。有以下結論:
阻塞的常規信號不支持排隊,產生多次只記錄一次。(后32個實時信號支持排隊)。
14. 內核實現信號捕捉過程
總結
以上是生活随笔為你收集整理的【Linux系统编程学习】信号、信号集以其相关函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小的子宫内膜息肉影响怀孕吗
- 下一篇: 无精症的怎么治疗