进程间通信之管道

目录

为什么需要进程间通信

进程间通信的目的

什么是管道?

匿名管道

管道的创建

管道的读写规则

管道的特点

管道的实现

命名管道(FIFO)

创建一个命名管道

匿名管道和命名管道的区别

命名管道读写规则


为什么需要进程间通信

进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源(例如打开的文件描述符)。

但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

管道通信方式

什么是管道?

  • 管道是Unix中最古⽼的进程间通信的形式。
  • 我们把从⼀个进程连接到另⼀个进程的⼀个数据流称为⼀个管道

通常通过符号“|"来使用管道.管道实际上是内核申请的一段内存.

 

 

匿名管道

匿名管道只能用于具有亲缘关系的进程之间的通信.例如父子进程和兄弟进程.

它是一种半双工的通信方式,具有固定的读端和写端.

管道可以说是特殊的文件,也可以使用read( )和write( )函数来进行读写,但他不属于文件系统,只能存在于内核的内存空间中.

管道的创建

pipe函数用于创建管道

包含头文件

#include<unistd.h>

函数原型

Int pipe( int fd[2]) )

函数传入值

fd[2]:一个整形数组只有两个元素表示管道的两个文件描述符,之后就可以直接操作这两个文件描述符.

函数返回值

成功返回0,失败返回-1.

管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1].其中fds[0]固定用于读管道,fds[1]固定用于写管道.

管道关闭只需将这两个文件描述符关闭即可,可使用普通的close( )逐个关闭文件描述符.

如图所示,用pipe()函数创建的管道两端处于同一个进程中,管道主要用于不同进程通信,因此实际意义不大.

实际中,通常是创建一个管道,再通过fork( )函数创建一个子进程,该子进程会继承父进程所创建的管道,此时父子进程分别拥有自己的读写管道.

为了实现父子进程之间的读写,只需要把无关的读端或写端的文件描述符关闭即可.此时,父子进程之间就建立了一条”子进程读取父进程写入”的管道.

 

父进程调用pipe开辟管道,得到两个文件描述符指向管道两端.

父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道.

 

父进程关闭管道读端,子进程关闭管道写端.父进程可以网管道里写,子进程可以从管道里读,管道是用环队列实现的,数据从写端流入从读端流出.这就实现了进程间通信.

 

管道的读写规则

如果所指向的管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读取数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样.

如果有指向管道写端的文件描述符没关闭,而持有写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了 才读取数据并返回.

如果所指向管道写端的文件描述符都关闭了,这时有进程向管道的写端write,那么该进程会受到信号SIGPIPE,通常会导致进程异常终止.

如果指向管道读端的文件描述符没有关闭,而持有管道读端的进程也没有从管道中读数据,这时进程向管道写端写数据,那么管道被写满时再次write会阻塞,直到管道中有空位置才写入数据并返回.

当要写的数据不大于pipe_buf,要保证写入的原子性

当要写的数据大于pipe_buf,linux不会保护写入的原子性

管道的特点

①管道是半双工的,也就是说,两个进程都能访问这个文件,假设进程1往文件内写东西,那么进程2 就只能读取文件的内容。
②只能用于具有血缘关系的进程间通信,通常用于父子进程建通信
③管道是基于字节流来通信的
④依赖于文件系统,它的生命周期随进程的结束结束,多个进程打开,只有所有进程结束才释放
⑤其本身自带同步互斥效果

管道的实现

从标准输入读取数据,写入管道,再从管道读数据,写入标准输出

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
	int fd[2];
	int ret = pipe(fd[2]);
	if (ret < 0)
	{
		perror("pipe");
		return 1;
	}
	while (1)
	{
		//从标准输入读数据
		char buf[1024] = { 0 };
		ssize_t read_size = read(0, buf, sizeof(buf)-1);
		if (read_size < 0)
		{
			perror("read");
			return 1;
		}
		if (read_size == 0)//读到EOF标志结束
		{
			printf("read done\n");
			return 0;
		}
		buf[read_size] = '\0';
		//把数据写到管道中
		write(fd[1], buf, strlen(buf));
		//从管道中读数据
		char buf_output[1024] = { 0 };
		read_size=read(fd[0], buf_output, sizeof(buf_output)-1);
		if (read_size < 0)
		{
			perror("read");
			return 1;
		}
		if (read_size == 0)
		{
			//如果管道的所有写端关闭,那么从读端读数据将会read返回0
			printf("pipe close!");
			return 0;
		}
		buf_output[read_size] = '\0';
		//把数据写到标准输出中
		write(1, buf_output, strlen(buf_output));
	}
	return 0;
}

 

命名管道(FIFO)

以FIFO文件的形式存储于文件系统中,能够实现任何两个进程之间通信。

命名管道是一种特殊类型的文件.

创建一个命名管道

 

 Mkfifo函数

 

包含头文件

#include<sys/types.h>

#include<sys/types.h>

函数原型

Int mkfifo(const char* filename,mode_t mode )

函数传入值

Filename:要创建的管道

 

Mode

O_RDONLY:读管道

 

 

O_WRONLY:写管道

 

 

O_RDWR:读写管道

 

 

O_NONBLOCK:非阻塞

 

 

O_CREAT:如果该文件不存在,就创建一个新文件,并用第三个参数为其设置权限

 

 

O_EXCL如果使用O_CREAT时文件存在,那么可返回错误消息,这一参数可测试文件是否存在

函数返回值

成功:0

失败:1

匿名管道和命名管道的区别

命名管道创建完成后就可以使用,其使用方法与管道一样

命名管道使用之前需要使用open()打开。命名管道是设备文件,它是存储在硬盘上的,而管道是存在内存中的特殊文件。

 

命名管道读写规则

命名管道调用open()打开有可能会阻塞,但是如果以读写方式(O_RDWR)打开则一定不会阻塞;

以只读(O_RDONLY)方式打开时,调用open()的函数会被阻塞直到有数据可读;

如果以只写方式(O_WRONLY)打开时同样也会被阻塞,知道有以读方式打开该管道。

 

同类阅读推荐:https://blog.csdn.net/skyroben/article/details/71513385

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值