Linux學習之信號

目錄

1.信號的概念

2.信號的產生

3.信號的保存

4.信號的捕捉

信號的其它內容:

SIGCHLD信號


1.信號的概念

在Linux中,信號是一種用于進程之間通信的基本機制。它是一種異步事件通知,用于通知進程發生了某些事件。如下是一些常見的Linux信號類型:

SIGINT (2):中斷進程,通常由終端產生,例如用戶按下Ctrl+C。
SIGKILL (9):立即終止進程,無法被捕獲或忽略。
SIGTERM (15):請求終止進程,可以被捕獲或忽略。
SIGQUIT (3):請求進程退出并生成核心轉儲文件,可以被捕獲或忽略。
SIGSTOP (17):暫停進程的執行,無法被捕獲或忽略。
SIGCONT (19):恢復進程的執行,無法被捕獲或忽略

?這些信號在進程控制、異常處理和進程間通信中扮演著重要角色。請注意,信號只是通知進程發生了什么事件,并不傳遞任何數據。進程對不同信號有不同的處理方式,可以指定處理函數、忽略或保留系統的默認值。信號機制在Linux編程中非常重要,幫助實現進程之間的協作和控制。

2.信號的產生

先舉兩個樣例:

eg1:

首先我們編寫一個死循環代碼,編譯運行后,我們的命令行就不再有用了,現在是前臺程序,只運行當前的程序,當我們編譯時加上&,使他成為后臺程序,此時的命令行也可以繼續使用,

程序在運行的時候,前臺程序只能有一個,后臺程序可以有多個。后臺程序在運行時,我們的鍵盤可以輸入數據,指令可以運行。

一般操作系統會自動根據情況把shell程序提到前臺或者后臺。下面的指令對shell無效。

前后臺程序切換

./可執行 &? 把程序放到后臺

jobs? 查看后臺任務

fg number(任務編號) 把任務放到前臺

ctrl+z 再加 bg number? ?把后臺任務轉到前臺

ctrl+\ 默認終止

ctrl + z 暫停程序,先放到后臺

而這就是信號的產生,除此之外操作系統知曉鍵盤的輸入也是一種信號:

eg2:當鍵盤的某個按鈕被按下的時候,就會產生高電平信號間接給cpu,cpu得知了之后某個按鈕的高電平,發生中斷,就產生對應的數據。

而信號的產生就是用軟件來模擬中斷行為。我們的指令都是發出信號,

例如接口signal

可以發出我們需要的信號。

如下一段代碼:

#include<iostream>
#include<unistd.h>
#include <signal.h>
#include <stdlib.h>void handler(int signo)
{std::cout<<"獲得一個"<<signo<<"信號"<<std::endl;exit(1);
}int main()
{signal(2,handler);while(true){std::cout<<"pid:"<<getpid()<<",i am running......"<<std::endl;sleep(1);}return 0;
}

?再運行的時候,我們ctrl+z,此時退出進程就會獲得一個為2的信號。

因此信號的產生可以通過鍵盤發出,對于我們的linux也是有許多信號的(kill -l):

其中,沒有0號信號,從1-31的信號我們把它叫做普通信號,沒有32,33信號,從34到64的信號,我們把它叫做實時信號。這些信號的本質就是一些函數指針數組,對應的下標就與他們的編號有關。

對于普通信號,進程是否收到了普通信號,操作系統(pcb中)會用一張位圖來表示,利用位圖中的第幾個比特位表示編號,0表示沒收到,1表示收到。

無論信號有多少種,都是只能讓os來寫(寫)信號,因為os是進程的管理者。

了解到了信號的接收,因此我們在編寫程序時就可以直接發送信號,之后自動運行對應handler方法,例如之前我們使用kill -9殺進程,現在我們發送一個為9的信號,此時自定義它的處理方法,例如只是打印一句話,那么我們kill -9的指令就不會再殺掉我們的進程,而是打印一句話。

但實際上并不可以,操作系統對于某些信號是不可以被自定義捕捉的。

除此以外,Linux提供了三種接口供我們產生信號。

方式一:通過鍵盤組合鍵發送產生信號。

方式二:通過函數接口

接口 raise 可以自己給自己發送任意信號

接口 abort? 收到信號后終止運行

方式三,通過異常:

?以我們熟知的除零錯誤為例,首先除零錯誤并不是語言錯誤,而是進程錯誤,再cpu中通過各個寄存器來計算除零,此時cpu中還有表示狀態的寄存器,當發生除零問題后,狀態寄存器就會產生溢出標記位,從而轉化為信號,就是信號8 SIGFPE? 也就是flaot point exception。

當然發出信號也不僅僅可能是因為異常而導致的,也有可能是鬧鐘響了:

方式四:由軟件條件產生信號:

alarm接口可以設置鬧鐘

#include<iostream>
#include<unistd.h>
#include <signal.h>
#include <stdlib.h>
int cnt=0;
void handler(int signo)
{std::cout<<"獲得一個"<<signo<<"信號"<<"alarm is:"<<cnt<<std::endl;exit(1);
}int main()
{std::cout<<"pid:"<<getpid()<<std::endl;//本質上就是修改函數指針數組的位置signal(14,handler);//設置1s鬧鐘,到點了終止進程alarm(1);while(true){//cout<<cnt++<<endl;  可以看出外設是很慢的cnt++;}}

?操作系統的時間:

當我們電腦關機了,程序結束了,再次重新啟動,我們會發現,時間永遠是跟著走的,實際上,即使關機了,在電腦里也會有一個紐扣電池一直給硬件供電,固定時間間隔計數,再將計數器轉換為時間戳給我們的電腦。CMOS周期性的高頻的發送時間中斷。

3.信號的保存

. 信號其他相關常見概念
實際執行信號的處理動作稱為信號遞達 (Delivery)
信號從產生到遞達之間的狀態 , 稱為信號未決 (Pending)
進程可以選擇阻塞 (Block ) 某個信號。
被阻塞的信號產生時將保持在未決狀態 , 直到進程解除對此信號的阻塞 , 才執行遞達的動作 .
注意 , 阻塞和忽略是不同的 , 只要信號被阻塞就不會遞達 , 而忽略是在遞達之后可選的一種處理動作

?遞達就是開始處理信號,當信號被記錄再為途中時就是信號未決狀態,阻塞:被阻塞的信號一直處在未決狀態,只有當阻塞取消時,才進入遞達狀態。

阻塞與忽略是有區別的,忽略本身沒有阻塞而是遞達,處理了信號,效果為忽略,而阻塞是沒有抵達,且沒處理。

了解了以上概念,因此再管理信號的狀態時,os就需要維護這三張位圖表,用來表示阻塞,未決,遞達這三個狀態的信號。

比特位的位置:代表信號的編號

比特位的內容:對特定信號進行阻塞還是屏蔽。?

每個信號都有兩個標志位分別表示block(阻塞)和pending(未決),其次還有一個函數指針表示要處理的方法。

void handler(int signo)
{cout<<"signo is "<<signo<<endl;exit(1);
}
int main()
{//發送2信號signal(2,signo);//把信號的粗粒設置為原來默認的signal(2,SIG_DFL);//當然還可以把信號忽略signal(2,SIG_IGN);std::cout<<"my pid id:"<<getpid()<<endl;while(true){cout<<"i am running....."<<endl;sleep(1);}}

由于有這么多信號集,操作系統還提供了許多信號及操作接口:

sigset_t 類型對于每種信號用一個 bit 表示 有效 無效 狀態 , 至于這個類型內部如何存儲這些 bit 則依賴于系統 實現, 從使用者的角度是不必關心的 , 使用者只能調用以下函數來操作 sigset_ t 變量 , 而不應該對它的內部數據做 任何解釋, 比如用 printf 直接打印 sigset_t 變量是沒有意義的。
#include <signal.h>
int sigemptyset(sigset_t *set);? //對指定的位圖進行清零
int sigfillset(sigset_t *set);? ?//對指定的位圖進行置1
int sigaddset (sigset_t *set, int signo); //對指定信號添加到指定的位圖中
int sigdelset(sigset_t *set, int signo);
int sigismember const sigset_t *set, int signo); //判定一個信號是否在為位圖中

對于block表的修改:

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

如下代碼:


int main()
{//例如對2號信號屏蔽cout<<"my pid is"<<getpid()<<endl;//先定義兩個信號集位圖sigset_t block,oblock;//先對信號集清空sigemptyset(&block);sigemptyset(&oblock);//其次對2號信號添加到信號集sigaddset(&block,2);  //當前并沒有讓操作系統2信號屏蔽,只是語言層面的定義sigaddset(&oblock,2);sigprocmask(SIG_BLOCK,&block,&oblock);   //真正讓操作系統屏蔽、更改信號while(true){sleep(1);}return 0;
}

?此時我們再發2號信號就沒有作用了,ctrl+c也無法中斷程序。

既然如此,那么我們是否可以將一個程序的所有信號屏蔽,這樣他就有金剛不壞之身,誰也干不掉他,實際上并是不是所有的信號你都能屏蔽,就跟不是所有的信號的處理可以自定義是一樣的。

比如說9號信號就無法被屏蔽。

那么pending表的修改:接口 sigpending

重要的是獲取pending表.

接下來我們用一個整體的實例來認識這些接口:

void printpending(const sigset_t &pending)
{for(int signo=31;signo>0;signo--){if(sigismember(&pending,signo)){cout<<"1";}else{cout<<"0";}}cout<<"\n";
}
//自定義捕捉
void handler(int signo)
{cout<<"已接受到信號"<<signo<<endl;//exit(1);}int main()
{//例如對2號信號屏蔽cout<<"my pid is"<<getpid()<<endl;signal(2,handler);//先定義兩個信號集位圖sigset_t block,oblock;//先對信號集清空sigemptyset(&block);sigemptyset(&oblock);//其次對2號信號添加到信號集sigaddset(&block,2);  //當前并沒有讓操作系統2信號屏蔽,只是語言層面的定義sigaddset(&oblock,2);sigprocmask(SIG_BLOCK,&block,&oblock);   //真正讓操作系統屏蔽、更改信號//下打印pending表int cnt=0;sigset_t pending;while(true){sigpending(&pending);printpending(pending);sleep(1);cnt++;if(cnt==5){//直到5S,解除2信號的屏蔽cout<<"解除對2號信號的屏蔽,2號準備抵達"<<endl;sigprocmask(SIG_SETMASK,&oblock,nullptr); //設置為舊的信號 }}return 0;
}

?運行結果如圖:

4.信號的捕捉

信號在什么時候去被捕捉處理呢,在合適的時候---從內核態返回到用戶態的時候,進行信號的檢測和信號的處理。

內核態:內核態是操作系統的一種狀態,能夠大量訪問資源

用戶態:用戶態是一種受控的轉臺,能夠訪問的資源是有限

用戶想要訪問操作系統只能通過系統調用的方式訪問。

首先無論進程如何調度,cpu都會找到os,我們的進程的所有代碼的執行,都可以在地址空間中通過跳轉的方式進行調用和返回。

?那么對于系統的信號的捕捉,首先介紹第一個接口sigaction

第三個參數表示把舊的handler表返回給我,達爾戈參數就是新的handler的設置,第一個參數為信號編號,接口的作用是檢測和修改信號動作。

返回類型是sigaction的結構體類型,其中有五個字段。其中我們比較重點關注的是sa_mask字段,

如果在調用信號處理函數時,除了當前信號被屏蔽外,還希望屏蔽些別的信號,此時sa_mask就是需要被額外屏蔽的信號。

以該代碼為例:

#include<signal.h>
#include<unistd.h>
#include<iostream>
using namespace std;
void print(sigset_t &pending);
void handler(int signo)
{cout<<"接收到信號"<<signo<<"......"<<endl;while(true){//獲取當前pending列表sigset_t pending;sigpending(&pending);print(pending);sleep(1);}
}
void print(sigset_t &pending)
{for(int signo=31;signo>0;signo--){if(sigismember(&pending,signo)){cout<<"1";}else{cout<<"0";}}cout<<endl;
}
int main()
{cout<<"my pid is "<<getpid()<<endl;//定義新的與舊的actstruct sigaction act,oact;//設置handler為當前自定義的處理方法act.sa_handler=handler;sigaction(2,&act,&oact);while(true) sleep(1);return 0;
}

用改接口接受2號信號時,和之前一樣,運行程序,第一次我們ctrl+c,發出2信號時接收到2好信號,但自此之后的2好信號都被屏蔽掉了,再次crtl+c時,信號無法被接受處于未決狀態。

例如:當我們要修改信號2時,這里默認會自動屏蔽信號2,如下圖

信號的其它內容:

可重入函數

數被不同的控制流程調用 , 有可能在第一次調用還沒返回時就再次進入該函數 , 這稱 為重入,insert 函數訪問一個全局鏈表 , 有可能因為重入而造成錯亂 , 像這樣的函數稱為 不可重入函數 , 反之 , 如果一個函數只訪問自己的局部變量或參數, 則稱為可重入 (Reentrant) 函數。
如果一個函數符合以下條件之一則是不可重入的 :
調用了 malloc free, 因為 malloc 也是用全局鏈表來管理堆的。
調用了標準 I/O 庫函數。標準 I/O 庫的很多實現都以不可重入的方式使用全局數據
在這里我們就這樣理解,住執行流與信號捕捉流是兩種不同的流。
關鍵字volatile
volatile 作用:保持內存的可見性,告知編譯器,被該關鍵字修飾的變量,不允許被優化,對該變量 的任何操作,都必須在真實的內存中進行操作。
那么對于信號有什么作用呢?
int flag = 0;
void handler(int sig)
{printf("chage flag 0 to 1\n");flag = 1;
}
int main()
{signal(2, handler);while(!flag);printf("process quit normal\n");return 0;
}
優化情況下,鍵入 CTRL - C ,2 號信號被捕捉,執行自定義動作,修改 flag 1 ,但是 while 條件依舊滿足 , 進 程繼續運行!但是很明顯flag 肯定已經被修改了,但是為何循環依舊執行?很明顯, while 循環檢查的 flag , 并不是內存中最新的flag ,這就存在了數據二異性的問題。 while 檢測的 flag 其實已經因為優化,被放在了 CPU寄存器當中。如何解決呢?很明顯需要 volatile。
實際中在gcc中,也是有自帶優化的選項。

SIGCHLD信號

我們?早已經了解到子進程在退出的時候,是要給父進程發送退出信息的,不然父進程還要維護一份沒必要的資源,而子進程是給父進程發送什么樣的信號呢?---SIGCHLD

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handler(int sig)
{pid_t id;//收到退出信號  等待子進程while( (id = waitpid(-1, NULL, WNOHANG)) > 0){printf("wait child success: %d\n", id);}printf("child is quit! %d\n", getpid());
}
int main()
{signal(SIGCHLD, handler);pid_t cid;if((cid = fork()) == 0){//childprintf("child : %d\n", getpid());sleep(3);exit(1);}while(1){printf("father proc is running\n");sleep(1);}return 0;
}

可以看到子進程退出時,時回給父進程發信號的。

在Linux中支持手動忽略信號SIGCHDL,可以不用wait子進程。退出自動回收。

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

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

相關文章

[計算機網絡]--五種IO模型和select

前言 作者&#xff1a;小蝸牛向前沖 名言&#xff1a;我可以接受失敗&#xff0c;但我不能接受放棄 如果覺的博主的文章還不錯的話&#xff0c;還請點贊&#xff0c;收藏&#xff0c;關注&#x1f440;支持博主。如果發現有問題的地方歡迎?大家在評論區指正 目錄 一、五種IO…

線性規劃問題的高斯消元法

線性規劃的算法和解方程組的方法很像,常用的方程組的解法叫做高斯消元法,對于高斯消元法的基本流程,現給定一組線性方程: 添加圖片注釋,不超過 140 字(可選) 對于給定的線性方程組,目的是將方程組中同時能夠滿足三個等式的變量x,y,z求解出來,對于高斯消元法的基本過程…

【精通Spring】基于注解管理Bean

個人名片&#xff1a; &#x1f43c;作者簡介&#xff1a;一名大三在校生&#xff0c;喜歡AI編程&#x1f38b; &#x1f43b;???個人主頁&#x1f947;&#xff1a;落798. &#x1f43c;個人WeChat&#xff1a;hmmwx53 &#x1f54a;?系列專欄&#xff1a;&#x1f5bc;?…

集智書童 | YOLO+混合注意力機制 | YOLOv5再加4.3%才可以做對手,Transformer混合設計依舊可以卷

本文來源公眾號“集智書童”&#xff0c;侵權刪&#xff0c;干貨滿滿。YOLOv5重出江湖&#xff01; 原文鏈接&#xff1a;https://mp.weixin.qq.com/s/vb7HsA0fKDgRc3uC8Z-2yw 在工業生產過程中&#xff0c;由于低效率、不統一的評估、高成本以及缺乏實時數據&#xff0c;傳統…

LeetCode //C - 32. Longest Valid Parentheses

32. Longest Valid Parentheses Given a string containing just the characters ‘(’ and ‘)’, return the length of the longest valid (well-formed) parentheses substring. Example 1: Input: s “(()” Output: 2 Explanation: The longest valid parentheses s…

【刷題1】LeetCode 994. 腐爛的橘子 java題解

tag:圖論 廣度優先搜索 https://leetcode.cn/problems/rotting-oranges/description/?envTypestudy-plan-v2&envIdtop-100-liked 使用廣度優先搜索&#xff0c;搜索步數就是分鐘數&#xff0c;等到所有橘子都腐爛后&#xff0c;各個橘子腐爛的最長分鐘數就是全部都爛的最小…

C語言-指針(上)

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 前言一、pandas是什么&#xff1f;二、使用步驟 1.引入庫2.讀入數據總結 前言 本篇文章將為大家介紹C語言中的核心內容-指針&#xff0c;指針在C語言的中知識內容比…

【文件管理】關于上傳下載文件的設計

這里主要談論的是產品設計里面的文件管理&#xff0c;比如文件的上傳交互及背后影響到的前后端設計。 上傳文件 場景&#xff1a;一條記錄&#xff0c;比如個人信息&#xff0c;有姓名&#xff0c;出生年月&#xff0c;性別等一般的字段&#xff0c;還可以允許用戶上傳附件作為…

Java 小項目開發日記 04(文章接口的開發、oss圖片上傳)

Java 小項目開發日記 04&#xff08;文章接口的開發、oss圖片上傳&#xff09; 項目目錄 配置文件&#xff08;pom.xml&#xff09; <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:sc…

機器學習:集成學習(Python)

一、Adaboost算法 1.1 Adaboost分類算法 adaboost_discrete_c.py import numpy as np import copy from ch4.decision_tree_C import DecisionTreeClassifierclass AdaBoostClassifier:"""adaboost分類算法&#xff1a;既可以做二分類、也可以做多分類&#…

python常用pandas函數nlargest 和 nsmallest及其手動實現

pandas是Python數據分析的重要工具之一&#xff0c;提供了大量便捷的數據操作方法。nlargest和nsmallest是pandas中兩個非常實用的函數&#xff0c;它們可以幫助我們快速找出Series或DataFrame中最大或最小的n個值。 ### pandas中的nlargest和nsmallest函數 - nlargest(n, colu…

掌握Go語言:深入探究Go語言中的命令源碼文件與參數處理技巧(3)

在Go語言學習的路上&#xff0c;掌握命令源碼文件與參數處理技巧是至關重要的。本文將深入探討命令源碼文件的概念、作用以及參數處理的方法&#xff0c;同時結合進銷存項目&#xff0c;展示實際應用與代碼示例。 命令源碼文件的概述 命令源碼文件是Go語言程序的運行入口&…

uniapp的h5端在線預覽文件

步驟如下&#xff1a; 1、下載需要準備的工具文件包 2、將其解壓到/static/pdf文件夾下,如圖&#xff1a; 3、創建在線查看文件的頁面&#xff1a; <template><view><web-view :src"path"></web-view></view> </template>&l…

linux檢測和重啟python腳本

#!/bin/bash# 檢測Flask應用是否掛了 if ! pgrep -f "flask_app.py" >/dev/null; then# 重啟Flask應用cd /path/to/your/flask/appnohup python3 flask_app.py >/dev/null 2>&1 & fi這是一個簡單的bash腳本&#xff0c;用于檢測Flask應用是否掛掉&a…

JavaScript練手小技巧:一文看懂<script>標簽的 ansyc 和 defer

<script>標簽的 ansyc 和 defer 屬性。只對外部加載 JS 文件有效。 <script src"js/app.js" async></script> <script src"js/app.js" defer></script> 普通加載 js&#xff08;同步加載&#xff09;&#xff1a;會打斷 …

ES7、ES8、ES9、ES10、ES11、ES12 新特性匯總合集

在過去幾年里&#xff0c;ECMAScript 標準不斷更新&#xff0c;引入了許多令人激動的功能和改進。讓我們來看看從 ES7 到 ES12 各個版本帶來的重要變化&#xff1a; 1. ES7&#xff08;ECMAScript 2016&#xff09; 1. Array.prototype.includes 方法 Array.prototype.includ…

【字符串左旋右旋不會做?快來看看這篇“彈幕滾動”,你就有思路了!】

前言 不知道大家在做題時有沒有遇到過“字符串旋轉”的題目&#xff0c;很多人可能沒有思路&#xff0c;這里我不具體講解單一的題目&#xff0c;而是展現一個“彈幕滾動”的示例&#xff0c;相信大家看懂后就能做出“字符串旋轉”的題了&#xff01; 技術名詞解釋 1.什么是“…

關于決策樹模型

決策樹模型是一種常用的數據挖掘方法&#xff0c;它通過模擬人類決策過程來對數據進行分類或回歸分析。決策樹由節點和邊組成&#xff0c;其中每個內部節點代表一個屬性上的測試&#xff0c;每個分支代表測試的一個結果&#xff0c;而每個葉節點&#xff08;樹的末端&#xff0…

Vue3 isProxy,isReactive,isReadonly 三者解析

1、isProxy 作用&#xff1a;判斷當前數據是否為代理數據。 注意&#xff1a;它只對通過 reactive&#xff0c;readonly&#xff0c;shallowReactive&#xff0c;shallowReadonly 這四個方法包裹的數據返回true&#xff0c;對于 ref 以及通過 new Proxy 代理的數據返回都是fal…

ChatGPT科研與AI繪圖及論文高效寫作教程

原文鏈接&#xff1a;ChatGPT科研與AI繪圖及論文高效寫作教程 2023年隨著OpenAI開發者大會的召開&#xff0c;最重磅更新當屬GPTs&#xff0c;多模態API&#xff0c;未來自定義專屬的GPT。微軟創始人比爾蓋茨稱ChatGPT的出現有著重大歷史意義&#xff0c;不亞于互聯網和個人電…