总结这几天学习的知识点,管道。
管道的作用就是在具有亲缘关系的进程间传递信息。所谓的亲缘关系,就是他们具有共同的祖先。所以,只要共同的祖先曾今调用调用了fork函数,打开的管道文件就会在fork函数之后。被各个后代进程所共享。
- 管道是单向通信的,管道的生命周期是随进程的,管道依赖于文件流,管道是面向字节流的。
管道是一种文件,可以调用read,write,close等操作文件的接口来操作管道。另一方面管道不是一种普通的文件,它属于独特的文件系统:pipefs。
管道的本质就是内核维护了的一块缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓存区 的操作。
然后我们来讲一下管道接口的创建,使用。
管道所用到的所有头文件:
#include<unistd.h>
#include<sys/types.h>
#incldue<errno.h>
#include<stdlib.h>
管道的创建:
int pipe(int pipefg[2])
如果调用成功后会返回0,失败返回-1,并且设置errno。
errno | 原因 |
---|---|
EMFILE | 该进程的文件描述符多于MAX_OPEN-2 |
ENFILE | 系统中同时打开的文件超过了系统的限制 |
EFAULT | pipefd参数不合法 |
当成功调用后,就会返回俩个文件描述符。一个是管道的读取端的文件描述符pipefd[0],另一个是管道的写入端 的文件描述符pipefd[1]。
管道没有文件名与之关联。所有只能通过文件描述符来访问管道。
然后调用pipe函数后,就可以往管道里写数据:
write(pipefd[1],void *wbuf,size_t count)//这三个参数分别代表:指定输入要源 缓冲区的首地址 缓冲区的大小字节数
一旦对管道里写数据,那就可以在在读取端进行read进行读取数据
read(pipefd[0],void *rbuf,szie_t count)//这三个参数分别代表:指定输入要源 缓冲区的首地址 缓冲区的大小字节数
管道间的通信就是用到了这俩个文件描述符。调用fork函数会在子进程上也复制了父进程的俩个文件描述符。但是管道里是字节流,父子进程都进行读,写。那样就会导致内容混在了一起,所有平常我们就会使父进程放弃读,子进程放弃写,变成父进程写,子进程读,成为了通信的通道。
如何让父进程放弃读,子进程放弃写呢?
close(pipefd[1])//子进程放弃写
close(pipefd[0])//父进程放弃读
我们可以从内核角度看:调用pipe函数后系统会给进程分配俩个文件描述符,然后在调用了fork函数后,子进程也有了对应的文件描述符。和普通文件不同,这俩个文件描述符对应的是一块内存缓存区。
所以,任何俩个有亲缘关系的进程,只要祖先打开了管道,总能通过关闭不相关进程的某些文件描述符,建立起俩者之间单向通信的管道。
管道具有如下三种性质:
- 只有当写入端的文件描述符都关闭,管道里没有可读的数据,读取端的文件描述符调用read是才会返回0。
- 如果所有的读取端 的描述符都关闭,在往管道里写数据,写操作会失败,errno会置为EPIPE,同时内核会向写入进程发送一个SIGPIPE的信号。
- 当所有的读取端,写入端都关闭,管道才能被销毁。
管道对应的内存大小:
管道本质就是一块内存区域,肯定有大小的。管道的默认大小是65536字节,但是可以用fcntl来获取和修改这个值。
pipe_capacity = fcntl(fd,?F_GETPIPE_SZ)//获取管道的大小
ret = fcntl(fd,?F_SETPIPE_SZ,SZ)//设置管道的大小
我们在使用管道 的时候要意识到:管道有大小,写入需谨慎,不能连续的写入大量的内存,一旦管道满了,写入就会阻塞;在读取端,要及时读取,防止管道被写满,造成写入阻塞。