【Linux】—— Linux中進程信號1

信號的概念

生活中的信號

  • 爲了理解一下信號這個概念,我們來舉一些生活中的例子
  • 比如:你在淘寶上買了一件商品,你在等這些商品來,雖然這些商品沒有來,但是你卻知道他來了之後你應該怎麼做。之後當我收到快遞公司給我通知我們的快遞到了之後,我們有些人會立刻去拿,有的可能選擇過一會去拿,我們可以理解爲"在合適的時間去完成取快遞這件事"
  • 再舉個栗子,平時我們睡覺之前會設置一個鬧鐘,定時叫我們起牀,雖然這個鬧鐘沒響,但是我們也知道鬧鐘響了我們應該怎麼做。再鬧鐘響了之後,我們要處理這個事件,有三種方式:1.執行默認動作(把鬧鐘關了,然後起牀)。2、執行自定義動作(把鬧鐘關了,繼續睡覺)。3.忽略鬧鐘(不關鬧鐘讓它繼續響)。

技術應用角度的信號

信號其實我們也見過,當我們在shell上寫出一個死循環退不出來的時候,只需要一個組合鍵,ctrl+c,就可以解決了,這就是一個信號,但是真正的過程並不是那麼簡單的。

  • 1、當用戶按下這一對組合鍵時,這個鍵盤輸入會產生一個硬件中斷,如果CPU正在執行這個進程的代碼時,則該進程的用戶代碼先暫停執行,用戶從用戶態切換到內核態處理硬件中斷
  • 2、終端驅動程序將這一對組合鍵翻譯成一個SIGINT信號記在該進程的PCB中(也就是發送了一個SIGINT信號給該進程)
  • 3、當某個時刻要從內核態回到該進程的用戶·空間代碼繼續執行之前,首先處理PCB中的信號,發現有一個SIGINT信號需要處理,而這個信號的默認處理方式是終止進程,所以直接終止進程,不再返回用戶空間執行代碼。

注意:ctrl+c只能終止前臺進程。一個命令可以加&可以將進程放在後臺執行,這樣shell就不必等待進程結束就可以接收新的命令,啓動新的進程。

信號的概念

  • 信號是進程之間實踐異步通知的一種方式,屬於軟中斷

通過我們剛剛舉的生活中信號的例子和我們講到的技術應用角度的信號,我們類比在進程間的信號,我們同樣可以得到以下結論:

  • 1、進程必須學會辨認信號,必須知道遇到信號之後應該如何處理。
  • 2、進程收到信號之後不一定會立即處理,而是等到合適的時間去處理,但是他記住了自己收到了一個信號,等待處理。
  • 3、進程收到信號之後處理方式有三種:1.執行默認動作 2.執行自定義動作 3.忽略該信號
  • 4、進程儲存信號的地方是進程的PCB,儲存信號可以使用一個int位圖(有該信號對應位置爲一,沒有爲0).
  • 5、操作系統向進程法信號,其實是到進程的PCB的存儲信號位圖中找到對應的信號所在的位置將該位的0變爲1,因此其實可以形象說操作系統是想進程寫信號

系統信號列表

  • kill -l 查看系統定義的信號列表
    信號列表
  • 編號34以上的都爲實時信號,我們這裏只討論編號34一下的信號
  • 這些信號各自在什麼條件下產生,默認的處理方式是什麼在signal(7)中都有詳細說明,我們可以通過命令 man 7 signal
    signal(7)

產生信號的方式

1.通過終端按鍵產生信號

SIGINT的默認處理動作是終止進程,SIGQUIT的默認處理動作是終止進程並且Core Dump,核心轉儲,這個概念我們在之前博客 進程控制 進程等待部分我們提到過,現在我們來驗證一下。
Core Dump

  • 首先解釋什麼是Core Dump。當一個進程要異常終止時,可以選擇把進程的用戶空間內存數據全部 保存到磁盤上,文件名通常是core,這叫做Core Dump
  • 進程異常終止通常是因爲有Bug,比如非法內存訪問導致段錯誤,事後可以用調試器檢查core文件以查清錯誤原因,這叫做Post-mortem Debug(事後調試)。
  • 一個進程允許產生多大的core文件取決於進程的Resource Limit(這個信息保存 在PCB中)。默認是不允許產生core文件的,因爲core文件中可能包含用戶密碼等敏感信息,不安全。在開發調試階段可以用ulimit命令改變這個限制,允許產生core文件。 首先用ulimit命令改變Shell進程的Resource Limit,允許core文件最大爲1024K: $ ulimit -c1024
    Core Dump
  • 寫一個死循環程序
    在這裏插入圖片描述
  • 前臺運行該程序,然後再終端鍵入Ctrl + \,終止該程序
    信號
  • ulimit命令改變了Shell進程的Resource Limit,test進程的PCB由Shell進程複製而來,所以也具 有和Shell進程相同的Resource Limit值,這樣就可以產生Core Dump了。

2.系統函數向進程發信號

  • 首先我們運行我們剛剛編寫的死循環進程,在通過kill命令向其發送SIGSEGV信號
    段信號

  • 29225是test進程的id。之所以要再次回車才顯示 Segmentation fault(段錯誤) ,是因爲在29225進程終止掉之前已經回到了Shell提示符等待用戶輸入下一條命令,Shell不希望Segmentation fault信息和用 戶的輸入交錯在一起,所以等用戶輸入命令之後才顯示

  • 指定發送某種信號的kill命令可以有多種寫法,上面的命令還可以寫成 kill -SIGSEGV 29225kill -11 29225 , 11是信號SIGSEGV的編號。以往遇到的段錯誤都是由非法內存訪問產生的,而這個程序本身沒錯,給它發SIGSEGV也能產生段錯誤。

  • kill命令是調用kill函數實現的。kill函數可以給一個指定的進程發送指定的信號。raise函數可以給當前進程發送指定的信號(自己給自己發信號)。

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
這兩個函數都是成功返回0,錯誤返回-1。
  • abort函數使當前進程接收到信號而異常終止。
#include <stdlib.h>
void abort(void);
就像exit函數一樣,abort函數總是會成功的,所以沒有返回值。

3.由軟件條件產生信號

SIGPIPE是一種由軟件條件產生的信號,在“管道”中已經介紹過了。這裏主要主要介紹alarm函數SIGALRM信號

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
調用alarm函數可以設定一個鬧鐘,也就是告訴內核在seconds秒之後給當前進程發SIGALRM信號, 
該信號的默認處理動作是終止當前進程。

這個函數的返回值是0或者是以前設定的鬧鐘時間還餘下的秒數。打個比方,某人要小睡一覺,設定鬧鐘爲30分鐘之後響,20分鐘後被人吵醒了,還想多睡一會兒,於是重新設定鬧鐘爲15分鐘之後響,“以前設定的鬧鐘時間還餘下的時間”就是10分鐘。如果seconds值爲0,表示取消以前設定的鬧鐘,函數的返回值仍然是以前設定的鬧鐘時間還餘下的秒數

  • 來段代碼驗證一下alarm函數
    alarm
  • 運行結果爲
    alarm

4.硬件異常產生的信號

  • 硬件異常被硬件以某種方式被硬件檢測到並通知內核,然後內核向當前進程發送適當的信號。

  • 例如當前進程執行了除以0的指令,CPU的運算單元會產生異常,內核將這個異常解釋 爲SIGFPE信號發送給進程。再比如當前進程訪問了非法內存地址,MMU會產生異常,內核將這個異常解釋爲SIGSEGV信號發送給進程。

  • 模擬一下野指針異常默認行爲下
    默認
    默認

  • 模擬野指針異常捕捉行爲
    默認行爲野指針
    在這裏插入圖片描述
    由此可以確認,我們在C/C++當中除零,內存越界等異常,在系統層面上,是被當成信號處理的。

總結思考一下

  • 上面所說的所有信號產生,最終都要有OS來進行執行,爲什麼?OS是進程的管理者
  • 信號的處理是否是立即處理的?在合適的時候
  • 信號如果不是被立即處理,那麼信號是否需要暫時被進程記錄下來?記錄在哪裏最合適呢?
  • 一個進程在沒有收到信號的時候,能否能知道,自己應該對合法信號作何處理呢?
    如何理解OS向進程發送信號?能否描述一下完整的發送處理過程?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章