目录
一、概念
二、管道函数
1.popen函数
2.pclose函数
3.文件函数
三、管道的操作
1.管道的分类
无名管道
有名管道
管道的特点
四、管道的实现
操作系统对进程之间相互保护
两个进程之间相互通信
前言:
进程间通信的方法/IPC机制都有哪些:
- 管道
- 套接字
- 信号量
- 共享内存
- 消息队列
一、概念
什么是管道?
当从一个进程连接数据流到另一个进程时,使用“管道”。通常把一个进程的输出通过管道连接到另一个进程的输入。
shell命令的连接是通过管道字符来完成,如下所示:
cmd1|cmd2
shell负责安排两个命令的标准输入和标准输出。cmd1的标准输入来自终端键盘,cmd1的标准输出传递给cmd2,作为它的标准输入。cmd2的标准输出来接到终端屏幕。管道是在内存上分配空间,cmd1把数据写到内存中,cmd2从内存中读取数据,效率高。
shell做的工作实际就是对标准输入和标准输出流进行重新连接,使数据流从键盘输入通过两个命令最终输出到屏幕上。
图示为用管道将多个进程连接起来
二、管道函数
1.popen函数
头文件:
#include<stdio.h>
函数原型:
FILE *popen(const char * command,const char * open_mode);
- 作用:
- 允许一个程序将另一个程序作为新程序来启动,并可以传递数据给它或者通过它接收数据。
- 参数:
- 第一个参数command字符串是要运行的程序名和相应参数。open_mode必须是“r”或者是“w”。
- 第二个参数open_mode必须是“r”或者是“w”。如果open_mode是“r”,被调用的程序输出就可以被调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过常用的stdio库函数(如fread)来读取被调用程序的输出。如果open_mode是“w”,调用程序就可以用fwrite调用向被调用程序发送数据,而被调用程序可以在自己的标准输入流上读取数据,然后做出相应的操作。
- 返回值:
- 失败返回空指针
2.pclose函数
头文件:#include<stdio.h>
函数原型:
FILE *pclose(FILE *stream_to_close);
- 作用:
- 关闭与之关联的文件流。
- 返回值:
- 通常是它所关闭的文件流所在进程的退出码。
3.文件函数
#include<unistd.h>
write:
把缓冲区Buf的前nbytes个字节写入与文件描述符fildes关联的文件中。返回实际写入的字节数。
size_t write(int fildes,const void *buf,size_t nbytes)
read:
从文件描述符filedes相关联的文件里读入nbytes个字节的数据,并且把他们放到数据区buf中。返回实际读入的字节数。
size_t read(int dildes,void* buf,size_t nbytes);
open:
创建一个新的文件描述符
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
int open(const char * path,int oflags);
int open(const char *path,int oflags,mode_t mode);
三、管道的操作
管道也是个文件,用文件操作的命令对管道进程操作。管道存放在内存中,读取速度很快。
1.管道的分类
- 有名管道:在任意两个进程间通信
- 无名管道:在父子进程间通信
无名管道
- 只能进行父子间通信
- 使用pipe创建无名管道
- pipe:
- 头文件:
#include<unistd.h> int pipe(int file_descriptor[2]);
- 成功返回0,失败返回-1
- file_descriptor[0]:管道读端的描述符
- file_descriptor[1]:管道写端的描述符
两个返回的文件描述符以一种特殊的方式连接起来。写到file_deacriptor[1]的所有数据都有可以从file_descriptor[0]读回来。数据基于先进先出的原则(通常写为FIFO)进行处理,这意味着如果把字节1,2,3写到file_deacriptor[1],从file_deacriptor[0]读取到的数据也会是1,2,3,这也栈的处理方式不同,栈采用后进先出,写作LIFO。
- 函数在两个程序之间传递数据不需要启动一个shell来解释所请求的命令,他同时还提供了对亵读写数据的更多控制。
- pipe函数的参数是一个由两个整数类型的文件描述符组成数组的指针。该函数在数组中天上两个新的文件描述符后返回0,如果失败则返回-1并设置error来表示失败的原因。
- 代码演示:父进程写入数据,子进程读取:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<assert.h> int main() { int fd[2];//写端描述符 assert(pipe(fd)!=-1); pid_t pid=fork();//open->fork,父子共享文件描述法 assert(pid!=-1); if(pid==0)//子进程 { close(fd[1]); char buff[128]={0}; read(fd[0],buff,127); printf(fd[0]); } else { close(fd[0]); write(fd[1],"hello",5); close(fd[1]); } exit(0); }
- 代码牵扯到一个知识点:先open打开文件再fork出子进程,此时,父子进程共享文件偏移量/文件描述符。注意:这里使用的文件描述符而不是文件流,所以我们必须用底层的read和write调用来访问数据,而不是用文件流库函数fread和fwrite。
fork,可以把fds[0][1]带给子进程,传递描述符
子进程可以共享,管道就通过fork把管道描述符带给fork
有名管道
- 有名管道实现任意进程间通信
- 有名管道的创建:
- 创建命令:mkfifo
- 打开管道:open();
- 读数据:read();
- 写入数据:write();
- 关闭管道:close()
- 代码实现有名管道间通信:
a.c:读取fifi的管道文件,写入5个字符的"hello"数据进去。
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<fcntl.h> #include<string.h> int main() { int fd=open("fifi",O_WRONLY); assert(fd!=-1); printf("fd=%d\n",fd); write(fd,"hello",5); close(fd); }
b.c
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<fcntl.h> #include<string.h> int main() { int fd=open("./fifi",O_RDONLY); assert(fd!=-1); printf("fd=%d\n",fd); char buff[128]={0}; read(fd,buff,127);//不读\0 printf("read %s\n",buff); close(fd); }
管道的特点
- 管道必须读,写进程同时open会阻塞
- 如果管道中没有数据,那么read就会堵塞
- 管道的写端关闭,读read返回值为0,终止程序
- 管道的读端关闭,写端写入时产生信号,终止程序
- 管道打开的时候只有只读和只写两种方式,不能是读写方式,因为读写方式是未定义的
- 管道是半双工的:
- 全双工、单工、半双工、
- 全双工:任意时刻都是双向的
- 半双工:某一时刻只能是一个方向
- 单:发送方和接收方只有一个方向
- 管道文件大小永远为0
四、管道的实现
open打开管道后,会开辟如下所示大小的空间:
头指针:当前待写入的数据位置
尾指针:当前待读取数据的位置,图示为即将读取h
abcdefg为已经读取的数据,用户使用过的空间可以被重新利用,当前空间满了可以写入在缓冲区指针位置。--相当于循环队列
IPC机制有:IPC进程间通信。