1. 程式人生 > >linux信號基本概念及如何產生信號

linux信號基本概念及如何產生信號

arch track point 原型 lock signal函數 操作系統 get 如果

linux信號基本概念及如何產生信號

摘自:https://blog.csdn.net/summy_j/article/details/73199069

2017年06月14日 09:34:21 閱讀數:4131 標簽: linux信號 更多個人分類: Linux版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/summy_J/article/details/73199069

閱前須知


本文的主要內容有:

1.信號的基本概念(包括進程對信號的3種處理方式)

2.特殊信號舉例:寫代碼證明信號存在,並實現信號的簡單捕捉

3.如何產生一個信號(代碼舉例:mykill的實現)

其中拓展知識有:

1.前臺進程與後臺進程(代碼舉例)

2.核心轉儲core dumped的概念及其在代碼調試中的作用(代碼舉例)

——>全篇閱讀大概需要5分鐘<——


信號的基本概念


首先,我們可以用kill -l命令查看系統中定義的信號列表:

技術分享圖片

乍一看,好像有64種信號,但如果仔細觀察,你就會發現並非如此。沒有32和33號信號。一共只有62個信號。。。

說實話,你是不是和博主剛開始一樣被騙了>.<

我們可以看到,每個信號都有一個編號和一個宏定義名稱,這些宏定義可以在頭文件signal.h中找到。

其中編號34以上的是實時信號,34以下的信號是普通信號。而這些信號各自在什麽條件下產生,默認的處理動作是什麽,在signal(7)中都有詳細說明,在命令行上輸入man 7 signal:

技術分享圖片

知道了什麽是信號,那麽為什麽有信號,信號是誰發送給誰的呢,怎麽發送?

答案很簡單,想想我們在日常生活中也有很多信號,比如常見的紅綠燈信號。所以linux中的信號也是類似的。它無非是想提供一個機制在需要的時候告訴某個進程該怎樣做。是一種規定,便於系統操作。就像我們都知道”紅燈停,綠燈行“一樣。

信號的發送者有很多,比如終端驅動程序,進程,系統。而接收者大多是一個進程。

那麽怎麽做就是給某進程發送一個信號呢?事實上,給進程發一個信號就是修改目標進程pcb結構體中的關於信號的字段(讓進程記錄此信號),想一想,用什麽數據結構可以解決這個問題呢?

答案也很簡單,進程是否接收到信號本身是一個原子問題。它要麽收到,要麽沒收到。所以可以用位圖

來表示進程是否收到信號,只需要修改一個比特位(操作系統完成):收到信號就置1。如果有小夥伴不了解位圖的概念,可以戳這裏:STL容器BitSet(位圖)——1道騰訊筆試題的正確打開方式

進程收到信號後,其可選的處理動作有以下三種:

  1. 忽略此信號。

  2. 執?行該信號的默認處理動作(終止該信號)。

  3. 提供?個信號處理函數(自定義動作),要求內核在處理該信號時切換到用戶態執行這個處理函數,這種方式稱為捕捉(Catch)一個信號。


Code(特殊信號舉例)


知道了信號的基本概念,接下來我們通過代碼來感受信號的真實存在性。

以 2號信號SIGINT:Ctrl + C為例。

代碼:寫一個死循環程序,並用進程命令查看。觀察輸入Ctrl + C前後系統中的進程變化

技術分享圖片

可以看到,在終端按下Ctrl + C產生一個硬件中斷,向進程發送了2號信號,系統中pid為4940的進程(sig)被終止。


知識拓展1:前臺進程與後臺進程

以上邊程序為例,我們在運行程序時在命令後邊加一個”&“符號,再用命令查看系統中的進程,就會變成這樣:

技術分享圖片

圖中我們可以看到,加”&“運行程序後,系統發送了一個編號,編號後是對應進程的pid。用命令查看發現系統中確實多了一個編號為pid的進程。而且無法用Ctrl + C終止它。

其實這就是後臺進程,一個程序運行命令後面加個&可以放到後臺運行。形成後臺進程,對於前臺進程與後臺進程,我們需要知道以下幾點:

1.用命令查看時,發現後臺進程STAT狀態欄是R,所以有+表示前臺進程,無+表示後臺進程。

技術分享圖片

2.Ctrl-C產生的信號只能發給前臺進程。因為後臺進程使Shell不必等待進程結束就可以接受新的命令,啟動新的進程。而前臺進程運行時占用SHELL,它運行的時候SHELL不能接受其他命令。

技術分享圖片

3.Shell可以同時運行一個前臺進程和任意多個後臺進程。

技術分享圖片

4.前臺進程在運行過程中用戶隨時可能按下Ctrl-C而產生一個信號,也就是說該進程的用戶空間代碼執行到任何地方都有可能收到SIGINT信號而終止,所以信號相對於進程的控制流程來說是異步(Asynchronous)的。

後臺進程也不是任一進程都能做,要看實際情況。一般來說,如果某進程不需從鍵盤輸入輸出(交互少的)或者執行所需時間較長的話,就比較合適做後臺進程。


為了證明ctrl+c就對應2號信號,接下來利用信號捕捉函數來簡單實現對2號信號的捕捉。

代碼:加入signal函數,它是一個信號捕捉函數,包在signal.h頭文件中。

技術分享圖片

執行結果:

技術分享圖片


信號的產生


首先明確信號的4種產生條件:

1.通過終端按鍵(組合鍵)產生信號

2.硬件異常產生的信號

3.調用系統函數向進程發信號

4.由軟件條件產生信號

接下來,詳細說明每種產生條件的含義和方法。

————通過終端按鍵(組合鍵)產生信號————

其實這種方式上面已經講過一種了,就是Ctrl+C組合鍵。並且其對應信號為2號SIGINT

其實還有很多種通過組合鍵產生的信號,比如:Ctrl+\ 它對應的是3號信號SIGQUIT:

技術分享圖片

那麽2號信號和3號信號都是終止進程的,他們有什麽不同呢?區別就在這裏:

技術分享圖片

如果去掉信號捕捉,鍵入Ctrl+\就會發現後邊多了一個core dumped,這是什麽鬼?

所以SIGINT的默認處理動作是終止進程,而SIGQUIT的默認處理動作是終止進程並且Cor Dump。下面解釋什麽是 Core Dump。


知識拓展2:核心轉儲core dumped

概念:當?個進程要異常終止時,可以選擇把進程的用戶空間內存數據全部保存到磁盤上,文件名通常是core,這叫做Core Dump。也叫核心轉儲,幫助開發者進行調試,在程序崩潰時把內存數據dump到硬盤上,讓gdb識別

一個進程允許產生多大的core文件取決於進程的 Resource Limit(這個信息保存在PCB中)。默認是不允許產生core文件的,因為core文件中可能包含用戶密碼等敏感信息,不安全。

用ulimit -a命令查看系統中的軟硬件資源限制

技術分享圖片
其中core file size = 0,也就印證了上邊的說法.
上邊還有其他資源的限制,比如:
硬盤swap分區:用於內存數據換入換出的分區
max locked memory:不允許換出的內存數據,就被鎖住

但我們在開發調試階段可以用ulimit命令改變這個限制, 允許產生core文件:ulimit -c 1024,允許core?件最大為1024K

技術分享圖片

更改後再次運行程序就可以看到core文件,其文件名後邊的數字就是進程的pid號。

技術分享圖片

進程異常終止通常是因為有 Bug,比如非法內存訪問導致段錯誤,事後可以用調試器檢查core文件以查清錯誤原因,這叫做 Post-mortem Debug(事後調試)。

技術分享圖片

如圖,輸入命令就可以直接定位到是SIGQUIT信號引起的進程退出。


這裏只講這一種,有興趣的小夥伴可以再百度下其他組合鍵產生的信號。

————-硬件異常產生的信號————

硬件異常產生信號是由硬件檢測到並通知內核,然後內核向當前進程發送的信號。

例如當前進程執行了除以0的指令,CPU的運算單元會產生異常。內核將這個異常解釋為SIGFPE信號發送給進程。

再?如當前進程訪問了非法內存地址,MMU會產?生異常。內核將這個異常解釋為SIGSEGV信號(11號)發送給進程:

加入引起段錯誤的代碼:

技術分享圖片

運行:

技術分享圖片

這種硬件異常同樣會引起核心轉儲:

技術分享圖片

所以我們可以知道:windows下程序崩潰也是因為進程收到了信號

而且有了信號的概念就可以很好理解C++中的異常了。

————調用系統函數向進程發信號————

系統中定義了3個函數來給進程發送信號。

1.Kill命令(用kill函數實現):可以給任意進程發送任意信號(功能很強大)

比如繼續運行剛才的死循環程序,用kill命令也可以向其發送3號SIGQUIT信號終止它。
技術分享圖片

命令行上輸入指令man 2 kill就可以看到函數kill(系統調用接口)的實現:

技術分享圖片

下面是它兩個參數不同值所表示的含義:

技術分享圖片

下來利用kill函數實現仿kill命令的mykill命令:

step1:利用kill函數實現mykill.c
技術分享圖片

step2:寫一個進程
技術分享圖片

用mykill向進程發送信號:
技術分享圖片

這裏可以發現,9號信號是不能被捕捉的。

2.raise函數:給當前進程發送指定的信號(?己給?己發信號)。

函數原型:
技術分享圖片

執行效果:

技術分享圖片

3.abort函數(stdlib.h):自己給自己發送signal abort(6)號信號(終止進程)

函數原型:void abort(void)

執行效果:

技術分享圖片

捕捉後進程會終止掉,不會頻繁打印get a singal

————由軟件條件產生信號 ————

SIGPIPE和SIGALRM信號都是由軟件條件產生的信號。以alarm函數 和SIGALRM信號為例。

函數原型: unsigned int alarm(unsigned int seconds),在頭文件unistd.h中。

作用機制:調用alarm函數可以設定?個鬧鐘,也就是告訴內核在seconds秒之後給當前進程發SIGALRM信號, 該信號的默認處理動作是終?當前進程。這個函數的返回值是0或者是以前設定的鬧鐘時間還余下的秒數。

舉個栗子

某人要小睡一覺,設定鬧鐘為30分鐘之後響,20分鐘後被別人吵醒了,但還想多睡一會兒。於是重新設定鬧鐘為15分鐘之後響,“以前設定的鬧鐘時間還余下的時間”就是10分鐘。

如果seconds值為0,表示取消以前設定的鬧鐘,函數的返回值仍然是以前設定的鬧鐘時間還余下的秒數

鬧鐘:在一段時間之後才產生的信號(這個機制是不是和sleep函數有點像呢。。。)

代碼:

技術分享圖片

執行結果:

技術分享圖片


The End


信號產生後,在進程pcb中如何組織存儲也是值得研究的問題。博主的下一篇博客中也會有介紹,敬請期待。

linux信號基本概念及如何產生信號