一、IPC 总览
1. IPC 目的
- 数据传输:一个进程把数据发给另一个进程。
- 资源共享:多个进程共享同一资源。
- 通知事件:通知另一个或一组进程发生了某事件。
- 进程控制:一个进程控制另一个进程,如 Debug 进程。
2. IPC 发展
- 管道
- System V IPC
- POSIX IPC
3. IPC 分类
- 管道
- 匿名管道:pipe
- 命名管道:FIFO、mkfifo
- System V IPC
- 消息队列
- 共享内存
- 信号量
- POSIX IPC
- 消息队列、共享内存、信号量
- 互斥量、条件变量、读写锁
4. 本节重点
- 掌握匿名/命名管道的原理与操作。
- 编写进程池。
- 掌握共享内存。
- 了解消息队列、信号量。
- 理解内核如何管理 IPC 资源。
- 了解 C/C++ 中回调、多态在进程池中的体现。
二、管道
1. 匿名管道 pipe
函数原型:
int pipe(int fd[2]);
- fd[0]:读端
- fd[1]:写端
- 成功返回 0,失败返回错误码。
本质:
- 管道是内核中的一块缓冲区。
- 从文件描述符角度看,管道也像文件,符合“Linux 一切皆文件”。
- 读写管道使用 read、write、close。
fork 后共享管道:
- 父进程创建管道。
- fork 后子进程继承 fd[0]、fd[1]。
- 通常父进程关闭读端,子进程关闭写端,实现单向通信。
- 双向通信需要两个管道。
管道特点:
- 只能用于有亲缘关系的进程,通常是父子进程。
- 半双工,数据单向流动。
- 提供流式服务,没有消息边界。
- 生命周期随进程,进程退出管道释放。
- 内核会对管道操作进行同步与互斥。
- 写端全关,read 返回 0。
- 读端全关,write 会产生 SIGPIPE。
2. 管道读写规则
没有数据可读:
- O_NONBLOCK 关闭:read 阻塞。
- O_NONBLOCK 开启:read 返回 -1,errno = EAGAIN。
管道满:
- O_NONBLOCK 关闭:write 阻塞,直到有进程读走数据。
- O_NONBLOCK 开启:write 返回 -1,errno = EAGAIN。
关闭情况:
- 所有写端关闭:read 返回 0。
- 所有读端关闭:write 触发 SIGPIPE,可能导致进程退出。
原子性:
- 写入数据量 <= PIPE_BUF:Linux 保证写入原子性。
- 写入数据量 > PIPE_BUF:不保证原子性。
3. 命名管道 FIFO
创建方式:
mkfifo filename
程序内:
int mkfifo(const char *filename, mode_t mode);
特点:
- 命名管道是文件系统中的特殊文件。
- 可用于无亲缘关系进程间通信。
- 匿名管道用 pipe 创建并打开;命名管道用 mkfifo 创建,用 open 打开。
- 创建方式不同,但打开之后语义相同。
打开规则:
- 读打开:
- O_NONBLOCK 关闭:阻塞,直到有进程为写而打开 FIFO。
- O_NONBLOCK 开启:立刻返回成功。
- 写打开:
- O_NONBLOCK 关闭:阻塞,直到有进程为读而打开 FIFO。
- O_NONBLOCK 开启:立刻返回失败,错误码 ENXIO。
典型用途:
- 文件拷贝。
- server/client 通信。
三、进程池
目标:
- 预先创建多个子进程。
- 父进程通过管道向子进程派发任务。
- 避免频繁 fork,提高效率。
核心结构:
- Channel:封装写端 fd、子进程 pid,提供 Send、Close。
- ProcessPool:负责创建管道、fork 子进程、保存 channel、派发任务、清理。
- TaskManager:任务函数表,随机选择任务编号。
- Worker:子进程循环从标准输入读任务编号并执行。
- Main:初始化进程池、派发任务、清理进程池。
关键流程:
要点:
- 子进程只保留读端,父进程只保留写端。
- 管道用于任务派发。
- 回调函数 std::function 体现多态思想。
- 清理时关闭写端是让子进程退出的关键。
四、System V 共享内存
共享内存是最快的 IPC 形式:
- 共享内存映射到进程地址空间后,进程间数据传递不再经过内核。
- 进程像访问普通内存一样读写共享内存。
1. 相关函数
shmget
int shmget(key_t key, size_t size, int shmflg);
作用:
- 创建或获取共享内存。
参数:
- key:共享内存名字,通常用 ftok 生成。
- size:共享内存大小。
- shmflg:
- IPC_CREAT:不存在则创建,存在则获取。
- IPC_CREAT | IPC_EXCL:不存在则创建,存在则报错。
- 权限标志如 0666。
返回值:
- 成功返回共享内存标识符 shmid。
- 失败返回 -1。
shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);
作用:
- 将共享内存段连接到当前进程地址空间。
参数:
- shmid:共享内存标识。
- shmaddr:
- NULL:内核自动选择地址。
- 非 NULL:按规则连接。
- shmflg:
- SHM_RND
- SHM_RDONLY
返回值:
- 成功返回指向共享内存首地址的指针。
- 失败返回 -1。
shmdt
int shmdt(const void *shmaddr);
作用:
- 将共享内存段与当前进程脱离。
注意:
- shmdt 不等于删除共享内存。
- 只是当前进程不再映射它。
shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
常用命令:
- IPC_STAT:获取共享内存状态。
- IPC_SET:设置共享内存状态。
- IPC_RMID:删除共享内存段。
2. 使用流程
ftok 生成 key
↓
shmget 创建/获取共享内存
↓
shmat 挂接到进程地址空间
↓
读写共享内存
↓
shmdt 去关联
↓
shmctl(IPC_RMID) 删除
3. 重要注意点
- 共享内存没有同步与互斥。
- 共享内存缺乏访问控制,会带来并发问题。
- 需要配合管道、信号量等实现访问顺序。
- IPC 资源生命周期随内核,必须显式删除,否则不会自动清除,除非重启。
- 查看共享内存:
ipcs -m
删除:
ipcrm -m shmid
4. 示例思路
- server 创建共享内存,并创建 FIFO。
- client 获取共享内存,写入数据。
- 通过 FIFO 进行访问控制,让 server 按顺序读取。
- server 最后 shmdt,再 shmctl(IPC_RMID) 删除共享内存。
五、System V 消息队列(了解)
- 消息队列提供从一个进程向另一个进程发送数据块的方法。
- 每个数据块都有类型。
- 接收者可以按类型接收数据。
- IPC 资源必须删除,否则不会自动清除。
- System V IPC 资源生命周期随内核。
六、System V 信号量(了解)
信号量主要用于同步和互斥。
基本概念:
- 共享资源:多个执行流能看到的同一份公共资源。
- 临界资源:被保护起来的共享资源。
- 临界区:访问临界资源的代码。
- 互斥:任何时刻只允许一个执行流访问资源。
- 同步:多个执行流访问临界资源时具有顺序性。
信号量理解:
- 本质是一个计数器。
- 作用:保护临界区。
- 本质:对资源的预订机制。
- P 操作:申请资源,计数器 –。
- V 操作:释放资源,计数器 ++。
七、内核如何组织管理 IPC 资源
参考 Linux 2.6.18 内核源码。
关键结构:
- struct ipc_ids
- in_use
- max_id
- seq
- mutex
- ipc_id_ary
- struct ipc_id_ary
- size
- struct kern_ipc_perm *p[0]
- struct kern_ipc_perm
- lock
- deleted
- key
- uid
- gid
- cuid
- cgid
- mode
- seq
- security
具体资源结构:
- 消息队列:struct msg_queue
- 信号量:struct sem_array
- 共享内存:struct shmid_kernel
理解:
- 内核用统一的 kern_ipc_perm 管理 IPC 资源的权限和公共属性。
- 具体资源结构内嵌 kern_ipc_perm,类似面向对象中的“继承”或“多态”。
网硕互联帮助中心




评论前必须登录!
注册