Linux中的pipe(管道)與named pipe(FIFO 命名管道)

Andrew.Hann發表於2017-05-26

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

相關文章