跟著iMX28x開發套件學linux-067
七、linux應用編程之五:管道
進程間通信有多種方式,管道是其中一種。管道分為匿名管道和命名管道,匿名管道僅用於父子進程之間通信,沒有實際文件。而命名管道可以實現任意進程間的通信,在系統中需要創建一個fifo文件作為管道。
管道的理解:無論是匿名管道還是命名管道,都可以把管道看做一個文件,進程A給這個文件寫數據,進程B從這個文件讀數據,那麽進程A就可以給進程B傳輸數據了。而重點在於,匿名管道沒有實際文件要怎麽實現,還有如何保證進程A,B之間的通信同步(即進程A發送數據的時候,是不是進程B需要這個數據的時候)。
匿名管道
1) 創建管道:匿名管道沒有實際的文件,想象出一個虛擬的文件,父進程給這個文件寫數據,子進程從這個文件讀數據,完成父子進程間的通信。既然想象成文件,那讀寫數據就需要文件描述符,但是這個文件又是虛擬的,無法通過
PS:代碼中文件描述符用了int pipefd[2],一般文件描述符都是int fd,實際上pipefd[2]並不是兩個文件的意思,而是一個文件的兩個端口,讀端口和寫端口。linux系統定義pipefd[1]是寫端口,pipefd[0]是讀端口,匿名管道實際模型更加接近於下圖:
2) 將數據寫入匿名管道:父子進程都可以是數據的接收方或者數據的發送方,但是不能同時是數據的發送方和接收方,所以匿名通道實際上是一個半雙工通信。
代碼說明:在父進程中先關閉輸入端口pipefd[0],接著在pipefd[1]寫入要發送的數據,這裏的數據是運行程序時的第二個參數。發送完成之後關閉輸出端口pipefd[1],最後給子進程發送wait()。
3) 從匿名管道中讀取數據:上面程序中父進程已經把數據寫入了匿名通道,子進程應當把管道中的數據讀取出來,完成一次進程通信。子進程中先關閉匿名通道的輸出端口pipe[1],然後讀取匿名通道中的內容,判斷是否讀取完畢,然後將讀取到的內容打印到屏幕上,觀察與運行程序時的第二個參數是否相同。代碼如下:
代碼說明:在讀取數據的時候,應該一個字節一個字節的讀,當read()函數返回-1的時候代表數據已經全部讀取。
4) 通信同步問題:有四種特殊情況需要註意。
① 寫端開啟,讀端開啟,當讀端讀出數據時,若匿名管道內沒有數據,讀端進程將在read()處阻塞。
② 寫段關閉,讀端開啟,當讀端讀出數據時,匿名管道中的數據被全部讀出,讀端進程的read()將會返回0
③ 寫端開啟,讀端關閉,若寫端進程試圖運行write()函數時,寫端進程將會收到信號SIGPIPE,並終止。
④ 寫端開啟,讀端開啟,但是讀端沒有進行read(),而寫端一直運行write(),等到匿名管道被寫滿之後,寫端進程將在write()處阻塞。
5) 匿名管道完整實驗代碼:
程序運行結果:
命名管道
1) 創建通道:命名通道是有具體文件的fifo,可以時間無關進程間的通信,所以創建命名通道的過程跟創建文件是一樣的。在創建命名通道之前,要用acess(fifo_name, F_OK);函數檢查通道文件是否存在,若存在返回0,不存在返回-1,根據返回的結果決定是否創建命名管道。創建過程代碼如下:
2) 寫端將數據寫進命名管道:跟寫數據進文件一樣,寫入數據之前要先打開文件。需要註意的是,當使用只寫(O_WRONLY)模式打開命名管道時,若沒有進程使用只讀(O_RDONLY)模式打開命名管道,則用只寫模式打開命名管道的進程將會阻塞,直到有進程使用只讀模式打開命名管道。當然,如果一個進程使用讀寫(O_RDWR)模式打開命名管道時,不會阻塞。打開命名管道以及寫數據進管道的代碼如下:
3) 讀端從命名管道讀取數據:與從文件讀數據一樣,讀取數據之前要先打開文件,然後寫入,代碼如下:
4) 完整實驗代碼:進程A創建命名管道,並復制一個文件的內容到管道。進程B從管道中讀取數據,然後寫入到另一個文件中。最後通過MD5校驗查看兩個代碼的內容是否相同。
進程A代碼:
進程B代碼:
程序運行結果,先運行進程A,再運行進程B:
說明:運行進程A之前先創建進程A要用到的文件a.bin(在程序中文件的打開方式沒寫O_CREAT),然後運行進程A,註意用的指令是./processA a.bin &,表示程序後臺運行,因為以只寫模式打開命名管道時,進程會阻塞,如果不後臺運行,這個終端將無法操作。接著運行進程B,運行進程B之後,進程A結束阻塞狀態,從a.bin中讀取數據,然後寫到管道中,進程B從管道中讀取數據,然後寫到b.bin中。運行結束後,運行md5sum指令,查看a.bin和b.bin的MD5碼是否一致。
跟著iMX28x開發套件學linux-067