1、信號
首先信號我們要和信號量區分開來,雖然兩者都是操作系統進程通信的方式。可以簡單的理解,信號是用來通知進程發生了什么需要做什么,信號量一般是用作進程同步(pv操作)
2、常見信號量
(以下數字標號代表信號再bitmap中的位置)
2SIGINT 可能使我們最常用的信號之一。一般在我們想進程中斷,鍵盤輸入Ctrl + C 即可實現,這是一個進程終止信號。
3 SIGQUIT程序異常退出信號和2 類似, 輸入Ctrl + \ 實現
4SIGILL 執行了非法指令. 通常是因為可執行文件本身出現錯誤, 或者試圖執行數據段. 堆棧溢出時也有可能產生這個信號。
11SIGSEGV試圖訪問未分配給自己的內存, 或試圖往沒有寫權限的內存地址寫數據.和SIGILL一樣,程序出BUG時,我們經常見到。
17 SIGCHLD 子進程結束時, 父進程會收到這個信號。如果父進程沒有處理這個信號,也沒有等待(wait)子進程,子進程雖然終止,但是還會在內核進程表中占有表項,這時的子進程稱為僵屍進程。
這種情況我們應該避免(父進程或者忽略SIGCHILD信號,或者捕捉它,或者wait它派生的子進程,或者父進程先終止,這時子進程的終止自動由init進程來接管)。后面會詳細介紹。
19 SIGSTOP 停止(stopped)進程的執行. 注意它和terminate以及interrupt的區別:該進程還未結束, 只是暫停執行. 本信號不能被阻塞, 處理或忽略. GDB中就使用了該信號。
20 SIGIO 文件描述符准備就緒, 可以開始進行輸入/輸出操作.
3、信號常見操作
我們從進程說起,在進程的控制塊中(PCB)有兩個表一個叫做信號未決表,一個叫做信號阻塞表(都是64位圖存儲)。內核首先根據信號阻塞表中屏蔽狀態字判斷是否需要阻塞,如果該信號被設為為了阻塞的,那么信號未決表中對應 狀態字(pending)相應位制成1;若該信號阻塞解除,信號未
決狀態字(pending)相應位制成0;表示信號此時可以抵達了,也就是可以接收該信號了。其實由這個地方我們可以看到,同一個時刻,如果一個信號為阻塞了,那么無論你到來了多少次,在解除阻塞的時候進程只會處理一次。
備注: 阻塞意味着,信號到了,我暫時不處理,放着就是。 信號忽略,進程看到了,但是什么都不會做。
由此可見,對於信號而言,要么直接處理,要么一會處理(也有可能一直不處理), 要么壓根就不會處理。
我們看下系統中內置的API接口:
int sigemptyset(sigset_t *set);//將信號集清空,共64bits
int sigfillset(sigset_t *set);//將信號集置1
int sigaddset(sigset_t *set, int signum);//將signum對應的位置為1
int sigdelset(sigset_t *set, int signum);//將signum對應的位置為0
int sigismember(const sigset_t *set, int signum);//判斷signum是否在該信號集合中,如果集合中該位為1,則返回1,表示位於在集合中
// 讀取更改屏蔽狀態字的API函數
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/*
參數how有下面三種取值:
SIG_BLOCK: 將參數set指向的信號集中設置的信號添加到現在的屏蔽狀態字中,設置為阻塞;
SIG_UNBLOCK:將參數set指向的信號集中設置的信號添加到現在的屏蔽狀態字中,設置為非阻塞, 也就是解除阻塞;
SIG_SETMASK:將參數set指向的信號集直接覆蓋現在的屏蔽狀態字的值;
如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。
若成功則為0,若出錯則為-1
*/
#include <signal.h>
#include <unistd.h>
using namespace std;
void Handle(int sig) {
printf("sig = %d\n", sig);
abort();
}
void Prints(sigset_t* set) {
for (int i = 1; i <= 31; ++i) {
if (sigismember(set, i)) {
printf("1");
} else {
printf("0");
}
}
printf("\n");
}
int main(){
signal(SIGINT, Handle);
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
sigprocmask(SIG_BLOCK, &set, NULL);
// signal(SIGINT, SIG_IGN); //忽略信號操作。
int i = 5;
while(i) {
i--;
sigset_t p;
sigpending(&p); // 獲得當前信號未決表的數據。
Prints(&p);
sleep(2);
}
sigprocmask(SIG_UNBLOCK, &set, NULL);
//sigprocmask(SIG_UNBLOCK, &set, NULL);
//sigset_t p;
//sigpending(&p);
//Prints(&p);
printf("-1----------------------");
printf("-2----------------------");
printf("-3----------------------");
printf("-4----------------------");
return 0;
}
在上面代碼中,signal(SIGINT, Handle),我們自己注冊了處理信號的函數。先展示結果:
0000000000000000000000000000000
0000000000000000000000000000000
^C0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
sig = 2
簡單解釋一下,按下ctrl + c 給進程傳入了中斷的信號,在map中位置為2的地方置為了1,因為我們阻塞了信號,所以他不做處理。
等在循環結束以后我們解除了屏蔽,系統調用了我們注冊的函數,進行了信號處理,在處理函數中我們調用了abort,所以剩下的代碼並沒有執行。
如果我們把代碼中的注釋打開再編譯運行,發現是下面的結果:
0000000000000000000000000000000
0000000000000000000000000000000
^C0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0000000000000000000000000000000
-1-----------------------2-----------------------3-----------------------4----------------------
可以看到,SIGINT信號,到的時候,不是先忽略,是先判定阻塞,放在了未決信號表中。
然后等信號解阻塞以后,可以看到,進程忽略了該信號。不做處理。
4、SIGCHLD信號
這個信號,我們上面解釋說明過。子進程退出時,會給父進程發送這個命令,讓父進程回收資源。如果父進程不做處理,那么就成了子進程就成了我們說的僵屍進程,造成內存泄漏(敲黑板,這玩意也會造成內存泄漏)。
如果子進程退出的時候,父進程早就沒了,那么回收資源的工作交給init進程。一般情況下,如果父進程,不關心子進程的資源回收,也不期待從兒子那邊獲得什么,可以對這個信號進行忽略,像上面代碼中那樣操作即可
signal(SIGCHLD, SIG_IGN)
。這樣也不會產生僵屍進程,子進程退出的時候,發個信號給父進程。父進程處理方式是忽略,子進程就自己退出了。
當然也可以自己創建處理函數,處理該信號。
在引入代碼之前我們先說兩個函數:wait 和 waitpid
頭文件sys/wait.h
pid_t wait(int *status);
進程一旦調用了wait,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成僵屍的子進程,wait就會收集這個子進程的信息,並把它徹底銷毀后返回;如果沒有找到這樣一個子進程,wait就會一直阻塞在這里,直到有一個出現為止。
參數status用來保存被收集進程退出時的一些狀態,它是一個指向int類型的指針。但如果我們對這個子進程是如何死掉的毫不在意,只想把這個僵屍進程消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個參數為NULL,就象下面這樣: pid = wait(NULL);
pid_t waitpid(pid_t pid, int *status, int options);
參數:
status:如果不是空,會把狀態信息寫到它指向的位置,與wait一樣
options:允許改變waitpid的行為,最有用的一個選項是WNOHANG,它的作用是防止waitpid把調用者的執行掛起
The value of options is an OR of zero or more of the following con-
stants:
WNOHANG return immediately if no child has exited.
wait與waitpid區別:
在一個子進程終止前, wait 使其調用者阻塞,而waitpid 有一選擇項,可使調用者不阻塞。
waitpid並不等待第一個終止的子進程—它有若干個選擇項,可以控制它所等待的特定進程。
實際上wait函數是waitpid函數的一個特例。waitpid(-1, &status, 0);
了解這些以后我們看代碼例子:
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
void waitchildren(int signo) {
int status;
wait(&status);
//while(1)waitpid(-1, &status, WNOHANG); //注釋3
//while(1)wait(&status) //注釋4
}
int main() {
int i;
pid_t pid;
//signal(SIGCHLD, waitchildren); //注釋1
//signal(SIGCHLD, SIG_IGN); //注釋2
for(i=0; i<100; i++) {
pid = fork();
if(pid == 0)
break;
}
if(pid>0) {
printf("press Enter to exit...");
getchar();
}
return 0;
}
如果 我們編譯運行,發現在不退出的情況下,重新開一個終端運行top命令,可以看到zombie字段為100.即是100個僵屍進程。
如果打開注釋1 發現還是有僵屍進程,但是數量不為100,同一時刻,很多子進程退出,但是可惜只處理一部分。
關閉注釋1打開注釋2.發現一個僵屍進程都沒了。
如果只打開注釋3 你也會發現一個僵屍進程都沒了。
如果你只打開注釋4,你會發現程序只處理了一個,而且卡死了。
wait 我們可以稱其為同步接口,而waitpid為異步接口。
再看下面這段代碼:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <iostream>
int count = 0;
void waitchildren(int signo) {
count++;
std::cout << count << "------------" << std::endl;
int status;
wait(&status);
}
int main() {
int i;
pid_t pid;
signal(SIGCHLD, waitchildren);
//signal(SIGCHLD, SIG_IGN);
sigset_t set;
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);
for(i=0; i<100; i++) {
pid = fork();
if(pid == 0)
break;
}
if(pid>0) {
printf("press Enter to exit...");
getchar();
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
return 0;
}
這段程序你會發現,只有一個子進程發送的信號,被捕獲處理了。
以上就是全部內容,歡迎各位大佬,糾錯指正。