進程間通信

進程間通信

進程間通信就是在不同進程之間傳播或交換信息,那么不同進程之間存在著什麼雙方都可以訪問的介質呢?進程的用戶空間是互相獨立的,一般而言是不能互相訪問的,唯一的例外是共享記憶體區。另外,系統空間是“公共場所”,各進程均可以訪問,所以核心也可以提供這樣的條件。此外,還有雙方都可以訪問的外設。在這個意義上,兩個進程當然也可以通過磁碟上的普通檔案交換信息,或者通過“註冊表”或其它資料庫中的某些表項和記錄交換信息。廣義上這也是進程間通信的手段,但是一般都不把這算作“進程間通信”。

概述

進程間通信(IPC,Interprocess communication)是一組編程接口,讓程式設計師能夠協調不同的進程,使之能在一個作業系統里同時運行,並相互傳遞、交換信息。這使得一個程式能夠在同一時間裡處理許多用戶的要求。因為即使只有一個用戶發出要求,也可能導致一個作業系統中多個進程的運行,進程之間必須互相通話。IPC接口就提供了這種可能性。每個IPC方法均有它自己的優點和局限性,一般,對於單個程式而言使用所有的IPC方法是不常見的。
IPC方法包括管道(PIPE)、訊息排隊、旗語、共用記憶體以及套接字(Socket)。

主要分類

種類

進程間通信主要包括管道, 系統IPC(包括訊息佇列,信號,共享存儲), 套接字(SOCKET).

管道包括三種:

1)普通管道PIPE, 通常有兩種限制,一是單工,只能單向傳輸;二是只能在父子或者兄弟進程間使用.

2)流管道s_pipe: 去除了第一種限制,為半雙工,可以雙向傳輸.

3)命名管道:name_pipe, 去除了第二種限制,可以在許多並不相關的進程之間進行通訊.

識別

系統IPC的三種方式類同,都是使用了核心里的標識符來識別.

FAQ1: 管道與檔案描述符,檔案指針的關係?

答: 其實管道的使用方法與檔案類似,都能使用read,write,open等普通IO函式. 管道描述符來類似於檔案描述符. 事實上, 管道使用的描述符,檔案指針和檔案描述符最終都會轉化成系統中SOCKET描述符. 都受到系統核心中SOCKET描述符的限制. 本質上LINUX核心源碼中管道是通過空檔案來實現.

FAQ2: 管道的使用方法?

答: 主要有下面幾種方法: 1)pipe, 創建一個管道,返回2個管道描述符.通常用於父子進程之間通訊. 2)popen, pclose: 這種方式只返回一個管道描述符,常用於通信另一方是stdin or stdout; 3)mkpipe:命名管道, 在許多進程之間進行互動.

FAQ3: 管道與系統IPC之間的優劣比較?

答: 管道: 優點是所有的UNIX實現都支持, 並且在最後一個訪問管道的進程終止後,管道就被完全刪除;缺陷是管道只允許單向傳輸或者用於父子進程之間.

系統IPC: 優點是功能強大,能在毫不相關進程之間進行通訊; 缺陷是關鍵字KEY_T使用了核心標識,占用了核心資源,而且只能被顯式刪除,而且不能使用SOCKET的一些機制,例如select,epoll等.

FAQ4: WINDOS進程間通信與LINUX進程間通信的關係?

答: 事實上,WINDOS的進程通信大部分移植於UNIX, WINDOS的剪貼簿,檔案映射等都可從UNIX進程通信的共享存儲中找到影子.

FAQ5: 進程間通信與執行緒間通信之間的關係?

答: 因為WINDOWS運行的實體是執行緒, 狹義上的進程間通信其實是指分屬於不同進程的執行緒之間的通訊.而單個進程之間的執行緒同步問題可歸併為一種特殊的進程通信.它要用到核心支持的系統調用來保持執行緒之間同步. 通常用到的一些執行緒同步方法包括:Event, Mutex,信號量Semaphore,臨界區資源等.

IPC目的

1)數據傳輸:一個進程需要將它的數據傳送給另一個進程,傳送的數據量在一個位元組到幾兆位元組之間。

2)共享數據:多個進程想要操作共享數據,一個進程對共享數據的修改,別的進程應該立刻看到。

3)通知事件:一個進程需要向另一個或一組進程傳送訊息,通知它(它們)發生了某種事件(如進程終止時要通知父進程)。

4)資源共享:多個進程之間共享同樣的資源。為了作到這一點,需要核心提供鎖和同步機制。

5)進程控制:有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,並能夠及時知道它的狀態改變。

進程通過與核心及其它進程之間的互相通信來協調它們的行為。Linux支持多種進程間通信(IPC)機制,信號和管道是其中的兩種。除此之外,Linux還支持System V 的IPC機制(用首次出現的Unix版本命名)。

信號

信號(Signals )是Unix系統中使用的最古老的進程間通信的方法之一。作業系統通過信號來通知進程系統中發生了某種預先規定好的事件(一組事件中的一個),它也是用戶進程之間通信和同步的一種原始機制。一個鍵盤中斷或者一個錯誤條件(比如進程試圖訪問它的虛擬記憶體中不存在的位置等)都有可能產生一個信號。Shell也使用信號向它的子進程傳送作業控制信號。

信號是在Unix System V中首先引入的,它實現了15種信號,但很不可靠。BSD4.2解決了其中的許多問題,而在BSD4.3中進一步加強和改善了信號機制。但兩者的接口不完全兼容。在Posix 1003.1標準中做了一些強行規定,它定義了一個標準的信號接口,但沒有規定接口的實現。目前幾乎所有的Unix變種都提供了和Posix標準兼容的信號實現機制。

一、 在一個信號的生命周期中有兩個階段:生成和傳送。當一個事件發生時,需要通知一個進程,這時生成一個信號。當進程識別出信號的到來,就採取適當的動作來傳送或處理信號。在信號到來和進程對信號進行處理之間,信號在進程上掛起(pending)。

核心為進程生產信號,來回響不同的事件,這些事件就是信號源。主要的信號源如下:

異常:進程運行過程中出現異常;

其它進程:一個進程可以向另一個或一組進程傳送信號;

終端中斷:Ctrl-C,Ctrl-\等;

作業控制:前台、後台進程的管理;

分配額:CPU逾時或檔案大小突破限制;

通知:通知進程某事件發生,如I/O就緒等;

報警:計時器到期。

在 Linux 中,信號的種類和數目與硬體平台有關。核心用一個字代表所有的信號,每個信號占一位,因此一個字的位數就是系統可以支持的最多信號種類數。i386 平台上有32 種信號,而Alpha AXP 平台上最多可有 64 種信號。系統中有一組定義好的信號,它們可以由核心產生,也可以由系統中其它有許可權的進程產生。可以使用kill命令列出系統中的信號集。

下面是幾個常見的信號。

SIGHUP: 從終端上發出的結束信號;

SIGINT: 來自鍵盤的中斷信號(Ctrl-C);

SIGQUIT:來自鍵盤的退出信號(Ctrl-\);

SIGFPE: 浮點異常信號(例如浮點運算溢出);

SIGKILL:該信號結束接收信號的進程;

SIGALRM:進程的定時器到期時,傳送該信號;

SIGTERM:kill 命令發出的信號;

SIGCHLD:標識子進程停止或結束的信號;

SIGSTOP:來自鍵盤(Ctrl-Z)或調試程式的停止執行信號;

…………

每一個信號都有一個預設動作,它是當進程沒有給這個信號指定處理程式時,核心對信號的處理。有5種預設的動作:

異常終止(abort):在進程的當前目錄下,把進程的地址空間內容、暫存器內容保存到一個叫做core的檔案中,而後終止進程。

退出(exit):不產生core檔案,直接終止進程。

忽略(ignore):忽略該信號。

停止(stop):掛起該進程。

繼續(continue):如果進程被掛起,則恢復進程的運行。否則,忽略信號。

進程可以對任何信號指定另一個動作或重載預設動作,指定的新動作可以是忽略信號。進程也可以暫時地阻塞一個信號。因此進程可以選擇對某種信號所採取的特定操作,這些操作包括:

忽略信號:進程可忽略產生的信號,但 SIGKILL 和 SIGSTOP 信號不能被忽略,必須處理(由進程自己或由核心處理)。進程可以忽略掉系統產生的大多數信號。

阻塞信號:進程可選擇阻塞某些信號,即先將到來的某些信號記錄下來,等到以後(解除阻塞後)再處理它。

由進程處理該信號:進程本身可在系統中註冊處理信號的處理程式地址,當發出該信號時,由註冊的處理程式處理信號。

由核心進行預設處理:信號由核心的預設處理程式處理,執行該信號的預設動作。例如,進程接收到SIGFPE(浮點異常)的預設動作是產生core並退出。大多數情況下,信號由核心處理。

需要指出的是,對信號的任何處理,包括終止進程,都必須由接收到信號的進程來執行。而進程要執行信號處理程式,就必須等到它真正運行時。因此,對信號的處理可能需要延遲一段時間。

信號沒有固有的優先權。如果為一個進程同時產生了兩個信號,這兩個信號會以任意順序出現在進程中並會按任意順序被處理。另外,也沒有機制用於區分同一種類的多個信號。如果進程在處理某個信號之前,又有相同的信號發出,則進程只能接收到一個信號。進程無法知道它接收了1個還是42個SIGCONT信號。

管道

普通的Linux shell都允許重定向,而重定向使用的就是管道。例如:

$ ls | pr | lpr

把命令ls(列出目錄中的檔案)的輸出通過管道連線到命令pr的標準輸入上進行分頁。最後,命令pr的標準輸出通過管道連線到命令lpr的標準輸入上,從而在預設印表機上列印出結果。進程感覺不到這種重定向,它們和平常一樣地工作。正是shell建立了進程之間的臨時管道。

管道是單向的、先進先出的、無結構的、固定大小的位元組流,它把一個進程的標準輸出和另一個進程的標準輸入連線在一起。寫進程在管道的尾端寫入數據,讀進程在管道的首端讀出數據。數據讀出後將從管道中移走,其它讀進程都不能再讀到這些數據。管道提供了簡單的流控制機制。進程試圖讀空管道時,在有數據寫入管道前,進程將一直阻塞。同樣,管道已經滿時,進程再試圖寫管道,在其它進程從管道中移走數據之前,寫進程將一直阻塞。

管道示意圖 管道示意圖

傳統上有很多種實現管道的方法,如利用檔案系統、利用套接字(sockets)、利用流等。在Linux中,使用兩個file數據結構來實現管道。這兩個file數據結構中的f_inode(f_dentry)指針指向同一個臨時創建的VFS I節點,而該VFS I節點本身又指向記憶體中的一個物理頁,如圖5.1所示。兩個file數據結構中的f_op指針指向不同的檔案操作例程向量表:一個用於向管道中寫,另一個用於從管道中讀。這種實現方法掩蓋了底層實現的差異,從進程的角度來看,讀寫管道的系統調用和讀寫普通檔案的普通系統調用沒什麼不同。當寫進程向管道中寫時,位元組被拷貝到了共享數據頁,當讀進程從管道中讀時,位元組被從共享頁中拷貝出來。Linux必須同步對於管道的存取,必須保證管道的寫和讀步調一致。Linux使用鎖、等待佇列和信號(locks,wait queues and signals)來實現同步。

右圖 --管道示意圖所示

參見include/linux/inode_fs.h

當寫進程向管道寫的時候,它使用標準的write庫函式。這些庫函式(read、write等)要求傳遞一個檔案描述符作為參數。檔案描述符是該檔案對應的file數據結構在進程的file數據結構數組中的索引,每一個都表示一個打開的檔案,在這種情況下,是打開的管道。Linux系統調用使用描述這個管道的file數據結構中f_op所指的write例程,該write例程使用表示管道的VFS I 節點中存放的信息,來管理寫請求。如果共享數據頁中有足夠的空間能把所有的位元組都寫到管道中,而且管道沒有被讀進程鎖定,則Linux就在管道上為寫進程加鎖,並把位元組從進程的地址空間拷貝到共享數據頁。如果管道被讀進程鎖定或者共享數據頁中沒有足夠的空間,則當前進程被迫睡眠,它被掛在管道I節點的等待佇列中等待,而後調用調度程式,讓另外一個進程運行。睡眠的寫進程是可以中斷的(interruptible),所以它可以接收信號。當管道中有了足夠的空間可以寫數據,或者當鎖定解除時,寫進程就會被讀進程喚醒。當數據寫完之後,管道的VFS I 節點上的鎖定解除,在管道I節點的等待佇列中等待的所有讀進程都會被喚醒。

參見fs/pipe.c pipe_write()

從管道中讀取數據和寫數據非常相似。Linux允許進程無阻塞地讀檔案或管道(依賴於它們打開檔案或者管道的模式),這時,如果沒有數據可讀或者管道被鎖定,系統調用會返回一個錯誤。這意味著進程會繼續運行。另一種方式是阻塞讀,即進程在管道I節點的等待佇列中等待,直到寫進程完成。

如果所有的進程都完成了它們的管道操作,則管道的I節點和相應的共享數據頁會被廢棄。

參見fs/pipe.c pipe_read()

Linux也支持命名管道(也叫FIFO,因為管道工作在先入先出的原則下,第一個寫入管道的數據也是第一個被讀出的數據)。與管道不同,FIFO不是臨時的對象,它們是檔案系統中真正的實體,可以用mkfifo命令創建。只要有合適的訪問許可權,進程就可以使用FIFO。FIFO的打開方式和管道稍微不同。一個管道(它的兩個file數據結構、VFS I節點和共享數據頁)是一次性創建的,而FIFO已經存在,可以由它的用戶打開和關閉。Linux必須處理在寫進程打開FIFO之前讀進程對它的打開,也必須處理在寫進程寫數據之前讀進程對管道的讀。除此以外,FIFO幾乎和管道的處理完全一樣,而且它們使用一樣的數據結構和操作。

從IPC的角度看,管道提供了從一個進程向另一個進程傳輸數據的有效方法。但是,管道有一些固有的局限性:

因為讀數據的同時也將數據從管道移去,因此,管道不能用來對多個接收者廣播數據。

管道中的數據被當作位元組流,因此無法識別信息的邊界。

如果一個管道有多個讀進程,那么寫進程不能傳送數據到指定的讀進程。同樣,如果有多個寫進程,那么沒有辦法判斷是它們中那一個傳送的數據。

系統V

系統V IPC機制(System V IPC Mechanisms)

前面討論的信號和管道雖然可以在進程之間通信,但還有許多應用程式的IPC需求它們不能滿足。因此在System V UNIX(1983)中首次引入了另外三種進程間通信機制(IPC)機制:訊息佇列、信號燈和共享記憶體(message queues,semaphores and shared memory)。它們最初的設計目的是滿足事務式處理的套用需求,但目前大多數的UNIX供應商(包括基於BSD的供應商)都實現了這些機制。 Linux完全支持Unix System V中的這三種IPC機制。

System V IPC機制共享通用的認證方式。進程在使用某種類型的IPC資源以前,必須首先通過系統調用創建或獲得一個對該資源的引用標識符。進程只能通過系統調用,傳遞一個唯一的引用標識符到核心來訪問這些資源。在每一種機制中,對象的引用標識符都作為它在資源表中的索引。但它不是直接的索引,需要一個簡單的操作來從引用標識符產生索引。對於System V IPC對象的訪問,使用訪問許可權檢查,這很象對檔案訪問時所做的檢查。System V IPC對象的訪問許可權由對象的創建者通過系統調用設定。

系統中表示System V IPC對象的所有Linux數據結構中都包括一個ipc_perm數據結構,用它記錄IPC資源的認證信息。其定義如下:

struct ipc_perm

{

__kernel_key_t key;

__kernel_uid_t uid;

__kernel_gid_t gid;

__kernel_uid_t cuid;

__kernel_gid_t cgid;

__kernel_mode_tmode;

unsigned short seq;

};

在ipc_perm數據結構中包括了創建者進程的用戶和組標識、所有者進程的用戶和組標識、對於這個對象的訪問模式(屬主、組和其它)以及IPC對象的鍵值(key)。Linux通過key 來定位System V IPC對象的引用標識符,每個IPC對象都有一個唯一的key。Linux支持兩種key:公開的和私有的。如果key是公開的,那么系統中的任何進程,只要通過了許可權檢查,就可以找到它所對應的System V IPC對象的引用標識符。System V IPC對象不能直接使用key來引用,必須使用它們的引用標識符來引用。(參見include/linux/ipc.h)每種IPC機制都提供一種系統調用,用來將鍵值(key)轉化為對象的引用標識符。

對所有的System V IPC,Linux提供了一個統一的系統調用:sys_ipc,通過該函式可以實現對System V IPC的所有操作。函式sys_ipc的定義如下:

int sys_ipc (uint call, int first, int second,

int third, void *ptr, long fifth)

這裡call是一個32位的整數,其低16位指明了此次調用所要求的工作。對不同的call值,其餘各參數的意義也不相同。以下將分別介紹各IPC機制及其所提供的操作。

Message Queues(訊息佇列)

訊息佇列就是訊息的一個鍊表,它允許一個或多個進程向它寫訊息,一個或多個進程從中讀訊息。Linux維護了一個訊息佇列向量表:msgque,來表示系統中所有的訊息佇列。其定義如下:

struct msqid_ds *msgque[MSGMNI];

該向量表中的每一個元素都是一個指向msqid_ds數據結構的指針,而一個msqid_ds數據結構完整地描述了一個訊息佇列。

MSGMNI的值是128,就是說,系統中同時最多可以有128個訊息佇列。

msqid_ds數據結構的定義如下:

struct msqid_ds {

struct ipc_perm msg_perm;

struct msg *msg_first; /* first message on queue */

struct msg *msg_last; /* last message in queue */

__kernel_time_t msg_stime; /* last msgsnd time */

__kernel_time_t msg_rtime; /* last msgrcv time */

__kernel_time_t msg_ctime; /* last change time */

struct wait_queue *wwait;

struct wait_queue *rwait;

unsigned short msg_cbytes; /* current number of bytes on queue */

unsigned short msg_qnum; /* number of messages in queue */

unsigned short msg_qbytes; /* max number of bytes on queue */

__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */

__kernel_ipc_pid_t msg_lrpid; /* last receive pid */

};

其中包括:

l 一個ipc_perm的數據結構(msg_perm域),描述該訊息佇列的通用認證方式。

l 一對訊息指針(msg_first、msg_last),分別指向該訊息佇列的隊頭(第一個訊息)和隊尾(最後一個訊息)(msg)。傳送者將新訊息加到隊尾,接收者從隊頭讀取訊息。

l 三個時間域(msg_stime、msg_rtime、msg_ctime)用於記錄佇列最後一次傳送時間、接收時間和改動時間。

l 兩個進程等待佇列(wwait、rwait)分別表示等待向訊息佇列中寫的進程(wwait)和等待從訊息佇列中讀的進程(rwait)。如果某進程向一個訊息佇列傳送訊息而發現該佇列已滿,則進程掛在wwait佇列中等待。從該訊息佇列中讀取訊息的進程將從佇列中刪除訊息,從而騰出空間,再喚醒wwait佇列中等待的進程。如果某進程從一個訊息佇列中讀訊息而發現該佇列已空,則進程掛在rwait佇列中等待。向該訊息佇列中傳送訊息的進程將訊息加入佇列,再喚醒rwait佇列中等待的進程。

l 三個記數域(msg_cbytes、msg_qnum、msg_qbytes)分別表示佇列中的當前位元組數、佇列中的訊息數和佇列中最大位元組數;

l 兩個PID域(msg_lspid、msg_lrpid)分別表示最後一次向該訊息佇列中傳送訊息的進程和最後一次從該訊息佇列中接收訊息的進程。

System V IPC 機制——訊息佇列 System V IPC 機制——訊息佇列

見右圖(參見include/linux/msg.h)圖 System V IPC 機制——訊息佇列

當創建訊息佇列時,一個新的msqid_ds數據結構被從系統記憶體中分配出來,並被插入到msgque 向量表中。

每當進程試圖向訊息佇列寫訊息時,它的有效用戶和組標識符就要和訊息佇列的ipc_perm數據結構中的模式域比較。如果進程可以向這個訊息佇列寫(比較成功),則訊息會從進程的地址空間拷貝到一個msg數據結構中,該msg數據結構被放到訊息佇列的隊尾。每一個訊息都帶有進程間約定的、與應用程式相關的類型標記。但是,因為Linux限制了可以寫的訊息的數量和長度,所以可能會沒有空間來容納該訊息。這時,進程會被放到訊息佇列的寫等待佇列中,然後調度程式會選擇一個新的進程運行。當一個或多個訊息從這個訊息佇列中讀出去時,等待的進程會被喚醒。

從佇列中讀訊息與向佇列中寫訊息是一個相似的過程。進程對訊息佇列的訪問許可權一樣要被檢查。讀進程可以選擇從佇列中讀取第一條訊息而不管訊息的類型,也可以選擇從佇列中讀取特殊類型的訊息。如果沒有符合條件的訊息,讀進程會被加到訊息佇列的讀等待佇列,然後運行調度程式。當一個新的訊息寫到訊息佇列時,這個進程會被喚醒,繼續它的運行。

Linux提供了四個訊息佇列操作。

1. 創建或獲得訊息佇列(MSGGET)

在系統調用sys_ipc中call值為MSGGET,調用的函式為sys_msgget。該函式的定義如下:

int sys_msgget (key_t key, int msgflg)

其中key是一個鍵值,而msgflg是一個標誌。

該函式的作用是創建一個鍵值為key的訊息佇列,或獲得一個鍵值為key的訊息佇列的引用標識符。這是使用訊息佇列的第一步,即獲得訊息佇列的引用標識符,以後就通過該標識符使用這個訊息佇列。

工作過程如下:

1) 如果key == IPC_PRIVATE,則申請一塊記憶體,創建一個新的訊息佇列(數據結構msqid_ds),將其初始化後加入到msgque向量表中的某個空位置處,返回標識符。

2) 在msgque向量表中找鍵值為key的訊息佇列,如果沒有找到,結果有二:

l msgflg表示不創建新的佇列,則錯誤返回。

l msgflg表示要創建新的佇列,則創建新訊息佇列,創建過程如1)。

3) 如果在msgque向量表中找到了鍵值為key的訊息佇列,則有以下情況:

l 如果msgflg表示一定要創建新的訊息佇列而且不允許有相同鍵值的佇列存在,則錯誤返回。

l 如果找到的佇列是不能用的或已損壞的佇列,則錯誤返回。

l 認證和存取許可權檢查,如果該佇列不允許msgflg要求的存取,則錯誤返回。

l 正常,返回佇列的標識符。

2. 傳送訊息

在系統調用sys_ipc中call值為MSGSND,調用的函式為sys_msgsnd。該函式的定義如下:

int sys_msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)

其中:msqid是訊息佇列的引用標識符;

msgp是訊息內容所在的緩衝區;

msgsz是訊息的大小;

msgflg是標誌。

該函式做如下工作:

1) 該訊息佇列在向量msgque中的索引是id = (unsigned int) msqid % MSGMNI,認證檢查(許可權、模式),合法性檢查(類型、大小等)。

2) 如果佇列已滿,以可中斷等待狀態(TASK_INTERRUPTIBLE)將當前進程掛起在wwait等待佇列上。

3) 申請一塊空間,大小為一個訊息數據結構加上訊息大小,在其上創建一個訊息數據結構struct msg,將訊息緩衝區中的訊息內容拷貝到該記憶體塊中訊息頭的後面(從用戶空間拷貝到核心空間)。

4) 將訊息數據結構加入到訊息佇列的隊尾,修改佇列的相應參數(大小等)。

5) 喚醒在該訊息佇列的rwait進程佇列上等待讀的進程。

6) 返回

3. 接收訊息

在系統調用sys_ipc中call值為MSGRCV,調用的函式為sys_msgrcv。該函式的定義如下:

int sys_msgrcv (int msqid, struct msgbuf *msgp, size_t msgsz,

long msgtyp, int msgflg)

其中:msqid是訊息佇列的引用標識符;

msgp是接收到的訊息將要存放的緩衝區;

msgsz是訊息的大小;

msgtyp是期望接收的訊息類型;

msgflg是標誌。

該函式做如下工作:

1) 該訊息佇列在向量msgque中的索引是id = (unsigned int) msqid % MSGMNI,認證檢查(許可權、模式),合法性檢查。

2) 根據msgtyp和msgflg搜尋訊息佇列,情況有二:

l 如果找不到所要的訊息,則以可中斷等待狀態(TASK_INTERRUPTIBLE)將當前進程掛起在rwait等待佇列上。

l 如果找到所要的訊息,則將訊息從佇列中摘下,調整佇列參數,喚醒該訊息佇列的wwait進程佇列上等待寫的進程,將訊息內容拷貝到用戶空間的訊息緩衝區msgp中,釋放核心中該訊息所占用的空間,返回。

4. 訊息控制

在系統調用sys_ipc中call值為MSGCTL,調用的函式為sys_msgctl。該函式的定義如下:

int sys_msgctl (int msqid, int cmd, struct msqid_ds *buf)

其中:msqid是訊息佇列的引用標識符;

cmd是執行命令;

buf是一個緩衝區。

該函式做如下工作:

該函式對訊息佇列做一些控制動作,如:釋放佇列,獲得佇列的認證信息,設定佇列的認證信息等。

訊息佇列和管道提供相似的服務,但訊息佇列要更加強大並解決了管道中所存在的一些問題。訊息佇列傳遞的訊息是不連續的、有格式的信息,給對它們的處理帶來了很大的靈活性。可以用不同的方式解釋訊息的類型域,如可以將訊息的類型同訊息的優先權聯繫起來,類型域也可以用來指定接收者。

小訊息的傳送效率很高,但大訊息的傳送性能則較差。因為訊息傳送的過程中要經過從用戶空間到核心空間,再從核心空間到用戶空間的拷貝,所以,大訊息的傳送其性能較差。另外,訊息佇列不支持廣播,而且核心不知道訊息的接收者。

操作

管道分為有名管道和無名管道,無名管道只能用於親屬進程之間的通信,而有名管道則可用於無親屬關係的進程之間。

在Linux系統下,命名管道可由兩種方式創建(假設創建一個名為“fifoexample”的有名管道):

(1)mkfifo("fifoexample","rw");

(2)mknod fifoexample p

mkfifo是一個函式,mknod是一個系統調用,即我們可以在shell下輸出上述命令。

有名管道創建後,我們可以像讀寫檔案一樣讀寫它。

訊息佇列用於運行於同一台機器上的進程間通信,與管道相似。

共享記憶體

通常由一個進程創建,其餘進程對這塊記憶體區進行讀寫。得到共享記憶體有兩種方式:映射/dev/mem設備和記憶體映像檔案。前一種方式不給系統帶來額外的開銷,但在現實中並不常用,因為它控制存取的是實際的物理記憶體;常用的方式是通過shmXXX函式族來實現共享記憶體:

int shmget(key_t key, int size, int flag); /* 獲得一個共享存儲標識符*/

該函式使得系統分配size大小的記憶體用作共享記憶體;

void *shmat(int shmid, void *addr, int flag); /* 將共享記憶體連線到自身地址空間中*/

如果一個進程通過fork創建了子進程,則子進程繼承父進程的共享記憶體,既而可以直接對共享記憶體使用,不過子進程可以自身脫離共享記憶體。

shmid為shmget函式返回的共享存儲標識符,addr和flag參數決定了以什麼方式來確定連線的地址,函式的返回值即是該進程數據段所連線的實際地址。此後,進程可以對此地址進行讀寫操作訪問共享記憶體。

對於共享記憶體,linux本身無法對其做同步,需要程式自己來對共享的記憶體做出同步計算,而這種同步很多時候就是用信號量實現。

獲得共享資源

本質上,信號量是一個計數器,它用來記錄對某個資源(如共享記憶體)的存取狀況。信號量,分為互斥信號量,和條件信號量。一般說來,為了獲得共享資源,進程需要執行下列操作:

(1)測試控制該資源的信號量;

(2)若此信號量的值為正,則允許進行使用該資源,進程將信號量減去所需的資源數;

(3)若此信號量為0,則該資源目前不可用,進程進入睡眠狀態,直至信號量值大於0,進程被喚醒,轉入步驟(1);

(4)當進程不再使用一個信號量控制的資源時,信號量值加其所占的資源數,如果此時有進程正在睡眠等待此信號量,則喚醒此進程。

其他信息

套接字通信並不為Linux所專有,在所有提供了TCP/IP協定棧的作業系統中幾乎都提供了socket,而所有這樣作業系統,對套接字的編程方法幾乎是完全一樣的。

效率比較

進程間通信各種方式效率比較

類型 無連線 可靠 流控制 記錄 訊息類型優先權
普通PIPE N Y Y N
流PIPE N Y Y N
命名PIPE(FIFO) N Y Y N
訊息佇列 N Y Y Y
信號量 N Y Y Y
共享存儲 N Y Y Y
UNIX流SOCKET N Y Y N
UNIX數據包SOCKET Y Y N N

相關詞條

相關搜尋

熱門詞條

聯絡我們