进程间通信——管道
原文地址:点击打开链接
一.管道容量:管道容量分为pipi capacity 和 pipe_buf .这两者的区别在于pipe_buf定义的是内核管道缓冲区的大小,这个值的大小是由内核设定的,这个值仅需一条命令就可以查到;而pipe capacity指的是管道的最大值,即容量,是内核内存中的一个缓冲区。pipe_buf: 命令:ulimit -a在终端输入该命令就会出现如下一表:
管道容量 sizeof(pipe_buf)= 512 bytes* 8 = 4kb
pipi capacity:
当管道满的时候 O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
pipi capacity的大小需要写一段程序去检测,代码如下所示:
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
int pipefd[2];
if (pipe(pipefd) == -1)
ERR_EXIT("pipe error");
int ret;
int count = 0;
int flags = fcntl(pipefd[1], F_GETFL);
fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK); // 设置为非阻塞
while (1)
{
ret = write(pipefd[1], "A", 1);
if (ret == -1)
{
printf("err=%s\n", strerror(errno));
break;
}
count++;
}
printf("count=%d\n", count); //管道容量
return 0;
}
运行结果如下所示:
由此图可知 pipe capacity的大小为 65536
二.管道缓冲区
1、管道(pipe)
管道是进程间通信的主要手段之一。一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端。管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构。根据管道的适用范围将其分为:无名管道和命名管道。
● 无名管道
主要用于父进程与子进程之间,或者两个兄弟进程之间。在Linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立。因此,每个管道都是单向的,当需要双向通信时就需要建立起两个管道。管道两端的进程均将该管道看做一个文件,一个进程负责往管道中写内容,而另一个从管道中读取。这种传输遵循“先入先出”(FIFO)的规则。
● 命名管道
命名管道是为了解决无名管道只能用于近亲进程之间通信的缺陷而设计的。命名管道是建立在实际的磁盘介质或文件系统(而不是只存在于内存中)上有自己名字的文件,任何进程可以在任何时间通过文件名或路径名与该文件建立联系。为了实现命名管道,引入了一种新的文件类型——FIFO文件(遵循先进先出的原则)。实现一个命名管道实际上就是实现一个FIFO文件。命名管道一旦建立,之后它的读、写以及关闭操作都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但是仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。
管道通讯如下图:
管道通讯带来的问题:管道容量不像其他文件一样不加检索的增长
1.使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。
2.读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。
注意,从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。
管道的结构:在Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file 结构和VFS 的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。如下图所示。
注意,在管道中的数据始终以和写数据相同的次序来进行读,这表示lseek()系统调用对管道不起作用。
转载出处:http://blog.csdn.NET/csdnldsg/article/details/51813366
一,关于匿名管道
匿名管道的通信步骤
(1)父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端;
管道的5个特性:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if (-1 == ret)
{
printf("creat pipe error! errno code is:%d\n", errno);
return 1;
}
pid_t id = fork();
if (id < 0)
{
printf("fork error!\n");
return 2;
}
else if (0 == id)//child
{
close(_pipe[0]);//close read fd
int i = 0;
char *_mesg_c = NULL;
while(i < 10)
{
_mesg_c = "i am a child!";
write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
sleep(1);
i++;
}
close(_pipe[1]);//close write fd
exit(1);
}
else//father
{
close(_pipe[1]);
char _mesg[100];
int j = 0;
while (j < 100)
{
memset(_mesg, '\0', sizeof(_mesg));
ssize_t ret = read(_pipe[0], _mesg, sizeof(_mesg));
printf("%s:code is:%d\n", _mesg,ret);
j++;
}
if (waitpid(id, NULL, 0)<0)
{
return 3;
}
}
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if (-1 == ret)
{
printf("creat pipe error! errno code is:%d\n", errno);
return 1;
}
pid_t id = fork();
if (id < 0)
{
printf("fork error!");
return 2;
}
else if (0 == id)//child
{
close(_pipe[0]);//close read fd
int i = 0;
char *_mesg_c = NULL;
while(i < 20)
{
if (i < 10)
{
_mesg_c = "i am a child!";
write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
}
sleep(1);
i++;
}
close(_pipe[1]);//close write fd
}
else//father
{
close(_pipe[1]);
char _mesg[100];
int j = 0;
while (j < 10)
{
memset(_mesg, '\0', sizeof(_mesg));
int ret = read(_pipe[0], _mesg, sizeof(_mesg));
printf("%s:code is:%d\n", _mesg,ret);
j++;
}
if (waitpid(id, NULL, 0)<0)
{
return 3;
}
}
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if (-1 == ret)
{
printf("creat pipe error! errno code is:%d\n", errno);
return 1;
}
pid_t id = fork();
if (id < 0)
{
printf("fork error!");
return 2;
}
else if (0 == id)//child
{
close(_pipe[0]);//close read fd
int i = 0;
char *_mesg_c = NULL;
while(i < 20)
{
if (i < 10)
{
_mesg_c = "i am a child!";
write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
}
sleep(1);
i++;
}
}
else//father
{
close(_pipe[1]);
char _mesg[100];
int j = 0;
while (j < 3)
{
memset(_mesg, '\0', sizeof(_mesg));
int ret = read(_pipe[0], _mesg, sizeof(_mesg));
printf("%s:code is:%d\n", _mesg,ret);
j++;
}
close(_pipe[0]);
sleep(10);
if (waitpid(id, NULL, 0)<0)
{
return 3;
}
}
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if (-1 == ret)
{
printf("creat pipe error! errno code is:%d\n", errno);
return 1;
}
pid_t id = fork();
if (id < 0)
{
printf("fork error!");
return 2;
}
else if (0 == id)//child
{
close(_pipe[0]);//close read fd
int i = 0;
char *_mesg_c = NULL;
while(1)
{
_mesg_c = "i am a child!";
write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
// sleep(1);
i++;
printf("%d\n",i);
}
}
else//father
{
close(_pipe[1]);
sleep(1);
if (waitpid(id, NULL, 0)<0)
{
return 3;
}
}
return 0;
}
命名管道:
一 .概念
二:命名管道实现函数:mkfifo
函数原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname,mode_t mode)
函数调用成功都返回0,失败都返回-1
示例:
umask(0);
if (mkfifo("/tmp/fifo",S_IFIFO|0666) == -1)
{
perror("mkfifo error!");
exit(1);
}
三:注意
“S_IFIFO|0666”指明创建一个命名管道且存取权限为0666,即创建者、与创建者同组的 用户、其他用户对该命名管道的访问权限都是可读可写( 这里要注意umask对生成的
管道文件权限的影响 )。
命名管道创建后就可以使用了,命名管道和管道的使用方法基本是相同的。只是使用命 名管道时,必须先调用open()将其打开。因为命名管道是一个存在于硬盘上的文件,而管道
是存在于内存中的特殊文件。
需要注意的是,调用open()打开命名管道的进程可能会塞。但如果同时用读写方式 (O_RDWR)打开,则一定不会导致阻塞;如果以只读方式(O_RDONLY)打开,则调 用open()函数的进程将会被阻塞直到有写方打开管道;同样以写方式(O_WRONLY)打开 也会阻塞直到有读方式打开管道。
write:
Read:
然后运行两个文件,就可以实现进程间的通信(这里不贴图了)。
匿名实现机制:
实现细节:
关于管道的读写
管道实现的源代码在fs/pipe.c中,在pipe.c中有很多函数,其中有两个函数比较重要,即管道读函数pipe_read()和管道写函数pipe_wrtie()。管道写函数通过将字节复制到 VFS 索引节点指向的物理内存而写入数据,而管道读函数则通过复制物理内存中的字节而读出数据。当然,内核必须利用一定的机制同步对管道的访问,为此,内核使用了锁、等待队列和信号。
当写进程向管道中写入时,它利用标准的库函数write(),系统根据库函数传递的文件描述符,可找到该文件的 file 结构。file 结构中指定了用来进行写操作的函数(即写入函数)地址,于是,内核调用该函数完成写操作。写入函数在向内存中写入数据之前,必须首先检查 VFS 索引节点中的信息,同时满足如下条件时,才能进行实际的内存复制工作:
* 内存中有足够的空间可容纳所有要写入的数据;
* 内存没有被读程序锁定。
如果同时满足上述条件,写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存。否则,写入进程就休眠在 VFS 索引节点的等待队列中,接下来,内核将调用调度程序,而调度程序会选择其他进程运行。写入进程实际处于可中断的等待状态,当内存中有足够的空间可以容纳写入数据,或内存被解锁时,读取进程会唤醒写入进程,这时,写入进程将接收到信号。当数据写入内存之后,内存被解锁,而所有休眠在索引节点的读取进程会被唤醒。
pipe_read()
1.获取索引节点的i_sem信号量 //我要读了
2.判断缓冲区 是否 空 ,或阻塞 //空不空啊?
阻塞: a .调用prepare_wait() 把当前进程(cur)加到等待队列
b.释放索引节信号量
c.调用 schedule()
d.cur一旦被唤醒,从等待队列中删除 。拷贝所有请求字节 //读
3.释放索引节点的i_sem信号量 //我读完了
4.唤醒管道中所有的写者进程 //你来写吧
5.返回 已经拷贝的字节数目 //读了这么多
1.获取索引节点的i_sem信号量 //我要写了
2.检查是否至少有一个读进程, //有没有人正在读啊
如果不是,向当前进程发送 SIGPIPE信号,释放i_sem信号量 ,并返回-EPIPE
3.判断是否有足够的写空间, //满不满啊?
是,则向缓冲区 拷贝数据。如果不是非阻塞,释放索引节点并返回—EAGAIN; 如果不是且阻塞, 将当前写操作放入等待队列,释放信号量 ,调用schedule(),一旦被唤醒,返回 3 操作。
4.写入 所有请求的字节 //不满就写
5.释放索引节点信号量 //我写完了
6.唤醒所有等待队列的读进程 //快来读吧
智能推荐
进程间通信------管道
通过前面对进程概念,进程控制的了解,我们知道,每个进程都有自己独立的用户地址空间,任何一个进程中的全局变量在另一个进程中是看不到的。所以进程间的运行是相互独立的。这样做可以保证安全,当一个进程出现问题时,不至于影响其他进程。 但是,当不同进程间要进...
进程间通信——管道
一、进程间通信的目的 1.数据传输:一个进程需要将它的数据发送给另一个进程。 2.资源共享:多个进程之间共享同样的资源。 3.通知事件:一个进程需要给另一个或是一组进程发送消息,通知它们发生了某种事件,(比如进程终止时要通知父进程)。 4.进程控制:有些进程希望控制另一个进程,此时控制进...
【进程间通信】----管道
进程间通信的概念: 进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。 以Linux中的C语言编程为例。 进程通信的本质是两个毫不相干的进程,看到了同一份资源(这个资...
进程间通信——管道
一、进程间通信 1、进程间通信的目的 * 数据传输:一个进程需要将他的数据发送给另一个进程 * 资源共享:多个进程之间共享同样的资源 * 通知事件:一个进程需要向另一个进程(组)发送消息,如子进程终止时要通知父进程 * 进程控制:有些进程希望完全控制另一个进程(如Debug),此时,控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道他的状态改变 2、进程间通信的分类 * 管道 匿名管道...
进程间通信——管道
一、进程间通信 1、本质:让两个不相干的进程看到同一块资源,这个资源肯定是操作系统提供的 2、目的 ·数据传输:一个进程需要将它的数据发给另一个进程 ·资源共享:多个进程之间共享同样的资源 ·通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(例...
猜你喜欢
进程间通信---管道
进程间通信----(IPC)InterProcess Communication 一:管道 管道(包括无名管道和命名管道),通常指无名管道,是 UNIX 系统IPC最古老的形式。 [含义]:管道是一个进程的数据流到另一个进程的通道,即一个进程的数据的输出作为另一个进程的数据的输入,管道起到了桥梁的作用。 比如:当我们输入: ls -l | cat test .其中ls和cat是两个进程,|代表管道...
进程间通信——管道
进程间通信目的 数据传输: 一个进程需要把它的数据发送给另一个进程 资源共享:多个进程间共享同样的资源 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。 进程具有独立性,让其通信时具有难度的...
进程间通信---管道
我们知道,进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。但是,系统空间却是“公共场所”,所以内核显然可以提供这样的条件。所以进程之间交换数据时在内核进行,通俗的说A进程把数据拷贝到内核,B进程从内核中把数据读走,我们把内核提供的这种机制叫做进程间通信,(IPC)。 一、管道(pipe) 管道是Unix中最古老的进程间通信的形式。 我们把从一个...
进程间通信——管道
进程间通信——管道 要学习进程间通信方式之一的管道,就要了解进程间通信的相关概念。 进程间通信的本质:进程间通信的本质就是让不同的进程看到公共的资源 什么叫做进程间通信呢? 进程之间要交换数据,必须要通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核的缓冲区,进程2再从内核的缓冲区把数据拷走,内核提供的这种机制叫做:进程间通信 进程间通...
进程间通信--管道
概述 管道是最初的Unix 进程间通信(IPC)的方式,它的局限在于没有名字(有名管道FIFO下节讲),从而只能由有亲缘关系的进程间使用,例如父子进程。管道和FIFO都是使用read和write函数访问的。 管道由pipe函数创建,提供一个单向的数据流 函数返回两个文件描述符,fd[0]和fd[1],0用于读,1用于写。 调用pipe函数会在内核中开辟一块缓存区用于通信,一个读端,一个写端,fd[...