Linux: 系統內核中的信號

目錄

?一 前言

二? 信號在內核中的表示

三 sigset_t? ?

四?信號集操作

1. sigpending()?

2. sigemptyset()

3.?sigfillset()

4.?sigaddset ()和sigdelset()?

5.?sigismember()

6.?sigprocmask()

五 深入理解信號的捕捉流程?




?一 前言

在Linux: 進程信號初識-CSDN博客信號的初識這一篇我們已經了解了什么是信號,和信號的產生及信號的捕捉,但是那些都是信號在用戶層的理解,同時也產生了幾個問題:?

  • 之前講到的所有進程信號的產生都需要OS來執行,為什么?

  • 我們提到進程在接收到信號之后通常有著三種處理方式(1. 忽略此信號。 2. 執行該信號的默認處理動作。 3. 自定義處理動作),那么針對這三種處理方式,進程是立即處理的嗎?如果不是立即處理,那么信號是否需要暫時被進程記錄下來?記錄下來放在哪里呢?

  • 一個進程在沒有收到信號的時候,怎么知道自己應該對合法信號作何處理呢?

  • 怎么理解OS向進程發送信號?是怎么發送的?具體情況是什么?

接下來我們將從系統內核層面著重討論和理解進程信號產生之后進程處理信號的詳細操作以及進程信號的產生到進程接收之間內核做了哪些事情。

為了后續學習,我們需要知道信號其他相關概念

  1. 執行信號的處理動作稱為信息遞達(Delivery)
  2. 信號從產生到遞達之間的狀態成為信號未決(Pending)
  3. 進程可以選擇阻塞(Block)某個信號。
  4. 被阻塞的信號產生時將保持在未決狀態,直到進程接觸對信號的阻塞。才執行遞達的動作。
  5. 阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是遞達之后的可選的一種處理動作。

二? 信號在內核中的表示

在上篇Linux: 進程信號初識-CSDN博客,我們簡單提到過,進程信號保存是以位圖的方式存儲在進程的PCB中的,通常每個信號對應位圖中的一個特定位置,即一個比特位,如果該比特位設置為1,表示對應信號已經收到但是尚未處理;若為0.則表示沒有收到該信號。事實上,在PCB中描述著一個有關進程信號的位圖和一個有關進程信號的指針數組如下

  • 每個信號都有兩個標志位分別表示阻塞(block)和未決(pending),還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標志(pending),位圖比特位設為1,直到信號遞達才清除該標志。在上圖的例子 中,SIGHUP信號未阻塞也未產生過,當它遞達時執行默認處理動作。
  • SIGINT信號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前 不能忽略這個信號,因為進程仍有機會改變處理動作之后再解除阻塞。
  • SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,也就是說阻塞信號,可以在沒有信號傳遞時就可以阻塞,它的處理動作是用戶自定義函數sighandler。
  • 如果在進程解除對某信號的阻塞之前這種信號產生過多次,將如何處理? POSIX.1允許系統遞送該信號一次 或多次。Linux是這樣實現的:常規信號在遞達之前產生多次只計一次,而實時信號在遞達之前產生多次可 以依次放在一個隊列里。本章不討論實時信號。
  • pending位圖:pending表示信號未決,所以它是未決位圖,用來表示進程收到了信號,對應位置即為對應編號的信號,當該位圖中的某個位置設置為1時,即表示此位的信號在進程中處于未決狀態,即接收到了信號但是還未處理。

  • handler指針數組:顯而易見,存儲的是信號處理方法的數組,每位對應一個處理方法。

  • block位圖:阻塞位圖,表示對應位置的進程信號是否阻塞,當指定位置為1時,即表示此位置的信號會被阻塞??


三 sigset_t? ?

?從上面來看,pending位圖和block位圖所表示的信息能力都是有限的,其每一位的0 1都只能表示進程信號是否存在或者阻塞并不能表示有多少信號產生?并?發送給了進程。

在Linux操作系統中pending和block并不是以整型來表示位圖的,而是以一個結構體的形式sigset_t

sigset_t?是一個 typedef 出來的類型, 實際上是一個結構體?__sigset_t,?不過這個結構體內部只有一個?unsigned long int?類型的數組 ,也就是說pending和block位圖其實是以數組的形式表現出來的。

其中, 實際以 sigset_t 形式表現的 pending位圖, 被稱為未決信號集; 同樣以 sigset_t 形式表現的 block位圖, 被稱為阻塞信號集, 也叫信號屏蔽集。


四?信號集操作

信號集實際上是以數組來表示位圖的,且系統為用戶提供了相關的系統調用接口:

int sigpending(sigset_t *set);
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);

1. sigpending()?

該接口的作用是檢查未決信號集,即獲取進程的未決信號集。其參數是一個輸出型參數,獲取的未決信號集的內容會存儲在傳入的變量中,成功則返回0,錯誤返回-1.?

2. sigemptyset()

調用此接口會將傳入的信號集初始化為空, 即所有信號、阻塞會被消除, 信號集的所有位設置為0成功返回0, 錯誤返回-1

3.?sigfillset()

調用此函數,?會將傳入的信號集所有位設置為1.成功返回0, 錯誤返回-1

4.?sigaddset ()和sigdelset()?

前者的作用是,?給指定信號集中添加指定信號, 即將指定信號集中的指定位置設置為1

后者的作用是,?刪除指定信號集中的指定信號, 即將指定信號集中的指定位置設置為0

這兩個函數, 都是成功返回0, 失敗返回-1

5.?sigismember()

調此函數, 可以判斷信號集中是否有某信號??即??判斷信號集的某位是否為1

如果信號在信號集中返回1,如果不在返回0,如果出現錯誤 則返回-1

6.?sigprocmask()

這個接口的作用是:獲取?和?修改?信號屏蔽集合(阻塞信號集)?

如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。如果set是非空指針,則 更改進程的信 號屏蔽字,參數how指示如何更改。如果oset和set都是非空指針,則先將原來的信號 屏蔽字備份到oset里,然后 根據set和how參數更改信號屏蔽字。假設當前的信號屏蔽字為mask,下表說明了how參數的可選值。

下面我用圖片的形式 解釋一下:

? 1.為指定位置添加阻塞

2.為指定信號解除阻塞

3.直接設置信號屏蔽字

?直接將傳入的set覆蓋進程原來的信號屏蔽字, 將傳入的set作為進程新的信號屏蔽字?

?測試:

#include <iostream>
#include <signal.h>
#include <unistd.h>#define BLOCK_SIGNAL 2 //需要屏蔽信號2
#define MAX_SIGNUM 31static void show_pending(const sigset_t & pending)
{for(int signo=MAX_SIGNUM;signo>=1;signo--){if(sigismember(&pending,signo)){std::cout<<"1";}else  std::cout<<"0";}std::cout<<"\n";
}int main()
{//先嘗試屏蔽指定信號sigset_t block,oblock,pending;//1.初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);//1.2添加要屏蔽的信號sigaddset(&block,BLOCK_SIGNAL);//1.3開始屏蔽sigprocmask(SIG_SETMASK,&block,&oblock);//2.遍歷打印pending 信號集while(true){//2.1初始化sigemptyset(&pending);//2.2獲取sigpending(&pending);//2.3打印show_pending(pending);sleep(1);}return 0;
}

五 深入理解信號的捕捉流程?

在前言部分,我們曾說過信號產生的時候,不會立即被處理,而是在合適的時候,那是在什么時候呢?

🍉:從內核態返回用戶態的時候,進行處理。所以什么是內核態?什么又是用戶態呢?

1.進程的內核態與用戶態

我們知道對于系統中的每一個進程其都有自己的一份獨立的程序地址空間

且進程地址空間與物理內存是通過頁表映射的。但是之前講到的Linux : 進程地址空間-CSDN博客只是用戶空間部分與物理內存之間的相互映射。?

事實上 ,對于1GB的內核空間,也存在著一張頁表,用于內核空間和物理內存之間的相互映射,稱為內核級頁表

如上圖所示,所有的進程都用著同一張內核級頁表,也就是說每個進程的內核空間的內容是一樣的,也就是說物理內存中只加載著一份有關于進程內核空間內容的數據代碼。

如果每個進程都可以訪問及隨意修改內核空間中的數據代碼,這是一件很恐怖的事情,畢竟操作系統做了那么多工作,提供了那么多系統調用封裝了那么多系統接口,就是為了不讓用戶直接操作系統內核。所以為了保護這部分數據代碼,進程會分為兩種狀態: 內核態 和 用戶態

🌔當進程 需要訪問、調用、執行 內核數據或 代碼(系統調用等)時, 就會 陷入內核, 轉化為內核態, 因為只有進程處于內核態時, 才有權限訪問內核級頁表, 即有權限訪問內核數據與代碼。

🌖當進程不需要訪問、調用、執行內核數據或代碼或系統調用結束時, 就會返回用戶, 轉化為用戶態 , 此時 進程將不具備訪問內核級頁表的權限, 只能訪問用戶級頁表

?那么系統如何分清當前的進程處于哪一種狀態下呢?

事實上,在CPU內部存在著一個 狀態寄存器CR3,此寄存器內有比特標識位標識當前進程的狀態。

若標識位 表示0, 則表明進程此時處于內核態
若標識位 表示3, 則表明進程此時處于用戶態?

在操作系統中,當進程處于運行時,它會有兩種狀態,用戶態和內核態,且在進程的整個周期內會發生著無數次的狀態轉換。我們平常寫的代碼大部分情況下是沒有資格直接訪問系統的軟硬件資源的 ,本質上我們都是通過調用系統所提供的接口,通過系統去訪問這些資源,這樣的情況下,進程需要訪問硬件資源地時候,就會無數次地陷入內核(切換狀態、切換頁表),再訪問內核代碼數據, 然后完成訪問,再將結果返回給用戶(切換狀態,切換頁表),最終用戶得到結果。

還有一種情況,在用戶不調用任何函數的時候,這時候還會發生進程狀態的轉換嗎?答案是會的。因為只要是進程, 那么他就有一定的時間片. 即使是一個什么都不執行的死循環, 只要時間片用完了, 那么就需要將此進程從CPU上剝離下來, 而剝離操作一定是操作系統做的, 那么也就是說將 進程從CPU上剝離下來也是需要陷入內核執行內核代碼的. 將進程從CPU上剝離下來的時候, 需要維護一下進程的上下文, 以便下次接著執行進程的代碼.剝離下來之后, 操作系統執行調度算法, 將下一個需要運行的進程的上下文加載到CPU中。

🍰:下面我們舉個例子詳細分析一下在進程切換狀態的時候,信號在什么時候處理。

如圖所示:

  • 代碼在運行到需要執行系統調用signal()接口的時候,此時進程就需要陷入內核態執行signal()代碼
  • 陷入內核并執行完signal()代碼后, 需要將signal()結果返回給用戶, 需要轉換回用戶態
  • 在轉換回用戶態之前, 需要先在進程PCB中檢測進程的未決信號集
  • 在未決信號集中, 檢測到1和2信號未決, 并且均未被屏蔽(阻塞). 就需要在handler數組中尋找指定的處理方法
  • 1信號默認處理, 需要執行內核中的默認處理方法(一般為進程終止); 2信號忽略處理, 直接將未決信號集中2信號改為0
  • 處理完信號, 再將signal()結果返回給用戶, 這個過程需要轉換為用戶態

🍅:上面的信號的處理方式都是默認或者是忽略,但是如果我們捕捉了一個信號并且讓它按照自定義的方式處理,這時候在最后一步怎么辦?

? ? 進程首先會從內核態切換為用戶態去執行用戶自定義的信號處理動作,(雖然內核態也能處理用戶態的代碼,但是操作系統不會這樣做,因為萬一用戶自己寫個Bug,內核態去執行的話會影響操作系統。)

? ? ? ? ? ?其次進程現在是用戶態,此時進程是無法返回到進程原本代碼的執行位置的。因為進程執行系統調用之后的返回信息還在內核中,以用戶態的身份是無法訪問并返回給用戶的。?所以, 進程還需要再次陷入內核,轉換成內核態 然后根據內核中的返回信息使用特定的返回調用 返回到用戶.

圖示如下:

🍎可以看到如果處理信號需要執行用戶自定義的處理方法時, 那么 從調用內核代碼到返回用戶的整個過程一共需要經歷4次狀態轉換

🍏而, 如果處理信號不需要執行用戶自定義處理方法時, 那么 從調用內核代碼到返回用戶的整個過程 就只需要經歷2次狀態轉換

簡化圖如下:

縮略圖:

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

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

相關文章

Nginx-keepalived-高可用

Nginx 高可用 通常 借助 Keepalived 實現&#xff0c; Keepalived 能通過 VRRP &#xff08;虛擬路由冗余協議&#xff09;讓多個 Nginx 服務器 組成一個 熱備集群&#xff0c;當主服務器故障時自動切換到備用服務器&#xff0c;保障服務不間斷。 一、環境準備 角色IP 地址主…

使用python完成手寫數字識別

入門圖像識別的第一個案例,看到好多小伙伴分享,也把自己當初的思路捋捋,寫成一篇博客,作為記錄和分享,也歡迎各位交流討論。 實現思路 數據集:MNIST(包含60,000個訓練樣本和10,000個測試樣本) 深度學習框架:Keras(基于TensorFlow) 模型架構:卷積神經網絡(CNN) 實…

Java學習總結-多線程-三種創建方法

什么是線程&#xff1f; 線程&#xff08;Thread&#xff09;是程序內部的一條執行流程。 程序如果只有一條執行流程&#xff0c;那這個程序就是單線程程序。 什么是多線程&#xff1f; 多線程是指從軟硬件上實現的多條執行流程的技術&#xff08;多條線程由CPU負責調度執行…

電動垂直起降飛行器(eVTOL)

電動垂直起降飛行器&#xff08;eVTOL&#xff09;的詳細介紹&#xff0c;涵蓋定義、技術路徑、應用場景、市場前景及政策支持等核心內容&#xff1a; 一、定義與核心特性 eVTOL&#xff08;Electric Vertical Take-off and Landing&#xff09;即電動垂直起降飛行器&#xf…

ensp 網絡模擬器 思科華為基于VLANIF的公司網絡搭建

該文章僅記錄作業配置過程 如有雷同純屬巧合 一. 其它&#xff08;共1題&#xff0c;100分&#xff09; 1. (其它) 為大學生公司創建部門VLAN 1.項目 背景 為大學生公司現有財務部、技術部和業務部&#xff0c;出于數據安全的考慮&#xff0c;各部門的計算機需進行隔離。公…

使用`sklearn`中的邏輯回歸模型進行股票的情感分析,以及按日期統計積極和消極評論數量的功能

以下是完成上述任務的Python代碼&#xff0c;可在Jupyter Notebook中運行。此代碼包含了使用sklearn中的邏輯回歸模型進行情感分析&#xff0c;以及按日期統計積極和消極評論數量的功能。 import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer f…

oracle批量刪除分區

為了清理數據&#xff0c;往往需要刪除一些分區 簡單查看當前分區 附件 --創建測試表 -- drop table test_part purge;CREATE TABLE test_part (sales_id NUMBER,sale_date DATE,amount NUMBER ) PARTITION BY RANGE (sale_date) INTERVAL (INTERVAL 1 MONTH) -- 每個月創建…

java流程控制08:For循環

For循環 雖然所有循環結構都可以用while或者do…while表示&#xff0c;但Java提供了另一種語句-----for循環&#xff0c;使一些循環結構變得更加簡單。 for循環語句是支持迭代的一種通用結構&#xff0c;是最有效、最靈活的循環結構。 for循環執行的次數是在執行前就確定的。…

嵌入式軟件開發調試方法

文章目錄 1. 利于函數返回值,retrurn 定位錯誤位置2. 合理使用邏輯分析儀&#xff08;正點原子 厲害&#xff01;&#xff01;&#xff09; 1. 利于函數返回值,retrurn 定位錯誤位置 如下圖所示&#xff0c;設置不同的返回值&#xff0c;0是ok的&#xff0c;其他值均為失敗&…

P1025 [NOIP 2001 提高組] 數的劃分(DFS)

題目描述 將整數 n 分成 k 份&#xff0c;且每份不能為空&#xff0c;任意兩個方案不相同&#xff08;不考慮順序&#xff09;。 例如&#xff1a;n7&#xff0c;k3&#xff0c;下面三種分法被認為是相同的。 1,1,5; 1,5,1; 5,1,1. 問有多少種不同的分法。 輸入格式 n,k …

設計模式簡述(三)工廠模式

工廠模式 描述簡單工廠&#xff08;靜態工廠&#xff09;工廠方法模式 抽象工廠增加工廠管理類使用 描述 工廠模式用以封裝復雜的實例初始化過程&#xff0c;供外部統一調用 簡單工廠&#xff08;靜態工廠&#xff09; 如果對象創建邏輯簡單且一致&#xff0c;可以使用簡單工…

批量將 JSON 轉換為 Excel/思維導入等其它格式

json 格式相信對大家來說都不陌生&#xff0c;這是一種輕量級的結構化數據&#xff0c;可以對對象進行描述。json 格式也是一種普通的文本文件格式&#xff0c;用記事本就能夠打開編輯 json 格式的文件&#xff0c;可以很方便的轉換為其他格式。今天要給大家介紹的就是如何將 j…

電腦有時出現檢測不到音箱設備怎么辦?

問題 有時候電腦開機之后就檢測不到音箱&#xff0c;經過我一頓檢查發現是檢測不到聲卡&#xff0c;即使拔插了音箱也沒用&#xff0c;但是當我重啟或者休眠之后再重啟發現就檢測到了 解決方案 方案一 重啟或者休眠之后再開啟 方案二 使用powershell指令將聲卡彈出和載入…

Qwen-Agent框架的文件相關操作:從Assistant到BasicDocQA

在前面的幾篇文章如《針對Qwen-Agent框架的Function Call及ReAct的源碼閱讀與解析&#xff1a;Agent基類篇》 、《基于Qwen-Agent框架的Function Call及ReAct方式調用自定義工具》、 《針對Qwen-Agent框架的源碼閱讀與解析&#xff1a;FnCallAgent與ReActChat篇》中&#xff0c…

RSSI定位程序,N個錨點、三維空間,使用CKF對軌跡進行濾波,附MATLAB代碼的下載鏈接

本文所述的程序實現三維空間中基于RSSI信號的多錨點定位&#xff0c;并采用容積卡爾曼濾波&#xff08;CKF&#xff09;對動態軌跡進行降噪優化。代碼包含完整的定位仿真流程&#xff0c;涵蓋環境建模、信號強度模擬、定位解算、軌跡濾波及可視化分析模塊 文章目錄 程序介紹概述…

開源軟件與自由軟件:一場理念與實踐的交鋒

在科技的世界里&#xff0c;“開源軟件”和“自由軟件”這兩個詞幾乎無人不知。很多人或許都聽說過&#xff0c;它們的代碼是公開的&#xff0c;可以供所有人查看、修改和使用。然而&#xff0c;若要細究它們之間的區別&#xff0c;恐怕不少朋友會覺得云里霧里。今天&#xff0…

C++ - 頭文件基礎(常用標準庫頭文件、自定義頭文件、頭文件引入方式、防止頭文件重復包含機制)

一、頭文件 在 C 中&#xff0c;頭文件&#xff08;.h&#xff09;用于函數聲明、類定義、宏定義等等 在 Visual Studio 中&#xff0c;頭文件通常放在頭文件目錄中&#xff0c;頭文件實現通常放在源文件目錄中 二、常用標準庫頭文件 1、輸入輸出 <iostream> 標準輸入…

CSS 背景屬性學習筆記

一、CSS 背景屬性概述 CSS 背景屬性用于定義 HTML 元素的背景效果&#xff0c;主要包括以下幾種屬性&#xff1a; background-color&#xff1a;定義元素的背景顏色。 background-image&#xff1a;定義元素的背景圖像。 background-repeat&#xff1a;定義背景圖像如何重復…

Qt實現鼠標拖動窗口

Qt實現鼠標拖動窗口 1、設置窗口無邊框2、重寫鼠標點擊&#xff0c;移動函數2.1添加頭文件2.2 重寫函數2.3 添加定義 3、定義一個偏移值4、判斷鼠標左鍵是否按下并計算偏移值5、移動窗口6、.h文件和.cpp文件6.1 .h文件6.2 .cpp文件 7、總結 1、設置窗口無邊框 this->setWin…

MDX語言的數論算法

MDX語言的數論算法探討 引言 數論作為數學的一個重要分支&#xff0c;主要研究整數及其性質。在計算機科學和信息技術領域&#xff0c;數論算法被廣泛應用于密碼學、算法設計、數據加密等領域。MDX&#xff08;Multi-Dimensional Expressions&#xff09;語言&#xff0c;雖然…