云计算百科
云计算领域专业知识百科平台

进程间通信(2)---消息队列/共享内存/信号灯---嵌入式(Linux)

1、消息队列

消息队列是 Linux 系统中一种基于内核的、面向数据块(消息)的异步 IPC 机制,本质是内核维护的一个消息链表,进程通过 API 向队列中添加 / 读取消息,实现进程间通信。

1.1 IPC对象

  • 消息队列、共享内存、信号量(信号灯)都属于System V IPC 对象,内核会为每个 IPC 对象维护一个独立的内核数据结构(而非 “内存文件”);
  • 每个 IPC 对象有唯一的IPC 标识符(ID)(如消息队列 ID、共享内存 ID),且会被内核持久化(进程退出后仍存在,需手动删除)。

1.2 键值

  • 键值(key_t)是 IPC 对象的全局唯一标识,用于在多个进程间定位同一个 IPC 对象(ID 是内核内部标识,键值是用户层标识);
  • IPC对象的名称,一个键值对应一个IPC对象,而每个IPC对象只能是三种类型中的一种:(要么是消息队列,要么是共享内存,要么是信号量)
  • 键值的生成方式:
    • 手动指定固定值(如key_t key = 0x123456;);
    • 通过ftok()函数生成(推荐,避免键值冲突)。

1.3 进程间通信(消息队列)

多个进程要通过消息队列通信,必须满足:

使用同一消息队列 → 访问同一个System V IPC对象 → 使用同一个键值(key) → (若用ftok)使用同一个pathname和proj_id。

1.4 常用命令(管理IPC对象)

命令功能
ipcs 查看所有 System V IPC 对象的信息(消息队列、共享内存、信号量)
ipcs -q 只查看消息队列(-m:共享内存,-s:信号量)
ipcs -l 可查看 IPC 对象的系统限制(如最大消息队列数、单队列最大字节数)。
ipcrm -Q <KEY> 根据键值(key)删除消息队列(-M:共享内存,-S:信号量)
ipcrm -q <MSGID/SHMID/SEMID> 根据消息队列 ID 删除消息队列(-m:共享内存 ID,-s:信号量 ID)

1.5 核心函数接口

1.5.1 ftok(生成键值)

  • 函数原型:

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

  • 功能:根据文件路径和项目 ID 生成唯一的 IPC 键值;
  • 参数:
    • pathname:必须是存在且可访问的文件路径(如"/tmp/ipc_test");
    • proj_id:项目 ID(仅低 8 位有效,通常传 1 个字符如'a',范围 1-255);
  • 返回值:
    • 成功:返回key_t类型的键值;
    • 失败:返回-1(需检查errno,如文件不存在、权限不足);
  • 关键:同一pathname + 同一proj_id → 生成相同键值;任意一个不同 → 键值不同。

1.5.2 msgget(创建 / 获取消息队列)

  • 函数原型:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);

  • 功能:创建新的消息队列,或获取已存在的消息队列 ID;
  • 参数:
    • key:IPC 键值(由ftok生成或手动指定);
    • msgflg:标志位(组合使用):
      • IPC_CREAT:若键值对应的队列不存在则创建,存在则获取;
      • IPC_EXCL:与IPC_CREAT配合使用,若队列已存在则报错(确保创建新队列);
      • 权限位:如0664(同文件权限,所有者可读可写,组可读,其他可读);
  • 返回值:
    • 成功:返回消息队列 ID(非负整数);
    • 失败:返回-1(如权限不足、队列已存在且用了IPC_EXCL);
  • 示例:msgget(key, IPC_CREAT | 0664);(创建 / 获取队列,权限 0664)。

1.5.3 mesgsnd(发送消息到队列)

  • 函数原型:

#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(msgget返回值);
    • msgp:指向自定义消息结构体的指针(必须以long mtype开头):

// 自定义消息结构(mtype必须>0,mtext长度可自定义)
struct msgbuf {
long mtype; /* 消息类型,必须 > 0 */
char mtext[1024]; /* 消息数据(可自定义长度) */
};

  • msgsz:消息数据部分的长度(即mtext的有效长度,不含mtype);
  • msgflg:发送标志:
    • 0:默认,队列满时阻塞;
    • IPC_NOWAIT:队列满时不阻塞,直接返回错误;
  • 返回值:成功返回0,失败返回-1(如队列不存在、权限不足、消息类型≤0)。

1.5.4 msgrcv(从队列接收消息)

  • 函数原型:

#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:存放接收消息的缓冲区(同msgsnd的msgbuf结构);
    • msgsz:缓冲区mtext的最大长度(超出部分会被截断,且不报错);
    • msgtyp:指定接收的消息类型(核心参数):
      • msgtyp = 0:读取队列中第一个消息(不区分类型);
      • msgtyp > 0:读取队列中第一个类型为 msgtyp的消息;
      • msgtyp < 0:读取队列中类型≤|msgtyp|的最小类型的消息;
    • msgflg:接收标志:
      • 0:默认,队列无匹配消息时阻塞;
      • IPC_NOWAIT:无匹配消息时不阻塞,直接返回错误;
      • MSG_NOERROR:消息长度超过msgsz时,截断数据且不报错(默认会报错);
  • 返回值:
    • 成功:返回实际读取的mtext字节数;
    • 失败:返回-1(如队列不存在、权限不足)。

1.5.5 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_STAT:获取队列的状态信息(存入buf);
      • IPC_SET:修改队列的属性(从buf读取);
      • IPC_RMID:删除消息队列(最常用,删除后队列立即消失);
    • buf:
      • 执行IPC_STAT/IPC_SET时:指向msqid_ds结构体(存放队列状态);
      • 执行IPC_RMID时:传NULL即可;
  • 返回值:成功返回0,失败返回-1(如队列不存在、无删除权限);
  • 示例:msgctl(msqid, IPC_RMID, NULL);(删除消息队列)。

2、共享内存

共享内存是 Linux 中最高效的进程间通信方式—— 内核在物理内存中开辟一块连续区域,多个进程将这块内存映射到自己的虚拟地址空间,进程可直接读写该内存(无需数据拷贝),通信效率远超管道 / 消息队列。

核心优势:其他 IPC 方式(如消息队列)需要 2 次数据拷贝(用户态→内核态→用户态),共享内存仅需映射,数据直接在用户态读写,无拷贝开销。

2.1 核心函数接口

2.1.1 ftok(同消息队列)

2.1.2 shmget(创建 / 获取共享内存)

  • 函数原型:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

  • 功能:创建新的共享内存段,或获取已存在的共享内存 ID;
  • 参数:
    • key:IPC 键值(ftok生成或手动指定,如IPC_PRIVATE表示创建私有共享内存);
    • size:共享内存的大小(字节数,建议按页大小对齐,如 4096、8192,系统页大小用getpagesize()获取)或者终端:getconf PAGESIZE;
    • shmflg:标志位(组合使用):
      • IPC_CREAT:不存在则创建,存在则获取;
      • IPC_EXCL:与IPC_CREAT配合,若已存在则报错(确保创建新内存段);
      • 权限位:如0664(同文件权限,控制进程对共享内存的读写);
  • 返回值:
    • 成功:返回共享内存 ID(非负整数);
    • 失败:返回-1(如权限不足、内存大小非法)。

2.1.3 shmat(映射共享内存到进程地址空间)

  • 函数原型:

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

  • 功能:将内核中的共享内存段映射到当前进程的虚拟地址空间,返回可直接读写的指针;
  • 参数:
    • shmid:共享内存 ID(shmget返回值);
    • shmaddr:指定映射到进程的虚拟地址:
      • NULL:推荐用法,让系统自动选择合适的地址(避免地址冲突);
      • 非空:手动指定地址(需确保地址合法,一般不推荐);
    • shmflg:映射属性:
      • 0:默认,读写权限(需共享内存创建时的权限允许);
      • SHM_RDONLY:只读权限(即使共享内存有写权限,也只能读);
  • 返回值:
    • 成功:返回映射后的内存起始地址(void*,需强转为对应类型使用);
    • 失败:返回(void*)-1(注意不是-1,需用(void*)-1判断)。

2.1.4 shmdt(解除共享内存映射)

  • 函数原型:

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);

  • 功能:将共享内存从当前进程的地址空间中解除映射(仅断开连接,不删除共享内存);
  • 参数:shmaddr:shmat返回的映射地址;
  • 返回值:成功返回0,失败返回-1;
  • 关键:进程退出前必须解除映射,否则可能导致内存泄漏。

2.1.5 shmctl(控制共享内存)

  • 函数原型:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

  • 功能:对共享内存执行查询、修改、删除等操作;
  • 参数:
    • shmid:共享内存 ID;
    • cmd:控制命令(核心):
      • IPC_STAT:读取共享内存属性(存入buf,同消息队列);
      • IPC_SET:修改共享内存属性(从buf读取,仅能改权限、所有者等);
      • IPC_RMID:删除共享内存段(最常用,删除后内核释放该内存);
      • SHM_LOCK:锁定共享内存(禁止换出到交换分区,需 root 权限);
      • SHM_UNLOCK:解锁共享内存;
    • buf:
      • IPC_STAT/IPC_SET:指向shmid_ds结构体(存放属性);
      • IPC_RMID:传NULL即可;
  • 返回值:成功返回0,失败返回-1。

3、信号灯

System V信号灯是多个信号量组成的数组,用于实现进程间的同步 / 互斥(如保护共享内存的读写);单个信号量是一个计数器,通过P/V操作(减 1 / 加 1)控制资源访问。

3.1 函数接口

3.1.1 ftok(同消息队列)

3.1.2 semget(创建 / 获取信号量集)

  • 函数原型:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

  • 功能:创建新的信号量集,或获取已存在的信号量集 ID;
  • 参数:
    • key:IPC 键值(ftok生成或IPC_PRIVATE);
    • nsems:信号量集中的信号量个数(创建时必须指定,获取时可填 0);
    • semflg:标志位(组合使用):
      • IPC_CREAT:不存在则创建,存在则获取;
      • IPC_EXCL:与IPC_CREAT配合,若已存在则报错(确保创建新集);
      • 权限位:如0664(控制进程对信号量集的操作权限);
  • 返回值:
    • 成功:返回信号量集 ID(非负整数);
    • 失败:返回-1(如权限不足、nsems 非法)。

3.1.3 semctl(控制信号量集 / 单个信号量)

  • 函数原型:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, …);

  • 功能:对信号量集 / 单个信号量执行初始化、删除、查询等操作;
  • 参数:
    • semid:信号量集 ID(semget返回值);
    • semnum:信号量集中的单个信号量下标(从 0 开始,操作整个集时可填 0);
    • cmd:核心命令:
      命令功能
      SETVAL 设置单个信号量的初始值(需配合第四个参数union semun)
      GETVAL 获取单个信号量的当前值(返回值为信号量值)
      IPC_RMID 删除整个信号量集(semnum 无效,第四个参数传 NULL,也可省略)
      SETALL 设置信号量集中所有信号量的初始值(第四个参数传数组)
      GETALL 获取信号量集中所有信号量的当前值(第四个参数传数组)
    • 可变参数(…):需传入union semun结构体(必须手动定义,系统不默认提供):

      // 必须手动定义该联合体!
      union semun {
      int val; // SETVAL:单个信号量的初始值
      struct semid_ds *buf; // IPC_STAT/IPC_SET:信号量集属性
      unsigned short *array; // GETALL/SETALL:所有信号量的值数组
      struct seminfo *__buf; // IPC_INFO:系统限制(Linux特有)
      };

  • 返回值:
    • 成功:SETVAL/IPC_RMID返回 0,GETVAL返回信号量当前值;
    • 失败:返回-1。

3.1.4 semop(执行 P/V 操作,核心)

  • 函数原型:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, size_t nsops);

  • 功能:对信号量集执行一组原子操作(P 操作 / V 操作),确保操作不被中断;
  • 参数:
    • semid:信号量集 ID;
    • sops:指向struct sembuf结构体数组的指针(定义操作列表):

      struct sembuf {
      unsigned short sem_num; // 操作的信号量下标(从0开始)
      short sem_op; // 操作类型(核心):
      // -1:P操作(申请资源,信号量减1,无资源则阻塞)
      // +1:V操作(释放资源,信号量加1)
      // 0:等待信号量值变为0
      short sem_flg; // 操作标志:
      // 0:默认,无资源则阻塞
      // IPC_NOWAIT:无资源则不阻塞,直接返回错误
      // SEM_UNDO:进程退出时自动撤销本次操作(避免死锁)
      };

    • nsops:操作列表的长度(即sops数组的元素个数);
  • 返回值:成功返回0,失败返回-1;
  • 核心特性:semop的所有操作是原子性的 —— 要么全部执行,要么全部不执行(避免竞态条件)。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 进程间通信(2)---消息队列/共享内存/信号灯---嵌入式(Linux)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!