Linux 進程間通訊(IPC)總結

概述

一個大型的應用系統,往往需要眾多進程協作,進程(Linux進程概念見附1)間通訊的重要性顯而易見。本系列文章闡述了 Linux 環境下的幾種主要進程間通訊手段。

進程隔離

進程隔離是為保護作業系統中進程互不干擾而設計的一組不同硬體和軟體的技術。這個技術是為了避免進程A寫入進程B的情況發生。 進程的隔離實現,使用了虛擬地址空間。進程A的虛擬地址和進程B的虛擬地址不同,這樣就防止進程A將數據資訊寫入進程B。

虛擬地址空間

當創建一個進程時,作業系統會為該進程分配一個 4GB 大小的虛擬進程地址空間。之所以是 4GB ,是因為在 32 位的作業系統中,一個指針長度是 4 位元組,而 4 位元組指針的定址能力是從 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即為 4GB 大小的容量。與虛擬地址空間相對的,還有一個物理地址空間,這個地址空間對應的是真實的物理記憶體。要注意的是這個 4GB 的地址空間是「虛擬」的,並不是真實存在的,而且每個進程只能訪問自己虛擬地址空間中的數據,無法訪問別的進程中的數據,通過這種方法實現了進程間的地址隔離。

針對 Linux 作業系統,將最高的1G位元組(從虛擬地址 0xC0000000 到 0xFFFFFFFF )供內核使用,稱為內核空間,而較低的 3G 位元組(從虛擬地址 0x00000000 到0xBFFFFFFF),供各個進程使用,稱為用戶空間。每個進程都可以通過系統調用進入到內核。其中在 Linux 系統中,進程的用戶空間是獨立的,而內核空間是共有的,進程切換時,用戶空間切換,內核空間不變

創建虛擬地址空間目的是為了解決進程地址空間隔離的問題。但程式要想執行,必須運行在真實的記憶體上,所以,必須在虛擬地址與物理地址間建立一種映射關係。這樣,通過映射機制,當程式訪問虛擬地址空間上的某個地址值時,就相當於訪問了物理地址空間中的另一個值。人們想到了一種分段、分頁的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。這種思想理解起來並不難,作業系統保證不同進程的地址空間被映射到物理地址空間中不同的區域上,這樣每個進程最終訪問到的物理地址空間都是彼此分開的。通過這種方式,就實現了進程間的地址隔離。

 

系統調用/內核態/用戶態

雖然從邏輯上抽離出用戶空間和內核空間;但是不可避免的的是,總有那麼一些用戶空間需要訪問內核的資源;比如應用程式訪問文件,網路是很常見的事情,怎麼辦呢?

用戶空間訪問內核空間的唯一方式就是系統調用;通過這個統一入口介面,所有的資源訪問都是在內核的控制下執行,以免導致對用戶程式對系統資源的越權訪問,從而保障了系統的安全和穩定。用戶軟體良莠不齊,要是它們亂搞把系統玩壞了怎麼辦?因此對於某些特權操作必須交給安全可靠的內核來執行。

當一個任務(進程)執行系統調用而陷入內核程式碼中執行時,我們就稱進程處於內核運行態(或簡稱為內核態)此時處理器處於特權級最高的(0級)內核程式碼中執行。當進程在執行用戶自己的程式碼時,則稱其處於用戶運行態(用戶態)。即此時處理器在特權級最低的(3級)用戶程式碼中運行。處理器在特權等級高的時候才能執行那些特權CPU指令。

IPC 通訊原理

理解了上面的幾個概念,我們再來看看進程之間是如何實現通訊的。

通常的做法是消息發送方將要發送的數據存放在記憶體快取區中,通過系統調用進入內核態。然後內核程式在內核空間分配記憶體,開闢一塊內核快取區,調用 copy_from_user() 函數將數據從用戶空間的記憶體快取區拷貝到內核空間的內核快取區中。同樣的,接收方進程在接收數據時在自己的用戶空間開闢一塊記憶體快取區,然後內核程式調用 copy_to_user() 函數將數據從內核快取區拷貝到接收進程的記憶體快取區。這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,我們稱完成了一次進程間通訊。如下圖:

進程間通訊方式

Linux 進程間基本的通訊方式主要有:管道(pipe) (包括匿名管道和命名管道)、訊號(signal)、消息隊列(queue)、共享記憶體、訊號量和套接字。

管道

管道的實質是一個內核緩衝區(調用 pipe 函數來開闢),管道的作用正如其名,需要通訊的兩個進程在管道的兩端,進程利用管道傳遞資訊。管道對於管道兩端的進程而言,就是一個文件,但是這個文件比較特殊,它不屬於文件系統並且只存在於記憶體中。 Linux一切皆文件,作業系統為管道提供操作的方法:文件操作,用 fork 來共享管道原理。

管道依據是否有名字分為匿名管道和命名管道(有名管道),這兩種管道有一定的區別。

匿名管道有幾個重要的限制:

  1. 管道是半雙工的,數據只能在一個方向上流動,A進程傳給B進程,不能反向傳遞
  2. 管道只能用於父子進程或兄弟進程之間的通訊,即具有親緣關係的進程。

命名管道允許沒有親緣關係的進程進行通訊。命名管道不同於匿名管道之處在於它提供了一個路徑名與之關聯,這樣一個進程即使與創建有名管道的進程不存在親緣關係,只要可以訪問該路徑,就能通過有名管道互相通訊。

pipe 函數接受一個參數,是包含兩個整數的數組,如果調用成功,會通過 pipefd[2] 傳出給用戶程式兩個文件描述符,需要注意 pipefd[0] 指向管道的讀端, pipefd[1] 指向管道的寫端,那麼此時這個管道對於用戶程式就是一個文件,可以通過 read(pipefd [0]);或者 write(pipefd [1]) 進行操作。pipe 函數調用成功返回 0,否則返回 -1.

那麼再來看看通過管道進行通訊的步驟:

  • 父進程創建管道,得到兩個文件描述符指向管道的兩端

  • 利用fork函數創建出子進程,則子進程也得到兩個文件描述符指向同一管道

  • 父進程關閉讀端(pipe[0]),子進程關閉寫端pipe[1],則此時父進程可以往管道中進行寫操作,子進程可以從管道中讀,從而實現了通過管道的進程間通訊。

     

管道的特點:

  • 只能單向通訊

兩個文件描述符,用一個,另一個不用,不用的文件描述符就要 close

  • 只能血緣關係的進程進行通訊

  • 依賴於文件系統

  • 生命周期隨進程

  • 面向位元組流的服務

面向位元組流:數據無規則,沒有明顯邊界,收發數據比較靈活:對於用戶態,可以一次性發送也可以分次發送,當然接受數據也如此;而面向數據報:數據有明顯邊界,數據只能整條接受 

  • 管道內部提供了同步機制

臨界資源: 大家都能訪問到的共享資源

臨界區: 對臨界資源進行操作的程式碼

同步: 臨界資源訪問的可控時序性(一個操作完另一個才可以操作)

互斥: 對臨界資源同一時間的唯一訪問性(保護臨界資源安全)

說明:因為管道通訊是單向的,在上面的例子中我們是通過子進程寫父進程來讀,如果想要同時父進程寫而子進程來讀,就需要再打開另外的管道;

管道的讀寫端通過打開的文件描述符來傳遞,因此要通訊的兩個進程必須從它們的公共祖先那裡繼承管道的件描述符。 上面的例子是父進程把文件描述符傳給子進程之後父子進程之 間通訊,也可以父進程fork兩次,把文件描述符傳給兩個子進程,然後兩個子進程之間通訊, 總之 需要通過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通訊。

四個特殊情況:

  1. 如果所有指向管道寫端的文件描述符都關閉了,而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像讀到文件末尾一樣

  2. 如果有指向管道寫端的文件描述符沒關閉,而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。

  3. 如果所有指向管道讀端的文件描述符都關閉了,這時有進程指向管道的寫端write,那麼該進程會收到訊號SIGPIPE,通常會導致進程異常終止。

  4. 如果有指向管道讀端的文件描述符沒關閉,而持有管道寫端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再write會阻塞,直到管道中有空位置了才寫入數據並返回。

命名管道FIFO

在管道中,只有具有血緣關係的進程才能進行通訊,對於後來的命名管道,就解決了這個問題。FIFO 不同於管道之處在於它提供一個路徑名與之關聯,以 FIFO 的文件形式存儲於文件系統中。命名管道是一個設備文件,因此,即使進程與創建FIFO的進程不存在親緣關係,只要可以訪問該路徑,就能夠通過 FIFO 相互通訊。值得注意的是, FIFO (first input first output) 總是按照先進先出的原則工作,第一個被寫入的數據將首先從管道中讀出。

命名管道的創建

創建命名管道的系統函數有兩個: mknod 和 mkfifo。兩個函數均定義在頭文件 sys/stat.h,
函數原型如下:

#include <sys/types.h>
#include <sys/stat.h>
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);

函數 mknod 參數中 path 為創建的命名管道的全路徑名: mod 為創建的命名管道的模指明其存取許可權; dev 為設備值,該值取決於文件創建的種類,它只在創建設備文件時才會用到。這兩個函數調用成功都返回 0,失敗都返回 -1。

命名管道打開特性:

  1. 如果用只讀打開命名管道,open 函數將阻塞等待直至有其他進程以寫的方式打開這個命名管道,如果沒有進程以寫的方式發開這個命名管道,程式將停在此處

  2. 如果用只寫打開命名管道,open 函數將阻塞等到直至有其他進程以讀的方式打開這個命名管道,如果沒有進程以讀的方式發開這個命名管道,程式將停在此處;

  3. 如果用讀寫打開命名管道,則不會阻塞(但是管道是單向)

System V IPC   

IPC(Inter-Process Communication)是指多個進程之間相互通訊,交換資訊的方法,System V 是 Unix 作業系統最早的商業發行版,由 AT&T(American Telephone & Telegraph)開發。System V IPC 是指 Linux 引入自 System V 的進程通訊機制,一共有三種:

  • 訊號量,用來管理對共享資源的訪問;

  • 共享記憶體,用來高效地實現進程間的數據共享;

  • 消息隊列,用來實現進程間數據的傳遞。

這三種統稱 IPC 資源,每個 IPC 資源都是請求時動態創建的,都是永駐記憶體,除非被進程顯示釋放,都是可以被任一進程使用。每個 IPC 資源都使用一個 32 位的 IPC 關鍵字和 32 位的 IPC 標識符,前者類似文件系統中的路徑名,由程式自由訂製,後者類似打開文件的文件描述符,由內核統一分配,在系統內部是唯一的,當多個進程使用同一個IPC資源通訊時需要該資源的 IPC 標識符。     

創建新的 IPC 資源時需要指定 IPC 關鍵字,如果沒有與之關聯的 IPC 資源,則創建一個新的 IPC 資源;如果已經存在,則判斷當前進程是否具有訪問許可權,是否超過資源使用限制等,如果符合條件則返回該資源的 IPC 標識符。為了避免兩個不同的 IPC 資源使用相同的 IPC 關鍵字,創建時可以指定IPC關鍵字為 IPC_PRIVATE,由內核負責生成一個唯一的關鍵字。   

創建新的 IPC 資源時最後一個參數可以包括三個標誌,PC_CREAT 說明如果IPC資源不存在則必須創建它,IPC_EXCL 說明如果資源已經存在且設置了 PC_CREAT 標誌則創建失敗,IPC_NOWAIT 說明訪問 IPC 資源時進程從不阻塞。 

訊號量

訊號量(semaphore)是一種用於提供不同進程之間或者一個給定的不同執行緒間同步手段的原語。訊號量多用於進程間的同步與互斥,簡單的說一下同步和互斥的意思:

同步:處理競爭就是同步,安排進程執行的先後順序就是同步,每個進程都有一定的先後執行順序。

互斥:互斥訪問不可共享的臨界資源,同時會引發兩個新的控制問題(互斥可以說是特殊的同步)。

競爭:當並發進程競爭使用同一個資源的時候,我們就稱為競爭進程。

共享資源通常分為兩類:一類是互斥共享資源,即任一時刻只允許一個進程訪問該資源;另一類是同步共享資源,即同一時刻允許多個進程訪問該資源;訊號量是解決互斥共享資源的同步問題而引入的機制。

下面說一下訊號量的工作機制,可以直接理解成計數器(當然其實加鎖的時候肯定不能這麼簡單,不只只是訊號量了),訊號量會有初值(>0),每當有進程申請使用訊號量,通過一個 P 操作來對訊號量進行-1操作,當計數器減到 0 的時候就說明沒有資源了,其他進程要想訪問就必須等待(具體怎麼等還有說法,比如忙等待或者睡眠),當該進程執行完這段工作(我們稱之為臨界區)之後,就會執行 V 操作來對訊號量進行 +1 操作。

  • 臨界區:臨界區指的是一個訪問共用資源(例如:共用設備或是共用存儲器)的程式片段,而這些共用資源又無法同時被多個執行緒訪問的特性。

  • 臨界資源:只能被一個進程同時使用(不可以多個進程共享),要用到互斥。

我們可以說訊號量也是進程間通訊的一種方式,比如互斥鎖的簡單實現就是訊號量,一個進程使用互斥鎖,並通知(通訊)其他想要該互斥鎖的進程,阻止他們的訪問和使用。

當有進程要求使用共享資源時,需要執行以下操作:

  1. 系統首先要檢測該資源的訊號量;

  2. 若該資源的訊號量值大於 0,則進程可以使用該資源,此時,進程將該資源的訊號量值減1;

  3. 若該資源的訊號量值為 0,則進程進入休眠狀態,直到訊號量值大於 0 時進程被喚醒,訪問該資源;

       當進程不再使用由一個訊號量控制的共享資源時,該訊號量值增加 1,如果此時有進程處於休眠狀態等待此訊號量,則該進程會被喚醒

每個訊號量集都有一個與其相對應的結構,該結構定義如下:

/* Data structure describing a set of semaphores.  */  
struct semid_ds  
{  
    struct ipc_perm sem_perm;   /* operation permission struct */  
    struct sem *sem_base;       /* ptr to array of semaphores in set */  
    unsigned short sem_nsems;   /* # of semaphores in set */  
    time_t sem_otime;           /* last-semop() time */  
    time_t sem_ctime;           /* last-change time */  
};  
  
/* Data structure describing each of semaphores.  */  
struct sem  
{  
    unsigned short semval;  /* semaphore value, always >= 0 */  
    pid_t          sempid;  /* pid for last successful semop(), SETVAL, SETALL */  
    unsigned short semncnt; /* # processes awaiting semval > curval */  
    unsigned short semzcnt; /* # processes awaiting semval == 0 */  
};  

訊號量集的結構圖如下所示:

消息隊列

消息隊列,是消息的鏈接表,存放在內核中。一個消息隊列由一個標識符(即隊列 ID)來標識。其具有以下特點:

  1. 消息隊列是面向記錄的,其中的消息具有特定的格式以及特定的優先順序。

  2. 消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容並不會被刪除。

  3. 消息隊列可以實現消息的隨機查詢,消息不一定要以先進先出的次序讀取,也可以按消息的類型讀取。

原型

1 #include <sys/msg.h>
2 // 創建或打開消息隊列:成功返回隊列ID,失敗返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失敗返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 讀取消息:成功返回消息數據的長度,失敗返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息隊列:成功返回0,失敗返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

在以下兩種情況下,msgget 將創建一個新的消息隊列:

  • 如果沒有與鍵值key相對應的消息隊列,並且flag中包含了IPC_CREAT標誌位。

  • key參數為IPC_PRIVATE

函數msgrcv在讀取消息隊列時,type參數有下面幾種情況:

  • type == 0,返回隊列中的第一個消息;

  • type > 0,返回隊列中消息類型為 type 的第一個消息;

  • type < 0,返回隊列中消息類型值小於或等於 type 絕對值的消息,如果有多個,則取類型值最小的消息。

可以看出,type 值非 0 時用於以非先進先出次序讀消息。也可以把 type 看做優先順序的權值。

共享記憶體

共享記憶體是 System V 版本的最後一個進程間通訊方式。共享記憶體,顧名思義就是允許兩個不相關的進程訪問同一個邏輯記憶體,共享記憶體是兩個正在運行的進程之間共享和傳遞數據的一種非常有效的方式。不同進程之間共享的記憶體通常為同一段物理記憶體。進程可以將同一段物理記憶體連接到他們自己的地址空間中,所有的進程都可以訪問共享記憶體中的地址。如果某個進程向共享記憶體寫入數據,所做的改動將立即影響到可以訪問同一段共享記憶體的任何其他進程。

特別提醒:共享記憶體並未提供同步機制,也就是說,在第一個進程結束對共享記憶體的寫操作之前,並無自動機制可以阻止第二個進程開始對它進行讀取,所以我們通常需要用其他的機制來同步對共享記憶體的訪問,例如訊號量。

共享記憶體的通訊原理
在 Linux 中,每個進程都有屬於自己的進程式控制制塊(PCB)和地址空間(Addr Space),並且都有一個與之對應的頁表,負責將進程的虛擬地址與物理地址進行映射,通過記憶體管理單元(MMU)進行管理。兩個不同的虛擬地址通過頁表映射到物理空間的同一區域,它們所指向的這塊區域即共享記憶體。

共享記憶體的通訊原理示意圖:

 

對於上圖我的理解是:當兩個進程通過頁表將虛擬地址映射到物理地址時,在物理地址中有一塊共同的記憶體區,即共享記憶體,這塊記憶體可以被兩個進程同時看到。這樣當一個進程進行寫操作,另一個進程讀操作就可以實現進程間通訊。但是,我們要確保一個進程在寫的時候不能被讀,因此我們使用訊號量來實現同步與互斥。

對於一個共享記憶體,實現採用的是引用計數的原理,當進程脫離共享存儲區後,計數器減一,掛架成功時,計數器加一,只有當計數器變為零時,才能被刪除。當進程終止時,它所附加的共享存儲區都會自動脫離。

為什麼共享記憶體速度最快?

藉助上圖說明:Proc A 進程給記憶體中寫數據, Proc B 進程從記憶體中讀取數據,在此期間一共發生了兩次複製

(1)Proc A 到共享記憶體       (2)共享記憶體到 Proc B

因為直接在記憶體上操作,所以共享記憶體的速度也就提高了。

共享記憶體的介面函數以及指令

查看系統中的共享存儲段

ipcs -m

刪除系統中的共享存儲段

ipcrm -m [shmid]

shmget ( ):創建共享記憶體

int shmget(key_t key, size_t size, int shmflg);

[參數key]:由ftok生成的key標識,標識系統的唯一IPC資源。

[參數size]:需要申請共享記憶體的大小。在作業系統中,申請記憶體的最小單位為頁,一頁是4k位元組,為了避免記憶體碎片,我們一般申請的記憶體大小為頁的整數倍。

[參數shmflg]:如果要創建新的共享記憶體,需要使用IPC_CREAT,IPC_EXCL,如果是已經存在的,可以使用IPC_CREAT或直接傳0。

[返回值]:成功時返回一個新建或已經存在的的共享記憶體標識符,取決於shmflg的參數。失敗返回-1並設置錯誤碼。


shmat ( ):掛接共享記憶體

void *shmat(int shmid, const void *shmaddr, int shmflg);

[參數shmid]:共享存儲段的標識符。

[參數*shmaddr]:shmaddr = 0,則存儲段連接到由內核選擇的第一個可以地址上(推薦使用)。

[參數shmflg]:若指定了SHM_RDONLY位,則以只讀方式連接此段,否則以讀寫方式連接此段。

[返回值]:成功返回共享存儲段的指針(虛擬地址),並且內核將使其與該共享存儲段相關的shmid_ds結構中的shm_nattch計數器加1(類似於引用計數);出錯返回-1。


shmdt ( ):去關聯共享記憶體:當一個進程不需要共享記憶體的時候,就需要去關聯。該函數並不刪除所指定的共享記憶體區,而是將之前用shmat函數連接好的共享記憶體區脫離目前的進程。

int shmdt(const void *shmaddr);

[參數*shmaddr]:連接以後返回的地址。

[返回值]:成功返回0,並將shmid_ds結構體中的 shm_nattch計數器減1;出錯返回-1。


shmctl ( ):銷毀共享記憶體

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

[參數shmid]:共享存儲段標識符。

[參數cmd]:指定的執行操作,設置為IPC_RMID時表示可以刪除共享記憶體。

[參數*buf]:設置為NULL即可。

[返回值]:成功返回0,失敗返回-1。

POSIX 消息隊列   

POSIX 消息隊列是 POSIX 標準在 2001 年定義的一種 IPC 機制,與 System V 中的消息隊列相比有如下差異:

  • 更簡單的基於文件的應用介面,Linux 通過 mqueue 的特殊文件系統來實現消息隊列,隊列名跟文件名類似,必須以”/”開頭,每個消息隊列在文件系統內都有一個對應的索引節點,返回的隊列描述符實際是一個文件描述符

  • 完全支援消息優先順序,消息在隊列中是按照優先順序倒序排列的(即0表示優先順序最低)。當一條消息被添加到隊列中時,它會被放置在隊列中具有相同優先順序的所有消息之後。如果一個應用程式無需使用消息優先順序,那麼只需要將msg_prio指定為0即可。

  • 完全支援消息到達的非同步通知,當新消息到達且當前隊列為空時會通知之前註冊過表示接受通知的進程。在任何一個時刻都只有一個進程能夠向一個特定的消息隊列註冊接收通知。如果一個消息隊列上已經存在註冊進程了,那麼後續在該隊列上的註冊請求將會失敗。可以給進程發送訊號或者另起一個執行緒調用通知函數完成通知。當通知完成時,註冊即被撤銷,進程需要繼續接受通知則必須重新註冊。

  • 用於阻塞發送與接收操作的超時機制,可以指定阻塞的最長時間,超時自動返回 

套接字:

套接字是更為基礎的進程間通訊機制,與其他方式不同的是,套接字可用於不同機器之間的進程間通訊。

有兩種類型的套接字:基於文件的和面向網路的。

  • Unix 套接字是基於文件的,並且擁有一個「家族名字」–AF_UNIX,它代表地址家族 (address family):UNIX。

  • 第二類型的套接字是基於網路的,它也有自己的家族名字–AF_INET,代表地址家族 (address family):INTERNET

不管採用哪種地址家族,都有兩種不同的套接字連接:面向連接的和無連接的。

  • 面向連接的套接字 (SOCK_STREAM):進行通訊前必須建立一個連接,面向連接的通訊提供序列化的、可靠地和不重複的數據交付,而沒有記錄邊界。

這意味著每條資訊可以被拆分成多個片段,並且每個片段都能確保到達目的地,然後在目的地將資訊拼接起來。

實現這種連接類型的主要協議是傳輸控制協議 (TCP)。

  • 無連接的套接字 (SOCK_DGRAM):在通訊開始之前並不需要建立連接,在數據傳輸過程中並無法保證它的順序性、可靠性或重複性。

然而,數據報確實保存了記錄邊界,這就意味著消息是以整體發送的,而並非首先分成多個片段。

由於面向連接的套接字所提供的保證,因此它們的設置以及對虛擬電路連接的維護需要大量的開銷。然而,數據報不需要這些開銷,即它的成本更加「低廉」

實現這種連接類型的主要協議是用戶數據報協議 (UDP)。

訊號

訊號是軟體層次上對中斷機制的一種模擬,是一種非同步通訊方式,進程不必通過任何操作來等待訊號的到達。訊號可以在用戶空間進程和內核之間直接交互,內核可以利用訊號來通知用戶空間的進程發生了哪些系統事件。

訊號來源:

訊號事件的發生有兩個來源:硬體來源,比如我們按下了鍵盤或者其它硬體故障;軟體來源,最常用發送訊號的系統函數是 kill, raise, alarm 和 setitimer 以及 sigqueue 函數,軟體來源還包括一些非法運算等操作。

進程對訊號的響應:

進程可以通過三種方式來響應訊號:

  • 忽略訊號,即對訊號不做任何處理,但是有兩個訊號是不能忽略的:SIGKLL 和 SIGSTOP;

  • 捕捉訊號,定義訊號處理函數,當訊號發生時,執行相應的處理函數;

  • 執行預設操作,Linux 對每種訊號都規定了默認操作。

Tags: