二 Linux進程間通信( 三 )

代碼示例:
#include <stdio.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>//信號處理函數void fun1(int signum){printf("捕捉到信號:%d\n",signum);}//信號處理函數2void fun2(int signum){printf("捕捉到信號:%d\n",signum);}//信號注冊函數int main(){//信號注冊//ctrl+csignal(SIGINT,fun1);//ctrl+\signal(SIGQUIT,fun2);while(1){sleep(1);}return 0;}運行結果如下:

二 Linux進程間通信

文章插圖
信號先注冊,進程不要退出 , 然后等待信號的到達,信號到達之后就會執行信號處理函數 。
阻塞信號了解幾個概念:
  • 實際執行信號的處理動作稱為信號遞達
  • 信號遞達的三種方式:默認、忽略和自定義捕捉
  • 信號從產生到遞達之間的狀態,稱為信號未決(Pending)
  • 進程可以選擇阻塞 (Block )某個信號
  • 被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作
    注意:
    • 阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之后可選的一種處理動作
    • OS發生信號給一個進程,此信號不是立即被處理的,那么這個時間窗口中,信號就需要被記錄保存下來 , 那么信號是如何在內核中保存和表示的呢?

二 Linux進程間通信

文章插圖
每個進程都存在一個PCB,在PCB中存在兩個集合 , 一個是未決信號集,一個是阻塞信號集 。
以SIGINT為例說明未決信號集和阻塞信號集的關系:
  • 當進程收到SIGINT信號(信號編號為2),首先這個信號會保存在未決信號集合中,此時對應的2號編號的這個位置上置為1,表示處于未決狀態;在這個信號需要被處理之前首先要在阻塞信號集中的編號2的位置上區檢查該值是否為1
  • 如果是1,表示SIGINT信號被當前進程阻塞了,這個信號暫時不被處理,所以未決集上該位置上的值保持為1,表示該信號處于未決狀態
  • 如果是0,表示SIGINT信號沒有被當前進程阻塞,這個信號需要被處理,內核會對SIGINT信號進行處理(執行默認動作,忽略或者執行用戶自定義的信號處理函數),并將未決信號集合中編號2的位置將1置為0,表示該信號已經被處理了,這個時間非常短
  • 當SIGINT信號從阻塞信號集中解除阻塞之后,該信號就會被處理
注意:
未決信號集在內核中 , 要對內核進行操作只能通過系統調用 , 但是沒有提供這樣的方法,所以只能對未決信號集進行讀操作,但是可以對阻塞信號集進行讀寫操作 。
問題1:所有信號的產生都要由OS來進行執行 , 這是為什么?
信號的產生涉及到軟硬件,且OS是軟硬件資源的管理者,還是進程的管理者 。
問題2:進程在沒有收到信號的時候,能否知道自己應該如何對合法信號進行處理呢?
答案是能知道的 。每個進程都可以通過task_struct找到表示信號的三張表 。此時該進程的未決信號集表中哪些信號對應的那一位比特位是為0的,且進程能夠查看阻塞信號集表知道如果收到該信號是否需要阻塞,可以查看handler表知道對該信號的處理動作 。
問題3:OS如何發生信號?
OS給某一個進程發送了某一個信號后,OS會找到信號在進程中未決信號集表對應的那一位比特位,然后把那一位比特位由0置1,這樣OS就完成了信號發送的過程 。
信號集操作函數sigset_t: 未決和阻塞標志可以用相同的數據類型sigset_t來存儲,sigset_t稱為信號集,也被定義為一種數據類型 。這個類型可以表示每個信號狀態處于何種狀態(是否被阻塞,是否處于未決狀態) 。阻塞信號集也叫做當前進程的信號屏蔽字,這里的“屏蔽”應該理解為阻塞而不是忽略 。
實際上兩個信號集在都是內核使用位圖機制來實現的,想了解的可以自己去了解下,但是操作系統不允許我們直接對其操作 。而需要自定義另外一個集合,借助于信號集操作函數來對PCB中的這兩個信號集進行修改 。
信號集操作函數: sigset_t類型對于每種信號用一個bit表示“有效”或“無效”狀態 , 至于這個類型內部如何存儲這些bit則依賴于系統實現,從使用者的角度是不必關心的,使用者只能調用以下函數來操作sigset_ t變量,而不應該對它的內部數據做任何解釋 。注意: 對應sigset類型的變量,我們不可以直接使用位操作來進行操作,而是一個嚴格實現系統給我們提供的庫函數來對這個類型的變量進行操作 。

推薦閱讀