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

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


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

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

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

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

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

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

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

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

- sigaction()
- volatile關(guān)鍵字
- SIGCHLD
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于linux分區(qū),文件系統(tǒng),目錄結(jié)構(gòu)的概述
下面小編就為大家?guī)硪黄P(guān)于linux分區(qū),文件系統(tǒng),目錄結(jié)構(gòu)的概述。小編覺得挺不錯的?,F(xiàn)在就分享給大家。也給大家做個參考。一起跟隨小編過來看看吧2016-12-12
Ubuntu 20.04缺少libssl.so.1.0.0錯誤的解決方案
在使用Ubuntu 20.04時,你可能會遇到一些較老的應(yīng)用程序因缺少libssl.so.1.0.0庫而無法運行的問題,這是因為Ubuntu 20.04默認使用的是更新版本的OpenSSL庫,本文將介紹如何快速解決這個問題,需要的朋友可以參考下2025-11-11
如何解決Kali?Linux?APT更新中的GPG簽名錯誤問題
這篇文章主要介紹了如何解決Kali?Linux?APT更新中的GPG簽名錯誤問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-05-05
用DNSPod和Squid打造自己的CDN (六) 編譯并安裝Squid
這篇文章主要介紹centos下編譯并安裝Squid的方法,需要的朋友可以參考下2013-04-04

