日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

【Linux系统编程学习】信号、信号集以其相关函数

發布時間:2023/11/30 linux 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【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: 設置定時器的屬性
struct itimerval { // 定時器的結構體struct timeval it_interval; // 每個階段的時間,間隔時間struct timeval it_value; // 延遲多長時間執行定時器 };struct timeval { // 時間的結構體time_t tv_sec; // 秒數 suseconds_t tv_usec; // 微秒 };
  • 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
int sigfillset(sigset_t *set);
  • 功能:將信號集中的所有的標志位置為1
  • 參數:set:傳出參數,需要操作的信號集
  • 返回值:成功返回0, 失敗返回-1
int sigaddset(sigset_t *set, int signum);
  • 功能:設置信號集中的某一個信號對應的標志位為1,表示阻塞這個信號
  • 參數:
    - set:傳出參數,需要操作的信號集
    - signum:需要設置阻塞的那個信號
  • 返回值:成功返回0, 失敗返回-1
int sigdelset(sigset_t *set, int signum);
  • 功能:設置信號集中的某一個信號對應的標志位為0,表示不阻塞這個信號
  • 參數:
    - set:傳出參數,需要操作的信號集
    - signum:需要設置不阻塞的那個信號
  • 返回值:成功返回0, 失敗返回-1
int sigismember(const sigset_t *set, int signum);
  • 功能:判斷某個信號是否阻塞
  • 參數:
    - 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系统编程学习】信号、信号集以其相关函数的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。