进程间通信-管道
进程间通信-管道
一、管道简述
- 管道是Unix中最古老的
进程间通信
的形式。 - 我们把从一个进程连接到另一个进程的一个数据流称为一个“
管道
” - 我们通常把是把一个进程的
输出
连接或“管接
”(经过管道来连接)到另一个进程的输入
- 管道实现
通信原理
:- 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不 到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
二、在shell中使用管道
链接shell命令
:把一个进程的输出直接馈入另一个的输入,命令格式如下
command1 | command2 | command3
操作符是:”|
”,它只能处理经由前面一个指令传出的正确输出信息
,对错误信息
没有直接处理能力。然后,传递
给下一个命令,作为标准的输入
.
管理命令的输出说明:
【指令1】 正确输出,作为 【指令2】的输入 然后**【指令2】的输出作为【指令3】的输入 ,【指令3】输出就会直接显示在屏幕**上面了。
通过管道之后【指令1】和【指令2】的正确输出不显示在屏幕上面
【提醒注意】:
-
管道命令只处理前一个命令正确输出,不处理错误输出;
-
管道命令右边命令,必须能够接收标准输入流命令才行;
举例应用:
- 读出logcat.log文件的内容,通过管道转发给grep作为输入内容
cat logcat.log | grep –n ‘ActivitManager’
- who|grep tty|wc –l 显示从控制台登录的人数
who|grep tty|wc –l
三、pipe管道
3.1- pipe函数说明
管道是一种最基本的IPC机制,可由pipe函数创建:
#include <unistd.h>
int pipe(int filedes[2]);
pipe管道
作用于有血缘关系
的进程之间,通过fork
来传递- 调用
pipe函数
时在内核
中开辟一块缓冲区(称为管道)
用于通信,它有一个读端一个
写端,然后通过filedes参数
传出给用户程序两个文件描述符
,filedes[0]指向管道的读
端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道 在用户程序看起来就像一个打开的文件,通过read(filedes[0]);
或者write(filedes[1]);
向这个文件读写数据其实是在读写内核缓冲区。 pipe函数
调用成功返回0
,调用失败返回-1
。
3.2- pipe函数实现通信的原理
- 1.
父进程
调用pipe开辟管道,得到两个文件描述符
指向管道的两端
。 - 2.父进程调用
fork
创建子进程
,那么子进程也有两个文件描述符指向同一管道
。 - 3.
父进程关闭管道读端,子进程关闭管道写端
。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列
实现的,数据从写端流入从读端流出,这样就实现了进程间通信
。
3.3-应用示例: 父进程写,子进程读
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd[2];
pid_t pid;
char str_write[1024] = "hello, I am content.";
char str_read[1024] = {0};
//创建管道,参数fd作为要传出去的两个文件描述符
if(pipe(fd) < 0)
{
//创建管道失败
perror("pipe");
exit(1);
}
//创建子进程
pid = fork();
//fd[0] 读端 , fd[1] 写端
//父进程
if(pid > 0)
{
close(fd[0]);//父进程 关闭读端
write(fd[1],str_write,strlen(str_write));
wait(NULL);//立即阻塞父进程,等待子进程退出
}
//子进程
else if (pid == 0)
{
int len;
close(fd[1]);//子进程 关闭写端
len = read(fd[0],str_read,sizeof(str_read));
write(STDOUT_FILENO,str_read,len);//把str_read输出到终端
}
return 0;
}
3.4- 管道读写规则
- 如果试图
从管道写端读取数据
,或者向管道读端写入数据都将导致错误发生 - 当
没有数据可读时
,read调用就会阻塞,即进程暂停执行,一直等到有数据来到为止。
如果管道的另一端已经被关闭
,也就是没有进程打开这个管道并向它写数据时,read调用就会阻塞 - 如果管道的
写端不存在
,则认为已经读到了数据的末尾,读函数返回的读出字节数为0; - 当管道的
写端存在
时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。 - 向管道中
写入数据
时,linux将不保证写入的原子性
,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。
四、fifo有名管道
4.1-fifo有名管道简述
-
匿名管道
应用的一个限制就是只能在具有共同祖先
(具有亲缘关系
)的进程间通信。
如果我们想在不相关
的进程之间交换数据,可以使用FIFO文件
来做这项工作,它经常被称为命名管道
。 -
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
-
$ mkfifo filename
-
-
命名管道也可以从程序里创建,相关函数有:
-
int mkfifo(const char *filename,mode_t mode); ```
-
mkfifo
创建的FIFO文件
的文件属性的第一位是p
4.2- FIFO的打开规则:
- 如果当前打开操作是为
读
而打开FIFO时,若已经有相应进程为写
而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO
(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。 - 如果当前打开操作是为
写
而打开FIFO时,如果已经有相应进程为读
而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO
(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
4.3-应用示例
用来写FIFO文件:fifo_w.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
// write
void sys_err(char *str,int exitno)
{
perror(str);
exit(exitno);
}
int main(int argc, char*argv[])
{
int fd;
char buf[1024] = "hello world\n";
if(argc < 2)
{
printf("enter fifoname\n");
exit(1);
}
fd = open(argv[1],O_WRONLY);
if(fd<0)
{
sys_err("open",1);
}
write(fd,buf,strlen(buf));
close(fd);
return 0;
}
用来读FIFO文件:fifo_r.c
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>
//read
void sys_err(char *str,int exitno)
{
perror(str);
exit(exitno);
}
int main(int argc, char*argv[])
{
int fd;
char buf[1024];
int len;
if(argc < 2)
{
printf("enter fifoname\n");
exit(1);
}
fd = open(argv[1],O_RDONLY);
if(fd<0)
{
sys_err("open",1);
}
len= read(fd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,len);
close(fd);
return 0;
}
终端上测试:
1.创建FIOF文件
2.编译文件
3.在终端1
上执行fifo_r
(用来读 创建的 fifo1文件中的数据)
但是现在没有进程为写
而打开该fifo1,所以终端1被阻塞
4.打开终端2,执行fifo_w
(用来向 创建的 fifo1文件中写入数据)
5.此时切换回终端1
,可以看到已经读到数据