Linux 行程信號
- 0 查看IPC(行程間通信)資源的指令
- 1 學習行程信號的程序
- 2 Linux 行程信號的基本概念
- 2.1 對信號的基本認知
- 3 Linux 行程信號的產生方式
- 4 Linux 行程信號的保存和處理
- 5 Linux 行程信號遞達
- 6 volatile關鍵字
0 查看IPC(行程間通信)資源的指令
ipcs -m : 查看共享記憶體

ipcs -s 查看信號量

ipcs -q 查看訊息佇列

洗掉行程間通信資源
- ipcrm -m : 查看共享記憶體
- ipcrm -q : 洗掉訊息佇列
- ipcrm -s : 洗掉信號量
1 學習行程信號的程序

2 Linux 行程信號的基本概念
2.1 對信號的基本認知
從生活角度來講
- 你在網上買了很多件商品,再等待不同商品快遞的到來,但即便快遞沒有到來,你也知道快遞來臨時,你該怎么處理快遞,也就是你能“識別快遞”
- 當快遞員到了你樓下,你也收到快遞到來的通知,但是你正在打游戲,需5min之后才能去取快遞,那么在在這5min之內,你并沒有下去去取快遞,但是你是知道有快遞到來了,也就是取快遞的行為并不是一定要立即執行,可以理解成“在合適的時候去取”,
- 在收到通知,再到你拿到快遞期間,是有一個時間視窗的,在這段時間,你并沒有拿到快遞,但是你知道有一個快遞已經來了,本質上是你“記住了有一個快遞要去取”
- 當你時間合適,順利拿到快遞之后,就要開始處理快遞了,而處理快遞一般方式有三種:1. 執行默認動作(幸福的打開快遞,使用商品)2. 執行自定義動作(快遞是零食,你要送給你你的女朋友)3. 忽略快遞(快遞拿上來之后,扔掉床頭,繼續開一把游戲)
- 快遞到來的整個程序,對你來講是異步的,你不能準確斷定快遞員什么時候給你打電話
從技術角度
用戶輸入命令,在Shell下啟動一個前臺行程,
- 用戶按下Ctrl-C ,這個鍵盤輸入產生一個硬體中斷,被OS獲取,解釋成信號,發送給目標前臺行程
- 前臺行程因為收到信號,進而引起行程退出
如下代碼


從這兩個角度來看,行程就是我,作業系統就是快遞員,信號就是快遞
注意
- Ctrl + c 產生的信號只能發給前臺行程,當 “命令” + & 可以把行程放到后臺運行,這樣 Shell 就不必等待行程結束就可以接受新的命令,啟動新的行程,
- Shell 可以同時運行一個前臺行程和任意多個后臺行程,只有前臺行程才能接受到 Crtl + c這樣的控制鍵產生的信號,使用 bg 可以把前臺行程轉為后臺行程 fg 可以把后臺行程轉為前臺行程
- 前臺行程在運行程序中用戶隨時可能按下 Ctrl-C 而產生一個信號,也就是說該行程的用戶空間代碼執行到任何地方都有可能收到 SIGINT 信號而終止,所以信號相對于行程的控制流程來說是異步(Asynchronous)的,
信號概念
- 信號是行程之間事件異步通知的一種方式,屬于軟中斷,
用kill -l命令可以察看系統定義的信號串列

- 每個信號都有一個編號和一個宏定義名稱,這些宏定義可以在signal.h中找到,例如其中有定 義 #define SIGINT 2
- 編號34以上的是實時信號,我們只討論編號34以下的信號,不討論實時信號,
信號處理常見方式
- 忽略此信號,
- 執行該信號的默認處理動作,例如 Ctrl + c 終止行程
- 提供一個信號處理函式,要求內核在處理該信號時切換到用戶態執行這個處理函式,這種方式稱為捕(Catch)一個信號,
3 Linux 行程信號的產生方式
- 通過終端按鍵產生信號
SIGINT的默認處理動作是終止行程,SIGQUIT的默認處理動作是終止行程并且Core Dump,現在我們來驗證一下,
Core Dump 核心轉儲
當一個行程例外終止時,可以選擇把行程的用戶空間記憶體資料的全部保存到磁盤上,叫 Core Dump,行程例外終止通常因為有Bug,比如非法存訪問導致段錯誤,事后可以用除錯器檢查 core 檔案以查清錯誤原因,這就叫做Post-mortem Debug(事后除錯),一個行程允許產生多大的 Core 檔案取決于行程的 resource Limit (保存在PCB里),默認是不允許產生core檔案的,因為core檔案中可能包含用戶密碼等敏感資訊,不安全,在開發除錯階段可以用ulimit命令改變這個限制,允許產生core檔案,首先用ulimit命令改變Shell行程的Resource Limit,允許core檔案最大為1024K:ulimit -c 1024




- 呼叫系統函式向行程發信號
在后臺執行死回圈程式,然后用kill命令給它發SIGSEGV信號,指定發送某種信號的kill命令可以有多種寫法,上面的命令還可以寫成 kill -SIGSEGV 行程id 或 kill -11 行程id , 11是信號SIGSEGV的編號,以往遇 到的段錯誤都是由非法記憶體訪問產生的,而這個程式本身沒錯,給它發SIGSEGV也能產生段錯誤,
還有3個系統呼叫函式可以實作向行程發信號,kill raise abort
kill
raise
abort
- 由軟體條件產生信號
SIGPIPE是一種由軟體條件產生的信號,在行程的通信的管道中已經介紹過了,本節主要介紹alarm函式和 SIGALRM信號,
呼叫alarm函式可以設定一個鬧鐘,也就是告訴內核在seconds秒之后給當前行程發SIGALRM信號, 該信號的默認處理動作是終止當前行程,

- 硬體例外產生信號
硬體例外被硬體以某種方式被硬體檢測到并通知內核,然后內核向當前行程發送適當的信號,例如當前行程執行了除以0的指令,CPU的運算單元會產生例外,內核將這個例外解釋 為SIGFPE信號發送給行程,再比如當前行程訪問了非法記憶體地址,MMU會產生例外,內核將這個例外解釋為SIGSEGV信號發送給行程,
模擬野指標例外


例外被捕獲,并被自定義處理


由此可以確認,我們在C/C++當中除零,記憶體越界等例外,在系統層面上,是被當成信號處理的,
總結一下,有幾個問題會在后面的知識里呈現
- 上面所說的所有信號產生,最終都要OS來執行,為什么?OS 是行程的管理者
- 信號的處理是否是立即處理的呢?不是的,是在合適的時候,而且需要被行程所記錄
- 信號產生有4種方式,所有信號必須經過作業系統才可以發出,因為作業系統內核是行程的管理者,
- 野指標導致的段錯誤的底層原因:MMU 在頁表地址映射的時候(虛擬地址<=====>物理地址),可能這個虛擬地址在頁表里并不存在,或者存在但是映射到了記憶體中只讀的位置,作業系統內核一旦發現這個問題就形成信號干掉行程,
- 一個行程在沒有收到信號的時候能否知道,自己應該對合法信號作何處理,
4 Linux 行程信號的保存和處理
信號在行程的PCB里保存,用一個無符號數的位圖來保存,位元位的位置代表是哪個信號,位元位的內容代表是否收到此信號,那么作業系統是如何給行程發(其實是寫信號)信號的呢?kill -8 行程ID ==> 把行程里的位圖的第8位改為1,那么行程執行信號的行為,

信號的相關術語
- 實際執行信號的處理動作稱為信號遞達(Delivery),
- 信號從產生到遞達之間的狀態稱為未決(Pending),
- 行程可以**阻塞(屏蔽)(Block)**某個信號,
- 被阻塞(屏蔽)的信號產生時將保持在未決狀態,直到行程解除對此信號的阻塞,才執行遞達的動作,
- 注意,阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是遞達之后可選的一種處理動作,
信號在內核中的表示

- 每個信號都有兩個標志位分別表示阻塞(block)和未決(pending),還有一個函式指標表示處理動作,信號產生時,內核在行程控制塊中設定該信號的未決標志,直到信號遞達才清除該標志,在上圖的例子中,SIGHUP信號未阻塞也未產生過,當它遞達時執行默認處理動作,
- SIGINT信號產生過,但正在被阻塞,所以暫時不能遞達,雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,因為行程仍有機會改變處理動作之后再解除阻塞,
- SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,它的處理動作是用戶自定義函式sighandler,如果在行程解除對某信號的阻塞之前這種信號產生過多次,將如何處理?POSIX.1允許系統遞送該信號一次或多次,Linux是這樣實作的:常規信號在遞達之前產生多次只計一次,而實時信號在遞達之前產生多次可以依次放在一個佇列里,
sigset_t
- 從上圖來看,每個信號只有一個bit的未決標志,非0即1,不記錄該信號產生了多少次,阻塞標志也是這樣表示的,
- 因此,未決和阻塞標志可以用相同的資料型別sigset_t來存盤,sigset_t稱為信號集,這個型別可以表示每個信號
- 的“有效”或“無效”狀態,在阻塞信號集中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信號是否處于未決狀態,
- 阻塞信號集也叫做當前行程的信號屏蔽字(Signal Mask),這里的“屏蔽”應該理解為阻塞而不是忽略,
信號集操作函式
- sigset_t型別對于每種信號用一個bit表示“有效”或“無效”狀態,至于這個型別內部如何存盤這些bit則依賴于系統實作
- 從使用者的角度是不必關心的,使用者只能呼叫以下函式來操作sigset_ t變數,而不應該對它的內部資料做任何解釋,比如用printf直接列印sigset_t變數是沒有意義的

- 函式sigemptyset初始化set所指向的信號集,使其中所有信號的對應bit清零,表示該信號集不包含 任何有效信號,
- 函式sigfillset初始化set所指向的信號集,使其中所有信號的對應bit置1,表示 該信號集的有效信號包括系統支持的所有信號,
- 注意,在使用sigset_ t型別的變數之前,一定要調 用sigemptyset或sigfillset做初始化,使信號集處于確定的狀態,初始化sigset_t變數之后就可以在呼叫sigaddset和sigdelset在該信號集中添加或洗掉某種有效信號,
- 這四個函式都是成功回傳0,出錯回傳-1,sigismember是一個布爾函式,用于判斷一個信號集的有效信號中是否包含某種 信號,若包含則回傳1,不包含則回傳0,出錯回傳-1,
處理阻塞信號集和未決信號集的相關介面
sigprocmask : 讀取或者更改行程的信號屏蔽字(阻塞信號集)

- 如果oldset 是非空指標,則讀取行程的當前行程的信號屏蔽字通過oldset 引數傳出,如果 set 為非空指標,則更改行程的 信號屏蔽字,引數 how 是修改方式,如果set 和oldset都是非空指標,則先將原來的信號屏蔽字備份到 oldset里,然后根據set 和 how 引數更改信號屏蔽字,假設當前的信號屏蔽字是mask,下面說明how引數的可選值,
- SIG_BLOCK :set 包含了我們希望添加到當前信號屏蔽字的信號,相當于 mask = mask | set
- SIG_UNBLOCK : set 包含了我們希望從當前信號屏蔽字中解除阻塞的信號,相當于 mask = mask &~set
- SIG_SETMASK :設定當前信號屏蔽字為 set 所指向的值,相當于 mask = set
- 如果呼叫sigprocmask解除了對當前若干個未決信號的阻塞,則在sigprocmask回傳前,至少將其中一個信號遞達
sigpending : 讀取當前行程的未決信號集聽過輸出型引數傳出

一段有意思的代碼



5 Linux 行程信號遞達
行程信號遞達的3種方式
- 默認
- 忽略
- 自定義捕獲
相應的系統呼叫介面介紹



sigaction函式可以讀取和修改與指定信號相關聯的處理動作,呼叫成功則回傳0,出錯則回傳- 1,signum 是指定信號的編號,若act指標非空,則根據act修改該信號的處理動作,若oact指標非 空,則通過oact傳出該信號原來的處理動作,act和oact指向sigaction結構體:
結構體成員說明:將sa_handler賦值為常數SIG_IGN傳給sigaction表示忽略信號,賦值為常數SIG_DFL表示執行系統默認動作,,賦值為一個函式指標表示用自定義函式捕捉信號,或者說向內核注冊了一個信號處理函 數,該函式回傳值為void,可以帶一個int引數,通過引數可以得知當前信號的編號,這樣就可以用同一個函式處理多種信號,顯然,這也是一個回呼函式,不是被main函式呼叫,而是被系統所呼叫,
當某個信號的處理函式被呼叫時,內核自動將當前信號加入行程的信號屏蔽字,當信號處理函式回傳時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產生,那么 它會被阻塞到當前處理結束為止, 如果在呼叫信號處理函式時,除了當前信號被自動屏蔽之外,還希望自動屏蔽另外一些信號,則用sa_mask欄位說明這些需要額外屏蔽的信號,當信號處理函式回傳時自動恢復原來的信號屏蔽字, sa_flags欄位包含一些選項,代碼都把sa_flags設為0,sa_sigaction是實時信號的處理函式,不詳細解釋這兩個欄位,有興趣的小伙伴可以在了解一下,
sigaction的使用

結果和signal函式是一樣的,sigaction函式的功能比signal函式豐富一些

信號是什么時候處理的呢?從內核態切換到用戶態的時候
6 volatile關鍵字
站在信號的角度理解一下 volatile
Makefile

test.c

結果

- 標準情況下,鍵入 CTRL-C ,2號信號被捕捉,執行自定義動作,修改 quit=1 , while 條件不滿足,退出回圈,行程退出
我們在看編譯器優化的情況

結果

- 優化情況下,鍵入 CTRL-C ,2號信號被捕捉,執行自定義動作,修改 quit=1 ,但是 while 條件依舊滿足,行程繼續運行!但是很明顯 quit 肯定已經被修改了,但是為何回圈依舊執行?很明顯, while 回圈檢查的quit ,并不是記憶體中最新的quit,這就存在了資料二異性的問題, while 檢測的quit其實已經因為優化,被放在了CPU暫存器當中,
給全域變數 quit 加關鍵字 volatile 修飾

結果

可以得出結論
- volatile 作用:保持記憶體的可見性,告知編譯器,被該關鍵字修飾的變數,不允許被優化,對該變數的任何操作,都必須在真實的記憶體中進行操作
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/275513.html
標籤:其他




