文章目錄
- 4. 由軟件條件產生信號
- 5. 硬件異常產生信號
- 模擬一下除0錯誤和野指針異常
- 除0錯誤
- 野指針錯誤
- 總結思考一下
4. 由軟件條件產生信號
SIGPIPE
是一種由軟件條件產生的信號,在“管道”中已經介紹過了。
軟件條件不就緒,很明顯這個軟件條件沒有直接報錯,而是通過返回值來反映。
#include <iostream>
#include <signal.h>
#include<unistd.h>using namespace std;int main()
{char buffer[1024];int n=1024;n=read(4,buffer,sizeof(buffer));printf("n=%d\n",n);perror("read");return 0;
}
軟件條件可能會產生信號也可能不會,取決于操作系統本身。
操作系統是由對軟件檢測的能力的,所以能通過軟件條件產生信號。
本節主要介紹alarm
函數 和SIGALRM
信號。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
調用alarm函數可以設定一個鬧鐘,
也就是告訴內核在seconds秒之后給當前進程發SIGALRM信號,
該信號的默認處理動作是終止當前進程。
這個函數的返回值是0或者是以前設定的鬧鐘時間還余下的秒數。
打個比方:
某人要小睡一覺,設定鬧鐘為30分鐘之后響,
20分鐘后被人吵醒了,還想多睡一會兒,
于是重新設定鬧鐘為15分鐘之后響,“以前設定的鬧鐘時間還余下的時間”就是10分鐘。
如果seconds
值為0,表示取消以前設定的鬧鐘,
函數的返回值仍然是以前設定的鬧鐘時間還余下的秒數
例 alarm
#include <iostream>
#include <signal.h>
#include<unistd.h>using namespace std;
int main()
{alarm(5);while(1){cout<<"proc is running"<<endl;sleep(1);}return 0;
}
驗證:收到了14號信號
#include <iostream>
#include <signal.h>
#include<unistd.h>using namespace std;void handler(int signum)
{cout<<"get a sig,num: "<<signum<<endl;
}int main()
{signal(14,handler);alarm(5);while(1){cout<<"proc is running"<<endl;sleep(1);}return 0;
}
因為只設置一次,鬧鐘只響了一次(因為不是異常)
如果想讓鬧鐘每隔5秒響一次
(收到了14號信號,就去調用處理方法,
在調用方法里,又設置了一個鬧鐘,5秒之后,又收到了14號信號,繼續調用處理方法……)
void handler(int signum)
{cout<<"get a sig,num: "<<signum<<endl;alarm(5);
}
查看剩余時間
收到了14號信號,那么調用處理方法,鬧鐘將會重新設置,alarm(5)
#include <iostream>
#include <signal.h>
#include<unistd.h>using namespace std;void handler(int signum)
{cout<<"get a sig,num: "<<signum<<endl;int n=alarm(5);cout<<"time: "<<n<<endl;
}int main()
{signal(14,handler);int n=alarm(50);while(1){cout<<"proc is running,pid: "<<getpid()<<endl;sleep(1);}return 0;
}
操作系統內部會有很多鬧鐘,所以OS要管理鬧鐘,
先描述再組織,對鬧鐘的管理就變成了對鏈表的增刪查改。
鬧鐘的描述:有指向進程的指針,有時間(使用時間戳)
時間戳+設定的時間=未來時間
如果現在時間大于等于這個未來時間就表示超時了。
遍歷鏈表對比時間,如果時間到了就發送信號,該節點就可以從鏈表里刪除了。
提高效率:
使用優先級隊列或者堆等數據結構。
最小堆,將數據結構都放進最小堆,
堆頂數據沒有超時,那么整個堆都沒有超時。
堆頂超時了,只要將堆頂處理,就可以移除堆頂元素。
5. 硬件異常產生信號
硬件異常被硬件以某種方式被硬件檢測到并通知內核,
然后內核向當前進程發送適當的信號。
例如當前進程執行了除以0的指令,
CPU的運算單元會產生異常,
內核將這個異常解釋 為SIGFPE
信號發送給進程。
再比如當前進程訪問了非法內存地址,
MMU
會產生異常,內核將這個異常解釋為SIGSEGV
信號發送給進程。
捕捉信號,不是為了解決出現的問題,
而是為了讓用戶知道進程為什么掛了。(做做收尾工作)
模擬一下除0錯誤和野指針異常
makefile
mysignal:mysignal.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -rf mysignal
除0錯誤
mysignal.cc
#include <iostream>
#include <signal.h>
#include<unistd.h>using namespace std;int main()
{cout<<"div before"<<endl;sleep(1);int a=10;a/=0;cout<<"div after"<<endl;sleep(1);return 0;
}
證明收到了8號信號
mysignal.cc
#include <iostream>
#include <signal.h>
#include<unistd.h>using namespace std;void handler(int signum)
{cout<<"get a sig,num: "<<signum<<endl;//只是打印了一行信息,其他什么都沒干
}int main()
{signal(8,handler);cout<<"div before"<<endl;// sleep(1);int a=10;a/=0;cout<<"div after"<<endl;// sleep(1);return 0;
}
代碼為什么一直都不退出呢?
因為8號信號的默認動作是退出,但是現在改成了自定義動作,
自定義動作只有打印信息沒有設置退出,所以進程不會退出。
野指針錯誤
mysignal.cc
#include <iostream>
#include <signal.h>
#include<unistd.h>using namespace std;int main()
{cout<<"point error before"<<endl;int *p=nullptr;//p指向0號地址*p=100;//沒有資格訪問0號地址(權限問題/野指針問題)cout<<"point error after"<<endl;return 0;
}
驗證收到11號信號
mysignal.cc
#include <iostream>
#include <signal.h>
#include<unistd.h>using namespace std;void handler(int signum)
{cout<<"get a sig,num: "<<signum<<endl;//只是打印了一行信息,其他什么都沒干
}int main()
{signal(11,handler);cout<<"point error before"<<endl;int *p=nullptr;//p指向0號地址*p=100;//沒有資格訪問0號地址(權限問題/野指針問題)cout<<"point error after"<<endl;return 0;
}
以上證明,進程出了異常不一定會退出,只要捕捉信號即可。
(但是不退出意義不大了,還占用著CPU的資源)
不退出就會一直被調度運行,硬件異常沒有被修正,
然后運行又有硬件報錯,然后OS一直發信號,
進程收到信號繼續被捕捉……如此循環。
由此可以確認,我們在C/C++當中除零,內存越界等異常,
在系統層面上,是被當成信號處理的。
為什么除0和野指針會讓進程崩潰呢?
因為收到了信號,該信號的默認處理動作是終止進程自己。
為什么除0和野指針會給進程發信號呢?
因為硬件發生了報錯,OS檢測到了,所以給進程發信號。
OS怎么知道發生了除0和野指針?
除0:
所以,除0錯誤最終會被轉化成硬件問題,
OS識別到了硬件報錯,
所以OS要給進程發信號,
所以進程收到信號會自己終止(崩潰)。
野指針:
CPU內部不同的寄存器的報錯代表不同的信號。
總結思考一下
上面所說的所有信號產生,最終都要有OS來進行執行,為什么?
因為OS是進程的管理者!
OS是進程的管理者信號的處理是否是立即處理的?
不是立即處理。進程可能正在做更重要的事。
在合適的時候,信號如果不是被立即處理,那么信號是否需要暫時被進程記錄下來?記錄在哪里最合適呢?
一個進程在沒有收到信號的時候,能否能知道,自己應該對合法信號作何處理呢?
如何理解OS向進程發送信號?能否描述一下完整的發送處理過程?