linux c編程:FIFO


前面介紹的pipe屬於匿名管道

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

  • 只支持單向數據流; 
  • 只能用於具有親緣關系的進程之間; 
  • 沒有名字; 
  • 管道的緩沖區是有限的(管道制存在於內存中,在管道創建時,為緩沖區分配一個頁面大小); 
  • 管道所傳送的是無格式字節流,這就要求管道的讀出方和寫入方必須事先約定好數據的格式,比如多少字節算作一個消息(或命令、或記錄)等等;

如果我們想在不相關的進程之間交換數據,可以使用FIFO文件來做這項工作,它經常被稱為命名管道,是一種特殊類型的文件。

二,命名管道FIFO

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

管 道應用的一個重大限制是它沒有名字,因此,只能用於具有親緣關系的進程間通信,在有名管道(named pipeFIFO)提出后,該限制得到了克服。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,如closereadwrite等等。

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錯誤,提醒以后再寫;

來看一個具體的實現:

write的函數:

#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 infd;

    int fd;

    char buf[1024*4];

    int n = 0;

    char *path="/home/zhf/c_prj/test.c";

    char *path1="/home/zhf/c_prj/tmpfifo";

    infd = open(path,O_RDONLY);

    if(infd == -1){

        perror("open error");

        exit(EXIT_FAILURE);

    }

 

    if(mkfifo(path1,0644) == -1){

        perror("mkfifo error");

        exit(EXIT_FAILURE);

    }

    fd = open(path1,O_WRONLY);

    if(fd == -1){

        perror("open fifo error");

        exit(EXIT_FAILURE);

    }

    while((n = read(infd,buf,1024*4))){

        write(fd,buf,n);

    }

    close(infd);

    close(fd);

    printf("write success\n");

    return 0;

}

read的函數:

#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 outfd;

    int fd;

    char buf[1024*4];

    int n = 0;

    char *path="/home/zhf/c_prj/tmp.txt";

    char *path1="/home/zhf/c_prj/tmpfifo";

    outfd = open(path,O_WRONLY | O_CREAT | O_TRUNC);

    if(outfd == -1){

        perror("open error");

        exit(EXIT_FAILURE);

    }

    fd = open(path1,O_RDONLY);

    if(fd == -1){

        perror("open fifo error");

        exit(EXIT_FAILURE);

    }

    while((n = read(fd,buf,1024*4))){

        write(outfd,buf,n);

    }

    close(outfd);

    close(fd);

    printf("read success\n");

    return 0;

}

運行步驟:

首先在write函數中打開/home/zhf/c_prj/test.c文件並且建立tmpfifo文件。以寫的方式打開FIFO文件

test.c中讀取數據存入buf數組中,並繼續寫入tmpfifo文件中

read函數中打開/home/zhf/c_prj/tmp.txt以及tmpfifo文件,以讀的方式打開FIFO文件。並將FIFO文件的數據寫入tmp.txt

運行結果

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM