Linux 命名管道


前文中筆者介紹了管道,本文接着介紹命名管道。文中演示所用環境為 Ubuntu 18.04 desktop。

命名管道(named pipe)又被稱為先進先出隊列(FIFO),是一種特殊的管道,存在於文件系統中。命名管道與管道非常類似,但是又有自身的顯著特征:

  • 命名管道可以用於任何兩個進程間的通信,而不限於同源的兩個進程。
  • 命名管道作為一種特殊的文件存放在文件系統中,而不是像管道那樣存放在內核中。當進程對命名管道的使用結束后,命名管道依然存在於文件系統中,除非對其進行刪除操作,否則該命名管道不會自行消失。

和管道一樣,命名管道也只能用於數據的單向傳輸,如果要用命名管道實現兩個進程間數據的雙向傳輸,建議使用兩個單向的命名管道。

創建命名管道

在命令行上創建命名管道
可以通過命令行命令 mkfifo 或 mknod 創建命名管道:

$ mkfifo /tmp/testp
$ mknod /tmp/testp p

可以通過 ls 命令查看命名管道的文件屬性:

輸出中的第一個字符為 p,表示這個文件的類型為管道。最后的 | 符號是有 ls 命令的 -F 選項添加的,也表示這個一個管道。

在程序中創建命名管道
在程序中創建命名管道,可以使用 mkfifo 函數,其簽名如下:

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

int mkfifo(const char *pathname, mode_t mode);

參數 pathname 是一個字符串指針,用於存放命名管道的文件路徑。參數 mode 用於表示指定所創建文件的權限。該函數調用成功時返回 0;調用失敗時返回 -1。
mkfifo 函數是一個專門用來創建命名管道的函數,而另外一個函數 mknod 卻可以兼職創建命名文件,其函數簽名如下:

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

int mknod(char *pathname, mode_t mode, dev_t dev);

創建命名管道只是 mknod 函數的功能之一,它的前兩個參數和 mkfifo 函數相同。在創建命名管道時,為第三個參數 dev 傳遞 0 就可以了。該函數調用成功時返回 0;調用失敗時返回 -1。

在程序中使用命名管道

下面的 demo 模擬一個生產者進程和消費者進程,二者通過命名管道傳輸數據。生產者的代碼如下:

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

#define FIFO_NAME "/tmp/testp"
#define BUFFER_SIZE 4096
#define TEN_MEG (1024 * 1024 * 10)

int main(void)
{
    int pipe_fd;
    int res;
    int open_mode = O_WRONLY;
    int bytes_sent = 0;
    char buffer[BUFFER_SIZE + 1];

    if(access(FIFO_NAME, F_OK) == -1)
    {
        res = mkfifo(FIFO_NAME, 0777);
        if(res != 0)
        {
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }

    printf("Process %d opening FIFO O_WRONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d opened fd %d\n", getpid(), pipe_fd);

    if(pipe_fd != -1)
    {
        while(bytes_sent < TEN_MEG)
        {
            res = write(pipe_fd, buffer, BUFFER_SIZE);
            if(res == -1)
            {
                fprintf(stderr, "Write error on pipe\n");
                exit(EXIT_FAILURE);
            }
            bytes_sent += res;
        }
        (void)close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }
    printf("Process %d finished\n", getpid());
    exit(EXIT_SUCCESS);
}

把上面的代碼保存到文件 namedpipedemo.c 中。
消費者的代碼如下:

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

#define FIFO_NAME "/tmp/testp"
#define BUFFER_SIZE 4096

int main(void)
{
    int pipe_fd;
    int res;
    int open_mode = O_RDONLY;
    int bytes_read = 0;
    char buffer[BUFFER_SIZE + 1];

    memset(buffer, '\0', sizeof(buffer));

    printf("Process %d opening FIFO O_RDONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d opened fd %d\n", getpid(), pipe_fd);

    if(pipe_fd != -1)
    {
        do
        {
            res = read(pipe_fd, buffer, BUFFER_SIZE);
            bytes_read += res;
        } while (res > 0);
        (void)close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }
    printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
    exit(EXIT_SUCCESS);
}

把上面的代碼保存到文件 namedpipedemo2.c 中。並分別編譯這兩個程序:

$ gcc -Wall namedpipedemo.c -o pipe1
$ gcc -Wall namedpipedemo2.c -o pipe2

先在一個終端中執行生產者:

然后在另一個終端中執行消費者:

結果是二者完成數據傳輸后都返回了:

刪除命名管道

刪除命名管道和刪除一個普通文件沒有什么區別:

$ rm /tmp/testp

這就可以了!

參考:
《Linux 程序設計》
《Linux 環境下 C 編程指南》


免責聲明!

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



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