目录
1. 进程间通信介绍
1-1 进程间通信目的
1-2 进程间通信发展
1-3 进程间通信分类
SystemVIPC
POSIXIPC
2. 管道
什么是管道?
3. 匿名管道
3.1站在文件描述符角度-深度理解管道
3.2站在内核角度-管道本质
4匿名管道代码实例
一张图带你彻底弄清楚
5.总结管道通信原理
1. 进程间通信介绍
1-1 进程间通信目的
• 数据传输:⼀个进程需要将它的数据发送给另⼀个进程
• 资源共享:多个进程之间共享同样的资源。
• 通知事件:⼀个进程需要向另⼀个或⼀组进程发送消息,通知它(它们)发⽣了某种事件(如进
程终⽌时要通知⽗进程)。
• 进程控制:有些进程希望完全控制另⼀个进程的执⾏(如Debug进程),此时控制进程希望能够
拦截另⼀个进程的所有陷⼊和异常,并能够及时知道它的状态改变。
1-2 进程间通信发展
• 管道
• SystemV进程间通信
• POSIX进程间通信
1-3 进程间通信分类
管道
• 匿名管道pipe
• 命名管道
SystemVIPC
• SystemV消息队列
• SystemV共享内存
• SystemV信号量
POSIXIPC
• 消息队列
• 共享内存
• 信号量
• 互斥量
• 条件变量
• 读写锁
2. 管道
什么是管道?
管道是Unix中最古⽼的进程间通信的形式。
我们把从⼀个进程连接到另⼀个进程的⼀个数据流称为⼀个“管道”
3. 匿名管道
#include
功能:
创建⼀⽆名管道
原型
int pipe(int fd[2]);
参数
fd:⽂件描述符数组,其中fd[0]表⽰读端, fd[1]表⽰写端
返回值:0成功返回,失败返回错误代码
3.1站在文件描述符角度-深度理解管道
3.2站在内核角度-管道本质
所以,看待管道,就如同看待文件⼀样!管道的使⽤和⽂件⼀致,迎合了“Linux⼀切皆⽂件思
想”。
4匿名管道代码实例
#include
#include
#include
#include
#include
#include
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:"< } } } 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]),或者反之,这样形成了单向通信。