概述
管道简述
管道(pipe)是Unix/Linux中最常见的进程间通信方式之一,它在两个进程之间实现一个数据流通的通道,数据以一种数据流的方式在进程间流动。在系统中,管道相当于文件系统上的一个文件,用于缓存所要传输的数据。在某些特性上又不同于文件,例如当数据读出后,管道中就没有数据了,但文件没有这个特性。管道有两个特点:
·部分系统下的管道是半双工的,数据在同一时间只能向一个方向流动。从实现的角度看,Linux内核采用环形缓冲区实现管道,如果允许同时读写,可能会导致数据冲突,Linux采用锁机制来防止同时读写。
·管道通常来说只能在具有亲属关系(父子进程、兄弟进程)的进程间使用。
管道(也称为匿名管道)是Linux中最古老的进程间通信机制,其应用非常广泛,也为用户在shell中提供了相应的管道操作符“|”。操作符“|”将其前后两个命令连接到一起,前一个命令的输出成为后一个命令的输入,且可以支持使用多个“|”连接多个命令。
管道特点
·管道没有名字,所以也称为匿名管道
·管道是半双工的,数据只能向一个方向流动,需要双向通信时,需要建立起两个管道。
·只能用于父子进程或兄弟进程之间。
·管道对于其两端的进程而言只是一个文件,但它不是普通的文件,它不属于操作系统的某种文件系统,而是单独构成一种文件系统,并且只存在于内存中。
·管道传送的是无格式字节流,这就要求使用管道的通信双方必须事先约定好数据的格式。
管道实现
/**
* 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
* @mutex: mutex protecting the whole thing
* @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
* @files: number of struct file referring this pipe (protected by ->i_lock)
* @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
* @bufs: the circular array of pipe buffers
* @user: the user who created this pipe
**/
struct pipe_inode_info {
struct mutex mutex;
wait_queue_head_t wait;
unsigned int nrbufs, curbuf, buffers;
unsigned int readers;
unsigned int writers;
unsigned int files;
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 pipe_buffer *bufs;
struct user_struct *user;
};
当进程创建一个匿名管道时,Linux内核为匿名管道准备了两个文件描述符:一个用于管道的输入,即在管道中写入数据;另一个用于管道的输出,也就是从管道中读出数据。抽象图如下:
如果一个管道只与一个进程相联系,只实现进程自身的内部通信,则这个管道是毫无意义的。通常情况下,一个创建管道的进程接着就会创建其子进程,由于父子进程可以共享打开文件,子进程将从父进程那里继承到读写管道的文件描述符,这样便建立了父子进程之间的通信管道。
由于管道是半双工,因此在编程时使用管道,需要确定数据的传输方向,父子进程分别关闭与之无关的描述符。例如数据从子进程传送到父进程,则子进程关闭读管道的描述符。
管道的读写规则
由于管道是半双工通信,因此在进行相应操作时需要注意以下几点:
·如果从一个写描述符关闭的管道中读数据,当读完所有的数据后,read函数返回0,表明已到达文件末尾。严格来说,只有当没有数据继续写入后,才可以说达到了文件末尾。所以应该分清到底是暂时没有数据输入还是已经到达文件末尾。如果是前者,该进程应该等待。若为多进程写、单进程读的情况就更为复杂。
·如果向一个读描述符关闭的管道中写数据,就会产生SIGPIPE信号,不管是忽略这个信号还是处理它,write函数都返回-1。
·常数PIPE_BUF规定了内核中管道缓冲区的大小,因为管道被设计为环形缓冲区,所以在写管道时要注意写入的数据大小。如果超过规定大小就会产生交错现象,从而导致数据丢失。
Linux的管道操作
创建
Linux内核提供了函数pipe用于创建一个管道,它接收一个长度为2的int类型数组,用于内核写入文件描述符。fd[0]为读出端的描述符,fd[1]为写入端的描述符。以下示例代码在同一个进程中读写同一个管道,这并无意义,仅作为示例。
#include <iostream>
#include <cstdlib>
#include <unistd.h>
int main()
{
int fd[2]; //创建文件描述符数组
char writebuf[20] = {"Hello World!"}; //写缓冲区
char readbuf[20]; //读缓冲区
if (pipe(fd) < 0)
{
std::cerr << "创建管道失败n";
exit(0);
}
write(fd[1], writebuf, sizeof(writebuf)); //向管道写入端写入数据
read(fd[0], readbuf, sizeof(readbuf)); //向管道读出端读出数据
std::cout << readbuf << "n";
std::cout << "管道的读fd是" << fd[0] << "n写fd是" << fd[1] << std::endl;
return 0;
}
管道通信
管道的用途是为了在不同的进程中进行数据交互,但仅限于在有亲属关系的进程间使用,即父子进程、兄弟进程等。以下代码以父子进程间通信为例。
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int fd[2];
pid_t pid;
char buf[20];
if (pipe(fd) < 0)
{
std::cerr << "创建管道失败n";
exit(0);
}
if ((pid = fork()) < 0)
{
std::cerr << "创建子进程失败n";
exit(0);
}
if (pid == 0) //子进程
{
close(fd[1]); //关闭写入端
read(fd[0], buf, sizeof(buf));
std::cout << buf;
exit(0);
}
else
{
close(fd[0]); //关闭读出端
write(fd[1], "I'm your father.n", 17);
if (waitpid(pid, NULL, 0) != pid)
{
std::cerr << "销毁进程失败n";
}
}
exit(0);
}
管道高级应用
从上一段代码示例可看出,管道创建函数pipe通常是和进程创建函数fork或fork配合使用。Linux内核提供了将两者“合二为一”的函数popen和pclose。
FILE *popen(const char *cmdstring, const char *type);
int pclose(FILE *stream);
与fopen和fclose函数一样,popen和pclose必须配合使用。
函数popen用于创建管道,内部调用fork和exec函数执行第一个参数中指出的命令,返回一个FILE结构的指针,即用于访问管道的指针。第二个参数则用于指出管道的类型,如果管道是以类型“r”打开的,那么这个管道的输入端将连接到命令行cmdstring的标准输出端,此时命令行的输出可以从管道中读入。反之,如果是以类型“w”打开的,那么这个管道的输出端将连接到命令行的标准输入端,此时向管道中写入的数据就成为命令行的输入数据。
最后
以上就是失眠面包为你收集整理的进程间通信之Linux管道编程管道简述管道特点管道实现管道的读写规则Linux的管道操作的全部内容,希望文章能够帮你解决进程间通信之Linux管道编程管道简述管道特点管道实现管道的读写规则Linux的管道操作所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复