Linux進(jìn)程信號(hào)的使用解讀
什么是信號(hào)
信號(hào)是進(jìn)程之間事件異步通知的一種方式,屬于軟中斷。
- 信號(hào)的處理能力是進(jìn)程的內(nèi)置功能的一部分
- 進(jìn)程即便沒(méi)有收到信號(hào)也知道要如何處理信號(hào)
- 當(dāng)進(jìn)程收到一個(gè)信號(hào)時(shí)可能不會(huì)立刻處理,而是在合適時(shí)機(jī)處理(按優(yōu)先級(jí))
- 一個(gè)進(jìn)程必須當(dāng)信號(hào)產(chǎn)生,到信號(hào)開(kāi)始被處理,就一定會(huì)有時(shí)間窗口,進(jìn)程具有保存哪些信號(hào)已經(jīng)發(fā)生了的能力。
前后臺(tái)進(jìn)程
Linux中,一次登錄,一個(gè)終端一般會(huì)配上一個(gè)bash,每一次登錄,只允許一個(gè)進(jìn)程是前臺(tái)進(jìn)程,可以允許多個(gè)后臺(tái)進(jìn)程
本質(zhì)區(qū)別(前臺(tái)進(jìn)程獲取鍵盤(pán)輸入)
前臺(tái)進(jìn)程
./運(yùn)行后bash不再接受任何指令的進(jìn)程(bash變?yōu)楹笈_(tái)),可以用ctrl c二號(hào)信號(hào)殺掉
后臺(tái)進(jìn)程
./proc & 運(yùn)行(ctrl c不能退出:鍵盤(pán)輸入給bash,后臺(tái)進(jìn)程接受不到鍵盤(pán)輸入),可以用ctrl /殺掉
鍵盤(pán)是如何輸入給內(nèi)核,ctral+c如何變成信號(hào)的
鍵盤(pán)也是文件 ,具有文件緩沖區(qū),操作系統(tǒng)開(kāi)機(jī)后加載到內(nèi)存里,,即把鍵盤(pán)上數(shù)據(jù)拷貝到文件緩沖區(qū),再通過(guò)接口將文件緩沖區(qū)內(nèi)數(shù)據(jù)拷貝到用戶緩沖區(qū)
但是如何知道鍵盤(pán)有數(shù)據(jù)呢?
鍵盤(pán)可以給CPU發(fā)送硬件中斷(終端號(hào)),操作系統(tǒng)識(shí)別到中斷號(hào),以中斷號(hào)為索引到中斷表里找方法,再執(zhí)行。然后將數(shù)據(jù)發(fā)送給CPU寄存器(高低電平)。
信號(hào)概念
查看信號(hào)
編號(hào)34以上的是實(shí)時(shí)信號(hào)
信號(hào)是數(shù)字,這里是宏定義
信號(hào)處理常見(jiàn)方式
可選的處理動(dòng)作有以下三種:
- 1. 忽略此信號(hào)。
- 2. 執(zhí)行該信號(hào)的默認(rèn)處理動(dòng)作。
- 3. 提供一個(gè)信號(hào)處理函數(shù),要求內(nèi)核在處理該信號(hào)時(shí)切換到用戶態(tài)執(zhí)行這個(gè)處理函數(shù),這種方式稱為捕捉一個(gè)信號(hào)。
signal函數(shù)
- 修改特定進(jìn)程對(duì)信號(hào)的處理動(dòng)作
- 只要設(shè)置一次,往后都有效
- 收到信號(hào)才調(diào)用handler方式
- signum信號(hào)編號(hào)
- handler:誰(shuí)調(diào)用這個(gè)signal
- typedef void (*sighandler_t)(int);
- 返回值是void,參數(shù)是int的函數(shù)指針類(lèi)型
信號(hào)的產(chǎn)生
鍵盤(pán)組合鍵
- Ctrl+C (SIGINT) 已經(jīng)驗(yàn)證過(guò),這?不再重復(fù)
- Ctrl+\(SIGQUIT)可以發(fā)送終?信號(hào)并?成core dump?件,?于事后調(diào)試
- Ctrl+Z(SIGTSTP)可以發(fā)送停?信號(hào),將當(dāng)前前臺(tái)進(jìn)程掛起到后臺(tái)等。
kill命令
kill -9 pid即可
系統(tǒng)調(diào)用
kill
pid
:目標(biāo)進(jìn)程的 PID(進(jìn)程 ID)。sig
:要發(fā)送的信號(hào)(如SIGTERM
、SIGKILL
等)。
mykill:
#include <iostream> #include <string> #include <unistd.h> #include <signal.h> #include <sys/types.h> using namespace std; void Usage(string proc) { cout << "Usage:\n\t" << proc << " signum pid\n\n"; } int main(int argc, char *argv[]) { if(argc != 3) { Usage(argv[0]); exit(1); } int signum = stoi(argv[1]); pid_t pid = stoi(argv[2]); int n = kill(pid, signum); if(n == -1) { perror("kill"); exit(2); } return 0; }
./mykill 9 進(jìn)程pid即可
raise
給調(diào)用者發(fā)送一個(gè)制定信號(hào)
#include <iostream> #include <string> #include <cstdlib> #include <unistd.h> #include <signal.h> #include <sys/types.h> using namespace std; void Usage(string proc) { cout << "Usage:\n\t" << proc << " signum pid\n\n"; } // mykill signum pid int main(int argc, char *argv[]) { // signal(2, myhandler); int cnt = 0; while (true) { cout << "I am a process, pid: " << getpid() << endl; sleep(1); cnt++; if(cnt % 2 == 0) { //kill(getpid(), 2); raise(2); } } return 0; }
abort
引起一個(gè)正常信號(hào)直接終止(發(fā)送6號(hào)信號(hào))
#include <iostream> #include <string> #include <cstdlib> #include <unistd.h> #include <signal.h> #include <sys/types.h> using namespace std; void Usage(string proc) { cout << "Usage:\n\t" << proc << " signum pid\n\n"; } void myhandler(int signo) { cout << "process get a signal: " << signo <<endl; // exit(1); } // mykill signum pid int main(int argc, char *argv[]) { // signal(2, myhandler); signal(SIGABRT, myhandler); int cnt = 0; while (true) { cout << "I am a process, pid: " << getpid() << endl; sleep(1); cnt++; if(cnt % 2 == 0) { //kill(getpid(), 2); //raise(2); abort(); } } return 0; }
此時(shí)發(fā)現(xiàn)即便把a(bǔ)bort信號(hào)捕捉了仍然會(huì)abort退出
但是如果把a(bǔ)bort注釋?zhuān)迷诮K端用kill -6 pid不會(huì)終止
總結(jié)
無(wú)論哪種產(chǎn)生方式,最終一定是操作系統(tǒng)發(fā)送給操作系統(tǒng)的,因?yàn)椴僮飨到y(tǒng)是進(jìn)程的管理者
異常
硬件
除零錯(cuò)誤
#include <iostream> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; void handler(int signo) { cout << "...get a sig, number: " << signo <<endl; //我什么都沒(méi)干,我只是打印了消息 } int main() { signal(SIG_FPE, handler); cout<<"div before"<<endl; sleep(1); int a = 10; int b = 0; a /= b; cout<<"div after"<<endl; sleep(1); return 0; }
野指針問(wèn)題
#include <iostream> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; void handler(int signo) { cout << "...get a sig, number: " << signo <<endl; //我什么都沒(méi)干,我只是打印了消息 } int main() { signal(SIGSEGV, handler); cout<<"point error before"<<endl; sleep(1); int *p = nullptr; *p = 100; cout<<"point error after"<<endl; sleep(1); return 0; }
操作系統(tǒng)如何知道錯(cuò)誤了呢?
除零錯(cuò)誤時(shí):CPU內(nèi)狀態(tài)寄存器的溢出標(biāo)志位被置為1,操作系統(tǒng)會(huì)知道(OS是硬件的管理者),向進(jìn)程發(fā)送信號(hào),殺死進(jìn)程。
任何異常只會(huì)影響對(duì)應(yīng)進(jìn)程,不會(huì)波及到操作系統(tǒng)
進(jìn)程出異常與進(jìn)程是否切換無(wú)關(guān),當(dāng)進(jìn)程被調(diào)度時(shí)要把自己進(jìn)程上下文帶走,把別人上下文拿回來(lái)(而CPU中狀態(tài)寄存器輸入上下文)
野指針錯(cuò)誤時(shí):頁(yè)表的查詢由MMU(硬件內(nèi)存管理單元)完成,出現(xiàn)異常訪問(wèn)時(shí),頁(yè)表轉(zhuǎn)換時(shí)因權(quán)限問(wèn)題或映射問(wèn)題轉(zhuǎn)換失敗
虛擬到物理地址轉(zhuǎn)換失敗,MMU硬件單元報(bào)錯(cuò),將轉(zhuǎn)換失敗的虛擬地址放到CPU一個(gè)寄存器里
出現(xiàn)異常不崩潰會(huì)一直被調(diào)度,捕捉信號(hào)不是為了解決異常,而是讓用戶清除為什么掛掉
捕捉信號(hào)不是為了解決異常而是讓用戶清楚為什么進(jìn)程會(huì)掛掉
core dump
當(dāng)?個(gè)進(jìn)程要異常終?時(shí),可以選擇把進(jìn)程的?戶空間內(nèi)存數(shù)據(jù)全部保存到磁盤(pán)上,?件名通常是core,這叫做Core Dump。
云服務(wù)器core默認(rèn)為0,需要自己修改
示例代碼:
#include <iostream> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; int main() { // signal(2, SIG_IGN); signal(2, SIG_DFL); pid_t id = fork(); if(id == 0) { //child int cnt = 500; while(cnt) { cout << "i am a child process, pid: " << getpid() << "cnt: " << cnt << endl; sleep(1); cnt--; } exit(0); } // father int status = 0; pid_t rid = waitpid(id, &status, 0); if(rid == id) { cout << "child quit info, rid: " << rid << " exit code: " << ((status>>8)&0xFF) << " exit signal: " << (status&0x7F) << " core dump: " << ((status>>7)&1) << endl; // ? & (0000 0000 ... 0001) } return 0; }
#include <iostream> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; int main() { // signal(2, SIG_IGN); signal(2, SIG_DFL); while (1) { cout << "hello signal" << endl; sleep(1); } int a = 10; int b = 0; a /= b; cout << " a = " << a << endl; return 0; }
core dump復(fù)現(xiàn)問(wèn)題錯(cuò)誤后直接定位到出錯(cuò)行 ,方便事后調(diào)試。
軟件
例如:管道如果只有讀沒(méi)有寫(xiě)就會(huì)觸發(fā)異常
- 鬧鐘
鬧鐘的返回值是鬧鐘響之后的剩余時(shí)間,如果有多個(gè)鬧鐘就返回上個(gè)腦中的剩余時(shí)間
鬧鐘響過(guò)后默認(rèn)調(diào)用17號(hào)信號(hào),但鬧鐘不是異常,如果進(jìn)行信號(hào)捕捉,里邊沒(méi)有寫(xiě)退出的話,響過(guò)后不會(huì)終止程序
如果想每隔n秒響一次,就在捕捉后再次設(shè)置鬧鐘即可
#include <iostream> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; using namespace std; void work() { cout << "print log..." << endl; } // 信號(hào)為什么會(huì)一直被觸發(fā)?? void handler(int signo) { // work(); cout << "...get a sig, number: " << signo <<endl; //我什么都沒(méi)干,我只是打印了消息 // exit(1); int n = alarm(5); cout << "剩余時(shí)間:" << n << endl; } int main() { signal(SIGALRM, handler); int n = alarm(50); while(1) { cout << "proc is running..., pid: " << getpid() << endl; sleep(1); } sleep(1); return 0; }
信號(hào)的保存
信號(hào)的發(fā)送是給進(jìn)程的pcb發(fā)
task_struct結(jié)構(gòu)體里有int single對(duì)象,int32位比特位(普通信號(hào),位圖管理信號(hào))
比特位的內(nèi)容是1還是0表明是否收到
比特位的位置(第幾個(gè))表示信號(hào)的編號(hào)
所謂的發(fā)信號(hào),本質(zhì)就是OS去修改task_struct中的信號(hào)位圖對(duì)應(yīng)的比特位(實(shí)質(zhì)是寫(xiě)信號(hào))
發(fā)信號(hào)的一定是操作系統(tǒng),操作系統(tǒng)是進(jìn)程的管理者,只有OS才能修改task_struct內(nèi)部的屬性
信號(hào)其他相關(guān)常見(jiàn)概念
實(shí)際執(zhí)行信號(hào)的處理動(dòng)作稱為信號(hào)遞達(dá)
信號(hào)從產(chǎn)生到遞達(dá)之間的狀態(tài),稱為信號(hào)未決。 進(jìn)程可以選擇阻塞某個(gè)信號(hào)。
被阻塞的信號(hào)產(chǎn)生時(shí)將保持在未決狀態(tài),直到進(jìn)程解除對(duì)此信號(hào)的阻塞,才執(zhí)行遞達(dá)的動(dòng)作.
注意,阻塞和忽略是不同的,只要信號(hào)被阻塞就不會(huì)遞達(dá),而忽略是在遞達(dá)之后可選的一種處理動(dòng)作。
在內(nèi)核中的表示
1.每個(gè)信號(hào)都有兩個(gè)標(biāo)志位分別表示阻塞(block)和未決(pending),還有一個(gè)函數(shù)指針表示處理動(dòng)作。信號(hào) 產(chǎn)生時(shí),內(nèi)核在進(jìn)程控制塊中設(shè)置該信號(hào)的未決標(biāo)志,直到信號(hào)遞達(dá)才清除該標(biāo)志。在上圖的例子 中,SIGHUP信號(hào)未阻塞也未產(chǎn)生過(guò),當(dāng)它遞達(dá)時(shí)執(zhí)行默認(rèn)處理動(dòng)作。
2.SIGINT信號(hào)產(chǎn)生過(guò),但正在被阻塞,所以暫時(shí)不能遞達(dá)。雖然它的處理動(dòng)作是忽略,但在沒(méi)有解除阻塞之前 不能忽略這個(gè)信號(hào),因?yàn)檫M(jìn)程仍有機(jī)會(huì)改變處理動(dòng)作之后再解除阻塞。
3.SIGQUIT信號(hào)未產(chǎn)生過(guò),一旦產(chǎn)生SIGQUIT信號(hào)將被阻塞,它的處理動(dòng)作是用戶自定義函數(shù)sighandler。 如果在進(jìn)程解除對(duì)某信號(hào)的阻塞之前這種信號(hào)產(chǎn)生過(guò)多次,將如何處理?POSIX.1允許系統(tǒng)遞送該信號(hào)一次 或多次。Linux是這樣實(shí)現(xiàn)的:常規(guī)信號(hào)在遞達(dá)之前產(chǎn)生多次只計(jì)一次,而實(shí)時(shí)信號(hào)在遞達(dá)之前產(chǎn)生多次可 以依次放在一個(gè)隊(duì)列里。本章不討論實(shí)時(shí)信號(hào)。
handler表:
- 信號(hào)的范圍[1,31],每一種信號(hào)都要有自己的一種處理方法(系統(tǒng)默認(rèn)),用戶可以通過(guò)signal()函數(shù)替換處理方法
pending表:
- 信號(hào)位圖,記錄是否收到信號(hào)及哪些信號(hào)
block表:
- 用來(lái)記錄某種信號(hào)是否被屏蔽
- 對(duì)一個(gè)特定任務(wù)可以屏蔽,能夠接受到但是不會(huì)執(zhí)行,想做了就解除屏蔽
- 屏蔽是一種狀態(tài),和現(xiàn)在收到信號(hào)沒(méi)有無(wú)關(guān)
- 信號(hào)沒(méi)有產(chǎn)生也能被屏蔽
阻塞(屏蔽)和忽略
- 阻塞/屏蔽(未讀):屏蔽掉信號(hào),阻止其立即遞達(dá),但信號(hào)會(huì)被記錄在未決集中,待解除阻塞后處理。
- 忽略(拒收):不會(huì)遞達(dá),處理信號(hào)(信號(hào)抵達(dá)三種方式之一),明確丟棄信號(hào),內(nèi)核直接刪除該信號(hào),不會(huì)遞達(dá)也不會(huì)記錄
- SIG_IGN忽略
- SIG_IGN將1強(qiáng)轉(zhuǎn)成函數(shù)指針類(lèi)型
- SIG_DFL使用信號(hào)默認(rèn)方式
#include <iostream> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; int main() { // signal(2, SIG_IGN); signal(2, SIG_DFL); while(1) { cout << "hello signal" << endl; sleep(1); } return 0; }
sigset_t(數(shù)據(jù)類(lèi)型)
從上圖來(lái)看,每個(gè)信號(hào)只有一個(gè)bit的未決標(biāo)志,非0即1,不記錄該信號(hào)產(chǎn)生了多少次,阻塞標(biāo)志也是這樣表示的。 因此,未決和阻塞標(biāo)志可以用相同的數(shù)據(jù)類(lèi)型sigset_t來(lái)存儲(chǔ)
sigset_t稱為信號(hào)集,這個(gè)類(lèi)型可以表示每個(gè)信號(hào) 的“有效”或“無(wú)效”狀態(tài),在阻塞信號(hào)集中“有效”和“無(wú)效”的含義是該信號(hào)是否被阻塞,而在未決信號(hào)集中“有 效”和“無(wú)效”的含義是該信號(hào)是否處于未決狀態(tài)。阻塞信號(hào)集也叫做當(dāng)前進(jìn)程的信號(hào)屏蔽字
這里的“屏蔽”應(yīng)該理解為阻塞而不是忽略。
信號(hào)集操作函數(shù)
信號(hào)集是系統(tǒng)提供的一種數(shù)據(jù)類(lèi)型,方便我們未來(lái)對(duì)pending表和block表做操作
sigset_t類(lèi)型對(duì)于每種信號(hào)用一個(gè)bit表示“有效”或“無(wú)效”狀態(tài),至于這個(gè)類(lèi)型內(nèi)部如何存儲(chǔ)這些bit則依賴于系統(tǒng) 實(shí)現(xiàn),從使用者的角度是不必關(guān)心的,使用者只能調(diào)用以下函數(shù)來(lái)操作sigset_ t變量,而不應(yīng)該對(duì)它的內(nèi)部數(shù)據(jù)做 任何解釋,比如用printf直接打印sigset_t變量是沒(méi)有意義的
#include <signal.h> 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.函數(shù)sigemptyset初始化set所指向的信號(hào)集,使其中所有信號(hào)的對(duì)應(yīng)bit清零,表示該信號(hào)集不包含 任何有效信號(hào)。
2.函數(shù)sig?llset初始化set所指向的信號(hào)集,使其中所有信號(hào)的對(duì)應(yīng)bit置位,表示 該信號(hào)集的有效信號(hào)包括系 統(tǒng)支持的所有信號(hào)。
3.注意,在使用sigset_ t類(lèi)型的變量之前,一定要調(diào) 用sigemptyset或sig?llset做初始化,使信號(hào)集處于確定的狀態(tài)。初始化sigset_t變量之后就可以在調(diào)用sigaddset和sigdelset在該信號(hào)集中添加或刪除某種有效信號(hào)。
4.這四個(gè)函數(shù)都是成功返回0,出錯(cuò)返回-1。sigismember是一個(gè)布爾函數(shù),用于判斷一個(gè)信號(hào)集的有效信號(hào)中是否包含 某種 信號(hào),若包含則返回1,不包含則返回0,出錯(cuò)返回-1。
sigprocmask
調(diào)用函數(shù)sigprocmask可以讀取或更改進(jìn)程的信號(hào)屏蔽字(阻塞信號(hào)集)。
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 返回值:若成功則為0,若出錯(cuò)則為-1
如果oset是非空指針,則讀取進(jìn)程的當(dāng)前信號(hào)屏蔽字通過(guò)oset參數(shù)傳出。如果set是非空指針,則 更改進(jìn)程的信 號(hào)屏蔽字,參數(shù)how指示如何更改。如果oset和set都是非空指針,則先將原來(lái)的信號(hào) 屏蔽字備份到oset里,然后 根據(jù)set和how參數(shù)更改信號(hào)屏蔽字。假設(shè)當(dāng)前的信號(hào)屏蔽字為mask,下表說(shuō)明了how參數(shù)的可選值。
- SIG_BLOCK:新增屏蔽信號(hào)(或運(yùn)算有1為1)
- SIG_UNBLOCK:在進(jìn)程block表里去掉傳入的set信號(hào)
- SIG_SETMASK:直接設(shè)置進(jìn)進(jìn)程pcb的block表里,覆蓋式的設(shè)置全新屏蔽字段
sigset_t *oset:輸出型參數(shù),將上一個(gè)block表進(jìn)行保存(為了未來(lái)恢復(fù))
sigpending
讀取當(dāng)前進(jìn)程的未決信號(hào)集,通過(guò)set參數(shù)傳出。調(diào)用成功則返回0,出錯(cuò)則返回-1。
set輸出型參數(shù):把pending表以位圖形式帶出來(lái)
示例代碼:
#include <iostream> #include <unistd.h> #include <signal.h> using namespace std; void PrintPending(sigset_t &pending) { for (int signo = 31; signo >= 1; signo--) { if (sigismember(&pending, signo)) { cout << "1"; } else { cout << "0"; } } cout << "\n\n"; } void handler(int signo) { cout << "catch a signo: " << signo << endl; } int main() { // 0. 對(duì)2號(hào)信號(hào)進(jìn)行自定義捕捉 signal(2, handler); // 1. 先對(duì)2號(hào)信號(hào)進(jìn)行屏蔽 --- 數(shù)據(jù)預(yù)備 sigset_t bset, oset; // 在哪里開(kāi)辟的空間???用戶棧上的,屬于用戶區(qū) sigemptyset(&bset); sigemptyset(&oset); sigaddset(&bset, 2); // 我們已經(jīng)把2好信號(hào)屏蔽了嗎?并沒(méi)有設(shè)置進(jìn)入到你的進(jìn)程的task_struct // 1.2 調(diào)用系統(tǒng)調(diào)用,將數(shù)據(jù)設(shè)置進(jìn)內(nèi)核 sigprocmask(SIG_SETMASK, &bset, &oset); // 我們已經(jīng)把2好信號(hào)屏蔽了嗎?ok // 2. 重復(fù)打印當(dāng)前進(jìn)程的pending 0000000000000000000000000 sigset_t pending; int cnt = 0; while (true) { // 2.1 獲取 int n = sigpending(&pending); if (n < 0) continue; // 2.2 打印 PrintPending(pending); sleep(1); cnt++; // 2.3 解除阻塞 if(cnt == 20) { cout << "unblock 2 signo" << endl; sigprocmask(SIG_SETMASK, &oset, nullptr); // 我們已經(jīng)把2好信號(hào)屏蔽了嗎?ok } } // 3 發(fā)送2號(hào) 0000000000000000000000010 return 0; }
9號(hào)和19號(hào)新號(hào)不可以被捕捉和屏蔽(演示代碼)
#include <iostream> #include <unistd.h> #include <signal.h> using namespace std; void PrintPending(sigset_t &pending) { for (int signo = 31; signo >= 1; signo--) { if (sigismember(&pending, signo)) { cout << "1"; } else { cout << "0"; } } cout << "\n\n"; } void handler(int signo) { cout << "catch a signo: " << signo << endl; } int main() { // 4. 我可以將所有的信號(hào)都進(jìn)行屏蔽,信號(hào)不就不會(huì)被處理了嗎? 肯定的!9 sigset_t bset, oset; sigemptyset(&bset); sigemptyset(&oset); for (int i = 1; i <= 31; i++) { sigaddset(&bset, i); // 屏蔽了所有信號(hào)嗎??? } sigprocmask(SIG_SETMASK, &bset, &oset); sigset_t pending; while (true) { // 2.1 獲取 int n = sigpending(&pending); if (n < 0) continue; // 2.2 打印 PrintPending(pending); sleep(1); } return 0; }
信號(hào)處理
信號(hào)是什么時(shí)候被處理的
- 當(dāng)進(jìn)程從內(nèi)核態(tài)返回到用戶態(tài)時(shí)進(jìn)行對(duì)信號(hào)的檢測(cè)和處理
- 調(diào)用系統(tǒng)調(diào)用時(shí)——操作系統(tǒng)會(huì)自動(dòng)做身份切換,用戶身份變成內(nèi)核身份(或者反著來(lái))
- 由CPU中esc寄存器決定:當(dāng)esc寄存器低兩位為11時(shí)為用戶態(tài),為00時(shí)為內(nèi)核態(tài)
- int 80 從用戶態(tài)陷入內(nèi)核態(tài)的一條匯編,將11改為00
剛開(kāi)機(jī)操作系統(tǒng)就加載了,所以在物理內(nèi)存較靠下的位置 ,進(jìn)程pcb的3——4G空間是內(nèi)核空間,由內(nèi)核級(jí)頁(yè)表映射到物理內(nèi)存,內(nèi)核級(jí)頁(yè)表只有一個(gè)(所有進(jìn)程都可以看到),用戶級(jí)頁(yè)表好幾個(gè)(進(jìn)程具有獨(dú)立性)。
進(jìn)程視角:調(diào)用系統(tǒng)中的方法時(shí),就是在自己的地址空間中進(jìn)行的(類(lèi)似于共享區(qū)與代碼段之間的跳轉(zhuǎn))。
操作系統(tǒng)視角:任何一個(gè)時(shí)刻,都是有進(jìn)程執(zhí)行的,我們想執(zhí)行操作系統(tǒng)的代碼就可以執(zhí)行。
操作系統(tǒng)的本質(zhì)是一個(gè)死循環(huán)。
操作系統(tǒng)的運(yùn)行是被動(dòng)的,由時(shí)鐘來(lái)推動(dòng)進(jìn)行,當(dāng)時(shí)鐘信號(hào)來(lái)時(shí),停止運(yùn)行當(dāng)前進(jìn)程代碼,去查看進(jìn)程的調(diào)度,只要進(jìn)程在跑,cpu就會(huì)調(diào)度進(jìn)程,只要調(diào)度,時(shí)間片就會(huì)被消耗完畢,消耗完后把進(jìn)程從CPU上剝離下來(lái)。(下次調(diào)度將pcb等匯總到CPU上(內(nèi)核態(tài)))
操作系統(tǒng)不相信用戶還體現(xiàn)在不會(huì)執(zhí)行用戶的代碼(要執(zhí)行代碼必須從內(nèi)核態(tài)跳轉(zhuǎn)到用戶態(tài))
沒(méi)用調(diào)用系統(tǒng)調(diào)用的代碼也會(huì)有內(nèi)核態(tài)和用戶態(tài)的切換(二次調(diào)用時(shí))上邊紅字
信號(hào)捕捉
內(nèi)核如何實(shí)現(xiàn)信號(hào)的捕捉
如果信號(hào)的處理動(dòng)作是用戶自定義函數(shù),在信號(hào)遞達(dá)時(shí)就調(diào)用這個(gè)函數(shù),這稱為捕捉信號(hào)。由于信號(hào)處理函數(shù)的代碼 是在用戶空間的,處理過(guò)程比較復(fù)雜,舉例如下: 用戶程序注冊(cè)了SIGQUIT信號(hào)的處理函數(shù)sighandler。 當(dāng)前正在執(zhí)行 main函數(shù),這時(shí)發(fā)生中斷或異常切換到內(nèi)核態(tài)。
在中斷處理完畢后要返回用戶態(tài)的main函數(shù)之前檢查到有信號(hào) SIGQUIT遞達(dá)。 內(nèi)核決定返回用戶態(tài)后不是恢復(fù)main函數(shù)的上下文繼續(xù)執(zhí)行,而是執(zhí)行sighandler函 數(shù),sighandler 和main函數(shù)使用不同的堆??臻g,它們之間不存在調(diào)用和被調(diào)用的關(guān)系,是 兩個(gè)獨(dú)立的控制流程。
sighandler函數(shù)返 回后自動(dòng)執(zhí)行特殊的系統(tǒng)調(diào)用sigreturn再次進(jìn)入內(nèi)核態(tài)。 如果沒(méi)有新的信號(hào)要遞達(dá),這次再返回用戶態(tài)就是恢復(fù) main函數(shù)的上下文繼續(xù)執(zhí)行了。
實(shí)現(xiàn)信號(hào)捕捉代碼
#include <iostream> #include <cstring> #include <unistd.h> #include <signal.h> using namespace std; void handler(int signo) { cout<< "catch a signal, signal number : " << signo << endl; } int main() { struct sigaction act, oact; memset(&act, 0, sizeof(act)); memset(&oact, 0, sizeof(oact)); act.sa_handler = handler; sigaction(2,&act,&oact); while (true) { cout << "I am a process: " << getpid() << endl; sleep(1); } return 0; }
pending位圖,什么時(shí)候從1->0
當(dāng)進(jìn)行信號(hào)處理時(shí),倘若已經(jīng)進(jìn)入到了信號(hào)的捕捉代碼里,先把信號(hào)位圖由1清零,才調(diào)用handler方法
執(zhí)行信號(hào)捕捉方法之前,先清0,在調(diào)用
演示代碼
#include <iostream> #include <cstring> #include <unistd.h> #include <signal.h> using namespace std; void PrintPending() { sigset_t set; sigpending(&set); for (int signo = 1; signo <= 31; signo++) { if (sigismember(&set, signo)) cout << "1"; else cout << "0"; } cout << "\n"; } void handler(int signo) { PrintPending(); sleep(1); } int main() { struct sigaction act, oact; memset(&act, 0, sizeof(act)); memset(&oact, 0, sizeof(oact)); act.sa_handler = handler; sigaction(2,&act,&oact); while (true) { cout << "I am a process: " << getpid() << endl; sleep(1); } return 0; }
當(dāng)某個(gè)信號(hào)的處理函數(shù)被調(diào)用時(shí)
內(nèi)核自動(dòng)將當(dāng)前信號(hào)加入進(jìn)程的信號(hào)屏蔽字
信號(hào)被處理的時(shí)候,對(duì)應(yīng)的信號(hào)也會(huì)被添加到block表中,防止信號(hào)捕捉被嵌套調(diào)用
操作系統(tǒng)不允許對(duì)同一種信號(hào)進(jìn)行嵌套式的捕捉(如果沒(méi)有屏蔽會(huì)導(dǎo)致在處理時(shí)調(diào)用handler函數(shù))
當(dāng)信號(hào)處理函數(shù)返回時(shí)自動(dòng)恢復(fù)原來(lái)的信號(hào)屏蔽字,這樣就保證了在處理某個(gè)信號(hào)時(shí),如果這種信號(hào)再次產(chǎn)生,那么它會(huì)被阻塞到當(dāng)前處理結(jié)束為止。
演示代碼
#include <iostream> #include <cstring> #include <unistd.h> #include <signal.h> using namespace std; void PrintPending() { sigset_t set; sigpending(&set); for (int signo = 1; signo <= 31; signo++) { if (sigismember(&set, signo)) cout << "1"; else cout << "0"; } cout << "\n"; } void handler(int signo) { cout << "catch a signal, signal number : " << signo << endl; while (true) { PrintPending(); sleep(1); } } int main() { struct sigaction act, oact; memset(&act, 0, sizeof(act)); memset(&oact, 0, sizeof(oact)); act.__sigaction_handler = handler; sigaction(2,&act,&oact); while (true) { cout << "I am a process: " << getpid() << endl; sleep(1); } return 0; }
如果在調(diào)用信號(hào)處理函數(shù)時(shí)
除了當(dāng)前信號(hào)被自動(dòng)屏蔽之外,還希望自動(dòng)屏蔽另外一些信號(hào)
- 用sa_mask字段說(shuō)明這些需 要額外屏蔽的信號(hào),當(dāng)信號(hào)處理函數(shù)返回時(shí)自動(dòng)恢復(fù)原來(lái)的信號(hào)屏蔽字。
- sa_?ags字段包含一些選項(xiàng),本章的代碼都 把sa_?ags設(shè)為0,sa_sigaction是實(shí)時(shí)信號(hào)的處理函數(shù)
示例代碼
#include <iostream> #include <cstring> #include <unistd.h> #include <signal.h> using namespace std; void PrintPending() { sigset_t set; sigpending(&set); for (int signo = 1; signo <= 31; signo++) { if (sigismember(&set, signo)) cout << "1"; else cout << "0"; } cout << "\n"; } void handler(int signo) { cout << "catch a signal, signal number : " << signo << endl; while (true) { PrintPending(); sleep(1); } } int main() { struct sigaction act, oact; memset(&act, 0, sizeof(act)); memset(&oact, 0, sizeof(oact)); sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask, 1); sigaddset(&act.sa_mask, 3); sigaddset(&act.sa_mask, 4); act.__sigaction_handler = handler; sigaction(2, &act, &oact); while (true) { cout << "I am a process: " << getpid() << endl; sleep(1); } return 0; }
可重入函數(shù)
會(huì)導(dǎo)致節(jié)點(diǎn)丟失,內(nèi)存泄漏問(wèn)題
main函數(shù)調(diào)用insert函數(shù)向一個(gè)鏈表head中插入節(jié)點(diǎn)node1,插入操作分為兩步,剛做完第一步的 時(shí)候,因 為硬件中斷使進(jìn)程切換到內(nèi)核,再次回用戶態(tài)之前檢查到有信號(hào)待處理,于是切換 到sighandler函 數(shù),sighandler也調(diào)用insert函數(shù)向同一個(gè)鏈表head中插入節(jié)點(diǎn)node2,插入操作的 兩步都做完之后從 sighandler返回內(nèi)核態(tài),再次回到用戶態(tài)就從main函數(shù)調(diào)用的insert函數(shù)中繼續(xù) 往下執(zhí)行,先前做第一步 之后被打斷,現(xiàn)在繼續(xù)做完第二步。結(jié)果是,main函數(shù)和sighandler先后 向鏈表中插入兩個(gè)節(jié)點(diǎn),而最后只 有一個(gè)節(jié)點(diǎn)真正插入鏈表中了。
像上例這樣,insert函數(shù)被不同的控制流程調(diào)用,有可能在第一次調(diào)用還沒(méi)返回時(shí)就再次進(jìn)入該函數(shù),這稱 為重入,insert函數(shù)訪問(wèn)一個(gè)全局鏈表,有可能因?yàn)橹厝攵斐慑e(cuò)亂,像這樣的函數(shù)稱為 不可重入函數(shù),反之, 如果一個(gè)函數(shù)只訪問(wèn)自己的局部變量或參數(shù),則稱為可重入(Reentrant) 函數(shù)。想一下,為什么兩個(gè)不同的 控制流程調(diào)用同一個(gè)函數(shù),訪問(wèn)它的同一個(gè)局部變量或參數(shù)就不會(huì)造成錯(cuò)亂?
如果一個(gè)函數(shù)符合以下條件之一則是不可重入的:
調(diào)用了malloc或free,因?yàn)閙alloc也是用全局鏈表來(lái)管理堆的。
調(diào)用了標(biāo)準(zhǔn)I/O庫(kù)函數(shù)。標(biāo)準(zhǔn)I/O庫(kù)的很多實(shí)現(xiàn)都以不可重入的方式使用全局?jǐn)?shù)據(jù)結(jié)構(gòu)。
volatile
- volatile防止編譯器過(guò)度優(yōu)化,保存內(nèi)存可見(jiàn)性(保證每次都從內(nèi)存讀?。?/li>
- 邏輯反也是一種計(jì)算
- 在優(yōu)化條件下,flag在CPU內(nèi)執(zhí)行,在內(nèi)存修改不會(huì)影響到寄存器
makefile
mysignal:mysignal.cc g++ -o $@ $^ -O3 -g -std=c++11 .PHONY:clean clean: rm -f mysignal
#include <iostream> #include <cstring> #include <unistd.h> #include <signal.h> using namespace std; volatile int flag = 0; void handler(int signo) { cout << "catch a signal: " << signo << endl; flag = 1; } int main() { signal(2, handler); // 在優(yōu)化條件下, flag變量可能被直接優(yōu)化到CPU內(nèi)的寄存器中 while(!flag); // flag 0, !falg 真 cout << "process quit normal" << endl; return 0; }
SIGCHLD信號(hào)
子進(jìn)程退出時(shí)主動(dòng)向父進(jìn)程發(fā)送SIGCHLD(17)信號(hào)
#include <iostream> #include <cstdlib> #include <signal.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> void handler(int signo) { void handler(int signo) { sleep(5); pid_t rid; while ((rid = waitpid(-1, nullptr, WNOHANG)) > 0) { cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child process quit: " << rid << endl; } } } // 驗(yàn)證子進(jìn)程退出,給父進(jìn)程發(fā)送SIGCHLD int main() { signal(SIGCHLD, handler); // 問(wèn)題1: 1個(gè)子進(jìn)程,10個(gè)呢? // 問(wèn)題2: 10個(gè)子進(jìn)程,6個(gè)退出了! for (int i = 0; i < 10; i++) { if (fork() == 0) { sleep(5); std::cout << "子進(jìn)程退出" << std::endl; // 子進(jìn)程 exit(0); } } while (true) { sleep(1); } return 0; }
如果不用知道子進(jìn)程退出結(jié)構(gòu)直接這樣即可。
int main() { signal(SIGCHLD, handler); // 問(wèn)題1: 1個(gè)子進(jìn)程,10個(gè)呢? // 問(wèn)題2: 10個(gè)子進(jìn)程,6個(gè)退出了! for (int i = 0; i < 10; i++) { if (fork() == 0) { sleep(5); std::cout << "子進(jìn)程退出" << std::endl; // 子進(jìn)程 exit(0); } } while (true) { sleep(1); } return 0; }
17號(hào)信號(hào)默認(rèn)SIG_DFL他的默認(rèn)行為是IGN,所以仍舊會(huì)有僵尸進(jìn)程
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
centos開(kāi)機(jī)自動(dòng)啟動(dòng)RabbitMq軟件的方法
本文詳細(xì)講解了centos開(kāi)機(jī)自動(dòng)啟動(dòng)RabbitMq軟件的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以收藏下2021-11-11Linux中crontab定時(shí)任務(wù)不執(zhí)行的原因
本篇文章主要介紹了Linux中crontab定時(shí)任務(wù)不執(zhí)行的原因,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03關(guān)于linux中系統(tǒng)輸入輸出的管理詳解
這篇文章主要給大家介紹了關(guān)于linux中系統(tǒng)輸入輸出的管理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用linux具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04ubuntu系統(tǒng)theano和keras的安裝方法
這篇文章主要介紹了ubuntu系統(tǒng)theano和keras的安裝方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12Linux定時(shí)刪除日志的簡(jiǎn)單實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Linux定時(shí)刪除日志的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Linux具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Linux systemV消息隊(duì)列和信號(hào)量詳解
這篇文章主要介紹了Linux systemV消息隊(duì)列和信號(hào)量,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03