本文主要摘自網(wǎng)絡(luò):http://blog.csdn.net/sunmenggmail/article/details/7888746 linux下的進(jìn)程通信手段基本上是從Unix平臺上的進(jìn)程通信手段繼承而來的。而對Unix發(fā)展做出重大貢獻(xiàn)的兩大主力AT&T的貝爾實(shí)驗(yàn)室及 BSD(加州大學(xué)伯克利分校的伯克利軟件發(fā)布中心)在進(jìn)程間通信方面的側(cè)重點(diǎn)有所不同。前者對Unix早期的進(jìn)程間通信手段進(jìn)行了系統(tǒng)的改進(jìn)和擴(kuò)充,形成 了“system V IPC”,通信進(jìn)程局限在單個(gè)計(jì)算機(jī)內(nèi);后者則跳過了該限制,形成了基于套接口(socket)的進(jìn)程間通信機(jī)制。Linux則把兩者繼承了下來,如圖 示: ![]() Unix IPC包括:管道、FIFO、信號。 System V IPC包括:System V消息隊(duì)列、System V信號燈、System V共享內(nèi)存區(qū)。 Posix IPC包括: Posix消息隊(duì)列、Posix信號燈、Posix共享內(nèi)存區(qū)。 有兩點(diǎn)需要簡單說明一下:1)由于Unix版本的多樣性,電子電氣工程協(xié)會(huì)(IEEE)開發(fā)了一個(gè)獨(dú)立的Unix標(biāo)準(zhǔn),這個(gè)新的ANSI Unix標(biāo)準(zhǔn)被稱為計(jì)算機(jī)環(huán)境的可移植性操作系統(tǒng)界面(PSOIX)?,F(xiàn)有大部分Unix和流行版本都是遵循POSIX標(biāo)準(zhǔn)的,而Linux從一開始就遵 循POSIX標(biāo)準(zhǔn);2)BSD并不是沒有涉足單機(jī)內(nèi)的進(jìn)程間通信(socket本身就可以用于單機(jī)內(nèi)的進(jìn)程間通信)。事實(shí)上,很多Unix版本的單機(jī) IPC留有BSD的痕跡,如4.4BSD支持的匿名內(nèi)存映射、4.3+BSD對可靠信號語義的實(shí)現(xiàn)等等。 linux下進(jìn)程間通信的幾種主要手段簡介:
1、管道(pipe) 管道是進(jìn)程間通信的主要手段之一。一個(gè)管道實(shí)際上就是個(gè)只存在于內(nèi)存中的文件,對這個(gè)文件的操作要通過兩個(gè)已經(jīng)打開文件進(jìn)行,它們分別代表管道的兩端。管道是一種特殊的文件,它不屬于某一種文件系統(tǒng),而是一種獨(dú)立的文件系統(tǒng),有其自己的數(shù)據(jù)結(jié)構(gòu)。根據(jù)管道的適用范圍將其分為:無名管道和命名管道。 ● 無名管道 主要用于父進(jìn)程與子進(jìn)程之間,或者兩個(gè)兄弟進(jìn)程之間。在linux系統(tǒng)中可以通過系統(tǒng)調(diào)用建立起一個(gè)單向的通信管道,且這種關(guān)系只能由父進(jìn)程來建立。因此,每個(gè)管道都是單向的,當(dāng)需要雙向通信時(shí)就需要建立起兩個(gè)管道。管道兩端的進(jìn)程均將該管道看做一個(gè)文件,一個(gè)進(jìn)程負(fù)責(zé)往管道中寫內(nèi)容,而另一個(gè)從管道中讀取。這種傳輸遵循“先入先出”(FIFO)的規(guī)則。 ● 命名管道 命名管道是為了解決無名管道只能用于近親進(jìn)程之間通信的缺陷而設(shè)計(jì)的。命名管道是建立在實(shí)際的磁盤介質(zhì)或文件系統(tǒng)(而不是只存在于內(nèi)存中)上有自己 名字的文件,任何進(jìn)程可以在任何時(shí)間通過文件名或路徑名與該文件建立聯(lián)系。為了實(shí)現(xiàn)命名管道,引入了一種新的文件類型——FIFO文件(遵循先進(jìn)先出的原 則)。實(shí)現(xiàn)一個(gè)命名管道實(shí)際上就是實(shí)現(xiàn)一個(gè)FIFO文件。命名管道一旦建立,之后它的讀、寫以及關(guān)閉操作都與普通管道完全相同。雖然FIFO文件的 inode節(jié)點(diǎn)在磁盤上,但是僅是一個(gè)節(jié)點(diǎn)而已,文件的數(shù)據(jù)還是存在于內(nèi)存緩沖頁面中,和普通管道相同。 2、環(huán)形緩沖區(qū) 每個(gè)管道只有一個(gè)頁面作為緩沖區(qū),該頁面是按照環(huán)形緩沖區(qū)的方式來使用的。這種訪問方式是典型的“生產(chǎn)者——消費(fèi)者”模型。當(dāng)“生產(chǎn)者”進(jìn)程有大量 的數(shù)據(jù)需要寫時(shí),而且每當(dāng)寫滿一個(gè)頁面就需要進(jìn)行睡眠等待,等待“消費(fèi)者”從管道中讀走一些數(shù)據(jù),為其騰出一些空間。相應(yīng)的,如果管道中沒有可讀數(shù)據(jù), “消費(fèi)者”進(jìn)程就要睡眠等待。 2.1環(huán)形緩沖區(qū)實(shí)現(xiàn)原理 環(huán)形緩沖區(qū)是嵌入式系統(tǒng)中一個(gè)常用的重要數(shù)據(jù)結(jié)構(gòu)。一般采用數(shù)組形式進(jìn)行存儲,即在內(nèi)存中申請一塊連續(xù)的線性空間,可以在初始化的時(shí)候把存儲空間一 次性分配好。只是要模擬環(huán)形,必須在邏輯上把數(shù)組的頭尾相連接。只要對數(shù)組最后一個(gè)元素進(jìn)行特殊的處理——訪問尾部元素的下一元素時(shí),重新回到頭部元素。 對于從尾部回到頭部只需模緩沖長度即可(假設(shè)maxlen為環(huán)形緩沖的長度,當(dāng)讀指針read指向尾部元素時(shí),只需執(zhí)行read=read%maxlen 即可使read回到頭部元素)。 2.2讀寫操作 環(huán)形緩沖區(qū)要維護(hù)寫端(write)和讀端(read)兩個(gè)索引。寫入數(shù)據(jù)時(shí),必須先確保緩沖區(qū)沒有滿,然后才能將數(shù)據(jù)寫入,最后將write指針
指向下一個(gè)元素;讀取數(shù)據(jù)時(shí),首先要確保緩沖區(qū)不為空,然后返回read指針對應(yīng)得元素,最后使read指向下一個(gè)元素的位置。讀寫操作偽代碼: 2.3判斷“滿”和“空” 當(dāng)read和write指向同一個(gè)位置時(shí)環(huán)形緩沖區(qū)為空或滿。為了區(qū)別環(huán)滿和空,當(dāng)read和write重疊的時(shí)候環(huán)空;而當(dāng)write比read快,追到距離read還有一個(gè)元素間隔的時(shí)候,就認(rèn)為環(huán)已經(jīng)滿了。環(huán)形緩沖區(qū)原理圖如圖3所示。 3 并發(fā)訪問 考慮到在不同環(huán)境下,任務(wù)可能對環(huán)形緩沖區(qū)的訪問情況不同,需要對并發(fā)訪問的情況進(jìn)行分析。 在單任務(wù)環(huán)境下,只存在一個(gè)讀任務(wù)和一個(gè)寫任務(wù),只要保證寫任務(wù)可以順利的完成將數(shù)據(jù)寫入,而讀任務(wù)可以及時(shí)的將數(shù)據(jù)讀出即可。如果有競爭發(fā)生,可能會(huì)出現(xiàn)如下情況: Case1:假如寫任務(wù)在“寫指針加1,指向下一個(gè)可寫空位置”執(zhí)行完成時(shí)被打斷,如圖3所示,此時(shí)寫指針write指向非法位置。當(dāng)系統(tǒng)調(diào)度讀任 務(wù)執(zhí)行時(shí),如果讀任務(wù)需要讀多個(gè)數(shù)據(jù),那么不但應(yīng)該讀出的數(shù)據(jù)被讀出,而且當(dāng)讀指針被調(diào)整為0是,會(huì)將以前已經(jīng)讀出的數(shù)據(jù)重復(fù)讀出。 Case2:假設(shè)讀任務(wù)進(jìn)行讀操作,在“讀指針加1”執(zhí)行完時(shí)被打斷,如圖4所示,此時(shí)read所處的位置是非法的。當(dāng)系統(tǒng)調(diào)度寫任務(wù)執(zhí)行時(shí),如果 寫任務(wù)要寫多個(gè)數(shù)據(jù),那么當(dāng)寫指針指到尾部時(shí),本來緩沖區(qū)應(yīng)該為滿狀態(tài),不能再寫,但是由于讀指針處于非法位置,在讀任務(wù)執(zhí)行前,寫任務(wù)會(huì)任務(wù)緩沖區(qū)為 空,繼續(xù)進(jìn)行寫操作,將覆蓋還沒有來的及讀出的數(shù)據(jù)。 為了避免上述錯(cuò)誤的發(fā)生,必須保證讀寫指針操作是原子性的,讀寫指針的值要么是沒有修改的,要么是修改正確的。可以引入信號量,有效的保護(hù)臨界區(qū)代碼,就可以避免這些問題。在單任務(wù)環(huán)境下,也可以通過采取適當(dāng)?shù)拇胧﹣肀苊庑盘柫康氖褂?,從而提高程序的?zhí)行效率。 4.linux內(nèi)核中pipe的讀寫實(shí)現(xiàn) Linux內(nèi)核中采用struct pipe_inode_info結(jié)構(gòu)體來描述一個(gè)管道。 其中,當(dāng)pipe為空/滿時(shí),采用等待隊(duì)列,該隊(duì)列使用自旋鎖進(jìn)行保護(hù)。 用struct Pipe_buffer數(shù)據(jù)結(jié)構(gòu)描述pipe的緩沖(buffer) 本文重點(diǎn)針對pipe實(shí)現(xiàn)中對環(huán)形緩沖區(qū)的操作方法,目的是借鑒學(xué)習(xí)其互斥訪問方法。因此,著重分析pipe_read和pipe_write方法。 ●Pipe_read(fs/pipe.c) 訪問pipe對應(yīng)的inode必須獲得相應(yīng)的互斥鎖,防止并發(fā)訪問。 數(shù)據(jù)的讀出放在一個(gè)死循環(huán)中,整個(gè)for循環(huán)中的代碼均屬于臨界區(qū),需要互斥鎖進(jìn)行保護(hù)。 有以下幾種情況才會(huì)退出: ▲ 完成數(shù)據(jù)的讀出; ▲ Pipe沒有writer進(jìn)程 ▲ 進(jìn)程設(shè)置了O_NONBLOCK標(biāo)志 325行將buffer中的數(shù)據(jù)讀出。完成后,緊接著調(diào)整buffer中指針的位置 其中,348行設(shè)置標(biāo)志,do_wakeup為1,說明buffer中已經(jīng)有空位置可以寫入數(shù)據(jù),這時(shí),可以喚醒等待隊(duì)列中的睡眠的寫進(jìn)程。 如果沒有退出,或者成功讀取數(shù)據(jù),讀進(jìn)程會(huì)主動(dòng)調(diào)用pipe_wait函數(shù)進(jìn)行睡眠等待,直到有writer進(jìn)程寫入數(shù)據(jù)并將其喚醒。 ![]() 當(dāng)進(jìn)程從臨界區(qū)中退出后會(huì)釋放互斥鎖。
![]() 最后,為了防止reader進(jìn)程是因?yàn)槭盏叫盘柫慷顺觯俳o睡眠的writer進(jìn)程一次機(jī)會(huì),檢查do_wakeup,如果為1就喚醒睡眠的writer進(jìn)程。 ● pipe_write(fs/pipe.c) 首先,與pipe_read相同,pipe_write采用互斥鎖對臨界區(qū)進(jìn)行保護(hù)。寫操作也放在死循環(huán)中,退出條件也與read相同。 ![]() 與pipe_read不同,writer進(jìn)程不總是睡眠等待,在調(diào)用pipe_wait進(jìn)行睡眠后,如果有read進(jìn)程讀走某些數(shù)據(jù),write進(jìn)程會(huì)隨時(shí)進(jìn)行寫操作。 |
|