上篇文章: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就會向設置鬧鐘的進程發送信號。
????????這種超時的行為,全部是由軟件構成的。所以稱為 軟件條件產生信號