当前课程知识点:基于Linux的C++ > 第十三讲 进程编程 > 13.07 进程间通信(一) > LinuxCPP1307
接下来一节就是进程间通信
我们要谈到几个机制
一个是管道 一个是进程信号量
一个是共享内存 一个是映射内存
还有消息队列、套接字
有几个
这一讲里面我们会讨论
前面五种——管道、进程信号量、
共享内存、映射内存、消息队列
套接字我们会在网络那一讲里
再详细讨论
我们首先来看管道
管道是什么东西
管道是允许单向通讯的自动同步设备
它是一种半双工的模式
一般情况下我们是以半双工的模式
去使用它的
数据在写入端写入 在读取端读取
就这个意思
管道是一个典型的串型设备
数据的读取和写入
顺序是完全相同的
写入是什么顺序
读取出来 它也是什么顺序
这个就是管道
管道只能应用于有亲缘关系的进程
常常是父进程和子进程之间的通讯
我们使用管道
在使用管道的时候 特别注意
管道的数据容量是有限的
在大部分情况下
它是一个内存页面的大小
如果写入的速度超过了它读取的速度
写入的进程就会被阻塞
一直到管道中有空闲的空间
可以朝里写
那么如果读取的速度
超过了写入速度呢
你会把那个管道里的数据都读完的
读完了以后呢
读进程呢 它也会被阻塞
一直到那个管道里
有新的数据你可以去读取
创建管道那个函数叫pipe()
函数倒很简单
带一个只包含两个元素的整数数组
那是管道的文件描述符
因为一个管道有一个写入端
有一个读取端
每一端都有一个文件描述符
所以它是双文件描述符
那么这个数组保存的就是这个管道的
双文件描述符
0号元是读取端 一号元是写入端
注意这个
这个函数调用的时候
成功的时候会返回0
不成功的时候就会返回-1
设errno的值
调用的方式其实也简单
定义这一个包含两个元素的整数数组
然后调用pipe()
这个pipe()一调用
它就会给你构造一个管道
构造一个管道之后呢
它的文件描述符
就会给你写到这个数组里
pipe_fds这个数组里
它帮你写进去的
然后接下来你就可以用了
所以这个pipe_fds数组的0号元
就是管道读取端的文件描述符
1号元就是写入端的文件描述符
你就可以直接用它
操纵这个管道
我们看管道通讯的一个例子
我实现一个函数Write()
向这个特定的一个流里输出一串信息
这串信息我们会重复输出好几遍
所以这个信息是用message传进来的
流是一个文件指针
FILE * stream传进来的
这个消息写多少遍呢 写count遍
我就一个for循环朝这里面写数据
就完了
读呢 我们就从这个流里边去读这个数据
读这个数据的话我们就蛮贪心地读取
就是当这个流没有结束的时候
当读取这个信息没有出错的时候
当这个流里还有数据可以读的时候
我们就把这个数据全都读出来
然后读出来以后就向标准输出流里
输出这个数据就完了 很典型
这个数据从哪来呢 管道里来
这个数据朝哪里写呢 管道里写
你看我们父子进程
怎么通过这个管道传这个数据的
在主函数里调用pipe()构造一个管道
我们会创建这样的一个管道
然后fork() 创建一个子进程
用pid保存子进程的PID
if( pid== 0 )
OK 子进程close( fds[1] )
关闭管道的写入端
表示在我们的子进程里
我们只从管道里读数据
我们不向管道里写数据
刚才不说了嘛
管道 你创建出来
虽然有一个写入端 有一个读取端
但是对于两个进程
使用管道进行通讯的时候
一个进程只使用写入端
一个进程只使用读取端
所以它实际上是一个半双工的模式
你也可以倒过来
A进程写入 B进程读取
可以倒过来
A进程读取 B进程写入
但是正常情况下
一个管道只能一端读取 一端写入
两个进程一个读取、一个写入
这叫半双工的模式
双工模式就是双向读写嘛
半双工呢 就是确实它都可读可写
但是每次都只能这边读、那边写
或者这边写、那边读
你如果真让两个进程
同时要互相传递数据
都要读写的话
那就只能创建两个管道
就是这个意思
我们这里面只创建一个管道
创建一个管道 对于子进程来讲呢
我们只负责读取 从管道中读数据
所以写入端对我们没有用
我们直接关闭它的文件描述符
close( fds[1] ) 上来就关掉 不要
然后我们调用一个系统调用fdopen()
传文件描述符 以读模式打开它
得到它的文件指针 FILE *
这样的话就可以和C的函数
完全地吻合起来
因为C的很多文件操作
实际上使用的是文件指针
C标准库使用的是这个东西
我们可以使用FILE * stream
来得到这个管道的读取端的文件指针
fdopen()函数干的就是这个事
然后我们从这个流里读数据
读完了关闭这个管道的读取端
这是子进程
接下来是父进程
父进程呢 我们会构造一个缓冲区
要朝里面写数据
数据在哪呢 数据存在这个缓冲区里 对吧
我们会构造一系列的数据
这个数据我们怎么构造呢
就是定义一个buffer
这是一个缓冲区 buf_size
我定义成4096个字节 一个页面
然后我就朝里面写
我写多少呢 我写4094个字节
都是“abcdefg” 一直到“z”
然后再来一遍“abcdefg”
就26个字符、26个字符不停地往下写
我写了4094个字节
最后两个字节呢 我们封装成“\0”
表示这个串结尾了 就这个意思
我们就生成这些信息
然后关闭管道的读取端
我们只负责写入 我不负责读取
这是父进程
所以要做到这个事
然后我们fdopen( fds[1] )
以写模式打开它的管道写入端
得到FILE * 文件指针
得到文件指针之后
我们就像这个stream里写数据
写这个buffer里面的数据 写三遍
一次一个页面 一次一个页面
我们写三遍
写完了我们close( fds[1] )
把管道的写入端也给它关闭掉
这个就是管道通讯的示例
我们前边用到了一个很特殊的函数 dup()
现在我们就谈它怎么进行
文件描述符的复制
管道可以进行重定向
在这里使用到的一个非常重要的概念
叫等位文件描述符
所谓等位文件描述符
就是共享相同的文件位置
和状态标志设置
它们代表的是同一个文件和设备
这个就叫等位文件描述符
dup()函数就将两个文件描述符等位对待
认为它两者是一样的
这个函数有两个版本
一个叫dup()带单参数的版本oldfd
一个是 dup2() 单双参数的版本
一个是oldfd 一个是newfd
都将创建
oldfd这个老的文件描述符的一份副本
构造它的一个拷贝
第一个就是选择最小的文件描述符
来作为新的文件描述符
第二个呢 就是你传的那个参数
作为新的文件描述符
一旦构造出来这样一个副本之后
在你的文件描述符那个表格里面
那么就会有两个文件描述符
它后面跟着的那个文件
或设备的那个指针
就会变成同样的值
你比如说0号文件描述符
它后面跟着指向的
不是那个标准输入流那地方吗
stdin嘛 它就会指向那个地方
你把它dup( 0 )
就按我刚才那个例子
如果你把三个标准流全关掉了
那么你把哑终端
又构造到了0号文件描述符上
一旦你dup( 0 )
就把0号文件描述符的
那个指向文件或设备的那个指针
也写到了1号描述符的那个地方
它优先占用最小的文件描述符
0号被占用 1号还空闲
所以占用1号
那就意味着1号文件描述符
将会指向那个哑终端
也指向哑终端
再来一个dup( 0 )
它就把0号文件描述符
又写到2号文件描述符后边的那个区域
就意味着2号文件描述符后面那个
文件或设备的指针也指向哑终端
这就意味着
我们的标准输入流、标准输出流、
标准错误流都指向了哑终端嘛
dup()干的就是这个事
dup2()就是把那个新的文件描述符
设定给它 设定给新的文件描述符
你看dup2( fd, STDIN_FILENO ) 什么意思
就是把fd这个文件描述符
所对应的那个文件或设备的
那个指针 那个值传
给STDIN_FILENO 这个文件描述符
让它也指向那个地方
这就意味着标准输入流会被我们关闭
然后会被作为fd的一个副本重新打开
我们的输入就不再从键盘来
而是从fd所表示那个文件设备里边来
dup2()干的就是这个事
我们看这个例子
构造一个管道pipe( fds )
当然还是两个整数的一个数组
fork() 创建一个子进程
if( pid == 0 ) 说明它是子进程里
关闭0号元 我们只写入 不读取
然后把1号元和标准输出流
挂接在一起
这个管道不是写入嘛 对吧
然后就会挂接到我们的标准输出流
所以你一写就向标准输出流里输数据了
就这个意思
然后我们一执行
execvp() 就执行这个程序“ls”
所以这个数据就全写到了管道里
“ls”一运行
向标准输出流里写数据
标准输出流被我挂到哪去了
挂到管道的写入端了呀
所以这个数据就哗啦哗啦
就全写到管道里了
然后父进程就去读这个管道
我们关闭这个管道的写入端
我们就去读
打开这个管道的读取端
得到它的文件指针
然后我们就从这里边
就把这个写入过来的数据就全读出来了
所以你看我们这个程序
子进程就会“ls” 就列目录
把这个信息列出来以后
全扔到管道里
而我们父进程
就从管道里把那个信息就拿出来了
你写了什么
我们这里边就读出什么来
就这个模式
最后当父进程读取完全部的数据之后
关闭我们管道的读取端
最后waitpid()
等待我们的子进程结束
这就把我们前面讲到的几个知识点
都串在一起了
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口