Linux:带你理解进程间通信--管道

450 阅读8分钟

管道


进程间通信

  1. 进程间通信(IPC)是什么?

操作系统为用户提供的几种进程间的通信方式

  1. 为什么操作系统要为用户提供进程间通讯方式呢?

进程间因为每一个进程都有一个虚拟地址空间,在保证了进程独立性的同时,却使得进程间无法直接通信。因此需要操作系统来提供进程间通信方式,并且因为通信场景不同,提供的方式也有多种

  1. 如何提供进程间通信方式?

给多个进程之间提供一个大家都能访问到的传播介质,并且操作系统在提供进程间通信方式的时候也根据通信场景的不同提供不同的方式

  1. 进程间通信目的是什么?
  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变。
    进程间通信发展
  1. 进程间通信方式的种类: 数据传输,数据共享,进程控制
  • 管道—用于进程间的数据传输(从unix借鉴)

systemV标准的通信方式:

  • 共享内存—用于进程间的数据共享
  • 消息队列—用于进程间的数据传输
  • 信号量—用于实现进程间的控制

管道

什么是管道?

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
    在这里插入图片描述

管道的本质:

操作系统在内核中为进程创建的的一块缓冲区,若多个进程可以访问到同一块缓冲区,就可以实现进程间通信 —通过半双工通信(可以选择方向的单向通信)实现数据传输

匿名管道

定义:

在这块内核中的缓冲区没有明确的标识符,其他进程无法直接访问管道

特性:

  • 匿名管道只能用于具有亲缘关系的进程间通信
  • 匿名管道创建时,操作系统会提供两个操作句柄(文件描述符)(其中一个用于从操作管道读取数据,一个向管道中写入数据)
  • 因此只能通过创建子进程,子进程通过复制父进程的方式,获取到管道的操作句柄,进而实现访问同一个管道通信

多个进程只要能够拿到同一个管道(缓冲区)的操作句柄就可以进行通信

#include <unistd.h>
功能:创建一匿名管道
函数原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端 
返回值:成功返回0,失败返回错误代码(-1

在这里插入图片描述

注意:

  • 管道要不然只能读,要不然只能写(单向传输);使用的时候如果不使用哪一端,关闭哪一端就可以
  • 若管道中没有数据,则read就会阻塞;若管道中数据写满了则write 就会阻塞
从键盘读取数据,写入管道,读取管道,写到屏幕
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h>

int main()
{
    int fds[2];
    char buf[100];
    int len;
    
	if ( pipe(fds) == -1 ) 
		perror("make pipe error"),exit(1);
		
    // read from stdin
    while ( fgets(buf, 100, stdin) ) {
        len = strlen(buf);
        // write into pipe
        if ( write(fds[1], buf, len) != len ) {
            perror("write to pipe error");
			break; 
		}
        memset(buf, 0x00, sizeof(buf));
        
        // read from pipe
		if ( (len=read(fds[0], buf, 100)) == -1 ) { 
			perror("read from pipe error");
			break;
		}
		
        // write to stdout
        if ( write(1, buf, len) != len ) {
            perror("write to stdout error");
            break;
		} 	
	}
	return 0;
}

命名管道

定义:

内核中的缓冲区,这块缓冲区具有标识符(标识符是一个可见于文件系统的管道文件),其他的进程可以通过这个标识符,找到这个缓冲区(通过打开一个管道文件,进而访问到同一块缓冲区),进而实现通信

可用于同一主机上的任意进程间通信

多个进程通过命名管道通信时通过打开命令管道文件访问同一块内核中的缓冲区实现通信

命令操作: mkfifo filename

int mkfifo(const char *filename, mode_t mode) ---创建命名管道文件
  • filename:管道文件名称
  • mode: 文件权限
  • 返回值:成功返回0,失败返回-1

管道的特性

管道的读写: (不管是匿名还是命名都一样)

  1. 若管道中没有数据,则调用read读取数据会阻塞;若管道写满了,则调用write写入数据会阻塞;(阻塞:为了完成一个功能,发起调用,若当前不具备完成条件,则一直等待
  2. 若管道所有读端pipefd[0]被关闭,(表示没有进程读取数据了),继续write会触发问题,程序退出
  3. 若管道所有的写端pipefd[1]被关闭,(表示当前没有进程继续写入数据了),read读完管道中的数据之后,就不会再阻塞,而是返回0

管道的特性:

  1. 管道是一块缓冲区(内存空间),并非无限制大
  2. 管道生命周期随进程(打开管道的所有进程退出,管道就会被释放(命名管道也一样,本质都是缓冲区,文件只是标识符))
  3. 提供字节流服务 —有序的,可靠的,基于连接的一种灵活性比较高的传输服务

命名管道额外有打开(open)特性:

  1. 若管道文件以只读的方式打开,则会阻塞,直到这个管道文件被以写的方式打开
  2. 若管道文件以只写的方式打开,则会阻塞,直到这个管道文件被以读的方式打开
  3. 若管道以读写的方式打开,则不会阻塞

管道自带同步与互斥

  • 同步:通过条件判断,判断当前进程是否能访问,不能访问则等待,能访问的时候,再唤醒,实现对临界资源访问的合理性 (管道中没有数据则read会阻塞/管道中数据满了则write会阻塞)

  • 互斥:通过保证同一时间只有一个进程能够访问临界资源,保证临界资源访问的安全性对管道进行数据操作的大小不超过PIPE_BUF = 4096的时候,则保证操作的原子性

    • 临界资源:大家都能访问到的资源
    • 原子资源:不能被打断的操作,指的是一个操作要么一次完成,要么就不做

管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

在这里插入图片描述

匿名管道与命名管道的区别:

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

知识点习题

  1. 下列关于管道(Pipe)通信的叙述中,正确的是()

A. 一个管道可以实现双向数据传输
B. 管道的容量仅受磁盘容量大小限制
C. 进程对管道进行读操作和写操作都可能被阻塞
D. 一个管道只能有一个读进程或一个写进程对其操作

正确答案: C

答案解析:

A:由于管道采用半双工通信方式。因此,数据只能在一个方向上流动,A错
B: 管道是由内核管理的一个缓冲区,其容量受多方面因素影响,包括缓冲区的大小、磁盘容量大小等问题
C: 当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。进程对管道进行读操作和写操作都可能被阻塞,因此C正确
D: 管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息,管道可以同时进行读进程和写进程

  1. 下列关于管道(Pipe)通信的叙述中,正确的是()?

A. 进程对管道进行读操作和写操作都可能被阻塞
B. 一个管道只能有一个进程或一个写进程对其操作
C. 一个管道可实现双向数据传输
D. 管道的容量仅受磁盘容量大小限制

正确答案:A

答案解析

A.正确,因为管道为空,读操作会被阻塞;管道满了,写操作会被阻塞
B.可以有多个进程对其读;也可以有多个进程写,只不过不能同时写。并且题目没有说“同时”,B不对
C.匿名管道只能单向;命名管道可以双向;所以C过于绝对
D.管道是内存中的,所以D不对


如果有帮助到您的,留个赞呐~~