`
836811384
  • 浏览: 545517 次
文章分类
社区版块
存档分类
最新评论

一步步理解Linux之进程间通信(1)–管道和FIFO

 
阅读更多

一步步理解Linux之进程间通信(1)–管道和FIFO

作者:gaopenghigh,转载请注明出处。(原文地址)


进程间通信叫做IPC:InerProcess Communication

Unix系统的进程间通信主要有下面几种机制:

  • 管道和FIFO * 信号量
  • 消息(Linux内核提供“System V IPC消息”和“POSIX消息队列”) * 共享内存区 * 套接字

本文讨论管道和FIFO,以及它们在Linux内核中的实现方式。

管道和FIFO

管道和FIFO最适合在进程之间实现生产者/消费者的交互。

Shell中的管道

让我们考察一个Shell命令ls | more执行时到底发生了哪些事:

  1. 调用pipe()系统调用,产生了两个文件描述符3(读)和4(写)。
  2. 两次fork()系统调用。 - 第一次fork(),需要执行more命令: * 调用dup2(3, 0)把文件描述符3拷贝到标准输入0. * 两次调用close()系统调用关闭文件描述符3和4. * 调用execve()系统调用执行more程序,从标准输入(之前已经被接到了管 道)读入输入,输出到标准输出。 - 第二次fork(),需要执行ls命令: * 调用dup2(4, 1)把文件描述符4拷贝到标准输入1. * 两次调用close()系统调用关闭文件描述符3和4. * 调用execve()系统调用执行ls程序,输出到标准输出(之前已经被接到了 管道)。

pipe()

半双工的管道是最常见的IPC形式。管道通过pipe()函数创建:

#include <unistd.h>
int pipe(int filedes[2]);
/* 成功返回0,失败返回-1 */

参数filedes是int型的长度为2的数组,经过pipe()函数调用后:

  • filedes[0]可读
  • filedes[1]可写
  • filedes[0]的输入是filedes[1]的输出

父进程和子进程通过管道通信的例子是:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 100

void err(char *s) {
    printf("%s\n", s);
    exit(1);
}

void test_pipe() {
    int n;
    int fd[2];
    int pid;
    char line[MAXLINE];

    if (pipe(fd) < 0)
        err("pipe failed");
    if ((pid = fork()) < 0) {
        err("fork failed");
    } else if (pid == 0) {        /* parent */
        close(fd[0]);             /* close read side of PIPE */
        write(fd[1], "hello son\n", 10);
    } else {                      /* child */
        close(fd[1]);             /* close write side of PIPE */
        n = read(fd[0], line, MAXLINE);
        write(STDOUT_FILENO, line, n);
    }
    exit(0);
}

在APUE中用管道实现了父子进程之间的通信函数TELL_WAIT,实现如下:

/* tell_wait_pipe.c
 * Use PIPE to sync between child process and parent process
 * From APUE
 * Create 2 PIPEs, fd1[2], fd2[2]
 * fd1 : parent --> child, write 'p' if parent is OK
 * fd2 : child --> parent, write 'c' if child is OK
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define ERRORCODE 1

static int fd1[2], fd2[2];

void err(char *s) {
    printf("ERROR: %s\n", s);
    exit(ERRORCODE);
}

void TELL_WAIT(void) {
    if (pipe(fd1) < 0 || pipe(fd2) < 0)
        err("pipe failed");
}

void TELL_PARENT(pid_t pid) {
    if (write(fd2[1], "c", 1) != 1)
        err("write error");
}

void WAIT_PARENT(pid_t pid) {
    char c;
    if (read(fd1[0], &c, 1) != 1)
        err("read error");
    if (c != 'p')
        err("WAIT_PARENT: incorrect data");
}

void TELL_CHILD(pid_t pid) {
    if (write(fd1[1], "p", 1) != 1)
        err("write error");
}

void WAIT_CHILD(pid_t pid) {
    char c;
    if (read(fd2[0], &c, 1) != 1)
        err("read error");
    if (c != 'c')
        err("WAIT_CHILD: incorrect data");
}

popen()pclose()

popen()函数创建一个管道,然后调用fork产生一个子进程,关闭管道的不使用端,执行 一个shell以运行命令,然后等待命令终止。

#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
/* type 可以是"r"或者"w" */
/* 成功返回文件指针,出错返回NULL */
int pclose(FILE *fp);
/* 返回cmdstring的终止状态,出错返回-1 */

FIFO

FIFO又叫做命名管道。

#include <sys.stat.h>
int mkfifo(const char *pathname, mode_t mode);
/* mode的值和open()函数一样,常用O_RDONLY, O_WRONLY, O_RDWR三者之一 */
/* 返回值:成功返回0,出错返回-1 */

通过prog1产生的一份数据,要分别经过prog2和prog3的处理,使用FIFO可以这样做:

mkfifo fifo1
prog3 < fifo1 &
prog1 < infile | tee fifo1 | prog2

FIFO的一个重要应用是在客户进程和服务器进程间传递数据。

管道的内核实现

管道数据结构

管道被创建时,进程就可以使用两个VFS系统调用–(即read()write()来访问管道 。所以,对于每个管道,内核都要创建一个索引节点和两个文件对象,一个文件对象用于 读。

在索引节点结构struct inode中,有一个表示索引节点类型的union:

union {
    struct pipe_inode_info  *i_pipe;
    struct block_device *i_bdev;
    struct cdev     *i_cdev;
};

i_pipe字段就指向一个代表管道的结构体struct pipe_inode_info:

/**
 *	struct pipe_buffer - a linux kernel pipe buffer
 *	@page: the page containing the data for the pipe buffer
 *	@offset: offset of data inside the @page
 *	@len: length of data inside the @page
 *	@ops: operations associated with this buffer. See @pipe_buf_operations.
 *	@flags: pipe buffer flags. See above.
 *	@private: private data owned by the ops.
 **/
struct pipe_buffer {
	struct page *page;
	unsigned int offset, len;
	const struct pipe_buf_operations *ops;
	unsigned int flags;
	unsigned long private;
};

/**
 *	struct pipe_inode_info - a linux kernel pipe
 *	@wait: reader/writer wait point in case of empty/full pipe
 *	@nrbufs: the number of non-empty pipe buffers in this pipe
 *	@buffers: total number of buffers (should be a power of 2)
 *	@curbuf: the current pipe buffer entry
 *	@tmp_page: cached released page
 *	@readers: number of current readers of this pipe
 *	@writers: number of current writers of this pipe
 *	@waiting_writers: number of writers blocked waiting for room
 *	@r_counter: reader counter
 *	@w_counter: writer counter
 *	@fasync_readers: reader side fasync
 *	@fasync_writers: writer side fasync
 *	@inode: inode this pipe is attached to
 *	@bufs: the circular array of pipe buffers
 **/
struct pipe_inode_info {
	wait_queue_head_t wait;
	unsigned int nrbufs, curbuf, buffers;
	unsigned int readers;
	unsigned int writers;
	unsigned int waiting_writers;
	unsigned int r_counter;
	unsigned int w_counter;
	struct page *tmp_page;
	struct fasync_struct *fasync_readers;
	struct fasync_struct *fasync_writers;
	struct inode *inode;
	struct pipe_buffer *bufs;
};

每个管道都有自己的管道缓冲区(pipe buffer),事实上,每个管道都有一个管道缓冲区 的数组,即bufs字段表示的数组,这个数组可以看作一个环形缓冲区:写进程不断向这 个大缓冲区追加数据,而读进程则不断移出数据。当前使用的那个缓冲区就是curbuf

每个管道缓冲区由struct pipe_buffer表示,结构体中指明了缓冲区页框的描述符地址 ,页框内有效数据的offset以及数据的长度,ops字段表示的是操作管道缓冲区的方法表 的地址。

管道的创建和撤销

创建

pipe()系统调用由sys_pipe()函数处理,后者又会调用do_pipe()函数。该函数的主 要操作如下:

1. 创建一个索引节点对象并初始化。 2. 分配可读的文件对象和文件描述符,该文件对象的f_op字段初始化成read_pipe_fops表的地址。 3. 分配可写的文件对象和文件描述符,该文件对象的f_op字段初始化成write_pipe_fops表的地址。 4. 分配一个目录项对象,并使用它把两个文件对象和索引节点连接在一起。然后把新的索 引节点插入pipefs图书文件系统中。 5. 把两个文件描述符返回给用户态进程。

撤销

只要进程对与管道相关的一个文件描述符调用close()系统调用,内核就对相应的文件对 象执行fput()函数,这会减少它的引用计数器的值。如果这个计数器变成0,那么该函数 就会调用这个文件操作的release方法。realeas方法根据文件是与读通道还是写通道 关联,对这个管道相关的资源进行释放。

管道的读取和写入

从管道中读取

希望从管道中读取数据的进程发出一个read()系统调用,内核最终调用与这个文件描述 符相关的文件操作表中所找到的read方法,对于管道,read方法在read_pipe_fops表中 指向pipe_read()函数。

当出现下面的情况时,读操作会阻塞:

* 系统调用开始时管道缓冲区为空。 * 管道缓冲区没有包含所有请求的字节,写进程在等待缓冲区的空间时曾被置为睡眠。

当然,通过fcntl()系统调用也可以把对管道的读操作设置为非阻塞的。

向管道中写入

希望向管道中写入数据的进程发出一个write()系统调用,和读取类似,内核最终会调用pipe_write()函数。

需要注意的是,如果有两个或者多个进程并发地在写入一个管道,那么任何少于一个管道 缓冲区大小的写操作都必须单独(原子地)完成,而不能与其它进程交叉进行。

FIFO

在Linux中,FIFO和管道几乎是相同的,除了:

  • FIFO索引节点出现在系统目录树上而不是pipefs特殊文件系统中。
  • FIFO是一种双向通信管道;也就是说,可以以读/写模式打开一个FIFO。

打开一个FIFO时,VFS首先判断到这个文件是特殊的FIFO类型文件,然后找到针对这种文件 的操作函数表,在使用表中对应的函数对FIFO进行操作。


参考资料:

分享到:
评论

相关推荐

    进程间通信之有名管道(fifo) 完整代码

    进程间通信之有名管道(fifo) 注意: 如果只打开有名管道的一端 则系统将暂时阻塞打开进程 直到有另一个进程打开该管道的另一端 当前进程才会继续执行 七种进程间通信方式: 一 无名管道( pipe ) 二 有名管道...

    Linux下进程间通信FIFO演示程序

    一个简单程序,演示了Linux下的FIFO IPC机制 http://blog.csdn.net/ZhengZhiRen/archive/2010/05/21/5613843.aspx

    进程间通信之无名管道(pipe) 完整代码

    进程间通信之无名管道(pipe) 注意: 1 只能用于具有亲缘关系的进程之间的通信 2 SIGPIPE信号的处理 七种进程间通信方式: 一 无名管道( pipe ) 二 有名管道( fifo ) 三 共享内存 shared memory 四 信号 ...

    进程间通信

    进程间通信之有名管道(fifo) 进程间通信之共享内存 shared memory 进程间通信之信号 sinal 进程间通信之消息队列 ( message queue ) 进程间通信之信号量( semophore ) 进程间通信之套接字( socket )

    fifo.rar_fifo_fifo lin_fifo linux_linux fifo_进程间通信

    linux下进程间通信方式之一的fifo读写源程序。

    Linux进程通信--FIFO(写)

    Linux进程间通信之FIFO,适用于任意进程间. 此C文件为FIFO的写端

    进程间通信之信号 sinal ) 完整代码

    进程间通信之信号 sinal ) 唯一的异步通信方式 七种进程间通信方式: 一 无名管道( pipe ) 二 有名管道( fifo ) 三 共享内存 shared memory 四 信号 sinal 五 消息队列 message queue ) 六 信号量 ...

    linux 进程间通信

    Demo是实现进程间通信的实例当前大型进程间通信都是以本Demo为例实现的 本代码通过测试无误直接贴过去就能运行Linux下 SYSTEM V

    linux进程间通信 教程

    IPC(InterProcess Communication)是各种进程通信方式的统称,主要有下面几种类型:  管道  FIFO(命名管道)  消息队列  信号量  共享空间  套接口 前五种IPC只能用于一台主机内的进程间通信,套接口...

    深刻理解Linux系统进程间通信

    Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的,最初Unix IPC包括:管道、FIFO、信号;System V IPC包括:System V消息队列、System V信号灯、System V共享内存区;Posix IPC包括: Posix消息...

    linux进程间通信英文版

    linux系统进程间通信英文版,里面详细介绍了linux环境下常用的进程间通信方式,shared memery, pipes, FIFO, semaphores, message queue, sockets, rpc

    进程间通信之套接字( socket )——完整代码

    进程间通信之套接字( socket ) 网络间通信 七种进程间通信方式: 一.无名管道( pipe ) 二.有名管道( fifo ) 三.共享内存 ( shared memory ) 四.信号 ( sinal ) 五.消息队列 ( message queue ) 六.信号量 ( ...

    进程间通信-管道和信号.ppt

    进程间通信-PIPE 进程间通信―FIFO 信号中断处理

    fifo进程间通信

    用于进程间通信fifo测试,用来学习fifo通信机智

    管道通信fifo程序

    管道fifo通信程序,自己写的有注释,能够一方发数据,接收端能够接受并打印出来

    进程间通信之共享内存 shared memory 完整代码

    进程间通信之共享内存 shared memory ) 1 效率最高 2 存在竞态 七种进程间通信方式: 一 无名管道( pipe ) 二 有名管道( fifo ) 三 共享内存 shared memory 四 信号 sinal 五 消息队列 message queue ) ...

    命名管道(FIFO)示例代码

    Linux系统编程——进程间通信:命名管道(FIFO),相关教程链接如下: http://blog.csdn.net/tennysonsky/article/details/46326957

    UNIX网络编程_卷2_进程间通信

    卷2:进程间通信(第2版)》从对Posix IPC和System V IPC的内部结构开始讨论,全面深入地介绍了4种IPC形式:消息传递(管道、FIFO、消息队列)、同步(互斥锁、条件变量、读写锁、文件与记录锁、信号量)、共享内存(匿名...

    Linux进程通信--FIFO(读)

    Linux进程间通信之FIFO,适用于任意两个进程间.此C文件件为读端.

    FIFO.rar_进程间通信

    客户进程-服务器通信,用于学习FIFO进程间通信

Global site tag (gtag.js) - Google Analytics