Linux進(jìn)程信號(hào)的用法及說(shuō)明
信號(hào)產(chǎn)生
生活中的信號(hào)類比(交通信號(hào)燈、警報(bào)),當(dāng)產(chǎn)生這些信號(hào)時(shí),我們會(huì)立馬想到對(duì)應(yīng)的動(dòng)作。
在Linux中,信號(hào)是事件發(fā)生對(duì)進(jìn)程的通知機(jī)制亦稱軟件中斷,由操作系統(tǒng)內(nèi)核、進(jìn)程本身或者其他進(jìn)程向目標(biāo)進(jìn)程異步事件發(fā)送機(jī)制(即收到某種信號(hào),并不會(huì)立馬去執(zhí)行)。通知進(jìn)程發(fā)生某種預(yù)定義的時(shí)間,要求進(jìn)程作出相應(yīng)響應(yīng)。
信號(hào)與硬件中斷相似之處在于會(huì)打斷程序執(zhí)行流程。
常見(jiàn)信號(hào)
硬件異常:
- 如內(nèi)存越界(
SIGSEGV) - 除零錯(cuò)誤(
SIGFPE) - 由內(nèi)核自動(dòng)生成
系統(tǒng)調(diào)用:
kill():向指定進(jìn)程發(fā)送信號(hào)raise():進(jìn)程向自身發(fā)送信號(hào)alarm():設(shè)置定時(shí)器,超時(shí)后發(fā)送SIGALRM
終端輸入:
Ctrl+C:SIGINT(終止進(jìn)程)Ctrl+Z:SIGTSTP(暫停進(jìn)程)Ctrl+\:
軟件條件:
- 子進(jìn)程退出時(shí),父進(jìn)程收到
SIGCHLD - 定時(shí)器到期(如
alarm())觸發(fā)信號(hào)
核心轉(zhuǎn)儲(chǔ) :
信號(hào)的分類
信號(hào)分為兩大類。
(編號(hào)1-31)為傳統(tǒng)信號(hào)信號(hào),內(nèi)核向進(jìn)程通知且遞送一次,(編號(hào)34-64)為實(shí)時(shí)信號(hào)使信號(hào)按序遞送。
信號(hào)處理方式
- 默認(rèn)動(dòng)作: 部分是終止自己,暫停等
- 忽略動(dòng)作: 是一種信號(hào)處理的方式,只不過(guò)動(dòng)作就是什么都不干
- 自定義動(dòng)作: 使用signal方法修改信號(hào)的處理動(dòng)作。(即用戶程序員編寫(xiě)的函數(shù):將默認(rèn)動(dòng)作轉(zhuǎn)化為自定義動(dòng)作)
改變信號(hào)處理方式
signal,sigaction
信號(hào)阻塞
概念悉知
- 實(shí)際執(zhí)行信號(hào)的處理動(dòng)作稱為信號(hào)遞達(dá)(Delivery)
- 信號(hào)從產(chǎn)生到遞達(dá)之間的狀態(tài),稱為信號(hào)未決(Pending)。
- 進(jìn)程可以選擇阻塞 (Block )某個(gè)信號(hào)。
- 被阻塞的信號(hào)產(chǎn)生時(shí)將保持在未決狀態(tài),直到進(jìn)程解除對(duì)此信號(hào)的阻塞,才執(zhí)行遞達(dá)的動(dòng)作.
- 阻塞和忽略是不同的,只要信號(hào)被阻塞就不會(huì)遞達(dá),而忽略是在遞達(dá)之后可選的一種處理動(dòng)作。

信號(hào)產(chǎn)生后會(huì)稍后遞達(dá)給某進(jìn)程,信號(hào)在產(chǎn)生和到達(dá)期間會(huì)一直處于pending等待狀態(tài)。
信號(hào)在內(nèi)核中的表示
每個(gè)進(jìn)程都有一個(gè)信號(hào)屏蔽字亦稱阻塞信號(hào)集,一個(gè) sigset_t 類型的位圖,每一位對(duì)應(yīng)一個(gè)信號(hào)(如 SIGINT、SIGQUIT 等)。用來(lái)標(biāo)記哪些信號(hào)當(dāng)前被阻塞。被阻塞的信號(hào)若已產(chǎn)生,會(huì)進(jìn)入未決狀態(tài),直到阻塞被解除才會(huì)觸發(fā)處理。位為1生效,0不生效。
block與pending之間聯(lián)系
pending集是信號(hào)產(chǎn)生后的暫存區(qū),block是控制信號(hào)是否能從pending暫存區(qū)遞交給進(jìn)程的開(kāi)關(guān)
- block:決定哪些信號(hào)會(huì)被阻塞,近而將其放入未決信號(hào)集
- pending:記錄已產(chǎn)生但未被處理的信號(hào)(因被阻塞或正在處理其他信號(hào))
- 通過(guò)
sigpending()査詢掛起的信號(hào)集,通過(guò)sigprocmask()控制阻塞狀態(tài)。
其中block位圖用于表示進(jìn)程是否阻塞。
pending位圖用于是否有信號(hào)寫(xiě)入(信號(hào)是否產(chǎn)生,信號(hào)產(chǎn)生時(shí),內(nèi)核在進(jìn)程控制塊中設(shè)置該信號(hào)的pending位圖,直到信號(hào)抵達(dá)才消失)。handler函數(shù)指針數(shù)組用于進(jìn)程執(zhí)行何種動(dòng)作(其中存放的是動(dòng)作函數(shù)指針,每個(gè)信號(hào)的編號(hào)就是其數(shù)組下標(biāo))。


關(guān)鍵系統(tǒng)調(diào)用
信號(hào)集操作函數(shù)
sigset_t 本質(zhì)上是一個(gè)位圖,每一位代表一個(gè)信號(hào)。當(dāng)某一位被設(shè)置為 1 時(shí),表示對(duì)應(yīng)的信號(hào)被包含在信號(hào)集中;為 0 則表示不包含。使用者只能調(diào)用以下函數(shù)來(lái)操作sigset_t變量,不用關(guān)注內(nèi)部數(shù)據(jù)。
在使用sigset_t類型的變量之前,一定要調(diào)用sigemptyset()函數(shù)初始化一個(gè)未包含任何成員的信號(hào)集或者sigfillset()函數(shù)則初始化一個(gè)信號(hào)集,使其包含所有信號(hào)(包括所有實(shí)時(shí)信號(hào))。
#include <signal.h> // 清空信號(hào)集,置0 int sigemptyset(sigset_t *set); // 填充所有信號(hào), 置有效狀態(tài)1? int sigfillset(sigset_t *set);
信號(hào)集初始化后,可以分別使用 sigaddset()和sigdelset()函數(shù)向一個(gè)集合中添加或者移除單個(gè)信號(hào)。使用sigismember()測(cè)試信號(hào)是否是信號(hào)集set的成員。
#include <signal.h> // 添加單個(gè)信號(hào) int sigaddset(sigset_t *set, int signo); // 刪除單個(gè)信號(hào) int sigdelset(sigset_t *set, int signo); // 判斷信號(hào)是否存在 int sigismember(const sigset_t *set, int signo);
信號(hào)掩碼(阻塞信號(hào)傳遞)

內(nèi)核會(huì)為每個(gè)進(jìn)程維護(hù)一個(gè)信號(hào)掩碼(一組信號(hào)),阻塞其針對(duì)該進(jìn)程的傳遞。如果將被阻塞的信號(hào)發(fā)送給某進(jìn)程,那么對(duì)該信號(hào)的傳遞將延后,直至從進(jìn)程信號(hào)掩碼中移除該信號(hào),從而解除阻塞為止。阻塞信號(hào)集內(nèi)0變1只能由其觸發(fā)。
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
參數(shù) :
how:指定了sigprocmask()函數(shù)想給信號(hào)掩碼帶來(lái)的變化。
SIG_BLOCK:set包含了我們希望添加到當(dāng)前信號(hào)屏蔽字的信號(hào)(邏輯或)SIG_UNBLOCK:set包含了我們希望從當(dāng)前信號(hào)屏蔽字中解除阻塞的信號(hào)(與非)SIG_SETMASK:設(shè)置當(dāng)前信號(hào)屏蔽字為set所指向的值(直接賦值)
set
- 要操作的信號(hào)集,(為NULL則忽略)
oldset
- 保存舊的信號(hào)掩碼(可為NULL)
返回值:成功返回 0,失敗返回 -1(設(shè)置 errno)
- 阻塞:信號(hào)暫存到未決信號(hào)集直到解除阻塞
- 忽略:直接丟棄
操作示例:
void handler(int no){
cout << "execcute user-defined actions" << " ";
}
int main(){
//更改2號(hào)信號(hào)執(zhí)行動(dòng)作
signal(2, handler);
//創(chuàng)建兩個(gè)信號(hào)集
sigset_t set, old_set;
//清空信號(hào)集
sigemptyset(&set);
sigaddset(&set, SIGINT);//將2號(hào)信號(hào)添加進(jìn)set集01000...000
//將set信號(hào)集中指定信號(hào)添加到當(dāng)前阻塞信號(hào)集中,同時(shí)將原來(lái)的信號(hào)集保存到old_set中
sigprocmask(SIG_BLOCK, &set, &old_set);//阻塞set中包含的信號(hào)集
cout << "SIG_INT is blocked, ctrl+c has failed" << endl;
sleep(5);//5秒內(nèi)ctrl+c不會(huì)有反應(yīng)
//使用old_set替換當(dāng)前所有阻塞信號(hào)集
sigprocmask(SIG_SETMASK, &old_set, NULL);//恢復(fù)之前阻塞信號(hào)集
cout << "SIG_INT has been resloved" << endl;
sleep(5);
return 0;
}

阻塞期間發(fā)送2號(hào)信號(hào)?
信號(hào)不會(huì)立馬處理,而是將該信號(hào)加入pending信號(hào)集(即pending位圖的SIG_INT位從0-->1)
信號(hào)阻塞接觸后?
pending信號(hào)集內(nèi)該信號(hào)立即處理(處理方式鑒于默認(rèn)動(dòng)作,忽略(丟棄) 或 自定義動(dòng)作),該位由1-->0。
sigpending獲取未決信號(hào)集

如果某進(jìn)程接受了一個(gè)該進(jìn)程正在阻塞的信號(hào),那么會(huì)將該信號(hào)填加到進(jìn)程的等待信號(hào)集中。當(dāng)之后解除了對(duì)該信號(hào)的鎖定時(shí),會(huì)隨之將信號(hào)傳遞給此進(jìn)程。
#include <signal.h> int sigpending(sigset_t *set);
系統(tǒng)調(diào)用返回后,用戶空間的set變量包含了當(dāng)前的未決信號(hào)集,可以使用sigismember()檢查特定信號(hào)是否在未決信號(hào)集中。
作用: 返回處于等待狀態(tài)的信號(hào)集,并將其置于 set指向的sigset_t 結(jié)構(gòu)中。
| 操作 | 是否進(jìn)入未決? | 后續(xù)動(dòng)作 |
|---|---|---|
| 信號(hào)被阻塞,且期間被發(fā)送 | 是 | 該信號(hào)記錄在未決信號(hào)集中,直到阻塞解除后被處理 |
| 信號(hào)被阻塞,期間未被發(fā)送 | 否 | 不記錄該信號(hào),解除阻塞后若收到信號(hào)則直接處理 |
| 信號(hào)未被阻塞,但被發(fā)送 | 否 | 信號(hào)直接調(diào)用對(duì)應(yīng)函數(shù)即可 |
signal() - 設(shè)置信號(hào)處理
#include <signal.h> void (*signal(int sig, void (*func)(int)))(int);
返回函數(shù)指針的函數(shù):

代碼示例:
void handler(int signo){
cout << "execcute user-defined actions" << " ";
}
...
//更改2號(hào)信號(hào)執(zhí)行動(dòng)作
signal(2, handler);
聲明該函數(shù)類型是一個(gè)函數(shù)指針類型,signal是函數(shù)名(如:int (*p)(int,int),p是函數(shù)名,有兩個(gè)int類型參數(shù),返回值是一個(gè)int類型)。signal返回的是一個(gè)函數(shù)指針,該函數(shù)指向的函數(shù)參數(shù)是一個(gè)int類型,返回值是void。所以外層是指明這是一個(gè)函數(shù)指針類型。里面才是使用的函數(shù)本體。
signal是一個(gè)函數(shù)- 它接受一個(gè)整數(shù)和一個(gè)函數(shù)指針作為參數(shù)
- 它返回一個(gè)與第二個(gè)參數(shù)類型相同的函數(shù)指針
返回函數(shù)指針的數(shù)組: 對(duì)?

sigaction() - 設(shè)置信號(hào)處理
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum : 目標(biāo)信號(hào)
act 新的信號(hào)處理方式
sa_handler:信號(hào)處理函數(shù)指針(或SIG_IGN、SIG_DFL)。sa_mask:處理該信號(hào)時(shí)額外阻塞的信號(hào)集。sa_flags:控制選項(xiàng)(如SA_RESTART、SA_NODEFER)NULL
oldact
- 保存舊的處理配置(可為
NULL)
返回值:成功返回 0,失敗返回 -1(設(shè)置 errno)
struct sigaction {
void (*sa_handler)(int); // 簡(jiǎn)單處理函數(shù)
void (*sa_sigaction)(int, siginfo_t *, void *); // 高級(jí)處理函數(shù)
sigset_t sa_mask; // 處理期間屏蔽的信號(hào)集
int sa_flags; // 控制標(biāo)志
void (*sa_restorer)(void); // 內(nèi)部使用(已廢棄)
};
現(xiàn)在我們使用sigprocmask函數(shù)和sigpending函數(shù)完成一個(gè)函數(shù)。思路是
- 對(duì)2號(hào)信號(hào)寫(xiě)入信號(hào)集
- 將該信號(hào)集使用
sigprocmask函數(shù)將2號(hào)信號(hào)block住 - 我們鍵盤(pán)輸入
ctrl+c,產(chǎn)生2號(hào)信號(hào),發(fā)送至該進(jìn)程。 - 使用
sigpending函數(shù)輸出pending位圖,因?yàn)槲覀儗?code>2號(hào)進(jìn)程block住了,該信號(hào)并不能抵達(dá)。我們?cè)傧蚱浒l(fā)送2號(hào)信號(hào),信號(hào)被block住了并不會(huì)影響信號(hào)的寫(xiě)入。因此我們?cè)侔l(fā)送2號(hào)進(jìn)程之前pending位圖應(yīng)該全為0,發(fā)送2號(hào)進(jìn)程之后會(huì)看見(jiàn)pending位圖發(fā)送由0至1的變化。
執(zhí)行流程:
- 前 5 秒內(nèi),
SIGINT(Ctrl+C)被阻塞,信號(hào)進(jìn)入未決狀態(tài)但不觸發(fā)處理。 - 5 秒后解除阻塞,若之前有未決的
SIGINT,會(huì)立即觸發(fā)handle函數(shù)。
#include <iostream>
#include <cassert>
#include <unistd.h>
#include <signal.h>
using namespace std;
static void handler(int signo){
cout << signo << " 號(hào)信號(hào)確實(shí)遞達(dá)了" << endl;
//最終不退出進(jìn)程
}
void DisplayPending(const sigset_t pending){
// 打印 pending 表
int i = 1;
while (i < 32)
{
if (sigismember(&pending, i)) cout << "1";
else cout << "0";
i++;
} cout << endl;
}
int main(){
// 更改 2 號(hào)信號(hào)的執(zhí)行動(dòng)作
signal(2, handler);
// 創(chuàng)建信號(hào)集
sigset_t set, oset;
// 信號(hào)集清空 0
sigemptyset(&set);
sigemptyset(&oset);
sigaddset(&set, 2); //將2號(hào)信號(hào)寫(xiě)入set
// 設(shè)置當(dāng)前進(jìn)程的屏蔽信號(hào)集
sigprocmask(SIG_BLOCK, &set, &oset);//0給進(jìn)程
// 死循環(huán)
int n = 0;
while (true){
if (n == 5){
// 采用 SIG_SETMASK 的方式,覆蓋進(jìn)程的 block 表
sigprocmask(SIG_SETMASK, &oset, NULL); // 不接收進(jìn)程的 block 表
}
// 獲取進(jìn)程的 未決信號(hào)集
sigset_t pending;
sigemptyset(&pending);
int ret = sigpending(&pending);
assert(ret == 0);
(void)ret; // 避免 release 模式中出錯(cuò)
DisplayPending(pending);
n++;
sleep(1);
}
return 0;
}
信號(hào)處理
進(jìn)程地址空間

為了進(jìn)程可以通過(guò)訪問(wèn)虛擬地址來(lái)間接訪問(wèn)物理資源,我們需要建立虛擬地址空間與物理內(nèi)存的映射關(guān)系。用戶區(qū)和內(nèi)核區(qū)都需要通過(guò)內(nèi)核級(jí)頁(yè)表或者用戶級(jí)頁(yè)表進(jìn)行映射使用。

系統(tǒng)調(diào)用:
系統(tǒng)調(diào)用是受控的內(nèi)核入口,借助于這一機(jī)制,進(jìn)程可以請(qǐng)求內(nèi)核以自己的名義去執(zhí)行某些動(dòng)作。以應(yīng)用程序編程接口(API)的形式,內(nèi)核提供有一系列服務(wù)供程序訪問(wèn)。
信號(hào)處理過(guò)程
用戶態(tài)的時(shí)候執(zhí)行用戶代碼,在中斷/異常/系統(tǒng)調(diào)用的時(shí)候切換到內(nèi)核態(tài),査看進(jìn)程的三張表。(信號(hào)捕捉過(guò)程)當(dāng)需要執(zhí)行自定義動(dòng)作的時(shí)候,切回用戶態(tài)執(zhí)行自定義動(dòng)作,自定動(dòng)作完成之后,再切到內(nèi)核態(tài)執(zhí)行sys_sigretur()函數(shù),最后再切回用戶態(tài)中用戶代碼的下一行繼續(xù)執(zhí)行。
當(dāng)執(zhí)行默認(rèn)動(dòng)作或者忽略動(dòng)作的時(shí)候,直接可以在內(nèi)核中完成,完成之后切回用戶態(tài)中的用戶代碼的下一行,繼續(xù)執(zhí)行。

- sigaction()
- volatile關(guān)鍵字
- SIGCHLD
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Linux實(shí)現(xiàn)搭建ssh并允許使用root遠(yuǎn)程
這篇文章主要介紹了Linux實(shí)現(xiàn)搭建ssh并允許使用root遠(yuǎn)程方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02
centos yum更新及刪除多余啟動(dòng)項(xiàng)
在CentOS更新后,并不會(huì)自動(dòng)刪除舊內(nèi)核。所以在啟動(dòng)選項(xiàng)中會(huì)有多個(gè)內(nèi)核選項(xiàng),可以手動(dòng)使用以下命令刪除多余的內(nèi)核:2018-04-04
CentOS 6.5 環(huán)境實(shí)現(xiàn)本地局域網(wǎng)搭建YUM的方法【基于FTP】
這篇文章主要介紹了CentOS 6.5 環(huán)境實(shí)現(xiàn)本地局域網(wǎng)搭建YUM的方法,結(jié)合實(shí)例形式分析了CentOS基于FTP本地局域網(wǎng)搭建YUM的具體步驟、相關(guān)命令與操作技巧,需要的朋友可以參考下2018-04-04
詳解CentOS7下PostgreSQL 11的安裝和配置教程
這篇文章主要介紹了CentOS7下PostgreSQL 11的安裝和配置教程,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10

