信號(hào)的舊識(shí)引入
kill -l
是一個(gè)在Linux
和Unix
系統(tǒng)中使用的命令,用于列出可用的信號(hào)列表。
在Linux和Unix系統(tǒng)中,進(jìn)程可以通過發(fā)送信號(hào)來與其他進(jìn)程或操作系統(tǒng)交互。kill 命令可以向指定的進(jìn)程發(fā)送一個(gè)特定的信號(hào),以便對(duì)其進(jìn)行控制,例如終止進(jìn)程或重新啟動(dòng)進(jìn)程等。
kill -l 命令會(huì)列出可用的信號(hào)列表,每個(gè)信號(hào)都有一個(gè)唯一的數(shù)字編號(hào)和一個(gè)名稱。輸出結(jié)果通常是一個(gè)由數(shù)字和名稱組成的列表,例如:
[AMY@VM-12-15-centos ~]$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
我們可以發(fā)現(xiàn)里面數(shù)字沒有0、32 、33:1~31
普通信號(hào)34~64
實(shí)時(shí)信號(hào)
進(jìn)程信號(hào)的認(rèn)識(shí):信號(hào)是給進(jìn)程發(fā)的,比如kill -9 (pid)
信號(hào)相關(guān)總結(jié):
- 進(jìn)程是如何識(shí)別信號(hào)的?認(rèn)識(shí)+動(dòng)作
- 進(jìn)程本身是被程序員編寫的屬性和邏輯的集合—程序員編碼完成的
- 當(dāng)進(jìn)程收到信號(hào)的時(shí)候,進(jìn)程可能正在執(zhí)行更重要的代碼,所以信號(hào)不一定會(huì)被立即處理
- 進(jìn)程本身必須要有對(duì)于信號(hào)的保存能力
- 進(jìn)程在處理信號(hào)(信號(hào)被捕捉)的時(shí)候,一般有三種動(dòng)作(默認(rèn),自定義,忽略)
如果一個(gè)信號(hào)是發(fā)給進(jìn)程的,而進(jìn)程要保存,那么應(yīng)該保存在哪里? task struct(PCB)
如何保存呢?是否收到了指定的信號(hào)1~31
struct task_struct
{
...
unsigned int signal;
...
}
32位:
比特位的位置,代表信號(hào)編號(hào)。
比特位的內(nèi)容,代表是否收到該信號(hào),0沒有
,1有
發(fā)送信號(hào)的本質(zhì):修改PCB中的信號(hào)位圖
PCB
是內(nèi)核維護(hù)的數(shù)據(jù)結(jié)構(gòu),PCB的管理者是OS
,那么誰有權(quán)力修改PCB的內(nèi)容呢?是OS,所以無論我們學(xué)習(xí)多少種發(fā)送信號(hào)的方法,本質(zhì)上都是通過OS向目標(biāo)進(jìn)程發(fā)送信號(hào)。那么OS一定需要提供發(fā)送信號(hào)的相關(guān)系統(tǒng)調(diào)用,我們也可以推測(cè)出kill
命令,底層肯定是調(diào)用了對(duì)應(yīng)的系統(tǒng)調(diào)用
信號(hào)引入
我們常常使用CTRL+c
來終止一個(gè)正在運(yùn)行的進(jìn)程,本質(zhì)上,這個(gè)CTRL+c
是一個(gè)組合鍵,OS將其解釋成為2
號(hào)信號(hào)2) SIGINT
我們可以使用:
man 7 signal
原始POSIX.1-1990標(biāo)準(zhǔn)中描述的信號(hào):
signal調(diào)用
man 2 signal
函數(shù)原型:
sighandler_t signal(int signum, sighandler_t handler);
案例使用:
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{
std::cout << "進(jìn)程捕捉到了一個(gè)信號(hào),信號(hào)編號(hào)是: " << signo << std::endl;
}
int main()
{
// 這里是signal函數(shù)的調(diào)用,并不是handler的調(diào)用
/// 僅僅是設(shè)置了對(duì)2號(hào)信號(hào)的捕捉方法,并不代表該方法被調(diào)用了
// 一般這個(gè)方法不會(huì)執(zhí)行,除非收到對(duì)應(yīng)的信號(hào)!
signal(2, handler);
while(true)
{
std::cout << "我是一個(gè)進(jìn)程: " << getpid() << std::endl;
sleep(1);
}
}
直接執(zhí)行我們并沒有執(zhí)行handler()
,因?yàn)闆]有傳遞這個(gè)信號(hào)
我們?cè)趫?zhí)行的時(shí)候按CTRL+c
就會(huì)執(zhí)行handler()
為什么我們現(xiàn)在的CTRL+c
不能夠終止進(jìn)程呢?是因?yàn)槲覀儗⒛J(rèn)動(dòng)作改為自定義動(dòng)作而去執(zhí)行handler()
了,所以我們不能終止,需要終止我們?cè)?code>handler()函數(shù)最后加上exit
就行。當(dāng)然我們還可以使用kill -9 (pid)
去殺掉這個(gè)進(jìn)程
系統(tǒng)調(diào)用向目標(biāo)進(jìn)程發(fā)送信號(hào)
模擬實(shí)現(xiàn)一個(gè)kill命令
先寫了一個(gè)將來會(huì)一直運(yùn)行的程序mytest.cpp
,用來進(jìn)行后續(xù)的命令測(cè)試
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(true)
{
std::cout << "我是一個(gè)正在運(yùn)行的進(jìn)程,pid: " << getpid() << std::endl;
sleep(1);
}
}
我們使用man手冊(cè)查看kill的系統(tǒng)調(diào)用
然后我們實(shí)現(xiàn)讀取鍵盤部分代碼mysignal.cpp
:
#include <iostream>
#include <cstdio>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
using namespace std;
static void Usage(const string &proc)
{
std::cout << "\nUsage: " << proc << " pid signo\n" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
pid_t pid = atoi(argv[1]);
int signo = atoi(argv[2]);
int n = kill(pid,signo);
if(n != 0)
{
perror("kill");
}
return 0;
}
從上述過程我們可得:我們使用自己的調(diào)用實(shí)現(xiàn)了kill命令
,我們平時(shí)使用的kill命令其實(shí)也就是封裝的系統(tǒng)調(diào)用kill
kill()
可以向任意進(jìn)程發(fā)送任意信號(hào)
raise給自己發(fā)送任意信號(hào)
int main(int argc, char* argv[])
{
int cnt=0;
while(true)
{
cnt++;
cout<<"cnt:"<<cnt<<endl;
if(cnt>=3)
{
raise(9);
}
}
return 0;
}
函數(shù)原型:
int raise(int sig);
只需要傳入信號(hào)值,就可以直接發(fā)送該信號(hào)
abort給自己發(fā)送指定信號(hào)(6)SIGABRT
int cnt=0;
while(true)
{
cnt++;
cout<<"cnt:"<<cnt<<endl;
if(cnt>=3)
{
abort();
//raise(9);
}
}
前三個(gè)調(diào)用總結(jié):
-
kill()
可以想任意進(jìn)程發(fā)送任意信號(hào) -
raise()
給自己 發(fā)送 任意信號(hào) 等同:kill(getpid(), 任意信號(hào))
-
abort()
給自己 發(fā)送 指定的信號(hào)SIGABRT 等同:kill(getpid(), SIGABRT)
關(guān)于信號(hào)處理的行為的理解:有很多的情況,進(jìn)程收到大部分的信號(hào),默認(rèn)處理動(dòng)作都是終止進(jìn)程
信號(hào)的意義:信號(hào)的不同,代表不同的事件,但是對(duì)事件發(fā)生之后的處理動(dòng)作可以一樣!
硬件異常產(chǎn)生信號(hào)
除0異常
我們對(duì)一個(gè)常數(shù)進(jìn)行除0運(yùn)算:
我們捕捉這個(gè)信號(hào):
void catchSig(int signo)
{
std::cout << "獲取到一個(gè)信號(hào),信號(hào)編號(hào)是: " << signo << std::endl;
}
int main(int argc, char* argv[])
{
signal(SIGFPE, catchSig);
while (true)
{
std::cout << "我在運(yùn)行中...." << std::endl;
sleep(1);
int a = 10;
a /= 0;
}
return 0;
}
雖然我們將signal(SIGFPE, catchSig);
是寫在循環(huán)之前,但是當(dāng)我們進(jìn)行除0以后,它還是會(huì)運(yùn)行,這是因?yàn)椋?code>signal()在這里是注冊(cè)一種未來的方法,當(dāng)某種條件成立后就自動(dòng)調(diào)用這個(gè)注冊(cè)好的方法
我們還發(fā)現(xiàn)這個(gè)捕捉到信號(hào)SIGFPE后,它就一直運(yùn)行catchSig()
打印
除0異常解釋:
由上述輸出結(jié)果我們可以知道:收到信號(hào)不一定會(huì)引起進(jìn)程退出,沒有退出有可能還會(huì)被調(diào)度。而CPU內(nèi)部的寄存器只有一份,但是寄存器里面的內(nèi)容,屬于當(dāng)前進(jìn)程上下文。一旦出現(xiàn)異常,我們有沒有能力或者動(dòng)作去修正這個(gè)問題呢?答案是沒有,比如溢出標(biāo)記位被置1了,我們有沒有能力去更改為0呢?我們肯定不能,因?yàn)闋顟B(tài)寄存器是由CPU自己維護(hù)的,用戶沒有辦法也沒有權(quán)力去更改。
當(dāng)進(jìn)程被切換的時(shí)候,就有無數(shù)次狀態(tài)寄存器被保存和恢復(fù)的過程,所以每一次恢復(fù)的時(shí)候,就讓OS識(shí)別到了CPU內(nèi)部的狀態(tài)寄存器中的溢出標(biāo)志位是1
野指針訪問異常
while(true)
{
std::cout << "我在運(yùn)行中...." << std::endl;
sleep(1);
int *p = nullptr;
*p=10;
}
void catchSig(int signo)
{
std::cout << "獲取到一個(gè)信號(hào),信號(hào)編號(hào)是: " << signo << std::endl;
}
int main(int argc, char *argv[])
{
signal(11, catchSig);
int *p = nullptr;
*p = 10;
while (true)
{
std::cout << "我在運(yùn)行中...." << std::endl;
sleep(1);
}
return 0;
}
由上述的操作結(jié)果我們可知:野指針訪問屬于是(11) SIGSEGV
,且跟除0的捕捉結(jié)果是一樣的,也是一直調(diào)用catchSig()
。
MMU位于CPU內(nèi)部,在ARM32中,MMU主要完成虛擬地址到物理地址的映射,并且能夠控制內(nèi)存的訪問權(quán)限,而頁表是實(shí)現(xiàn)上述功能的主要手段。頁表又分為一級(jí)頁表、二級(jí)頁表,在ARM64中甚至還有三級(jí)頁表。上圖這樣畫只是為了更形象。
while循環(huán)
的代碼不會(huì)被使用因?yàn)椋涸L問野指針并觸發(fā)SIGSEGV
信號(hào)時(shí),程序已經(jīng)進(jìn)入了異常狀態(tài)。當(dāng)catchSig函數(shù)
被調(diào)用時(shí),異常狀態(tài)并沒有被完全處理,因此程序進(jìn)入了一個(gè)不穩(wěn)定的狀態(tài)。一旦catchSig函數(shù)返回,程序會(huì)嘗試?yán)^續(xù)執(zhí)行之前的指令,但由于進(jìn)程仍處于異常狀態(tài),又會(huì)再次觸發(fā)SIGSEGV信號(hào)。這將導(dǎo)致catchSig函數(shù)被不斷地調(diào)用。
軟件條件產(chǎn)生信號(hào)
管道通信舉例:
當(dāng)讀端關(guān)閉,寫端卻一直在寫的情況時(shí)候,OS會(huì)發(fā)送(13) SIGPIPE
信號(hào)來讓寫端關(guān)閉,這中情況就是由軟件條件觸發(fā)產(chǎn)生信號(hào)。
定時(shí)器軟件條件:alarm()
:設(shè)定鬧鐘
int main(int argc, char *argv[])
{
alarm(1);
int cnt = 0;
while (true)
{
cout << "cnt:" << cnt << endl;
cnt++;
}
return 0;
}
輸出結(jié)果:
我們更改一下代碼:
int cnt = 0;
void catchSig(int signo)
{
std::cout << "獲取到一個(gè)信號(hào),信號(hào)編號(hào)是: " << signo << " cnt:" << cnt << std::endl;
}
int main(int argc, char *argv[])
{
signal(SIGALRM, catchSig);
alarm(1);
while (true)
{
cnt++;
}
我們首先可以的個(gè)結(jié)論:IO確實(shí)很慢,都是1秒鐘,一直打印cnt的那個(gè)明顯累加次數(shù)很少(我使用的是云服務(wù)器,這些運(yùn)行的結(jié)果還要通過網(wǎng)絡(luò)傳遞給我,所以會(huì)更慢,一般使用虛擬機(jī)會(huì)快很多)
我們這里跟之前的輸出結(jié)果不一樣,這里只打印了一次,說明alarm只發(fā)送一次信號(hào)
拓展
為什么你說設(shè)置“鬧鐘”是軟件條件呢?
任意一個(gè)進(jìn)程,都可以通過alarm
系統(tǒng)調(diào)用在內(nèi)核中設(shè)置鬧鐘,OS內(nèi)可能會(huì)存在著很多的鬧鐘,那么操作系統(tǒng)要不要管理這些鬧鐘呢?要→先描述再組織
舉例:操作系統(tǒng)中有很多管理鬧鐘的方法比如堆等
描述:
struct alarm
{
uint64_t when; // 未來的超時(shí)時(shí)間int type
// 鬧鐘類型,一次性的,還是周期性
task_struct *p;
struct alarm *next;
}
組織:
OS會(huì)周期性的檢測(cè)這些鬧鐘,如果curr_timestamp > alarm.when
,超時(shí)了OS發(fā)送SIGALARM -> alarm.p;
總結(jié)思考
關(guān)于信號(hào)發(fā)送的問題:
-
上面所說的所有信號(hào)產(chǎn)生,最終都要有OS來進(jìn)行執(zhí)行,為什么?
OS是進(jìn)程的管理者,也只有OS有權(quán)利去操作 -
信號(hào)的處理是否是立即處理的?
在合適的時(shí)候 -
信號(hào)如果不是被立即處理,那么信號(hào)是否需要暫時(shí)被進(jìn)程記錄下來?記錄在哪里最合適呢?
是的,保存在PCB -
一個(gè)進(jìn)程在沒有收到信號(hào)的時(shí)候,能否能知道,自己應(yīng)該對(duì)合法信號(hào)作何處理呢?
我們應(yīng)該知道,比如紅燈快要亮起,但還沒有,此時(shí)我們知不知道紅燈亮起的時(shí)候該怎么辦?答案是知道。信號(hào)還沒有產(chǎn)生但是我們應(yīng)該知道信號(hào)產(chǎn)生該做什么處理,這個(gè)工作由程序員系統(tǒng)中代碼默認(rèn)體現(xiàn)。 -
如何理解OS向進(jìn)程發(fā)送信號(hào)?能否描述一下完整的發(fā)送處理過程?
就是OS直接修改目標(biāo)進(jìn)程中PCB存放信號(hào)的位圖
進(jìn)程退出時(shí)核心轉(zhuǎn)儲(chǔ)問題
同樣都是越界,為什么右邊的越界報(bào)錯(cuò)了?因?yàn)閿?shù)組是只分配了10的空間,但是不代表整個(gè)函數(shù)的棧幀結(jié)構(gòu)只有那么多,所以你即使越界了,但是你還是在有效棧區(qū)里所以就沒報(bào)錯(cuò),除非你訪問了一個(gè)完全不屬于你的空間,比如系統(tǒng)的某些空間,這時(shí)候OS就會(huì)識(shí)別出來。(不同編譯器檢查越界方式也可能不同,比如vs2019就是采用抽檢)
Term正常結(jié)束,Core除了終止還要做其他工作
在云服務(wù)器上,默認(rèn)如果進(jìn)程是core退出的,我們暫時(shí)看不到明顯的現(xiàn)象,如果想看到:
[AMY@VM-12-15-centos lesson_18]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7260
max locked memory (kbytes, -l) unlimited
max memory size (kbytes, -m) unlimited
open files (-n) 100001
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7260
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
云服務(wù)器默認(rèn)關(guān)閉了core file size選項(xiàng):
如何打開這個(gè)選項(xiàng)呢?
我們現(xiàn)在還多了一個(gè)文件core.15199
(core dumped)核心轉(zhuǎn)儲(chǔ)
︰當(dāng)進(jìn)程出現(xiàn)異常的時(shí)候,我們將進(jìn)程在對(duì)應(yīng)的時(shí)刻,在內(nèi)存中的有效數(shù)據(jù),轉(zhuǎn)儲(chǔ)到磁盤中–核心轉(zhuǎn)儲(chǔ)!而core.15199
的15199
是引起core
問題的進(jìn)程的pid
進(jìn)程在運(yùn)行時(shí)出異常,以前的終止就是直接結(jié)束,而現(xiàn)在打開了core選項(xiàng)
進(jìn)程在終止時(shí)會(huì)多做一個(gè)工作,就是把進(jìn)程中一些有效的二進(jìn)制數(shù)據(jù)給dumped轉(zhuǎn)儲(chǔ)
到磁盤當(dāng)中,這個(gè)就叫核心轉(zhuǎn)儲(chǔ),形成的這個(gè)臨時(shí)文件以core
命名,后綴為該進(jìn)程的pid
為什么要有這個(gè)呢?
正常我們程序崩潰了,我們最想知道的是為什么崩潰?在哪里崩潰?而核心轉(zhuǎn)儲(chǔ)可方便我們調(diào)試(Linux調(diào)試我們需要帶上 -g
選項(xiàng))
在gdb
上下文之中,輸入core-file core.(pid值)
Term不可以核心轉(zhuǎn)儲(chǔ),Core可以核心轉(zhuǎn)儲(chǔ)
小實(shí)驗(yàn)
如果我們將所有的信號(hào)全部捕捉,那么還能不能殺死這個(gè)進(jìn)程呢?
void catchSig(int signo)
{
std::cout << "獲取到一個(gè)信號(hào),信號(hào)編號(hào)是: " << signo << std::endl;
}
int main(int argc, char *argv[])
{
for(int signo = 1; signo <= 31; signo++)
{
signal(signo, catchSig);
}
while(true)
{
cout << "我在運(yùn)行: " << getpid() <<endl;
sleep(3);
}
return 0;
}
我們將所有信號(hào)全部捕捉,別的確實(shí)無法終止進(jìn)程,但是kill -9
可以,這也是OS的設(shè)置,OS禁止捕捉9號(hào)進(jìn)程,即使你捕捉了也無法生效,它是管理員進(jìn)程。否則如果真出現(xiàn)惡意程序捕捉所有信號(hào),那豈不是真不能終止。文章來源:http://www.zghlxwxcb.cn/news/detail-578395.html
如有錯(cuò)誤或者不清楚的地方歡迎私信或者評(píng)論指出????文章來源地址http://www.zghlxwxcb.cn/news/detail-578395.html
到了這里,關(guān)于【Linux】進(jìn)程信號(hào) -- 信號(hào)產(chǎn)生 | 系統(tǒng)調(diào)用、硬件、軟件的信號(hào)發(fā)送的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!