指数平滑法,莱阳seo排名,花灯彩灯制作公司,开互联网公司需要什么条件一、共享内存#xff1a;直击内核的高速数据通道如果说管道、消息队列是进程间 “快递式” 的数据传递#xff0c;那共享内存就是为多个进程开辟的 “公共储物间”—— 内核划出一块物理内存区域#xff0c;让多个进程直接映射到自己的虚拟地址空间#xff0c;读写操作无需…一、共享内存直击内核的高速数据通道如果说管道、消息队列是进程间 “快递式” 的数据传递那共享内存就是为多个进程开辟的 “公共储物间”—— 内核划出一块物理内存区域让多个进程直接映射到自己的虚拟地址空间读写操作无需内核中转这也是它成为效率最高 IPC 方式的根本原因。1. 核心原理打破进程地址空间隔离Linux 系统中每个进程都有独立的虚拟地址空间彼此隔离。而共享内存的本质是内核创建一块全局可见的物理内存并将其同时映射到多个进程的虚拟地址空间中。这意味着进程 A 写入的数据进程 B 可以直接读取数据从一个进程到另一个进程没有任何拷贝操作。相比之下管道通信需要经历 “用户态→内核态→用户态” 两次拷贝效率差距一目了然。2. 共享内存 vs 管道核心差异对比共享内存与管道是 Linux 中最常用的两种 IPC 方式但设计逻辑和使用特性差异显著以下是核心对比特性共享内存管道无名 / 命名读写权限双方进程均可读写半双工无名管道/ 全双工命名管道但默认需明确读写端分工阻塞特性无读阻塞、无写阻塞直接操作内存读阻塞管道无数据时、写阻塞管道满时同步机制无原生同步需搭配信号 / 信号量自带简单同步基于阻塞机制数据形态连续内存区域类似字符数组流式数据字节流无边界数据留存数据持久化在内存中不主动删除数据读取后即从内核缓冲区删除拷贝次数零拷贝直接操作物理内存两次拷贝用户态↔内核态关键差异解读读写权限管道尤其是无名管道通常需要明确 “读端” 和 “写端”而共享内存无此限制所有映射的进程都可自由读写阻塞特性管道的读进程会阻塞直到有数据写入写进程会阻塞直到管道有空闲空间共享内存直接操作物理内存读写均无阻塞但若未做同步会导致数据错乱同步依赖共享内存的 “无阻塞” 特性是一把双刃剑必须搭配信号通知或信号量互斥实现进程协同否则多进程并发读写会导致数据覆盖数据形态与留存共享内存是连续的内存区域类似字符数组数据写入后会一直留存直到进程主动覆盖或删除共享内存管道是流式数据数据被读取后立即从内核缓冲区消失。3. 实战流程从创建到销毁的完整闭环共享内存的使用严格遵循 “创建 / 获取 → 映射 → 读写 → 解除映射 → 删除” 五步走每一步都对应着关键的 System V IPC 函数。1生成唯一标识ftok()函数要让多个进程找到同一块共享内存首先需要一个唯一的 “钥匙”——key_t类型的键值。ftok()函数通过一个存在的文件路径和项目 ID非 0 整数生成这个唯一键值。#include sys/ipc.h key_t ftok(const char *pathname, int proj_id);路径必须是进程都能访问的文件如/tmp/ipc_test确保不同进程调用ftok()时参数一致。项目 ID通常取 1~255 之间的整数用于区分同一文件下的不同共享内存。2创建 / 获取共享内存shmget()函数拿到键值后通过shmget()向内核申请共享内存若内存已存在则直接获取其 ID。#include sys/shm.h int shmget(key_t key, size_t size, int shmflg);参数详解keyftok()生成的键值size共享内存大小单位字节建议按页大小PAGE_SIZE对齐shmflg权限标志常用组合IPC_CREAT | 0664创建并设置读写权限。返回值成功返回共享内存 IDshmid失败返回 - 1。3映射到进程地址空间shmat()函数共享内存创建后还只是内核中的一块物理内存进程无法直接访问。shmat()的作用就是将这块内存映射到进程的虚拟地址空间返回一个可直接操作的指针。void *shmat(int shmid, const void *shmaddr, int shmflg);关键参数shmaddr指定映射的虚拟地址传NULL由系统自动分配推荐shmflg设0为可读可写SHM_RDONLY为只读。返回值成功返回映射后的内存指针失败返回(void*)-1。4读写操作直接操作内存指针映射成功后结合strcpy/memcpy读写数据// 字符串读写示例 char *shm_ptr (char*)shmat(shmid, NULL, 0); strcpy(shm_ptr, Hello, Shared Memory!); // 写入字符串 printf(读取字符串%s\n, shm_ptr); // 二进制数据读写示例 typedef struct { int a; double b; } Data; Data data {10, 3.14}; Data *shm_data (Data*)shm_ptr; memcpy(shm_data, data, sizeof(Data)); // 写入二进制数据 Data read_data; memcpy(read_data, shm_data, sizeof(Data)); // 读取二进制数据 printf(读取二进制数据a%d, b%.2f\n, read_data.a, read_data.b);这里要注意一个核心问题共享内存本身没有同步互斥机制如果多个进程同时写会导致数据错乱如果进程 A 还没写完进程 B 就开始读会读到不完整的数据。这时候就需要信号、信号量来配合同步。5解除映射shmdt()函数进程不再使用共享内存时需要调用shmdt()解除映射断开进程与共享内存的关联。int shmdt(const void *shmaddr);参数shmat()返回的内存指针注意解除映射不是删除共享内存只是进程不能再访问它内核中的内存区域依然存在。6删除共享内存shmctl()函数当所有进程都解除映射后必须调用shmctl()手动删除共享内存否则它会一直存在于内核中直到系统重启。int shmctl(int shmid, int cmd, struct shmid_ds *buf);关键参数cmd控制命令设为IPC_RMID表示删除共享内存buf传NULL即可。5. 运维必备IPC 资源查看与删除命令在开发和调试过程中经常需要手动查看系统中残留的 IPC 资源或清理异常退出进程留下的资源以下是核心命令命令功能说明ipcs -a查看系统中所有 IPC 资源共享内存、信号量集、消息队列ipcs -m仅查看共享内存资源ipcs -s仅查看信号量集资源ipcs -q仅查看消息队列资源ipcrm -m [shmid]根据共享内存 ID 删除指定共享内存ipcrm -s [semid]根据信号量集 ID 删除指定信号量集ipcrm -q [mqid]根据消息队列 ID 删除指定消息队列示例# 查看所有IPC资源 ipcs -a # 仅查看共享内存 ipcs -m # 删除ID为12345的共享内存 ipcrm -m 12345 # 删除ID为67890的信号量集 ipcrm -s 67890二、信号机制进程间的异步 “传令兵”如果说共享内存是进程间的 “数据高速公路”那信号就是跑在公路上的 “传令兵”—— 它不传递复杂数据只传递事件通知。信号是 Linux 最古老的 IPC 方式之一用于处理异步事件比如用户按下CtrlC、进程访问非法内存等。1. 信号的本质软中断的实现信号是一种异步通信机制一个进程向另一个进程发送信号本质上是向内核发送一个请求内核再将这个请求转发给目标进程。当目标进程接收到信号时会暂停当前执行的代码转而去执行信号对应的处理函数处理完成后再回到原来的代码继续执行。这个过程类似硬件中断因此信号也被称为 “软中断”。2. 信号的生命周期发送→注册→处理→注销一个信号从产生到被处理会经历四个阶段信号发送通过kill()、raise()等函数或键盘操作CtrlC对应SIGINT、硬件异常等方式产生信号信号注册内核在目标进程的 PCB 中将对应的信号标志位置 1表示该进程有未处理的信号信号处理进程从内核态返回用户态时检查是否有未处理的信号若有则执行对应的处理函数信号注销处理函数执行完毕后内核清除 PCB 中的信号标志位完成一次信号处理。3. 核心操作信号的发送与捕获1发送信号kill()函数最常用的信号发送函数是kill()它可以向指定 PID 的进程发送任意信号。#include signal.h int kill(pid_t pid, int sig);参数详解pid目标进程的 PIDpid 0发送给指定进程pid 0发送给同组进程pid -1发送给所有有权限的进程sig要发送的信号如SIGINT2、SIGTERM15、SIGUSR110。2捕获信号signal()函数默认情况下大部分信号的处理动作是终止进程如SIGINT或忽略如SIGCHLD。通过signal()函数我们可以自定义信号的处理函数。typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);参数详解signum要捕获的信号handler处理函数指针可设为三种值SIG_DFL恢复默认处理动作SIG_IGN忽略该信号自定义函数名执行自定义处理逻辑。举个例子捕获SIGINT信号自定义处理函数#include stdio.h #include signal.h void sigint_handler(int signum) { printf(捕获到信号%d程序不会终止\n, signum); } int main() { signal(SIGINT, sigint_handler); while(1); // 死循环等待信号 return 0; }运行这段代码按下CtrlC不会终止程序而是输出自定义提示这就是信号捕获的作用。4. 不可忽视的信号特性不可靠性早期的 Unix 信号无法排队若进程在处理一个信号时又收到相同的信号该信号可能会丢失不可捕获 / 忽略SIGKILL9和SIGSTOP19这两个信号无法被捕获或忽略只能执行默认动作用于强制终止或暂停进程继承性子进程会继承父进程的信号处理方式但fork()后父进程的未处理信号不会传递给子进程。三、强强联合共享内存 信号构建高效 IPC 方案前面提到共享内存缺少同步机制而信号恰好可以弥补这一缺陷。两者结合既能实现高效的数据传输又能保证进程间的协同有序。1. 协同流程设计我们以 “写进程写入数据→发信号通知读进程→读进程读取数据” 为例设计一套完整的通信方案初始化阶段读进程先启动创建共享内存 → 映射内存 → 注册自定义信号SIGUSR1处理函数 → 阻塞等待信号写进程后启动获取同一块共享内存 → 映射内存 → 写入数据 → 向读进程发送SIGUSR1信号 → 通知读进程读取数据通信阶段写进程向共享内存写入数据写进程通过kill()向读进程发送SIGUSR1信号读进程捕获信号执行处理函数读取共享内存中的数据收尾阶段读写进程分别调用shmdt()解除映射写进程调用shmctl()删除共享内存。2. 完整可运行代码共享内存 信号以下是写进程sender.c和读进程receiver.c的完整代码包含字符串和二进制数据的读写示例注释详尽可直接编译运行。1读进程receiver.c#include stdio.h #include stdlib.h #include string.h #include unistd.h #include signal.h #include sys/ipc.h #include sys/shm.h #include sys/types.h #define SHM_SIZE 1024 // 共享内存大小 #define FTOK_PATH /tmp/ipc_test // ftok用的文件路径 #define FTOK_PROJ_ID 123 // ftok用的项目ID // 定义二进制数据结构体 typedef struct { int id; char name[20]; float score; } Student; void *shm_ptr NULL; // 共享内存映射后的指针 // 信号处理函数收到SIGUSR1后读取共享内存 void sigusr1_handler(int signum) { if (shm_ptr NULL) { printf(共享内存未映射\n); return; } // 1. 读取字符串数据 printf(\n【字符串数据】读进程收到信号共享内存数据%s\n, (char*)shm_ptr); // 2. 读取二进制数据结构体 Student *stu_ptr (Student*)((char*)shm_ptr strlen((char*)shm_ptr) 1); Student read_stu; memcpy(read_stu, stu_ptr, sizeof(Student)); printf(【二进制数据】读进程收到信号学生信息ID%d, 姓名%s, 分数%.1f\n, read_stu.id, read_stu.name, read_stu.score); } int main() { key_t key; int shmid; // 1. 生成唯一key key ftok(FTOK_PATH, FTOK_PROJ_ID); if (key -1) { perror(ftok failed); exit(EXIT_FAILURE); } // 2. 创建/获取共享内存 shmid shmget(key, SHM_SIZE, IPC_CREAT | 0664); if (shmid -1) { perror(shmget failed); exit(EXIT_FAILURE); } printf(读进程共享内存ID %d\n, shmid); // 3. 映射共享内存到当前进程地址空间 shm_ptr shmat(shmid, NULL, 0); if (shm_ptr (void*)-1) { perror(shmat failed); exit(EXIT_FAILURE); } printf(读进程共享内存映射成功地址 %p\n, shm_ptr); // 4. 注册SIGUSR1信号的处理函数 if (signal(SIGUSR1, sigusr1_handler) SIG_ERR) { perror(signal failed); exit(EXIT_FAILURE); } printf(读进程已注册SIGUSR1处理函数PID %d等待信号...\n, getpid()); // 5. 阻塞等待信号也可以用pause() while (1) { sleep(1); // 避免CPU空转 } // 实际中需要信号触发退出这里简化 // 6. 解除映射 if (shmdt(shm_ptr) -1) { perror(shmdt failed); exit(EXIT_FAILURE); } return 0; }2写进程sender.c#include stdio.h #include stdlib.h #include string.h #include unistd.h #include signal.h #include sys/ipc.h #include sys/shm.h #include sys/types.h #define SHM_SIZE 1024 // 共享内存大小 #define FTOK_PATH /tmp/ipc_test // ftok用的文件路径 #define FTOK_PROJ_ID 123 // ftok用的项目ID // 定义二进制数据结构体 typedef struct { int id; char name[20]; float score; } Student; int main(int argc, char *argv[]) { if (argc ! 2) { fprintf(stderr, 用法%s 读进程PID\n, argv[0]); exit(EXIT_FAILURE); } pid_t receiver_pid atoi(argv[1]); // 读进程的PID key_t key; int shmid; void *shm_ptr NULL; // 1. 生成唯一key与读进程一致 key ftok(FTOK_PATH, FTOK_PROJ_ID); if (key -1) { perror(ftok failed); exit(EXIT_FAILURE); } // 2. 获取共享内存读进程已创建 shmid shmget(key, SHM_SIZE, 0664); if (shmid -1) { perror(shmget failed); exit(EXIT_FAILURE); } printf(写进程共享内存ID %d\n, shmid); // 3. 映射共享内存 shm_ptr shmat(shmid, NULL, 0); if (shm_ptr (void*)-1) { perror(shmat failed); exit(EXIT_FAILURE); } printf(写进程共享内存映射成功地址 %p\n, shm_ptr); // 4. 向共享内存写入数据字符串二进制 // 4.1 写入字符串 const char *msg Hello from Sender! This is Shared Memory Data.; strncpy(shm_ptr, msg, SHM_SIZE - sizeof(Student) - 1); // 预留二进制数据空间 ((char*)shm_ptr)[SHM_SIZE - sizeof(Student) - 1] \0; printf(写进程已向共享内存写入字符串%s\n, (char*)shm_ptr); // 4.2 写入二进制数据结构体 Student stu {101, Zhang San, 98.5}; Student *stu_ptr (Student*)((char*)shm_ptr strlen((char*)shm_ptr) 1); memcpy(stu_ptr, stu, sizeof(Student)); printf(写进程已向共享内存写入二进制数据ID%d, 姓名%s, 分数%.1f\n, stu.id, stu.name, stu.score); // 5. 向读进程发送SIGUSR1信号 if (kill(receiver_pid, SIGUSR1) -1) { perror(kill failed); exit(EXIT_FAILURE); } printf(写进程已向读进程PID%d发送SIGUSR1信号\n, receiver_pid); // 6. 等待读进程处理简化实际可加延迟 sleep(2); // 7. 解除映射 if (shmdt(shm_ptr) -1) { perror(shmdt failed); exit(EXIT_FAILURE); } // 8. 删除共享内存 if (shmctl(shmid, IPC_RMID, NULL) -1) { perror(shmctl failed); exit(EXIT_FAILURE); } printf(写进程已删除共享内存\n); return 0; }3. 编译与运行步骤共享内存 信号创建 ftok 所需文件touch /tmp/ipc_test编译代码gcc receiver.c -o receiver gcc sender.c -o sender启动读进程./receiver此时读进程会输出自身 PID例如读进程已注册SIGUSR1处理函数PID 12345等待信号...。启动写进程传入读进程 PID./sender 12345 # 替换为实际的读进程PID验证 IPC 资源可选# 运行过程中查看IPC资源 ipcs -a # 运行结束后清理残留的共享内存若有 ipcrm -m [shmid]4. 运行效果共享内存 信号读进程会输出plaintext【字符串数据】读进程收到信号共享内存数据Hello from Sender! This is Shared Memory Data. 【二进制数据】读进程收到信号学生信息ID101, 姓名Zhang San, 分数98.5写进程会输出写入数据、发送信号及删除内存的提示。四、进阶方案信号量 共享内存实现互斥信号仅能实现 “通知”无法解决多进程并发写共享内存的互斥问题比如两个写进程同时写入会导致数据错乱。此时需要System V 信号量来实现进程间的互斥锁保证同一时间只有一个进程操作共享内存。1. 信号量核心原理与设计思路1信号量的本质System V 信号量是内核维护的计数器用于实现进程间的 “同步 / 互斥”互斥场景信号量初始值设为1代表 “唯一的共享资源”P 操作申请资源信号量值减 1若结果 0 则进程阻塞直到信号量值 0V 操作释放资源信号量值加 1若有进程阻塞则唤醒其中一个。2为什么需要信号量共享内存的 “零拷贝” 特性是以 “无同步” 为代价的若两个进程同时写入会导致数据覆盖、错乱如进程 A 写了一半进程 B 开始写信号量通过 “加锁 - 操作 - 解锁” 的流程强制进程串行访问共享内存避免竞争。3整体设计流程初始化阶段创建信号量集初始值 1 创建共享内存访问阶段进程申请信号量P 操作→ 成功则获取 “锁”独占共享内存进程读写共享内存进程释放信号量V 操作→ 释放 “锁”允许其他进程访问收尾阶段删除信号量集 删除共享内存。2. 信号量核心函数深度解析函数函数原型核心说明semget()int semget(key_t key, int nsems, int semflg);创建 / 获取信号量集-key为 ftok 生成的唯一键值-nsems设为 1互斥场景仅需 1 个信号量-semflg用 IPC_CREAT0664创建 权限- 成功返回信号量集 ID失败返回 - 1。semop()int semop(int semid, struct sembuf *sops, unsigned nsops);执行 P/V 操作-semid为 semget 返回的信号量集 ID-nsops设为 1- 成功返回 0失败返回 - 1。semctl()int semctl(int semid, int semnum, int cmd, union semun arg);控制信号量集-semnum设为 0-cmd用SETVAL初始化值/IPC_RMID删除- 成功返回 0失败返回 - 1。3. 完整可运行代码信号量 共享内存1公共头文件shm_sem.h封装核心操作#ifndef SHM_SEM_H #define SHM_SEM_H #include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/ipc.h #include sys/shm.h #include sys/sem.h #include sys/types.h // -------------------------- 配置项 -------------------------- #define SHM_SIZE 1024 // 共享内存大小字节 #define FTOK_PATH /tmp/ipc_sem_test // ftok文件路径必须存在 #define FTOK_PROJ_ID 456 // ftok项目ID1~255 #define SEM_INIT_VAL 1 // 信号量初始值互斥锁设为1 // -------------------------- 信号量操作封装 -------------------------- /** * brief P操作申请信号量加锁 * param semid 信号量集ID * return 成功返回0失败返回-1 */ int sem_p(int semid) { struct sembuf sem_buf; sem_buf.sem_num 0; // 操作第0个信号量互斥场景仅需1个 sem_buf.sem_op -1; // 减1申请资源 sem_buf.sem_flg SEM_UNDO; // 进程异常退出时内核自动恢复信号量值 return semop(semid, sem_buf, 1); // 1表示执行1个操作 } /** * brief V操作释放信号量解锁 * param semid 信号量集ID * return 成功返回0失败返回-1 */ int sem_v(int semid) { struct sembuf sem_buf; sem_buf.sem_num 0; sem_buf.sem_op 1; // 加1释放资源 sem_buf.sem_flg SEM_UNDO; return semop(semid, sem_buf, 1); } /** * brief 创建并初始化信号量集 * param key ftok生成的键值 * return 成功返回信号量集ID失败返回-1 */ int sem_create_init(key_t key) { // 1. 创建信号量集包含1个信号量 int semid semget(key, 1, IPC_CREAT | IPC_EXCL | 0664); if (semid -1) { // 若信号量已存在直接获取 semid semget(key, 1, 0664); if (semid -1) { perror(semget failed (create/GET)); return -1; } printf(信号量集已存在直接获取ID %d\n, semid); return semid; } // 2. 初始化信号量值为SEM_INIT_VAL1 union semun sem_union; sem_union.val SEM_INIT_VAL; if (semctl(semid, 0, SETVAL, sem_union) -1) { perror(semctl SETVAL failed); semctl(semid, 0, IPC_RMID); // 初始化失败删除信号量集 return -1; } printf(信号量集创建并初始化成功ID %d初始值 %d\n, semid, SEM_INIT_VAL); return semid; } /** * brief 删除信号量集 * param semid 信号量集ID * return 成功返回0失败返回-1 */ int sem_destroy(int semid) { return semctl(semid, 0, IPC_RMID); } // -------------------------- 共享内存操作封装 -------------------------- /** * brief 创建/获取共享内存 * param key ftok生成的键值 * return 成功返回共享内存ID失败返回-1 */ int shm_create_get(key_t key) { int shmid shmget(key, SHM_SIZE, IPC_CREAT | 0664); if (shmid -1) { perror(shmget failed); return -1; } printf(共享内存创建/获取成功ID %d\n, shmid); return shmid; } /** * brief 映射共享内存到进程地址空间 * param shmid 共享内存ID * return 成功返回映射地址失败返回NULL */ void *shm_attach(int shmid) { void *shm_ptr shmat(shmid, NULL, 0); // 0表示可读可写 if (shm_ptr (void*)-1) { perror(shmat failed); return NULL; } printf(共享内存映射成功地址 %p\n, shm_ptr); return shm_ptr; } /** * brief 解除共享内存映射 * param shm_ptr 映射后的地址 * return 成功返回0失败返回-1 */ int shm_detach(void *shm_ptr) { return shmdt(shm_ptr); } /** * brief 删除共享内存 * param shmid 共享内存ID * return 成功返回0失败返回-1 */ int shm_destroy(int shmid) { return shmctl(shmid, IPC_RMID, NULL); } #endif // SHM_SEM_H2写进程 1writer1.c第一个写进程#include shm_sem.h // 定义二进制数据结构体 typedef struct { int seq; // 写入序号 char data[64]; long time; // 写入时间戳 } ShmData; int main() { key_t key; int semid, shmid; void *shm_ptr NULL; // 1. 生成唯一键值所有进程必须用相同的path和proj_id key ftok(FTOK_PATH, FTOK_PROJ_ID); if (key -1) { perror(ftok failed); exit(EXIT_FAILURE); } printf(Writer1ftok生成key %d\n, key); // 2. 创建并初始化信号量集 semid sem_create_init(key); if (semid -1) { exit(EXIT_FAILURE); } printf(Writer1信号量集ID %d\n, semid); // 3. 创建/获取共享内存 shmid shm_create_get(key); if (shmid -1) { exit(EXIT_FAILURE); } printf(Writer1共享内存ID %d\n, shmid); // 4. 映射共享内存到进程地址空间 shm_ptr shm_attach(shmid); if (shm_ptr NULL) { exit(EXIT_FAILURE); } // 5. P操作申请信号量加锁 printf(Writer1尝试获取信号量锁...\n); if (sem_p(semid) -1) { perror(sem_p failed (Writer1)); exit(EXIT_FAILURE); } printf(Writer1成功获取信号量锁开始独占共享内存\n); // 6. 写入数据二进制结构体字符串 ShmData data { .seq 1, .time time(NULL) }; strcpy(data.data, Writer1: Mutex Shared Memory Data); memcpy(shm_ptr, data, sizeof(ShmData)); // 二进制写入 printf(Writer1已写入数据 - 序号%d内容%s时间戳%ld\n, data.seq, data.data, data.time); sleep(5); // 模拟耗时操作验证Writer2会阻塞 // 7. V操作释放信号量解锁 printf(Writer1准备释放信号量锁...\n); if (sem_v(semid) -1) { perror(sem_v failed (Writer1)); exit(EXIT_FAILURE); } printf(Writer1成功释放信号量锁\n); // 8. 解除共享内存映射 if (shm_detach(shm_ptr) -1) { perror(shm_detach failed (Writer1)); exit(EXIT_FAILURE); } // 9. 不立即删除信号量/共享内存留给Writer2使用 printf(Writer1执行完毕保留信号量和共享内存\n); return 0; }3写进程 2writer2.c第二个写进程#include shm_sem.h // 定义二进制数据结构体 typedef struct { int seq; // 写入序号 char data[64]; long time; // 写入时间戳 } ShmData; int main() { key_t key; int semid, shmid; void *shm_ptr NULL; // 1. 生成唯一键值与Writer1一致 key ftok(FTOK_PATH, FTOK_PROJ_ID); if (key -1) { perror(ftok failed); exit(EXIT_FAILURE); } printf(Writer2ftok生成key %d\n, key); // 2. 获取已存在的信号量集无需重复初始化 semid semget(key, 1, 0664); if (semid -1) { perror(semget failed (Writer2)); exit(EXIT_FAILURE); } printf(Writer2获取信号量集ID %d\n, semid); // 3. 获取已存在的共享内存 shmid shmget(key, SHM_SIZE, 0664); if (shmid -1) { perror(shmget failed (Writer2)); exit(EXIT_FAILURE); } printf(Writer2获取共享内存ID %d\n, shmid); // 4. 映射共享内存 shm_ptr shmat(shmid, NULL, 0); if (shm_ptr (void*)-1) { perror(shmat failed (Writer2)); exit(EXIT_FAILURE); } printf(Writer2共享内存映射成功地址 %p\n, shm_ptr); // 5. P操作申请信号量若Writer1未释放则阻塞 printf(Writer2尝试获取信号量锁若Writer1未释放则阻塞...\n); if (sem_p(semid) -1) { perror(sem_p failed (Writer2)); exit(EXIT_FAILURE); } printf(Writer2成功获取信号量锁开始独占共享内存\n); // 6. 读取并覆盖共享内存数据 ShmData old_data; memcpy(old_data, shm_ptr, sizeof(ShmData)); printf(Writer2读取原有数据 - 序号%d内容%s时间戳%ld\n, old_data.seq, old_data.data, old_data.time); ShmData new_data { .seq 2, .time time(NULL) }; strcpy(new_data.data, Writer2: Overwrite Mutex Shared Memory); memcpy(shm_ptr, new_data, sizeof(ShmData)); // 覆盖写入 printf(Writer2已覆盖写入数据 - 序号%d内容%s时间戳%ld\n, new_data.seq, new_data.data, new_data.time); // 7. V操作释放信号量 printf(Writer2准备释放信号量锁...\n); if (sem_v(semid) -1) { perror(sem_v failed (Writer2)); exit(EXIT_FAILURE); } printf(Writer2成功释放信号量锁\n); // 8. 解除映射 if (shmdt(shm_ptr) -1) { perror(shmdt failed (Writer2)); exit(EXIT_FAILURE); } // 9. 清理资源最后退出的进程删除信号量和共享内存 printf(Writer2开始清理资源...\n); if (sem_destroy(semid) -1) { perror(sem_destroy failed (Writer2)); } else { printf(Writer2成功删除信号量集\n); } if (shm_destroy(shmid) -1) { perror(shm_destroy failed (Writer2)); } else { printf(Writer2成功删除共享内存\n); } printf(Writer2执行完毕\n); return 0; }4. 编译与运行步骤信号量 共享内存步骤 1准备环境# 创建ftok所需的文件必须存在否则ftok失败 touch /tmp/ipc_sem_test步骤 2编译代码# 编译两个写进程 gcc writer1.c -o writer1 gcc writer2.c -o writer2步骤 3运行测试核心验证互斥启动 Writer1./writer1Writer1 会输出ftok生成key xxxxx 信号量集创建并初始化成功ID xxx初始值 1 Writer1信号量集ID xxx 共享内存创建/获取成功ID xxx Writer1共享内存ID xxx 共享内存映射成功地址 0x... Writer1尝试获取信号量锁... Writer1成功获取信号量锁开始独占共享内存 Writer1已写入数据 - 序号1内容Writer1: Mutex Shared Memory Data时间戳17348xxxxxx 此处阻塞5秒模拟耗时操作快速启动 Writer2在 Writer1 阻塞的 5 秒内./writer2Writer2 会输出ftok生成key xxxxx Writer2获取信号量集ID xxx Writer2获取共享内存ID xxx Writer2共享内存映射成功地址 0x... Writer2尝试获取信号量锁若Writer1未释放则阻塞... 此处阻塞直到Writer1释放锁Writer1 释放锁后Writer1 输出Writer1准备释放信号量锁... Writer1成功释放信号量锁 Writer1执行完毕保留信号量和共享内存此时 Writer2 立即解除阻塞输出Writer2成功获取信号量锁开始独占共享内存 Writer2读取原有数据 - 序号1内容Writer1: Mutex Shared Memory Data时间戳17348xxxxxx Writer2已覆盖写入数据 - 序号2内容Writer2: Overwrite Mutex Shared Memory时间戳17348xxxxxx Writer2准备释放信号量锁... Writer2成功释放信号量锁 Writer2开始清理资源... Writer2成功删除信号量集 Writer2成功删除共享内存 Writer2执行完毕手动清理残留 IPC 资源可选# 查看所有IPC资源确认是否有残留 ipcs -a # 若有残留的共享内存按ID删除 ipcrm -m [shmid] # 若有残留的信号量集按ID删除 ipcrm -s [semid]5. 关键细节与避坑指南1SEM_UNDO 标志的重要性若进程获取信号量后异常退出如 kill -9未执行 V 操作信号量值会变为 0其他进程永久阻塞设置SEM_UNDO后内核会记录进程的信号量操作进程退出时自动恢复信号量值如 P 操作减 1退出时加 1避免死锁。2信号量 / 共享内存的残留问题若进程异常退出未执行sem_destroy/shm_destroy资源会残留在内核中优先使用ipcs -a查看所有 IPC 资源再用ipcrm -m/-s精准删除残留资源bash# 查看所有IPC资源 ipcs -a # 删除指定共享内存 ipcrm -m 12345 # 删除指定信号量集 ipcrm -s 678903多进程竞争的边界场景若多个进程同时执行 P 操作内核会按 “先进先出” 顺序唤醒阻塞的进程信号量初始值设为 N 时可允许 N 个进程同时访问共享内存适用于 “有限资源” 场景。4ftok 的一致性要求所有进程必须使用相同的FTOK_PATH和FTOK_PROJ_ID否则生成的 key 不同无法访问同一信号量 / 共享内存FTOK_PATH对应的文件必须存在且可访问建议用/tmp下的文件。五、总结与技术选型建议1. 技术特性对比技术核心优势适用场景注意事项共享内存零拷贝传输效率最高支持任意读写数据留存大量数据传输如文件、视频帧、二进制对象无原生同步需配合信号 / 信号量可通过 ipcs/ipcrm 管理用 strcpy/memcpy 读写管道自带简单同步使用简单小数据量、单向 / 双向流式传输两次拷贝读 / 写阻塞数据读取后删除信号轻量级异步通知简单的 “事件触发”如读写完成不可靠可能丢失无数据传递信号量强同步 / 互斥能力多进程并发访问共享资源需手动管理避免死锁可通过 ipcs/ipcrm 清理残留2. 选型建议简单通知 少量数据共享内存 信号高并发写 大量数据共享内存 信号量低延迟异步通知仅用信号如进程启停通知小数据量有序传输管道 / 消息队列无需手动同步二进制对象传输共享内存 memcpy字符串传输共享内存 strcpy。