信號(上)

本節目標:

1. 掌握Linux信號的基本概念
2. 掌握信號產生的一般方式
3. 理解信號遞達和阻塞的概念,原理。
4. 掌握信號捕捉的一般方式。
5. 重新了解可重入函數的概念。
6. 了解競態條件的情景和處理方式
7. 了解SIGCHLD信號, 重新編寫信號處理函數的一般處理機制

?首先聲明:這里所講的信號與上文的信號量毫無關聯。

目錄

1. 信號是什么?

2. 信號的處理過程?

注意:

3. 信號的準備知識??

3.1 信號的種類

3.2 信號的行為

core dump(核心轉儲)?

3.3? 常見信號處理方式??

4. 信號的一生

4.1 信號的產生?

方法1--kill命令?編輯

方法2--鍵盤鍵入?

方法3--?系統調用

1. kill 向任意進程發送任意信號

2. raise 向自己發送任意信號

3. abort 向自己發送六號信號?

方法四--由軟件條件產生信號

方法五--硬件異常產生信號?

4.2 信號的保存?

4.2.1 pending、block與handler?

4.2.2 sigset_t?

4.2.3 信號集操作函數?

4.2.4 sigprocmask與sigpending?

sigprocmask?

sigpending

4.2.5 實驗?

實驗1

代碼?

?結果

?實驗2

?代碼

結果


?

1. 信號是什么?

什么是信號呢?

我們從生活引入。當你在房間里苦學c++的時候,媽媽喊你吃飯,你有兩個選擇--立即去吃飯、看完再吃飯,這個例子就可以完美的解釋什么是信號。

這就是我們日常生活中的信號以及對信號的處理過程。

那么我們要講的信號是什么呢??

我們所將的信號同上例本質是一樣的,不過是由某人發給進程,然后由進程進行一系列處理過程罷了。為什么用某人呢?因為這個某人并不確定,有可能是OS,有可能是用戶,也有可能是其他進程乃至進程自己(這個就涉及信號的產生了)。

那么從上面的例子我們可以知道信號與進程之間有哪些關系呢?

1. 信號既然是由其他人發送的,會中斷我們(進程),且這一信號是我們無法預料的,那么信號本身就是異步的,即信號是OS提供給用戶(進程)向其他進程發送異步信息的一種方式,這一過程是并發的。

2. 信號發來我們就需要能夠進行處理,這就說明我們(進程)具備認識信號的能力,并知道如何對該信號進行處理。因此進程不僅認識信號,而且還儲存有對信號的處理方式。

3. 當我們(進程)接收到信號,如果我們在做更重要的事,我們可以先對信號進行保存,等到合適的時候再進行處理。因此進程應當具備保存信號,以及在合適時機處理信號的能力

2. 信號的處理過程?

下圖是信號的處理過程時間軸示意圖,我們后續的講解線就是根據這個時間軸。

注意:

1. Ctrl-C 產生的信號只能發給前臺進程。一個命令后面加個&可以放到后臺運行,這樣Shell不必等待進程結束就可以接受新的命令,啟動新的進程。
2. Shell可以同時運行一個前臺進程和任意多個后臺進程,只有前臺進程才能接到像 Ctrl-C 這種控制鍵產生的信號。
3. 前臺進程在運行過程中用戶隨時可能按下 Ctrl-C 而產生一個信號,也就是說該進程的用戶空間代碼執行到任何地方都有可能收到 SIGINT 信號而終止,所以信號相對于進程的控制流程來說是異步(Asynchronous)的。

3. 信號的準備知識??

3.1 信號的種類

我們先來看看都有哪些信號(kill -l命令)

這些信號大部分的行為都是終止進程,還有一部分是忽略,暫停等等。?

3.2 信號的行為

下圖信號的行為,三十一個信號的行為都囊括其中。

core dump(核心轉儲)?

其他的信號默認處理動作都很好理解,但core動作是怎么回事,好像有點看不懂。

?

首先解釋什么是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 -c 1024

也就是說,我們的云服務器默認關閉核心轉儲功能,在Linux中,我們的g++/gcc默認是release版本,因此如果要生成core文件,除卻上述的打開core dump功能外,還需要在編譯時加-g選項。

有這樣一種場景,倘若某種大型服務器出現異常,但由于大型服務器出錯的第一件事不是找錯,而是重新啟動,因此我們有自啟服務器的程序。如果服務器在無人發覺的情況下瘋狂終止又瘋狂重啟,經過一段時間后,會生成無數core文件,這會導致空間爆炸的問題。因此在部分系統里,core文件的名字就叫做core,一個進程即便不斷終止與重啟,也只會有一個core文件。

此前我們在講進程返回碼時有一個標志位略過沒有講,現在看他剛剛好。

3.3? 常見信號處理方式??

可選的處理動作有以下三種:
1. 忽略此信號。(即接收到該信號的進程對此信號進行忽略)
2. 執行該信號的默認處理動作。(每一個信號有自己的默認處理動作,如果用戶沒有對信號的處理動作進行自定義,那么就執行該默認處理動作)
3. 對信號進行捕捉。提供一個信號處理函數,要求內核在處理該信號時切換到用戶態執行這個處理函數,這種方式稱為捕捉(Catch)一個信號(即用戶對信號的處理動作進行自定義化)

比如:

SIGINT的默認處理動作是終止進程,我們現在對SIGINT信號進行捕捉,自定義其處理動作為打印一串字符。使用signal函數。

4. 信號的一生

4.1 信號的產生?

信號要想發給進程,首先當然要先產生,那么信號的產生方式有哪些呢?

我們先寫一個正常情況下不會終止的進程。

#include<iostream>
#include<unistd.h>int main()
{while(true){sleep(1);pid_t pid=getpid();std::cout<<"process pid :"<<pid<<std::endl;}return 0;
}

方法1--kill命令

方法2--鍵盤鍵入?

記得我們之前使用的ctrl+c嗎,它可以終止進程,但鍵盤可以輸入的信號可不只有他

ctrl+\后的core dumped是什么呢?

方法3--?系統調用

這里的系統調用一般有三種,我們挨個來看看。

1. kill 向任意進程發送任意信號

kill可以向任意進程發送任意信號,我們來試試吧。

我們看到實驗成功了,不過這一實驗有一些丑陋,大家可以使用父進程發送信號殺死子進程,同時記錄當前進程狀況。?

2. raise 向自己發送任意信號

3. abort 向自己發送六號信號?

相當于kill(getpid(),9)

方法四--由軟件條件產生信號

首先這一方式我們熟知的有SIGPIPE,即管道讀端關閉而寫端還在寫時,OS會向寫端進程發送SIGPIPE強制殺死該進程。

還有我們并未接觸過的SIGALARM,我們來看看。

我們來驗證一下。

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

鬧鐘是由OS發送的,而OS中的進程何其多,所以OS就需要對鬧鐘進行管理,先描述在組織。

方法五--硬件異常產生信號?

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

因此我們平時寫的程序崩潰了,就是硬件異常發送給我們進程信號了。

4.2 信號的保存?

欸你可能會疑惑,為什么信號的一生時間線中沒有發送信號的過程呢?別急,我們對信號的一生填充一下。?

我們學習了上面的內容,應該已經明白了向進程發送信號的過程是由OS來做的,因為產生信號的方式全部是系統調用。那么OS是如何發送的呢?

我們知道,進程是承擔系統資源的實體,那么信號既然要保存,自然也是存儲在進程中的某個區域。那么存在哪呢?進程地址空間嗎?

不是的,信號的保存是在pcb中的。信號本質并不屬于進程,而是屬于系統,但由于進程需要能夠對信號及時響應,因此進程需要保存信號,且進程要可以及時察覺到信號的變化,因此將信號保存在pcb中。

4.2.1 pending、block與handler?

那么問題來了,信號在pcb中要怎么保存呢?

由上圖我們可知,信號在pcb中的存儲是三張表, pending(未決信號)、block(阻塞信號)、handler(信號處理函數)。

注意,信號的屏蔽與忽略是截然不同的,信號的屏蔽是指信號始終處于未決,不對其進行處理;信號的忽略本身就是對信號的處理,即信號已然遞達

這里要注意,我們一開始就說,只談1-31個信號,因此這里的位圖都是三十二個比特位,1-31位標識信號。

我們來看看三張表的作用?

我們之前有一個案例代碼,其中有對信號進行捕捉,其實就是讓我們的捕捉函數覆蓋了該信號的原處理函數。

看到這里,有沒有明白信號是如何發送給進程的呢?

沒錯,就是OS對pending表進行寫入,進程會時刻監視這三張表,并做相應處理。

我們之前所講信號的產生,無論是命令行輸入命令,還是程序代碼調用系統調用,都是讓OS幫我們向進程內寫入信號。那么系統調用究竟是什么呢?系統調用其實就是寫在系統內的函數,只有系統有權限使用。OS內會有一個函數指針數組,其內放的全部都是系統方法。

4.2.2 sigset_t?

每個信號只有一個bit的未決標志,非0即1,不記錄該信號產生了多少次,阻塞標志也是這樣表示的。因此,未決和阻塞標志可以用相同的數據類型sigset_t來存儲,sigset_t稱為信號集,這個類型可以表示每個信號的“有效”或“無效”狀態,在阻塞信號集中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信號是否處于未決狀態。下一節將詳細介紹信號集的各種操作。 阻塞信號集也叫做當前進程的信號屏蔽字(Signal Mask),這里的“屏蔽”應該理解為阻塞而不是忽略。

4.2.3 信號集操作函數?

sigset_t類型對于每種信號用一個bit表示“有效”或“無效”狀態,至于這個類型內部如何存儲這些bit則依賴于系統實現,從使用者的角度是不必關心的,使用者只能調用以下函數來操作sigset_ t變量,而不應該對它的內部數據做任何解釋,比如用printf直接打印sigset_t變量是沒有意義的。

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

函數sigemptyset初始化set所指向的信號集,使其中所有信號的對應bit清零,表示該信號集不包含 任何有效信號。


函數sigfillset初始化set所指向的信號集,使其中所有信號的對應bit置為1,表示 該信號集的有效信號包括系統支持的所有信號。


注意,在使用sigset_ t類型的變量之前,一定要調用sigemptyset或sigfillset做初始化,使信號集處于確定的狀態。初始化sigset_t變量之后就可以在調用sigaddset和sigdelset在該信號集中添加或刪除某種有效信號。


這四個函數都是成功返回0,出錯返回-1。sigismember是一個布爾函數,用于判斷一個信號集的有效信號中是否包含某種信號,若包含則返回1,不包含則返回0,出錯返回-1。

注意:這里的幾個函數僅僅是對創建出的對象進行操作,要設置入進程內需要其他的函數,

4.2.4 sigprocmask與sigpending?

sigprocmask?

調用函數sigprocmask可以讀取或更改進程的信號屏蔽字(阻塞信號集)。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功則為0,若出錯則為-1

?如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。如果set是非空指針,則 更改進程的信號屏蔽字,參數how指示如何更改。如果oset和set都是非空指針,則先將原來的信號 屏蔽字備份到oset里,然后根據set和how參數更改信號屏蔽字。(即set為要設置入進程block位圖的信號集,oset為輸出型參數,將會記錄進程原block位圖信號集)假設當前的信號屏蔽字為mask,下表說明了how參數的可選值。

?如果調用sigprocmask解除了若干個對未決信號的阻塞,那么在sigprocmask返回前,OS會立即將其中一個信號遞達。

sigpending
#include <signal.h>
sigpending
讀取當前進程的未決信號集,通過set參數傳出。調用成功則返回0,出錯則返回-1。

4.2.5 實驗?

接下來我們將使用上面的函數做一個實驗

實驗1

將2,3信號屏蔽(sigprocmask)

向進程發送信號,打印pending位圖。

代碼?
#include<iostream>
#include<unistd.h>
#include<signal.h>//打印當前pending信號集
void printsig(sigset_t p)
{std::cout<<getpid()<<"  ";for(int i=31;i>0;i--){if(sigismember(&p,i))std::cout<<"1";elsestd::cout<<"0";}std::cout<<std::endl;
}int main()
{sigset_t s,p;//創建信號集sigemptyset(&s);//清空信號集sigaddset(&s,2);//向信號集內添加有效信號2sigaddset(&s,3);//添加3sigprocmask(SIG_BLOCK,&s,nullptr);//這里我們不需要記錄原信號屏蔽字while(true){sleep(1);sigpending(&p);//獲取當前進程pending信號集printsig(p);//打印pending信號集}return 0;
}
?結果

?

?實驗2

將所有信號屏蔽(sigprocmask)

向進程發送信號,打印pending位圖。

?代碼
#include<iostream>
#include<unistd.h>
#include<signal.h>//打印當前pending信號集
void printsig(sigset_t p)
{std::cout<<getpid()<<"  ";for(int i=31;i>0;i--){if(sigismember(&p,i))std::cout<<"1";elsestd::cout<<"0";}std::cout<<std::endl;
}int main()
{sigset_t s,p;//創建信號集sigemptyset(&s);//清空信號集for(int i=1;i<32;i++){sigaddset(&s,i);//向信號集內添加有效信號}sigprocmask(SIG_BLOCK,&s,nullptr);//這里我們不需要記錄原信號屏蔽字while(true){sleep(1);sigpending(&p);//獲取當前進程pending信號集printsig(p);//打印pending信號集}return 0;
}
結果

?

下篇我們來看信號的處理。

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

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

相關文章

ChatGPT基本原理詳細解說

ChatGPT基本原理詳細解說 引言 在人工智能領域&#xff0c;自然語言處理&#xff08;NLP&#xff09;一直是研究的熱點之一。隨著技術的發展&#xff0c;我們見證了從簡單的聊天機器人到復雜的語言模型的演變。其中&#xff0c;ChatGPT作為一項突破性技術&#xff0c;以其強大…

【Vue】自定義指令-v-loading指令的封裝

場景 實際開發過程中&#xff0c;發送請求需要時間&#xff0c;在請求的數據未回來時&#xff0c;頁面會處于空白狀態 > 用戶體驗不好 需求 封裝一個 v-loading 指令&#xff0c;實現加載中的效果 分析 本質 loading效果就是一個蒙層&#xff0c;蓋在了盒子上 數據請求…

從零開始精通Onvif之設備發現

設備發現的意義 在復雜的網絡環境中&#xff0c;如何快速而準確地識別網絡上的Onvif設備&#xff0c;對于攝像頭廠商、系統集成商、開發人員乃至最終用戶來說&#xff0c;都顯得至關重要。 首先&#xff0c;設備發現有效簡化了集成的復雜度。在沒有統一標準之前&#xff0c;每個…

2004NOIP普及組真題 2. 花生采摘

線上OJ&#xff1a; 【04NOIP普及組】花生采摘 核心思想&#xff1a; 1、本題為貪心即可。 2、因為本題嚴格限制了順序&#xff0c;所以先把每個節點的花生數量按降序排序。然后逐一判斷下一個花生是否需要去采摘即可 3、每一次采摘完&#xff0c;記錄耗時 t 以及采集的花…

力扣第417題測試程序

題目描述&#xff1a; 有一個 m n 的矩形島嶼&#xff0c;與 太平洋 和 大西洋 相鄰。 “太平洋” 處于大陸的左邊界和上邊界&#xff0c;而 “大西洋” 處于大陸的右邊界和下邊界。 這個島被分割成一個由若干方形單元格組成的網格。給定一個 m x n 的整數矩陣 heights &#…

基于web的垃圾分類回收系統的設計

管理員賬戶功能包括&#xff1a;系統首頁&#xff0c;個人中心&#xff0c;管理員管理&#xff0c;用戶管理&#xff0c;公告管理&#xff0c;運輸管理&#xff0c;基礎數據管理 用戶賬戶功能包括&#xff1a;系統首頁&#xff0c;個人中心&#xff0c;運輸管理&#xff0c;公告…

pyqt QlineEdit內部增加按鈕方法

pyqt QlineEdit內部增加按鈕方法 def addButton(self,lineEdit):btn QtWidgets.QPushButton("")icon1 QtGui.QIcon()icon1.addPixmap(QtGui.QPixmap(":/image/images/th.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)btn.setIcon(icon1)btn.setStyleShe…

全光譜led燈的危害有哪些?曝光低質量全光譜led燈產生的四大風險

眼睛是人類獲取信息最重要的感官器官之一&#xff0c;而近視則會導致視力模糊&#xff0c;進而影響學習效果和生活品質。因此&#xff0c;如何保護眼睛&#xff0c;尤其是在學習和使用電子設備時&#xff0c;成為了一個迫切需要解決的問題。然而在護眼領域上&#xff0c;護眼臺…

【DevOps】網絡安全進階之路:打造更安全、更可靠的網站

目錄 一、網站面臨的主要安全威脅 1、SQL注入攻擊 2、跨站腳本攻擊(XSS) 3、跨站請求偽造(CSRF) 4、文件上傳漏洞 5、不安全的直接對象引用 6、安全配置錯誤 7、使用含有已知漏洞的組件 二、網站安全防護措施 1、輸入驗證與過濾 2、使用參數化查詢 3、數據輸出編碼…

SCAU 數據結構 實驗六 排序算法

![[Pasted image 20240 8638 直接插入排序 Description 用函數實現直接插入排序&#xff0c;并輸出每趟排序的結果. 輸入格式 第一行&#xff1a;鍵盤輸入待排序關鍵的個數n 第二行&#xff1a;輸入n個待排序關鍵字&#xff0c;用空格分隔數據 輸出格式 每行輸出一趟排序…

掌握Java設計模式的23種武器(全):深入解析與實戰示例

目錄 一、創建型模式 1. 單例模式 (Singleton Pattern) 2. 工廠模式 (Factory Pattern) 3. 抽象工廠模式 (Abstract Factory Pattern) 4. 建造者模式 (Builder Pattern) 5. 原型模式 (Prototype Pattern) 二、結構型模式 6. 適配器模式 (Adapter Pattern) 7. 橋接模式…

通信的本質是什么

通信的本質是信息的傳遞和交換。在通信過程中&#xff0c;信息從一個主體&#xff08;發送方&#xff09;傳遞到另一個主體&#xff08;接收方&#xff09;&#xff0c;目的是使接收方理解或使用發送方傳遞的信息。無論使用什么樣的媒介或技術&#xff0c;通信的核心都是在不同…

十三、resultMap解析

分為兩部分&#xff1a;解析和使用 解析 1.解析XML的時候單獨解析所有的resultMap標簽&#xff0c;封裝成ResultMap對象存入configuration中 2.解析XML中的SQL語句&#xff0c;封裝MappedStatement對象&#xff0c;這里會根據SQL的返回類型是resultMap還是resultType做處理。如…

C語言 | Leetcode C語言題解之第133題克隆圖

題目&#xff1a; 題解&#xff1a; struct Node** visited; int* state; //數組存放結點狀態 0&#xff1a;結點未創建 1&#xff1a;僅創建結點 2&#xff1a;結點已創建并已填入所有內容void bfs(struct Node* s) {if (visited[s->val] && state[s->val] 2…

【嵌入式系統實踐】實驗三EXTI按鈕外部中斷控制LED燈參考代碼

此內容不屬于實驗內容&#xff0c;因自己手頭有一STM32F103&#xff0c;故驗證性的進行代碼實驗&#xff0c;按照老師課堂ppt進行了一下復現。 通過按鈕控制LED燈的亮滅(狀態取反)。 main.c代碼&#xff1a; #include "STM32F10X.h" #include "stdio.h"…

Open3D Guided濾波(Python版本)

文章目錄 一、簡介二、實現代碼三、實現效果參考資料一、簡介 Guided Filter原本主要用于2D圖像的降噪等處理,但經過適當的修改后,它可以有效地應用于3D點云的降噪。這種方法能夠保留點云中的細節信息,并且對邊緣和曲面進行保護。 其具體計算過程如下所述: 1.局部線性假設:…

Python Lambda函數的應用實例教程

在Python編程中&#xff0c;lambda函數是一種簡潔且強大的工具&#xff0c;用于創建小型匿名函數。它們在需要快速定義簡單函數時特別有用。本文將詳細介紹lambda函數的語法及其多種應用實例&#xff0c;幫助讀者更好地理解和使用lambda函數。 一、lambda函數的基本概念 1.1 什…

c++(內存分配,構造,析構)

#include <iostream>using namespace std; class Per { private:string name;int age;double *height;double *weigh; public://無參構造Per(){cout << "Per::無參構造" << endl;}//有參構造Per(string name,int age,double height,double weigh):…

Nginx 的 stream 模塊,配置轉發redis和mysql

Nginx 的 stream 模塊確實可以配置多個 upstream 塊&#xff0c;用于定義多個后端服務器組。然而&#xff0c;需要注意的是&#xff0c;每個 upstream 塊通常用于一種特定類型的服務&#xff0c;例如定義一組TCP服務器&#xff0c;可以是Redis服務器、MySQL服務器或其他任何TCP…

【TB作品】 51單片機8x8點陣顯示滾動漢字仿真

功能 題目5基于51單片機LED8x8點陣顯示 流水燈 直接滾動顯示HELLO 直接滾動顯示老師好 代碼 void main( void ) {/** 移位后&#xff0c;右邊的是第一個595&#xff0c;接收0X02&#xff0c;顯示出0X02* 移位后&#xff0c;左邊的是第2個595&#xff0c;接收0Xfe&#xff0c…