一、匿名管道pipe
特点:
1.用于有血缘关系的进程通讯(继承其文件描述符)
2.两个进程通过一个管道只能实现单向通信
3.流式服务
4.生命周期:随进程
5.提供同步、互斥
看一个例子:
-
#include<stdio.h>
-
#include<errno.h>
-
#include<sys/wait.h>
-
#include<unistd.h>
-
#include<string.h>
-
#include<sys/types.h>
-
-
int main()
-
{
-
int pipe_fd[2] = {-1,-1};
-
if(pipe(pipe_fd) < 0){
-
printf("pipe error, %s\n",strerror(errno));
-
return 1;
-
}
-
-
pid_t id = fork();
-
if(id == 0){
-
//child write
-
close(pipe_fd[0]);
-
-
char *msg = "child write:enenshiwo\n";
-
while(1){
-
write(pipe_fd[1],msg,strlen(msg));
-
sleep(1);
-
}
-
}else{
-
//father read
-
close(pipe_fd[1]);
-
-
char buf[1024];
-
while(1){
-
buf[0] = '\0';
-
ssize_t _sz = read(pipe_fd[0],buf,sizeof(buf) - 1);
-
-
if(_sz > 0){
-
buf[_sz] = '\0';
-
}
-
-
printf("father read : %s\n",buf);
-
}
-
wait(NULL);
-
}
-
return 0;
-
}
子进程关闭读端来写,父进程关闭写端来读。
在阻塞IO操作下,有以下四种情况:
1.没有人在写、写的文件描述符都关闭了,依然有人读,read会把剩余的读完后返回0
2.没有人写、写的文件描述符没关闭,一直有人读,read会阻塞,等待管道里有了数据再继续读并返回。
3.没有人读,读的文件描述符都关闭了,依然有人写,那么写的进程会收到信号SIGPIPE而异常终止
4.没有人读,读的文件描述符没关闭,依然有人写,等写满了管道后write会阻塞,直到有人读,管道也可以写进去才写入并返回。
可以想象是下水管道的一个,哪一段出了问题另一端都会跟着反应,总不能把管子撑爆、也不可能等着不会再有水的管道来水吧。
二、命名管道 FIFO
特点:
1.创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO 相互通信
2.先进先出,like its name.(FIFO)frist in frist out
3.当使用FIFO的进程退出,FIFO文件会继续保存在文件系统中以便以后使用。
4.FIFO中的内容放在内存中
看一个例子:
client:
-
#define _PATH_ "./file.tmp"
-
#define _SIZE_ 100
-
-
int main()
-
{
-
int fd = open(_PATH_,O_RDONLY,0);
-
if(fd < 0){
-
printf("open file failed!:%s\n",strerror(errno));
-
return 1;
-
}
-
char buf[_SIZE_];
-
while(1){
-
memset(buf,'\0',sizeof(buf));
-
int ret = read(fd,buf,sizeof(buf)-1);
-
if(ret <= 0){
-
printf("read file failed!\n");
-
break;
-
}
-
printf("%s\n",buf);
-
if(strncmp(buf,"quit",4) == 0){
-
break;
-
}
-
}
-
close(fd);
-
return 0;
-
}
server
-
#define _PATH_ "./file.tmp"
-
#define _SIZE_ 100
-
-
int main()
-
{
-
int ret = mkfifo(_PATH_,0666|S_IFIFO);
-
if(ret == -1){
-
printf("mkfifo failed!\n");
-
return 1;
-
}
-
int fd = open(_PATH_,O_WRONLY|O_CREAT,0644);
-
if(fd < 0){
-
printf("open error:%s\n",strerror(errno));
-
}
-
char buf[_SIZE_];
-
while(1){
-
printf("Server Enter:");
-
fflush(stdout);
-
memset(buf,'\0',sizeof(buf));
-
scanf("%s",buf);
-
int len = strlen(buf);
-
buf[len] = '\0';
-
int ret = write(fd,buf,strlen(buf));
-
if(ret < 0){
-
printf("write error!:%s\n",strerror(errno));
-
break;
-
}
-
if(strncmp(buf,"quit",4) == 0){
-
break;
-
}
-
}
-
close(fd);
-
return 0;
-
}
server is write one
client is read one
三、管道的容量
一次管道读写操作最多传送512字节, linux/limits.h中规定:
#define PIPE_BUF 4096
那么当写入超过buf会怎样,我们来man一下:
Writes of greater than {PIPE_BUF} bytes may have data interleaved(交织), on arbitrary(任意) boundaries, with writes by other processes, whether or not the O_NONBLOCK flag of the file status flags is set.
If the O_NONBLOCK flag is clear, a write request may cause the thread to block, but on normal completion it
shall return
nbyte.
ssize_t write(int fildes, const void *buf, size_t
nbyte);
总结一下:
写一个大于管道总容量的数据量,write无论阻塞还是非阻塞都会返回,对于阻塞的,需要有人read把空间让出来,直到nbyte全部写入才返回,对于
非阻塞的,write会尽量写入并且立即返回,并给给予一个保证就是如果pipe在此刻是空的,那么write起码会写入PIPE_SIZE字节。
四、管道的实现(这里参考:
这里)
1.环形缓冲区
每个管道只有一个页面作为缓冲区,该页面是按照环形缓冲区的方式来使用的。这种访问方式是典型的“生产者——消费者”模型。当“生产者”进程有大量的数据需
要写时,而且每当写满一个页面就需要进行睡眠等待,等待“消费者”从管道中读走一些数据,为其腾出一些空间。相应的,如果管道中没有可读数据,“消费者”
进程就要睡眠等待,
2.环形缓冲区实现原理
环形缓冲区是嵌入式系统中一个常用的重要数据结构。一般采用数组形式进行存储,即在内存中申请一块连续的线性空间,可以在初始化的时候把存储空间一次性分配
好。只是要模拟环形,必须在逻辑上把数组的头尾相连接。只要对数组最后一个元素进行特殊的处理——访问尾部元素的下一元素时,重新回到头部元素。对于从尾
部回到头部只需模缓冲长度即可(假设maxlen为环形缓冲的长度,当读指针read指向尾部元素时,只需执行read=read%maxlen即可使read回到头部元素)
3.读写操作
环形缓冲区要维护写端(write)和读端(read)两个索引。写入数据时,必须先确保缓冲区没有满,然后才能将数据写入,最后将write指针指向下一个元素;读取数据时,首先要确保缓冲区不为空,然后返回read指针对应得元素,最后使read指向下一个元素的位置。
4.判断“满”和“空”
当read和write指向同一个位置时环形缓冲区为空或满。为了区别环满和空,当read和write重叠的时候环空;而当write比read快,追到距离read还有一个元素间隔的时候,就认为环已经满了。
阅读(2072) | 评论(0) | 转发(0) |