_Linux进程信号详解
信號是什么
一個信號就是一條小消息,它通知進程系統中發生了一個某種類型的事件
信號是多種多樣的,并且一個信號對應一個事件,這樣才能做到收到一個信號后,知道到底是一個什么事件,應該如何處理(但是要保證必須識別這個信號)
信號的種類
使用kill-l命令查看信號種類
一共62種,其中1~31是非可靠信號,34~64是可靠信號(非可靠信號是早期Unix系統中的信號,后來又添加了可靠信號方便用戶自定義信號,這二者之間具體的區別在下文中會提到)
信號的生命周期
產生》》進程中的注冊》》進程中的注銷》》捕獲處理
信號的產生
硬件事件舉例:
- 如果一個進程試圖除以0,那么內核就發送給它一個SIGFPE信號(序號8)
- 如果一個進程執行一條非法指令,那么內核就發送給它一個SIGILL信號(序號4)
- 如果進程進行非法存儲器引用(野指針、段錯誤),內核就發送給它一個SIGSEGV信號(序號11)
軟件事件舉例:
- ctrl+c 中斷信號——20) SIGTSTP
- ctrl+| 退出信號——3) SIGQUIT
- ctrl+z 停止信號——2) SIGINT
- kill命令:kill -signum pid
當kill命令不帶-signum參數時(kill pid),默認的信號是15) SIGTERM
而kill -9 pid則是一個強大的“強殺”命令,能殺死kill pid殺不掉的處于T狀態的進程 - int kill(pid_t pid, int sig);
kill命令的系統調用接口(在代碼中使用kill)
示例:
#include運行結果:
ubuntu@VM-0-7-ubuntu:/home/zeno/c_practice$ ./217_kill Quit (core dumped)- int raise(int signum);
raise是一個庫函數(#include <signal.h>),作用是發送信號到調用這個函數的進程/線程
在單線程程序中,它等效于kill(getpid(), sig);(也就和上面的示例一樣)
在多線程程序中,它等效于pthread_kill(pthread_self(), sig); - void abort();
abort是一個庫函數(#include <stdlib.h>),作用是造成進程異常中止
在進程中調用abort()就相當于調用了raise(3) - unsigned int alarm(unsigned int seconds);
alarm是一個系統調用接口(#include <unistd.h>),在seconds秒后會將SIGALRM信號傳遞到調用進程
信號的注冊
在pcb中有一個未決(pending)信號集合(未決(pending)的意思是信號產生了但還沒有決定怎么做),信號的注冊就是指在這個pending集合中標記對應信號數值的二進制位為1
上面的話有些難以理解,我們先來看看在linux內核源碼里一個進程的信號是如何保存的
在linux內核源碼sched.h中的task_struct結構體里有這樣一段關于信號的內容:
/* signal handlers */上面最后一行的sigpending結構體定義在signal.h中:
struct這里的signal就是用來做信號標記的,給一個進程發送一個信號說白了就是在signal里標記一下這個信號曾經來過
那么signal是如何進行標記的呢?還得繼續了解一下sigset_t這個結構體
在bits/sigset.h中進行了以下定義:
/* A `sigset_t' has a bit for each signal. */注:這里的__sigset_t其實就是sigset_t,只是一個類型名的重定義
在這個結構體中只有一個數組成員,這個數組里存放著一些數作為位圖,位圖的每一個二進制位就代表了一種信號,0表示未曾收到這個信號,1表示已經收到這個信號
這里需要注意的是,真正存放信號的是數組中某個數的某個二進制位,數組的存在只是因為單獨一個數的二進制位存不下這么多種類的信號
現在我們就可以理解,當使用上述方式對某一個進程發送一個信號時,操作系統就會將該進程對應的pending集合中表示相應信號的位圖的二進制位由0改為1
但是非可靠信號和可靠信號的注冊還有一點區別
為了理解這種區別我們還應該了解一下list_head鏈表和signal.h中的sigqueue結構體
list_head是linux內核提供的一個用來創建雙向循環鏈表的結構,由于這個結構是沒有數據域的所以較為復雜,在這里不做深究,有興趣可以通過這篇博客詳細了解
我們需要知道的是,內核通過一個以list為表頭的鏈表將所有產生的信號都串在了一起,鏈表中的每個節點的結構是一個sigqueue:
/*這個結構體保存信號所攜帶的信息
現在我們就可以對非可靠信號和可靠信號的區別有一定的了解了
- 1~31非可靠信號的注冊:
當試圖對一個進程發送一個非可靠信號時,若發現位圖上對應的位為0,則置為1,并在list_head鏈表里加入一個sigqueue節點;若發現位圖上對應的位已經為1,則直接返回。簡單地說就是若信號還未注冊,則注冊一下,若已經注冊,則什么都不做 - 34~64可靠信號的注冊:
當試圖對一個進程發送一個可靠信號時,若發現位圖上對應的位為0,則置為1,并在list_head鏈表里加入一個sigqueue節點;若發現位圖上對應的位已經為1,對該位不進行操作但依舊在鏈表里加入一個節點。也就是說,每次對進程發送一個可靠信號時,不管該進程之前是否收到過相同的信號,總是會在list_head鏈表里加入sigqueue節點
對于信號來說,位圖只是用來標記有沒有待處理信號的,而節點才是信號真正注冊的信息
信號的注銷
看上文中信號的生命周期會發現,在處理信號之前,會先銷毀信號的信息
信號注銷存在的目的就是為了抹除信號存在的痕跡,防止對同一個信號進行多次處理
刪除要處理的信號sigqueue節點:
- 若信號是非可靠信號,則直接將位圖置0(非可靠信號在沒有處理之前只會注冊一次)
- 若信號是可靠信號,則刪除后需要判斷是否還有相同節點,沒有的話才會重置位圖為0
信號的捕獲處理
在學習信號的捕獲和處理之前我們還需要了解一下信號的阻塞
信號的阻塞
信號的阻塞就是阻止一個信號的抵達,當一種信號被阻塞時,它仍可以被發送,但是產生的待處理信號不會被接收,直到進程取消對這種信號的阻塞
在pcb中,有一個阻塞信號集合(blocked位圖,實現方式與pending相同),凡是添加到這個集合中的信號,都表示需要阻塞,暫時不處理
那么該如何實現對一個進程的某個信號進行阻塞呢?
我們可以通過sigprocmask函數顯式地阻塞和取消阻塞選擇的信號:
#include參數中的how表示了當前要對blocked集合進行的操作,它的值可從下面三個宏定義中選擇一個填入:
- SIG_BLOCK:添加set中的信號到blocked中(blocked = blocked | set)
- SIG_UNBLOCK:從blocked中刪除set中的信號(blocked = blocked & ~set)
- SIG_SETMASK:blocked = set
在這個函數中,如果oldset非空,blocked位圖以前的值會保存在oldset中
捕獲信號與處理信號
接著我們就可以來研究一下捕獲信號
當內核準備將控制傳遞給一個進程時,它會檢查該進程的未被阻塞的待處理信號的集合,也就是存在于pending集合中同時又不存在于blocked集合中(pending & ~blocked),如果這個集合為空(通常情況下),那么內核將控制傳遞到該進程中的下一條指令里
然而,如果該集合是非空的,那么內核會選擇集合中的某個信號(通常是序號最小的信號),并且強制該進程接收該信號,收到這個信號會觸發進程的某種行為。一旦進程完成了這個行為,那么控制就傳遞回該進程中的下一條指令
這里的“行為”,就是進程對信號的處理
處理的實現是調用一個信號處理函數signal:
#include這里的sighandler_t是一個函數指針類型,signal函數的第一個參數就是信號的序號,我們就可以通過第二個參數來改變處理信號signum的方式:
- 如果handler是SIG_IGN,那么忽略類型為signum的信號
- 如果handler是SIG_DFL,那么類型為signum的信號行為恢復為默認行為
- 在其它情況下,handler是一個用戶定義的函數的地址,也就是指向一個信號處理程序的函數指針,只要進程收到一個類型為signum的信號,就會調用這個函數
需要注意的是,在所有信號中,有兩個信號不可被阻塞,不可被自定義修改處理方式,也不可被忽略,這兩個信號分別是9) SIGKILL 和 19) SIGSTOP
一般情況下對于信號的捕獲和處理都是一起被提到的,上文中對“捕獲”和“處理”的分界可能并不是特別準確,在《深入理解計算機系統》中對捕獲信號和處理信號的定義如下:
調用信號處理程序稱為捕獲信號,執行信號處理程序稱為處理信號現在我們通過一個具體的例子來感受一下信號的阻塞與接觸阻塞的操作和信號的捕獲處理以及可靠信號與非可靠信號的區別:
#include運行程序并分別通過ctrl+c和kill命令多次發送2號和40號信號:
zeno@VM-0-7-ubuntu:~$ ./mask presse enter to continue: ^C^C^C^C^C^C^C^C^C^C zeno@VM-0-7-ubuntu:~$ ps -ef | grep mask | grep -v grep zeno 29043 27880 0 14:09 pts/10 00:00:00 ./mask zeno@VM-0-7-ubuntu:~$ kill -40 29043 zeno@VM-0-7-ubuntu:~$ kill -40 29043 zeno@VM-0-7-ubuntu:~$ kill -40 29043 zeno@VM-0-7-ubuntu:~$ kill -40 29043可以看到對于該進程,不論是非可靠信號(2)還是可靠信號(40),都被阻塞導致無法處理,但是接下來按下回車,所有阻塞都會被解除:
zeno@VM-0-7-ubuntu:~$ ./mask presse enter to continue: ^C^C^C^C^C^C^C^C^C^C receive a signal:40 receive a signal:40 receive a signal:40 receive a signal:40 receive a signal:2在這里我們就可以發現,雖然信號2和信號40都曾發送多次,但是只有信號40也就是可靠信號被處理了多次,而信號2也就是可靠信號只調用了一次信號處理函數,這也就印證了我們上文中所提到的可靠信號與非可靠信號的區別
至此我們就已經大致了解了什么是進程信號和信號的工作過程
總結
以上是生活随笔為你收集整理的_Linux进程信号详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 该计算机没有运行windows无线服务器
- 下一篇: php rewinddir(),PHP