概述
進程間通信(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,而所有這樣操作系統,對套接字的編程方法幾乎是完全一樣的。
效率比較
進程間通信各種方式效率比較