Linux软件编程学习笔记(九):消息队列、共享内存与信号灯详解

Linux进程间通信(IPC):消息队列、共享内存与信号灯详解

本文基于Linux基础课程笔记,系统梳理进程间通信的三种核心机制:消息队列、共享内存和信号灯(有名信号量),涵盖概念、API函数、操作步骤及代码示例。


一、进程间通信概述

进程空间相互独立,无法像线程那样直接共享全局变量或堆区数据。因此,进程间通信(IPC,Inter-Process Communication)需要借助内核提供的第三方机制。Linux支持多种IPC方式:

  • 管道(无名管道、有名管道)
  • 信号(Signal)
  • 消息队列(Message Queue)
  • 共享内存(Shared Memory)
  • 信号灯(Semaphore,即信号量数组)

本文重点讲解消息队列共享内存信号灯 ,它们统称为 IPC对象,具有以下共性:

  • 每个IPC对象都有一个唯一的键值(key_t),用于标识。
  • 通过ipcs命令查看系统当前的IPC对象,通过ipcrm删除。
  • 操作流程类似:创建键值 → 创建/获取对象 → 使用 → 控制(删除)。

二、IPC对象基础:ftok 与键值

所有IPC机制都需要一个key_t类型的键值,通常使用ftok函数生成。

ftok 函数

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
  • 功能:根据一个存在的路径名和一个项目ID(8位整数)生成一个唯一的键值。
  • 参数
    • pathname:必须是一个存在的目录或文件路径。
    • proj_id:项目标识符,只使用低8位。
  • 返回值 :成功返回key_t键值,失败返回-1。

注意 :多个进程必须使用相同的pathnameproj_id才能获得相同的键值,从而访问同一个IPC对象。


三、消息队列(Message Queue)

消息队列是内核中的一个消息链表,进程可以向其中发送或接收消息。消息具有类型,接收方可以根据类型选择接收特定消息。

1. 操作步骤

  1. 使用ftok生成键值。
  2. 使用msgget创建或获取消息队列ID。
  3. 发送消息使用msgsnd,接收消息使用msgrcv
  4. 使用msgctl删除消息队列。

2. 核心函数

msgget - 创建/获取消息队列
c 复制代码
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
  • 参数
    • key:ftok生成的键值。
    • msgflg:权限标志,通常为IPC_CREAT | 0664(不存在则创建,并设置权限)。
  • 返回值:成功返回消息队列ID(非负整数),失败返回-1。
msgsnd - 发送消息
c 复制代码
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • 参数
    • msqid:消息队列ID。
    • msgp:指向消息结构的指针。消息结构必须包含一个long mtype字段,后面是消息正文。
    • msgsz:消息正文的大小(不包括mtype)。
    • msgflg:通常为0(阻塞发送)。
  • 返回值:成功返回0,失败返回-1。

消息结构定义示例

c 复制代码
struct msgbuf {
    long mtype;       // 消息类型,必须 > 0
    char mtext[128];  // 消息正文
};
msgrcv - 接收消息
c 复制代码
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • 参数
    • msgtyp:指定接收的消息类型。
      • > 0:接收该类型的第一个消息。
      • == 0:接收队列中第一个消息。
      • < 0:接收类型值小于等于msgtyp绝对值的消息(优先级)。
    • msgflg:通常为0(阻塞接收)。
  • 返回值:成功返回实际接收的字节数,失败返回-1。
msgctl - 控制消息队列
c 复制代码
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 常用cmd
    • IPC_RMID:删除消息队列(buf传NULL)。
    • IPC_STAT:获取状态信息。
  • 返回值:成功返回0,失败返回-1。

3. 查看与删除命令

bash 复制代码
ipcs -q          # 查看所有消息队列
ipcrm -q <msqid> # 根据ID删除
ipcrm -Q <key>   # 根据键值删除

四、共享内存(Shared Memory)

共享内存是最快的IPC方式,因为它允许两个进程直接映射同一块物理内存,数据拷贝次数最少。

1. 操作步骤

  1. ftok生成键值。
  2. shmget创建共享内存。
  3. shmat将共享内存映射到进程的虚拟地址空间(获得访问指针)。
  4. 使用指针进行读写(需配合信号量同步)。
  5. shmdt解除映射(不删除内存)。
  6. shmctl删除共享内存。

2. 核心函数

shmget - 创建共享内存
c 复制代码
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
  • 参数
    • size:共享内存大小(字节),通常为页大小的整数倍。
    • shmflgIPC_CREAT | 0664
  • 返回值:成功返回共享内存ID,失败返回-1。
shmat - 映射到进程空间
c 复制代码
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 参数
    • shmaddr:通常传NULL,由系统自动选择映射地址。
    • shmflg:通常为0(可读可写);也可用SHM_RDONLY只读。
  • 返回值 :成功返回映射后的指针,失败返回(void *)-1
shmdt - 解除映射
c 复制代码
int shmdt(const void *shmaddr);
  • 成功返回0,失败返回-1。
shmctl - 控制共享内存
c 复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 常用cmdIPC_RMID删除共享内存(标记为销毁,当所有进程解除映射后真正释放)。

3. 命令

bash 复制代码
ipcs -m          # 查看共享内存
ipcrm -m <shmid> # 删除

4. 注意事项

  • 共享内存本身不提供同步机制,需要配合信号量互斥锁防止数据竞争。
  • 使用完毕后务必shmdtshmctl删除,否则会造成内存泄漏。

五、信号灯(有名信号量 / 信号量数组)

在进程间通信中,信号量用于同步互斥 ,通常是一个信号量数组 ,每个元素是一个计数器。它不同于线程中的无名信号量(sem_init),而是基于IPC对象的有名信号量

1. 操作步骤

  1. ftok生成键值。
  2. semget创建信号量数组(指定信号量个数)。
  3. semctl初始化或删除信号量。
  4. semop执行PV操作(申请/释放)。
  5. semctl删除信号量数组。

2. 核心函数

semget - 创建/获取信号量数组
c 复制代码
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
  • 参数
    • nsems:信号量个数(通常为1)。
    • semflgIPC_CREAT | 0664
  • 返回值:成功返回信号量数组ID,失败返回-1。
semctl - 控制信号量
c 复制代码
int semctl(int semid, int semnum, int cmd, ...);
  • 常用cmd

    • SETVAL:设置第semnum个信号量的值为val(需第四个参数union semun)。
    • IPC_RMID:删除信号量数组。
  • union semun (需自己定义):

    c 复制代码
    union semun {
        int val;
        struct semid_ds *buf;
        unsigned short *array;
    };
  • 示例初始化

    c 复制代码
    union semun su;
    su.val = 1;  // 初始值为1,表示可用资源数为1
    semctl(semid, 0, SETVAL, su);
semop - PV操作
c 复制代码
int semop(int semid, struct sembuf *sops, size_t nsops);
  • struct sembuf 结构:

    c 复制代码
    struct sembuf {
        unsigned short sem_num;  // 信号量下标
        short sem_op;            // 操作数:+1 释放,-1 申请
        short sem_flg;           // 标志,通常为0(阻塞)
    };
  • 示例

    c 复制代码
    struct sembuf p = {0, -1, 0};  // 申请(P操作)
    struct sembuf v = {0, +1, 0};  // 释放(V操作)
    semop(semid, &p, 1); // 阻塞直到资源可用
    // 临界区
    semop(semid, &v, 1);

3. 命令

bash 复制代码
ipcs -s          # 查看信号量数组
ipcrm -s <semid> # 删除

六、总结对比

机制 特点 适用场景 同步需求
消息队列 有类型,可选择性接收,数据按消息组织 小数据量,需要类型过滤 无需同步
共享内存 速度最快,无拷贝,数据直接访问 大数据量,高频读写 必须同步
信号灯 提供计数器,实现同步/互斥 保护临界资源,控制访问顺序 本身就是同步工具

七、常见错误与调试

  • 删除IPC对象失败 :使用ipcrm时确保权限足够,或对象已存在。
  • 共享内存映射失败 :检查size是否过大或系统限制。
  • 消息队列阻塞:检查消息类型是否匹配,或队列已满。
  • 信号量死锁:确保PV操作成对出现,避免进程异常退出导致资源未释放。

附:查看系统IPC限制

bash 复制代码
cat /proc/sys/kernel/msgmax   # 消息最大字节数
cat /proc/sys/kernel/shmmax   # 共享内存最大字节数

掌握这三大IPC机制,足以应对大多数进程间协作场景。实际编程中,推荐优先考虑共享内存+信号量组合,兼顾效率与同步安全性。


本文为学习笔记,如有错误欢迎指正。

相关推荐
MartinYeung514 分钟前
[论文学习]激活差异揭示后门:SAE架构对比研究
人工智能·学习·架构
小雪崩16 分钟前
嵌入式学习 day35:进程间通信-消息队列、共享空间及有名信号量
linux·c语言·学习
从零开始的嵌入式之旅29 分钟前
day33
linux·c语言·经验分享·笔记·嵌入式硬件
qetfw1 小时前
Debian lftp FTP 客户端使用:登录、目录切换与文件传输
linux·运维·debian
神威难绷泪1 小时前
Linux应用软件编程:线程分离属性 互斥机制 同步机制 死锁
linux·开发语言·算法·线程
2401_862880821 小时前
Linux应用层开发 --- 管道
linux·运维·c语言
NJCloud1 小时前
OpenStack(四)——进阶篇之私有网络配置与块存储服务(Cinder)部署
linux·网络·云计算·openstack
raindayinrain1 小时前
深入理解Linux内核--ext文件系统,open/write/read/close执行流程,性能优化
linux·性能优化·文件系统·文件系统调用
一条破秋裤1 小时前
STM32 学习笔记:STM32 简介与学习路线
笔记·stm32·学习