Linux内核中无名管道pipe和有名管道fifo的分析
1、管道(pipe)
管道是進(jìn)程間通信的主要手段之一。一個管道實際上就是個只存在于內(nèi)存中的文件,對這個文件的操作要通過兩個已經(jīng)打開文件進(jìn)行,它們分別代表管道的兩端。管道是一種特殊的文件,它不屬于某一種文件系統(tǒng),而是一種獨立的文件系統(tǒng),有其自己的數(shù)據(jù)結(jié)構(gòu)。根據(jù)管道的適用范圍將其分為:無名管道和命名管道。
●??? 無名管道
主要用于父進(jìn)程與子進(jìn)程之間,或者兩個兄弟進(jìn)程之間。在linux系統(tǒng)中可以通過系統(tǒng)調(diào)用建立起一個單向的通信管道,且這種關(guān)系只能由父進(jìn)程來建立。因此,每個管道都是單向的,當(dāng)需要雙向通信時就需要建立起兩個管道。管道兩端的進(jìn)程均將該管道看做一個文件,一個進(jìn)程負(fù)責(zé)往管道中寫內(nèi)容,而另一個從管道中讀取。這種傳輸遵循“先入先出”(FIFO)的規(guī)則。
●??? 命名管道
命名管道是為了解決無名管道只能用于近親進(jìn)程之間通信的缺陷而設(shè)計的。命名管道是建立在實際的磁盤介質(zhì)或文件系統(tǒng)(而不是只存在于內(nèi)存中)上有自己名字的文件,任何進(jìn)程可以在任何時間通過文件名或路徑名與該文件建立聯(lián)系。為了實現(xiàn)命名管道,引入了一種新的文件類型——FIFO文件(遵循先進(jìn)先出的原則)。實現(xiàn)一個命名管道實際上就是實現(xiàn)一個FIFO文件。命名管道一旦建立,之后它的讀、寫以及關(guān)閉操作都與普通管道完全相同。雖然FIFO文件的inode節(jié)點在磁盤上,但是僅是一個節(jié)點而已,文件的數(shù)據(jù)還是存在于內(nèi)存緩沖頁面中,和普通管道相同。
2、環(huán)形緩沖區(qū)
每個管道只有一個頁面作為緩沖區(qū),該頁面是按照環(huán)形緩沖區(qū)的方式來使用的。這種訪問方式是典型的“生產(chǎn)者——消費者”模型。當(dāng)“生產(chǎn)者”進(jìn)程有大量的數(shù)據(jù)需要寫時,而且每當(dāng)寫滿一個頁面就需要進(jìn)行睡眠等待,等待“消費者”從管道中讀走一些數(shù)據(jù),為其騰出一些空間。相應(yīng)的,如果管道中沒有可讀數(shù)據(jù),“消費者” 進(jìn)程就要睡眠等待,具體過程如下圖所示。
2.1環(huán)形緩沖區(qū)實現(xiàn)原理
環(huán)形緩沖區(qū)是嵌入式系統(tǒng)中一個常用的重要數(shù)據(jù)結(jié)構(gòu)。一般采用數(shù)組形式進(jìn)行存儲,即在內(nèi)存中申請一塊連續(xù)的線性空間,可以在初始化的時候把存儲空間一次性分配好。只是要模擬環(huán)形,必須在邏輯上把數(shù)組的頭尾相連接。只要對數(shù)組最后一個元素進(jìn)行特殊的處理——訪問尾部元素的下一元素時,重新回到頭部元素。對于從尾部回到頭部只需模緩沖長度即可(假設(shè)maxlen為環(huán)形緩沖的長度,當(dāng)讀指針read指向尾部元素時,只需執(zhí)行read=read%maxlen即可使read回到頭部元素)。
2.2讀寫操作
環(huán)形緩沖區(qū)要維護(hù)寫端(write)和讀端(read)兩個索引。寫入數(shù)據(jù)時,必須先確保緩沖區(qū)沒有滿,然后才能將數(shù)據(jù)寫入,最后將write指針指向下一個元素;讀取數(shù)據(jù)時,首先要確保緩沖區(qū)不為空,然后返回read指針對應(yīng)得元素,最后使read指向下一個元素的位置。讀寫操作偽代碼:
2.3判斷“滿”和“空”
當(dāng)read和write指向同一個位置時環(huán)形緩沖區(qū)為空或滿。為了區(qū)別環(huán)滿和空,當(dāng)read和write重疊的時候環(huán)空;而當(dāng)write比read快,追到距離read還有一個元素間隔的時候,就認(rèn)為環(huán)已經(jīng)滿了。環(huán)形緩沖區(qū)原理圖如圖3所示。
4.linux內(nèi)核中pipe的讀寫實現(xiàn)
Linux內(nèi)核中采用struct pipe_inode_info結(jié)構(gòu)體來描述一個管道。
其中,當(dāng)pipe為空/滿時,采用等待隊列,該隊列使用自旋鎖進(jìn)行保護(hù)。
用struct Pipe_buffer數(shù)據(jù)結(jié)構(gòu)描述pipe的緩沖(buffer)
本文重點針對pipe實現(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ù)的讀出放在一個死循環(huán)中,整個for循環(huán)中的代碼均屬于臨界區(qū),需要互斥鎖進(jìn)行保護(hù)。
有以下幾種情況才會退出:
▲??? 完成數(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ù),這時,可以喚醒等待隊列中的睡眠的寫進(jìn)程。
如果沒有退出,或者成功讀取數(shù)據(jù),讀進(jìn)程會主動調(diào)用pipe_wait函數(shù)進(jìn)行睡眠等待,直到有writer進(jìn)程寫入數(shù)據(jù)并將其喚醒。
當(dāng)進(jìn)程從臨界區(qū)中退出后會釋放互斥鎖。
最后,為了防止reader進(jìn)程是因為收到信號量而退出,再給睡眠的writer進(jìn)程一次機會,檢查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)程會隨時進(jìn)行寫操作。
FIFO文件的操作方法只有open方法(具體實現(xiàn)在fs/fifo.c)。但是,這并不是fifo文件真正的操作方法,其真正的讀寫方法是根據(jù)不同的打開方式而決定的。
FIFO文件的打開操作
第一次打開fifo文件的進(jìn)程調(diào)用fifo_open時,該命名管道的緩沖頁面還沒有分配,
因此43行中alloc_pipe_info()函數(shù)會被執(zhí)行。
分配所需要的pipe_inode_info數(shù)據(jù)結(jié)構(gòu)和緩沖頁面。以后打開該文件的進(jìn)程會跳過該部分。
Fifo可以以“只讀”、“只寫”、“讀寫”三種方式打開。另外,open系統(tǒng)調(diào)用中有flag參數(shù),如果調(diào)用fifo_open的進(jìn)程開始時設(shè)置了flag中的O_NONBLOCK參數(shù)為1,則在打開的過程中無論是否可以正常打開,進(jìn)程都不能進(jìn)入睡眠,必須立即返回。下面具體分析每個打開方式的不同操作。
◆??? 以“只讀”方式打開。即命名管道的讀端的幾種情況:
a)????? 如果命名管道的寫端已經(jīng)打開,那么管道的創(chuàng)建就完成了。這時,一般寫端(生產(chǎn)者)一般都在睡眠,因此要調(diào)用wake_up_partner()將其喚醒。
b)???? 如果寫端沒有打開,而且設(shè)置了O_NONBLOCK標(biāo)志,此時盡管讀端已經(jīng)打開,但是沒有完成管道的打開,由于進(jìn)程要求不能等待,因此必須立即返回。
c)????? 寫讀沒有打開,但是沒有設(shè)置O_NONBLOCK標(biāo)志,讀進(jìn)程調(diào)用wait_for_partner()函數(shù),進(jìn)入睡眠狀態(tài)等待寫讀打開后將其喚醒。
◆??? 以“只寫”方式打開。即打開命名管道寫端的幾種情況:
a)????? 如果命名管道的讀端沒有打開,并且設(shè)置了O_NONBLOCK標(biāo)志,寫端進(jìn)程就要直接跳轉(zhuǎn)到err處(判斷是否有讀進(jìn)程或?qū)戇M(jìn)程在睡眠,如果沒有就釋放pipe_inode_info)執(zhí)行。否則,讓filp的f_op指向write_pipefifo_fops方法。然后管道的寫進(jìn)程計數(shù)加1
b)???? 如果命名管道的讀端已經(jīng)打開,那么寫端就完成了命名管道的打開。此時,讀端一般都在睡眠等待,應(yīng)該調(diào)用wake_up_partner()將其喚醒。
c)????? 如果命名管道讀端沒有打開,那么寫端就要調(diào)用wait_for_partner()進(jìn)入睡眠等待,直到讀端打開,將其喚醒之后才能返回。
◆??? 以“讀寫”方式打開
讀寫的方式打開命名管道,相當(dāng)于同一個進(jìn)程打開了命名管道的兩端,因此不需要等待。但是,也有可能已經(jīng)有進(jìn)程已經(jīng)打開了某一端正在睡眠等待,因此,任意一端第一次打開,就喚醒了正在睡眠的進(jìn)程。這種打開方式下,真正的操作方法是rdwr_pipefifo_fops。
命名管道一旦建立,以后的讀寫以及關(guān)閉都與普通管道相同。盡管FIFO文件的inode節(jié)點是在磁盤上,但是數(shù)據(jù)只是存在于內(nèi)存緩存中,與普通管道相同。
分析完FIFO文件的不同打開方式之后,接下來分析各自對應(yīng)的操作方法。具體實現(xiàn)在
Fs/pipe.c文件中。
首先,我們可以看到在三個操作方法中l(wèi)lseek都調(diào)用的是no_llseek。
從代碼中,可以發(fā)現(xiàn)no_llseek并沒有做任何事情,只是在被調(diào)用的時候返回錯誤代碼。也就是在FIFO文件中是不能使用seek方法的,對文件的讀寫只能根據(jù)先進(jìn)先出的順序進(jìn)行訪問。
接下來,在read_pipefifo_fops和write_pipefifo_fops中分別調(diào)用了bad_pipe_w和bad_pipe_r函數(shù):
從代碼中,可以看到它們也只是返回錯誤碼,也就是說在pipe中讀端寫操作是禁止的,在寫端讀操作同樣也是禁止的。
總結(jié)
以上是生活随笔為你收集整理的Linux内核中无名管道pipe和有名管道fifo的分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cve-2015-0569 安卓手机提权
- 下一篇: 搭建一个日常好用的linux系统