知名企業(yè)網(wǎng)站建設(shè)哈爾濱網(wǎng)站制作軟件
前言
作者:小蝸牛向前沖
名言:我可以接受失敗,但我不能接受放棄
??如果覺的博主的文章還不錯(cuò)的話,還請
點(diǎn)贊,收藏,關(guān)注👀支持博主。如果發(fā)現(xiàn)有問題的地方歡迎?大家在評論區(qū)指正
目錄
一、信號基礎(chǔ)知識
1、信號是什么
2、信號的定義
3、信號的處理方式?
二、有關(guān)信號操作的函數(shù)
1、signal函數(shù)(捕捉信號)
2、kill函數(shù)
3、raise函數(shù)
4、abort函數(shù)
三、信號的產(chǎn)生?
1、通過終端按鍵產(chǎn)生信號
2、調(diào)用系統(tǒng)函數(shù)向進(jìn)程發(fā)信號
3、硬件異常產(chǎn)生信號?
4、由軟件條件產(chǎn)生信號?
本期學(xué)習(xí)目標(biāo):?了解什么是信號,明白部分信號操作的相關(guān)函數(shù),理解信號產(chǎn)生的過程
一、信號基礎(chǔ)知識
1、信號是什么
? ? ? ? 在日常生活中,我們在遇到十字路口過馬路,我們知道紅燈停綠燈行。為什么我們能夠知道紅燈停綠燈行呢?這不難理解,這些規(guī)則都刻畫在我們的腦海中了,也就是說紅燈其實(shí)就是一種信號,當(dāng)我們識別紅燈信號,大腦就會(huì)做出反應(yīng)控制身體不動(dòng),當(dāng)然有時(shí)候會(huì)人闖紅燈,信號肯定是會(huì)傳遞了的,也就是說其實(shí)信號也是可以被忽略的。
那在Linux下的信號又是指的是什么
下面我們見一個(gè)現(xiàn)象
這里我們運(yùn)行程序,當(dāng)我們按下Ctrl+c將的時(shí)候,我們發(fā)現(xiàn)進(jìn)程退出。
?這也就說明,操作系統(tǒng)肯定給進(jìn)程發(fā)送了一個(gè)信號。
操作系統(tǒng)發(fā)送一個(gè)SIGINT信號給當(dāng)前正在運(yùn)行的進(jìn)程。SIGINT信號表示一個(gè)中斷請求,它的默認(rèn)行為是終止進(jìn)程。
當(dāng)你按下Ctrl+C時(shí),終端會(huì)捕獲這個(gè)鍵盤事件,并轉(zhuǎn)發(fā)一個(gè)SIGINT信號給當(dāng)前在前臺運(yùn)行的進(jìn)程。這個(gè)信號告訴進(jìn)程有一個(gè)中斷請求,通常意味著用戶希望終止該進(jìn)程。
2、信號的定義
信號是一個(gè)用來表示事件或消息的物理量。在操作系統(tǒng)中,信號是一種軟件中斷,用于通知進(jìn)程發(fā)生了某個(gè)事件。這種事件可能是硬件異常、用戶鍵入特殊的終端控制字符,或者其他進(jìn)程發(fā)送給該進(jìn)程的消息。每個(gè)信號都有一個(gè)唯一的正數(shù)描述和一個(gè)符號名。例如,SIGINT是用于表示中斷的信號,其編號通常是2。信號可以用于進(jìn)程間的簡單通信,也可以用于處理異步事件
在操作系統(tǒng)下我們可以通過:
//查看系統(tǒng)定義的命令
kill -l
在Linux中總共62共信號?
- 每個(gè)信號都有一個(gè)編號和一個(gè)宏定義名稱,這些宏定義可以在signal.h中找到,例如其中有定 義 #define SIGINT 2
- 編號34以上的是實(shí)時(shí)信號,這里只討論編號34以下的信號,不討論實(shí)時(shí)信號。這些信號各自在什么條件下 產(chǎn)生,默認(rèn)的處理動(dòng)作是什么,在signal(7)中都有詳細(xì)說明: man 7 signal
//查看7號手冊
man 7 signal
3、信號的處理方式?
在Linux中,信號處理的常見方式包括以下幾種:
- 忽略信號:進(jìn)程可以選擇忽略某些信號,即當(dāng)這些信號發(fā)生時(shí),進(jìn)程不會(huì)執(zhí)行任何操作。這種方式可以用于屏蔽一些不必要的信號干擾。
- 默認(rèn)行為:對于每個(gè)信號,系統(tǒng)都定義了一個(gè)默認(rèn)行為。如果進(jìn)程沒有為某個(gè)信號設(shè)置自定義處理函數(shù),那么信號發(fā)生時(shí)將會(huì)執(zhí)行默認(rèn)行為。默認(rèn)行為可以是終止進(jìn)程、忽略信號、停止進(jìn)程等。
- 自定義處理函數(shù):進(jìn)程可以通過信號處理函數(shù)來捕獲信號,并在信號發(fā)生時(shí)執(zhí)行自定義的操作。這種方式常用于實(shí)現(xiàn)一些特定的行為,比如在接收到某個(gè)信號時(shí)進(jìn)行日志記錄、清理資源等。進(jìn)程可以使用
signal
函數(shù)或者sigaction
函數(shù)來設(shè)置信號處理函數(shù)。
二、有關(guān)信號操作的函數(shù)
1、signal函數(shù)(捕捉信號)
功能:用于捕捉信號并執(zhí)行相應(yīng)的處理操作
原型:? sighandler_t signal(int signum, sighandler_t handler);
參數(shù) :
? ? ? ? signum:要捕捉的信號編號
? ? ? ? handler:指定一個(gè)函數(shù)指針,它指向的信號處理函數(shù)將在接收到指定的信號時(shí)被調(diào)用
返回值:
signal
函數(shù)返回之前為sig
信號設(shè)置的處理函數(shù)的地址
typedef void (*sighandler_t)(int);
這里我們要特別? sighandler_t其實(shí)是一個(gè)函數(shù)指針。
signal
函數(shù)的第二個(gè)參數(shù)handler
是一個(gè)指向信號處理函數(shù)的指針,也就是sighandler_t
類型的函數(shù)指針。這個(gè)參數(shù)用于指定當(dāng)信號發(fā)生時(shí)應(yīng)該執(zhí)行的函數(shù)。
使用handler
參數(shù)的方式如下:
自定義信號處理函數(shù):首先,你需要定義一個(gè)符合
sighandler_t
類型的函數(shù),即接受一個(gè)整數(shù)參數(shù)(信號編號)并返回void
的函數(shù)。這個(gè)函數(shù)將用于處理信號。例如:void my_handler(int signal_num) { // 處理信號的代碼 }
設(shè)置信號處理函數(shù):使用
signal
函數(shù)來設(shè)置信號的處理函數(shù)。將信號編號和自定義的處理函數(shù)作為參數(shù)傳遞給signal
函數(shù)。例如,為了設(shè)置SIGINT信號(通常由Ctrl+C發(fā)送)的處理函數(shù)為my_handler
signal(SIGINT, my_handler);
注意:
- 處理函數(shù)通常會(huì)根據(jù)信號編號采取不同的操作。因此,在處理函數(shù)中,你可以使用傳遞的信號編號來判斷是哪個(gè)信號被接收。
- 如果你將
handler
設(shè)置為SIG_IGN
,那么信號將被忽略。- 如果你將
handler
設(shè)置為SIG_DFL
,那么將采取信號的默認(rèn)行為。
舉例用法:
#include<iostream>
#include<unistd.h>
#include<signal.h>using namespace std;void my_handler(int signal_num)
{cout<< "接收到的信號碼: "<<signal_num << " "<<"my_pid: "<< getpid()<<endl;
}
//測試
int main()
{signal(SIGINT,my_handler);while(1){cout<< "我是一個(gè)進(jìn)程,我正在運(yùn)行" <<endl;sleep(1);}return 0;
}
這里我們發(fā)現(xiàn),當(dāng)我們在按Ctrl+?c,進(jìn)程不再中止,而是去執(zhí)行my_handler函數(shù)中的操作,singal也捕獲了2號命令。(這里我們是用kill -9 pid中止進(jìn)程的)?。
2、kill函數(shù)
功能:用于向指定進(jìn)程發(fā)送信號
原型:?int kill(pid_t pid, int sig)
參數(shù) :
? ? ? ?pid:數(shù)指定要發(fā)送信號的進(jìn)程ID
? ? ? ? sig:參數(shù)指定要發(fā)送的信號類型
返回值:當(dāng)調(diào)用成功時(shí),kill函數(shù)返回0;否則,返回-1并設(shè)置errno來表示錯(cuò)誤原因
?kill函數(shù)可以發(fā)送多種類型的信號,例如:
SIGINT
:中斷進(jìn)程。通常是通過用戶按下Ctrl+C來產(chǎn)生的。SIGTERM
:終止進(jìn)程。這是一個(gè)終止進(jìn)程的請求,進(jìn)程可以捕獲該信號進(jìn)行清理操作,然后退出。SIGKILL
:強(qiáng)制終止進(jìn)程。這個(gè)信號會(huì)直接終止進(jìn)程,進(jìn)程無法捕獲或忽略它。?
3、raise函數(shù)
功能:用于發(fā)送信號給當(dāng)前進(jìn)程
原型:int raise(int sig)
參數(shù) :
? ? ? ? sig:參數(shù)指定要發(fā)送的信號類型
?當(dāng)調(diào)用raise
函數(shù)時(shí),它會(huì)向當(dāng)前進(jìn)程發(fā)送指定的信號。如果信號處理程序已經(jīng)注冊了對應(yīng)的信號,它將被調(diào)用進(jìn)行處理。否則,默認(rèn)的信號處理動(dòng)作將被執(zhí)行,可能會(huì)導(dǎo)致進(jìn)程終止、停止或忽略信號。
舉例:
#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>using namespace std;void my_handler(int signal_num)
{cout<< "接收到的信號碼: "<<signal_num << " "<<"my_pid: "<< getpid()<<endl;
}
//測試
int main()
{//注冊信號處理程序signal(SIGINT,my_handler);cout<<"SIGINT to slef"<<endl;//發(fā)送SIGINT給當(dāng)前進(jìn)程raise(SIGINT);cout<<" signal after" <<endl;return 0;
}
?
4、abort函數(shù)
功能:用于異常終止程序的函數(shù)
原型:void abort(void);
調(diào)用abort函數(shù)將導(dǎo)致程序異常終止,并返回一個(gè)非零的退出碼給操作系統(tǒng),以指示程序異常結(jié)束。
由于abort函數(shù)會(huì)立即終止程序,所以它通常會(huì)在發(fā)現(xiàn)嚴(yán)重錯(cuò)誤的情況下使用,例如內(nèi)存泄漏、無效參數(shù)等。它向程序員提供了一種機(jī)制,用于在無法恢復(fù)正常執(zhí)行流程的情況下緊急停止程序。
然而,由于abort函數(shù)不進(jìn)行任何清理操作,因此不建議在正常情況下使用它來結(jié)束程序。在大多數(shù)情況下,應(yīng)該嘗試通過其他方式恢復(fù)程序的執(zhí)行,或者使用更適當(dāng)?shù)暮瘮?shù)來進(jìn)行正常的程序終止,如exit函數(shù)。
三、信號的產(chǎn)生?
1、通過終端按鍵產(chǎn)生信號
通過終端按鍵產(chǎn)生信號是指用戶在終端(命令行界面)按下特定的按鍵組合,向當(dāng)前進(jìn)程發(fā)送一種信號。這種信號可以是一種通知或請求,告訴進(jìn)程執(zhí)行某種特定的操作。
在Unix和類Unix系統(tǒng)中,終端按鍵可以產(chǎn)生信號,這些信號可以與進(jìn)程進(jìn)行交互。例如,當(dāng)用戶按下 Ctrl+C 組合鍵時(shí),會(huì)向當(dāng)前正在運(yùn)行的進(jìn)程發(fā)送一個(gè) SIGINT 信號。這個(gè)信號的默認(rèn)處理動(dòng)作是終止進(jìn)程。
類似地,還有其他按鍵組合可以發(fā)送不同的信號,比如 Ctrl+\ 會(huì)發(fā)送 SIGQUIT 信號,默認(rèn)處理動(dòng)作也是終止進(jìn)程,并生成 core dump 文件。
這種通過終端按鍵產(chǎn)生信號的方式,提供了一種用戶與進(jìn)程交互的手段,用戶可以通過這種方式控制進(jìn)程的行為,比如終止進(jìn)程、暫停進(jìn)程等。同時(shí),進(jìn)程也可以根據(jù)自身需要對這些信號進(jìn)行處理,比如忽略信號、自定義處理函數(shù)等。
那么core dump (核心轉(zhuǎn)儲)又是指的什么呢?
? ? ? ?首先解釋什么是Core Dump。當(dāng)一個(gè)進(jìn)程要異常終止時(shí),可以選擇把進(jìn)程的用戶空間內(nèi)存數(shù)據(jù)全部 保存到磁盤上,文件名通常是core,這叫做Core Dump。
? ? ? ? 進(jìn)程異常終止通常是因?yàn)橛蠦ug,比如非法內(nèi)存訪問導(dǎo)致段錯(cuò)誤, 事后可以用調(diào)試器檢查core文件以查清錯(cuò)誤原因,這叫做Post-mortem Debug(事后調(diào)試)。一個(gè)進(jìn)程允許產(chǎn)生多大的core文件取決于進(jìn)程的Resource Limit(這個(gè)信息保存 在PCB中)。默認(rèn)是不允許產(chǎn)生core文件的, 因?yàn)閏ore文件中可能包含用戶密碼等敏感信息,不安全。在開發(fā)調(diào)試階段可以用ulimit命令改變這個(gè)限制,允許 產(chǎn)生core文件。 首先用ulimit命令改變Shell進(jìn)程的Resource Limit,允許core文件最大為1024K: $ ulimit -c 1024
這里本系統(tǒng)出于上面所說的安全等因素考慮core file?size文件數(shù)量是0?
2、調(diào)用系統(tǒng)函數(shù)向進(jìn)程發(fā)信號
這里我們可以通過系統(tǒng)函數(shù)kill,raise,abort函數(shù)來向進(jìn)程發(fā)送我們想要的信息
int cnt = 0;while(cnt <= 10){printf("cnt : %d ,pid: %d\n",cnt++,getpid());sleep(1);// if(cnt >= 5) abort();if(cnt >= 5) raise(9);}
?調(diào)用abort終止程序
?調(diào)用raise(9)終止程序
3、硬件異常產(chǎn)生信號?
? ? ? ? 硬件異常被硬件以某種方式被硬件檢測到并通知內(nèi)核,然后內(nèi)核向當(dāng)前進(jìn)程發(fā)送適當(dāng)?shù)男盘?。例如?dāng)前進(jìn)程執(zhí)行了除以0的指令,CPU的運(yùn)算單元會(huì)產(chǎn)生異常,內(nèi)核將這個(gè)異常解釋 為SIGFPE信號發(fā)送給進(jìn)程。再比如當(dāng)前進(jìn)程訪問了非法內(nèi)存地址,,MMU會(huì)產(chǎn)生異常,內(nèi)核將這個(gè)異常解釋為SIGSEGV信號發(fā)送給進(jìn)程。
除以0的指令
當(dāng)我們在編寫的程序進(jìn)行除0操作,為什么就會(huì)發(fā)送8好信號終止進(jìn)程呢?
通過下圖進(jìn)行理解:
當(dāng)我們進(jìn)行除0操作,在CPU中有一個(gè)專門用來檢測運(yùn)算的,狀態(tài)寄存器,里面有一個(gè)溢出標(biāo)記位當(dāng)除0時(shí),標(biāo)記位就會(huì)溢出,從而被操作系統(tǒng)知曉,發(fā)送8好信號給進(jìn)程。?
非法內(nèi)存地址?
OS怎么知道呢??我野指針了呢?,為什么程序中有野指針就會(huì)崩潰?
我們知道,其實(shí)指針指向的是虛擬地址,通過頁表映射到物理地址,當(dāng)指針越界訪問的時(shí)候,操作系統(tǒng)就可以察覺到,從而對進(jìn)程發(fā)送11號信號終止進(jìn)程。
void my_handler(int signal_num)
{cout<< "接收到的信號碼: "<<signal_num << " "<<"my_pid: "<< getpid()<<endl;sleep(1);
}
//測試
int main(int argc, char *argv[])
{//硬件異常產(chǎn)生信號signal(11,my_handler);while(true){std::cout << "我在運(yùn)行中...." << std::endl;sleep(1);int *p = nullptr; //寫一個(gè)野指針*p = 1;}return 0;
}
4、由軟件條件產(chǎn)生信號?
在理解軟件條件產(chǎn)生信號之前,我們需要理解allarm函數(shù)
頭文件:#include unsigned
類型:int alarm(unsigned int seconds);
用途:調(diào)用alarm函數(shù)可以設(shè)定一個(gè)鬧鐘,也就是告訴內(nèi)核在seconds秒之后給當(dāng)前進(jìn)程發(fā)SIGALRM信號, 該信號的默認(rèn)處理動(dòng) 作是終止當(dāng)前進(jìn)程。
這個(gè)函數(shù)的返回值是0或者是以前設(shè)定的鬧鐘時(shí)間還余下的秒數(shù)。打個(gè)比方,某人要小睡一覺,設(shè)定鬧鐘為30分鐘之后 響,20分鐘后被人吵醒了,還想多睡一會(huì)兒,于是重新設(shè)定鬧鐘為15分鐘之后響,“以前設(shè)定的鬧鐘時(shí)間還余下的時(shí)間”就 是10分鐘。如果seconds值為0,表示取消以前設(shè)定的鬧鐘,函數(shù)的返回值仍然是以前設(shè)定的鬧鐘時(shí)間還余下的秒數(shù)
void my_handler(int signal_num)
{cout<< "接收到的信號碼: "<<signal_num << " "<<"my_pid: "<< getpid()<<endl;exit(1);
}
//測試
int main(int argc, char *argv[])
{//4、軟件異常產(chǎn)生信號int cnt = 0;signal(SIGALRM,my_handler);//統(tǒng)計(jì)1秒數(shù)據(jù)能夠累計(jì)到多少alarm(1);while(true){cnt++;cout<< cnt <<endl;}return 0;
}
這里是沒有用signal捕捉信號?
?這里是用singal捕捉了14號信號
?從現(xiàn)象上來看,當(dāng)調(diào)用alarm函數(shù),在經(jīng)過設(shè)置的時(shí)間后,會(huì)給進(jìn)程發(fā)送14號信號。
總結(jié)思考
上面所說的所有信號產(chǎn)生,最終都要有OS來進(jìn)行執(zhí)行,為什么?
這是因?yàn)椴僮飨到y(tǒng)是計(jì)算機(jī)硬件和軟件之間的中介,負(fù)責(zé)管理計(jì)算機(jī)的資源,并提供一個(gè)運(yùn)行環(huán)境,使得程序能夠在計(jì)算機(jī)上正確、高效地運(yùn)行。?
?OS是進(jìn)程的管理者 信號的處理是否是立即處理的?
信號的處理并不一定是立即的,它取決于信號的類型以及接收進(jìn)程的狀態(tài)?
在合適的時(shí)候 信號如果不是被立即處理,那么信號是否需要暫時(shí)被進(jìn)程記錄下來??
是的,如果一個(gè)信號在適當(dāng)?shù)臅r(shí)候不能立即處理,操作系統(tǒng)通常會(huì)將信號暫時(shí)記錄下來,以便稍后處理。這種記錄信號的機(jī)制通常稱為信號隊(duì)列(Signal Queue)。
信號隊(duì)列允許操作系統(tǒng)將接收到的信號排隊(duì),而不會(huì)丟失它們。每個(gè)進(jìn)程都有一個(gè)相關(guān)聯(lián)的信號隊(duì)列,用于存儲接收到的信號。當(dāng)信號到達(dá)時(shí),它會(huì)被添加到接收進(jìn)程的信號隊(duì)列中。接收進(jìn)程可以隨后檢查隊(duì)列中的信號并決定如何處理它們。
一個(gè)進(jìn)程在沒有收到信號的時(shí)候,能否能知道,自己應(yīng)該對合法信號作何處理呢??
一個(gè)進(jìn)程可以預(yù)先定義信號的處理方式,無論是否收到信號。這就是通過設(shè)置信號處理程序(Signal Handler)來實(shí)現(xiàn)的。信號處理程序是一段特定的代碼,它規(guī)定了進(jìn)程在接收特定信號時(shí)應(yīng)采取的操作。
進(jìn)程可以使用操作系統(tǒng)提供的系統(tǒng)調(diào)用(如signal()
、sigaction()
等,具體取決于操作系統(tǒng)和編程語言)來注冊信號處理程序。一旦設(shè)置了信號處理程序,當(dāng)進(jìn)程接收到相應(yīng)的信號時(shí),操作系統(tǒng)將執(zhí)行信號處理程序中定義的操作。
如何理解OS向進(jìn)程發(fā)送信號?能否描述一下完整的發(fā)送處理過程?
操作系統(tǒng)(OS)可以向進(jìn)程發(fā)送信號,這是一種進(jìn)程間通信的機(jī)制,用于通知接收進(jìn)程發(fā)生了某些事件或條件。下面是完整的發(fā)送信號的處理過程:
信號產(chǎn)生:信號的產(chǎn)生通常是由操作系統(tǒng)、其他進(jìn)程或硬件事件引發(fā)的。信號可以表示不同的事件,如用戶按下終止鍵(Ctrl+C),進(jìn)程除零錯(cuò)誤,或者定時(shí)器超時(shí)等。
信號的種類:每個(gè)信號都有一個(gè)唯一的標(biāo)識符,如SIGTERM、SIGINT、SIGSEGV等,用來表示不同的事件類型。操作系統(tǒng)和程序員可以根據(jù)需要定義自定義信號。
信號發(fā)送:操作系統(tǒng)將信號發(fā)送給目標(biāo)進(jìn)程。這通常涉及到操作系統(tǒng)查找目標(biāo)進(jìn)程的進(jìn)程標(biāo)識符(PID)或進(jìn)程組標(biāo)識符(PGID),然后將信號發(fā)送給目標(biāo)。
信號傳遞:操作系統(tǒng)將信號傳遞給目標(biāo)進(jìn)程。目標(biāo)進(jìn)程的執(zhí)行可能會(huì)被中斷,以便它可以處理接收到的信號。這意味著操作系統(tǒng)在進(jìn)程執(zhí)行期間會(huì)修改進(jìn)程的執(zhí)行流程,以引發(fā)信號處理。
信號處理程序執(zhí)行:目標(biāo)進(jìn)程在接收到信號后,會(huì)查找它為該信號注冊的信號處理程序。信號處理程序是一段用戶定義的代碼,用于定義在接收信號時(shí)應(yīng)執(zhí)行的操作。處理程序可以是默認(rèn)的操作,用戶自定義的操作,或者忽略信號。
信號處理:根據(jù)信號處理程序的定義,進(jìn)程采取相應(yīng)的操作。這可以包括終止進(jìn)程、忽略信號、記錄事件、執(zhí)行自定義操作等。處理程序執(zhí)行后,進(jìn)程可以繼續(xù)執(zhí)行原來的任務(wù)。
信號傳遞結(jié)果:在信號處理程序執(zhí)行后,進(jìn)程可以向操作系統(tǒng)報(bào)告信號的處理結(jié)果,通常以退出狀態(tài)碼的形式。這可以幫助操作系統(tǒng)了解信號處理的成功與否,以及進(jìn)程是否需要進(jìn)一步處理。
總結(jié)來說,操作系統(tǒng)向進(jìn)程發(fā)送信號的過程涉及信號的產(chǎn)生、發(fā)送、傳遞、處理,以及可能的反饋。這種機(jī)制使得操作系統(tǒng)和不同進(jìn)程之間能夠進(jìn)行通信和協(xié)作,通常用于處理異常情況、用戶交互、進(jìn)程控制等。
?