Linux操作系統5-進程信號2(信號的4種產生方式,signal系統調用)

上篇文章:Linux操作系統5-進程信號1(信號基礎)-CSDN博客

本篇Gitee倉庫:myLerningCode/l25 · 橘子真甜/Linux操作系統與網絡編程學習 - 碼云 - 開源中國 (gitee.com)

本篇重點:信號的4種產生

目錄

一. signal系統調用

二. 產生信號的4種方式

2.1 終端按鍵產生信號

?2.2 系統調用/命令產生信號

a kill調用向其他進程發送信號

b raise向自己發送信號

2.3 硬件異常產生信號

a 除 0 異常

b 空指針解引用?

2.4 軟件產生信號

a pipe 讀端退出,寫端立馬退出

b alarm定時器產生信號?


一. signal系統調用

? ? ? ? signal系統調用可以幫助我們自定義信號的行為

//所需頭文件
#include <signal.h>//函數原型    當進程收到signum這個信號之后,執行handler中的代碼
typedef void(* sighandler_t)(int)    //函數指針
sighandler_t signal(int signum, sighandler_t handler);//參數說明
signum    需要自定義行為的信號編號
handler   自定義行為的函數//當某一個進程使用了signal系統調用之后,捕捉signum編號的信號就會執行下面的自定義行為
void handler(int signum)
{//由程序員自定義
}

舉例代碼:

我們自定義了2號信號的行為(ctrl c?發送的信號就是2號信號

#include <iostream>#include <unistd.h>
#include <signal.h>void handler(int signum)
{while(true){printf("進程[%d]收到信號[%d]\n",getpid(),signum);sleep(1);}
}int main()
{signal(2, handler);while (true){std::cout << "進程pid:" << getpid() << std::endl;sleep(1);}return 0;
}

????????我們定義一個死循環的進程,并且自定義2號信號的行為。如果該進程收到2號信號那么他就會執行handler?中的死循環代碼

測試結果如下:

?

我們輸入ctrl c 來測試一下。

?

可以看到,輸入ctrl c之后該進程收到2號信號。并且再次ctrl c 之后仍執行自定義行為的代碼

注意:在我們調用signal之后并不會執行handler中的方法,而是在收到2號信號后再調用

二. 產生信號的4種方式

2.1 終端按鍵產生信號

常見的比如 ctrl c 向當前的前臺進程發送2號信號,ctrl \ 向當前前臺進程發送3號信號。

代碼測試:

我們自定義2號和3號信號的行為來測試 ctrl c 和 ctrl \:

#include <iostream>#include <unistd.h>
#include <signal.h>void handler(int signum)
{while(true){printf("進程[%d]收到信號[%d]\n",getpid(),signum);sleep(1);}
}int main()
{//同時自定義2號信號和3號信號的行為signal(2, handler);signal(3, handler);while (true){std::cout << "進程pid:" << getpid() << std::endl;sleep(1);}return 0;
}

?

?2.2 系統調用/命令產生信號

? ? ? ? 命令產生信號我們經常使用,就是 kill 信號 pid 即可向對應的進程發送對應的信號

a kill調用向其他進程發送信號

? ? ? ? kill不僅僅在命令中可以發送信號,也能在代碼中使用

//頭文件
#include <sys/types.h>
#include <signal.h>//函數原型
int kill(pid_t pid, int signum);//參數
向 pid 這個進程編號的進程發送 signum 這個編號的信號//返回值
成功返回0,失敗返回-1,并且設置錯誤碼

測試代碼:

mykill.cpp

#include <iostream>
#include <sys/types.h>
#include <signal.h>void Usage(const std::string &proc)
{std::cout << "Usage\n"<< proc << "pid signum\n ";
}int main(int argc, char *argv[])
{if (argc != 3)Usage(argv[0]);pid_t pid = atoi(argv[1]);int signo = atoi(argv[2]);int n = kill(pid, signo);if(n < 0){std::cout << "kill error"<<std::endl;}return 0;
}

該代碼通過命令行參數獲取鍵盤輸入的信息,解析后執行kill

test.cpp?

#include <iostream>#include <unistd.h>
#include <signal.h>void handler(int signum)
{while (true){printf("進程[%d]收到信號[%d]\n", getpid(), signum);sleep(1);}
}int main()
{// 同時自定義2號信號和9號信號的行為signal(2, handler);signal(3, handler);while (true){std::cout << "進程pid:" << getpid() << std::endl;sleep(1);}return 0;
}

可以看到,我們可以通過kill系統調用向其他進程發送信號

b raise向自己發送信號

#include <signal>int rasie(int sig);//給自己發送sig這個信號

測試代碼:

通過raise向自己發送3號信號?

#include <iostream>#include <unistd.h>
#include <signal.h>void handler(int signum)
{while (true){printf("進程[%d]收到信號[%d]\n", getpid(), signum);sleep(1);}
}int main()
{// 同時自定義2號信號和9號信號的行為signal(3, handler);int cnt = 0;while (true){std::cout << "進程pid:" << getpid() << "[" << cnt++ << "]" << std::endl;if (cnt == 5)raise(3);sleep(1);}return 0;
}

測試結果:

可以看到,第5次的時候,收到3號信號執行自定義行為。

2.3 硬件異常產生信號

? ? ? ? 信號不一定由用戶發出,也有可能由OS發出。比如我們的 /0操作,越界訪問操作。

a 除 0 異常

#include <iostream>
#include <string>#include <sys/types.h>
#include <signal.h>
#include <unistd.h>int main()
{// 3.硬件異常產生信號// 信號產生,不一定由用戶顯示發送。有可能由操作系統自動產生while (true){std::cout << "我正在運行..." << std::endl;sleep(1);int a = 10;a /= 0;}return 0;
}

運行結果如下:

可以看到進程收到了 Floating point exception。這個其實就是8號信號

可以自定義8號信號的行為來證明:

#include <iostream>
#include <string>#include <sys/types.h>
#include <signal.h>
#include <unistd.h>void catchSig(int signo)
{std::cout << "獲取一個信號編號,編號是:" << signo << std::endl;
}int main()
{// 3.硬件異常產生信號// 信號產生,不一定由用戶顯示發送。有可能由操作系統自動產生signal(SIGFPE, catchSig);while (true){std::cout << "我正在運行..." << std::endl;sleep(1);int a = 10;a /= 0; // 為什么除0 會終止進程? 當前進程會收到來自OS的信號}return 0;
}

運行結果如下:

可以看到,該進程收到了8號信號。

可是為什么一直打印這條信息呢?我們沒有寫死循環

分析如下:

1 OS怎么知道該進程? \0 了?

? ? ? ? 因為在cpu中有一個狀態寄存器,這個寄存器中有一個狀態標志位。如果我們有 \0 運算,就會導致結果溢出,此時這個寄存器就會將標志位由 0 設置為 1。說明該進程發生了運算異常。

? ? ? ? 當OS發現某一個進程的狀態標志位是1,就會向其發送8號信號終止它!

2 為什么會一直打印信息?

? ? ? ? 一個進程不會一直占用CPU。當發送進程調度的時候,這個進程可能會被調走。此時進程會將自己的上下文信息保存到PCB中。當進程切換切換回來的時候,這個進程的狀態標志位還是1,OS仍會向其發送8號信號,繼續打印這條信息!

b 空指針解引用?

#include <iostream>
#include <string>#include <sys/types.h>
#include <signal.h>
#include <unistd.h>void catchSig(int signo)
{std::cout << "獲取一個信號編號,編號是:" << signo << std::endl;
}int main()
{ signal(SIGFPE, catchSig);while (true){std::cout << "我正在執行代碼" << std::endl;sleep(1);int *p = nullptr;*p = 1; //野指針}return 0;
}

運行結果如下:

?可以看到,顯示段錯誤。收到11號信號(非法訪問內存)

原因分析:

? ? ? ? 我們的指針都是在虛擬內存上的,虛擬內存通過頁表和MMU的映射到物理內存上(MMU是集成在CPU上的)。當我們發送非法訪問的時候,MMU就會發送硬件異常,OS向進程發送11號信號進行終止。

? ? ? ? 不斷打印的原因如上面。

2.4 軟件產生信號

a pipe 讀端退出,寫端立馬退出

? ? ? ? 在這篇文章中,我們看到。管道的讀端退出,寫端會收到13號信號退出

Linux操作系統4-進程間通信1(通信與管道實現通信)-CSDN博客

這就是一種軟件異常產生的信號

b alarm定時器產生信號?

//頭文件
#include <unistd.h>//函數原型
unsigned int alarm(unsigned int seconds);//使用alarm可以設定鬧鐘,在輸入的參數 seconds 秒之后
//OS會向當前進程發送 SIGALRM 信號,該信號的默認行為是終止該進程

測試代碼:?

#include <iostream>
#include <string>#include <sys/types.h>
#include <signal.h>
#include <unistd.h>void catchSig(int signo)
{std::cout << "獲取一個信號編號,編號是:" << signo << std::endl;exit(1);
}int main()
{alarm(10);int count = 0;while (1){std::cout << "hello world! " << count++ << std::endl;sleep(1);}return 0;
}

運行結果:?

可以看到10秒后,進程收到14號信號退出?

? ? ? ? 通過alarm定義鬧鐘我們可以寫出很多有用的代碼。

????????任意一個進程都能通過alarm向OS中設置鬧鐘,OS會周期性檢測這些鬧鐘,當鬧鐘到了之后OS就會向設置鬧鐘的進程發送信號。

????????這種超時的行為,全部是由軟件構成的。所以稱為 軟件條件產生信號

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

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

相關文章

如何在docker中的mysql容器內執行命令與執行SQL文件

通過 docker ps -a 查詢當前運行的容器&#xff0c;找到想執行命令的容器名稱。 docker ps -a若想執行sql文件&#xff0c;則將sql文件放入當前文件夾下后將項目內的 SQL 文件拷貝到 mysql 容器內部的 root下。 sudo docker cp /root/enterprise.sql mysql:/root/然后進入 my…

STM32 RTC實時時鐘詳解與HAL庫實戰教程

摘要&#xff1a;本文深入講解STM32的RTC&#xff08;Real-Time Clock&#xff09;模塊&#xff0c;涵蓋原理分析、CubeMX配置、HAL庫編程實現&#xff0c;并提供完整的鬧鐘設置與時間校準例程代碼。通過本文&#xff0c;您將掌握RTC在低功耗場景下的核心應用技巧。 1. RTC模塊…

Spring Boot攔截器(Interceptor)與過濾器(Filter)詳細教程

Spring Boot攔截器&#xff08;Interceptor&#xff09;與過濾器&#xff08;Filter&#xff09;詳細教程 目錄 概述 什么是攔截器&#xff08;Interceptor&#xff09;&#xff1f;什么是過濾器&#xff08;Filter&#xff09;&#xff1f;兩者的核心區別 使用場景 攔截器的典…

Tauri跨平臺開發問題及解決方案深度解析(React版)

Tauri跨平臺開發問題及解決方案深度解析&#xff08;React版&#xff09; 一、環境配置與項目初始化難題&#xff08;React適配&#xff09; 1.1 React項目初始化 推薦模板&#xff1a; # 使用ReactTypeScript模板 npm create tauri-applatest -- --template react-ts# 項目…

AIGC和搜索引擎的異同

AIGC&#xff08;生成式人工智能&#xff09;與搜索引擎的核心差異體現在信息處理方式和輸出形態上&#xff0c;我們可以從以下維度對比&#xff1a; 一、工作原理的本質差異 信息檢索機制 搜索引擎&#xff1a;基于關鍵詞匹配&#xff08;如"中暑怎么辦"→返回相關…

SFT與RLHF的關系

在大模型訓練中&#xff0c;SFT&#xff08;監督微調&#xff09;和RLHF&#xff08;基于人類反饋的強化學習&#xff09;是相互關聯但目標不同的兩個階段&#xff0c;通常需要結合使用以優化模型性能&#xff0c;而非互相替代。以下是關鍵要點&#xff1a; 1. 核心關系 SFT&…

C# 類型轉換

C# 類型轉換 引言 在C#編程語言中&#xff0c;類型轉換是一種將一個數據類型的變量轉換成另一個數據類型的操作。類型轉換是編程中常見的操作&#xff0c;特別是在處理不同數據類型的變量時。本文將詳細探討C#中的類型轉換&#xff0c;包括隱式轉換和顯式轉換&#xff0c;以及…

提升系統效能:從流量控制到并發處理的全面解析

在當今快速發展的數字時代&#xff0c;無論是構建高效的網絡服務、管理海量數據&#xff0c;還是優化系統的并發處理能力&#xff0c;都是技術開發者和架構師們面臨的重大挑戰。本文集旨在深入探討幾個關鍵技術領域&#xff0c;包括用于網絡通信中的漏桶算法與令牌桶算法的原理…

Git GitHub基礎

git是什么&#xff1f; Git是一個分布式版本控制系統&#xff0c;用于管理源代碼的變更。它允許多個開發者在同一個項目上協作&#xff0c;同時跟蹤每個修改的歷史記錄。 關鍵詞&#xff1a; 分布式版本控制軟件 軟件 安裝到我們電腦上的一個工具 版本控制 例如論文&…

派可數據BI接入DeepSeek,開啟智能數據分析新紀元

派可數據BI產品完成接入DeepSeek&#xff0c;此次接入標志著派可數據BI在智能數據分析領域邁出了重要一步&#xff0c;將為用戶帶來更智能、更高效、更便捷的數據分析體驗。 派可數據BI作為國內領先的商業智能解決方案提供商&#xff0c;一直致力于為用戶提供高效、穩定易擴展…

Linux-ftrace-雙nop機制的實現

Linux 內核調試工具ftrace 之&#xff08;NOP動態插樁的實現原理&#xff09; ftrace 是 Linux 內核中的一種跟蹤工具&#xff0c;主要用于性能分析、調試和內核代碼的執行跟蹤。它通過在內核代碼的關鍵點插入探針&#xff08;probe&#xff09;來記錄函數調用和執行信息。這對…

Qt互斥鎖(QMutex)的使用、QMutexLocker的使用

Qt互斥鎖【QMutex】的使用、QMutexLocker的使用 基于讀寫鎖(QReadWriteLock)的線程同步Chapter1 Qt互斥鎖(QMutex)的使用、QMutexLocker的使用一、QMutexLocker和QMutex實現示例圖二、QMutex和QMutexLocker的關系&#xff08;個人理解&#xff09;三、QMutex使用和QMutexLocker…

【無標題】Ubuntu22.04編譯視覺十四講slambook2 ch4時fmt庫的報錯

Ubuntu22.04編譯視覺十四講slambook2 ch4時fmt庫的報錯 cmake ..順利&#xff0c;make后出現如下報錯&#xff1a; in function std::make_unsigned<int>::type fmt::v8::detail::to_unsigned<int>(int): trajectoryError.cpp:(.text._ZN3fmt2v86detail11to_unsi…

SpringBoot ——簡單開發流程實戰

本文使用SpringBoot進行電商系統商品數據增刪改查的簡單開發流程。 本文目錄 一、創建Spring Boot項目二、配置數據庫連接三、創建實體類四、創建Repository接口五、創建Service層六、創建Controller層七、測試 一、創建Spring Boot項目 可以通過https://start.spring.io/或者…

fastadmin 后臺商品sku(vue)

先上個效果圖 首先先引入vue define([backend], function (Backend) {require.config({paths: {vue: /assets/jeekshopskugoods/libs/vue.min,skuimg: /assets/jeekshopskugoods/js/skuimg,skugoods: /assets/jeekshopskugoods/js/skugoods,layui: /assets/LayuiSpzj/layui/la…

LeetCode 718 - 最長重復子數組

LeetCode 718 - 最長重復子數組 是一個典型的數組和字符串問題&#xff0c;適合考察動態規劃、滑動窗口和二分查找等多種編程能力。掌握其多種解法及變體能夠有效提高處理字符串和數組算法的能力。 題目描述 輸入: 兩個整數數組 nums1 和 nums2。輸出: 兩個數組中存在的最長的…

LeetCode 0132.分割回文串 II:動態規劃

【LetMeFly】132.分割回文串 II&#xff1a;動態規劃 力扣題目鏈接&#xff1a;https://leetcode.cn/problems/palindrome-partitioning-ii/ 給你一個字符串 s&#xff0c;請你將 s 分割成一些子串&#xff0c;使每個子串都是回文串。 返回符合要求的 最少分割次數 。 示例 …

iOS 實現UIButton自動化點擊埋點

思路&#xff1a;我們HOOK UIControl的 addtarget:action:forControlEvents方法&#xff0c;交換UIControl的 addtarget:action:forControlEvents 方法的實現&#xff0c; 在交換的方法中添加原來響應的同時&#xff0c;再添加一個埋點響應&#xff0c;該響應方法實現了點擊埋點…

C++藍橋杯基礎篇(六)

片頭 嗨~小伙伴們&#xff0c;大家好&#xff01;今天我們來一起學習藍橋杯基礎篇&#xff08;六&#xff09;&#xff0c;練習相關的數組習題&#xff0c;準備好了嗎&#xff1f;咱們開始咯&#xff01; 第1題 數組的左方區域 這道題&#xff0c;實質上是找規律&#xff0c;…

git -學習筆記

目錄 基本操作語法 設置用戶和郵箱 版本回退 工作區和暫存區 撤銷修改 刪除與恢復 一工作區刪除了&#xff0c;但是暫存區沒刪除 二工作區誤刪了&#xff0c;暫存區還有 github-Git 連接 報錯解決-push遠程倉庫被拒絕 遠程庫 分支 分支沖突 儲藏分支 回到當前分…