1. 程式人生 > >Linux下的程序訊號處理過程

Linux下的程序訊號處理過程

訊號的產生

訊號的產生方式

  • 鍵盤產生

    • 鍵盤產生的訊號只能傳送給前臺程序。例如:[Ctrl+C]…
  • 程式異常

    • 除0錯誤。除0錯誤會導致硬體錯誤。
    • core dumped(核心轉儲):當程序異常退出時,作業系統會將該程序發生異常退出之前在記憶體中的資料儲存至硬碟上。
      • 但是通常發生程式異常退出時,使用者並不會發現程式產生了一個core dumped檔案,這是因為作業系統當前給使用者可產生core file size大小為0,因為core檔案的大小不是很小的,所以一般系統會將可產生core檔案的大小為0,即通常不產生core檔案。
      • ulimit -a:檢視系統當前分配給使用者設定資源方面的限制條件。
      • core檔案前面是core,後面是引起產生該core檔案的程序pid。
      • 使用core檔案除錯程式碼:在Linux環境下,進入使用gdb除錯程式碼,在gdb內部,使用core-file [core檔名],就會直接定位到引發core檔案產生的對應行。
      • 2、9號訊號不會產生core檔案。
  • 使用kill命令

  • 通過系統呼叫介面給特定程序傳送訊號

    • #include<signal.h>
      int kill
      (pid_t pid, int signo); //向特定程序傳送特定訊號;成功返回0;失敗返回-1
    • #include<signal.h>
      int raise(int signo);
      //向當前程序傳送特定訊號;成功返回0;失敗返回-1
      
    • #include<stdlib.h>
      void abort(void);
      //使當前程序收到訊號而異常終止;就像exit()函式一樣,abort()函式總是會成功的,所以沒有返回值
      
  • 由軟體條件傳送訊號

    • SIGPIPE:SIGPIPE是一種由軟體條件產生的訊號,當一個管道的讀端被關閉時,這時候作業系統就會檢測到該管道中寫入的資料不會在有人來管道內讀檔案了,作業系統會認為該管道的存在會造成記憶體資源的極大浪費,則作業系統就會向寫端對應的目標程序傳送SIGPIPE訊號。

    • #include<unistd.h>
      unsigned int alarm(unsigned int seconds);
      //呼叫alarm函式可以對當前程序設定一個鬧鐘,也就是告訴作業系統在seconds秒之後對當前程序傳送SIGALRM訊號,該訊號的預設處理動作是終止當前程序。
      
      • 返回值:該函式的返回值是0或者是以前設定的鬧鐘時間還剩餘的秒數。如果second的值為0,則表示取消以前設定的鬧鐘。

訊號的在核心中的表示

FSqxRH.png

程序的PCB包含了程序的一切資訊,所以它理所應當的要儲存程序的訊號資訊。作業系統使用了兩張點陣圖&一張函式指標陣列表來控制程序的訊號

  • 實際執行訊號的處理動作稱為訊號遞達。
  • 訊號從產生到遞達之間的狀態,稱為訊號未決。
  • 被阻塞的訊號產生時將儲存在未決狀態,直到程序解除對該訊號的阻塞,才能執行遞達的動作。
  • 阻塞和忽略不同,只要訊號被阻塞就不會遞達;而忽略是在遞達之後,可選的一種處理動作。

pending(未決訊號表)

  • pending表用來表示程序收到的未決訊號。當程序收到一個訊號時,就會將該程序PCB中的pending表中對應的表示該訊號的點陣圖置為1。當訊號被遞達時,對應位置會被置為0。
    • 常規訊號在遞達之前產生多次只記一次,而實時訊號在遞達之前產生多次可以依次放在一個佇列裡

block(阻塞訊號表)

  • block表用來表示程序的哪些訊號被阻塞。當程序的PCB中的block表中的一個訊號對應的點陣圖被置為1時,代表該程序阻塞了對應的訊號,即當該程序收到對應訊號時,對應訊號的pending位一直為1,無法被遞達。

hander(函式指標陣列)

  • 該表對應了相應訊號的處理方式,儲存了處理訊號方式的函式指標。該處儲存的是訊號的預設處理動作還是使用者自定義的處理動作,忽略訊號如何定義處理動作?

使用者自定義程序收到訊號後的處理動作

sigset_t

當需要得知程序的pending表或block表中的資訊時,需要定義一個特有的結構來儲存程序中的訊號資訊。sigset_t是專門用來儲存程序訊號資訊的結構,雖然程序中的儲存訊號的方式是以點陣圖來儲存,但這裡禁止直接使用位操作來操作sigset_t的資料,必須通過特定的函式介面堆sigset_t進行操作。

  • 當使用sigset_t結構來接收程序的block訊號集時,通常稱為程序的訊號遮蔽字。
  • 當使用sigset_t結構來接收程序的pending訊號集時,通常稱為程序的pending訊號集。

訊號集操作函式

#include<signal.h>

int sigemptyset(sigset_t *set);
//初始化set所指向的訊號集,使其中所有訊號對應的位元位清零,表示該訊號集不包含任何訊號

int sigfillset(sigset_t *set);
//初始化set所指向的訊號集,將其中所有訊號對應的位元位置1,表示該訊號集的有效訊號包括系統支援的所有訊號

int sigaddset(sigset_t *set, int signo);
//表示將set所指向的訊號集中的signo訊號置1

int sigdelset(sigset_t *set, int signo);
//表示將set所指向的訊號集中的signo訊號清零

int sigismember(const sigset_t *set, int signo);
//用來判斷set所指向的訊號集的有效訊號中是否包含signo訊號,包含返回1,不包含返回0,出錯返回-1
  • 注意:在使用sigset_t型別的變數前,一定要呼叫sigemptyset或sigfillset進行初始化,使訊號集處於某種確定的狀態,初始化之後就可以呼叫sigaddset或sigdelset在訊號集中新增或刪除某種有效訊號。

設定\修改程序的訊號遮蔽字(block表)

#include<signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
  • int how:
    • SIG_BLOCK:set包含了使用者希望新增到當前訊號遮蔽字的訊號,即就是在老的訊號遮蔽字中新增上新的訊號。相當於:mask=mask|set
    • SIG_UNBLOCK:set包含了使用者希望從當前訊號遮蔽字中解除阻塞的訊號,即就是在老的訊號遮蔽字中取消set表中的訊號。相當於:mask=mask&~set
    • SIG_SETMASK:設定當前程序的訊號遮蔽字為set所指向的訊號集。相當於:mask=set
  • const sigset_t *set:將要設定為程序block表的訊號集。
  • sigset_t *oset:用來儲存程序舊的block表。
    • 若無需儲存程序舊的block表,傳遞空指標即可。

獲取程序的pending訊號集

#include<signal.h>

int sigpending(sigset_t *set);
  • 成功返回0;失敗返回-1

修改程序收到訊號的處理動作

程序收到訊號的處理方式

  • 忽略
    • SIG_IGN:程序收到訊號忽略的處理方式。
  • 預設處理方式
    • SIG_DFL:程序收到訊號的預設處理方式。
  • 自定義處理方式

sigaction

#include<signal.h>

struct sigaction
{
    void (*sa_handler)(int);//指向訊號處理對應的函式
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;//當在處理所收到訊號時,想要附帶遮蔽的其他普通訊號,當不需要遮蔽其他訊號時,需要使用sigemptyset初始化sa_mask
    int sa_flags;
    void (*sa_restorer)(void);
};

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
  • int signo:指定的訊號編號。
  • const struct sigaction *act:若該act指標非空,則根據act指標來修改程序收到signo訊號的處理動作。
  • struct sigaction *oact:若oact指標非空,則使用oact來儲存訊號舊的處理動作。

訊號的捕捉

FSLGYF.png

處理訊號的時機

程序收到一個訊號時,並不會立即就去處理這個訊號,而是先將收到的訊號儲存下來,並在合適的時候對訊號進行處理,作業系統會在程序進入了核心態並從核心態返回使用者態時,檢測程序中可以進行處理的訊號,並進行處理

使用者寫好的程式碼會在什麼情況下進入核心態呢?
  • 呼叫系統呼叫介面
  • 異常
  • 中斷

SIGCHLD訊號

使用fork函式創建出一個子程序,當子程序退出時會向父程序傳送一個SIGCHLD訊號,該訊號的預設處理動作是忽略,父程序可以自定義SIGCHLD訊號的處理方式。