linux系統程式設計之管道(三):命令管道(FIFO)

mickole發表於2013-07-16

一,匿名管道PIPE侷限性

管道的主要侷限性正體現在它的特點上:

  • 只支援單向資料流;
  • 只能用於具有親緣關係的程式之間;
  • 沒有名字;
  • 管道的緩衝區是有限的(管道制存在於記憶體中,在管道建立時,為緩衝區分配一個頁面大小);
  • 管道所傳送的是無格式位元組流,這就要求管道的讀出方和寫入方必須事先約定好資料的格式,比如多少位元組算作一個訊息(或命令、或記錄)等等;

如果我們想在不相關的程式之間交換資料,可以使用FIFO檔案來做這項工作,它經常被稱為命名管道,是一種特殊型別的檔案。

二,命名管道FIFO。

2.1 有名管道相關的關鍵概念

管 道應用的一個重大限制是它沒有名字,因此,只能用於具有親緣關係的程式間通訊,在有名管道(named pipe或FIFO)提出後,該限制得到了克服。FIFO不同於管道之處在於它提供一個路徑名與之關聯,以FIFO的檔案形式存在於檔案系統中。這樣,即 使與FIFO的建立程式不存在親緣關係的程式,只要可以訪問該路徑,就能夠彼此通過FIFO相互通訊(能夠訪問該路徑的程式以及FIFO的建立程式之 間),因此,通過FIFO不相關的程式也能交換資料。值得注意的是,FIFO嚴格遵循先進先出(first in first out),對管道及FIFO的讀總是從開始處返回資料,對它們的寫則把資料新增到末尾。它們不支援諸如lseek()等檔案定位操作。

2.2有名管道的建立

命名管道可以從命令列上建立,命令列方法是使用下面這個命令:

$ mkfifo filename

命名管道也可以從程式裡建立,相關函式有:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname, mode_t mode)

該函式的第一個引數是一個普通的路徑名,也就是建立 後FIFO的名字。第二個引數與開啟普通檔案的open()函式中的mode 引數相同。 如果mkfifo的第一個引數是一個已經存在的路徑名時,會返回EEXIST錯誤,所以一般典型的呼叫程式碼首先會檢查是否返回該錯誤,如果確實返回該錯 誤,那麼只要呼叫開啟FIFO的函式就可以了。一般檔案的I/O函式都可以用於FIFO,如close、read、write等等。

man幫助說明:

DESCRIPTION        

       mkfifo() makes a FIFO special file with name pathname.  mode
       specifies the FIFO's permissions.  It is modified by the process's
       umask in the usual way: the permissions of the created file are (mode
       & ~umask).

       A FIFO special file is similar to a pipe, except that it is created
       in a different way.  Instead of being an anonymous communications
       channel, a FIFO special file is entered into the file system by
       calling mkfifo().

       Once you have created a FIFO special file in this way, any process
       can open it for reading or writing, in the same way as an ordinary
       file.  However, it has to be open at both ends simultaneously before
       you can proceed to do any input or output operations on it.  Opening
       a FIFO for reading normally blocks until some other process opens the
       same FIFO for writing, and vice versa.  See fifo(7) for nonblocking
       handling of FIFO special files.

RETURN VALUE        

       On success mkfifo() returns 0.  In the case of an error, -1 is
       returned (in which case, errno is set appropriately).
 
2.3有名管道的開啟規則(與匿名管道一樣)

FIFO(命名管道)與pipe(匿名管道)之間唯一的區別在它們建立與開啟的方式不同,一量這些工作完成之後,它們具有相同的語義。

man幫助說明:The only difference between pipes and FIFOs is the manner in which they are created and opened. Once these tasks have been accomplished, I/O on pipes and FIFOs has exactly the same semantics。

有名管道比管道多了一個開啟操作:open。

FIFO的開啟規則:

如果當前開啟操作是為讀而開啟FIFO時,若已經有相應程式為寫而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程式為寫而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,成功返回(當前開啟操作沒有設定阻塞標誌)。

如果當前開啟操作是為寫而開啟FIFO時,如果已經有相應程式為讀而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程式為讀而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,返回ENXIO錯誤(當前開啟操作沒有設定阻塞標誌)。

2.4有名管道的讀寫規則

從FIFO中讀取資料:

約定:如果一個程式為了從FIFO中讀取資料而阻塞開啟FIFO,那麼稱該程式內的讀操作為設定了阻塞標誌的讀操作。

  • 如果有程式寫開啟FIFO,且當前FIFO內沒有資料,則對於設定了阻塞標誌的讀操作來說,將一直阻塞。對於沒有設定阻塞標誌讀操作來說則返回-1,當前errno值為EAGAIN,提醒以後再試。
  • 對於設定了阻塞標誌的讀操作說,造成阻塞的原因有兩種:當前FIFO內有資料,但有其它程式在讀這些資料;另外就是FIFO內沒有資料。解阻塞的原因則是FIFO中有新的資料寫入,不論信寫入資料量的大小,也不論讀操作請求多少資料量。
  • 讀開啟的阻塞標誌只對本程式第一個讀操作施加作用,如果本程式內有多個讀操作序列,則在第一個讀操作被喚醒並完成讀操作後,其它將要執行的讀操作將不再阻塞,即使在執行讀操作時,FIFO中沒有資料也一樣(此時,讀操作返回0)。
  • 如果沒有程式寫開啟FIFO,則設定了阻塞標誌的讀操作會阻塞。

注:如果FIFO中有資料,則設定了阻塞標誌的讀操作不會因為FIFO中的位元組數小於請求讀的位元組數而阻塞,此時,讀操作會返回FIFO中現有的資料量。

向FIFO中寫入資料:

約定:如果一個程式為了向FIFO中寫入資料而阻塞開啟FIFO,那麼稱該程式內的寫操作為設定了阻塞標誌的寫操作。

對於設定了阻塞標誌的寫操作:

  • 當要寫入的資料量不大於PIPE_BUF時,linux將保證寫入的原子性。如果此時管道空閒緩衝區不足以容納要寫入的位元組數,則進入睡眠,直到當緩衝區中能夠容納要寫入的位元組數時,才開始進行一次性寫操作。
  • 當要寫入的資料量大於PIPE_BUF時,linux將不再保證寫入的原子性。FIFO緩衝區一有空閒區域,寫程式就會試圖向管道寫入資料,寫操作在寫完所有請求寫的資料後返回。

對於沒有設定阻塞標誌的寫操作:

  • 當要寫入的資料量大於PIPE_BUF時,linux將不再保證寫入的原子性。在寫滿所有FIFO空閒緩衝區後,寫操作返回。
  • 當要寫入的資料量不大於PIPE_BUF時,linux將保證寫入的原子性。如果當前FIFO空閒緩衝區能夠容納請求寫入的位元組數,寫完後成功返回;如果當前FIFO空閒緩衝區不能夠容納請求寫入的位元組數,則返回EAGAIN錯誤,提醒以後再寫;

三,FIFO使用

示例一:建立FIFO檔案

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
    if(argc != 2){
        fprintf(stderr,"usage:%s fifoname\n",argv[0]);
        exit(EXIT_FAILURE);
    }
    if(mkfifo(argv[1],0644) == -1){
        perror("mkfifo error");
        exit(EXIT_FAILURE);
    }
    printf("creat FIFO success\n");
    return 0;
}

結果:

QQ截圖20130716102107

示例二:如果當前開啟操作是為讀而開啟FIFO時,若已經有相應程式為寫而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程式為寫而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,成功返回(當前開啟操作沒有設定阻塞標誌)。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char **argv)
{

    int fd;
    
    //fd = open("mypipe",O_RDONLY| O_NONBLOCK);//非阻塞模式開啟
    fd = open("mypipe",O_RDONLY);//預設是阻塞模式開啟
    if(fd == -1){
        perror("open error");
        exit(EXIT_FAILURE);
    }
    printf("read open  FIFO success\n");
    return 0;
}

結果:

QQ截圖20130716103323

第一次以非阻塞開啟

第二次以阻塞模式開啟

示例三:如果當前開啟操作是為寫而開啟FIFO時,如果已經有相應程式為讀而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程式為讀而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,返回ENXIO錯誤(當前開啟操作沒有設定阻塞標誌)。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char **argv)
{

    int fd;
    
    //fd = open("mypipe",O_WRONLY| O_NONBLOCK);//非阻塞模式開啟
    fd = open("mypipe",O_WRONLY);//預設是阻塞模式開啟
    if(fd == -1){
        perror("open error");
        exit(EXIT_FAILURE);
    }
    printf("read open  FIFO success\n");
    return 0;
}

結果:

QQ截圖20130716103647

第一次以非阻塞模式開啟

第二次以阻塞模式開啟

示例四:不同程式間利用命名管道實現檔案複製

寫管道程式:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    if(argc != 2){
        fprintf(stderr,"usage:%s srcfile\n",argv[0]);
        exit(EXIT_FAILURE);
    }
    int infd;
    infd = open(argv[1],O_RDONLY);
    if(infd == -1){
        perror("open error");
        exit(EXIT_FAILURE);
    }

    if(mkfifo("tmpfifo",0644) == -1){
        perror("mkfifo error");
        exit(EXIT_FAILURE);
    }
    int fd ;
    fd = open("tmpfifo",O_WRONLY);
    if(fd == -1){
        perror("open error");
        exit(EXIT_FAILURE);
    }
    char buf[1024*4];
    int n = 0;
    while((n = read(infd,buf,1024*4))){
        write(fd,buf,n);
    }
    close(infd);
    close(fd);
    printf("write success\n");
    return 0;
}

讀程式:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    if(argc != 2){
        fprintf(stderr,"usage:%s desfile\n",argv[0]);
        exit(EXIT_FAILURE);
    }
    int outfd;
    outfd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC);
    if(outfd == -1){
        perror("open error");
        exit(EXIT_FAILURE);
    }

    int fd ;
    fd = open("tmpfifo",O_RDONLY);
    if(fd == -1){
        perror("open error");
        exit(EXIT_FAILURE);
    }
    char buf[1024*4];
    int n = 0;
    while((n = read(fd,buf,1024*4))){
        write(outfd,buf,n);
    }
    close(fd);
    close(outfd);
    unlink("tmpfifo");
    printf("read success\n");
    return 0;
}

結果:

QQ截圖20130716110143

複製成功!

注:本文參考:http://blog.csdn.net/sooneboy/article/details/3915490

相關文章