Skip to content

IPC 通信

IPC: Internal Process Communication 进程间通信
实质: 信息(数据)的交换(通信)

进程间无法直接共享内存空间(地址空间独立),需通过共享介质实现通信:

  1. 文件: 可支持通信但速度慢
  2. 内核空间: 操作系统内核开辟共享区域(高效)

IPC 方式

类型具体实现
管道pipe(无名管道)、fifo(有名管道)
信号signal
信号量System V 信号量、POSIX 信号量
共享内存System V 共享内存、POSIX 共享内存
SocketUnix 域协议
消息队列System V 消息队列、POSIX 消息队列

管道

无名管道 (pipe)

特点:

  • 无文件名(无 inode),内容存于内核
  • 两端分离:读端 (fd[0]) 和写端 (fd[1])
  • 顺序读写,不支持 lseek
  • 内容读后即消失
  • 生命周期:随内核持续

API:

c
#include <unistd.h>  
int pipe(int pipefd[2]);  
/*  
@描述: 创建无名管道  
@pipefd:  
   pipefd[0] - 读端文件描述符  
   pipefd[1] - 写端文件描述符  
@return: 成功返回0,失败返回-1(errno被设置)  
*/

注意: 关闭时先关写端再关读端。
限制: 仅用于有血缘关系的进程间通信(无文件名)。


有名管道 (fifo)

特点:

  • 文件系统中有文件名(含 inode)
  • 内容存于内核
  • 生命周期:
    • 文件名随文件系统持续
    • 内容随内核持续

API:

c
#include <sys/stat.h>  
int mkfifo(const char *pathname, mode_t mode);  
/*  
@描述: 创建有名管道文件  
@pathname: 管道文件名  
@mode: 权限(如 0664)  
@return: 成功返回0,失败返回-1(errno被设置)  
*/

关键机制:

  • 需同时打开读写两端才能传递数据
  • 阻塞模式(默认):
    • 读空管道时 read 阻塞
    • 写满管道时 write 阻塞
  • 非阻塞模式O_NONBLOCK):
    • 读空管道立即返回错误
    • 写满管道立即返回错误

信号 (Signal)

作用: 内核传递整数信号值(无数据传输),不同值代表不同含义。

常见信号

信号默认行为触发场景
SIGHUP1Term控制终端挂起或控制进程死亡
SIGINT2Term键盘中断(Ctrl+C)
SIGQUIT3Core键盘退出(Ctrl+\)
SIGILL4Core非法指令
SIGABRT6Core调用 abort() 函数
SIGFPE8Core浮点运算异常
SIGKILL9Term强制杀死进程
SIGSEGV11Core非法内存访问
SIGPIPE13Term向无读端的管道写数据
SIGALRM14Termalarm() 超时
SIGTERM15Term终止信号
SIGUSR130Term用户自定义信号 1
SIGUSR231Term用户自定义信号 2
SIGCHLD20Ign子进程停止或终止

信号处理方式

  1. 忽略: 不响应信号
  2. 默认行为: 操作系统预设处理(多数终止进程)
  3. 捕获: 绑定自定义处理函数

信号处理过程

通过 软中断 实现:

  • 信号处理函数在 中断上下文 执行
  • 进程状态分 用户态(执行用户代码)和 内核态(执行内核代码)

信号 API

发送信号
c
#include <signal.h>  
int kill(pid_t pid, int sig);  
/*  
@描述: 向指定进程发送信号  
@pid:  
  >0: 目标进程ID  
  =0: 同组所有进程  
  =-1: 所有有权限的进程  
  <-1: 组ID为 |pid| 的所有进程  
@sig: 信号值  
@return: 成功返回0,失败返回-1  
*/  

int raise(int sig);  // 等价于 kill(getpid(), sig)
捕获信号
c
#include <signal.h>  
typedef void (*sighandler_t)(int);  
sighandler_t signal(int signum, sighandler_t handler);  
/*  
@描述: 绑定信号处理函数  
@signum: 信号值  
@handler:  
  自定义函数地址  
  SIG_IGN(忽略)  
  SIG_DFL(默认行为)  
@return: 成功返回旧处理函数指针,失败返回SIG_ERR  
*/
定时信号
c
#include <unistd.h>  
unsigned int alarm(unsigned int seconds);  
/*  
@描述: 设置定时器(秒级)  
@seconds: 超时秒数  
@return: 剩余秒数(0表示无待处理闹钟)  
*/

注意: 同一进程只有一个有效闹钟。

等待信号
c
#include <unistd.h>  
int pause(void);  // 阻塞直到收到信号

共享内存

特点:

  • 多进程共享同一内存区域
  • 数据写入即时可见(无拷贝开销)
  • 生命周期:随内核持续

System V 共享内存流程

  1. 生成 IPC Key:

    c
    #include <sys/ipc.h>  
    key_t ftok(const char *pathname, int proj_id);
  2. 创建/打开共享内存:

    c
    #include <sys/shm.h>  
    int shmget(key_t key, size_t size, int shmflg);  
    /*  
    @size: 共享内存大小(字节)  
    @shmflg:  
       IPC_CREAT | 权限(如 0666) - 创建  
       0 - 打开  
    @return: 共享内存 ID  
    */
  3. 映射共享内存:

    c
    void *shmat(int shmid, const void *shmaddr, int shmflg);  
    /*  
    @shmaddr: 映射地址(通常为 NULL,由系统分配)  
    @shmflg:  
       SHM_RDONLY - 只读  
       0 - 读写  
    @return: 映射后的内存首地址  
    */
  4. 解映射:

    c
    int shmdt(const void *shmaddr);  // 解除映射
  5. 控制操作:

    c
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);  
    /*  
    @cmd:  
       IPC_RMID - 删除共享内存  
       ...  
    */

信号量机制

竞争条件 (Race Condition)

问题: 多进程并发访问共享资源导致结果不可预测。
解决: 通过信号量实现 有序访问(互斥或同步)。

信号量核心概念

  • 信号量值: 整数计数器,表示可用资源数量
    • 值=1:互斥信号量(一次只允许一个进程访问)
    • 值>1:计数信号量(允许多个进程同时访问)
  • 原子操作:
    • P 操作wait):
      c
      while (sem_value <= 0);  // 阻塞直到资源可用  
      sem_value--;             // 占用资源
    • V 操作signal):
      c
      sem_value++;  // 释放资源

System V 信号量 API

  1. 创建/打开信号量集:

    c
    #include <sys/sem.h>  
    int semget(key_t key, int nsems, int semflg);  
    /*  
    @nsems: 信号量数量  
    @semflg: IPC_CREAT | 权限  
    @return: 信号量集 ID  
    */
  2. 控制信号量:

    c
    int semctl(int semid, int semnum, int cmd, ...);  
    /*  
    @cmd:  
       GETVAL - 获取信号量值  
       SETVAL - 设置信号量值  
       IPC_RMID - 删除信号量集  
    */
  3. P/V 操作:

    c
    struct sembuf {  
        unsigned short sem_num;  // 信号量索引  
        short sem_op;            // 操作类型:  
                                //   >0: V操作(+sem_op)  
                                //   <0: P操作(-sem_op)  
                                //   =0: 尝试等待  
        short sem_flg;           // 标志:  
                                //   0 - 阻塞  
                                //   IPC_NOWAIT - 非阻塞  
                                //   SEM_UNDO - 进程退出时自动撤销操作  
    };  
    int semop(int semid, struct sembuf *sops, size_t nsops);
  4. 限时 P/V 操作:

    c
    int semtimedop(int semid, struct sembuf *sops, size_t nsops,  
                   const struct timespec *timeout);

作业:售票系统设计

要求:

  • 5个售票窗口(进程)
  • 票信息从 "代售文件" 读取
  • 每售出一张票写入 "已售文件"

实现要点:

  1. 共享资源:
    • 票库存(共享内存)
    • 代售/已售文件(需信号量保护)
  2. 信号量应用:
    • 互斥信号量(值=1)保护文件读写操作
  3. 售票流程:
    c
    P(semid);                     // 获取信号量  
    read_ticket_from_file();       // 读代售文件  
    update_ticket_count();         // 更新库存  
    write_sold_ticket_to_file();   // 写已售文件  
    V(semid);                     // 释放信号量

知识如风,常伴吾身