@bit::Shadow
✧(≖ ◡ ≖✿
目录
通信的目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程间共享同样的资源。
- 通知事件:一个进程需要向另一个/一组进程发送消息,通知它们发生了某种事件(如进程终止时通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug的进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
通信的本质
先让不同进程看到(维护)同一份资源(内存),然后才有通信的条件。
这份资源一般都为OS提供,OS的系统调用。
管道
管道作为一种进程间通信的较古老方式,依旧在后端中占有较重要地位,实现进程间通信的目的。
管道文件:只会被打开无需刷新的磁盘就可以被读取与写入,针对的是进程间的实时通信。
匿名管道
父子/pid相邻的进程间通信的中间件。
pipe()创建
cpp
int pipe(int fds[2]);
参数:
- 传递整型数组作为创建的管道的两端(本质是"文件"的两侧),读侧与写侧。
- 传递进的参数fds0被设置为读侧,fds1被设置为写侧。
返回值:
0:正常创建
-1:创建失败,errno被设置。
原理:通过创建当前进程的PCB指向的file_struct内未占用相邻文件流的fd使其分别指向"管道"的读端和写端。
图解

验证fds0 fds1
cpp
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
void test1() {
int fds[2] = {0};
int n = pipe(fds);
if(n == -1)
{
perror("pipe failed");
exit(1);
}
std::cout << fds[0] << " " << fds[1] << std::endl;
}
内核角度:
浅度:

深度:

真正的管道是内存级的,与磁盘无关联。并不会出现先前文件内容"数据刷新到磁盘"的现象。
所以真正的图片是:

父子进程关于pipe端口设计
在进程匿名管道的通信中,管道链接父子进程。
要求父端写入子端读取图解

父子通信实例
父端发送信息+模拟间隔。子端先阻塞等待,后接受后退出。
cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>
#include <cstdlib>
int main()
{
int fds[2];
// 创建匿名管道
if (pipe(fds) == -1)
{
perror("pipe failed");
return 1;
}
//父子
pid_t pid = fork();
if (pid == -1)
{
perror("fork failed");
return 1;
}
// ==================== 子进程:只读 ====================
if (pid == 0)
{
close(fds[1]); // 关闭写端
char buffer[1024];
ssize_t n;
//read阻塞等待
while ((n = read(fds[0], buffer, sizeof(buffer) - 1)) > 0)
{
buffer[n] = '\0';
std::cout << "[子进程] 收到: " << buffer << std::endl;
}
if (n == 0)
std::cout << "[子进程] 写端已关闭,退出\n";
else if (n == -1)
perror("read error");
close(fds[0]);
//子进程退出
exit(0);
}
// ==================== 父进程:只写 ====================
close(fds[0]); // 关闭读端
const char* messages[] = {
"Hello from parent",
"第二条消息",
"第三条消息",
nullptr
};
for (int i = 0; messages[i] != nullptr; ++i)
{
ssize_t written = write(fds[1], messages[i], strlen(messages[i]));
if (written == -1)
{
perror("write error");
break;
}
std::cout << "[父进程] 发送: " << messages[i] << std::endl;
sleep(1); // 模拟间隔发送
}
close(fds[1]); // 关闭写端 → 子进程 read() 返回 0(EOF)
// 等待子进程退出,回收资源
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
std::cout << "[父进程] 子进程退出码: " << WEXITSTATUS(status) << std::endl;
return 0;
}
管道与系统的关系:
1.由PCB->fieles_struct指向的文件流内缓冲区(Page Cache)是系统级缓冲,由内核统一管理,对用户透明,存在"落盘风险"(丢失数据)。
2.管道是纯内存匿名缓冲,根本没有对应的磁盘inode,所以"刷盘"对它而言毫无意义。
管道文件的五种特性
- 匿名管道常用来进行具有血缘关系的(fd临近)的进程间通信(常用于父子通信)。
- 管道文件自带同步机制。像上面的子进程每隔1秒写一次那么父进程的无延迟循环必须等待子进程的成功写入,否则阻塞(read)。
- 管道传输是面向字节流的。
- 匿名管道是单向通信的? 不,这取决于你的实现方式。
写满时发生:
模式: 操作 返回情况
阻塞(默认情况) write()强制挂起 等读端情况读取后继续执行
非阻塞 立即返回失败 处理返回失败的错误码情况
半双工与全双工
半双工:任意一个时刻,一个发出一个接受 或 另一个发出另一个接受。(半双工具有两种能力但两者不能同时存在)
全双工:任何时刻可以同时接收发送信息。(吵架)
匿名管道是半双工的一种特殊情况。
*系统退出管道会怎么样?
答:系统退出,OS检测发出异常,执行资源回收逻辑。
进程通信4种(异常)通信情况:
- 写慢读快,读端阻塞等待。
- 写快读慢,读端满则立即读取输出,读端不满缓冲等待。
- 写端关读端继续,读完(read() == 0)退出。
- 读端关写端继续,写端直接关闭。
管道的容量为64KB。
匿名管道模拟进程池
两种通信模式
普通模式:任务来了 → fork() → 处理 → 子进程退出
↑ 每次都有 fork/exit 开销
进程池模式:程序启动时预先 fork N 个子进程
任务来了 → 从池中取一个空闲进程 → 处理 → 归还池中
↑ 无 fork 开销,子进程常驻
进程池图解
图解1
图解2:

核心数据结构(类)
cpp
// 单个管道+子进程的封装
struct Channel
{
int _wfd; // 父进程持有的写端
pid_t _cpid; // 子进程 pid
std::string _name; // 调试用名称
Channel(int fd, pid_t pid, const std::string& name)
: _wfd(fd), _cpid(pid), _name(name) {}
};
// 进程池管理器
class ProcessPool
{
private:
std::vector<Channel> _channels;
int _num;
int _next; // 轮询下标
public:
ProcessPool(int num)
: _num(num), _next(0)
{
_channels.reserve(num);
for (int i = 0; i < num; ++i)
createWorker(i);
}
};
任务分发轮询调度方法:
cpp
void dispatch(int taskId)
{
// 轮询选择子进程(Round-Robin)
Channel& ch = _channels[_next % _num];
_next++;
std::cout << "[主进程] 分发任务" << taskId
<< " → " << ch._name
<< " (pid=" << ch._cpid << ")\n";
write(ch._wfd, &taskId, sizeof(taskId));
}
完整退出示例:
cpp
void ShutDown()
{
//1.关闭所有写端,子进程read()==0子进程自然退出
for(aotu& e:_channels)
{
close(e._wfd);
}
//2子进程退出后的僵尸进程处理
for(auto& e:_channels)
{
//waitpid(_channels._cpid,&status,0);
waitpid(_channels._cpid,nullptr,0);
}
}
完整流程:
close(写端)
↓
子进程 read() 返回 0(EOF)
↓
子进程退出循环,exit(0)
↓
父进程 waitpid() 回收,无僵尸
宏PIPE_BUF是管道原子写入的限制大小,它第一轮内核保证一次write()操作不会与其他的进程的写入交错的最大字节数4096.
感谢支持,持续更新
欢迎关注
