Linux相关概念和易错知识点(52)(基于System V的信号量和消息队列)

目录

之前我分享过基于System V的共享内存的使用,上篇文章也引入mmap分析了其底层实现,即共享映射文件缓冲区使得不同进程看到同一块区域,本篇文章会继续分享System V标准下的信号量和消息队列。整体难度不大,不需记忆接口,了解即可,因此本文借助AI生成,仅做简单记录和介绍。

1、System V信号量

(1)信号量的本质与核心原理

无论是POSIX还是System V标准下,信号量本质是一个计数器,即资源的预定机制,主要用于进程间同步与互斥管理,在用环形队列实现生产者消费者模型中有很大用处。

信号量操作分为P操作(申请资源)与V操作(归还资源)。

信号量计数器s的含义:

  • s > 0:表示当前可用资源的个数
  • s = 0:表示无可用资源,且无等待进程
  • s < 0:表示无可用资源,|s|为等待队列中进程的个数

内核中信号量可由结构体表示:

cpp 复制代码
struct semaphore {
    int value;
    struct PCB* queue;
};

(2)PV原语(均为原子操作)

a. P原语(申请资源)

  1. 执行 value--
  2. 判断 value < 0:若成立,说明资源不足
  3. 将当前进程加入等待队列并阻塞
  4. 进程调度切换,等待被唤醒
cpp 复制代码
void P(struct semaphore* sem) {
    sem->value--;
    if (sem->value < 0) {
        add_to_queue(sem->queue, current_process);
        block_current_process();
    }
}

b. V原语(归还资源)

  1. 执行 value++
  2. 判断 value <= 0:若成立,说明有进程在等待
  3. 从等待队列唤醒一个进程至就绪态
  4. 唤醒后的进程可重新尝试申请信号量
cpp 复制代码
void V(struct semaphore* sem) {
    sem->value++;
    if (sem->value <= 0) {
        struct PCB* proc = remove_from_queue(sem->queue);
        wake_up_process(proc);
    }
}

(3)System V信号量接口

a. 创建信号量集 semget

cpp 复制代码
int semget(key_t key, int nsems, int semflg);
  • key:唯一标识,与共享内存规则一致
  • nsems:信号量集中信号量的个数
  • semflg:支持 IPC_CREAT 与 IPC_EXCL
  • 返回值:成功返回 semid,失败返回 -1

b. 控制信号量 semctl

cpp 复制代码
int semctl(int semid, int semnum, int cmd, ...);
  • semid:信号量集标识符
  • semnum:信号量下标,从0开始
  • cmd:
    • SETVAL:初始化单个信号量,该操作不具备原子性
    • IPC_RMID:删除整个信号量集,第二个参数无意义
  • 返回值:失败返回 -1

c. PV操作 semop

cpp 复制代码
int semop(int semid, struct sembuf* sops, size_t nsops);
  • sops:操作结构体数组
  • nsops:数组长度
  • 可同时对多个信号量执行原子操作,例如一个P、一个V
  • 返回值:成功 0,失败 -1

(4)信号量管理

  • 查看信号量:ipcs -s
  • 删除信号量:ipcrm -s

System V 信号量生命周期随内核,进程退出不会自动释放,必须手动删除,通常在析构函数中完成释放逻辑。

2、System V 消息队列

(1)消息队列特点

a. 允许任意进程间通信

消息队列是内核提供的 System V IPC 机制,不要求进程之间存在亲缘关系,只要多个进程通过同一个 key 访问同一个消息队列,即可实现数据收发,支持无关联进程间通信。

b. 消息自带类型标识,可按类型选择性接收

每条消息必须以一个 long 类型的消息类型字段开头,接收方可以指定只接收某一类型的消息,不匹配类型的消息会继续保留在队列中,也可以不指定类型直接读取最早消息,实现消息分类与分发。

c. 支持全双工通信

通信双方可以使用不同消息类型分别进行发送和接收,无需切换读写方向,双向通信互不干扰,天然实现全双工。

d. 生命周期随内核

消息队列创建后会持续存在于内核中,进程退出不会自动删除,只有通过调用msgctl(IPC_RMID) 或系统重启才会销毁。

e. 面向数据块传输

以消息为单位传输,每条消息独立,不同于管道的字节流传输,边界清晰。

(2)消息队列接口函数

a. 创建 / 获取消息队列 msgget

cpp 复制代码
int msgget(key_t key, int msgflg);
  • key:消息队列的唯一标识符,可通过 ftok 生成或手动指定
  • msgflg:权限位 + 创建标志
    • IPC_CREAT:队列不存在则创建,存在则返回已存在队列 ID
    • IPC_EXCL:与 IPC_CREAT 配合使用,队列已存在则报错,确保新建队列
    • 例:IPC_CREAT | 0666
  • 返回值:成功返回消息队列 ID msqid,失败返回 -1

b. 消息队列控制 msgctl

cpp 复制代码
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • cmd:
    • IPC_RMID:删除消息队列,此时 buf 传 NULL
    • IPC_STAT:获取队列属性信息,存入 buf
    • IPC_SET:设置队列属性
  • 常用于删除队列:msgctl(msqid, IPC_RMID, NULL);

c. 发送消息 msgsnd

cpp 复制代码
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • msgp:指向自定义消息结构体指针,结构体必须以 long mtype 开头
  • msgsz:消息数据部分长度,不包含 mtype 字段
  • msgflg:一般为 0(阻塞发送),或 IPC_NOWAIT(非阻塞)
  • 消息结构体示例:
cpp 复制代码
  struct msgbuf {
      long mtype;
      char mtext[1024];
  };

d. 接收消息 msgrcv

cpp 复制代码
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • msgtyp:要接收的消息类型
    • 0:接收队列中第一条任意类型消息
    • 0:接收指定类型的第一条消息

    • <0:接收类型 ≤ |msgtyp| 的最小类型消息
  • msgsz:数据缓冲区大小
  • msgflg:一般为 0(阻塞),或 IPC_NOWAIT、MSG_NOERROR 等

(3)命令行管理

  • 查看消息队列:ipcs -q
  • 删除消息队列:ipcrm -q msqid
相关推荐
wanhengidc8 小时前
云手机 高振畅玩不踩坑
运维·服务器·安全·web安全·智能手机
有谁看见我的剑了?8 小时前
linux 添加硬盘后系统识别不到硬盘处理
linux·运维·服务器
JoyCong19988 小时前
ToDesk远程屏幕墙技术白皮书:如何重塑全局运维视界
运维·电脑·远程工作
偶尔上线经常挺尸9 小时前
《100个“反常识”经验15:Nginx 502排查:从应用到内核》
运维·nginx·性能调优·反向代理·502错误·http排错
yc_12249 小时前
用 Visual Studio 远程调试 Linux:从零到流畅的完整指南
linux·ide·visual studio
思茂信息9 小时前
CST软件如何进行参数化扫描?
运维·开发语言·javascript·windows·ecmascript·软件工程·软件需求
计算机安禾10 小时前
【Linux从入门到精通】第31篇:防火墙漫谈——iptables与firewalld防护指南
linux·运维·php
下一页盛夏花开10 小时前
ubuntu 20中安装QT以后出现红色空心断点
linux·运维·ubuntu
金色光环10 小时前
FreeModbus释放底层的 TCP 监听端口
服务器·网络·tcp/ip
sanshanjianke10 小时前
Thunderobot 911ME 笔记本 Linux 风扇控制研究
linux