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