云计算百科
云计算领域专业知识百科平台

linux 进程间通信

一、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:初始化进程池、派发任务、清理进程池。

关键流程:

  • 父进程创建管道。
  • fork 子进程。
  • 子进程关闭历史写端,关闭当前管道写端,dup2(pipefd[0], 0),然后执行 Worker。
  • 父进程关闭读端,保存写端。
  • 父进程轮询选择 Channel,发送任务编号。
  • 清理时父进程关闭所有写端,子进程 read 返回 0 后退出,父进程 waitpid 回收。
  • 要点:

    • 子进程只保留读端,父进程只保留写端。
    • 管道用于任务派发。
    • 回调函数 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,类似面向对象中的“继承”或“多态”。
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » linux 进程间通信
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!