【Linux】信號的處理

在這里插入圖片描述

你很自由
充滿了無限可能
這是很棒的事
我衷心祈禱你可以相信自己
無悔地燃燒自己的人生
-- 東野圭吾 《解憂雜貨店》

信號的處理

  • 1 信號的處理
  • 2 內核態 VS 用戶態
  • 3 鍵盤輸入數據的過程
  • 4 如何理解OS如何正常的運行
  • 5 如何進行信號捕捉
  • 信號處理的總結
  • 6 可重入函數
  • volatile關鍵字
  • Thanks?(・ω・)ノ謝謝閱讀!!!
  • 下一篇文章見

1 信號的處理

處理信號本質就是遞達這個信號!首先我們來看如何進行捕捉信號:信號的處理有三種:

signal(2 , handler);//自定義
signal(2 , SIG_IGN);//忽略
signal(2 , SIG_DFL);//默認

注意handler表是函數指針表,傳入的參數一定是函數指針類型!!!

我們說過:信號可能不會被立即處理,而是在合適的時候進行處理。那么這個合適的時候到底是什么時候?!

進程從內核態(處于操作系統的狀態)返回到用戶態(處在用戶狀態)的時候進行處理!

在這里插入圖片描述

  1. 首先用戶運行一個進程,在執行代碼指令時因為中斷,異常或者系統調用進如操作系統。
  2. 進入操作操作系統就變為內核態,操作系統處理完之后,就對進程的三張表進行檢查:如果pending中存在,繼續判斷,如果被block了了就不進行處理,反之執行對應方法!
  3. 執行對應的方法時,如果是自定義方法,會返回到用戶層面的代碼,執行對應的方法。然后通過系統調用再次回到內核態。
  4. 進入內核態之后,再返回到原本的用戶指令位置中

注意:

  • 操作系統不能直接轉過去執行用戶提供的handler方法!因為操作系統權限太高了,必須回到用戶權限來執行方法!
  • 類似一個∞符號:在這里插入圖片描述

2 內核態 VS 用戶態

再談地址空間
在這里插入圖片描述
這樣無論進程如何切換,都可以找到OS!!!
所以我們訪問OS,其實還是在我們的地址空間進行的,和訪問庫函數沒有區別!OS不相信任何用戶,用戶訪問[3 , 4]地址空間,要受到一定約束(只能通過系統調用!)

3 鍵盤輸入數據的過程

操作系統如何知道我們按下鍵盤呢?肯定不能是每一時刻都進行檢查,這樣消耗太大!

在CPU中,鍵盤按下時會向cpu發送硬件中斷,CPU就會讀取中斷號讀到寄存器中,CPU會告訴OS,后續通過軟件來讀取寄存器。

內存中,操作系統在啟動時就會維護一張函數指針數組(中斷向量表),數組下標是中斷號,數組內容是讀磁盤函數,讀網卡函數等方法。每個硬件都有自己的中斷號,鍵盤也是。按下鍵盤時,向CPU發送中斷信號,然后調用鍵盤讀取方法,將鍵盤數據讀取到內存中!這樣就不需要輪詢檢查鍵盤是否輸入了!

4 如何理解OS如何正常的運行

根據我們使用電腦的經驗,電腦開機到關機的過程中,本質一定是一個死循環。那這死循環是如何工作的呢?那么CPU內部有一個時鐘,可以不斷向CPU發送中斷(例如每隔10納秒),所以CPU可以被硬件推動下在死循環內部不斷執行中斷方法。來看Linux內核:
在操作系統的主函數中,首先是進行一些初始化(包括系統調用方法),然后就進入到了死循環!
在這里插入圖片描述

操作系統本質是一個死循環 + 時鐘中斷 (不斷調度系統任務)

那么系統調用時什么東西呢?
在操作系統內部,操作系統提供給我們一張表:系統調用函數表
在這里插入圖片描述
平時我們用戶層使用的fork , getpid , dup2...等都對應到底層的sys_fork , sys_getpid ...。只有我們找到特定數組下標(系統調用號)的方法,就能執行系統調用了!

回到之前的函數指針數組,我們在這里再添加一個新方法,用來調度任何的系統調用。使用系統調用就要有:

  1. 系統調用號
  2. 系統調用函數指針表(操作系統內部)

用戶層面如何使用到操作系統中的函數指針表呢?
這就要回到CPU中來談,CPU中兩個寄存器,假設叫做X 和 eax,當用戶調用fork時,函數內部有類似

mov 2 eax //將系統調用號放入寄存器中

而所謂的中斷不也是讓CPU中的寄存器儲存一個中斷號來進行調用嗎!那CPU內部可不可以直接寫出數字呢?可以,當eax獲取到數字時,寄存器X就會形成對應的數字,來執行操作系統的系統調用。

通過這種方法就可以通過用戶的代碼跳轉到內核,來執行系統調用。但操作系統不是不相信任何用戶嗎?怎么就直接跳轉了呢?用戶是無法直接跳轉到內存中的內核空間(3~4GB)。那么就有幾個問題:

  1. 操作系統如何阻止用戶直接訪問?
  2. 系統調用最終是可以被調用的,又是如何做到的?

在操作系統中,解決這兩種問題是非常復雜的!有很多概念,所以簡單單來講:做到這些需要硬件CPU配合,在CPU中存在一個寄存器code semgent記錄代碼段的起始與終止地址。就可以通過兩個cs寄存器來分別儲存用戶與操作系統的代碼!CS寄存器中單獨設置出兩個比特位來記錄是OS還是用戶,這樣就要區分了內核態和用戶態。運行代碼時就會檢測當前權限與代碼權限是否匹配,進而做到阻止用戶直接訪問。而當我們調用系統調用(中斷,異常)時,會改變狀態,變成內核態,此時就可以調用系統調用

5 如何進行信號捕捉

今天我們來認識一個新的系統調用:

NAMEsigaction, rt_sigaction - examine and change a signal actionSYNOPSIS#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

使用方法和signal很像,先介紹struct sigaction

struct sigaction {void     (*sa_handler)(int);void     (*sa_sigaction)(int, siginfo_t *, void *);sigset_t   sa_mask;int        sa_flags;void     (*sa_restorer)(void);
};

在這其中我們只需要注意 void (*sa_handler)(int);,這是個函數指針,就是自定義捕捉的函數方法。這樣看來是不是就和signal很類似了

再來看看參數

  1. int signum : 表示要對哪個信號進行捕捉
  2. const struct sigaction *act : 輸入型參數,表示要執行的結構體方法
  3. struct sigaction *oldact: 輸出型參數,獲取更改前的數據

我們寫一段代碼來看看:

// 創建一個進行,進入死循環
// 對2號信號進行自定義捕捉void handler(int signum)
{std::cout << "get a sig : " << signum << " pid: " << getpid() << std::endl;
}int main()
{struct sigaction act, oact;// 自定義捕捉方法act.sa_handler = handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(2, &act, &oact);while (true){std::cout << "I am a process... pid: " << getpid() << std::endl;sleep(1);}return 0;
}

我們運行看看:
在這里插入圖片描述
這樣就成功捕捉了2號信號!用起來和之前的signal很類似!那么我們介紹這個干什么呢?我們慢慢來說:

首先信號處理有一個特性,比如我們在處理二號信號的時候,默認會對二號信號進行屏蔽!對2號信號處理完成的時候,會自動解除對2號信號的屏蔽!也就是操作系統不允許對同一個信號進行遞歸式的處理!!!

我們來簡單驗證一下:我們在handler方法中進行休眠,看看傳入下一個2號信號是否會進行處理

void handler(int signum)
{std::cout << "get a sig : " << signum << " pid: " << getpid() << std::endl;sleep(100);
}

來看:
在這里插入圖片描述
可見進程就屏蔽了對2號信號的處理!

我們之前學習過三張表:阻塞,未決和抵達
既然操作系統對信號進行來屏蔽,那么再次傳入的信號應該就會被記錄到未決表(pending表)中,我們打印這個表來看看:


void Print(sigset_t &pending)
{for (int sig = 31; sig > 0; sig--){if (sigismember(&pending, sig)){std::cout << 1;}else{std::cout << 0;}}std::cout << std::endl;
}void handler(int signum)
{std::cout << "get a sig : " << signum << " pid: " << getpid() << std::endl;while (true){// 建立位圖sigset_t pending;// 獲取pendingsigpending(&pending);Print(pending);}
}

來看:
在這里插入圖片描述
可以看的我們在傳入2號信號時就進入到了未決表中!處理信號完畢,就會解除屏蔽!

接下來我們既可以來介紹sa_mask了,上面只是對2號信息進行了屏蔽,當我傳入3號新號ctrl + \時就正常退出了,那么怎么可以在處理2號信號時屏蔽其他信號呢?就是通過sa_mask,將想要屏蔽的信號設置到sa_mask中,就會在處理2號信號的時候,屏蔽所設置的信號!

int main()
{struct sigaction act, oact;// 自定義捕捉方法act.sa_handler = handler;sigemptyset(&act.sa_mask);//向sa_mask中添加3號信號sigaddset(&act.sa_mask , 3);act.sa_flags = 0;sigaction(2, &act, &oact);while (true){std::cout << "I am a process... pid: " << getpid() << std::endl;sleep(1);}return 0;
}

這樣就也屏蔽了3號信號
在這里插入圖片描述
當然如果把所有信號都屏蔽了,肯定是不行的,所以有一部分信號不能被屏蔽,比如9號信號永遠都不能屏蔽!!!

信號處理的總結

對于信號我們學習了三個階段:

  1. 信號的產生與發送:中斷,異常,系統調用。
  2. 信號的保存:三張表:阻塞,未決和遞達
  3. 信號的處理

6 可重入函數

介紹一個新概念:可重入函數。
我們先來看一個情景:
在這里插入圖片描述
這是一個鏈表,我們的inser函數會進行一個頭插,頭插會有兩行代碼:

void insert(node_t* p)
{p->next = head;//------在這里接收到信號-----head = p;
}

我們進行頭插時,進行完第一步之后,突然來了一個信號,但是我們之前說過:信號處理時在用戶態到內核態進行切換時才進行處理,這鏈表的頭插沒有進行狀態的切換啊?其實狀態的切換不一定只能是系統調用方法,在時間片到了(時鐘中斷)之后,也進行了狀態的切換。

而且恰好,該信號的自定義捕捉方法也是insert這時就導致node2插入到了鏈表中,信號處理完之后,頭指針又被掰到node1了,就造成node2丟失了(內存泄漏了)!!!

這就叫做insert函數被重入了!!!

在重入過程中一旦造成了問題,就叫做不可重入函數!!!(因為一旦重入就造成了問題,那當然不能重入了)
絕大部分函數都是不可重入函數!

volatile關鍵字

我們今天在信號的角度再來重溫一下:
volatile 作用:保持內存的可見性,告知編譯器,被該關鍵字修飾的變量,不允許被優化,對該變量的任何操作,都必須在真實的內存中進行操作保持數據可見性!

看這樣一段代碼:

#include <iostream>
#include <signal.h>int flag = 0;
void changdata(int signo)
{std::cout << "get a sig : " << signo << " change flag 0->1"  << std::endl;flag = 1; 
}int main()
{signal(2 , changdata);while(!flag);std::cout << "process quit normal" << std::endl;
}

主函數會一直進行死循環,只有接收到了2號信號才會退出!
在這里插入圖片描述
但當我們進行編譯優化時(因為如果進程不接受到2號信號,那么flag就沒有人來修改,編譯器就認為沒有任何代碼對flag進行修改),共同有四級優化00 01 02 03

而while(!flag)是一個邏輯運算,CPU 一般進行兩種類別計算:算術運算和邏輯運算!會從內存進行讀取,然后進行運算

g++ main main.cc -01

我們再次運行,卻發現,進程不會結束了?!這是為什么!因為優化直接將數據優化到寄存中,因為編譯器認為后續不會進行修改,所以寄存器中的值不會改變,程序只會讀到寄存器中的值。所以就有了volatile關鍵字解決了這樣的問題!!!

Thanks?(・ω・)ノ謝謝閱讀!!!

下一篇文章見

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

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

相關文章

C# 如何獲取屬性的displayName的3種方式

文章目錄 1. 使用特性直接訪問2. 使用GetCustomAttribute()方法通過反射獲取3. 使用LINQ查詢總結和比較 在C#中&#xff0c;獲取屬性的displayName可以通過多種方式實現&#xff0c;包括使用特性、反射和LINQ。下面我將分別展示每種方法&#xff0c;并提供具體的示例代碼。 1.…

數據庫逆向工程工具reverse_sql

reverse_sql 是一個用于解析和轉換 MySQL 二進制日志&#xff08;binlog&#xff09;的工具。它可以將二進制日志文件中記錄的數據庫更改操作&#xff08;如插入、更新、刪除&#xff09;轉換為反向的 SQL 語句&#xff0c;以便對系統或人為產生的誤操作進行數據回滾和恢復。 *…

JVM專題之垃圾收集器

JVM參數 3.1.1 標準參數 -version -help -server -cp 3.1.2 -X參數 非標準參數,也就是在JDK各個版本中可能會變動 ``` -Xint 解釋執行 -Xcomp 第一次使用就編譯成本地代碼 -Xmixed 混合模式,JVM自己來決定 3.1.3 -XX參數 > 使用得最多的參數類型 > > 非…

RedHat運維-Linux文本操作基礎-牛客AWK

1. 查看static這個連接文件是否自動連接的命令是____________________________________&#xff1b; 2. 查看default這個連接文件是否自動連接的命令是_____________________________________&#xff1b; 3. 查看con0這個連接文件是否自動連接的命令是_______________________…

【Python】已解決:(paddleocr導包報錯)ModuleNotFoundError: No module named ‘paddle’

文章目錄 一、分析問題背景二、可能出錯的原因三、錯誤代碼示例四、正確代碼示例五、注意事項 已解決&#xff1a;&#xff08;paddleocr導包報錯&#xff09;ModuleNotFoundError: No module named ‘paddle’ 一、分析問題背景 近日&#xff0c;一些使用PaddleOCR庫進行文字…

Python數據分析案例49——基于機器學習的垃圾郵件分類系統構建(樸素貝葉斯,支持向量機)

案例背景 trec06c是非常經典的郵件分類的數據&#xff0c;還是難能可貴的中文數據集。 這個數據集從一堆txt壓縮包里面提取出來整理為excel文件還真不容不易&#xff0c;肯定要做一下文本分類。 雖然現在文本分類基本都是深度學習了&#xff0c;但是傳統的機器學習也能做。本案…

C#架構師的成長之路

成為一名C#架構師不僅需要深厚的技術功底&#xff0c;還需要廣泛的行業知識、優秀的團隊協作能力和領導力。以下是C#架構師成長之路上的關鍵步驟和技能要求&#xff1a; 1. 扎實的技術基礎 精通C#語言&#xff1a;深入理解C#的語法、特性&#xff0c;包括但不限于泛型、多線程…

Xilinx FPGA:vivado關于真雙端口的串口傳輸數據的實驗

一、實驗內容 用一個真雙端RAM&#xff0c;端口A和端口B同時向RAM里寫入數據0-99&#xff0c;A端口讀出單數并存入單端口RAM1中&#xff0c;B端口讀出雙數并存入但端口RAM2中&#xff0c;當檢測到按鍵1到來時將RAM1中的單數讀出顯示到PC端&#xff0c;當檢測到按鍵2到來時&…

Vim編輯器與Shell命令腳本

前言&#xff1a;本博客僅作記錄學習使用&#xff0c;部分圖片出自網絡&#xff0c;如有侵犯您的權益&#xff0c;請聯系刪除 目錄 一、Vim文本編輯器 二、編寫Shell腳本 三、流程控制語句 四、計劃任務服務程序 致謝 一、Vim文本編輯器 “在Linux系統中一切都是文件&am…

Rust 程序設計語言學習——函數式語言功能:迭代器和閉包

Rust 的閉包&#xff08;closures&#xff09;是可以保存在一個變量中或作為參數傳遞給其他函數的匿名函數。可以在一個地方創建閉包&#xff0c;然后在不同的上下文中執行閉包運算。不同于函數&#xff0c;閉包允許捕獲被定義時所在作用域中的值。 迭代器&#xff08;iterato…

C++ STL 隨機數用法介紹

目錄 一:C語言中的隨機數 二:C++中的隨機數 1. 生成隨機數的例子 2. 隨機數引擎 3. 隨機數引擎適配器 4. C++中預定義的隨機數引擎,引擎適配器 5. 隨機數分布 一:C語言中的隨機數 <stdlib.h>//初始化隨機種子 srand(static_cast<unsigned int>(time(nullptr)…

C#面: 依賴注入有哪幾種方式?

依賴注入&#xff08;Dependency Injection&#xff0c;簡稱DI&#xff09;是一種設計模式&#xff0c;用于解耦組件之間的依賴關系。在C#中&#xff0c;常見的依賴注入方式有以下幾種&#xff1a; 構造函數注入&#xff08;Constructor Injection&#xff09;&#xff1a;通過…

dependencyManagement的作用、nacos的學習

使用SpringCloudAlibaba注意各組件的版本適配 SpringCloudAlibaba已經包含了適配的各組件&#xff08;nacos、MQ等&#xff09;的版本號&#xff0c;也是一個版本仲裁者&#xff0c;但是可能已經有了父項目Spring-Boot-Starter-Parent這個版本仲裁者&#xff0c;又不能加多個父…

什么是獨立服務器?

獨立服務器是指一個單獨的物理服務器&#xff0c;整體的硬件設施都是獨立存在的&#xff0c;有著強大的性能&#xff0c;只需要運行用戶個人的數據信息&#xff0c;并且可以享受到獨立服務器的硬件與軟件&#xff0c;當網站有著大量的用戶進行訪問或者是需要運行大型的軟件時&a…

leetcode熱題100.零錢兌換(動態規劃)

今天給大家分享一道動態規劃的常考題&#xff0c;零錢兌換&#xff0c;很有趣的動態規劃題目&#xff0c;希望可以對大家找工作過程中起到幫助&#xff0c;幫助大家拓展下思維 給你一個整數數組 coins &#xff0c;表示不同面額的硬幣&#xff1b;以及一個整數 amount &#xf…

6、Redis系統-數據結構-06-跳表

六、跳表&#xff08;Skiplist&#xff09; 跳表是一種高效的動態數據結構&#xff0c;可以用于實現有序集合&#xff08;Sorted Set&#xff0c;Zset&#xff09;。與平衡樹相比&#xff0c;跳表具有實現簡單、效率高的優點&#xff0c;因此被 Redis 選用作為有序集合的底層數…

階段三:項目開發---搭建項目前后端系統基礎架構:任務13:實現基本的登錄功能

任務描述 任務名稱&#xff1a; 實現基本的登錄功能 知識點&#xff1a; 了解前端Vue項目的基本執行過程 重 點&#xff1a; 構建項目的基本登陸功能 內 容&#xff1a; 通過實現項目的基本登錄功能&#xff0c;來了解前端Vue項目的基本執行過程&#xff0c;并完成基…

如何讓代碼兼容 Python 2 和 Python 3?Future 庫助你一臂之力

目錄 01Future 是什么? 為什么選擇 Future? 安裝與配置 02Future 的基本用法 1、兼容 print 函數 2、兼容整數除法 3、兼容 Unicode 字符串 03Future 的高級功能 1. 處理字符串與字節 2. 統一異常處理…

linux kthread任務管理

目錄 一、linux 創建內核線程1.1 kthread_create1.2 kthread_create_worker kthread_queue_work 二、設置線程優先級和調度策略2.1 sched_setscheduler2.2 調度策略 一、linux 創建內核線程 1.1 kthread_create 在 linux 中&#xff0c;可以使用 kthread_create 接口創建內核…

移動校園(7)ii:uniapp路由響應攔截器處理token,以及微信小程序報錯當前頁面正在處于跳轉狀態,請稍后再進行跳轉....

依據昨天的寫完&#xff0c;在token過期之后&#xff0c;再次調用接口&#xff0c;會觸發后端攔截&#xff0c;扔進全局錯誤處理中間件 前端說明提示都沒有&#xff0c;只有一個這個&#xff0c;現在優化一下&#xff0c;再寫一個類似全局后置守衛&#xff0c;當狀態碼是401的時…