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


进程间通信

  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不对


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

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实验一 linux 内核编译及添加系统调用 设计目的 Linux 是开源操作系统,用户可以根据自身系统需要裁剪、修改内核,定制出功能更加 合适、运行效率更高的系统,因此,编译 linux 内核是进行内核开发的必要基本功。 在系统中根据需要添加新的系统调用是修改内核的一种常用手段,通过本次实验, 者应理解 linux 系统处理系统调用的流程以及增加系统调用的方法。 内容要求 (1) 添加一个系统调用,实现对指定进程的 nice 值的修改或取功能,并返回进程最 新的 nice 值及优先级 prio。建议调用原型为: int mysetnice(pid_t pid, int flag, int nicevalue, void __user * prio, void __user * nice); 参数含义: pid:进程 ID。 flag:若值为 0,表示取 nice 值;若值为 1,表示修改 nice 值。 Prio、nice:进程当前优先级及 nice 值。 返回值:系统调用成功时返回 0,失败时返回错误码 EFAULT。 (2) 一个简单的应用程序测试(1)中添加的系统调用。 (3) 若程序中调用了 linux 的内核函数,要求深入阅相关函数源码。 实验二 linux 内核模块编程 设计目的 Linux 提供的模块机制能动态扩充 linux 功能而无需重新编译内核,已经广泛应用在 linux 内核的许多功能的实现中。在本实验中将学习模块的基本概念、原理及实现技术,然后利 用内核模块编程访问进程的基本信息,从而加深对进程概念的理解、对模块编程技术的掌 握。 内容要求 (1) 设计一个模块,要求列出系统中所有内核线程的程序名、PID 号、进程状态及 进程优先级。 (2) 设计一个参数的模块,其参数为某个进程的 PID 号,该模块的功能是列出该 进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID 号。 (3) 请根据自身情况,进一步阅分析程序中用到的相关内核函数的源码实现。 实验四 linux 进程管理 设计目的 (1) 熟悉 linux 的命令接口。 (2) 通过对 linux 进程控制的相关系统调用的编程应用,进一步加深对进程概念的理解, 明确进程和程序的联系和区别,理解进程并发执行的具体含义。 (3) 通过 Linux 管道通信机制、消息队列通信机制、共享内存通信机制的使用,加深 对不同类型的进程通信方式的理解。 (4) 通过对 linux 的 Posix 信号量的应用,加深对信号量同步机制的理解。 (5)请根据自身情况,进一步阅分析相关系统调用的内核源码实现。 设计内容 (1)熟悉 linux 常用命令:pwd,useradd,passwd, who, ps, pstree, kill, top, ls, cd, mkdir, rmdir, cp, rm, mv, cat, more, grep 等。 (2) 实现一个模拟的 shell: 编三个不同的程序 cmd1.c,cmd2.c,cmd3.c,每个程序的功能自定,分别编译成可执 行文件 cmd1,cmd2,cmd3。然后再编一个程序,模拟 shell 程序的功能,能根据用户输 入的字符串(表示相应的命令名),去为相应的命令创建子进程并让它去执行相应的程序,而父进程则等待子进程结束,然后再等待接收下一条命令。如果接收到的命令为 exit,则父 进程结束;如果接收到的命令是无效命令,则显示“Command not found”,继续等待。 (3) 实现一个管道通信程序: 由父进程创建一个管道,然后再创建 3 个子进程,并由这三个子进程利用管道与父进程 之间进行通信:子进程发送信息,父进程等三个子进程全部发完消息后再接收信息。通信的 具体内容可根据自己的需要随意设计,要求能试验阻塞型过程中的各种情况,测试管道 的默认大小,并且要求利用 Posix 信号量机制实现进程间对管道的互斥访问。运行程序,观 察各种情况下,进程实际的字节数以及进程阻塞唤醒的情况。 (4) 利用 linux 的消息队列通信机制实现两个线程间的通信: 编程序创建两个线程:sender 线程和 receive 线程,其中 sender 线程运行函数 sender(), 它创建一个消息队列,然后,循环等待用户通过终端输入一串字符,将这串字符通过消息队 列发送给 receiver 线程,直到用户输入“exit”为止;最后,它向 receiver 线程发送消息“end”, 并且等待 receiver 的应答,等到应答消息后,将接收到的应答信息显示在终端屏幕上,删除 相关消息队列,结束程序的运行。Receiver 线程运行 rece
1. 实验目的 1) 加深对进程概念的理解,明确进程和程序的区别。 2) 进一步认识并发执行的实质。 3) 分析进程争用资源的现象,学习解决进程互斥的方法。 4) 学习解决进程同步的方法。 5) 了解Linux系统中进程通信的基本原理。   进程操作系统中最重要的概念,贯穿始终,也是学习现代操作系统的关键。通过本次实验,要求理解进程的实质和进程管理的机制。在Linux系统下实现进程从创建到终止的全过程,从中体会进程的创建过程、父进程和子进程之间的关系、进程状态的变化、进程之间的互斥、同步机制、进程调度的原理和以管道为代表的进程间的通信方式的实现。 2. 内容及要求:   这是一个设计型实验,要求自行编制程序。   使用系统调用pipe()建立一条管道,两个子进程分别向管道一句话:   Child process1 is sending a message!   Child process2 is sending a message!   父进程管道出来自两个子进程的信息,显示在屏幕上。   要求: 1) 父进程先接收子进程1发来的消息,然后再接收子进程2发来的消息。 2) 实现管道的互斥使用,当一个进程正在对管道进行操作时,另一子进程必须等待。使用系统调用lockf(fd[1],1,0)实现对管道的加锁操作,用lockf(fd[1],0,0)解除对管道的锁定。 3) 实现父子进程的同步,当子进程把数据管道后,便去睡眠等待;当父进程试图从一空管道取数据时,也应等待,直到子进程将数据管道后,才将其唤醒。 3.相关的系统调用 1) fork() 用于创一个进程。 格式:int fork(); 返回值:在子进程中返回0;在父进程中返回所创建的子进程的ID值;当返回-1时,创建失败。 2) wait() 常用来控制父进程与子进程的同步。 在父进程中调用wait(),则父进程被阻塞,进入等待队列,等待子进程结束。当子进程结束时,父进程从wait()返回继续执行原来的程序。 返回值:大于0时,为子进程的ID值;等于-1时,调用失败。 3) exit() 是进程结束时最常调用的。 格式:void exit( int status); 其中,status为进程结束状态。 4) pipe() 用于创建一个管道 格式:pipe(int fd); 其中fd是一个由两个数组元素fd[0]和fd[1]组成的整型数组,fd[0]是管道端口,用于从管道出数据,fd[1] 是管道端口,用于向管道入数据。 返回值:0 调用成功;-1 调用失败。 5) sleep() 调用进程睡眠若干时间,之后唤醒。 格式:sleep(int t); 其中t为睡眠时间。 6) lockf() 用于对互斥资源加锁和解锁。在本实验中,该调用的格式为: lockf(fd[1],1,0);/* 表示对管道入端口加锁。 lockf(fd[1],0,0);/* 表示对管道入端口解锁。 7) write(fd[1],String,Length) 将字符串String的内容管道入口。 8) read(fd[0],String,Length) 从管道入口出信息放入字符串String中。 4.程序流程 父进程: 1) 创建管道; 2) 创建子进程1; 3) 创建子进程2; 4) 等待从管道出子进程1入的数据,并显示在屏幕上; 5) 等待从管道出子进程2入的数据,并显示在屏幕上; 6) 退出。 子进程: 1) 将管道入口加锁; 2) 将信息“Child process n is sending message!”输入到变量OutPipe中,n=1,2; 3) 将OutPipe中信息管道; 4) 睡眠等待; 5) 将管道入口解锁; 6) 退出。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值