接上一篇:linux_何为IPC-进程间常用的通信方式
今天来分享linux的管道学习,希望我的笔记能对大家有用,开始上菜:
目录
- 1.管道的概念:
- 2.pipe函数
- 3.管道的读写行为
- 4.管道缓冲区大小
- 5.管道的优劣
1.管道的概念:
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。
调用pipe系统函数即可创建一个管道。
管道有如下特质:
1. 其本质是一个伪文件(实为内核缓冲区)
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
什么是有血缘关系的进程: 就是父子进程、兄弟进程等。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
512B是磁盘的最小单位,即半k。
管道的局限性:
① 数据自己读不能自己写。
② 数据一旦被读走,便不在管道中存在,不可反复读取。
③ 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
④ 只能在有公共祖先的进程间使用管道。
管道属于单工通信。
2.pipe函数
函数作用:
创建一个管道。
头文件:
#include <unistd.h>
函数原型:
int pipe(int pipefd[2]);
函数参数:
pipefd:pipefd[0]为读端,pipefd[1]为写端。
向管道文件读写数据其实是在读写内核缓冲区。
返回值:
成功:返回0;
失败:返回-1;会设置errno,通过perror函数来打印错误信息。
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。
1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
3.管道的读写行为
使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志)(可直接看下方总结):
1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。
4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
总结:
① 读管道:
1. 管道中有数据,read返回实际读到的字节数。
2. 管道中无数据:
(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)
(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
② 写管道:
1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
2. 管道读端没有全部关闭:
(1) 管道已满,write阻塞。
(2) 管道未满,write将数据写入,并返回实际写入的字节数。
例子:
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
int main(void)
{pid_t pid;char buf[1024];int fd[2];char *p = "hello pipe!\n";//pipe函数,0读,1写if (pipe(fd) == -1) {perror("pipe");}pid = fork();//创建子进程if (pid < 0) {perror("fork err");} else if (pid == 0) //子进程 读数据{close(fd[1]);//关闭写端for (size_t i = 0; i < 2; i++){printf("------%ld\n",i+1);//从fd中读数据,会在这里阻塞等待读取管道中的数据,直至有数据读取后程序才往下走int len = read(fd[0], buf, sizeof(buf));//将数据写到屏幕中write(STDOUT_FILENO, buf, len);}printf("---------------\n");close(fd[0]);} else {//父进程 写数据close(fd[0]);//关闭读端sleep(2);//暂停2s后才写数据write(fd[1], p, strlen(p));//向fd中写数据sleep(2);//再次暂停2swrite(fd[1], p, strlen(p));//向fd中写数据wait(NULL);//等待回收子进程close(fd[1]);//关闭写端}return 0;
}
4.管道缓冲区大小
可以使用ulimit –a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。
通常为:
pipe size (512 bytes, -p) 8
函数作用:
查看管道的大小。
头文件:
#include <unistd.h>
函数原型:
long fpathconf(int fd, int name);
函数参数:
fd:
若需要求出管道的大小,则该fd指向管道。
name:
将name设置为等于以下常量之一将返回以下配置:
_PC_LINK_MAX
指向文件的最大链接数。如果fd或path引用一个目录,那么该值将应用于整个目录。相应的宏是_POSIX_LINK_MAX。
_PC_MAX_CANON
格式化输入行的最大长度,其中fd或path必须指向一个终端。相应的宏是_POSIX_MAX_CANON。
_PC_MAX_INPUT
输入行的最大长度,其中fd或path必须指向一个端子。相应的宏是_POSIX_MAX_INPUT。
_PC_NAME_MAX
允许进程创建的目录路径或fd中文件名的最大长度。相应的宏是_POSIX_NAME_MAX。
_PC_PATH_MAX
当path或fd是当前工作目录时,相对路径名的最大长度。相应的宏是_POSIX_PATH_MAX。
_PC_PIPE_BUF
可以原子方式写入FIFO管道的最大字节数。对于fpathconf(),fd应该指管道或FIFO。对于fpathconf(),路径应该指向FIFO或目录;在后一种情况下,返回的值对应于在该目录中创建的FIFO。相应的宏是_POSIX_PPIPE_BUF。
_PC_CHOWN_RESTRICTED
如果使用chown(2)和fchown(2)更改文件的用户ID仅限于具有适当权限的进程,并且将文件的组ID更改为进程的有效组ID或其补充组ID之一以外的值仅限于具有相应权限的进程时,则返回正值。根据POSIX.1,该变量应始终使用-1以外的值进行定义。相应的宏是_POSIX_CHOWN_RESTRECTED。如果fd或path引用了一个目录,那么返回值将应用于该目录中的所有文件。
_PC_NO_TRUNC
如果访问长度超过_POSIX_NAME_MAX的文件名会产生错误,则返回非零。相应的宏是_POSIX_NO_TRUNC。
_PC_VDISABLE
如果可以禁用特殊字符处理,则返回非零,其中fd或path必须指代终端。指向文件的最大链接数。如果fd或path引用一个目录,那么该值将应用于整个目录。相应的宏是_POSIX_LINK_MAX。
返回值:
成功:根据不同的name返回不同的值。
失败:-1,设置errno
例如:
long lsize = fpathconf(fd[0], _PC_PIPE_BUF);//获得管道的大小
5.管道的优劣
优点:简单,相比信号,套接字实现进程间通信,简单很多。
缺点:
1. 只能单向通信,双向通信需建立两个管道。
2. 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决。
以上就是本次的分享了,希望能对广大网友有所帮助,也希望大家踊跃讨论,相互学习。
此博主在CSDN发布的文章目录:【我的CSDN目录,作为博主在CSDN上发布的文章类型导读】