【Linux】 IPC 进程间通信(三)(消息队列 信号量)

📃个人主页:island1314

🔥个人专栏:Linux—登神长阶

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏  💞 💞 💞


一、消息队列 💌 

1. 了解

🔥 消息队列(Message Queue) 是一种进程间通信(IPC)机制,它允许不同进程或线程之间通过发送和接收消息来交换数据。

🔥 消息队列提供了一个先入先出(FIFO)结构,消息被放入队列后,接收者按顺序取出。消息队列广泛用于分布式系统、并发程序设计以及需要可靠异步通信的场景。

  • 消息队列的本质:一个进程向另外一个进程发送一块数据的方法
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

消息队列 VS 管道:消息队列基于消息,而管道则基于字节流

2. 消息队列函数

  • msgget:获取消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflag);

 参数:

  • key_t key: 消息队列的标识符(键值),用于区分不同的消息队列。可以使用 ftok 函数生成一个唯一的键值。如果一个消息队列已经存在,msgget 会返回该队列的标识符;如果该队列不存在,则会创建一个新的消息队列。
  • int msgflg: 控制标志,指示消息队列的操作方式。它可以是以下标志的组合:
    • IPC_CREAT: 如果消息队列不存在,则创建一个新的消息队列。
    • IPC_EXCL: 如果消息队列已经存在,则返回错误。
    • 权限位: 类似于文件的权限控制,使用类似 S_IRUSRS_IWUSR 的权限位来设置对消息队列的访问权限。

返回值

  • 成功时,msgget 返回一个非负整数,该整数是消息队列的标识符。失败时,返回 -1

  • msgctl删除消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);

 参数

  1. msqid 

    • 这是消息队列的标识符,是由 msgget() 函数返回的消息队列 ID
    • 表示要操作的目标消息队列
    • 在消息队列操作中,msqid 必须有效,即指向一个存在的消息队列
  2. cmd 

    • cmd 参数指定了 msgctl 要执行的操作类型。它有以下几种常用的值:

      • IPC_STAT:获取指定消息队列的当前状态和属性。
      • IPC_SET:修改指定消息队列的属性(如权限、队列容量等)。
      • IPC_RNID:删除指定的消息队列。

    这些命令定义了对消息队列的不同操作。

  3. buf

    • 这是一个指向 msqid_ds 结构体的指针,用于存储消息队列的状态或提供修改信息。
    • 对于 IPC_STAT 命令,用于接收当前消息队列的状态信息。
    • 对于 IPC_SET 命令,包含要设置的消息队列新属性信息。
    • 对于 IPC_RNID 命令,可以传递 NULL,因为删除操作不需要额外的数据

msqid_ds 结构体定义如下:

struct msqid_ds {struct ipc_perm msg_perm;    // 消息队列的权限size_t msg_qnum;             // 队列中的消息数量size_t msg_qbytes;           // 队列的最大字节数pid_t msg_lspid;             // 最后发送消息的进程 IDpid_t msg_lrpid;             // 最后接收消息的进程 IDtime_t msg_stime;            // 最后一次发送消息的时间time_t msg_rtime;            // 最后一次接收消息的时间time_t msg_ctime;            // 消息队列的最后修改时间
};

  • 。 msgsnd:发送消息,msgrcv: 接收消息

msgsnd 函数分析:

  • msqid

    • 消息队列标识符,指定要发送消息的目标消息队列
  • msgp

    • 一个指向消息结构的指针,包含了要发送的消息数据。
    • 消息结构应该是一个 struct,并且必须包含一个 long 类型的 mtype 字段,该字段用于表示消息的类型(消息队列通常会按消息类型排序)。
  • msgsz 

    • 消息的大小(字节数),不包括 mtype 字段。实际消息的大小应小于或等于消息队列的最大字节数限制。
  • msgflg

    • 标志位,用于指定消息发送的特性。常用的标志有:
      • IPC_NOWAIT:如果队列已满,消息不会阻塞调用,而是直接返回失败(设置 errno 为 EAGAIN)。
      • MSG_NOERROR:如果消息超出了队列的剩余空间,系统会自动截断消息,确保消息能够成功放入队列。

msgrcv 函数分析:

  • msqid

    • 消息队列的标识符,指定要接收消息的目标队列
  • msgp 

    • 一个指向消息结构的指针,用于存储接收到的消息
    • 该结构必须至少包含一个 long 类型的 mtype 字段,接收到的消息会被复制到这个结构中。
  • msgsz

    • 接收的最大字节数(不包括 mtype 字段)系统会在该大小限制内复制消息。如果消息的内容超过该大小,msgrcv 会截断消息。
  • msgtyp

    • 消息类型,用于指定从队列中接收哪一类的消息
    • 如果 msgtyp 是 0,则接收队列中最先到达的消息
    • 如果 msgtyp 是大于 0 的整数,则接收与该类型匹配的第一个消息
    • 如果 msgtyp 是负数,则接收小于或等于该类型的消息(按照优先级顺序)
  • msgflg 

    • 标志位,用于指定消息接收的特性。常见的标志有:
      • IPC_NOWAIT:如果没有符合条件的消息,msgrcv 会立即返回失败(并设置 errno 为 ENOMSG),而不是阻塞。
      • MSG_NOERROR:如果消息超出了 msgsz 的大小,系统会自动截断消息。
  • ipcs -q:查看消息队列的指令

  • ipcrm -q + id:删除消息队列指令

3. 案例

#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>const std::string pathName = "/home/island/code";
const int pro_id = 0600;struct mymsgbuf{long mtype;char mtext[108];
};int main()
{// 1. 创建消息队列key_t key = ftok(pathName.c_str(), pro_id);int msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0600);if(msqid == -1){std::cout << "msgget error" << std::endl;return 1;}else std::cout << "msgget success, id: " << msqid << std::endl;// 2. 发送消息struct mymsgbuf buf; // 创建系统提供的 struct msgbuf 类型数据块buf.mtype = 1;strncpy(buf.mtext, "IsLand1314 Hello Everyone", sizeof(buf.mtext) - 1);buf.mtext[sizeof(buf.mtext) - 1] = '\0'; // 确保空字符串结尾int n = msgsnd(msqid, &buf, sizeof(buf.mtext), 0);if(n == -1) {std::cout <<"msgsnd error" << std::endl;return 2;}else std::cout <<"msgsnd success, 消息类型为:" << buf.mtype << std::endl;// 3. 接收消息struct mymsgbuf info;size_t sz = msgrcv(msqid, &info, sizeof(info), 1, 0);if(sz == -1){std::cout << "msgrcv error" <<std::endl;}else std::cout << "msgrcv success: " << info.mtext << ", 消息类型为: " << info.mtype << std::endl;// 4. 销毁消息队列int ret = msgctl(msqid, IPC_RMID, nullptr);if(ret == -1) {std::cout << "msgget error" << std::endl;return 4;}else std::cout << "msgget success" << std::endl;return 0;
}

二、信号量 🦌

前提知识:

  • 共享资源:可以被多个进程访问的资源
  • 临界资源:在系统中被多个进程共享,但在任一时刻只允许一个进程使用的资源。将共享资源保护起来就是临界资源,例如通过互斥访问的方式保护共享资源,其就变成了临界资源
  • 临界区/非临界区:代码中有用于访问资源的代码,这些代码就叫做临界区;不访问资源的代码就叫做共享区

💦 信号量(Semaphore) 是一种同步机制,用于控制多个进程或线程对共享资源的访问。它主要用于解决进程间的同步与互斥问题,防止资源冲突和数据竞争。信号量是一个整数,用来表示可用资源的数量,操作系统通过它来协调并发执行的进程

由于信号量本质是一个对资源进行预订的计数器,因此必须解决下面两个问题:

  1. 信号量必须能被多个进程看到 。
  2. 信号量的 - - 与 ++ 操作(PV操作)必须具有原子性

💢 原子性:原子性是指一个操作是不可中断的,即该操作要么全部执行成功,要么全部执行失败,不存在执行到中间某个状态的情况

1. 信号量的种类

  1. 计数信号量(Counting Semaphore)计数信号量的值可以为任何非负整数,表示资源的数量。当资源可用时,信号量的值增加;当资源被占用时,信号量的值减少。

    • P操作(Proberen):进程申请资源,信号量值减1,如果值为负,进程会被阻塞。
    • V操作(Verhogen):进程释放资源,信号量值加1,若有阻塞进程,唤醒其中一个。
  2. 二值信号量(Binary Semaphore)二值信号量的值只能是0或1,通常用于互斥锁(mutex)。它可以确保在任意时刻只有一个进程能访问共享资源,防止多个进程同时执行关键区域代码。

访问临界资源的步骤:1.申请信号量 2.访问临界资源 3.释放信号量

  • 申请信号量的本质就是对临界资源的预定

信号量和共享内存、消息队列一样,需要实现被不同的进程访问,所以信号量本身也是一个共享资源

2. 信号量的工作原理

  • P操作(等待操作):信号量的值减1,如果信号量的值为负,表示没有足够的资源,调用该操作的进程会被阻塞,直到信号量的值大于等于0。
  • V操作(释放操作):信号量的值加1,如果有进程因为信号量值为负而被阻塞,V操作会唤醒一个阻塞的进程。

举个例子 💫

  • 假设有一个计数信号量 S,初始值为 5,表示有5个资源可以被并发访问。若一个进程执行P操作,S减1,变成4,表示该进程占用了一个资源。当该进程释放资源时,执行V操作,S加1,变回5

3. 信号量操作

由于信号量也是遵循System V标准的,所以它的常用方法和前面的类似。信号量主要是用于同步和互斥的。

保护的常见方式:

  • 互斥:任何时刻,只允许一个执行流(进程)访问资源

  • 同步:多个执行流,访问临界资源的时候,具有一定的顺序性

因此,我们所写的代码 = 访问临界资源的代码(临界区) + 不访问临界资源的代码(非临界区)

所谓的对共享资源的保护,本质是对访问共享资源的代码进行保护

(1)创建 / 获取信号量

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg) 

参数:

  1. key:是一个键值,用于唯一标识信号量集
  2. nsems:指定信号量集中信号量的数量,通常这个值至少为1
  3. semflg:是一组标志位,用于指定信号量集的属性

常见标志位:

  • IPC_CREAT:如果信号量集存在则获取并返回;如果不存在则创建
  • IPC_CREAT | IPC_EXCL:如果信号量集存在则报错;如果不存在则创建

返回值:成功返回非零的信号量标识符;失败返回 -1,并设置 errno 以指示错误原因

(2)删除信号量

#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...)

参数:

  • semid:是信号量集合的标识符,由 semget 函数返回
  • semnum:信号量在信号量集合中的索引(从0开始)(如果要删除整个信号量集,则填0)
  • cmd:指定要执行的控制命令

常见命令:IPC_RMID:删除信号量集合

返回值:成功返回0;失败返回-1并设置 errno

(3)操作信号量

#include <sys/sem.h>int semop(int semid, struct sembuf *sops, size_t nsops);

参数说明

  • semid:信号量集的标识符,通常通过 semget 函数获得。信号量集是一个由多个信号量组成的集合
  • sops 指向 struct sembuf 数组的指针,这个数组包含了要对信号量集合进行的操作
  • nsops:: 表示要执行的操作数目(即 sops 数组的长度)

semop 操作的核心是 struct sembuf 结构体,它定义了每个操作的细节。该结构体的定义如下:

struct sembuf {unsigned short sem_num;   // 信号量的索引(信号量集合中的第几个信号量)short sem_op;             // 操作数,表示对信号量的操作short sem_flg;            // 操作标志,控制操作的行为
};

(4)信号量指令

  • 查看信号量 ipcs -s

  • 删除信号量 ipcrm -s semid

4. 信号量的应用

  1. 互斥锁:二值信号量通常用于互斥,确保只有一个进程可以访问临界区,避免数据竞争。例如,多个进程需要访问共享文件时,可以使用信号量来保证每次只有一个进程能进行读写操作。

  2. 生产者消费者问题:信号量可以用于协调生产者和消费者的关系,控制缓冲区的读写操作。生产者放入商品时减少空位信号量,消费者取出商品时减少产品信号量,从而保证生产与消费的同步。

  3. 资源分配:在有限资源(如打印机、数据库连接等)情况下,信号量用来管理资源的分配,确保资源的公平使用。

  4. 进程同步:信号量也可用于进程同步,确保多个进程按照特定的顺序执行。例如,进程A完成某项任务后,信号量允许进程B开始执行。

5. 注意事项

  • 死锁:不当使用信号量(例如多个进程循环等待)可能导致死锁,进程永远无法继续执行。
  • 忙等待:如果信号量操作不当,可能导致进程处于等待状态,而没有有效地释放CPU资源,造成系统性能下降。
  • 顺序问题:多个进程同时等待或释放信号量时,可能出现执行顺序不符合预期的情况,因此需要在使用信号量时小心设计

三、思考 -- IPC 

System V 是如何实现IPC的,和管道为什么不同呢?

🐸 用户角度

  • 首先我们要知道操作系统是如何管理 IPC 的:先描述,再组织。
  • IPC有哪些属性呢?

根据上面我们可以发现,它们内部都有一个 ipc_perm 的东西。我们可以推测一下,在 OS 层面,IPC 是同类资源。

我们也可以获取IPC对应的属性,案例如下:

🐸 内核角度

 由于需要让 IPC 资源被所有进程看到,那么它一定是全局的。所以IPC资源在内核中一定是一个全局变量

  • 我们发现在消息队列、信号量与共享内存的源码中,结构体开头位置都是 kern_ipc_perm,这点和我们上面从用户层看到的是一样的。

此时,所有的IPC资源都可以直接被柔性数组直接指向

柔性数组(了解)

  • 柔性数组的定义在 C 语言标准(特别是 C99 及以后版本)中引入
  • 允许结构体的最后一个成员声明为一个数组,但不指定数组的大小
  • 结构体的大小由前面的成员决定,而柔性数组的大小则依赖于后续内存的分配
struct my_struct {int size;              // 普通成员char data[];           // 柔性数组成员(没有指定大小)
};

言归正传,例如:

  • p[0] = (struct kern_ipc_perm) &(shmid_kernel)
  • p[1] = (struct kern_ipc_perm) &(msg_queue)
  • p[2] = (struct kern_ipc_perm) &(sem_array) 

那么不就可以使用柔性数组 (类型强转) ,管理所有的IPC资源了吗?数组下标就是之前的 xxid,即 xxget 的返回值!这也就是为什么之前我们见到的各种 IPC资源的 id 是连续的了。

所以,所有的 IPC 资源之所以能够区分 IPC 的唯一性,都是通过 key来进行的

注意:各类型的 IPC 资源之间的 key 也可能会冲突

  • 那么此时怎么访问IPC资源的其它属性呢?

直接强转,(struct msg_queue*) p[1] ->其它属性

  1. 那么一个指针,指向结构体的第一个元素,其实就是指向了整个结构体
  2. 访问头部,直接访问
  3. 访问其它属性,做强转,这种结构不就是C++中的多态吗?

这时,我们所看到的 kern_ipc_perm 就是 基类,与之相关的三个就是子类,继承了基类,此时就可以使用基类来管理所有的子类了,这是 C语言实现多态的另一种方式

那具体是怎么识别是哪一种子类的呢?

  • 实际在内核中,会定义各种的 ipc_ids,但是它们的 entries 指针都指向同一个 kern_ipc_perm 数组


四、小结

以上就是我对消息队列、信号量、IPC 的理解,那么我们的进程间通信(IPC) 就讲到这里啦,我们后面就开始进入进程信号的知识哩

【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束啦,如果我的这篇博客可以给你提供有益的参考和启示,可以三连支持一下 !!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/59029.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker:镜像构建 DockerFile

Docker&#xff1a;镜像构建 DockerFile 镜像构建docker build DockerfileFROMCOPYENVWORKDIRADDRUNCMDENTRYPOINTUSERARGVOLUME 镜像构建 在Docker官方提供的镜像中&#xff0c;大部分都是基础镜像&#xff0c;他们只提供某个简单的功能&#xff0c;如果想要一个功能更加丰富…

遥控器数图控链路系统核心技术+算法详解

一、核心技术 无线通信技术 遥控器数图控链路系统主要基于无线通信技术进行数据传输。通过特定的调制、编码和信号处理技术&#xff0c;将遥控器的操作指令转化为无线电信号&#xff0c;并传输给被控制设备。被控制设备接收到信号后&#xff0c;再将其解码为可识别的指令&…

Kafka 源码 KRaft 模式本地运行

KRaft&#xff08;Kafka Raft Metadata mode&#xff09;&#xff0c;从版本 2.8.0 开始作为测试特性引入&#xff0c;并在后续版本中持续得到改进和增强。 KRaft 模式是指 Kafka 使用 Raft 协议来管理集群元数据的一种运行模式&#xff0c;这标志着 Kafka 向去除对 ZooKeeper …

Android下的系统调用 (syscall),内联汇编syscall

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 什么是系统调用 (syscall) 系统调用是操作系统提供给应用程序的一组接口&#xff0c;允许用户空间程序与内核进行交互。 在 Android&#xff08;基于 Linux …

RAGulator:如何识别和缓解大模型所谓的“忠实幻觉”

RAGulator&#xff0c;一个轻量级的、用于检测RAG系统中语义上与上下文不符&#xff08;OOC&#xff09;的LLM生成文本的检测器 论文链接:https://arxiv.org/abs/2411.03920 论文概述 实时检测大型语言模型&#xff08;LLM&#xff09;生成的与上下文不符的输出问题&#xff…

Git核心概念

目录 版本控制 什么是版本控制 为什么要版本控制 本地版本控制系统 集中化的版本控制系统 分布式版本控制系统 认识Git Git简史 Git与其他版本管理系统的主要区别 Git的三种状态 Git使用快速入门 获取Git仓库 记录每次更新到仓库 一个好的 Git 提交消息如下&#…

leetcode82:删除排序链表中的重复节点||

给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,5]示例 2&#xff1a; 输入&#xff1a;head [1,1,1,2…

基于SpringBoot的Java教学支持系统开发指南

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理教学辅助平台的相关信息成为必然。开发合适…

python3的基本数据类型:可变集合的用法

一. 简介 前面学习了 python3中的一种基本数据类型-集合&#xff0c;文章如下&#xff1a; python3的基本数据类型&#xff1a;集合的创建与分类-CSDN博客 本文继续学习 Python3中的集合&#xff0c;主要学习 可变集合的用法。 二. python3的基本类型&#xff1a;可变集合的…

【Linux系列】 环境配置文件合并的艺术:从`.env`到`.env.combined`

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

C/C++语言基础--C++模板与元编程系列五(可变惨模板,形参包展开,折叠表达式)

本专栏目的 更新C/C的基础语法&#xff0c;包括C的一些新特性 前言 模板与元编程是C的重要特点&#xff0c;也是难点&#xff0c;本人预计将会更新10期左右进行讲解&#xff0c;这是第五期&#xff0c;讲解可变惨模板&#xff0c;形参包展开&#xff0c;折叠表达式等&#x…

Redis设计与实现 学习笔记 第十六章 Sentinel

Sentinel&#xff08;哨岗、哨兵&#xff09;是Redis的高可用性&#xff08;high availability&#xff09;解决方案&#xff1a;由一个或多个Sentinel实例&#xff08;instance&#xff09;组成的Sentinel系统可以监视任意多个主服务器&#xff0c;以及这些主服务器属下的从服…

贪心算法day05(k次取反后最大数组和 田径赛马)

目录 1.k次取反后最大化的数组和 2.按身高排序 3.优势洗牌 1.k次取反后最大化的数组和 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 代码&#xff1a; class Solution {public int largestSumAfterKNegations(int[] nums, int k) {//如…

易语言加载dll模拟windows鼠标轨迹移动

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

Go语言的常用内置函数

文章目录 一、Strings包字符串处理包定义Strings包的基本用法Strconv包中常用函数 二、Time包三、Math包math包概述使用math包 四、随机数包&#xff08;rand&#xff09; 一、Strings包 字符串处理包定义 Strings包简介&#xff1a; 一般编程语言包含的字符串处理库功能区别…

Springboot+Vue+mysql前后端分离的Java项目部署教程

参考了网上许多文章&#xff0c;有的使用的是nginx&#xff0c;eclipse&#xff0c;其实只要是数据库或者java的软件基本都大同小异。 本人使用phpstudy对项目进行部署&#xff0c;亲测有效。 需要的软件&#xff1a; 1.Node.js安装&#xff08;ps&#xff1a;这一步我也不知道…

Linux系统程序设计--2. 文件I/O

文件I/O 标准C的I/O FILE结构体 下面只列出了5个成员 可以观察到&#xff0c;有些函数没有FILE类型的结构体指针例如printf主要是一些标准输出&#xff0c;因为其内部用到了stdin&#xff0c;stdout&#xff0c;stderr查找文件所在的位置:find \ -name stat.h查找头文件所…

HarmonyOS Next 实战卡片开发 02

HarmonyOS Next 实战卡片开发 02 卡片开发中&#xff0c;还有一个难点是显示图片。其中分为显示本地图片和显示网络图片 显示本地图片 卡片可以显示本地图片&#xff0c;如存放在应用临时目录下的图片。路径比如 /data/app/el2/100/base/你的项目boundleName/temp/123.png 以…

【C++ 算法进阶】算法提升十一 十二

目录标题 让字符串成为回文串的最少插入次数题目题目分析代码题目题目 字符子串 &#xff08;滑动窗口&#xff09;题目题目分析代码 最长连续子序列 &#xff08;头尾表&#xff09;题目题目分析代码 让字符串成为回文串的最少插入次数 题目 本题为为LC原题 题目如下 题目分…

让redis一直开启服务/自动启动

文章目录 你的redis是怎么打开的黑窗不能关?必须要自动启动吗?再说说mysql 本文的所有指令都建议在管理员权限下打开cmd控制台 推荐的以管理员身份打开控制台的方式 Win R 打开运行 输入cmdShift Ctrl Enter 你的redis是怎么打开的 安装过redis的朋友都知道, redis的安…