Linux進程通信——有名管道

管道(pipe)是無名管道,他是進程資源的一部分,隨着進程的結束而消失。並且它只能在擁有公共祖先進程的進程內通信。而有名管道(FIFO)的出現則解決了這個問題。FIFO提供了一個路徑名與它關聯。這樣可以通過訪問該路徑就能使得兩個進程之間相互通信。此處的FIFO嚴格遵守“先進先出”原則。讀總是從頭開始的,寫總是從尾部進行的。匿名管道和FIFO都不支持lseek函數對他們操作。Linux下建立有名管道的函數是mkfifo。

函數原型: int mkfifo(const char * pathname,mode_t mode);

函數功能:創建一個FIFO文件,用於進程之間的通信。pathname就是路徑名,mode是該文件的權限。返回值表示是否成功,返回0表示成功,返回-1表示失敗,失敗原因在errno中。(建立FIFO的時候,要求不存在這樣的FIFO)。

例如執行下面代碼來創建一個FIFO。

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

int main()
{
    int ret;
    ret = mkfifo("My_FIFO",0666);
    if(0 != ret)
    {
        perror("mkfifo");
    }
    return 0;
}

 可以看到,它以P開頭,表面它是一個FIFO文件。它是真實存在於磁盤上的,不僅僅在內存中。進程結束了這個文件仍然在。FIFO和匿名管道一樣,默認下要考慮阻塞。

  1. 當使用O_NONBLOCK標誌的時候,打開FIFO文件,讀取操作會立即返回。但是如果沒有進程讀取FIFO文件,那麼寫入FIFO的操作會返回ENXIO錯誤代碼。
  2. 不使用O_NONBLOCK標誌時,打開FIFO的操作必須等待其他進程寫入FIFO後才執行,當然寫入FIFO的操作也必須等到其他進程來讀取FIFO以後才能執行。

當存在這個FIFO文件的時候,再次創建這個FIFO會顯示File exists。首先,第一種情形的測試。

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


int main()
{
    int ret;
    int fd;
    ret = mkfifo("My_FIFO",0666);
    if(0 != ret)
    {
        perror("mkfifo");
    }
    pid_t pid;
    pid = fork();
    if(0 < pid)
    {
        fd = open("My_FIFO",O_NONBLOCK|O_WRONLY);
        if(0 > fd)
        {
            perror("open FIFO");
        }
        else
        {
            write(fd,"Hello",5);
            printf("Write Over\n");
        }
        exit(0);
    }
    if(0 == pid)
    {
        sleep(2);       //在設置O_NONBLOCK標誌的情形下,讓父進程先寫入
    }
    if(-1 == pid)
    {
        perror("fork");
    }
    return 0;
}

運行結果如下:

可以看到會發生錯誤,因爲它沒有阻塞。那麼接着試一下直接讀一個FIFO文件,看看會發生什麼。

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


int main()
{
    int ret;
    int fd;
    char str[30] = {0};
    ret = mkfifo("My_FIFO",0666);
    if(0 != ret)
    {
        perror("mkfifo");
    }
    pid_t pid;
    pid = fork();
    if(0 < pid)
    {
        fd = open("My_FIFO",O_NONBLOCK|O_RDONLY);   //注意這裏和上面不一樣,改成O_RDONLY
        if(0 > fd)
        {
            perror("open FIFO");
        }
        else
        {
            read(fd,str,sizeof(str));
            printf("Read Over\n");
        }
        exit(0);
    }
    if(0 == pid)
    {
        sleep(2);       //在設置O_NONBLOCK標誌的情形下,讓父進程直接讀取
    }
    if(-1 == pid)
    {
        perror("fork");
    }
    return 0;
}

 看到的結果是對一個空的FIFO文件打開並執行read操作是沒有問題的。

先以只讀方式打開,如果沒有進程已經爲寫而打開一個 FIFO, 只讀 open() 成功,並且 open() 不阻塞。

下面,當不設置O_NONBLOCK標誌的時候,FIFO和匿名管道的處理方式是一樣的。管道這個名字是非常形象的,一個管道必須有兩端(就是在一個進程中必須讀,另一個進程必須寫),只有這樣,才能正常操作,否則進程將會阻塞。例如下面這樣。

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

int main()
{
    int ret;
    int fd;
    char str[30] = {0};
    ret = mkfifo("My_FIFO",0666);
    if(0 != ret)
    {
        perror("mkfifo");
    }
    pid_t pid;
    pid = fork();
    if(0 < pid)
    {
        fd = open("My_FIFO",O_WRONLY);
        if(0 > fd)
        {
            perror("Write FIFO");
        }
        else
        {
            write(fd,"Hello",5);
            printf("Write Over\n");
        }
        wait(NULL);
        exit(0);
    }
    if(0 == pid)
    {
        sleep(2);
        // fd = open("My_FIFO",O_RDONLY);
        // read(fd,str,5);
        exit(0);
    }
    if(-1 == pid)
    {
        perror("fork");
    }
    return 0;
}

我們僅僅在父進程中進行了寫(write),沒有其他進程在讀(read)。這樣造成的結果是進程一直阻塞在這裏,如下

沒有輸出結果,阻塞在這裏不動了。而當我們加上註釋掉了那兩句話以後,程序就會有輸出,結果如下:

或者說,這也體現了進程的併發行,管子有了一端以後,還必須有另一端,這才能構成管道。

測試一下,FIFO用於兩個無關進程直接的通信。首先建立我們有兩個進程,一個是test1,另一個是test2.

//test1的源代碼
//test1是寫FIFO

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

int main()
{
    int fd,ret;
    char str[10] = {"Hello"};
    ret = mkfifo("fifo",0666);      //test1.c中創建FIFO文件
    fd = open("fifo",O_WRONLY);     //只寫方式打開
    write(fd,str,5);
    close(fd);

    return 0;
}
//test2的源代碼
//test2是讀FIFO

#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    int fd;
    char str[10] = {0};
    fd = open("fifo",O_RDONLY);
    read(fd,str,5);
    printf("%s\n",str);
    close(fd);
    return 0;
}

我們把test1和test2的源代碼生成可執行文件後,打開兩個終端。如果我先運行test1,然後運行test2.那麼test2將讀取到FIFO中的數據。如下所示。

我們沒有設置O_NONBLOCK,先運行test1之後,會發現test1阻塞在這裏。等我們把test2也運行了之後,test1不在阻塞,運行結束,然後test2也成功打印出了Hello。

換個運行順序,我們先運行test2,然後運行test1.這樣會發現test2阻塞在這裏。等我們把test1也運行了之後,test2不在阻塞,向屏幕打印Hello.

如果我們不想讓FIFO阻塞,那麼打開文件的時候設置爲可讀可寫即可。

fd = open("fifo", O_RDWR);

當然,如果FIFO是空的,那麼即使設置了可讀可寫,read()操作仍舊會阻塞。這樣的運行結果和上面所說的是一致的。自己運行一下才能深刻理解。這裏不好用圖片說明。

調用 write() 函數向 FIFO 裏寫數據,當緩衝區已滿時 write() 也會阻塞。

通信過程中,讀進程退出後,寫進程向命名管道內寫數據時,寫進程也會退出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章