文章目錄
- 1.信號的概念
- 1.1基本概念
- 1.2信號的處理基本概念
- 1.3信號的發送與保存基本概念
- 2.信號的產生
- 2.1信號產生的五種方式
- 2.2信號遺留問題(core,temp等)
- 3.信號的保存
- 3.1 信號阻塞
- 3.2 信號特有類型 sigset_t
- 3.3 信號集操作函數
- 3.4 信號集操作函數的使用
- 4.信號的處理
- 4.1 信號的捕捉
- 4.2 深入理解地址空間
- 4.3 如何理解系統調用
- 4.4 sigaction對信號捕捉
- 5.可重入函數
- 6.編譯器的優化及volatile關鍵字
- 7.SIGCHLD信號(子進程退出發的信號)
1.信號的概念
1.1基本概念
所謂同步和異步就是:
比如我正在上課,我讓一個學生去幫我拿快遞,然后我停下等那個學生回來再繼續講,即同步。
如果學生去拿快遞,我不管他,我接著講就是異步!
1-31號為普通信號!
34-64號實時信號!
1.2信號的處理基本概念
信號的處理大致分為三種:
a.默認動作
b.忽略動作
c.自定義處理—信號的捕捉
core,temp都是終止,在本篇文章的后面會有更詳細的介紹!
1.3信號的發送與保存基本概念
2.信號的產生
2.1信號產生的五種方式
信號產生的三種主要方式和兩種不常用接口:
如果把所有信號都捕捉,換成自定義動作那么怎么辦?
答:操作系統有些信號是不允許自定義捕捉的,比如9號信號killed。如果所有信號都能被捕捉那不亂套了!!!而且信號的發送者只有一個,那就是操作系統發的,通過位圖來執行!
下面還有兩種信號產生的方式:
4.軟件條件:
5.異常:
我們都知道進程發生異常了就會崩潰,然后就會退出。
這便是異常發送信號!
那么崩潰了為啥會退出?因為異常的默認動作是終止進程!
那么可以不退出嘛?可以的,我們可以自定義捕捉異常!但是不推薦這么做!
2.2信號遺留問題(core,temp等)
我們用一個多進程的例子再來看看標志位:
3.信號的保存
3.1 信號阻塞
3.2 信號特有類型 sigset_t
從上圖來看,每個信號只有一個bit的未決標志,非0即1,不記錄該信號產生了多少次,阻塞標志也是這樣表示的。因此,未決和阻塞標志可以用相同的數據類型sigset_t來存儲,sigset_t稱為信號集,這個類型可以表示每個信號的“有效”或“無效”狀態,在阻塞信號集中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信號是否處于未決狀態。
阻塞信號集也叫做當前進程的信號屏蔽字(Signal Mask),這里的“屏蔽”應該理解為阻塞而不是忽略。
3.3 信號集操作函數
#include <signal.h>int sigemptyset(sigset_t *set); 把sigset_t 這個位圖全部清空int sigfillset(sigset_t *set); 把整個位圖全部置1int sigaddset (sigset_t *set, int signo); 把一個特定的信號signo設置到這個集合里(置1)int sigdelset(sigset_t *set, int signo); 把一個特定的信號signo在這個集合里清除(置0)int sigismember(const sigset_t *set, int signo); 判斷一個信號是否在集合中
以及兩個系統調用函數:
3.4 信號集操作函數的使用
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>void PrintPending(sigset_t &pending)
{std::cout << "curr process[" << getpid() << "]pending: ";for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)){std::cout << 1;}else{std::cout << 0;}}std::cout << "\n";
}void handler(int signo)
{std::cout << signo << " 號信號被遞達!!!" << std::endl;std::cout << "-------------------------------" << std::endl;sigset_t pending;sigpending(&pending);PrintPending(pending);這里正在處理handler方法的時候,把pending再獲取一次如果這時候打印出來的pending信號為1,就說明只能把handler方法處理完才能清空如果這時候打印出來的pending信號為全0,就說明在進入handler方法之前就把1清零了std::cout << "-------------------------------" << std::endl;
}int main()
{// 0. 捕捉2號信號signal(2, handler); // 自定義捕捉2號信號默認操作是退出,所以我們要自定義捕捉,否則推出了就看不到后面的現象了。// 1. 屏蔽2號信號sigset_t block_set, old_set;sigemptyset(&block_set);sigemptyset(&old_set);sigaddset(&block_set, SIGINT); // 我們有沒有修改當前進行的內核block表呢???1 0// 1.1 設置進入進程的Block表中sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改當前進行的內核block表,完成了對2號信號的屏蔽!int cnt = 15;while (true){// 2. 獲取當前進程的pending信號集sigset_t pending;sigpending(&pending);// 3. 打印pending信號集PrintPending(pending);cnt--;// 4. 解除對2號信號的屏蔽if (cnt == 0){std::cout << "解除對2號信號的屏蔽!!!" << std::endl;sigprocmask(SIG_SETMASK, &old_set, &block_set);}sleep(1);}
}
4.信號的處理
4.1 信號的捕捉
4.2 深入理解地址空間
4.3 如何理解系統調用
4.4 sigaction對信號捕捉
5.可重入函數
main函數調用insert函數向一個鏈表head中插入節點node1,插入操作分為兩步,剛做完第一步的時候,因為硬件中斷使進程切換到內核,再次回用戶態之前檢查到有信號待處理,于是切換到sighandler函數,sighandler也調用insert函數向同一個鏈表head中插入節node2,插入操作的兩步都做完之后從sighandler返回內核態,再次回到用戶態就從main函數調用的insert函數中繼續往下執行,先前做第一步之后被打斷,現在繼續做完第二步。結是,main函數和sighandler先后向鏈表中插入兩個節點,而最后只有一個節點真正插入鏈表中了!
像上例這樣,insert函數被不同的控制流程調用,有可能在第一次調用還沒返回時就再次進入該函數,這稱為重入,insert函數訪問一個全局鏈表,有可能因為重入而造成錯亂,像這樣的函數稱為不可重入函數。
6.編譯器的優化及volatile關鍵字
7.SIGCHLD信號(子進程退出發的信號)
要想不產生僵尸進程還有另外一種辦法:父進程調 用sigaction將SIGCHLD的處理動作置為SIG_IGN,這樣fork出來的子進程在終止時會自動清理掉,不會產生僵尸進程,也不會通知父進程。