catalogue
1. pipe匿名管道 2. named pipe(FIFO)有名管道
1. pipe匿名管道
管道是Linux中很重要的一種通信方式,是把一個程序的輸出直接連接到另一個程序的輸入,常說的管道多是指無名管道,無名管道只能用於具有親緣關系的進程之間,這是它與有名管道的最大區別。管道是Linux支持的最初Unix IPC形式之一,具有以下特點
1. 管道是半雙工的,數據只能向一個方向流動; 需要雙方通信時,需要建立起兩個管道 2. 只能用於父子進程或者兄弟進程之間(具有親緣關系的進程) 3. 單獨構成一種獨立的文件系統: 管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中 4. 數據的讀出和寫入: 一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩沖區的末尾,並且每次都是從緩沖區的頭部讀出數據
0x1: 管道的讀寫規則
管道兩端可分別用描述字fd[0]、fd[1]來描述,需要注意的是,管道的兩端是固定了任務的
1. 即一端只能用於讀,由描述字fd[0]表示,稱其為管道讀端 2. 另一端則只能用於寫,由描述字fd[1]來表示,稱其為管道寫端
如果試圖從管道寫端讀取數據,或者向管道讀端寫入數據都將導致錯誤發生。一般文件的I/O函數都可以用於管道,如close、read、write等等
1. 從管道中讀取數據
1. 如果管道的寫端不存在,則認為已經讀到了數據的末尾,讀函數返回的讀出字節數為0 2. 當管道的寫端存在時,如果請求的字節數目大於PIPE_BUF,則返回管道中現有的數據字節數,如果請求的字節數目不大於PIPE_BUF,則返回管道中現有數據字節數
pycode
import os, sys if __name__ == '__main__': print "The child will write text to a pipe and " print "the parent will read the text written by child..." # file descriptors r, w for reading and writing r, w = os.pipe() processid = os.fork() if processid: # This is the parent process # Closes file descriptor w os.close(w) r = os.fdopen(r) print "Parent reading" str = r.read() print "text =", str sys.exit(0) else: # This is the child process os.close(r) w = os.fdopen(w, 'w') print "Child writing" w.write("Text written by child...") w.close() print "Child closing" sys.exit(0)
2. 向管道中寫入數據
1. 向管道中寫入數據時,linux將不保證寫入的原子性,管道緩沖區一有空閑區域,寫進程就會試圖向管道寫入數據。如果讀進程不讀走管道緩沖區中的數據,那么寫操作將一直阻塞 2. 只有在管道的讀端存在時,向管道中寫入數據才有意義。否則,向管道中寫入數據的進程將收到內核傳來的SIFPIPE信號,應用程序可以處理該信號,也可以忽略(默認動作則是應用程序終止)
pycode: 寫端對讀端存在的依賴性
import os, sys if __name__ == '__main__': print "The child will write text to a pipe and " print "the parent will read the text written by child..." # file descriptors r, w for reading and writing r, w = os.pipe() processid = os.fork() if processid: # This is the parent process os.close(w) os.close(r) sys.exit(0) else: # This is the child process os.close(r) w = os.fdopen(w, 'w') print "Child writing" w.write("Text written by child...") w.close() print "Child closing" sys.exit(0)
則輸出結果為: Broken pipe,原因就是該管道以及它的所有fork()產物的讀端都已經被關閉。因此,在向管道寫入數據時,至少應該存在某一個進程,其中管道讀端沒有被關閉,否則就會出現上述錯誤(管道斷裂,進程收到了SIGPIPE信號,默認動作是進程終止)
從原理上,管道利用fork機制建立,從而讓兩個進程可以連接到同一個PIPE上。最開始的時候,上面的兩個箭頭都連接在同一個進程Process 1上(連接在Process 1上的兩個箭頭)。當fork復制進程的時候,會將這兩個連接也復制到新的進程(Process 2)。隨后,每個進程關閉自己不需要的一個連接 (兩個黑色的箭頭被關閉; Process 1關閉從PIPE來的輸入連接,Process 2關閉輸出到PIPE的連接),這樣,剩下的紅色連接就構成了如上圖的PIPE
0x2: 管道應用實例
1. 用於shell
管道可用於輸入輸出重定向,它將一個命令的輸出直接定向到另一個命令的輸入,當在某個shell程序(Bourne shell或C shell等)鍵入who │ wc -l后,相應shell程序將創建who以及wc兩個進程和這兩個進程間的管道。考慮下面的命令行
kill -l | grep SIGRTMIN 30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1 34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5 38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9 42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13 46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14
2. 用於具有親緣關系的進程間通信
0x3: 管道實現細節
在 Linux 中,管道的實現並沒有使用專門的數據結構,而是借助了文件系統的file結構和VFS的索引節點inode。通過將兩個 file 結構指向同一個臨時的 VFS 索引節點,而這個 VFS 索引節點又指向一個物理頁面而實現的
有兩個 file 數據結構,但它們定義文件操作例程地址是不同的,其中一個是向管道中寫入數據的例程地址,而另一個是從管道中讀出數據的例程地址。這樣,用戶程序的系統調用仍然是通常的文件操作,而內核卻利用這種抽象機制實現了管道這一特殊操作
0x4: 管道的局限性
管道的主要局限性正體現在它的特點上
1. 只支持單向數據流 2. 只能用於具有親緣關系的進程之間 3. 沒有名字 4. 管道的緩沖區是有限的(管道制存在於內存中,在管道創建時,為緩沖區分配一個頁面大小) 5. 管道所傳送的是無格式字節流,這就要求管道的讀出方和寫入方必須事先約定好數據的格式,比如多少字節算作一個消息(或命令、或記錄)等等
Relevant Link:
https://linux.die.net/man/2/pipe https://www.cnblogs.com/chengmo/archive/2010/10/21/1856577.html http://ryanstutorials.net/linuxtutorial/piping.php http://hwchiu.logdown.com/posts/1733-c-pipe https://www.tutorialspoint.com/python/os_pipe.htm
2. named pipe(FIFO)有名管道
為了解決飛親屬進程間通信這一問題,Linux提供了FIFO方式連接進程。FIFO又叫做命名管道(named PIPE)
FIFO (First in, First out)為一種特殊的文件類型,它在文件系統中有對應的路徑。當一個進程以讀(r)的方式打開該文件,而另一個進程以寫(w)的方式打開該文件,那么內核就會在這兩個進程之間建立管道,所以FIFO實際上也由內核管理,不與硬盤打交道
。之所以叫FIFO,是因為管道本質上是一個先進先出的隊列數據結構,最早放入的數據被最先讀出來,從而保證信息交流的順序。FIFO只是借用了文件系統(file system,命名管道是一種特殊類型的文件,因為Linux中所有事物都是文件,它在文件系統中以文件名的形式存在)來為管道命名。寫模式的進程向FIFO文件中寫入,而讀模式的進程從FIFO文件中讀出。當刪除FIFO文件時,管道連接也隨之消失
0x1: 有名管道的操作規則
1. 有名管道的打開規則
1. 如果當前打開操作是為讀而打開FIFO時 1) 若已經有相應進程為寫而打開該FIFO,則當前打開操作將成功返回 2) 否則,可能阻塞直到有相應進程為寫而打開該FIFO(當前打開操作設置了阻塞標志) 3) 或者,成功返回(當前打開操作沒有設置阻塞標志) 2. 如果當前打開操作是為寫而打開FIFO時 1) 如果已經有相應進程為讀而打開該FIFO,則當前打開操作將成功返回 2) 否則,可能阻塞直到有相應進程為讀而打開該FIFO(當前打開操作設置了阻塞標志) 3) 或者,返回ENXIO錯誤(當前打開操作沒有設置阻塞標志)
2. 有名管道的讀寫規則
1. 從FIFO中讀取數據: 如果一個進程為了從FIFO中讀取數據而阻塞打開FIFO,那么稱該進程內的讀操作為設置了阻塞標志的讀操作。 1) 如果有進程寫打開FIFO,且當前FIFO內沒有數據,則對於設置了阻塞標志的讀操作來說,將一直阻塞。對於沒有設置阻塞標志讀操作來說則返回-1,當前errno值為EAGAIN,提醒以后再試 2) 對於設置了阻塞標志的讀操作說,造成阻塞的原因有兩種,解阻塞的原因則是FIFO中有新的數據寫入,不論信寫入數據量的大小,也不論讀操作請求多少數據量 2.1) 當前FIFO內有數據,但有其它進程在讀這些數據 2.2) 另外就是FIFO內沒有數據 3) 讀打開的阻塞標志只對本進程第一個讀操作施加作用,如果本進程內有多個讀操作序列,則在第一個讀操作被喚醒並完成讀操作后,其它將要執行的讀操作將不再阻塞,即使在執行讀操作時,FIFO中沒有數據也一樣(此時,讀操作返回0) 4) 如果沒有進程寫打開FIFO,則設置了阻塞標志的讀操作會阻塞 2. 向FIFO中寫入數據: 如果一個進程為了向FIFO中寫入數據而阻塞打開FIFO,那么稱該進程內的寫操作為設置了阻塞標志的寫操作 1) 對於設置了阻塞標志的寫操作 1.1) 當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。如果此時管道空閑緩沖區不足以容納要寫入的字節數,則進入睡眠,直到當緩沖區中能夠容納要寫入的字節數時,才開始進行一次性寫操作 1.2) 當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。FIFO緩沖區一有空閑區域,寫進程就會試圖向管道寫入數據,寫操作在寫完所有請求寫的數據后返回 2) 對於沒有設置阻塞標志的寫操作 2.1) 當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。在寫滿所有FIFO空閑緩沖區后,寫操作返回 2.2) 當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。如果當前FIFO空閑緩沖區能夠容納請求寫入的字節數,寫完后成功返回;如果當前FIFO空閑緩沖區不能夠容納請求寫入的字節數,則返回EAGAIN錯誤,提醒以后再寫
pycode1
from subprocess import * import os if __name__ == '__main__': FIFO_PATH = '/tmp/my_fifo' if os.path.exists(FIFO_PATH): os.unlink(FIFO_PATH) if not os.path.exists(FIFO_PATH): os.mkfifo(FIFO_PATH) my_fifo = open(FIFO_PATH, 'w+') print "my_fifo:", my_fifo pipe = Popen('/bin/date', shell=False, stdin=PIPE, stdout=my_fifo, stderr=PIPE) print open(FIFO_PATH, 'r').readline() os.unlink(FIFO_PATH)
pycode2
# -*- coding: utf-8 -*- import io,win32file,win32pipe, win32api import msvcrt as ms # for fd magic class pipe(io.IOBase): def __init__(self, name, pipetype = 'server', openmode = win32pipe.PIPE_ACCESS_DUPLEX|win32file.FILE_FLAG_OVERLAPPED, pipemode = win32pipe.PIPE_TYPE_BYTE|win32pipe.PIPE_NOWAIT,maxinstances=255,outbuffersize=1000000,inbuffersize=1000000, defaulttimeout=50, securityattrib = None): """ An implementation of a file-like python object pipe. Documentation can be found at https://msdn.microsoft.com/en-us/library/windows/desktop/aa365150(v=vs.85).aspx""" self.pipetype = pipetype self.name = name self.openmode = openmode self.pipemode = pipemode self.__enter__ = self.connect if pipetype == 'server': self.handle = win32pipe.CreateNamedPipe(r"\\.\pipe\%s" % name, openmode, # default PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED pipemode, # default PIPE_TYPE_BYTE|PIPE_NOWAIT maxinstances, # default 255 outbuffersize, # default 1000000 inbuffersize, # default 1000000 defaulttimeout,# default 50 securityattrib)# default None elif pipetype == 'client': # it doesn't matter what type of pipe the server is so long as we know the name self.handle = win32file.CreateFile(r"\\.\pipe\%s" % name, win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0, None, win32file.OPEN_EXISTING, 0, None) self.fd = ms.open_osfhandle(self.handle,0) self.is_connected = False self.flags,self.outbuffersize,self.inbuffersize,self.maxinstances = win32pipe.GetNamedPipeInfo(self.handle) def connect(self): # TODO: WaitNamedPipe ? win32pipe.ConnectNamedPipe(self.handle,None) self.is_connected = True def __del__(self): print("del initiated") try: self.write(b'') # try to clear up anyone waiting except win32pipe.error: # no one's listening pass self.close() def __exit__(self): print("exit started") self.__del__() def isatty(self): #Return True if the stream is interactive (i.e., connected to a terminal/tty device). return False def seekable(self): return False def fileno(self): return self.fd def seek(self): # seek family raise IOError("Not supported") def tell(self): # Part of the seek family. Not supported raise IOError("Not supported") def write(self,data): # WriteFileEx impossible due to callback issues. if not self.is_connected and self.pipetype == 'server': self.connect() if type(data).__name__ != 'bytes': # if we don't get bytes, make it bytes data = bytes(data) win32file.WriteFile(self.handle,data) return len(data) def close(self): print("closure started") win32pipe.DisconnectNamedPipe(self.handle) def read(self,length=None): if length == None: length=self.inbuffersize resp = win32file.ReadFile(self.handle,length) if resp[0] != 0: raise __builtins__.BrokenPipeError(win32api.FormatMessage(resp[0])) else: return resp[1] if __name__ == '__main__': server = pipe("mypipename") client = pipe("mypipename", "client") client.write("hello") print server.read() server.write("words") print client.read()
Relevant Link:
http://quickies.seriot.ch/?id=241 https://www.ibm.com/developerworks/cn/linux/l-ipc/part1/ https://codereview.stackexchange.com/questions/88672/python-wrapper-for-windows-pipes https://bytes.com/topic/python/answers/28069-sharing-pipes-win32 https://msdn.microsoft.com/en-us/library/windows/desktop/aa365783(v=vs.85).aspx https://kodedevil.wordpress.com/2015/11/11/2-linux-fifo-in-python-autonomous-robot/ http://blog.csdn.net/gexueyuan/article/details/6428584 http://liwei.life/2016/07/18/pipe/ http://www.cnblogs.com/biyeymyhjob/archive/2012/11/03/2751593.html
Copyright (c) 2017 LittleHann All rights reserved