一、按通信范围分类
-
同一主机进程通信
- 传统IPC方式:
- System V IPC:
- POSIX IPC(较新标准):
- POSIX消息队列
- POSIX信号量
- POSIX共享内存
-
跨主机进程通信
二、通信原理
- 进程空间独立,必须通过内核中转
- 内核在内核空间建立通信机制,用户空间通过系统调用访问
三、管道通信
1.无名管道(Pipe)
特性:
- 半双工通信(数据单向流动)
- 仅用于亲缘关系进程(父子/兄弟进程)
- 数据遵循FIFO原则
- 最大容量64KB(可通过
fcntl(fd, F_GETPIPE_SZ)
查询)
解释:
双工 ---发送和接收可以同时进行 --手机,电话
半双工 ---发送端 和 接收端 同一个时刻之后有一个起效 ---对讲机
单工 ---发送端 接收端固定 --- 广播
(一)函数原型
#include <unistd.h>
int pipe(int pipefd[2]);
(二)参数说明
- pipefd[2]:输出参数,用于接收两个文件描述符的数组
pipefd[0]
:管道的读端(从该描述符读取数据)pipefd[1]
:管道的写端(向该描述符写入数据)
(三)返回值
- 成功返回
0
- 失败返回
-1
,并设置 errno
: EMFILE
:进程打开的文件描述符过多ENFILE
:系统文件表已满EFAULT
:非法地址空间
(四)基础用法示例
#include<stdio.h>
#include<unistd.h>
#include <sys/wait.h>
#include<string.h>
#include <stdlib.h>int main(int argc, const char *argv[])
{int fd[2];int fd1[2];int ret = pipe(fd);ret = pipe(fd1);char buf[20] = {0};char buf1[1024] = {0};if(ret < 0){perror("pipe fail");return -1;}pid_t pid = fork();if(pid < 0){perror("fork fail");return -1;}if(pid > 0){close(fd[0]);close(fd1[1]);while(1){printf("f> ");fgets(buf,sizeof(buf),stdin);write(fd[1],buf,strlen(buf)+1);//加1是要保证输入的是字符串if(strncmp(buf,"quit",4) == 0){wait(NULL);exit(EXIT_SUCCESS);}read(fd1[0],buf1,sizeof(buf));printf("buf1 = %s\n",buf1);}}else if(pid == 0){close(fd[1]);close(fd1[0]);while(1){read(fd[0],buf,sizeof(buf));if(strncmp(buf,"quit",4) == 0){printf("child exit....\n");exit(EXIT_SUCCESS);}printf("buf = %s\n",buf);sprintf(buf1,"child %s",buf);write(fd1[1],buf1,strlen(buf1)+1);}}return 0;
}
(五)关键特性说明
1. 数据流向
- 单向流动:数据从写端(
pipefd[1]
)流向读端(pipefd[0]
) - 半双工:同一时刻只能有一个方向的数据流
2. 原子性保证
3. 阻塞行为
场景 | 读端行为 | 写端行为 |
---|
管道空 & 写端开放 | 阻塞等待数据 | - |
管道满 & 读端开放 | - | 阻塞直到有空间 |
所有读端关闭 | - | 触发 SIGPIPE 信号(默认终止进程) |
所有写端关闭 | read 返回 0(EOF) | - |
四、mkfifo
(一)函数原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
(二)参数说明
- pathname:管道文件的路径(建议使用绝对路径)
- mode:文件权限(实际权限受
umask
影响,建议搭配 umask(0)
使用)
(三)返回值
- 成功返回
0
- 失败返回
-1
,并设置 errno
: EEXIST
:文件已存在ENOENT
:路径不存在EACCES
:权限不足ENOSPC
:磁盘空间不足
(四)基础用法示例
写入端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
//./a.out fifo_A2B fifo_B2A
int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <fifo_A2B> <fifo_B2A>\n",argv[0]);return -1;}if(mkfifo(argv[1],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}if(mkfifo(argv[2],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}int fd1 = open(argv[1],O_WRONLY);int fd2 = open(argv[2],O_RDONLY);if (fd1 < 0 || fd2 < 0){perror("open fail");return -1;}pid_t pid = fork();if (pid < 0){perror("fork fail");return -1;}char buf[1024];if (pid > 0){close(fd2);while (1){printf("> ");fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = '\0';write(fd1,buf,strlen(buf)+1);if (strncmp(buf,"quit",4) == 0){wait(NULL);printf("father exit......\n");exit(0);}}}else if (pid == 0){close(fd1);while (1){printf("c> ");read(fd2,buf,sizeof(buf));printf("%s \n",buf);if (strncmp(buf,"quit",4) == 0){printf("child exit......\n");exit(0);}}}return 0;
}
读取端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>//./a.out fifo_A2B fifo_B2A
int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <fifo_A2B> <fifo_B2A>\n",argv[0]);return -1;}if(mkfifo(argv[1],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}if(mkfifo(argv[2],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}int fd1 = open(argv[1],O_RDONLY);int fd2 = open(argv[2],O_WRONLY);if (fd1 < 0 || fd2 < 0){perror("open fail");return -1;}pid_t pid = fork();if (pid < 0){perror("fork fail");return -1;}char buf[1024];if (pid > 0){close(fd1);while (1){printf("> ");fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = '\0';write(fd2,buf,strlen(buf)+1);if (strncmp(buf,"quit",4) == 0){wait(NULL);printf("father exit......\n");exit(0);}}}else if (pid == 0){close(fd2);while (1){printf("c> ");read(fd1,buf,sizeof(buf));printf("%s \n",buf);if (strncmp(buf,"quit",4) == 0){printf("child exit......\n");exit(0);}}}return 0;
}
(五)打开模式与阻塞关系表
打开方式 | 读端行为 | 写端行为 | 是否推荐使用 |
---|
O_RDONLY | 阻塞直到有写端打开 | - | ✅ 推荐 |
O_WRONLY | - | 阻塞直到有读端打开 | ✅ 推荐 |
O_RDWR | 立即返回(破坏FIFO语义) | 立即返回(破坏FIFO语义) | ❌ 禁止使用 |
`O_RDONLY | O_NONBLOCK` | 立即返回(无数据返回0,需检查errno) | - |
`O_WRONLY | O_NONBLOCK` | - | 立即返回(无读端时返回ENXIO错误) |
五、卸载管道unlink
(一)函数原型
#include <unistd.h>
int unlink(const char *pathname); // 正确拼写为 pathname
(二)功能说明
-
核心作用:
- 删除文件系统中的一个目录项
- 当文件引用计数归零时释放磁盘空间
-
对FIFO的特殊行为:
- 立即删除文件系统入口(文件不再可见)
- 已打开的管道描述符仍可继续使用
- 实际文件资源在所有进程关闭描述符后释放
(三)参数说明
参数 | 说明 |
---|
pathname | 要删除的有名管道完整路径 |
| 示例:"/tmp/my_fifo" |
(四)返回值
返回值 | 说明 |
---|
0 | 删除成功 |
-1 | 删除失败,可通过 errno 获取错误原因 |
(五)错误处理
errno 值 | 触发场景 | 处理方法 |
---|
EACCES | 权限不足(文件/目录不可写) | 检查文件权限或使用 sudo |
ENOENT | 文件不存在 | 先检查文件是否存在 |
EISDIR | 路径是目录 | 改用 rmdir 删除目录 |
EBUSY | 文件正在被使用(某些系统) | 关闭所有进程的文件描述符 |
(六)使用示例
1. 基础用法
const char *fifo_path = "/tmp/my_fifo";// 创建并删除管道
if (mkfifo(fifo_path, 0666) == -1) {perror("mkfifo error");exit(EXIT_FAILURE);
}// 使用管道...// 删除管道文件
if (unlink(fifo_path) == -1) {perror("unlink error");exit(EXIT_FAILURE);
}
2. 安全删除模式
#include <stdlib.h>
#include <signal.h>// 注册退出清理函数
void cleanup() {if (unlink("/tmp/my_fifo") == -1 && errno != ENOENT) {perror("cleanup error");}
}int main() {atexit(cleanup); // 正常退出时调用signal(SIGTERM, cleanup); // 捕获终止信号signal(SIGINT, cleanup); // 捕获Ctrl+C// ...其他代码...
}
(七)重要注意事项
-
延迟释放机制:
int fd = open("/tmp/fifo", O_RDONLY);
unlink("/tmp/fifo"); // 立即删除文件系统入口
read(fd, buf, size); // 仍然可以正常读取数据
close(fd); // 此时真正释放资源
-
多进程场景:
-
临时文件最佳实践:
// 创建临时管道(自动删除)
char tmp_path[] = "/tmp/fifo_XXXXXX";
mktemp(tmp_path); // 生成唯一名称
mkfifo(tmp_path, 0600); // 严格权限
unlink(tmp_path); // 立即标记删除
(八)与 remove() 的区别
特性 | unlink() | remove() |
---|
标准来源 | POSIX 系统调用 | C标准库函数 |
目录处理 | 不能删除目录 | 可删除空目录 |
封装实现 | 原始系统调用 | 内部调用 unlink/rmdir |
错误返回 | 通过 errno 获取 | 通过返回值判断 |
推荐场景 | 删除文件/FIFO | 跨平台文件删除 |
(九)开发建议
-
在程序启动时清理旧管道:
if (access(fifo_path, F_OK) == 0) {unlink(fifo_path);
}
-
使用绝对路径避免歧义:
// 错误示例
unlink("my_fifo"); // 可能误删其他目录文件// 正确示例
unlink("/var/run/myapp_fifo");
-
监控文件状态:
struct stat st;
if (stat(fifo_path, &st) == 0) {if (S_ISFIFO(st.st_mode)) {// 确认是管道文件再删除unlink(fifo_path);}
}
📌 关键点:unlink
只是删除文件链接,实际资源释放需要等待所有引用关闭。对于管道文件的清理,建议结合引用计数和进程生命周期管理来实现安全删除。
六、信号处理的注册函数
(一)信号本质
- 软中断机制:操作系统内核向进程发送的异步通知
- 中断模型对比:
类型 | 中断源 | 处理程序 | 响应方式 |
---|
硬件中断 | 硬件设备 | 内核ISR | 立即处理 |
信号 | 软件事件 | 用户定义处理函数 | 异步延迟处理 |
(二)核心API - signal()
#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);
参数说明
参数 | 类型 | 说明 |
---|
signum | int | 要处理的信号编号(如 SIGINT) |
handler | sighandler_t | 信号处理函数指针 |
特殊 handler 值
值 | 作用 |
---|
SIG_DFL | 恢复默认处理 |
SIG_IGN | 忽略该信号 |
返回值
- 成功:返回之前的处理函数指针
- 失败:返回 SIG_ERR((sighandler_t)-1)
(三)信号处理函数
1. 函数原型
void handler(int signo) {/* 信号处理逻辑 */
}
2. 重要特性
- 参数 signo:接收到的信号编号(可通过
strsignal(signo)
获取信号名) - 执行环境:在进程的上下文随机时刻运行(异步执行)
- 限制:
- 禁止使用不可重入函数(如 malloc、printf)
- 应尽量保持处理逻辑简单
(四)关键信号列表
信号 | 值 | 默认行为 | 可否捕获 | 说明 |
---|
SIGHUP | 1 | Terminate | 是 | 终端挂断 |
SIGINT | 2 | Terminate | 是 | 键盘中断(Ctrl+C) |
SIGQUIT | 3 | Core Dump | 是 | 键盘退出(Ctrl+\) |
SIGILL | 4 | Core Dump | 是 | 非法指令 |
SIGABRT | 6 | Core Dump | 是 | abort() 产生 |
SIGFPE | 8 | Core Dump | 是 | 算术异常 |
SIGKILL | 9 | Terminate | 否 | 强制终止 |
SIGSEGV | 11 | Core Dump | 是 | 无效内存访问 |
SIGPIPE | 13 | Terminate | 是 | 管道破裂 |
SIGALRM | 14 | Terminate | 是 | 定时器超时 |
SIGTERM | 15 | Terminate | 是 | 终止信号 |
SIGCHLD | 17 | Ignore | 是 | 子进程状态改变 |
SIGSTOP | 19 | Stop Process | 否 | 暂停进程(Ctrl+Z) |
SIGCONT | 18 | Continue | 是 | 继续执行被暂停的进程 |
(五)信号处理流程
- 信号产生:由内核、进程或终端产生
- 信号递送:被目标进程接收
- 处理响应:
- 执行默认操作
- 执行自定义处理函数
- 忽略信号(SIG_IGN)
(六)典型应用场景
- 进程控制:处理 Ctrl+C 终止请求
- 异常处理:捕获段错误进行日志记录
- 进程通信:通过 SIGUSR1/SIGUSR2 自定义协议
- 资源管理:处理子进程退出(SIGCHLD)
- 定时操作:使用 SIGALRM 实现超时机制
七、发送信号函数 kill()
(一)函数原型
#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);
(二)参数详解
参数 | 类型 | 说明 |
---|
pid | pid_t | 目标进程标识符,支持多种特殊值 |
sig | int | 要发送的信号编号(可通过 kill -l 查看信号列表) |
pid 的取值规则:
值 | 作用范围 |
---|
> 0 | 发送给指定PID的进程 |
= 0 | 发送给当前进程所在进程组的所有进程 |
= -1 | 发送给所有有权限发送的进程(相当于广播) |
< -1 | 发送给进程组ID为 ` |
常用信号编号:
信号 | 值 | 说明 |
---|
SIGHUP | 1 | 终端挂断 |
SIGINT | 2 | 中断(Ctrl+C) |
SIGQUIT | 3 | 退出(Ctrl+\) |
SIGKILL | 9 | 强制终止(不可捕获) |
SIGTERM | 15 | 终止信号(默认) |
SIGUSR1 | 10 | 用户自定义信号1 |
SIGUSR2 | 12 | 用户自定义信号2 |
(三)返回值
返回值 | 说明 |
---|
0 | 成功发送信号 |
-1 | 发送失败,可通过 errno 获取错误原因 |
常见错误码:
errno | 说明 |
---|
EINVAL | 无效信号编号 |
EPERM | 权限不足(不能向目标进程发送信号) |
ESRCH | 目标进程/进程组不存在 |
EFAULT | 非法内存访问 |
(四)使用示例
1. 终止指定进程
pid_t target_pid = 1234;if (kill(target_pid, SIGTERM) == -1) {if (errno == EPERM) {fprintf(stderr, "Permission denied to send signal\n");} else if (errno == ESRCH) {fprintf(stderr, "Process %d does not exist\n", target_pid);} else {perror("kill failed");}
}
2. 发送给进程组
// 发送SIGKILL给进程组5678的所有进程
if (kill(-5678, SIGKILL) == -1) {perror("Failed to kill process group");
}
3. 广播信号
// 向所有有权限的进程发送SIGUSR1(慎用!)
if (kill(-1, SIGUSR1) == -1) {perror("Broadcast failed");
}
(五)重要注意事项
-
权限控制:
- 普通用户只能向自己的进程发送信号
- root 用户可以向所有进程发送信号
- 发送 SIGKILL 需要额外权限
-
信号处理限制:
// SIGKILL 和 SIGSTOP 永远不能被捕获或忽略
signal(SIGKILL, SIG_IGN); // 此调用会失败
-
僵尸进程:
- 无法向僵尸进程(Zombie)发送信号
- 发送信号给僵尸进程会返回 ESRCH 错误
-
原子性保证:
- 当向进程组发送信号时,保证所有目标进程都能收到信号后才返回
(六)扩展应用
1. 进程存在性检测
// 通过发送空信号(0)检测进程是否存在
int process_exists(pid_t pid) {if (kill(pid, 0) == 0) {return 1; // 存在} else {if (errno == ESRCH) return 0; // 不存在else return -1; // 权限不足等其他错误}
}
2. 实现 kill 命令
// 类似命令行工具的实现
if (argc != 3) {fprintf(stderr, "Usage: %s <signal> <pid>\n", argv[0]);exit(EXIT_FAILURE);
}int sig = atoi(argv[1]);
pid_t pid = atoi(argv[2]);if (kill(pid, sig) == -1) {perror("kill error");exit(EXIT_FAILURE);
}
八、raise() 函数
1. 函数原型
#include <signal.h>
int raise(int sig);
2. 功能说明
- 向当前进程发送指定信号
- 等价于
kill(getpid(), sig)
3. 返回值
返回值 | 说明 |
---|
0 | 发送成功 |
非0 | 发送失败(通常为无效信号) |
4. 使用示例
// 触发段错误(用于调试)
raise(SIGSEGV);// 优雅退出
if (error_condition) {raise(SIGTERM);
}
九、alarm() 函数
1. 函数原型
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
2. 功能说明
- 设置单次定时器,经过
seconds
秒后向进程发送 SIGALRM
信号 - 特性:
- 每个进程只能有一个活跃的 alarm 定时器
- 新调用会覆盖前一个定时器
- 精度为秒级(实际误差可能达 ±1 秒)
3. 返回值
返回值 | 说明 |
---|
0 | 首次设置定时器 |
剩余秒数 | 覆盖已有定时器时的剩余时间 |
4. 使用示例
检测用户是否输入, 如果用户3s没有输入, 则超时一次, 如果超时3次自动退出
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>int cnt = 0;void do_signal(int signal)
{alarm(3);cnt++;printf("cnt = %d\n",cnt);if(cnt == 3){exit(0);}
}int main(int argc, const char *argv[])
{alarm(3);signal(SIGALRM,do_signal);char buf[1024];while(1){fgets(buf,sizeof(buf),stdin);printf("buf = %s\n",buf);alarm(3);cnt = 0;}return 0;
}
三、核心机制对比
特性 | raise() | alarm() |
---|
信号源 | 进程主动触发 | 内核定时器触发 |
信号目标 | 只能是当前进程 | 当前进程 |
信号类型 | 任意有效信号 | 固定为 SIGALRM (14) |
时间控制 | 立即发送 | 延迟发送(秒级精度) |
主要应用场景 | 异常处理/进程自终止 | 超时控制/定时任务 |
十、pause() 函数详解
(一)函数原型
#include <unistd.h>
int pause(void);
(二)功能说明
- 核心作用:使调用进程挂起,永久等待直到收到任意信号
- 典型应用:
- 实现进程的事件驱动模型
- 创建守护进程的主循环
- 等待异步信号触发操作
(三)返回值
返回值 | 说明 |
---|
-1 | 总是返回 -1(被信号中断后返回) |
errno | 设置为 EINTR(被信号中断) |
(四)使用示例
基础用法
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void handler(int sig) {printf("Received signal %d\n", sig);
}int main() {signal(SIGUSR1, handler);printf("PID=%d, waiting for signal...\n", getpid());pause(); // 阻塞直到收到信号printf("Resuming execution\n");return 0;
}
结合 alarm() 实现超时等待
void alarm_handler(int sig) {printf("Timeout!\n");
}int main() {signal(SIGALRM, alarm_handler);alarm(5); // 设置5秒超时printf("Waiting for event (max 5s)...\n");pause(); // 可能被SIGALRM或目标信号中断if(errno == EINTR) {printf("Wait interrupted\n");}return 0;
}

十一、ftok()
函数详解
(一)函数原型
#include <sys/ipc.h>
#include <sys/types.h>key_t ftok(const char *pathname, int proj_id);
(二)功能说明
- 核心作用:通过文件路径和项目ID生成唯一IPC键值
- 典型应用:为共享内存、消息队列等System V IPC对象创建唯一标识符
(三)参数详解
参数 | 类型 | 说明 |
---|
pathname | const char* | 已存在的文件路径(建议选择稳定文件) |
proj_id | int | 项目标识符(仅使用低8位,范围0-255) |
推荐参数选择
- pathname:
/etc/passwd
(系统文件,稳定)- 应用程序专属配置文件
- proj_id:
- 使用ASCII字符(如
'a'
→ 97) - 范围:
0 ≤ proj_id ≤ 255
(四)返回值
返回值 | 说明 |
---|
有效key_t | 生成的IPC键值 |
(key_t)-1 | 生成失败,检查errno:<br>• ENOENT(文件不存在)<br>• EACCES(无权限) |
(五)工作原理
键值生成算法
key = (file_st.st_ino & 0xFFFF) | ((file_st.st_dev & 0xFF) << 16) | ((proj_id & 0xFF) << 24)
- file_st.st_ino:文件inode号(取低16位)
- file_st.st_dev:文件系统设备号(取低8位)
- proj_id:用户指定值(取低8位)
键值冲突概率
- 相同参数 → 相同键值
- 不同文件 → 可能产生相同键值(概率低,但存在)
(六)使用示例
基础用法
#include <stdio.h>
#include <sys/ipc.h>int main() {const char *path = "/etc/passwd";int proj_id = 'A'; // ASCII 65key_t key = ftok(path, proj_id);if (key == (key_t)-1) {perror("ftok failed");return 1;}printf("Generated key: 0x%08x\n", (unsigned int)key);return 0;
}
错误处理
key_t key = ftok("/non/existent/file", 1);
if (key == (key_t)-1) {if (errno == ENOENT) {fprintf(stderr, "Path does not exist\n");} else if (errno == EACCES) {fprintf(stderr, "Permission denied\n");}exit(EXIT_FAILURE);
}
十二、共享内存创建/获取函数 shmget
(一)函数原型
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
(二)核心功能
- 创建新共享内存段:当
key
不存在且使用 IPC_CREAT
- 获取已有共享内存:当
key
已存在 - 独占创建检测:配合
IPC_EXCL
使用
(三)参数详解
参数 | 类型 | 说明 |
---|
key | key_t | 共享内存键值(IPC_PRIVATE 或 ftok 生成) |
size | size_t | 共享内存大小(字节) |
shmflg | int | 权限标志与创建选项的组合 |
1. key 的取值规则
键值类型 | 适用场景 | 示例 |
---|
IPC_PRIVATE | 亲缘关系进程间共享 | shmget(IPC_PRIVATE, ...) |
自定义键值 | 任意进程通过相同键值访问 | shmget(0x1234, ...) |
2. shmflg 组合方式
标志 | 说明 |
---|
IPC_CREAT | 如果不存在则创建 |
IPC_EXCL | 配合 IPC_CREAT 使用,若存在则失败 |
权限模式 | 八进制权限(如 0666 ) |
典型组合:
// 创建新共享内存(如果存在则报错)
IPC_CREAT | IPC_EXCL | 0666// 获取或创建共享内存
IPC_CREAT | 0666
(四)返回值
返回值 | 说明 |
---|
正整数 | 共享内存标识符(shmid) |
-1 | 失败,检查 errno :<br>• EEXIST(已存在)<br>• ENOENT(不存在)<br>• EACCES(权限不足) |
(五)使用示例
1. 创建新共享内存
#define SHM_SIZE 4096int main() {key_t key = ftok("/tmp", 'A');if (key == -1) {perror("ftok failed");exit(EXIT_FAILURE);}// 创建共享内存(存在则失败)int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);if (shmid == -1) {if (errno == EEXIST) {fprintf(stderr, "Shared memory already exists\n");} else {perror("shmget failed");}exit(EXIT_FAILURE);}printf("Created shmid: %d\n", shmid);return 0;
}
2. 获取已有共享内存
int get_existing_shm(key_t key) {// 不指定大小,仅获取已有内存int shmid = shmget(key, 0, 0);if (shmid == -1) {perror("Access failed");exit(EXIT_FAILURE);}return shmid;
}
(六)共享内存生命周期
操作 | 函数 | 说明 |
---|
创建 | shmget | 内核分配资源 |
附加到进程空间 | shmat | 映射到进程地址空间 |
分离 | shmdt | 解除映射关系 |
控制/删除 | shmctl | 设置状态或删除共享内存 |
重要特性:
- 共享内存会持续存在直到:
- 所有进程分离(
shmdt
) - 被显式删除(
shmctl(..., IPC_RMID, ...)
) - 系统重启
(七)相关命令
# 查看共享内存段
ipcs -m# 删除指定共享内存
ipcrm -m <shmid># 查看系统限制
cat /proc/sys/kernel/shmmax # 最大单段内存大小
十三、共享内存附加函数 shmat
(一)函数原型
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
(二)核心功能
- 内存映射:将共享内存段附加到进程的地址空间
- 访问控制:设置读写权限(完整访问/只读)
- 地址管理:自动或手动指定映射地址
(三)参数详解
参数 | 类型 | 说明 |
---|
shmid | int | 共享内存标识符(由shmget 获得) |
shmaddr | const void* | 建议映射地址(通常设为NULL 由系统自动分配) |
shmflg | int | 附加标志:<br>• 0 - 可读写<br>• SHM_RDONLY - 只读 |
(四)返回值
返回值 | 说明 |
---|
有效指针 | 映射到进程空间的起始地址 |
(void*)-1 | 附加失败,检查errno :<br>• EINVAL(无效shmid)<br>• EACCES(权限不足) |
(五)使用示例
基础用法
int shmid = shmget(ftok("/tmp", 'A'), 4096, IPC_CREAT | 0666);
if (shmid == -1) {perror("shmget failed");exit(EXIT_FAILURE);
}// 附加共享内存(可读写)
char *shm_ptr = (char*)shmat(shmid, NULL, 0);
if (shm_ptr == (void*)-1) {perror("shmat failed");exit(EXIT_FAILURE);
}// 使用共享内存
strcpy(shm_ptr, "Hello Shared Memory");
printf("Data in SHM: %s\n", shm_ptr);// 分离内存
if (shmdt(shm_ptr) == -1) {perror("shmdt failed");
}
只读模式
// 以只读方式附加
void *read_ptr = shmat(shmid, NULL, SHM_RDONLY);
if (read_ptr == (void*)-1) {perror("Read-only attach failed");exit(1);
}// 尝试写入会引发段错误
// strcpy(read_ptr, "Test"); // 危险操作!
(六)重要注意事项
1. 地址空间管理
场景 | 建议方案 |
---|
32位系统 | 优先使用自动地址分配 |
需要固定地址 | 使用shmaddr + 地址对齐检查 |
多段内存访问 | 维护地址映射表 |
2. 生命周期控制
// 分离内存(不删除)
shmdt(shm_ptr);// 删除共享内存(需所有进程分离)
shmctl(shmid, IPC_RMID, NULL);
3. 跨平台差异
系统特性 | Linux | Windows |
---|
地址随机化 | 默认启用(需设置shmaddr ) | 无类似机制 |
最大附加次数 | SHMMNI (通过sysctl 查看) | 由系统资源限制 |
十四、共享内存控制函数 shmctl
(一)函数原型
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
(二)核心功能
- 删除共享内存:通过
IPC_RMID
命令 - 获取状态信息:通过
IPC_STAT
命令 - 修改权限属性:通过
IPC_SET
命令
(三)参数说明
参数 | 类型 | 说明 |
---|
shmid | int | 共享内存标识符 |
cmd | int | 控制命令:<br>• IPC_RMID - 删除<br>• IPC_STAT - 获取状态 |
buf | struct shmid_ds* | 用于存储/修改状态信息(删除时可设为NULL ) |
(四)删除操作详解
1. 删除命令使用
// 标记删除共享内存
if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("Delete failed");exit(EXIT_FAILURE);
}
2. 删除机制
删除阶段 | 系统行为 |
---|
立即效果 | 共享内存被标记为待删除,不再接受新进程附加 |
最终释放条件 | 所有已附加进程执行shmdt 分离后,内核自动释放资源 |
访问规则变化 | 已附加进程仍可正常访问,新shmget 请求会失败 |
(五)使用示例
1. 安全删除模式
struct shmid_ds shm_info;// 检查附加进程数
if (shmctl(shmid, IPC_STAT, &shm_info) == 0) {printf("当前附加进程数: %lu\n", shm_info.shm_nattch);if (shm_info.shm_nattch == 0) {// 立即删除shmctl(shmid, IPC_RMID, NULL);} else {// 标记延迟删除shmctl(shmid, IPC_RMID, NULL);printf("等待%lu个进程分离...\n", shm_info.shm_nattch);}
}
2. 错误处理
if (shmctl(shmid, IPC_RMID, NULL) == -1) {switch(errno) {case EINVAL:fprintf(stderr, "无效的shmid\n");break;case EACCES:fprintf(stderr, "权限不足\n");break;case EPERM:fprintf(stderr, "非创建者/root用户\n");break;default:perror("未知错误");}exit(EXIT_FAILURE);
}
(六)结构体信息
shmid_ds
关键字段
struct shmid_ds {struct ipc_perm shm_perm; // 权限信息size_t shm_segsz; // 内存大小time_t shm_atime; // 最后附加时间time_t shm_dtime; // 最后分离时间time_t shm_ctime; // 最后修改时间pid_t shm_cpid; // 创建者PIDpid_t shm_lpid; // 最后操作PIDunsigned long shm_nattch;// 当前附加进程数
};
(七)系统管理命令
# 查看所有共享内存段
ipcs -m# 强制删除共享内存(慎用!)
ipcrm -m <shmid># 查看内核参数
sysctl -a | grep shm
融合实列:
发送代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<string.h>
#include <signal.h>int main(int argc, const char *argv[])
{key_t key = ftok("/",'a');if(key < 0){perror("ftok fail");return -1;}printf("key is %#x\n",key);int shmid = shmget(key,1024,IPC_CREAT|0666);if(shmid < 0){perror("shget fail");return 1;}printf("shmid = %d\n",shmid);char *p = shmat(shmid,NULL,0);if(p == NULL){perror("shmat fail");return -1;}pid_t pid = *(pid_t*)p;while(1){fgets(p,1024,stdin);kill(pid,SIGUSR1); }return 0;
}
接收代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<signal.h>void do_signo(int signo)
{
}int main(int argc, const char *argv[])
{key_t key = ftok("/",'a');if(key < 0){perror("ftok fail");return -1;}printf("key is %#x\n",key);int shmid = shmget(key,1024,IPC_CREAT|0666);if(shmid < 0){perror("shget fail");return 1;}printf("shmid = %d\n",shmid);char *p = shmat(shmid,NULL,0);if(p == NULL){perror("shmat fail");return -1;}signal(SIGUSR1,do_signo);*(pid_t*)p = getpid();while(1){pause();printf("buf = %s\n",p);}if(shmdt(p) < 0){perror("shmdt fail");return -1;}if(shmctl(shmid,IPC_RMID,NULL)<0){perror("shmctl fail");return -1;}return 0;
}