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