Linux 信號相關知識

什麼是Linux中的信號

信號實際上是軟件中斷, 信號的存在提供了一種處理異步事件的方法。
給進程發信號 實際上是給PDB的信號字段的 對應信號的二進制位置 1;
是讓進程終止 。
信號可以讓進程暫停。
信號是有有限種類的 有編號的。

本質上爲什麼kill -9 (進程id)命令 殺了一個進程 9號信號對進程的默認處理方式
進程即使在沒有收到對應信號之前,也有默認的對每一個信號的處理方法。

一個熟悉的場景

當我們在shell中運行一個程序時,可以使用Ctrl + C來終止進程,這實際上是向正在執行的進程發送了一個SIGINT信號,當這個進程收到了SIGINT信號以後,進程從用戶態進入到內核態,要從內核態返回到用戶態繼續執行進程沒有跑完的代碼時,首先要處理該進程PCB中的信號,而SIGINT信號的默認處理方式時終止進程,這時候進程被終止了而不再返回用戶態去執行未被執行的代碼。

注意:
在這個場景中 被Ctrl + C終止的進程是前臺進程(該操作只能終止前臺進程),一個shell可以同時運行一個前臺進程和若干個後臺進程, 一個命令後面加一個 & 就可以將該操作放到後臺運行,這樣shell就可以繼續接受並處理其他命令。因爲不論進程代碼執行到什麼地方都有可能收到SIGINT信號使進程終止,所以信號相對於進程的控制流程來說是異步。

kill -l

kill -l 命令可以用來查看所有的信號種類和編號。

這裏寫圖片描述

每個信號在singal.h文件中都有自己的編號和宏定義名稱,編號1 — 31是普通信號 34 以上是實時信號 本文只討論普通信號。 man 7 singal 可以查看各個信號的產生原因和默認處理方式。

信號的產生

1、 終端按下某些鍵時,終端驅動程序會自動發送信號給前臺進程。按下ctrl C 操作系統向前臺進程發送了一個信號 2號信號 ctrl \ 3號信號 生成core dump 文件 向進程發信號實際山是對PCB中信號字段對應的那一位 信號編號相同的置1.

/.test & 把test可執行程序變成一個後臺進程 並在屏幕上打印出後臺進程的 id
每個進程的PCB 有 ulimit -a 要查找到的屬性 其中有 open file ——fd_array[] 的大小
stack size 棧的大小 ulimit 命令可以更改bash進程的屬性 bash上 運行的進程 是bash的子進程 gdb (exe) core.xxxx 直接顯示出錯位置
ctrl + Z 把一個前臺進程 變成後臺進程 fg 把一個後臺進程放在前臺進程

2、在代碼中出現除零錯誤 CPU運算單元發生異常後,內核將這個異常解釋爲SIGFPE信號發送給進程。對指向非法內存段的指針解引用,內存映射單元MMU把這個異常解釋爲SIGSEGV信號發送給進程。

3、kill 命令產生的信號發送給某個進程 進程調用kill函數可以發送信號給某個指定的進程。kill函數未制定發送給某個進程的信號時默認發送SIGTERM信號,進程對該信號的默認操作是終止進程。

4、由軟件條件產生的信號, 比如使用管道進行進程間通信時如果管道讀端已經被關閉這時寫端再寫數據內核就會產生 SIFPIPE信號發送給寫數據的進程。鬧鐘超時產生SIGALAM信號。

信號的處理方式

信號處理方式:

1.   忽略
2.   系用默認
3.   信號的捕捉    自定義處理方式   

Core Dump

當一個進程異常終止時, 可以把其用戶地址空間的內存數據全部保存到磁
盤上。磁盤上的這個文件叫core 。這就是 Core Dump ,事後可以通過調試該文件方便的找到進程代碼中的Bug。進程默認是不產生core文件的,可以通過更改進程PCB中的Resource limit 信息更改允許產生core文件的大小。ulimit -c 1024 命令可以更改 core文件的大小 最大(1024k)。

發信號的函數

在命令行使用kill命令 或者 進程內調用kill函數給指定進程髮指定信
raise()函數給自己進程發信號。

  #include <sys/types.h>
  #include <signal.h>
  int kill(pid_t pid, int sig);
  int  raise(int  signal);

代碼:
https://github.com/xym97/Linux/blob/master/Sig/myKill.c

成功返回0 失敗返回-1.
abort函數讓進程收到信號而常終止。

#include <stdlib.h>

void abort(void);

函數不會失敗 沒有返回值。
alarm()函數

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

代碼:
https://github.com/xym97/Linux/blob/master/Sig/alarm.c
該函數意爲設置一個時鐘, seconds秒後給當前進程發送SIGALARM信號,該信號的默認處理操作是終止進程。

信號相關概念

抵達:進程收到信號後(進程PCB表示信號字段的對應二進制位置1)對信號的處理動作叫抵達。
未決:進程收到了信號但未對其做處理。
阻塞:當進程阻塞了某個信號時,進程收到的信號會保持在未決狀態不會對其處理,直到進程取消阻塞該信號,纔會對該信號處理。
忽略:信號抵達後,可選的一種對信號的處理動作。

操作系統內核對信號的表示

這裏寫圖片描述

進程收到信號實際上是對進程PCB中Pending表中表示收到信號的那一位置1(抵達後清零),之後再與block表中對應的那一位比較,如果block對應位置沒有被置1,則表示該信號沒有被阻塞。然後在hander表中找到對應的位置儲存的函數指針,該函數指針指向的函數就是對收到的信號的處理動作。

普通信號在抵達之前產生多次之記一次, 實時信號在抵達之前產生多次存放在一個隊列中。

sigset_t

sigset_t類型表示Pending 和 Blocking 兩個位圖中的那些爲是0 , 那些位是1
這個類型也稱爲信號集。sigset_t類型用來存儲阻塞和未決信號集。

信號集操作函數

 #include <signal.h>

 int sigemptyset(sigset_t *set);

 int sigfillset(sigset_t *set);

 int sigaddset(sigset_t *set, int signum);

 int sigdelset(sigset_t *set, int signum);

 int sigismember(const sigset_t *set, int signum);

sigemptyset()函數和sigfillset()函數用來把set指針指向的保存所有類型信號在Pending表或Block表中是否有效(被置1)的sigset_t類型(信號集)所有信號對應的bit位清零或置1。

sigaddset()和sigdelset()函數在set指定的信號集中加上或刪除signum號信號。

注意:在使用sigset_t類型之前,必須調用sigempty或sigfillset函數將信號集處於確定狀態,再調用sigaddset()和sigdelset()函數在信號集中添加或刪除某個有效信號。

以上介紹的函數成功返回0,出錯返回-1.
sigismember()函數是個bool類型的函數,查看set指向的信號集中signum信號是否是有效的,是返回1,不是返回0,失敗返回-1。

sigprocmask()函數用於讀取或更改進程的信號屏蔽字。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

oldset是非空指針的時候,保存獲取的當前信號屏蔽字。
set是非空指針的時候,和how結合更改信號屏蔽字。

how是SIG_BLOCK mask|set
how是SIG_UNBLOCK mask &~ set
how是SIG_SETBLOCK時 mask=set

sigpending()函數獲取當前進程的未決信號集。

#include <signal.h>
int sigpending(sigset_t *set);

通過set參數傳出 成功返回0, 失敗返回-1。

代碼:
https://github.com/xym97/Linux/blob/master/Sig/block.c

捕捉信號

這裏寫圖片描述

如圖所示捕捉信號的流程 ,注意執行Myhander函數自定義處理信號時Func()是阻塞的 這兩個函數是兩個執行流不存在調用和被調用的關係

1、signal()函數

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

函數第一個參數是要捕捉的信號,第二個參數是對捕捉信號的自定義處理方法。
返回值是這個處理的函數指針。

2、 sigsction()函數

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

第一個參數是要捕捉的信號,第二個參數是對捕捉信號的自定義處理函數,第三個參數是爲了保存之前的處理函數。

當某個信號正在被處理時,內核自帶屏蔽該信號,直到處理動作被完成,內核自動撤銷對該信號的屏蔽。

3、 pause()函數  

#include<unistd.h>
int pause(void);

除非有信號抵達 否則就讓進程掛起  
只有出錯的返回值-1,errno的值爲EINTR。 在信號被捕捉時回到該函數的棧幀時返回-1.如果收到信號進程退出則沒有機會返回。

競態條件與sigsuspendind()函數

這裏寫圖片描述

https://github.com/xym97/Linux/blob/master/Sig/mysleep.c

#include <signal.h>
int sigsuspend(const sigset_t *mask);

該函數把解除信號屏蔽的掛起等待信號作爲一不不可分割的原子操作執行。
通過mask屏蔽字暫時解除某個對應的信號的屏蔽(在該函數內mask是信號屏蔽字)。
使用適當時間屏蔽信號的方法與sigsuspend結合。

可重入與不可重入

一次調用還沒有結束就再次進入該函數成爲重入,這樣做不會造成混亂叫可重入。

符合以下條件之一則不可重入。
調用malloc/free   因爲malloc是用全局雙向鏈表來管理堆空間的。
標準I/0庫函數很多都是不可重入的全局數據結構。

SIGCHLD信號

子進程結束時默認向父進程發送SIGCHLD信號,父進程對該信號的默認處理方式是忽略該信號。
父進程對子進程的處理可以不必阻塞式wait也不必輪詢,可以捕捉SIGCHLD信號
在信號處理函數中wait清理子進程。 
https://github.com/xym97/Linux/blob/master/Sig/sigchld.c

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章