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

news2024/11/24 0:47:19

📃个人主页: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;             // 最后发送消息的进程 ID
    pid_t msg_lrpid;             // 最后接收消息的进程 ID
    time_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.coloradmin.cn/o/2238180.html

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

相关文章

推荐一款Windows卸载工具:

Glary Absolute Uninstaller类似于标准的Windows添加/删除程序&#xff0c;但功能更强大。标准的添加/删除程序无法完全卸载应用程序&#xff0c;这通常会在硬盘上留下损坏的注册表项和不需要的文件。您的计算机拥有的垃圾文件越多&#xff0c;运行速度就越慢。Absolute Uninst…

资产管理系统:SpringBoot技术实现

企业资产管理系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了企业资产管理系统的开发全过程。通过分析企业资产管理系统方面的不足&#xff0c;创建了一个计算机管理企业资产管理系统的方案。文章介绍了企…

[High Speed Serial ] Xilinx

Xilinx 高速串行数据接口 收发器产品涵盖了当今高速协议的方方面面。GTH 和 GTY 收发器提供要求苛刻的光互连所需的低抖动&#xff0c;并具有世界一流的自适应均衡功能&#xff0c;具有困难的背板操作所需的 PCS 功能。 Versal™ GTY &#xff08;32.75Gb/s&#xff09;&…

数据表单查询,简单查询操作

一.简单查询,带条件查询 1.请按照以下要求查询表goods中的数据。 goods表结构如下&#xff1a; -- 创建表&#xff1a; use test; drop table if exists goods; create table if not exists goods ( id int(11) not null comment 商品编号, name varchar(20) default null…

面试击穿mysql

Mysql三大范式: 第一范式&#xff08;1NF&#xff09;&#xff1a; 不符合第一范式的典型情况是在一个字段中存放多种不同类型的详细信息。例如&#xff0c;在商品表中&#xff0c;若将商品名称、价格和类型都存储在同一个字段中&#xff0c;会带来诸多弊端。首先&#xff0c;在…

科普|分享10个你不知道的公司数据安全防泄密措施,让企业数据安全牢不可破!

《孙子兵法》云&#xff1a;“知彼知己&#xff0c;百战不殆&#xff1b;不知彼而知己&#xff0c;一胜一负&#xff0c;不知彼&#xff0c;不知己&#xff0c;每战必殆。” 在数据安全领域&#xff0c;企业不仅要了解自身的数据资产和潜在威胁&#xff0c;还要掌握有效的数据安…

PyTorch音频分类实战(完整代码)

《SwanLab机器学习实战教程》是一个主打「开箱即用」的AI训练系列教程&#xff0c;我们致力于提供完善的数据集、源代码、实验记录以及环境安装方式&#xff0c;手把手帮助你跑起训练&#xff0c;解决问题。 音频分类任务是指将音频信号按照其内容的类别归属进行划分。例如&…

产品经理如何使用项目管理软件推进复杂项目按时上线

前言 相信很多产品同学或多或少都有过这样的经历&#xff1a;平时没有听到任何项目延期风险&#xff0c;但到了计划时间却迟迟无法提测……评审时没有任何argue&#xff0c;提测后发现开发的功能不是自己想要的……费劲九牛二虎之力终于让项目上线了&#xff0c;然而发现成果达…

nginx配置自签证书

文章目录 一.OpenSSL下载二.生成证书三.nginx配置 四.常见错误4.1 the event "ngx_master_xxxx" was not signaled for 5s 一.OpenSSL下载 # ubuntu sudo apt-get update sudo apt-get install openssl二.生成证书 # 创建存放证书的目录 mkdir /usr/local/mydata/s…

【elkb】kibana后台删除索引

打开kibana后台 点击 Management ---> Index Management 找到要删除的所以点击 点击delete index 删除成功

【Web前端】从回调到现代Promise与Async/Await

异步编程是一种让程序能够在等待某些操作完成的同时继续执行其他任务的关键技术&#xff0c;打破了传统编程中顺序执行代码的束缚。这种编程范式允许开发者构建出能够即时响应用户操作、高效处理网络请求和资源加载的应用程序。通过异步编程&#xff0c;JavaScript 能够在执行耗…

11.9.2024刷华为

文章目录 HJ31 单词倒排HJ32 密码提取语法知识记录 傻逼OD题目又不全又要收费&#xff0c;看毛线&#xff0c;莫名奇妙 HW这叼机构别搁这儿害人得不得&#xff1f; 我觉得我刷完原来的题目 过一遍华为机考的ED卷出处&#xff0c;就行了 HJ31 单词倒排 游戏本做过了好像 HJ3…

【Qt聊天室客户端】登录窗口

1. 验证码 具体实现 登录界面中创建验证码图片空间&#xff0c;并添加到布局管理器中 主要功能概述&#xff08;创建一个verifycodewidget类专门实现验证码操作&#xff09; 详细代码 // 头文件#ifndef VERIFYCODEWIDGET_H #define VERIFYCODEWIDGET_H#include <QWidget>…

如何判断 Hive 表是内部表还是外部表

在使用 Apache Hive 进行大数据处理时&#xff0c;理解表的类型&#xff08;内部表或外部表&#xff09;对于数据管理和维护至关重要。本篇文章将详细介绍如何判断 Hive 表是内部表还是外部表&#xff0c;并提供具体的操作示例。 目录 Hive 表的类型简介判断表类型的方法 方法…

初始JavaEE篇 —— 文件操作与IO

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 文件介绍 Java标准库中提供操作文件的类 文件系统操作 File类的介绍 File类的使用 文件内容操作 二进制文件的读写操作…

推荐一个Star超过2K的.Net轻量级的CMS开源项目

推荐一个具有模块化和可扩展的架构的CMS开源项目。 01 项目简介 Piranha CMS是一个轻量级且跨平台的CMS库&#xff0c;专为.NET 8设计。 该项目提供多种模板&#xff0c;具备CMS基本功能&#xff0c;也有空模板方便从头开始构建新网站&#xff0c;甚至可以作为移动应用的后端…

【react-router-dom】你可能要知道的关于v6.x自定义权限路由的实现

路由权限&#xff0c;是webapp或者说后台管理都会需要的业务功能。现在对react-routerv6.x的路由库&#xff0c;封装一个简易的权限路由&#xff0c;实现思路: 后台登录效果 代码实现 思路就是对路由表迭代出来的路由&#xff0c;用一个HOC来进行拦截&#xff0c;在真实进入路…

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

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

AI(11)-海报制作

1-画板 2-【矩形工具】 130x20&#xff1a;2个 100x10&#xff1a;1个 200x30&#xff1a;1个 3-参数调整 3-1-【颜色&#xff1a;65&#xff0c;10&#xff0c;40&#xff0c;0】&#xff0c;【无描边】 4-【shift】【倾斜工具】-调整矩形形状 4-1-单独调整一个矩形 4-2…

cherno引擎课 -

感谢b站星云图形的翻译&#xff1a;【双语】【最佳游戏引擎教程实战】【入门】&#xff08;1&#xff09;&#xff1a;Introducing the GAME ENGINE series!_哔哩哔哩_bilibili Introducing the GAMEENGINE series 希望&#xff1a;它是一个制作(互动)3D实时渲染应用程序的引…