進程間通信

進程間通信

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

概述


進程間通信(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;
};
在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;
struct msg *msg_last;
__kernel_time_t msg_stime;
__kernel_time_t msg_rtime;
__kernel_time_t msg_ctime;
struct wait_queue *wwait;
struct wait_queue *rwait;
unsigned short msg_cbytes;
unsigned short msg_qnum;
unsigned short msg_qbytes;
__kernel_ipc_pid_t msg_lspid;
__kernel_ipc_pid_t msg_lrpid;
};
其中包括:
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,而所有這樣操作系統,對套接字的編程方法幾乎是完全一樣的。

效率比較


進程間通信各種方式效率比較
類型無連接可靠流控制記錄消息類型優先順序
普通PIPENYYN
流PIPENYYN
命名PIPE(FIFO)NYYN
消息隊列NYYY
信號量NYYY
共享存儲NYYY
UNIX流SOCKETNYYN
UNIX數據包SOCKETYYNN