目录
进程间通信
进程间通信目的
进程间通信的发展
进程间通信分类
1. 管道
2. System V IPC
3. POSIX IPC
管道
什么是管道
站在文件描述符角度-深度理解管道
站在内核角度-管道本质
编辑
匿名管道
测试匿名管道的读写
匿名管道的四大现象:
匿名管道的五大特性
进程间具有独立性 ,但是进程之间也需要数据传递等 ,管道应运而生.
进程间通信
进程间通信本质: 先让不同进程看到同一份资源.
进程间通信目的
- 数据传输:⼀个进程需要将它的数据发送给另⼀个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:⼀个进程需要向另⼀个或⼀组进程发送消息,通知它(它们)发生了某种事件(如进 程终⽌时要通知父进程)。
- 进程控制:有些进程希望完全控制另⼀个进程的执行(如Debug进程),此时控制进程希望能够 拦截另⼀个进程的所有陷⼊和异常,并能够及时知道它的状态改变。
进程间通信的发展
- 管道
- System V进程间通信
- POSIX进程间通信
进程间通信分类
1. 管道
匿名管道pipe
命名管道
2. System V IPC
System V 消息队列
System V 共享内存
System V 信号量
3. POSIX IPC
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
管道
什么是管道
- 管道是Unix中最古老的进程间通信的形式。(只能单项通信)
- 我们把从⼀个进程连接到另⼀个进程的⼀个数据流称为⼀个“管道”
我们原来在命令行使用的管道 | ,在命令行中集连了两个命令
站在文件描述符角度-深度理解管道
下面是进程打开同一个文件后 ,在创建子进程的情况 ,我们可以发现在内存中加载的文件内核缓冲区可以同时被两个进程都看到(进程的内核数据结构+ struct file 被拷贝 ,文件部分不需要拷贝)
补充:
- 文件只需要打开一次即可 ,多进程打开时 文件的引用计数++
- struct file中存储着各自进程对该文件的读写位置 ,父子进程对文件从读写位置是独立的 , 所以在创建子进程时需要拷贝struct file
在此管道中 读是通过一个struct file 写是通过另一个struct file
问题:
要是不关闭管道? 导致fd泄漏&&误操作
先创建管道到再创建子进程,父子进程才能看到同一个管道
站在内核角度-管道本质
匿名管道
利用上面的原理,设计了一种文件(纯内存级 ,与磁盘外设无关 ,没有名字 ),通过它可以实现进程间通信.
创建匿名管道用系统调用命令 ─── pipe
fd[0]表⽰读端, fd[1]表⽰写端
#include <unistd.h>
功能:创建⼀⽆名管道
原型int pipe(int fd[2]);
参数输出型参数fd:⽂件描述符数组, 其中fd[0]表⽰读端, fd[1]表⽰写端返回值:成功返回0,失败返回错误代码
测试匿名管道的读写
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<cstring>
int main()
{//创建管道int fds[2] = {0};int rfd = ::pipe(fds);if (rfd != 0)return 1;//创建子进程pid_t id = ::fork();// Fater process -> read// Child process -> writeif (id == 0){// child ,关闭读数据int CloseRet = ::close(fds[0]); int count = 5;const char *ch = "hello ubuntu";int len =strlen(ch);while (count--){ size_t n = ::write(fds[1], ch, len);if (n < 0){perror("write");return 4;}sleep(2);}::close(fds[1]);exit(0);}else{// parentint CloseRet = ::close(fds[1]);char buf[1024];while (true){size_t n = ::read(fds[0], buf, sizeof(buf)-1);if (n < 0){perror("read");return 4;}else if(n > 0){buf[n] = '\0';std::cout << "child--->father :" << buf << std::endl;}else {std::cout<<"child exit "<<std::endl;break;}}//等待子进程退出pid_t rid = waitpid(id, nullptr, 0);std::cout<<"wait child sucessfully"<<std::endl;::close(fds[0]);}return 0;
}
匿名管道的四大现象:
- 空管道读阻塞 管道为空且管道正常 ,read调⽤阻塞,即进程暂停执行,⼀直等到有数据来到为止(read是系统调用)
- 满管道写阻塞 管道写满且管道正常 ,write会阻塞,直到有进程读走数据 (write是系统调用)
- 写端关闭后读端返回
EOF
管道写端关闭 读端继续 ,一直读,读到0后 ,表示读到文件结尾,会自己关闭读端- 管道写段正常,读端关闭,write操作会产生信号SIGPIPE ,OS会直接杀死写入的进程!
PIPE_BUF为64KB(ubuntu下)
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
管道(Pipe)的 原子性(Atomicity) 指的是内核保证对管道的 单次读写操作是不可分割的,即使多个进程/线程并发访问,也不会导致数据混乱或中间状态被破坏。
匿名管道的五大特性
- 面向字节流
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,⼀个管道由⼀个进程创建,然后该进程调用fork,此后父,子进程之间就可应用该管道。
- 文件的生命周期随进程 ,管道也一样(引用计数)
- 管道是单向数据传递的,数据只能向⼀个方向流动;需要双方通信时,需要建立起两个管道
- 管道自带同步互斥等保护机制(同步:确保读写操作的合理时序,避免读空或写满。互斥:内核保证单次读写操作的原子性(如
write
不会中途被其他进程打断)