管道/命名管道

匿名管道:

概述:

用於Unix系列系統。單向數據通道,寫端寫的數據在被讀端讀取以前會被操做系統緩存。雙向管道須要經過建立兩個單向管道實現shell

之因此是匿名的。是由於匿名管道不存在於文件系統中,隨着使用它的進程結束而結束,沒有名稱。沒有特別指明的話,管道指匿名管道。數組

管道爲多個文件建立了臨時的直接鏈接,這使得整合起來的管道總體性能比各個程序分別運行要高。這種直接鏈接使得程序能夠同時運行,而且容許數據直接在它們之間連續的傳輸而沒必要將數據傳到臨時文件中或是顯示器上而後等待前一個程序執行完後一個才能夠執行。若是寫入程序寫的快於讀取程序,寫入程序就會被阻塞並等待數據被讀取;相反的,讀取程序就會被阻塞等待數據被寫入(若是設置爲阻塞讀寫的話)。緩存

文件描述符:當打開文件以後,系統會爲其維護一個描述文件的實體,相應的,這個實體會有一個整數做爲其描述符,經過這個整數就能夠訪問這個文件描述實體。因此在經過文件描述符使用文件的功能中,能夠經過改變文件描述符實際指向的內容來實現輸入輸出流的改變。使用fopen()返回的文件結構體struct FILE(即struct _IO_FILE)中的_fileno字段表示文件描述符。文件描述符0/1/2分別爲標準輸入輸出錯誤流,因此新打開的文件會從3開始使用並隨着打開的文件增加安全

在程序中使用管道

Unix系列系統經過pipe()函數建立新的管道。包含在頭文件unistd.h中。原型:int pipe(int filedes[2]);併發

返回值:成功返回0,失敗返回-1less

參數:一個2個元素的文件描述符數組,成功建立的話,函數將在其中分別放置讀端(filedes[0])和寫端(filedes[1])函數

read()向寫端寫,read()向讀端讀。參數爲文件描述符、存放位置、讀/寫大小。默認狀況下讀取是阻塞的,只要有寫端是打開的,就會一致阻塞地等待須要的數據性能

從管道中讀取:

讀取時spa

管道中操作系統

字節數(p)

至少有一個進程有打開的寫端 沒有進程有打開的寫端
阻塞讀 非阻塞讀
至少一個寫端進程在sleep 沒有寫端進程在sleep
p=0 若是管道不爲空就從中取n字節數據而後返回n,不然等待直到有數據 阻塞等待直到有數據,而後獲取數據並返回其大小 返回-EAGAIN 返回0
0<p<n 獲取p字節而後返回p
p>=n 獲取n個字節返回n,在管道中剩下(p-n)個字節

一般狀況下,一個進程建立了管道以後會fork()一個子進程,並分別的在父子進程中進行讀寫。可是這樣父子進程就會都有讀端和寫端,均可以進行讀和寫。在某些Unix系統中,管道實現爲全雙工模式。可是POSIX標準規定只能是單工模式,一個進程只能使用一個文件描述符。Linux遵循了POSIX規範,可是沒有強制要求進程必定要關閉不用的一端,而是將這項工做留給了開發者

原子寫:

遵循POSIX協議的系統,單次寫只要寫入的字節數沒有超過PIPE_BUF的限制,寫操做就是是原子的。默認狀況下,若是管道中沒有足夠的空間保存寫入的數據(此次寫入的<=PIPE_BUF,但總和>PIPE_BUF),寫操做就會被阻塞直到有足夠的空間。此外,若是單次寫入的字節數超過了PIPE_BUF就不能保證寫是原子的

有兩種方式獲取PIPE_BUF的大小。POSIX要求每一個PIPE_BUF至少須要512字節,Linux中是4096字節

  1. 包含頭文件<limits.h>使用PIPE_BUF,可是若是頭文件是過期的,就只能用下面的方法獲取準確的值
  2. 調用fpathconf()獲取一個打開的文件描述符的屬性值。long fpathconf(int filedes, int name);返回指定的文件描述符的指定配置選項的值

管道的實際容量可能會比PIPE_BUF大,可是沒有系統參數指明管道的總容量,能夠用程序檢測

關於阻塞I/O和管道:

若是write()向一個沒有任何讀進程鏈接的管道寫數據,SIGPIPE信號量會被髮送到寫進程,默認的信號處理函數會直接終止進程。若是實現了本身的處理函數,在處理完SIGPIPE信號量以後,write()會返回-1,而後errno被設置爲EPIPE

在有其餘進程向管道寫的時候,若是惟一的讀進程關閉了讀端,全部的寫進程都會執行上一條規則

只要有寫端沒有關閉,讀端就會一直阻塞地等待

向滿的管道(現有數據加上須要寫入的數據量)寫數據會阻塞寫進程直到有足夠的可用空間

和讀文件不一樣的是,從管道讀數據以後數據就再也不存在在管道中。因此即使有多個讀進程從同一個管道讀也不會有任何兩個進程讀到相同的數據

只要管道中的字節數不超過PIPE_BUF,寫就是原子的

進程不能對管道執行seek()(復位讀寫文件的偏移位置)

popen()/pclose():

包含在頭文件stdio.h中,封裝好了一部分建立管道的操做

FILE *popen(const char *command, const char *type);會建立一個管道,而後fork一個子進程,子進程爲command指定的程序。type能夠是"w"或"r",若是是"r",該函數會返回一個管道的讀端,該管道的寫端會連到command對應的子進程標準輸出流。若是是"w",會返回一個管道的寫端,該管道的讀端會連到command對應子進程的標準輸入流。

int pclose(FILE *stream);用popen()打開的文件指針只能用此函數關閉

命令行層面的管道:

基礎:

標準流重定向(stdin/stdout/stderr)

  • Stdin(0):在執行程序的時候,能夠在命令後面使用"<"操做符來指定標準輸入文件。重定向或者管道的數據是匿名的,接收程序沒法得知數據來源
  • Stdout(1):在執行程序的命令後面使用">"操做符來指定標準輸出文件。">>"命令在已有的文件後面增量的添加內容,只使用">"會覆蓋已存在文件的內容。值得注意的是,當重定向或使用管道的時候,實際存放的數據老是一致的,可是在輸出到屏幕上的時候可能會有些許的不一樣。由於顯示屏的寬是已知的,而重定向的位置是未知的。因此重定向的時候最安全的方式是一個元素一行,而在屏幕上多是全部的字符串在一行
  • Stderr(2):默認狀況下重定向的流是標準I/O流,想要重定向ERROR輸出流,須要指定。這三個輸出流有三個數字與之對應,在">"操做符以前加上數字2就能夠重定向stderr流。若是想要將stderr和stdout重定向到同一個文件,能夠先把stderr重定向到stdout,而後將stdout重定向到文件。2>&1,經過在數字前面加"&"指明這是個流而不是文件名

Error流:默認狀況下,管道中的全部程序的error流會合並在一塊兒併發送到console中。可是許多的shell都有其餘的語法來控制這個流程,好比csh shell使用"|&"代替"|"來指定標準錯誤流須要和標準輸出流合併而後重定向到下一個程序

用程序模擬shell命令">":

命令:ls > list,ls命令是將當前文件夾下的文件列表輸出到顯示器上,此命令將輸出重定向到list文件

shell執行的指令:

  1. Fork()一個子進程
  2. 在子進程中close()文件描述符1(標準輸出的文件描述符)
  3. 在子進程中open()文件list(和O_CREAT標誌一塊兒)
  4. 子進程exec()命令ls

以上過程可以實現重定向的緣由:Fork的子進程在關閉標準輸出的時候,其對應的文件描述符1就被釋放,以後使用open()命令打開文件list,list就會使用可用的文件描述符1。子進程再執行ls命令的時候,ls仍會去尋找文件描述符1,由於默認狀況下它就表明的是標準輸出,可是實際上指向的是文件list,因此就會輸出到list。與此同時,shell主進程的標準輸入輸出仍保持未改變

用程序模擬shell命令"|":

須要採起某種方式將管道一端鏈接到前一個程序的標準輸出,管道的另外一端鏈接到後一個的標準輸入

系統調用dup()和系統調用dup2():dup2能夠代替dup

dup():int dup(int oldfd)

Dup()複製文件描述符指向的內容,在成功調用以後,新舊文件描述符能夠通用。它們指向相同打開的文件描述實體,因此即使文件發生了改變,新舊文件描述符都會引用新的文件

可是dup的問題是,它返回的是最小的可用文件描述符。那麼一個進程若是關閉了標準輸出,而後dup了管道的寫端,標準輸出的文件描述符就會被使用做爲寫端的拷貝,因此在進程想要執行標準輸出的時候,就會輸出到管道的寫端

實現的方式:父進程P建立子進程C,須要實現父進程的標準輸出向子進程的標準輸入寫數據,須要建立管道,寫端連到父進程的標準輸出,讀端連到子進程的標準輸入。

父進程:關閉stdout和fd[0],此時此進程中最小的文件描述符就是stdout,dup(fd[1]),那麼此時標準輸出就和fd[1]一致,向標準輸出寫就至關於向fd[0]寫

子進程:關閉stdin和fd[1],此時此進程中最小的文件描述符就是stdin,dup(fd[0]),那麼此時標準輸入就和fd[0]一致,從標準輸入讀就至關於向fd[1]讀

問題:

父進程不會等待子進程,由於父進程用execlp()取代了它本身。避免這種狀況的方式是建立兩個子進程分別用於讀/寫

將標準輸入輸出鏈接到管道是分爲兩步的,這兩步之間有間隔,因此可能在進程關閉了標準輸入輸出後在將管道連到其上以前有一個信號量到來,其處理函數關閉了一個文件描述符,那麼以後dup()返回的文件描述符就會是剛剛關閉的,而不是標準輸入輸出

程序:

switch(fork()){
    case -1:{
        printf("Error:cannot fork a process.\n");
        return -1;
    }
    case 0:{
        close(fd[0]);
        dup2(fd[1],fileno(stdout));
        close(fd[1]);
        close(fd[1]);
        return 1;
    }
    default:{
        close(fd[1]);
        dup2(fd[0],fileno(stdin));
        close(fd[0]);
        fgets(message,27,stdin);
        return 2;
    }
}

int dup2(int oldfd, int newfd);

由於dup()存在的問題,dup2()被建立。

將oldfd的內容拷貝到newfd中,若是newfd以前是打開的,會先關閉再拷貝。整個操做是原子的

程序:

switch(fork()){
    case -1:{
        perror("Failed to fork:");
        exit(3);
    }
    case 0:{/* parent process */
        close(fd[0]);/* close read end */
        dup2(fd[1],fileno(stdout));/* set stdout as write end */
        close(fd[1]);/* close useless copy of write end */
        if(execlp(argv[1],argv[1],NULL) == -1)
            perror("Failed to execute parameter1:\n");
        exit(4);
    }
    default:{/* child process */
        close(fd[1]);/* close write end */
        dup2(fd[0],fileno(stdin));/* set stdin as read end */
        close(fd[0]);/* close useless copy of read end */
        if(execlp(argv[2],argv[2],NULL) == -1)
            perror("Failed to execute parameter2:\n");
        exit(5);
    }
}

命名管道:

匿名管道的缺點:

  1. 管道只能在有共同祖先的進程之間使用,好比父子進程
  2. 管道會隨着使用管道的進程結束而結束,因此每次使用的時候都要建立

兩者的異同:

  1. 在打開、關閉、讀、寫方面,命名管道和匿名管道的操做是相同的
  2. 命名管道的存在形式是文件系統中的目錄入口,因此相關的有訪問權限和全部者
  3. 命名管道能夠在不相關的進程之間使用
  4. 命名管道能夠在shell層面或是程序層面進程刪除和建立

命令層中的命名管道:

Mknod:此命令用於建立設備特殊文件,因此也能夠用於建立管道

須要注意的是,建立特殊文件要在Linux文件系統中才能夠,不能在微軟的文件系統下。

使用方式:mknod filename p

filename是想要建立的命名管道名,p告知mknod命令建立的是一個命名管道

以後其餘的程序執行的時候就能夠經過訪問這個文件來使用管道了

Mkfifo

Mkfifo [option]… NAME,建立名稱爲NAME的命名管道,若是有多個NAME,就會分別建立對應的命名管道

在程序中使用命名管道

可使用系統調用mknod()或是庫函數mkfifo()。可是在mknod()的Linux手冊中說明此命令不能用於建立目錄,若是想要建立目錄應該使用mkdir(2)建立目錄,用mkfifo(3)建立管道。因此咱們將使用mdfifo()來建立管道,相比於mknod(),mkfifo()還有一個優勢是不用超級用戶權限

使用時須要頭文件sys/types.h和sys/stat/h,int mkfifo(const char *pathname, mode_t mode);。按照慣例,命名管道名稱使用大寫字母

Public FIFO和private FIFO:沒有特定的函數使一個FIFO成爲public的,public的含義是建立的管道名被廣而告之,client程序均可以訪問它。而private是指建立的管道只會被其建立進程以及特定的被告知管道名的進程可使用

相關文章
相關標籤/搜索