Linux中的信號(注冊,注銷,處理,阻塞)
未決:pending(漢語翻譯:待定,即將發(fā)生)
- 信號產生但沒有被處理,pending表0變1,表示進程收到對應信號(準確的來說叫信號寫入)
- 阻塞,為了攔截對應信號做出對應的處理動作。不想處理某些信號,攔不住別人給進程發(fā)
- 信號待定是可以隨時遞達(被處理),如果沒有被阻塞,信號會在合適的時候遞達(被處理)
- 什么叫合適的時候:從內核態(tài)切換到用戶態(tài)時進行信號的檢測和處理
- 如果信號阻塞,該信號永遠處于未決狀態(tài)
- 總之pending表就是隨時能處理信號,一旦設置了阻塞,只有解除阻塞才能處理信號
信號注冊
信號注冊:在進程中做標記,讓進程能夠知道自己收到某個信號,官方說法操作系統(tǒng)內核給進程發(fā)送信號的過程,這個過程稱為注冊
- PCB中有一個pending未決(沒有被處理)信號的集合(是一個位圖結構),標記進程收到了某個信號
- PCB中有一個sigqueue鏈表(實質是一個雙向鏈表),進程每收到一個信號,就是給該鏈表中添加一個對應信號的節(jié)點,并且修改位圖,將信號值對應位置置1
非可靠信號注冊:
- 如果沒有收到信號則注冊(添加節(jié)點,位圖置1)
- 如果之前收到了信號還未處理,則什么都不做(丟棄)
可靠信號注冊:
- 不管位圖是否置1(是否已經注冊,還沒處理),都會添加一個信號節(jié)點
可以通過sigqueue函數(shù)在發(fā)送信號的同時攜帶數(shù)據(jù)
信號注銷
什么是注銷:
- 在信號被處理之前,消除信號存在的痕跡
- 主要防止信號被重復處理
非可靠信號的注銷:
刪除信號的信息節(jié)點,位圖置0
可靠信號的注銷:
刪除信號的一個信息節(jié)點,當前沒有相同節(jié)點則位圖置0
信號處理
處理就是調用信號的處理函數(shù)
有三種處理方式
默認處理:系統(tǒng)中已經定義好了處理方式
忽略處理:空的處理方式
SIGCHLD:子進程再退出時給父進程發(fā)送一個SIGCHLD信號,父進程對于該信號的處理方式是忽略處理 該信號對于所有進程而言都是忽略處理
自定義處理:自己定義一個處理函數(shù),替換掉處理函數(shù)
自定義處理函數(shù)signal
用handler函數(shù),替換signum信號當前的處理函數(shù)

自定義信號的捕捉流程(捕捉流程:從用戶態(tài)進入內核態(tài),找到信號并處理的過程)
進入內核的原因:
- 系統(tǒng)調用函數(shù)
- 中斷
- 進程異常
當前執(zhí)行流在用戶態(tài)收到信號,不能立即處理信號,而是從用戶態(tài)切換到內核態(tài)時處理
進入內核后,執(zhí)行流并不知道要處理信號,而是執(zhí)行一些功能當要從內核態(tài)返回到用戶態(tài),一定會調用do_signal函數(shù)判斷當前進程有無收到信號,收到則立即處理,沒有直接返回到用戶態(tài)
如果有信號就執(zhí)行信號對應動作
如果是自定義處理方式,會從內核態(tài)切換到用戶態(tài)執(zhí)行自定義函數(shù),調用sigreturn函數(shù)再次進入到內核(不能以內核身份執(zhí)行用戶寫的自定義代碼)
再去調用dosignal判斷是否有其它信號需要處理
信號阻塞
阻塞(阻塞不是在處理信號)
信號被阻塞后依然會注冊,但是暫時不處理(直到被解除阻塞)
PCB中有個block阻塞信號集合,哪個信號被添加到block阻塞集合中,表示收到信號但暫時不處理
更改或獲取進程的信號block表(將信號集合設置進內核)
int sigprocmask(int how, const sigset_t* set, sigset_t* oset)
how:對PCB中信號阻塞集合進行的操作
SIG_BLOCK:block|=set
把set集合中的信號添加到block集合中,阻塞set集合中的信號
SIG_UNBLOCK:block&=~set
解除
SIG_SETMASK:block=set
重新設置block表
oldset將修改前block集合中的信息添加到old中,便于還原
是輸出型參數(shù),將原block返回,不想返回設置為NULL
要屏蔽2號信號,將2號信號添加到set中
set是輸入型參數(shù)
- 函數(shù)在阻塞指定信號時,就要傳進去一個集合, 意味著要將所有的信號添加到一個集合中
- 所以要先定義一個信號集合的變量sigset_t
- 通過sigsetempty(sigset* set)先將集合中的數(shù)據(jù)清空
- 通過sigaddset(sigset_t* set, int sugnum)----將signum添加到set集合中(單個添加)
- 通過sigfillset(sigset_t *set)------------------------將所有信號添加到set中
- 通過sigdelset(sigset_t *set, int signum)----移除指定信號
- 通過sigismember(sigset_t *set, int signum)–按段信號signum是(返回1)否(-1)在set信號集合中
9號和19號不能被阻塞,忽略,自定義
僵尸進程是已經死了的
SIGPIPE
- 管道讀端關閉,繼續(xù)寫,會在write會觸發(fā)異常
SIGCHLD
- 子進程退出時給父進程發(fā)送的信號
- 這個信號的默認處理方式是忽略,因此未對子進程退出做出處理
修改SIGCHLD的默認處理方法(讓回調函數(shù)內部wait子進程)其實并不合理
因為該信號是非可靠信號,大量子進程同時退出,發(fā)出的多個非可靠信號可能只注冊了一次
可直接顯示忽略,明確告訴OS,子進程退出我不關心,直接把資源回收
signal(SIGCHLD, SIG_IGN)
或者在回調函數(shù)內部,使用waitpid循環(huán)等待每一個子進程退出的信號
int sigpending(sigset_t *set)
獲取當前進程的pending信號集(輸出型參數(shù)),放入set集合中
PCB如何獲取signal函數(shù)使用自定義信號
在PCB有一個結構體指針struct sighand_struct* sighand,該指針指向結構體sighand_struct;
該結構體中有一個struct k_sigaction action[_NSIG]數(shù)組,數(shù)組中的每個元素是一個多重嵌套結構體,如圖

- action數(shù)組中一個元素就保存了一個信號對應的處理方式,
- 例如,2號信號對應一個action數(shù)組中的元素,2號信號的處理方式就保存在_sighander_t類型的變量中
- 該類型的變量保存了三種數(shù)據(jù),就是上述的三種信號處理方式,默認處理、忽略處理、自定義處理
PCB與signal函數(shù)的關系
- 在調用signal函數(shù),參數(shù)sighandler保存了新函數(shù)地址,該新地址是傳送給PCB中action數(shù)組的元素,該元素內容被修改
- 就通過signal函數(shù)改變了2號信號的處理方式,
- 當進程在處理2號時通過PCB一步步找到action數(shù)組找到2號信號的處理方式,發(fā)現(xiàn)其處理方式是自定義的函數(shù)
- 從而調用自定義的函數(shù)
sigaction函數(shù)
修改指定信號的處理動作
改變action數(shù)組元素中第二個結構體,而signal函數(shù)只是改變該結構體下的一個成員變量
需要配合其他函數(shù)使用

sa_handler是信號的處理方式
sa_mask:信號正在被處理時,內存會將當前信號加入到信號屏蔽字,信號返回時回復成原來的信號屏蔽字
這樣保證了在處理某個信號時,此信號再次產生就會被阻塞直到當前處理結束

需要用到的清空函數(shù)(上圖黑色部分指錯了):

可重入函數(shù)
現(xiàn)有一個鏈表需要進行頭插,在執(zhí)行頭插函數(shù)時,代碼進入內核在退出時發(fā)現(xiàn)一個信號,該信號也是在執(zhí)行頭插代碼
這樣會發(fā)生內存泄漏,信號所調用的頭插函數(shù)插入的節(jié)點找不到了,這種函數(shù)就稱為不可重入函數(shù)
如果只是訪問變量或者參數(shù),則稱為可重入函數(shù)
volatile關鍵字

在編譯的時候,全局變量flags只進行邏輯運算,編譯器會自動將其優(yōu)化,將變量優(yōu)化到寄存器中
不從內存中讀取,而信號修改flag的值只是對內存值修改
防止編譯時因優(yōu)化,使CPU不從內存中取值,而從寄存器中取值
sigchld
子進程退出時不是默默的退出
子進程退出時會給父進程發(fā)送SIGCHLD信號,該信號的默認處理方式是IGN(忽略)
無論是阻塞還是非阻塞,都需要父進程主動去詢問
則現(xiàn)在提前告訴子進程,子進程死亡時發(fā)消息,父進程收到后回收資源即可
所以通過signal函數(shù)修改SIGCHLD信號的處理方式,改為wait等待子進程即可
前七個子進程結束,通過waitpid等待成功,在等待第八個子進程退出時,父進程會阻塞在waitpid函數(shù)處
通過sigaction將SIGCHLD的處理動作設置為SIG_IGN,這樣fork出來的子進程在終止時會自動清理掉,不會產生僵尸進程
這兩個忽略其實沒有任何區(qū)別
意義:手動設置了SIG_IGN,除了修改父進程,子進程也會受影響,讓子進程退出不給對應父進程發(fā)送信號
在OS層面上,手動設置信號,會在設置子進程時不給父進程發(fā)消息,并自動釋放
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
如何在Linux中route?add命令添加路由(Linux?route)
route?add命令是用于管理Linux操作系統(tǒng)網絡路由,允許用戶添加、刪除或修改網絡路由表中的條目,在Linux系統(tǒng)上添加靜態(tài)路由,靜態(tài)路由是手動配置的路由,它指定了數(shù)據(jù)包從一個網絡到達另一個網絡的路徑,通過添加靜態(tài)路由,可實現(xiàn)網絡分段、轉發(fā)和通信等2024-06-06
Linux環(huán)境使用crontab命令設置定時周期性執(zhí)行任務【含php執(zhí)行代碼】
這篇文章主要介紹了Linux環(huán)境使用crontab命令設置定時周期性執(zhí)行任務,結合實例形式較為詳細的分析了crontab命令的功能、應用場景及相關操作注意事項,并附帶了php執(zhí)行代碼供大家參考,需要的朋友可以參考下2019-07-07

