Linux:进程间通信IPC(interprocess)——消息队列,共享内存,信号灯(system v分支进程通信方法)
目录
1.消息队列(有点类似于管道):
1.1 IPC对象:
消息队列,共享内存,信号灯都是基于IPC对象的东西进行通信。
用于通信的Linux内核的内存文件(掉电会丢失)
1.2.键值——对IPC对象进行区分
键值即IPC对象的名称,一个键值对应一个IPC对象,一个IPC对象可以创建三个结构(1个消息队列、1个共享内存、1个信号灯)(如果两个信号要通信,则必须使用同1个消息队列,或同1个共享内存,或同1个信号灯)
1.3进程间通信
进程间通信使用同一消息队列 -> 同一IPC对象 ->同一键值->同-pathname和prej_id
1.4命令
1.ipcs:查看IPC对象的信息(包括:消息队列、共享内存、信号灯)
2.删除IPC对象
ipcrm -Q/M/S KEY 【大写加键值】(Q/M/S 分别对应消息队列、共享内存、信号灯)
ipcrm -q/m/s (msgid/shmid/semid) 【小写加ID】(Q/M/S 分别对应消息队列、共享内存、信号灯)
1.5函数接口
消息队列实现进程间通信的核心流程
步骤:
ftok生成键值key → megget创建消息队列 → megrcv收/megsnd发消息→msgctl销毁消息队列
ftok
头文件:#include <sys/types.h>
#include <sys/ipc.h>
原型: key_t ftok(const char* pathname, int proj_id);
功能: 根据pathname和proj_id生成唯一的IPC对象的临时键值,用于后续申请共享内存
参数: pathname:文件路径(任意文件的路径 + 名称,只要该文件不会被删除重建即可;)
proj_id:项目ID(整型数字,一般用 ASCII 码的单字符表示,与 pathname 运算生成唯一键值。)
返回值:
成功返回key_t类型的唯一键值
失败返回-1
示例:
//生成键值 #include"head.h" int main(void) { key_t key; key = ftok("./",'a'); if(-1 = key) { perror("fail to ftok"); return -1; } printf("key = %d\n",key); return 0; }
megget
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
原型: int msgget(key_t key, int msgflg);
功能: 创建一个消息队列(类似管道)
参数:
key:IPC对象的名称
msgflg:
IPC_CREAT 不存在就创建(要或上0664,给权限)
IPC_EXCL 存在报错(一般不用)返回值:
成功:返回消息队列ID号
失败:返回-1
示例:
//创建消息队列 #include "head.h" int main(void) { key_t key; int msgid = 0; int ret = 0; key = ftok("/", 'a'); if (-1 == key) { perror("fail to ftok"); return -1; } printf("key = %#x\n", key); msgid = msgget(key, IPC_CREAT | 0664); if (-1 == msgid) { perror("fail to msgget"); return -1; } printf("msgid = %d\n", msgid); return 0; }
megsnd
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
原型: int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能: 向消息队列中发送消息
参数:
msqid:消息队列ID号
msgp:发送消息空间的首地址
msgsz:发送消息的长度
msgflg:发送消息的属性,默认0
返回值:
成功:返回0
失败:返回-1
消息的结构:
struct msgbuf{ long mtype;/*message type, must be >0 *///消息类型 char mtext[1];/* message data*///消息内容 }示例:
sendmsg.mtype = 100L; sprintf(sendmsg.mtext, "hello world"); ret = msgsnd(msgid, &sendmsg, sizeof(sendmsg) - sizeof(long), 0); if (-1 == ret) { perror("fail to msgsnd"); return -1; }
megrcv
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
原型: ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能: 从消息队列中接收消息
参数:
msqid:消息队列ID号
msgp:发送消息空间的首地址
msgsz:发送消息的长度
msgflg:发送消息的属性,默认0
返回值:
成功:返回接收到数据的字节数(不包括消息的类型,仅仅是消息内容所占字节)
失败:返回-1
示例:
ret = msgrcv(msgid, &recvmsg, sizeof(recvmsg) - sizeof(long), 100L, 0); if (-1 == ret) { perror("fail to msgrcv"); return -1; } printf("实际接收 %d 字节:%s\n", ret, recvmsg.mtext);
msgctl
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
原型:int msgctl(int msqid,int cmd,struct msqid_ds *buf);
功能: 向消队列发送命令
参数:
msqid:消息队列ID号
cmd:命令
IPC_RMID
buf:NULL返回值:
成功:返回接收到数据的字节数
失败:返回-1
示例:
msgctl(msgid, IPC_RMID, NULL);
如下代码演示了消息队列(Message Queue) 的基本用法:
进程①:send.c(发送方)——创建队列 + 发送消息
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ipc.h> #include <sys/msg.h> #include <unistd.h> #include <errno.h> // 消息结构体定义 // 注意:第一个字段必须是long类型,用于消息类型标识 typedef struct msg { long mtype; // 消息类型,必须 > 0 char mtext[256]; // 消息正文,最大255字符 + '\0' } msg_t; int main(void) { key_t key; // 消息队列键值 int msgid = 0; // 消息队列标识符 msg_t sendmsg; // 发送消息缓冲区 int ret = 0; // 函数返回值 // 1. 生成消息队列键值 // 参数:路径名(必须存在且可访问),项目ID(0-255) // 同一通信组必须使用相同的键值 key = ftok("/tmp", 'a'); // 使用/tmp目录更可靠 if (-1 == key) { perror("生成键值失败"); return EXIT_FAILURE; } printf("消息队列键值: 0x%08X\n", key); // 2. 创建或获取消息队列 // IPC_CREAT: 不存在则创建 // 0666: 所有用户可读写 msgid = msgget(key, IPC_CREAT | 0666); if (-1 == msgid) { perror("创建/获取消息队列失败"); return EXIT_FAILURE; } printf("消息队列ID: %d\n", msgid); // 3. 准备并发送消息 sendmsg.mtype = 100L; // 消息类型设为100 snprintf(sendmsg.mtext, sizeof(sendmsg.mtext), "进程[%d]发送的消息", getpid()); // 发送消息 // 参数说明: // msgid: 消息队列ID // &sendmsg: 消息缓冲区指针 // sizeof(sendmsg.mtext): 发送消息正文长度 // 0: 标志位(0表示阻塞发送,IPC_NOWAIT表示非阻塞) ret = msgsnd(msgid, &sendmsg, strlen(sendmsg.mtext) + 1, 0); if (-1 == ret) { perror("发送消息失败"); // 尝试删除消息队列(可选) if (errno == EACCES || errno == EIDRM) { msgctl(msgid, IPC_RMID, NULL); } return EXIT_FAILURE; } printf("消息发送成功 (类型: %ld, 内容: %s)\n", sendmsg.mtype, sendmsg.mtext); // 4. 等待用户确认,然后退出 // 在实际应用中,发送方通常不负责删除消息队列 // 删除队列的责任由接收方或专门的清理进程承担 printf("按Enter键退出发送方...\n"); getchar(); // 等待用户输入,便于观察 return EXIT_SUCCESS; //使用信号处理实现优雅退出 }进程②:recv.c(接收方)——获取队列 + 接收消息 + 删除队列
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ipc.h> #include <sys/msg.h> #include <signal.h> #include <unistd.h> // 消息结构体(必须与发送方一致) typedef struct msg { long mtype; char mtext[256]; } msg_t; // 全局变量,用于信号处理 static int msgid = -1; // 信号处理函数:优雅地清理资源 void cleanup(int sig) { if (msgid != -1) { printf("\n接收到信号%d,清理消息队列...\n", sig); // 删除消息队列 if (msgctl(msgid, IPC_RMID, NULL) == 0) { printf("消息队列已删除\n"); } else { perror("删除消息队列失败"); } } exit(EXIT_SUCCESS); } int main(void) { key_t key; msg_t recvmsg; int ret = 0; // 注册信号处理函数,用于优雅退出 signal(SIGINT, cleanup); // Ctrl+C signal(SIGTERM, cleanup); // kill命令 // 1. 生成与发送方相同的键值 key = ftok("/tmp", 'a'); // 必须与发送方相同 if (-1 == key) { perror("生成键值失败"); return EXIT_FAILURE; } printf("消息队列键值: 0x%08X\n", key); // 2. 获取已存在的消息队列(不创建) // 这里不使用IPC_CREAT,因为队列应由发送方创建 // 如果队列不存在,会失败 msgid = msgget(key, 0666); if (-1 == msgid) { perror("获取消息队列失败 (队列可能不存在)"); fprintf(stderr, "请先运行发送方程序\n"); return EXIT_FAILURE; } printf("消息队列ID: %d\n", msgid); printf("等待接收消息... (按Ctrl+C退出)\n"); // 3. 循环接收消息 while (1) { // 接收消息 // 参数说明: // msgid: 消息队列ID // &recvmsg: 接收缓冲区 // sizeof(recvmsg.mtext)-1: 最大接收长度(留1字节给'\0') // 100L: 只接收类型为100的消息(0表示接收任意类型) // 0: 标志位(0表示阻塞接收) ret = msgrcv(msgid, &recvmsg, sizeof(recvmsg.mtext) - 1, 100L, 0); if (-1 == ret) { // 检查是否是信号中断 if (errno == EINTR) { printf("接收被信号中断\n"); continue; } perror("接收消息失败"); break; } // 确保字符串以'\0'结尾 recvmsg.mtext[ret] = '\0'; printf("接收成功!\n"); printf(" 消息类型: %ld\n", recvmsg.mtype); printf(" 消息长度: %d 字节\n", ret); printf(" 消息内容: %s\n", recvmsg.mtext); // 根据消息内容决定是否退出 if (strstr(recvmsg.mtext, "exit") != NULL) { printf("收到退出指令,准备退出...\n"); break; } } // 4. 清理资源 printf("正在清理...\n"); if (msgid != -1) { msgctl(msgid, IPC_RMID, NULL); printf("消息队列已删除\n"); } return EXIT_SUCCESS; }makefile:
CC = gcc CFLAGS = -Wall -Wextra -O2 all: msg_send msg_recv msg_send: send.c $(CC) $(CFLAGS) -o $@ $< msg_recv: recv.c $(CC) $(CFLAGS) -o $@ $< clean: rm -f msg_send msg_recvbash:
# 编译两个程序 gcc -o msg_send send.c gcc -o msg_recv recv.c # 终端1:运行接收方 ./msg_recv # 终端2:运行发送方 ./msg_sendbash补充:
shell有很多种,bash是shell的一种。(shell有:Bash,sh (Bourne Shell),
zsh,fish,csh/tcsh等等)Bash是Bourne-Again SHell的缩写,它是Linux和Unix系统中最常用的命令行解释器,也是大多数Linux发行版的默认shell。
Bash的本质:用户与内核的翻译官
示例:
// C语言中调用Bash命令 #include <stdlib.h> int main() { // 通过system()函数调用Bash命令 system("ls -l"); // 实际上是通过Bash执行ls return 0; }示意图:
①按照队列排好队
②管道无消息类型的概念,所以谁去接都可以,但是消息队列有类型就不行,首先得类型匹配【所以消息队列比管道功能更强】
2.共享内存Share memory——进程间通信最高效的方式
2.1函数接口
共享内存的核心流程
步骤:ftok生成键值 → shmget申请共享内存(内核内存区域(像字符数组)) → shmat映射共享内存到本地 → 读写数据 → shmdt撤销映射 → shmctrl删除共享内存
shmget
头文件: #include <sys/ipc.h>
#include <sys/shm.h>
原型: int shmget(key_t key, size_t size, int shmflg);
功能: 创建共享内存(使用唯一键值 key 向内核提出共享内存使用申请。)
参数:
key:键值
size:共享内存的大小
shmflg:标志(申请的共享内存访问权限(八进制表示),搭配宏使用:)
IPC_CREAT 不存在创建(第一个申请时使用)
IPC_EXCL:检测共享内存是否存在,需与 IPC_CREAT 配合使用。
返回值:
成功:返回共享内存的ID
失败:返回-1
示例;
key_t key; int shmid = 0; key = ftok("/",'c'); if(-1 == key) { perror("fail to ftok"); return -1; } shmid = shmget(key,4096,IPC CREAT 0664); if(-1 == shmid) { perror("fail to shmget"); return -1; }
shmat
头文件: #include <sys/types.h>
#include <sys/shm.h>
原型: void *shmat(int shmid, const void *shmaddr, int shmflg);
功能: 将地址映射到共享内存空间(将指定 shmid 对应的共享内存映射到本地内存,让进程能直接访问。)
参数:
shmid:共享内存ID号
shmaddr:ddr:映射到共享内存中的地址(本地可用的地址,不确定则设为 NULL(由系统自动分配))
NULL:让系统选择合适的地址映射
shmflg:属性,默认为0
0:表示对共享内存有读写权限;
SHM RDONLY:表示仅只读权限。返回值:
成功:返回映射到共享内存空间的地址
失败:返回 (void*)-1。
shmdt
原型:int shmdt(const void *shmaddr);
功能:解除映射(将本地内存与共享内存断开映射关系。)
shmctrl
头文件: #include <sys/types.h>
#include <sys/shm.h>
原型: int shmctl(int shmid,int cmd,struct shmid_ds *buf);
功能: 向共享内存发送命令(修改共享内存属性,或删除指定的共享内存对象。)
参数:
shmid:共享内存ID号
cmd:命令
IPC_RMID 删除
buf:NULL(修改共享内存属性,或删除指定的共享内存对象。)
返回值:
成功:返回0
失败:返回-1
2.2示例
练习:利用共享内存实现两个进程间的通信,write.c负责从终端接收数据写入共享内存中,read.c负责打印共享内存中的数据
进程1:send.c(发送方)——获取队列 + 接收消息 + 删除队列
进程②:recv.c(接收方)——获取队列 + 接收消息 + 删除队列
3.信号灯(线程间通信用信号量,进程间通信用信号灯)
3.1信号灯是多个信号量组成的数组,也称为有名信号量
3.2 函数接口
步骤:ftok生成键值 → semget创建信号灯 → semctl初始化信号灯 → semop申请和释放
semget
头文件: #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
原型: int semget(key_t key,int nsems,int semflg);
功能: 创建信号量数组
参数:
key:PIC对象名称
nsems:信号量的个数
semflg:属性
IPC_CREAT
返回值:
成功:返回信号灯的ID
失败:返回-1
semctl
头文件: #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
原型:int semctl(int semid,int semnum,int cmd,...);
功能: 向信号灯发送命令
参数:
semid:信号量的ID号
semnum:信号量的下标
cmd:
IPC_RMID 删除信号灯
SETVAL 设置信号量的初值//共用体 union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /*Buffer for IPC_STAT,IPC_SET */ unsigned short *array; /* Array fOr GETALL, SETALL */ struct seminfo *buf; /*Buffer for IPC INFO(Linux-specific)*/ }返回值:
成功:返回非负值
失败:返回-1
semop
头文件: #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
原型:int semop(int semid,struct sembuf *sops, size_t nsops);
功能: 对信号量完成一系列操作(结构体数组)
参数:
semid:信号量的ID号
sops:对信号量操作列表的首地址
nsops:对信号量操作的长度
// unsigned short sem_num; /* semaphore number */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */返回值:
成功:返回0
失败:返回-1
更多推荐







所有评论(0)