?? 14.htm
字號:
<p>14.5 FIFO </p>
<p>FIFO有時被稱為命名管道。管道只能由相關進程使用,它們共同的祖先進程創(chuàng)建了
</p>
<p>管道。但是,通過FIFO,不相關的進程也能交換數(shù)據。 </p>
<p>在十四章中,已經提及FIFO是一種文件類型。Stat結構(4.2節(jié))成員st_mode的編
</p>
<p>碼指明文件是否是FIFO類型。我們可以用S_ISFIFO宏對此進行測試。 </p>
<p>創(chuàng)建一個FIFO類似于創(chuàng)建一個文件。確實,一個FIFO的路徑名存在于文件系統(tǒng)中。
</p>
<p>#include <sys/types.h> </p>
<p>#include <sys/stat.h> </p>
<p>int mkfifo(const char *pathname, mode_t mode); </p>
<p>返回:若成功為0,出錯為-1 </p>
<p>mkfifo函數(shù)中mode參數(shù)的規(guī)格說明與open函數(shù)中的mode相同(見3.3節(jié))。新FIFO
</p>
<p>的用戶和組的所有權與我們在4.6節(jié)所述的相同。 </p>
<p>一旦已經用mkfifo創(chuàng)建了一個FIFO,我們就可用open打開它。
確實,一般的文件 </p>
<p>I/O函數(shù)(close,read,write,unlink等)都可用于FIFO。 </p>
<p>mkfifo是POSIX.1首先提出的。SVR3用mknod(2)系統(tǒng)調用創(chuàng)建FIFO。而在 </p>
<p>SVR4中,mkfifo調用mknod創(chuàng)建FIFO。 </p>
<p>POSIX.2已經建議了一個mkfifo(1)命令。SVR4和4.3+BSD現(xiàn)在支持此命令 </p>
<p>。于 </p>
<p>是,用一條shell命令就可以創(chuàng)建一個FIFO,然后用一般的shell I/O重新
</p>
<p>定向對 </p>
<p>其進行存取。 </p>
<p>當打開一個FIFO時,非阻塞標志(O_NONBLOCK)產生下列影響: </p>
<p>1. 在一般情況中(沒有說明O_NONBLOCK),只讀打開要阻塞到某個其它進程為寫
</p>
<p>打開此FIFO。類似,為寫而打開一個FIFO要阻塞到某個其它進程為讀而打開它。
</p>
<p>2. 如果指定了O_NONBLOCK,則只讀打開立即返回。但是,如果沒有進程已經為讀
</p>
<p>而打開一個FIFO,那么只寫打開將出錯返回,其errno是ENXIO。 </p>
<p>類似于管道,若寫一個尚無進程為讀而打開的FIFO,則產生信號SIGPIPE。若某個
</p>
<p>FIFO的最后一個寫進程關閉了該FIFO,則將為該FIFO的讀進程產生一個文件結束標
</p>
<p>志。 </p>
<p>一個給定的FIFO有多個寫進程是常見的。這就意味這如果我們不希望多個進程所寫
</p>
<p>的數(shù)據互相穿插,則
需考慮原子寫操作。正如對于管道一樣,常數(shù)PIPE_BUF說明 </p>
<p>了可被原子寫到FIFO的最大數(shù)據量。 </p>
<p>FIFO有兩種用途。 </p>
<p>1. FIFO由shell命令使用以便將數(shù)據從一條管道線傳送到另一條,為此無需創(chuàng)建中
</p>
<p>間臨時文件。 </p>
<p>2. FIFO用于客戶-服務者應用程序中,以在客戶和服務者之間傳遞數(shù)據。
</p>
<p>我們各用一個例子來說明這兩種用途。 </p>
<p>實例-用FIFO復制輸出流 </p>
<p>FIFO可被用于復制串行管道命令之間的輸出流,于是也就不需要寫數(shù)據到中間磁盤
</p>
<p>文件中(類似于使用管道以避免中間磁盤文件)。管道只能用于進程間的線性連接
</p>
<p>,然而,因為FIFO具有名字,所以它可用于非線性連接。 </p>
<p>考慮這樣一個操作過程,它需要處理一個經過過濾的輸入流兩次。圖14.10表示了
</p>
<p>這種安排。 </p>
<p>使用FIFO以及Unix程序tee(1),我們就可以實現(xiàn)這樣的過程而無需使用臨時文件。
</p>
<p>(tee程序將其標準輸入同時復制到其標準輸出以及其命令行中包含的命名文件中
</p>
<p>。) </p>
<p>mkfifo fifo1 </p>
<p>prog3<fifo1& </p>
<p>prog1<infile 1 tee fifo1 | prog2 </p>
<p>圖14.10 處理一個經過過濾的輸入流兩次 </p>
<p>我們創(chuàng)建FIFO,然后在后臺啟動prog3,它從FIFO讀數(shù)據。然后,我們啟動progl,用
</p>
<p>tee將其輸出發(fā)送到FIFO和prog2。圖14.11 顯示了有關安排。 </p>
<p>圖14.11 使用FIFO和tee將一個流發(fā)送到兩個不同的進程 </p>
<p>實例-客戶-服務者使用FIFO進行通信 </p>
<p>FIFO的另一個應用是在客戶和服務者之間傳送數(shù)據。如果有一個服務者,它與很多
</p>
<p>客戶有關,每個客戶都可將其請求寫到一個該服務者創(chuàng)建的眾所周知的FIFO中。(
</p>
<p>"眾所周知"的意思是;所有需與服務者聯(lián)系的客戶都知道該FIFO的路徑名。)圖1
</p>
<p>4.12顯示了這種安排。因為對于該FIFO有多個寫進程,客戶發(fā)送給服務者的請求其
</p>
<p>長度要小于PIPE_BUF字節(jié)。這樣就能避免客戶各次寫之間的穿插。 </p>
<p>圖14.12 客戶用-FIFO向服務者發(fā)送請求 </p>
<p>在這種類型的客戶-服務者通信中使用FIFO的問題是:服務者如何將回答送回各個
</p>
<p>客戶。不能使用單個FIFO的問題是:服務者如何將回答送回各個客戶。因為服務者
</p>
<p>會發(fā)出對各個客戶請求的響應,而請求者卻不可能知道什么時候去讀才能恰恰得到
</p>
<p>對它的響應。一種解決方法是每個客戶都在其請求中發(fā)送其進程ID。然后服務者為
</p>
<p>每個客戶創(chuàng)建一個FIFO,所使用的路徑名是以客戶的進程ID為基礎的。例如,服務
</p>
<p>者可以用名字/tmp/serv1.xxxxx創(chuàng)建FIFO,其中xxxxx被代換成客戶的進程ID。圖
</p>
<p>14.13顯示了這種安排。 </p>
<p>圖14.13 客戶-服務者用FIFO進行通信 </p>
<p>這種安排可以工作,但也有一些不足之處。其中之一是服務者不能判斷一個客戶是
</p>
<p>否崩潰終止,這就使得客戶專用的FIFO會遺留在文件系統(tǒng)中。另一個是服務者必須
</p>
<p>捕捉SIGPIPE信號,因為客戶在發(fā)送一個請求后沒有讀取響應就可能終止,于是留
</p>
<p>下一個有寫進程(服務者)而無讀進程的客戶專用FIFO。 </p>
<p>按照圖14.13中的安排,如果服務者以只讀方式打開眾所周知的FIFO(因為它只需
</p>
<p>讀該FIFO),則每次客戶數(shù)從1變成0,服務者就將在FIFO中讀到一個文件結束標記
</p>
<p>。為使服務者免于處理這種情況,一種常見的技巧是使服務者以讀-寫方式打開該
</p>
<p>FIFO(見練習14.10)。 </p>
<p>14.6 系統(tǒng)V IPC </p>
<p>三種系統(tǒng)V IPC-消息隊列、信號量以及共享存儲器之間有很多相似之處。在以下各
</p>
<p>節(jié)說明這些IPC的各自特殊功能之前,我們先在本節(jié)介紹它們的類似特征。
</p>
<p>這三種IPC源自于1970年的一種稱為"Columbus Unix"的Unix內部版本。后
</p>
<p>來 </p>
<p>它們被加到SV上。 </p>
<p>14.6.1 標識符和關鍵字 </p>
<p>每個在核中的IPC結構(消息隊列、信號量或共享存儲段)都用一個非負整數(shù)的標
</p>
<p>識符加以引用。例如,為了對一個消息隊列發(fā)送或取消息,我們只需知道其隊列標
</p>
<p>識符。與文件描述符不同,IPC標識符不是小的整數(shù)。當一個IPC結構被創(chuàng)建,以后
</p>
<p>又被刪除時,與這種結構相關的標識符連續(xù)加1,直至達到一個整型數(shù)的最大正值
</p>
<p>,然后又回轉到0。(即使在IPC結構被刪除后也記住該值,每次使用此結構時則增
</p>
<p>1,該值被稱為"槽使用順序號"。它在ipc_perm結構中,我們將在下一節(jié)說明此結
</p>
<p>構。) </p>
<p>無論何時在創(chuàng)建IPC結構時(調用msgget、semget或shmget),都應指定一個key(
</p>
<p>關鍵字),關鍵字的數(shù)據類型由系統(tǒng)規(guī)定為key_t,通常在頭文件<sys/types.h>中
</p>
<p>被規(guī)定為長整型。關鍵字由核變換成標識符。 </p>
<p>有多種方法使客戶和服務者在同一IPC結構上會合。 </p>
<p>1. 服務者可以指定關鍵字IPC_PRIVATE創(chuàng)建一個新IPC結構,將返回的標識符存放
</p>
<p>在某處(例如一個文件)以便客戶取用。關鍵字IPC_PRIVATE保證服務者創(chuàng)建一個
</p>
<p>新IPC結構。這種技術的缺點是;服務者要將整型標識符寫到文件中,然后客戶在
</p>
<p>此后又要讀文件取得此標識符。 </p>
<p>IPC_PRIVATE關鍵字也可用于父、子關系進程。父進程指定IPC_PRIVATE創(chuàng)建一個新
</p>
<p>IPC結構,所返回的標識符在fork后可由子進程使用。子進程可將此標識符作為ex
</p>
<p>ec函數(shù)的一個參數(shù)傳給一個新程序。 </p>
<p>2.
在一個公用頭文件中定義一個客戶和服務者都認可的關鍵字。然后服務者指定
</p>
<p>此關鍵字創(chuàng)建一個新的IPC結構。這種方法的問題是該關鍵字可能已與一個IPC結構
</p>
<p>相結合,在此情況下,get函數(shù)(msgget,semget或shmget)出錯返回。服務者必須
</p>
<p>處理這一錯誤,刪除已存在的IPC結構,然后試著再創(chuàng)建它。 </p>
<p>3. 客戶和服務者認同一個路徑名和課題ID(課題ID是在0~255之間的字符值),然
</p>
<p>后調用函數(shù)ftok將這兩個值變換為一個關鍵字。(函數(shù)ftok在手冊頁stdipc(3)
</p>
<p>中說明。)然后在方法2中使用此關鍵字。ftok提供的唯一服務就是由一個路徑名
</p>
<p>和課題ID產生一個關鍵字。因為客戶和服務者典型地至少共享一個頭文件,所以一
</p>
<p>個比較簡寫的方法是避免使用ftok,而只是在該頭文件中存放一個大家都知道的關
</p>
<p>鍵字。這樣做還避免了使用另一個函數(shù)。 </p>
<p>三個get函數(shù)(msgget,semget和shmget)都有兩個類似的參數(shù)key和一個整型的fl
</p>
<p>ag。如若滿足下列條件,則創(chuàng)建一個新的IPC結構(通常由服務者創(chuàng)建);
</p>
<p>1. key是IPC_PRIVATE,或 </p>
<p>2. key當前末與特定類型的IPC結構相結合,flag中指定了IPC_CREAT位。為訪問現(xiàn)
</p>
<p>存的隊列(通常由客戶進行),key必須等于創(chuàng)建該隊列時所指定的關鍵字,并且
</p>
<p>不應指定IPC_CREAT。 </p>
<p>注意,為了訪問一個現(xiàn)存隊列,決不能指定IPC_PRIVATE作為關鍵字因為這是
</p>
<p>一個特殊的鍵值,它總是用于創(chuàng)建一個新隊列。為了訪問一個用IPC_PRIVATE關鍵
</p>
<p>字創(chuàng)建的現(xiàn)存隊列,一定要知道與該隊列相結合的標識符,然后在其它IPC調用中
</p>
<p>(例如msgsnd、msgrcv)使用該標識符。 </p>
<p>如果我們希望創(chuàng)建一個新IPC結構,保證不是引用具有同一標識符的一個現(xiàn)行IPC結
</p>
<p>構,那么我們必須在flag中同時指定IPC_CREAT和IPC_EXCL位。這樣做了以后,如
</p>
<p>果IPC結構已經存在就會造成出錯,返回EEXIST(這與指定了O_CREAT和O_EXCL標志
</p>
<p>的open相類似)。 </p>
<p>14.6.2 許可權結構 </p>
<p>系統(tǒng)V IPC為每一個IPC結構設置了一個ipc_perm結構。該結構規(guī)定了許可權和屬主
</p>
<p>。 </p>
<p>struct ipc_perm { </p>
<p>uid_t uid; 所有者的有效用戶id </p>
<p>gid_t gid; 所有者的有效組id </p>
<p>uid_t cuid; 創(chuàng)建者的有效用戶id </p>
<p>gid_t cgid; 創(chuàng)建者的有效組id </p>
<p>mode_t mode; 訪問方式 </p>
<p>ulong seq; 槽使用序列號 </p>
<p>key_t key; 關鍵字 </p>
<p>} </p>
<p>在創(chuàng)建IPC結構時,除seq以外的所有字段都賦初值。以后,可以調用msgctl、sem
</p>
<p>ctl或shmctl修改uid、gid和mode字段。為了改變這些值,調用進程必須或者是IP
</p>
<p>C結構的創(chuàng)建者,或者是超級用戶。更改這些字段類似于對文件調用chown和chmod
</p>
<p>。 </p>
<p>mode字段的值類似于在圖4.4中所示的值,但是對于任何IPC結構都不存在執(zhí)行許可
</p>
<p>權。另外,消息隊列和共享存儲使用術語"讀"和"寫",而信號量則用術語"讀"和"
</p>
<p>更改"。圖14.14中對每種IPC說明了六種許可權。 </p>
<p>圖14.14 系統(tǒng)V IPC許可權 </p>
<p>14.6.3 結構限制 </p>
<p>三種形式的系統(tǒng)V IPC都有我們可能會遇到的內在限制。這些限制的大多數(shù)是可以
</p>
<p>通過重新配置而加以更改的。當敘說每種IPC時,我們都會指出它的限制
</p>
<p>在SVR4中,這些值,以及它們的最小、最大值都在文件/etc/conf/cf.d/mtun
</p>
<p>e中。 </p>
<p>14.6.4 優(yōu)、缺點 </p>
<p>系統(tǒng)V IPC的主要問題是;IPC結構是在系統(tǒng)范圍內起作用的,沒有訪問計數(shù)。例如
</p>
<p>,如若我們創(chuàng)建了一個消息隊列,在該隊列中放入了幾則消息,然后終止,但是該
</p>
<p>消息隊列及其內容并不被刪除。它們余留在系統(tǒng)中直至;由某個進程調用msgrcv或
</p>
<p>msgctl讀消息或刪除消息隊列;某個進程執(zhí)行ipcrm(1)命令刪除消息隊列;或由正
</p>
<p>在再啟動的系統(tǒng)刪除消息隊列。將此與管道pipe相比,那么當最后一個訪問管道的
</p>
<p>進程終止時,管道就被完全地刪除了。對于FIFO而言雖然當最后一個引用FIFO的進
</p>
<p>程終止時其名字仍保留在系統(tǒng)中,直至顯式地刪除它,但是留在FIFO中的數(shù)據卻在
</p>
<p>此時全部刪除。 </p>
<p>系統(tǒng)V IPC的另一個問題是;這些IPC結構并不按名字為文件系統(tǒng)所知。我們不能用
</p>
<p>第3、4章中所述的函數(shù)來存取它們或修改它們的特性。為了支持它們不得不增加了
</p>
<p>十個以上全新的系統(tǒng)調用(msgget、semop、shmat等)。我們不能用ls命令見到它
</p>
<p>們,不能用rm命令刪除它們,不能用chmod命令更改它們的存取權。于是,也不得
</p>
<p>不增加了全新的命令ipcs和 ipcrm。 </p>
<p>因為這些IPC不使用文件描述符,所以我們不能對它們使用多路轉接I/O函數(shù):sel
</p>
<p>ect和poll。這就使得難于在一次使用多個IPC結構,難于用文件或設備I/O來使用
</p>
<p>任何一個IPC結構。例如,沒有某種形式的忙一等待循環(huán),我們就不能使一個服務
</p>
<p>者等待一個消息放在兩個消息隊列中的任一個上。 </p>
<p>Andrade、Carges以及Kovach[1989]對使用系統(tǒng)V IPC的一個實際事務處理系統(tǒng)進行
</p>
<p>了綜述。他們認為系統(tǒng)V IPC使用的名字空間(標識符)是一個優(yōu)點而不是我們在
</p>
<p>前面所說的問題,理由是使用標識符使一個進程只要使用單個函數(shù)調用(msgsnd)
</p>
<p>就能將一個消息發(fā)送到一個隊列,而其它形式的IPC則通常要求open,write和clos
</p>
<p>e。這種論據是不真實的。為了避免使用一個關鍵字和調用msgget,客戶總要以某
</p>
<p>種方式獲得服務者隊列的標識符。分派給特定隊列的標識符取決于在創(chuàng)建該隊列時
</p>
<p>,有多少消息隊列已經存在,取決于自核自舉以來,在核中將分配給新隊列的表項
</p>
<p>已經使用了多少次。這是一個動態(tài)值,不能被猜測或事先存放在一個頭文件中。正
</p>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -