目录
1. 进程间通信介绍
1-1 进程间通信目的
• 数据传输:⼀个进程需要将它的数据发送给另⼀个进程
• 资源共享:多个进程之间共享同样的资源。
• 通知事件:⼀个进程需要向另⼀个或⼀组进程发送消息,通知它(它们)发⽣了某种事件(如进
程终⽌时要通知⽗进程)。
• 进程控制:有些进程希望完全控制另⼀个进程的执⾏(如Debug进程),此时控制进程希望能够
拦截另⼀个进程的所有陷⼊和异常,并能够及时知道它的状态改变。
1-2 进程间通信发展
• 管道
• SystemV进程间通信
• POSIX进程间通信
1-3 进程间通信分类
管道
• 匿名管道pipe
• 命名管道
SystemVIPC
• SystemV消息队列
• SystemV共享内存
• SystemV信号量
POSIXIPC
• 消息队列
• 共享内存
• 信号量
• 互斥量
• 条件变量
• 读写锁
2. 管道
什么是管道?
管道是Unix中最古⽼的进程间通信的形式。
我们把从⼀个进程连接到另⼀个进程的⼀个数据流称为⼀个“管道”
3. 匿名管道
#include <unistd.h>
功能:
创建⼀⽆名管道
原型
int pipe(int fd[2]);
参数
fd:⽂件描述符数组,其中fd[0]表⽰读端, fd[1]表⽰写端
返回值:0成功返回,失败返回错误代码
3.1站在文件描述符角度-深度理解管道
3.2站在内核角度-管道本质
所以,看待管道,就如同看待文件⼀样!管道的使⽤和⽂件⼀致,迎合了“Linux⼀切皆⽂件思
想”。
4匿名管道代码实例
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
ssize_t FatherWrite(int wfd)
{
char buffer[1024];
int cnt = 0;
while (true)
{
snprintf(buffer, sizeof(buffer), "I'm Father,cnt=%d\n", cnt++);
write(wfd, buffer, strlen(buffer));
sleep(1);
}
}
ssize_t ChildRead(int rfd)
{
char buffer[1024];
while(true)
{
buffer[0]=0;
ssize_t n=read(rfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n]=0;
std::cout<<"Child read that Father Say:"<<buffer<<std::endl;
}
}
}
int main()
{
int fds[2] = {0};
int n = pipe(fds);
if (n < 0)
{
std::cerr << "pipe error" << std::endl;
exit(1);
}
std::cout << "fds[0]:" << fds[0] << std::endl;
std::cout << "fds[1]:" << fds[1] << std::endl;
pid_t id = fork();
if (id == 0) // 子进程
{
// c->r f->w
close(fds[1]);
ChildRead(fds[0]);
close(fds[0]);
exit(0);
}
close(fds[0]);
FatherWrite(fds[1]);
close(fds[0]);
waitpid(id, nullptr, 0);
return 0;
}
你可能会有疑惑----linux 匿名管道通信中为什么父子进程没发生写时拷贝?
一张图带你彻底弄清楚
首先理解什么是写时拷贝
写时拷贝的核心思想是:
1在资源复制时,不立即执行实际的数据复制
2多个对象共享相同的原始数据
3当任一对象尝试修改数据时
4系统在此时才创建该数据的专用副本
5修改操作在副本上执行,保持原始数据不变
当fork创建新进程时,struct_file是父子共享的文件
当父子进程对该文件进行修改时才会发生写时拷贝(修改用户空间)
所以为什么管道通信不触发写时拷贝?
管道数据在内核空间:当进程通过管道写入数据时,实际上是调用了系统调用(如write(fd[1], buffer, size)),将数据从用户空间的内存复制到内核的管道缓冲区。这个复制过程不涉及修改父子进程共享的用户空间内存,因此不会触发写时拷贝。
结论
在管道通信中,父子进程通过管道传递数据并不会触发写时拷贝,因为传递数据是通过系统调用将数据复制到内核的管道缓冲区,不涉及对共享用户内存页的修改。但是,在fork之后,父子进程各自拥有独立的进程空间,当它们修改自己的用户空间内存(例如,修改变量)时,写时拷贝就会发生。管道通信本身并不依赖共享内存(用户空间),而是通过内核缓冲区进行数据传递。
5.总结管道通信原理
1.管道创建:当父进程调用pipe()系统调用时,会创建一个管道,这是一个单向通信通道,返回两个文件描述符:fd[0]用于读取,fd[1]用于写入。
2. 创建子进程:父进程调用fork()创建子进程。此时,父子进程拥有相同的地址空间(包括文件描述符表),因此它们都拥有指向同一个管道(即同一对文件描述符)的能力。
3. 关闭未使用的文件描述符:通常,父进程关闭读端(fd[0]),子进程关闭写端(fd[1]),或者反之,这样形成了单向通信。