【B站视频笔记】linux 进程间通信(ipc)信号(软中断信号)signal库函数、可靠信号和不可靠信号、信号集sigprocmask(信号掩码、信号递达Delivery、信号未决Pending)
【視頻教程】Linux信號詳解(可靠信號、不可靠信號、阻塞信號、信號處理函數)
【博文】Linux信號
文章目錄
- 背景
- 課程筆記
- 一、如何讓程序在后臺運行
- 1、加“&”符號
- 測試ky_ai_camera_engine_origin
- 2、采用fork
- 3、如何中止后臺運行中程序
- 二、signal信號
- 1、信號的基本概念
- 軟中斷測試
- 2、信號的類型
- 3、signal庫函數
- signal第三個參數可以這么玩
- 4、信號有什么用
- 5、信號應用示例
- 示例(book257.cpp)
- 三、發送信號 (kill函數)
- 四、信號更多的知識(20220123繼續)關于可靠信號與不可靠信號詳解
- 五、信號處理函數被中斷(新信號會插隊舊信號,同種信號不會插隊)
- 六、信號的阻塞(信號集概念)(讓信號集里的信號延遲到達【觸發】)(信號遞達Delivery、信號未決Pending)
- 七、信號signal拓展,功能更強的sigaction(但貌似也不能附加自定義消息)
背景
在真實的項目中,后臺服務程序不受終端控制,常駐內存中,沒有交互的界面。
周期性或通過事件喚醒的方式執行任務。(周期性指的是程序自發的,比如間隔一段時間就跑到某個文件目錄中看一看有沒有文件,如果有就上傳到服務器;事件喚醒指的是其他進程主動去通知該進程執行某項任務)
課程筆記
一、如何讓程序在后臺運行
在之前的章節中,如果要運行程序,在命令提示行下輸入程序名后回車,程序被執行,然后等待程序運行完成,在程序運行的過程中,也可以用Ctrl+c中止它。
在實際開發中,我們需要讓程序在后臺運行,沒有界面,沒有用戶輸入數據,例如socket服務端程序book250。
如果想讓程序在后臺運行,有兩種方法。
1、加“&”符號
如果想讓程序在后臺運行,執行程序的時候,命令的最后面加“&”符號。
如:./book250 &
程序就在后臺運行了。
在后臺運行的程序,用Ctrl+c無法中斷,并且就算終端退出了,程序仍在后臺運行。
如果終端退出了,后臺運行的程序將由系統托管。
在第一張圖中,book250的父進程是12178,第二張圖中,book250的父進程是1。
為了不影響接下來的學習,用killall book250指令讓book250程序退出。
測試ky_ai_camera_engine_origin
運行run.sh程序跑起來后,用ps -ef | grep xxx指令查看進程,第二個數字是子進程,第三個數字是父進程,可以一步一步查看父進程,但是一個父進程可能有幾個子進程,怎么查看父進程的子進程?(可以用ps --ppid 父進程號)
[root@RV1126_RV1109:/]# ps -ef | grep ky_ai_camera_engine root 1264 2648 0 20:46 pts/1 00:00:00 grep ky_ai_camera_engine root 3328 3327 99 20:41 pts/2 00:07:57 /userdata/kyai/ky_ai_camera_engine_origin -a /oem/etc/iqfiles -p /userdata/kyai/model/rv1109_rv1126/yolov5s_relu_rv1109_rv1126_out_opt.rknn -c /userdata/kyai/rtsp-nn.cfg -l /userdata/kyai/model/coco_80_labels_list.txt [root@RV1126_RV1109:/]# [root@RV1126_RV1109:/]# [root@RV1126_RV1109:/]# ps -ef | 3327 -sh: 3327: not found [root@RV1126_RV1109:/]# ps -ef | grep 3327 root 1508 2648 0 20:47 pts/1 00:00:00 grep 3327 root 3327 3229 0 20:41 pts/2 00:00:00 /bin/sh ./run.sh root 3328 3327 99 20:41 pts/2 00:09:10 /userdata/kyai/ky_ai_camera_engine_origin -a /oem/etc/iqfiles -p /userdata/kyai/model/rv1109_rv1126/yolov5s_relu_rv1109_rv1126_out_opt.rknn -c /userdata/kyai/rtsp-nn.cfg -l /userdata/kyai/model/coco_80_labels_list.txt [root@RV1126_RV1109:/]# [root@RV1126_RV1109:/]# [root@RV1126_RV1109:/]# [root@RV1126_RV1109:/]# ps -ef | grep 3229 root 1704 2648 0 20:48 pts/1 00:00:00 grep 3229 root 3229 3228 0 20:41 pts/2 00:00:00 -sh root 3327 3229 0 20:41 pts/2 00:00:00 /bin/sh ./run.sh [root@RV1126_RV1109:/]#用killall能殺死進程啊,我怎么之前試了半天殺不死??
[root@RV1126_RV1109:~]# killall ky_ai_camera_engine_origin [root@RV1126_RV1109:~]#2、采用fork
另一種方法是采用fork,主程序執行fork,生成一個子進程,然后父進程退出,留下子進程繼續運行,子進程將由系統托管。
在book250的main函數后增加以下代碼:
(需要包含一些庫,自己查詢)
重新編譯后執行book250,運行效果如下:(父進程號為1就表示在后臺運行了)
上圖中,20752是fork后的子進程,它的父進程號是1,是系統進程(親爹沒了,天地日月為父)。
3、如何中止后臺運行中程序
問題來了,程序在后臺運行了,離開了終端控制,用Ctrl+c上也無法中止,那怎么讓它停下來呢?暫時用一個笨方法,殺了它。
殺程序有兩個方法:
1)killall 程序名
killall book250執行效果
2)先用“ps -ef | grep 程序名”找到程序的進程編號,然后用“kill 進程編號”。
執行效果
老師:
程序在運行的過程中,用ctrl+c、kill、killall中止其本質是向程序發送信號,程序對這兩個信號的默認行為是程序中止運行。
在程序中,可以捕獲信號,編寫信息處理函數,即收到信號后執行的代碼。
(意思就是我們可以在程序中寫捕獲信號的接口,然后其他調用者去調用它)
二、signal信號
signal信號是Linux編程中非常重要的部分,接下來將詳細介紹信號的基本概念、實現和使用,和與信號的幾個系統調用(庫函數)。
signal信號是進程之間相互傳遞消息的一種方法,信號全稱為軟中斷信號,也有人稱作軟中斷,從它的命名可以看出,它的實質和使用很像中斷。
1、信號的基本概念
軟中斷信號(signal,又簡稱為信號)用來通知進程發生了事件。進程之間可以通過調用kill庫函數發送軟中斷信號。Linux內核也可能給進程發送信號,通知進程發生了某個事件(例如內存越界)。
注意,信號只是用來通知某進程發生了什么事件,無法給進程傳遞任何數據,進程對信號的處理方法有三種:
1)第一種方法是,忽略某個信號,對該信號不做任何處理,就象未發生過一樣。
2)第二種是設置中斷的處理函數,收到信號后,由該函數來處理。
3)第三種方法是,對該信號的處理采用系統的默認操作,大部分的信號的默認操作是終止進程。
軟中斷測試
在ubuntu下測試,測試發現,軟中斷觸發時,睡眠sleep會立刻結束,但是我開了個for循環,發現for循環不會立刻結束,幾乎不受影響
測試的時候還發現了這個問題。。為什么ubuntu64位下C語言for循環不能超過2147483647次?
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h>#include<time.h> #include<sys/time.h> #include<stdio.h>struct timeval tv;void EXIT(int sig) {gettimeofday(&tv,NULL);printf("exit開始計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);int count =0;//for(int i=0;i<2147483648;i++){ //nofor(int i=0;i<2147483647;i++){ //yes//count+=2; count+=0.1;}//printf("count:%d\n",count); printf("收到了信號%d\n",sig);gettimeofday(&tv,NULL);printf("exit結束計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);// 在這里添加釋放資源的代碼//exit(0); // 程序退出。 }int main(){for (int ii=0;ii<100;ii++) signal(ii,SIG_IGN); // 屏蔽全部的信號signal(SIGINT,EXIT); signal(SIGTERM,EXIT); // 設置SIGINT和SIGTERM的處理函數while (1){ // 一個死循環//printf("執行了一次\n"); //sleep(5);//printf("執行了二次\n"); //sleep(5);//printf("執行了三次\n"); gettimeofday(&tv,NULL);printf("main開始計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);int count =0;//for(int i=0;i<2147483648;i++){ //nofor(int i=0;i<2147483647;i++){ //yes//count+=2; count+=0.1;}//printf("count:%d\n",count);gettimeofday(&tv,NULL);printf("main結束計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);} }運行結果:
[yg@ubuntu /arnold_test/20220113_TEST_signal]1$ ./a.out main開始計算, 秒:1642822914,毫秒:985 main結束計算, 秒:1642822927,毫秒:194 (main interval 13s) main開始計算, 秒:1642822927,毫秒:194 ^Cexit開始計算, 秒:1642822929,毫秒:176 收到了信號2 exit結束計算, 秒:1642822940,毫秒:864 (exit interval 11s) main結束計算, 秒:1642822950,毫秒:20 (main interval 23s) main開始計算, 秒:1642822950,毫秒:20 ^Cexit開始計算, 秒:1642822955,毫秒:593 ^C收到了信號2 exit結束計算, 秒:1642822967,毫秒:9 (exit interval 12s) exit開始計算, 秒:1642822967,毫秒:9 ^C^C^C^C收到了信號2 exit結束計算, 秒:1642822978,毫秒:658 (exit interval 11s) exit開始計算, 秒:1642822978,毫秒:658 收到了信號2 exit結束計算, 秒:1642822991,毫秒:54 (exit interval 13s) main結束計算, 秒:1642822996,毫秒:605 (main interval 46s)結論:
當接收到信號后,會將主進程阻塞,去執行信號處理函數;在處理信號處理函數時,還收到多個相同信號,只按一個處理(為了增加可信度,我再在main函數里開個線程,測試一下)
我開多線程測試結論:
signal(SIGINT,EXIT); signal(SIGTERM,EXIT);這句代碼,只有它后面的的代碼才能生效,前面的代碼不會(在它后面開的線程也會被阻塞住) ;(貌似不能這么下定義,我測試發現,如果main和test_thread一起跑的時候,按下ctrl+c,那么main就停了,只有test_thread和exit在跑,然后再按一下,test_thread就停了,只有exit在跑(無論signal(SIGINT,EXIT); signal(SIGTERM,EXIT);是放在線程前還是后))(感覺這個有個問題就是signal觸發的時候它會把主進程給阻塞了,所以最好不要在signal處理函數里搞太多耗時的事情?)
附測試代碼:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h>#include<time.h> #include<sys/time.h> #include<stdio.h>#include <pthread.h>#define loop_num 5000000static int g_num = 0;struct timeval tv;void EXIT(int sig) {printf("g_num:%d\n", g_num); gettimeofday(&tv,NULL);printf("exit開始計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);int count =0;for(int i=0;i<loop_num;i++){ count+=1;printf("exit_count:%d\n",count);}//sleep(10);printf("exit結束計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);g_num++;// 在這里添加釋放資源的代碼//exit(0); // 程序退出。 }static void *Test(void *arg) {while (1){gettimeofday(&tv,NULL);printf("test_thread開始計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);int count =0;//for(int i=0;i<2147483648;i++){ //nofor(int i=0;i<loop_num;i++){ //yes//count+=2; count+=1;printf("test_count:%d\n",count);}//printf("count:%d\n",count);gettimeofday(&tv,NULL);printf("test_thread結束計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);} }int main(){for (int ii=0;ii<100;ii++) signal(ii,SIG_IGN); // 屏蔽全部的信號signal(SIGINT,EXIT); signal(SIGTERM,EXIT); // 設置SIGINT和SIGTERM的處理函數pthread_t test_thread;pthread_create(&test_thread, NULL, Test, NULL);//signal(SIGINT,EXIT); signal(SIGTERM,EXIT); // 設置SIGINT和SIGTERM的處理函數while (1){ // 一個死循環//printf("執行了一次\n"); //sleep(5);//printf("執行了二次\n"); //sleep(5);//printf("執行了三次\n"); gettimeofday(&tv,NULL);printf("main開始計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);int count =0;//for(int i=0;i<2147483648;i++){ //nofor(int i=0;i<loop_num;i++){ //yes//count+=2; count+=1;printf("main_count:%d\n",count);}gettimeofday(&tv,NULL);printf("main結束計算, 秒:%ld,毫秒:%ld\n", tv.tv_sec, tv.tv_usec/1000);}//signal(SIGINT,EXIT); signal(SIGTERM,EXIT); //前面while循環導致這句根本執行不到 }2、信號的類型
發出信號的原因很多,這里按發出信號的原因簡單分類,以了解各種信號:
(重點關注加粗的信號名,其他的信號老師開發也沒怎么用過。。。)
處理動作一項中的字母含義如下
3、signal庫函數
signal庫函數可以設置程序對信號的處理方式。
函數聲明:
sighandler_t signal(int signum, sighandler_t handler);參數signum表示信號的編號。(就是個宏定義,可直接用對應數字替代)
參數handler表示信號的處理方式,有三種情況:
1)SIG_IGN:忽略參數signum所指的信號。(用了這個參數后信號就相當于完全無效了)
2)一個自定義的處理信號的函數,信號的編號為這個自定義函數的參數。
3)SIG_DFL:恢復參數signum所指信號的處理方法為默認值。
程序員不關心signal的返回值。
signal第三個參數可以這么玩
調用signal函數后,相當于開啟了一個監聽子進程?同樣放在函數里調用也一樣,函數退出,監聽也就結束。。?這樣我們可以在信號執行默認操作前后摻雜我們自己的操作
4、信號有什么用
服務程序運行在后臺,如果想讓中止它,強行殺掉不是個好辦法,因為程序被殺的時候,程序突然死亡,沒有釋放資源,會影響系統的穩定,用Ctrl+c中止與殺程序是相同的效果。
如果能向后臺程序發送一個信號,后臺程序收到這個信號后,調用一個函數,在函數中編寫釋放資源的代碼,程序就可以有計劃的退出,安全而體面。
例如:(退出時判斷文件有沒關閉,沒關閉就關閉掉)
信號還可以用于網絡服務程序抓包等,這是較復雜的應用場景,暫時不介紹。
5、信號應用示例
在實際開發中,在main函數開始的位置,程序員會先屏蔽掉全部的信號。
for (int ii=0;ii<100;ii++) signal(ii,SIG_IGN);這么做的目的是不希望程序被干擾。然后,再設置程序員關心的信號的處理函數。
程序員關心的信號有三個:SIGINT、SIGTERM和SIGKILL。
程序在運行的進程中,如果按Ctrl+c,將向程序發出SIGINT信號,信號編號是2。
采用“kill 進程編號”或“killall 程序名”向程序發出的是SIGTERM信號,編號是15。
采用“kill -9 進程編號”向程序發出的是SIGKILL信號,編號是9,此信號不能被忽略,也無法捕獲,程序將突然死亡。
所以,程序員只要設置SIGINT和SIGTERM兩個信號的處理函數就可以了,這兩個信號可以使用同一個處理函數,函數的代碼是釋放資源。
示例(book257.cpp)
/** 程序名:book257.cpp,此程序用于演示用信號通知后臺服務程序退出。* 作者:C語言技術網(www.freecplus.net) 日期:20190525 */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h>void EXIT(int sig) {printf("收到了信號%d,程序退出。\n",sig);// 在這里添加釋放資源的代碼exit(0); // 程序退出。 }int main() {for (int ii=0;ii<100;ii++) signal(ii,SIG_IGN); // 屏蔽全部的信號signal(SIGINT,EXIT); signal(SIGTERM,EXIT); // 設置SIGINT和SIGTERM的處理函數while (1) // 一個死循環{sleep(10);} }運行效果
不管是用Ctrl+c還是kill,程序都能體面的退出。
三、發送信號 (kill函數)
Linux操作系統提供了kill命令向程序發送信號,C語言也提供了kill庫函數,用于在程序中向其它進程或者線程發送信號。
函數聲明:
int kill(pid_t pid, int sig);kill函數將參數sig指定的信號給參數pid 指定的進程。
參數pid 有幾種情況:
1)pid>0 將信號傳給進程號為pid 的進程。2)pid=0 將信號傳給和目前進程【相同進程組】的所有進程,常用于父進程給子進程發送信號,注意,發送信號者進程也會收到自己發出的信號。3)pid=-1 將信號廣播傳送給系統內所有的進程,例如系統關機時,會向所有的登錄窗口廣播關機信息。sig:準備發送的信號代碼,假如其值為零則沒有任何信號送出,但是系統會執行錯誤檢查,通常會利用sig值為零來檢驗某個進程是否仍在運行。返回值說明: 成功執行時,返回0;失敗返回-1,errno被設為以下的某個值。EINVAL:指定的信號碼無效(參數 sig 不合法)。EPERM:權限不夠無法傳送信號給指定進程。ESRCH:參數 pid 所指定的進程或進程組不存在。看到22:05,我先去看dbus進程間通信了,ly讓研究那個,貌似有點復雜
https://www.bilibili.com/video/BV145411a76x?p=2&spm_id_from=pageDriver
四、信號更多的知識(20220123繼續)關于可靠信號與不可靠信號詳解
不可靠信號可能會存在信號丟失
示例:
用killall+信號值+進程名給進程發信號
發現用killall -15 book10發送多次時,除第一次觸發了外,后面幾次信號都被合成為了一次
但如果用killall -34 book10發送多次時,每次信號都能正常觸發(不會丟失信號)
作者(吳哥)將這個信號值15稱為“不可靠信號”,將信號值34稱為“可靠信號”
五、信號處理函數被中斷(新信號會插隊舊信號,同種信號不會插隊)
示例:略
六、信號的阻塞(信號集概念)(讓信號集里的信號延遲到達【觸發】)(信號遞達Delivery、信號未決Pending)
另外:sigfillset()函數,跟sigaddset()類似,但它是一次性將所有信號添加進去,而sigaddset()只是單個信號值添加
從信號集set中sigdelset()刪除一個信號值
sigismember()判斷某個信號值在不在信號集set里面
sigprocmask()函數的參數(how的第三個SIG_SETMASK作者也不知道)
七、信號signal拓展,功能更強的sigaction(但貌似也不能附加自定義消息)
linux 信號 sigaction(是signal的擴展,在多線程應用中替代了signal)
總結
以上是生活随笔為你收集整理的【B站视频笔记】linux 进程间通信(ipc)信号(软中断信号)signal库函数、可靠信号和不可靠信号、信号集sigprocmask(信号掩码、信号递达Delivery、信号未决Pending)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 信号 sigaction(是
- 下一篇: linux 不同进程间能否传递指针?(不