Linux进程间通信---命名管道

文章目录

  • 概述
  • API
  • 访问命名管道
  • 安全性

概述

在 Linux进程间通信—管道(无名管道、pipe)一文中介绍了匿名管道的使用,但是其中有一个明显的缺陷,匿名管道只能用于有亲缘关系的进程之间通信,命名管道则解决了这个缺陷,可以在没有亲缘关系的2个进程之间进行通信。

命名管道(named pipe) 也被成为FIFO文件,是一种特殊类型的文件。在文件系统中可以找到实在的文件与之对应,这样进程可以像访问文件一样对管道进行访问,大部分文件访问的API(open、read、write)也可以用于命名管道的访问。

命名管道的行为和匿名管道基本一致,也存在如下特点:

  1. 阻塞方式下,read会因管道空阻塞,和write会因管道满阻塞,直到可读或者可写;
  2. 阻塞方式下,open操作也会阻塞,直到另一端也能够打开的时候,open才能正常返回;
  3. 非阻塞方式下,以O_RDONLY打开管道,可以正常打开;以O_WRONLY打开管道,会返回失败,并设置ENXIO(6)错误码;
  4. 在所有读端关闭的情况下,进行写入时会抛出SIGPIPE信号;
  5. 以O_RDWR方式打开命名管道,其行为是未定义的;(参考POSIX)

API

#include 
#include 
/* 创建fifo文件
 * @filename: 文件名
 * @mode: 文件的读写权限
 * return: 成功返回0, 失败返回-1, 并正确设置errno
 */
int mkfifo(const char *filename, mode_t mode);

#include  
#include 
/* 打开管道文件
 * @filename: 文件名
 * @oflag: 打开方式,O_WRONLY, O_RDONLY,O_NONBLOCK等
 * return: 成功返回非负的文件描述符, 失败返回-1, 并正确设置errno
 */
int open(const char *path, int oflag, ... );

#include 
/* 读取管道文件
 * @fildes: 文件描述符
 * @buf: 存储结果的内存
 * @nbyte: 最大读取的字节数
 * return: 成功返回非负的读取的字节数, 失败返回-1, 并正确设置errno
 */
ssize_t read(int fildes, void *buf, size_t nbyte);

#include 
/* 向管道文件写入数据
 * @fildes: 文件描述符
 * @buf: 需要写入的数据
 * @nbyte: 最大写入的字节数
 * return: 成功返回非负的读取的字节数, 失败返回-1, 并正确设置errno
 */
ssize_t write(int fildes, void *buf, size_t nbyte);

mkfifo创建一个FIFO文件,值得注意的是,这是一个***真实的文件系统中的文件***。用open打开一个管道,并通过readwrite进行文件读写。这些操作和直接操作文件基本没有任何区别。

访问命名管道

同匿名管道相比,访问命名管道多了一个open的操作。直接看例子:

pipe_read.c: 每隔2s,从管道中读取128个字节,pipe_write.c:每隔1s从管道中读取128字节。

// pipe_read.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define err_print(fmt, args...) do { print_now(); printf("[E_NO: %d, %s]", errno, strerror(errno)); printf(fmt, ##args ); printf("\n");} while(0)
#define dbg_print(fmt, args...) do { print_now(); printf("[file: %s, line: %d]", __FILE__, __LINE__); printf(fmt, ##args ); printf("\n"); } while(0)

void print_now()
{
    time_t rawtime;
    struct tm * timeinfo;
    char *t;

    time( &rawtime );
    timeinfo = localtime( &rawtime );
    t = asctime(timeinfo);
    t[strlen(t) - 1] = 0;
    printf("[%s] ", t);
}

int main(int argc, char *argv[])
{
    int fd;
    int ret;
    int len;

    ret = mkfifo("my_fifo", 0666);
    if(ret != 0)
    {
        err_print("mkfifo");
        if(EEXIST != errno)
            exit(-1);
    }

    // 只读并指定阻塞方式打开(默认)
    fd = open("my_fifo", O_RDONLY);
    if(fd<0)
    {
        err_print("open fifo");
        exit(-1);
    }

    dbg_print("open fifo ok.");

    while(1)
    {
        char recv[129] = {0};

        // 缓冲区空的情况下,会阻塞
        len = read(fd, recv, sizeof(recv) - 1);
        if (len <= 0)
        {
            dbg_print("read from my_fifo buf=%d [%s]",len, recv);
            continue;
        }

        recv[len] = '\0';
        dbg_print("read from my_fifo buf=%d [%s]",len, recv);
        sleep(2);
    }

    return 0;
}
// pipe_write.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define err_print(fmt, args...) do { print_now(); printf("[E_NO: %d, %s]", errno, strerror(errno)); printf(fmt, ##args ); printf("\n");} while(0)
#define dbg_print(fmt, args...) do { print_now(); printf("[file: %s, line: %d]", __FILE__, __LINE__); printf(fmt, ##args ); printf("\n"); } while(0)

static void sig_handler(int signo)
{
    dbg_print("get signal %d", signo);
}

void print_now()
{
    time_t rawtime;
    struct tm * timeinfo;
    char *t;

    time( &rawtime );
    timeinfo = localtime( &rawtime );
    t = asctime(timeinfo);
    t[strlen(t) - 1] = 0;
    printf("[%s] ", t);
}

int main(int argc, char *argv[])
{
    int fd;
    int ret;
    int len;
    int times;

    struct sigaction psa;

    psa.sa_handler = sig_handler;
    // 默认情况下,SIGPIPE会导致进程退出,在这里我们捕获并输出该信号
    sigaction(SIGPIPE, &psa, NULL);

    // 创建命名管道
    ret = mkfifo("my_fifo", 0666);
    if(ret != 0)
    {
        err_print("mkfifo");
        if(EEXIST != errno)
            exit(-1);
    }

    // 只写并指定阻塞方式打开(默认)
    fd = open("my_fifo", O_WRONLY);
    if(fd<0)
    {
        err_print("open fifo");
        exit(-1);
    }
    dbg_print("open fifo ok.");

    char send[129];
    send[sizeof(send) - 1] = '\0';
    times = 0;
    len = 0;
    while(1)
    {
        // 缓冲区满的情况下,会阻塞
        len = write(fd, send, sizeof(send) - 1);
        if (len < 0)
        {
            err_print("write error");
        }
        dbg_print("%d write to my_fifo buf len: %d",times, len);
        sleep(1);
        times ++;
    }

    return 0;
}
# 分别编译
$ gcc pipe_read.c -o pipe_read
$ gcc pipe_read.c -o pipe_read
# 在终端1中执行write动作
$ date; ./pipe_write
2019年 03月 15日 星期五 10:57:08 CST
[Fri Mar 15 10:57:08 2019] [E_NO: 17, File exists]mkfifo
# 等待6s以后才有open成功的输出,这个时间恰好是pipe_read执行的时间,说明open阻塞到了现在;
# 先执行pipe_read再执行pipe_write的效果是一样的
[Fri Mar 15 10:57:14 2019] [file: pipe_write.c, line: 61]open fifo ok.
[Fri Mar 15 10:57:14 2019] [file: pipe_write.c, line: 75]0 write to my_fifo buf len: 128
[Fri Mar 15 10:57:15 2019] [file: pipe_write.c, line: 75]1 write to my_fifo buf len: 128
[Fri Mar 15 10:57:16 2019] [file: pipe_write.c, line: 75]2 write to my_fifo buf len: 128
[Fri Mar 15 10:57:17 2019] [file: pipe_write.c, line: 75]3 write to my_fifo buf len: 128
[Fri Mar 15 10:57:18 2019] [file: pipe_write.c, line: 75]4 write to my_fifo buf len: 128
[Fri Mar 15 10:57:19 2019] [file: pipe_write.c, line: 75]5 write to my_fifo buf len: 128
# 因pipe_read关闭, 继续写入时收到了SIGPIPE(13)信号,此处如果我们不捕获该信号,默认进程退出
[Fri Mar 15 10:57:20 2019] [file: pipe_write.c, line: 16]get signal 13
# 此时write函数返回了-1,并设置了EPIPE(32)错误
[Fri Mar 15 10:57:20 2019] [E_NO: 32, Broken pipe]write error
[Fri Mar 15 10:57:20 2019] [file: pipe_write.c, line: 75]6 write to my_fifo buf len: -1
[Fri Mar 15 10:57:21 2019] [file: pipe_write.c, line: 16]get signal 13
[Fri Mar 15 10:57:21 2019] [E_NO: 32, Broken pipe]write error
[Fri Mar 15 10:57:21 2019] [file: pipe_write.c, line: 75]7 write to my_fifo buf len: -1

# 在终端2中执行read动作
$ date; ./pipe_read
# 执行时间比pipe_read晚6s
2019年 03月 15日 星期五 10:57:14 CST
[Fri Mar 15 10:57:14 2019] [E_NO: 17, File exists]mkfifo
[Fri Mar 15 10:57:14 2019] [file: pipe_read.c, line: 48]open fifo ok.
# 此时已经有数据可读,read会立马返回
[Fri Mar 15 10:57:14 2019] [file: pipe_read.c, line: 58]read from my_fifo buf=128 []
[Fri Mar 15 10:57:16 2019] [file: pipe_read.c, line: 58]read from my_fifo buf=128 []
[Fri Mar 15 10:57:18 2019] [file: pipe_read.c, line: 58]read from my_fifo buf=128 []
# 此时通过Ctrl + C 强制结束进程
^C
# 此时可以发现有个文件存在于文件夹下,首字符p表示这是一个管道文件
$ ls -l my_fifo
prw-rw-r-- 1 qingru.meng qingru.meng 0 3月  15 11:43 my_fifo

以阻塞方式open时,读、写必须都在open时才能同时返回,否则会被阻塞:

  • 如以O_WRONLY打开时,会阻塞,直到另外有以O_RDONLY、O_REWR方式open的操作时才会返回;
  • 如以O_RDWR打开时, open不会阻塞;

非阻塞方式时:

  • 以O_WRONLY打开,会直接返回负值,并设置错误为ENXIO(6);
  • 以O_RDONLY和O_RDWR可以正常打开;

以阻塞方式read和write时,如果不可读或者不可写时,会阻塞,直到可读或可写;以非阻塞方式read和write时,如果不可读或不可写,则直接返回-1。

安全性

命名管道的设计是为了方便不同进程之间的信息传递,一个进程向管道中写入信息,另外一个进程从中读取信息,假设有多个进程同时向管道中写入信息,是否会发生信息的交错?

在POSIX.1中有关于pipe的描述,通过man指令看一下:

$ man 7 pipe
PIPE_BUF
    POSIX.1 says that write(2)s of less than PIPE_BUF bytes must be atomic: the output data is written to the pipe as a contiguous sequence.  Writes of more than PIPE_BUF bytes  may  be nonatomic:  the  kernel  may  interleave  the  data  with data written by other processes. POSIX.1 requires PIPE_BUF to be at least 512 bytes.  (On Linux, PIPE_BUF is  4096  bytes.) The  precise  semantics depend on whether the file descriptor is nonblocking (O_NONBLOCK), whether there are multiple writers to the pipe, and on n, the number of bytes to be  written:
    O_NONBLOCK disabled, n <= PIPE_BUF 
    All  n  bytes are written atomically; write(2) may block if there is not room for n bytes to be written immediately
    O_NONBLOCK enabled, n <= PIPE_BUF
    If there is room to write n bytes to the pipe, then write(2) succeeds  immediately, writing all n bytes; otherwise write(2) fails, with errno set to EAGAIN.
    O_NONBLOCK disabled, n > PIPE_BUF
    The  write  is  nonatomic:  the  data  given  to  write(2)  may be interleaved with write(2)s by other process; the write(2) blocks until n bytes have been written.
    O_NONBLOCK enabled, n > PIPE_BUF
    If the pipe is full, then write(2) fails, with errno  set  to  EAGAIN.   Otherwise, from  1  to  n  bytes may be written (i.e., a "partial write" may occur; the caller should check the return value from write(2) to see how  many  bytes  were  actually written), and these bytes may be interleaved with writes by other processes.

意思就是规定了对pipe的实现时需要依据PIPE_BUF的大小来决定pipe的写入行为,而写入小于PIPE_BUF大小的数据必须是原子的

  1. 阻塞时,写入n <= PIPE_BUF字节

    写入n字节是原子的,不会发生交错,如果没有n个字节的空间,write会阻塞;

  2. 非阻塞时,写入n <= PIPE_BUF字节

    如果空间足够,则成功写入,否则返回-1,并设置EAGAIN;

  3. 阻塞时,写入n > PIPE_BUF字节

    写入是非原子的,数据可能发生交错,直到n字节全部写完,write才会返回;

  4. 非阻塞时,写入n > PIPE_BUF字节

    如果管道满,write返回-1,并设置EAGAIN,否则可能写入任意1到n字节数据,调用者需要自己检查write的返回值,以确定是否全部写完。

PIPE_BUF的大小依据Linux内核的不同而有所不同(最小512字节,一般为4K),最好事先确定:

$ ulimit -a
...
# 可以发现PIPE_BUF为512*8=4K
pipe size            (512 bytes, -p) 8
...

参考资料

你可能感兴趣的:(linux,C)