为了实现进程间信息的交流,创造了管道。
想要让不同的进程看到同一份资源,前提是必须让不同进程看到同一份资源。
所以实现进程通信的方式是:在OS在创建一块内存,不属于A,也不属于B,同时A和B都是读取写入,那么就能实现进程间的通信。
进程间的通信分类
管道
- 匿名管道pipe
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
进程间的通信的管道分为匿名管道和命名管道,本文主要介绍匿名管道和命名管道。
将一个进程连接到另外一个进程的流,叫做管道。
命名管道和匿名管道的差异在于进程是否具有血缘关系。
匿名管道是由子进程文件描述符继承的方式,命名管道是文件缓冲区的方式。
一个新文件被创建时,会由OS分配fd文件描述符,并开辟内存缓冲区(在内存中,由操作系统提供)
子进程的创建,OS先描述再组织,子进程会继承父进程的进程组织内容包括文件描述符
此时,不同的俩个PCB就会包含同一张文件描述符表,父进程和子进程都会指向同一份文件。
一个文件有读写,对于子进程和父进程都能和对方都是双向的。
但是管道只具有单向性,只能从一方写入,另一方读取数据,因此需要关闭不需要的fd。
在创建文件时,必须读写都打开,fork之后才能保证管道数据的交互。
pipe函数
int pipe(int pipefd[2]);
作用是创建无名管道
创建成功返回0,失败返回-1.
pipefd是一个2参数的输出形参数,指向文件描述符fd
数组元素
含义 pipefd[0] 管道读端文件 pipefd[1] 管道写端文件
设计管道 子进程写入 父进程读取
pipefd是输出形参数
管道会开启读端和写端
文件描述符的0 1 2 被默认开启
此时父进程的文件描述符3 和4 都会指向管道
子进程继承父进程的fd文件描述符,同时读端和写端同时指向管道
为了单向通信,必须关闭子进程的读端。
利用snfprintf向缓冲区格式化写入数据。
子进程退出后,OS会自动关闭打开的读写端,因此直接exit。
父进程需要关闭写端 pipefd[0] 读写读写 0是读 1是写
利用read函数读取数据的时候,最后一个位置不读,用户自定义放\0,视作字符串看待
4)进程等待,意外退出时,回收子进程状态
点我提取完整代码
- 正常情况,管道没有数据了,读端必须等待,直到有数据(写端输入)
- 正常情况,管道被写满了,写端必须等待,直到有数据位置(读端读取数据)
- 写端被关闭,读端会一直读取数据,直到读取到0,表示文件末尾
- 读端被关闭,写端一直写入,OS就会杀掉写端进程(向目标进程发送SIGPIEP(13)终止信号)
- 匿名管道允许带有血缘的进程之间相互通信,子孙,父子等等,常用于父子通信
- 匿名管道,默认给读写端提供同步机制
- 面向字节流
- 管道周期随进程
- 管道是单向通信的,半双工通信的一种
命名管道是让俩个没有血缘关系的进程实现通信。本质还是要让进程看到同一份资源。
该管道是借助同一个文件实现的通信。
普通文件很难实现通信,即使通信也不安全。
命名管道和匿名管道一样,都是内存文件。命名管道在磁盘中有简单的映射,只不过这个映射的大小永远为0,并且不会被刷新到磁盘文件中。
mkfifo 命令创建文件
利用man mkfifo 指令查看mkfifo的资料
用于创建一个特殊的文件
mkfifo函数创建管道
man 3 mkfifo查看函数信息
函数原型
int mkfifo(const char *pathname, mode_t mode);
- 第一个参数为路径,如果不带路径,只有文件名,则在当前目录下创建
- 第二个参数为文件权限,受到权限掩码的影响
- 成功创建返回0 ,失败返回-1
本质在打开用一个文件,因为路径是唯一的。
如果进程A需要打开文件file,会在磁盘加载到内存上,文件内会分配缓冲区。file链接到fd文件符上,因此通过文件描述符就能找到struct并且找到缓冲区。如果进程B同时也访问file文件,磁盘则不会再次加载struct file 不会再分配缓冲区。因为OS不会做任何浪费空间和效率的行为,所以只需要一个计数器,负责链接时的增加,将进程B里PCB的fd链接到文件上。
这样进程A和进程B就能看到同一块缓冲区。
实现client和serve的通信
Makefile 自动化构建
.PHONY : all all:server client server:server.cc g++ -o $@ $^ -std=c++11 client:client.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f server client fifo
为了一次make就能编译完成server和client
通过依赖关系all all会向下寻找server.cc 和client.cc编译
创建fifo文件,如果创建失败,就打印错误码
main函数open以读的方式打开文件,文件不存在就会创建,利用goto语句回到文件的打开
从文件中读取数据
read函数将缓冲区的最后一个元素视作0 ,当作字符串
如果写端退出,读端会读到0 ,那么就要退出进程
#include
#include
#include
#include
#include
#include
#include
#include "comm.h"
//负责读取
bool Makefifo()
{
int n = mkfifo(FILENAME,0666);
if(n<0)
{
std::cerr<<"errno: "<0)
{
buff[n] = 0;
std::cout<<"client say: "<
首先需要打开文件
写入数据
每次读取一行,读取到数据后,就写入文件
写入失败就输出错误码
#include
#include
#include
#include
#include
#include
#include
#include"comm.h"
int main()
{
int wfd =open(FILENAME,O_WRONLY);
if(wfd<0)
{
std::cerr<<"error: "<
写端每次输入数据,读端立马就能获得数据,并打印。
常见管道有匿名管道和命名管道,它们的区别是是否具有血缘关系。
管道的四个情况,正常情况下都会等待。如果写端关闭,那么读端会读到0,如果读端关闭,OS会杀死进程。
通信的本质就是让进程看到同一份资源。匿名管道借助拷贝到的文件描述符里指向同一个文件。
命名管道利用同一路径的fifo文件,通过同一块缓冲区就能看到同一份资源。