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。

注意 :多个进程必须使用相同的pathname和proj_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:共享内存大小(字节),通常为页大小的整数倍。
    • shmflg:IPC_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);
  • 常用cmd :IPC_RMID删除共享内存(标记为销毁,当所有进程解除映射后真正释放)。

3. 命令

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

4. 注意事项

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

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

在进程间通信中,信号量用于同步 和互斥 ,通常是一个信号量数组 ,每个元素是一个计数器。它不同于线程中的无名信号量(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)。
    • semflg:IPC_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机制,足以应对大多数进程间协作场景。实际编程中,推荐优先考虑共享内存+信号量组合,兼顾效率与同步安全性。


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

相关推荐
qq_4017004121 小时前
Linux设备树详解
linux·运维·服务器
云梦谭1 天前
让 Linux 主动连接 Windows:配置 Windows 11 SSH 服务端
linux·windows·ssh
刚入门的大一新生1 天前
Linux-信号
linux·运维·服务器
silvamux_1 天前
10款模型写种草笔记:可发布性、返工来源与成本数据缺口复盘
java·数据库·笔记
bkspiderx1 天前
基于Socket的广播通信及C语言实现详解
c语言·开发语言·socket·广播·socket的广播通信
蒸蒸yyyyzwd1 天前
kafka、redis、mysql不熟悉问题总结
笔记·八股
hgy89691 天前
深度学习(小白笔记)——迁徙
人工智能·笔记·深度学习
摇滚侠1 天前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 11
java·笔记·python
辣知1 天前
辣知·化智93 被历史尘封的鬻子
学习
每天题库1 天前
西藏安全员 A 证备考真题题库 强化重点知识刷题提升通过率
学习·安全·考试·题库·考证