1. 程式人生 > >Linux:訊號詳解

Linux:訊號詳解

        訊號,這在生活中是非常常見的,比如說紅綠燈、手機鈴聲等。所謂訊號就是在人或事情感受到這個元素產生以後會做出相應的處理動作,這就是訊號。而在我們Linux下,什麼是訊號呢?

訊號的基本概念:

        1.定義:訊號更多的是通知事件的發生。訊號產生之後第一時間也不是直接處理而是先儲存下來。

                       訊號實際是一個軟中斷。【軟中斷是一種需要核心為正在執行的程序去做一些事情(通常為I/O)的請求。】

        2.流程:訊號的產生--->訊號的註冊--->訊號的阻塞(不處理)--->訊號的登出--->訊號的處理

        3.分類:①不可靠訊號(非實時訊號):1~31

                       ②可靠訊號(實時訊號):34~64

                        Linux下有62種訊號,使用kill -l 命令檢視

                      

訊號的產生:

        在Linux終端下,我們常常在鍵盤上輸入ctrl+c來終止某個程序(這個按鍵只能用於終止前臺程序,如果執行程序之前加上&那麼這個程序就會到後臺執行,此時ctrl+c就沒效果了)。實際上,在使用者按下Ctrl-C的時候,由作業系統向程序傳送SIGINT訊號,然後該程序對這個訊號進行了響應,進而程序終止。其實我們在談到程序狀態的時候,其中談到kill -9這個操作,這個操作實質也是一種訊號。

 訊號的產生一般有三種:

   1.通過硬體中斷產生:ctrl+c

   2.程式異常產生: SIGFPE 、SIGSEGV   ...

   3..軟體條件產生: ① kill :     int kill(pid_t pid ,int sig):向指定的程序傳送指定的訊號。成功返回0,失敗返回-1

                                    ② raise:   int raise(int sig):向自身傳送訊號。成功返回0,失敗返回-1

                                    ③ abort:  void abort(void):向自身傳送SIGABRT訊號。這個訊號如同exit函式一樣,永遠成功沒有返回                                                                                               值。它可以使當前程序接收到異常訊號而終止

                                    ④ alarm: unsigned int alarm(unsigned int seconds):

                                                        設定一個定時器,在n秒之後向程序傳送SIGALRM訊號,取消上一個定時器,並且返回上一個                                                             定時器剩餘時間。也就是說呼叫alarm相當於給程序設定了一個鬧鐘,鬧鐘時間到達的時候,便                                                           會發送訊號,其訊號預設動作是終止當前程序。

  • 核心轉儲(core dump):儲存當前程式執行的資料以及呼叫棧資訊,用於錯誤原因定位除錯。

                           功能:如果程式執行出現錯誤,可以直接通過core檔案來進行gdb除錯(因為有些錯誤可能是偶然發生的)

                           開啟方式:ulimit -c 1024

                           core dump預設關閉:原因:隱私安全和資源佔用

      舉個例子:

                           

        這個例子中我們看到出現了段錯誤,在我們程序遇到問題,會收到作業系統傳送的訊號,需要終止程序時,可以選擇把程序的使用者空間的資料全部儲存到磁碟上,檔名通常是core開頭的,後面加上程序的pid,這就叫做Core Dump。 
程序異常終止,常常是因為有Bug的存在,如野指標非法訪問記憶體導致的段錯誤,事後我們可以用偵錯程式來檢查core檔案,找到程序錯誤的原因。 
        一個程序允許產生多大的core檔案取決於程序Recource Limit(這個資訊儲存在PCB當中),在Linux作業系統下是預設不允許產生core檔案的,因為core檔案中可能包含著使用者的密碼等敏感資訊,是不安全的。不過為了我們的實踐,我們可以利用指令ulimit來改變這個限制,允許讓其產生core檔案:

         

        此時在修改了ulimit之後,執行剛才的段錯誤檔案,這時候產生了一個core檔案。我們可以看看這個core檔案多大:

        

        可以看到這個core檔案是相當的大,如果系統將它加載出來是非常費時費力的,因此係統預設將其關閉。 
        如果一旦出現段錯誤,然後產生core檔案後,我們就可以用gdb test core.2885來進行除錯,最終找到錯誤。

訊號的註冊:

      給一個程序傳送訊號,就是修改這個程序pcb中關於訊號的pending點陣圖,將相應的訊號位 置為1.

     

訊號的阻塞:

先要了解兩個概念:

  • 訊號的遞達:訊號的處理。
  • 訊號未決:這是一種狀態,訊號從註冊成功到訊號遞達之間的一種狀態

        訊號的阻塞是指暫時不處理訊號(阻止訊號的遞達),並不是不接收訊號。

        產生訊號後,作業系統會在程序的PCB中修改一個名叫訊號點陣圖上的內容,訊號點陣圖可以理解成一個擁有31個位元位的點陣圖,每一個訊號都對應一個位元位,所以是31個位元位,而作業系統發訊號給程序,實質上是經過某些呼叫改變訊號點陣圖上對應位元位由0改為1,這樣訊號就傳送了。而這個時候訊號處於未決狀態(從產生到修改點陣圖上的對應位元位),當程序收到訊號時,會在合適的時間程序處理,實際上程序去執行訊號的動作稱為訊號的遞達。而程序可以選擇阻塞某一個訊號,當訊號被程序阻塞時,作業系統向程序傳送訊號,此時訊號只會未決而永遠不會遞達,直到解除阻塞。 
        注:阻塞的時候訊號還沒有遞達,而忽略是訊號遞達後的一個處理方式。
        要阻塞一個訊號,就是修改pcb中關於訊號的block點陣圖,將相應的訊號位置1,這個點陣圖就像一個備註,說明這個訊號暫時不去處理。

      從上圖可以看到,從上至下分別是一號訊號到31號訊號,此時二號訊號已經未決,所以它的二號位元位由0變為1,意味著作業系統已經向該程序傳送了訊號,而程序對二號訊號進行進行了阻塞,那麼這個時候二號訊號是永遠不能遞達的,因為受到了程序的阻塞,除非解除阻塞,才能夠遞達,如果一直無法遞達,那麼未決表上二號位元位永遠都是1。 另外,常規訊號在遞達之前如果產生多次同一種訊號,那麼只記一次;如果是實時訊號,那麼將這些多次產生的實時訊號進行儲存,儲存到一個鏈式佇列內。

  • sigset_t  訊號集

        從我們上面的圖來看,無論是未決表還是阻塞表,它們只有一個位元位來判斷是否未決或阻塞,即0或1,並不記錄該訊號產生了多少次,阻塞標誌也是這樣的。這個時候引入一個新的資料型別叫做sigget_t,這個資料型別稱作訊號集,這個型別可以表示每個訊號的有效或者是無效的狀態,

       sigset_t訊號集中,每個訊號只用一個位元位來表示,0與1代表它的訊號是否未決或阻塞,我們對它們的操作只能運用訊號集操作函式來操作。而無法直接對其內部的元素進行訪問操作。

       pending結構體中這個sigset_t點陣圖是未決訊號集,裡面放的是註冊了但是還沒處理的訊號。

       block結構體中這個sigset_t點陣圖是阻塞訊號集,裡面放的是被阻塞了的訊號。

訊號集操作函式:

   #include <signal.h>

  •        int sigemptyset(sigset_t *set)    //清空一個訊號集合
  •        int sigfillset(sigset_t *set)          //將所有的訊號都新增到set集合中
  •        int sigaddset(sigset_t *set, int signum)     //新增指定的單個訊號到set集合中
  •        int sigdelset(sigset_t *set, int signum)      //從集合中移除一個指定的訊號
  •        int sigismembert(const sigset_t *set, int signum)      //判斷一個訊號是否在一個集合中

sigprocmask函式:阻塞訊號/解除阻塞

  標頭檔案:#include <signal.h>

函式原型:int sigprocmask(int how, sigset_t *set,sigset_t *oldset)

              作用:讀取或者更改程序訊號集內的遮蔽字(阻塞訊號集) 

              引數:  how:選擇對訊號集的操作動作

                                       SIG_BLOCK           阻塞集合中的訊號

                                       SIG_UNBLOCK      對集合中的訊號解除阻塞

                                       SIG_SETMASK       將集合中的訊號設定到阻塞集合中

                             set:要阻塞/解除阻塞的集合

                        oldset:儲存原先阻塞集合中的訊號

          返回值:如果成功則為0,如果出錯返回-1 

有兩個訊號是不會被阻塞的:SIGKILL 、 SIGSTOP

訊號的登出:

       就是從pending集合中將即將要處理的訊號相應位置為0 (從pcb的pending集合中移除)  

       註冊:  

                 非可靠訊號:就是講相應pending點陣圖置1,然後新增一個sigqueue結構到連結串列中,之後如果有相同訊號到來,就不做任何操作。意味著後來的訊號在前一個訊號未處理之前不會重複註冊,代表丟了。

                 可靠訊號:就是不管有沒有註冊都要置1,並且新增節點到連結串列中,所以不會丟訊號。

      登出:

                 非可靠訊號:刪除連結串列節點,相應點陣圖置0

                 可靠訊號:刪除節點,判斷是否還有相同訊號節點,如果沒有點陣圖置0,如果有就不置0

訊號的處理:

    處理時有三種操作:

  •              預設操作:按照作業系統中對訊號事件的既定處理方式。
  •              忽略操作:直接將訊號丟掉。
  •              自定義處理:使用者自己定義事件的處理方式。

 

     handler表就是程序在遞達時所能夠執行的動作,包含:預設操作、忽略操作、自定義處理。

 訊號的捕捉流程:

主要是針對訊號的自定義處理方式,處理自定義動作的這個過程就叫做訊號的捕捉。

          實現函式有:signal、sigaction

          訊號並不是立即處理的,而是選擇一個合適的時機去處理,合適的時機就是當前程式從核心態切換到使用者態的時候。

          訊號的捕捉流程是當我們發起系統呼叫/程式異常/中斷當前程式從使用者態執行切換到核心態,去處理這些事情;處理完畢後,要從核心返回使用者態,但是在返回之前會看一下是否有訊號需要被處理,如果有就處理訊號(切換到使用者態執行訊號的自定義處理方式),處理完畢之後再次返回核心態,判斷如果沒有訊號要處理了就呼叫sys_sigreturn返回使用者態(我們程式之前執行的位置)。

  •     面試題:程式如何從使用者態切換到核心態?

                     答:1.發起系統呼叫(read,write...)   2.程式異常   3.程式中斷

 訊號捕捉函式:

  • signal
sighandler_t signal(int signum, sighandler_t handler);
//這個函式第一個引數為要捕捉的訊號,第二個引數為要執行的預設動作,這是一個函式指標

 程式碼實現:

                      

        這裡我們對二號訊號進行了捕捉,二號訊號為按鍵輸入Ctrl+C便可產生。發現這時候並不執行二號訊號應該有的動作終止程序,而改為了執行自定義動作,這就完成了一次訊號捕捉。但是,並不是所有訊號都能夠捕捉,其中kill -9九號訊號是無法捕捉的。

  • sigaction
#include <signal.h>

int sigaction(int signo, const struct sigaction* act, struct sigaction* ocat);

// signum:訊號編號
// act:如果act非空,那麼根據act的內容作為該訊號新的處理動作,即自定義動作
// ocat:如果oact非空,則通過oact來傳出該訊號原來的處理動作。act與oact都指向sigaction結構體


struct sigaction
{
  void (*sa_handler)(int); //處理函式

  void (*sa_sigaction)(int,siginfo_t *,void *);  //處理函式

  sigset_t sa_mask; //在處理訊號的時候可以通過這個mask暫時阻塞一些訊號,處理完畢後會還原
  
  int sa_flags; //決定了我們使用哪個回撥介面,並且還有一些其它的選項資訊

  void (*sa_restorer)(void);
}

當某個訊號處理函式被呼叫的時候,核心將自動將當前訊號加入程序的訊號遮蔽字當中,當訊號處理函式呼叫結束返回時,自動恢復到原來的訊號遮蔽字。這是為了防止在處理某個訊號的時候,如果這種訊號再次產生,那麼它就會被阻塞到當前呼叫結束。

  • 殭屍程序的避免

        殭屍程序是子程序先於父程序退出後,作業系統會通知父程序說子程序已掛,讓父程序收屍,但是父程序不予理睬,此時子程序就會成為殭屍程序。

        作業系統如何通知父程序說子程序退出呢?會通過訊號:SIGCHLD -17號訊號

        在不瞭解訊號之前,我們避免產生殭屍程序的處理方法就是讓父程序一直等待子程序的退出,但是這樣會浪費父程序的資源。在學習了訊號之後,我們可以通過自定義訊號:SIGCHLD的處理方式,相當於提前告訴程序,當接收到這個訊號的時候使用waitpid,這樣就不用讓父程序一直等了,節約資源。

相關推薦

Linux訊號

        訊號,這在生活中是非常常見的,比如說紅綠燈、手機鈴聲等。所謂訊號就是在人或事情感受到這個元素產生以後會做出相應的處理動作,這就是訊號。而在我們Linux下,什麼是訊號呢? 訊號的基本概念:         1.定義:訊號更多的是通知事件的發生。訊號產生

Linux select() 和 實現原理【轉】

https://www.cnblogs.com/sky-heaven/p/7205491.html#4119169   轉自:http://blog.csdn.net/huntinux/article/details/39289317 原文:http://blog.csdn.n

Linux訊號

訊號:當我們按下Ctrl+'C' /"D"/"\"/"Z"等組合鍵時,程序為什麼會停止下來,實際上是我們給程序發出了訊號,例如,我們在除錯的過程中,程式異常終止時,常常會受到SIGSEGV訊號,那麼核心是如何來管理這些訊號的呢? 當有訊號產生時,程序PCB會維護兩個訊號集

LinuxLinux程序訊號

一、引入訊號概念訊號其實我們也見過,當我們在shell上寫出一個死迴圈退不出來的時候,只需要一個組合鍵,ctrl+c,就可以解決了,這就是一個訊號,但是真正的過程並不是那麼簡單的。1、當用戶按下這一對組合鍵時,這個鍵盤輸入會產生一個硬體中斷,如果CPU正在執行這個程序的程式碼

Linux進階DNS

del 藍汛 網名 反垃圾郵件 區域傳送 author load man google DNS服務和BIND 本章內容 名字解析 DNS服務 實現主從服務器 實現子域 實現view 編譯安裝 壓力測試 DNS排錯 DNS服務 DNS:Domain Name Servi

Linux下的訊號及捕捉訊號

訊號的基本概念 每個訊號都有一個編號和一個巨集定義名稱 ,這些巨集定義可以在 signal.h 中找到。 使用kill -l命令檢視系統中定義的訊號列表: 1-31是普通訊號 regular signal(非可靠訊號); 34-64是實時訊號 real time sign

linux每日命令(26)Linux檔案屬性

Linux 檔案或目錄的屬性主要包括:檔案或目錄的節點、種類、許可權模式、連結數量、所歸屬的使用者和使用者組、最近訪問或修改的時間等內容。具體情況如下: 命令: ls -lih 輸出: [[email protected] test]# ls -lih total 0 51621141 dr

Linux命令grep

[[email protected] ~]# grep [-acinv] [--color=auto] '搜尋字串' filename 選項與引數: -a :將 binary 檔案以 text 檔案的方式搜尋資料 -c :計算找到 '搜尋字串' 的次數 -i :忽略大小寫的不同,所以大小寫

Linux命令top

top命令是Linux下常用的效能分析工具,能夠實時顯示系統中各個程序的資源佔用狀況,常用於服務端效能分析。 統計資訊:前五行是系統整體的統計資訊; 程序資訊:統計資訊下方類似表格區域顯示的是各個程序的詳細資訊,預設5秒重新整理一次。 統計資訊說明:   第

Linux基本指南(二)Linux基本命令

目錄 一、常用指令 ls 顯示檔案或目錄 -l 列出檔案詳細資訊l(list) -a 列出當前目錄下所有檔案及目錄,包括隱藏的a(all) mkdir 建立目錄 -p 建立目錄,若無父目錄,則建立p(parent)

Linux網路程式設計》 libpcap

1.概述 libpcap (Packet Capture Library)是一個網路資料包捕獲函式庫,是Unix/Linux平臺下的網路資料包捕獲函式庫。它是一個獨立於系統的使用者層包捕獲的API介面,為底層網路監測提供了一個可移植的框架。功能非常強大,Linux 下著名的 tcpd

Linux網路程式設計》 libnet

1 . 概述 通過《Linux網路程式設計》: 原始套接字傳送UDP報文 的學習,我們組 UDP 資料包時常考慮位元組流順序、校驗和計算等問題,有時候會比較繁瑣,那麼,有沒有一種更簡單的方法呢?答案是:藉助 libnet 函式庫。 libnet 是

Linux Rootkit系列三例項 Rootkit 必備的基本功能

前言 鑑於筆者知識能力上的不足,如有疏忽,歡迎糾正。 測試建議: 不要在物理機測試!不要在物理機測試! 不要在物理機測試! 概要 在 上一篇文章中筆者詳細地闡述了基於直接修改系統呼叫表 (即 sys_call_table /ia32_sys_call_

Linux核心程序之三flush-x:y

上一篇文章《裝置檔案與裝置號》當然不是突然穿插而來的自言自語,而是理解本文的前提,下面來看。是一類程序,這在系列的上一篇文章裡已經講到過,系統的絕大部分的bdi裝置都會有對應的flush-x:y核心程序,而這個x:y是對應bdi裝置的裝置號。 先看一下系統當前掛載的檔案系統

每天一個 Linux 命令(25)Linux 檔案屬性

Linux 檔案或目錄的屬性主要包括:檔案或目錄的節點、種類、許可權模式、連結數量、所歸屬的使用者和使用者組、最近訪問或修改的時間等內容。具體情況如下: 命令:  ls -lih 輸出: [root@localhost test]# ls -li

Linux 訊號三(sleep,raise)

//sleep 函式 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys

Linux核心程序之一sync_supers

先說下環境,CentOS 6.0/Linux kernel 2.6.38.8/X86-64,後面提到的程式碼也都來之kernel 2.6.38.8。這個環節下的程序列表具體如下所示,後續將有一系列的文章分析各個程序(主要是核心程序)的功能: [[email pro

Linux啟動流程

linux 詳解 啟動流程 grub mbr 內核 linux啟動流程第一部分 Linux啟動基礎知識1.1 linux centos6.8啟動流程圖 BIOS加電自檢à加載MBRà加載啟動grubà加載內核à啟動/sbin/i

Linux netstat命令,高級面試必備

bytes tool head osi ngs 進行 pen 通信 詳細信息 簡介 Netstat 命令用於顯示各種網絡相關信息,如網絡連接,路由表,接口狀態 (Interface Statistics),masquerade 連接,多播成員 (Multicast Mem

linux top 命令

ctrl+ 一次 所有 使用方法 ase 隱藏 統計 ini 前臺 top命令是Linux下常用的性能分析工具,能夠實時顯示系統中各個進程的資源占用狀況,類似於Windows的任務管理器。下面詳細介紹它的使用方法。top - 01:06:48 up 1:22, 1 user