IPC 通信
IPC: Internal Process Communication 进程间通信
实质: 信息(数据)的交换(通信)
进程间无法直接共享内存空间(地址空间独立),需通过共享介质实现通信:
- 文件: 可支持通信但速度慢
- 内核空间: 操作系统内核开辟共享区域(高效)
IPC 方式
| 类型 | 具体实现 |
|---|---|
| 管道 | pipe(无名管道)、fifo(有名管道) |
| 信号 | signal |
| 信号量 | System V 信号量、POSIX 信号量 |
| 共享内存 | System V 共享内存、POSIX 共享内存 |
| Socket | Unix 域协议 |
| 消息队列 | 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)
作用: 内核传递整数信号值(无数据传输),不同值代表不同含义。
常见信号
| 信号 | 值 | 默认行为 | 触发场景 |
|---|---|---|---|
SIGHUP | 1 | Term | 控制终端挂起或控制进程死亡 |
SIGINT | 2 | Term | 键盘中断(Ctrl+C) |
SIGQUIT | 3 | Core | 键盘退出(Ctrl+\) |
SIGILL | 4 | Core | 非法指令 |
SIGABRT | 6 | Core | 调用 abort() 函数 |
SIGFPE | 8 | Core | 浮点运算异常 |
SIGKILL | 9 | Term | 强制杀死进程 |
SIGSEGV | 11 | Core | 非法内存访问 |
SIGPIPE | 13 | Term | 向无读端的管道写数据 |
SIGALRM | 14 | Term | alarm() 超时 |
SIGTERM | 15 | Term | 终止信号 |
SIGUSR1 | 30 | Term | 用户自定义信号 1 |
SIGUSR2 | 31 | Term | 用户自定义信号 2 |
SIGCHLD | 20 | Ign | 子进程停止或终止 |
信号处理方式
- 忽略: 不响应信号
- 默认行为: 操作系统预设处理(多数终止进程)
- 捕获: 绑定自定义处理函数
信号处理过程
通过 软中断 实现:
- 信号处理函数在 中断上下文 执行
- 进程状态分 用户态(执行用户代码)和 内核态(执行内核代码)
信号 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 共享内存流程
生成 IPC Key:
c#include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);创建/打开共享内存:
c#include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); /* @size: 共享内存大小(字节) @shmflg: IPC_CREAT | 权限(如 0666) - 创建 0 - 打开 @return: 共享内存 ID */映射共享内存:
cvoid *shmat(int shmid, const void *shmaddr, int shmflg); /* @shmaddr: 映射地址(通常为 NULL,由系统分配) @shmflg: SHM_RDONLY - 只读 0 - 读写 @return: 映射后的内存首地址 */解映射:
cint shmdt(const void *shmaddr); // 解除映射控制操作:
cint shmctl(int shmid, int cmd, struct shmid_ds *buf); /* @cmd: IPC_RMID - 删除共享内存 ... */
信号量机制
竞争条件 (Race Condition)
问题: 多进程并发访问共享资源导致结果不可预测。
解决: 通过信号量实现 有序访问(互斥或同步)。
信号量核心概念
- 信号量值: 整数计数器,表示可用资源数量
- 值=1:互斥信号量(一次只允许一个进程访问)
- 值>1:计数信号量(允许多个进程同时访问)
- 原子操作:
- P 操作(
wait):cwhile (sem_value <= 0); // 阻塞直到资源可用 sem_value--; // 占用资源 - V 操作(
signal):csem_value++; // 释放资源
- P 操作(
System V 信号量 API
创建/打开信号量集:
c#include <sys/sem.h> int semget(key_t key, int nsems, int semflg); /* @nsems: 信号量数量 @semflg: IPC_CREAT | 权限 @return: 信号量集 ID */控制信号量:
cint semctl(int semid, int semnum, int cmd, ...); /* @cmd: GETVAL - 获取信号量值 SETVAL - 设置信号量值 IPC_RMID - 删除信号量集 */P/V 操作:
cstruct 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);限时 P/V 操作:
cint semtimedop(int semid, struct sembuf *sops, size_t nsops, const struct timespec *timeout);
作业:售票系统设计
要求:
- 5个售票窗口(进程)
- 票信息从 "代售文件" 读取
- 每售出一张票写入 "已售文件"
实现要点:
- 共享资源:
- 票库存(共享内存)
- 代售/已售文件(需信号量保护)
- 信号量应用:
- 互斥信号量(值=1)保护文件读写操作
- 售票流程:c
P(semid); // 获取信号量 read_ticket_from_file(); // 读代售文件 update_ticket_count(); // 更新库存 write_sold_ticket_to_file(); // 写已售文件 V(semid); // 释放信号量
