一個重要且實用的signal---SIGCHLD

https://blog.csdn.net/lyztyycode/article/details/78150805

SIGCHLD(修改)

因為筆者之前的文章里面有錯誤,今天發現,立馬做個修改。在下面我的一段關于sigchld信號相對于直接調用wait函數的好處時,我說調用wait函數要一直檢測子進程是否執行完其實是錯誤的, wait是阻塞函數,當主進程調用wait函數的時,主進程處于阻塞狀態,并沒有一直檢測的動作,他只是在等待,假設我們有三個子進程(編號1,2,3)假設2,3進程先執行完,然而1號子進程還沒有執行完,那么主進程的wait就一直處于阻塞狀態,這時候2,3可能產生僵尸進程。這里sigchld和wait的區別就很明顯了。
先來看看信號的基本概念:
信號kill-l查看linux信號及其宏定義編號,其中1~31非實時編號(發送的信號可能丟失,不支持信號排隊),31~64實時信號,發送的信號都會被接收(支持信號排隊)
信號的定義在/usr/include/bits/signum.h
1.信號是軟件中斷
2.信號是異步信號
3.信號的來源:
(1)、硬件來源:主要是硬件的驅動產生,如鍵盤,鼠標等
(2)、軟件來源:主要是一些信號函數、比如kill()、raise()、alarm()、setitimer()等函數,軟件來源包括一些非法運算等操作,軟件設置條件(gdb調試),信號由內核產生
#信號的處理
進程會采取三種方式響應和處理信號
1.忽略信號,sigkill和sigstop永遠不被忽略,忽略硬件異常、進程啟動時,sigusr1和sigusr2被忽略
2.執行默認操作
3.捕獲信號。告訴內核信號出現時調用自己的處理函數,SIGKILL和SIGSTOP不能被捕獲
#信號登記
void(*signal(int signo,void (*func)(int))(int)
signo--要登記的信號編號或者信號宏
func--信號處理函數指針、忽略信號(SIG_IGN)、默認信號(SIG_DEF)
今天我就不針對每個信號詳細介紹了,你也沒必要知道那么多信號,今天介紹一個很重要的信號,SIGCHLD這個信號的作用如下:
SIGCHLD?子進程狀態發生變化產生該信號(子進程運行結束)父進程調用wait函數,回收子進程的進程表項,task_struct結構體。有了這個信號父進程不需要處于阻塞狀態,任然可以干其他事情,當子進程結束時發送一個SIGCHLD信號給父進程,父進程調用wait回收子進程,避免僵尸進程的產生,提高了資源利用率。

再了解這個信號之前,先來簡單了解一下wait()函數:

pid_t wait(int *status)//狀態
<unistd.h>
<sys/wait.h>
等待子進程退出并回收,防止僵尸進程(子進程運行結束,但是內核中的task_struct沒有釋放)產生,凡是調用wait()就會阻塞,父進程等待,定時檢查子進程是否執行完畢
返回子進程id
pid_t waitpid(pid_t pid, int *status, int options);
成功返回子進程id,這是wait的非阻塞版本。
wait和waitpid區別:
1.在一個子進程終止前,wait使調用者阻塞
2.waitpid有一個選擇項,可以使調用者不阻塞
3。waitpid可以等待指定的一個子進程,wait等待所有的子進程,返回任意一個種植的子進程狀態。
子進程在運行中有暫停信號如果想要顯示暫停信號的信號碼不能使用wait()要用waitpid()
waitpid的宏WNOHANG(非阻塞)WUNTRACED(監聽信號)

我們處理僵尸進程有兩種方式:
1.kill -9 父進程 讓init進程回收僵尸進程
2.wait() 和 waitpid()讓父進程等待回收子進程
下面我們來使用信號實現解決和避免僵尸進程的第三種的方式:
[cpp]?view plaincopy
  1. #include<stdio.h>??
  2. #include<stdlib.h>??
  3. #include<sys/wait.h>??
  4. #include<signal.h>??
  5. void?sig_handler(int?signo)??
  6. {??
  7. ????printf("child?process??%d?stop\n",?signo);??
  8. ??
  9. }??
  10. void?out(int?n)??
  11. {??
  12. ????for(int?i?=?0;?i?<?n;?++i)??
  13. ????{??
  14. ????????printf("%d?out?%d\n",?getpid(),?i);??
  15. ????????sleep(1);??
  16. ????}??
  17. }??
  18. int?main(void)??
  19. {??
  20. ????if(signal(SIGCHLD,?sig_handler)?==?SIG_ERR)??
  21. ????{??
  22. ????????perror("sigchld?error");??
  23. ????????exit(1);??
  24. ????}??
  25. ????pid_t?pid?=?fork();??
  26. ????if(pid?<?0)??
  27. ????{??
  28. ????????perror("fork?error");??
  29. ????????exit(1);??
  30. ????}??
  31. ????else?if(pid?>?0)??
  32. ????{??
  33. ????????out(100);??
  34. ????}??
  35. ????else??
  36. ????{??
  37. ????????out(20);??
  38. ????}??
  39. ????return?0;??
  40. }??

這段代碼使用signal系統調用來捕獲信號,我們在signal里面注冊了SIGCHLD信號,程序中我們讓子進程現執行完,然后捕獲子進程執行完畢的信號。
下面是運行結果部分截圖:

這里進程pid3546為父進程3547為子進程
我們再次運行程序來觀察程序的運行狀態,把程序編譯gcc signal_sigchld.c -o child
運行程序./child
使用命令ps -aux|grep child 來觀察程序運行狀態,下面是結果截圖
你可以看到父子進程在子進程運行到20以前都處于S+即運行狀態,當子進程到達20的時候,signal捕獲到子進程退出的信號SIGCHLD
這時候子進程狀態變為Z+即僵尸進程。

所以我們說了那么多,為什么SIGCHLD沒有處理這個僵尸進程呢,這里我們要搞清楚,SIGCHLD只是子進程在運行結束的時候產生的一i個信號,我們要想處理這個僵尸狀態,還是要用到上面說的兩種方式。最好就是父進程調用wait(),你可能有要問,既然都要用到wait,那抹干嗎多此一舉使用信號呢?首先要知道父進程調用wait以后處于阻塞狀態,父進程不能干其他事情,使用效率降低,資源利用率低下,增加了開銷,而調用信號以后,當子進程執行完畢以后,自動產再生一個信號給父進程,父進程收到信號以后就調用wait揮手子進程沒有釋放的資源。這樣我的感覺就是子進程化被動為主動。父進程的工作也輕松了不少,可以做自己想做的事情。

所以為了避免僵尸進程的產生我們修改上面的代碼中的sig_handler函數如下:
[cpp]?view plaincopy
  1. void?sig_handler(int?signo)??
  2. {??
  3. ????printf("child?process??%d?stop\n",?signo);??
  4. ????wait(0);??
  5. }??
當父進程捕獲到SIGCHLD后調用wait。
按照上述步驟重新編譯,運行,用ps觀察進程運行狀態:
上面是子進程運行到20之前,下面看子進程運行完畢,父進程捕獲到SIGCHLD以后

這里你發現子進程沒有顯示,是因為紫禁城已經被回收釋放掉了。
這就是處理僵尸進程的第三種方式。
也是一種異步處理方式。



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383715.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383715.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383715.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據結構實驗之鏈表七:單鏈表中重復元素的刪除

https://blog.csdn.net/blessingxry/article/details/794455111.知識點&#xff1a;逆序建立鏈表&#xff0b;節點刪除 2.題意&#xff1a;按照數據輸入的相反順序&#xff08;逆位序&#xff09;建立一個單鏈表&#xff0c;并將單鏈表中重復的元素刪除&#xff08;值相同的元素…

Python3函數和代碼復用

函數的定義 def 函數名([參數列表]):注釋函數體注意事項 函數形參不需要聲明類型&#xff0c;可以使用return語句在結束函數執行的同時返回任意類型的值&#xff0c;函數返回值類型與return語句返回表達式i的類型一致 即使該函數不需要接受任何參數&#xff0c;也必須保留一堆…

一文說盡C++賦值運算符重載函數(operator=)

http://www.cnblogs.com/zpcdbky/p/5027481.html在前面&#xff1a;關于C的賦值運算符重載函數(operator)&#xff0c;網絡以及各種教材上都有很多介紹&#xff0c;但可惜的是&#xff0c;內容大多雷同且不全面。面對這一局面&#xff0c;在下在整合各種資源及融入個人理解的基…

Python a和a[:]的區別

簡單來講a[:]是深復制&#xff0c;a是淺復制&#xff0c;相當于賦值a的話是賦值了指針&#xff0c;賦值a[:]相當于復制了a對應的那段空間 例如&#xff1a; a [1,1,1,1,1,1]for x in a:if x1:a.remove(x)print(a)運行結果&#xff1a; remove操作是移除序列中第一個x元素。…

約瑟夫環(c語言程序完整版)

https://blog.csdn.net/m_hahahaha1994/article/details/51742453約瑟夫環&#xff08;約瑟夫問題&#xff09;是一個數學的應用問題&#xff1a;已知n個人&#xff08;以編號1&#xff0c;2&#xff0c;3…n分別表示&#xff09;圍坐在一張圓桌周圍。從編號為k的人開始報數&am…

Linux系統【二】exec族函數及應用

文件描述符 文件描述符表是一個指針數組&#xff0c;文件描述符是一個整數。 文件描述符表對應的指針是一個結構體&#xff0c;名字為file_struct&#xff0c;里面保存的是已經打開文件的信息 需要注意的是父子進程之間讀時共享&#xff0c;寫時復制的原則是針對物理地址而言…

白話C++系列(27) -- RTTI:運行時類型識別

http://www.cnblogs.com/kkdd-2013/p/5601783.htmlRTTI—運行時類型識別 RTTI&#xff1a;Run-Time Type Identification。 那么RTTI如何來體現呢&#xff1f;這就要涉及到typeid和dynamic_cast這兩個知識點了。為了更好的去理解&#xff0c;那么我們就通過一個例子來說明。這個…

使用頭文件的原因和規范

原因 通過頭文件來調用庫功能。在很多場合&#xff0c;源代碼不便&#xff08;或不準&#xff09;向用戶公布&#xff0c;只 要向用戶提供頭文件和二進制的庫即可。用戶只需要按照頭文件中的接口聲明來調用庫 功能&#xff0c;而不必關心接口怎么實現的。編譯器會從庫中提取相應…

轉圈踢人問題

https://www.cnblogs.com/lanxuezaipiao/p/3339603.html 有N個人圍一圈依次報數&#xff0c;數到3的倍數的人出列&#xff0c;問當只剩一個人時他原來的位子在哪里&#xff1f; 解答&#xff1a;經典的轉圈踢人問題&#xff0c;好吧專業一點&#xff0c;約瑟夫環問題&#xff0…

Linux系統【三】回收子進程

孤兒進程 父進程先于子進程結束&#xff0c;則子進程成為孤兒進程&#xff0c;子進程的父進程成為init進程&#xff0c;則稱init進程領養孤兒進程。現在好像是用戶進程中的system進程。 僵尸進程 進程終止&#xff0c;父進程不進行回收&#xff0c;自己成殘留資源(PCB)存放在…

string類的基本實現

https://blog.csdn.net/qq_29503203/article/details/52265829在面試中面試官常常會讓你寫出string類的基本操作&#xff0c;比如&#xff1a;構造函數&#xff0c;析構函數&#xff0c;拷貝構造等等.下面是除此之外的一些操作&#xff0c;希望可以幫助你更好的理解string以便以…

Python3常用數據結構

Python3中有三種組合數據類型&#xff0c;分別為&#xff1a; 序列類型&#xff1a;字符串&#xff08;str&#xff09;、元組&#xff08;tuple&#xff09;、列表&#xff08;list&#xff09;集合類型&#xff1a;集合&#xff08;set&#xff09;映射類型&#xff1a;字典…

Linux C++ 回射服務器

http://blog.csdn.net/qq_25425023/article/details/53914820回射服務器就是服務端將客戶端的數據發送回去。我實現的回射服務器返回增加了時間。服務端代碼&#xff0c;可以很容易看懂&#xff1a;[cpp] view plaincopy#include <sys/socket.h> #include <stdio.h&g…

TCP第四次揮手為什么要等待2MSL

當客戶端進入TIME-WAIT狀態的時候(也就是第四次揮手的時候)&#xff0c;必須經過時間計數器設置的時間2MSL(最長報文段壽命)后&#xff0c;才能進入關閉狀態&#xff0c;這時為什么呢&#xff1f;&#xff1f;&#xff1f; 這最主要是因為兩個理由&#xff1a; 1、為了保證客戶…

計算機網絡【一】概述+OSI參考模型

網絡概述 局域網:覆蓋范圍小(100m以內)&#xff0c;自己花錢買設備&#xff0c;帶寬固定(10M,100M,1000M)&#xff0c;自己維護&#xff08;接入層交換機直接連接電腦、匯聚層交換機直接連接接入層交換機&#xff09; 廣域網:距離遠&#xff0c;花錢買服務&#xff0c;租帶寬&…

單鏈表逆序的多種方式

https://www.cnblogs.com/eniac12/p/4860642.htmltemplate<class T> void List<T>::Inverse() {if(first NULL) return;LinkNode<T> *p, *prev, *latter; p first->link;   // 當前結點prev NULL;   // 前一結點l…

Linux系統【四】進程間通信-管道

進程間通信&#xff08;IPC Interprocess Communication&#xff09; 進程和進程之間的通信只能通過內核&#xff0c;在內核中提供一塊緩沖區進行通信。內核提供的這種機制叫做IPC 在進程間完成數據傳輸需要借助操作系統提供的特殊方法&#xff0c;如&#xff1a;文件&#xf…

單鏈表各種操作詳解

#include "stdio.h" #include "stdlib.h"#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0#define MAXSIZE 20 /* 存儲空間初始分配量 */typedef int Status;/* Status是函數的類型,其值是函數結果狀態代碼&#xff0c;如OK等 */ typedef int…

Linux系統【五】進程間通信-共享內存mmap

mmap函數 #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);參數&#xff1a; void *addr建立映射區的首地址&#xff0c;由Linux內核指定&#xff0c;所以我們直接傳遞NULL。也就是說雖然這是一個參宿但是并不…

socket編程 -- epoll模型服務端/客戶端通信的實現

https://blog.csdn.net/y396397735/article/details/50680359 本例實現如下功能&#xff1a; 支持多客戶端與一個服務端進行通信&#xff0c;客戶端給服務端發送字符串數據&#xff0c;服務端將字符串中小寫轉為大寫后發送回客戶端&#xff0c;客戶端打印輸出經轉換后的字符串。…