【Linux】进程间通信——System V消息队列和信号量

news2024/10/8 17:47:34

一、消息队列

1.1 概念

进程间通信的原理是让不同进程看到同一份资源,资源种类的不同就决定了通信方式的差异。如果用管道通信,则资源是文件缓冲区;如果用共享内存,则资源是内存块

消息队列是由操作系统提供的资源,其本质就是内核中存放通信数据块的一个队列,可供两个进程进行双向通信。

两个要进行通信的进程以数据块的形式发送数据,数据块被链入到消息队列中,并发送给目标进程

如果两个进程的数据块被存放到一个消息队列中,如何区分数据块是自己发送的还是对方进程发给自己的呢?所以数据块需要有自己的类型,根据类型来进行区分。

系统中存在这么多进程,因此肯定也有不止一个消息队列。既然消息队列由OS提供,那么OS必定要对消息队列进行管理。所以内核中除了有队列的结构外,一定需要其他的数据结构对消息队列进行描述和组织。

消息队列的内核结构体:

struct msqid_ds {
    struct ipc_perm msg_perm;     /* Ownership and permissions */
    time_t          msg_stime;    /* Time of last msgsnd(2) */
    time_t          msg_rtime;    /* Time of last msgrcv(2) */
    time_t          msg_ctime;    /* Time of last change */
    unsigned long   __msg_cbytes; /* Current number of bytes in
                                     queue (nonstandard) */
    msgqnum_t       msg_qnum;     /* Current number of messages
                                     in queue */
    msglen_t        msg_qbytes;   /* Maximum number of bytes
                                     allowed in queue */
    pid_t           msg_lspid;    /* PID of last msgsnd(2) */
    pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
};

细心的人可能已经发现了,消息队列的内核结构体,与我们之前讲的共享内存的内核结构体十分类似,特别是它们都有一个ipc_perm结构体

struct ipc_perm {
    key_t          __key;    /* Key supplied to shmget(2) */
    uid_t          uid;      /* Effective UID of owner */
    gid_t          gid;      /* Effective GID of owner */
    uid_t          cuid;     /* Effective UID of creator */
    gid_t          cgid;     /* Effective GID of creator */
    unsigned short mode;     /* Permissions + SHM_DEST and
                                       SHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};

其中存放了key等非常重要的信息,说明消息队列和共享内存一样,都有自己的key来标识自己在系统中的唯一性

1.2 API介绍和使用

如果有读者阅读过我前面讲共享内存的文章 【Linux】进程间通信——System V共享内存_共享内存进程间通信-CSDN博客

本文讲的消息队列和信号量与前面提到的共享内存同属于System V进程间通信方式,其内核结构体、API定义和使用方式都十分相似,大家可以对比一下

(1)msgget

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);

msgget函数用于创建消息队列,若创建成功会返回msqid,即消息队列标识符; 创建失败返回-1

其中:

  • key:类型为key_t,内核中标识消息队列的唯一标识符,需要用户生成
  • msgflg:标志位。其中IPC_CREAT表示如果申请的消息队列不存在就创建,存在就获取并返回;IPC_CREAT|IPC_EXCL表示如果申请的消息队列不存在就创建,存在就出错并返回 

在前面讲共享内存时,用于创建共享内存的函数shmget中也有参数key和权限位flg,而用于创建消息队列的函数中也有这两个参数,并且用法也是相同的。都有系统中标识唯一性的key,都返回用户层标识唯一性的标识符。

而这里的key,也是通过ftok函数生成的,关于ftok函数我们在讲共享内存时已经讲过了。

可以看出,消息队列的接口和共享内存的接口是非常类似的,很多概念都是共通的

(2)msgctl

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msgctl函数用于控制消息队列,和共享内存的shmctl函数也是非常类似,类似到我甚至可以直接把讲共享内存时的原话修改一下放到这篇文章中

其中:

  • msqid自然就是在创建消息队列时返回的标识符了
  • cmd:需要做的操作。IPC_STAT为获取消息队列属性;IPC_SET为设置消息队列的权限相关属性;IPC_RMID为删除消息队列并释放队列中的所有消息
  • buf:类型为msqid_ds,即消息队列的内核结构体。IPC_RMID操作中可设置为nullptr

(3)msgsnd

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

这里开始就与共享内存有所不同了,共享内存需要挂接到进程的地址空间上,不用时再去掉关联;而消息队列则是向指定消息队列中发送数据块或接收数据块

其中:

  • msgp:数据块的起始地址
  • msgsz:数据块大小
  • msgflg:0为阻塞状态,若消息队列空间满则阻塞;IPC_NOWAIT为消息队列满则返回

但是说了这么多,这数据块到底长啥样?我们怎么创建一个数据块?

struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[1];    /* message data */
};

这就是我们的数据块结构体,其中mtype是数据块的类型(必须大于0),mtext是消息的内容

但是为什么字符数组mtext大小只有1呢?实际上在使用时,这个结构体是需要用户自己定义的,只需要保证第一个字段是long类型第二个字段是消息内容即可,消息的大小由用户决定

所以未来我们想用消息队列发一个消息,只需要定义一个数据块结构体,然后创建对象,填充对应字段,并向msgsnd函数中传入该对象的指针即可。

(4)msgrcv

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

有发就有收,msgrcv函数用于从消息队列中读取数据块

其中:

  • msgtyp:要读取的数据块类型,为0则读取消息队列的第一条消息,大于0则读取消息队列中第一条和msgtyp相同类型的消息,小于0则读取消息队列中所有类型不大于msgtyp的绝对值的消息中类型最小的消息
  • msgflg:0为阻塞状态;IPC_NOWAIT代表如果消息队列中没有指定类型的数据块则立刻返回并设置错误码为ENOMSG;MSG_EXCEPT代表当msgtyp大于0时读取队列中与msgtyp相同的第一条消息;MSG_NOERROR代表如果消息文本长度超过msgsz则截断该文本

(5)查看系统中的消息队列

通过命令 ipcs -q 查看当前所有消息队列

通过命令 ipcrm -q msgid 删除指定消息队列

二、信号量

2.1 概念

前文提到,共享内存不具有任何的保护机制,也就是说如果我们不加保护,当一个进程没有完整的把内容写入时,另一方就开始读取,可能会导致数据的收发方出现数据不一致问题

像这种类似的不被保护的共享资源,被多个执行流同时访问时,都可能会出现这类问题。例如我们过年抢回家的车票时,车票的余量也是共享资源,如果不加以保护,则可能会导致余票变为负数的情况(抢到车票后车票余量需要减少,这两步是分开的,所以可能导致同时抢到票的用户数量大于车票的余量,但仍然判断抢票成功)

其中一个解决方案是通过加锁来实现对共享资源的互斥访问,即任何时刻只允许一个执行流访问共享资源

对于这类被多个执行流共享的,但通过某种方式保证其互斥访问的共享资源称为临界资源。例如管道也是一种临界资源。在整个程序的所有代码中,可能只有一小部分的代码访问了临界资源,这部分代码就称为临界区

说了这么多,到底什么是信号量呢?

信号量(Semaphore)本质就是一个计数器,用于记录临界资源中资源的数量。临界资源能被分为多少份,则信号量为几。

例如我们要看电影,自然需要先买票,只有买了票,我们才能够拥有使用电影院座位的资格。而电影院的剩余票数需要用一个计数器记录,每卖出一张票,则计数器减一,当计数器为0时就说明票已经卖光了。

电影院中的座位就是许多共享资源,而买票就是对这些座位的预定机制。也就是说,要具有访问共享资源的权限,首先就得申请计数器。但申请了计数器,并不代表一定访问共享资源,就像我只是买了电影票,我已经有了使用该座位的资格,不管我去不去看电影这个座位都是我的。计数器可以有效保证进入共享资源的执行流的数量,申请计数器就等同于“买票”。

而这个计数器,就叫做信号量!

有了信号量,我们就可以保证共享资源不会同时被多个执行流同时访问,避免了数据不一致问题

假设我们的信号量初始值为1,也就是将共享资源看作一个整体,则说明同一时间内只有一个执行流能够申请到信号量并具有访问共享资源的资格,实际上我们就实现了互斥。这种值只可能为1或0的信号量叫做二元信号量,其本质上就相当于一把锁。

到这里,大家可能还有一些疑惑。信号量既然是一个计数器,那和进程间通信又有什么关系呢?实际上进程间通信并不仅仅是进行数据的来往进程间的协同也是进程间通信的一部分

问题又来了,既然进程要申请信号量,信号量不也是共享资源吗?既然信号量要保证共享资源的安全,首先得保证自己是安全的。因此信号量的申请(P)释放(V)操作都是原子的,即只有没做和做完了两种状态,不存在“正在做”的情况。

拓展:我们能否用一个变量来代替信号量完成计数操作呢?不行!因为减少变量的值,这个操作并不是安全的。

在编程语言层面,看上去我们可以用一条语句完成减操作,但是变成汇编后则有多条语句。进程在执行这些汇编语句的过程中随时可能会被切换,存在风险

更多细节可以阅读:Linux下信号量的P.V操作如何保证其原子性_51CTO博客_信号量 Linux

我们来看看信号量的内核结构体:

struct semid_ds {
    struct ipc_perm sem_perm;  /* Ownership and permissions */
    time_t          sem_otime; /* Last semop time */
    time_t          sem_ctime; /* Last change time */
    unsigned long   sem_nsems; /* No. of semaphores in set */
};

同样,其内部也包含了ipc_perm结构体,这说明什么?信号量也有自己的key。也再次证明,同为System V进程间通信方式,共享内存、消息队列和信号量具有很多相似之处

2.2 API介绍和使用

(1)semget

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

semget函数用于创建一个信号量(集),如果成功将会返回该信号量集的semid,失败返回-1并设置错误码

其中:

  • 参数key和共享内存、消息队列一样,一般通过ftok函数生成
  • nsems:信号量集需要的信号量数目,大部分情况下为1
  • semflg:还是和前面一样,标志位。其中IPC_CREAT表示如果申请的信号量集不存在就创建,存在就获取并返回;IPC_CREAT|IPC_EXCL表示如果申请的信号量集不存在就创建,存在就出错并返回

(2)semctl

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);

semctl函数用于控制信号量集,成功会根据cmd返回一个非负值,失败返回-1并设置错误码 

如果需要,第四个参数通常被设置为union semun arg,其结构如下:

union semun {
    int              val;    /* SETVAL的值 */
    struct semid_ds *buf;    /* 用于IPC_STAT, IPC_SET的缓冲区 */
    unsigned short  *array;  /* 用于GETALL, SETALL的数组 */
    struct seminfo  *__buf;  /* IPC_INFO的缓冲区(Linux特有) */
};

 其中:

  • semid:信号量集id
  • semnum:指定当前信号量集的某一个信号量,类似于下标
  • cmd:要进行的操作,例如SETVAL为将指定信号量的值设置为arg.val;IPC_RMID为删除信号量集,删除操作则不需要传入arg参数;SETALL为设置信号量集中信号量的值为arg.array内部的值

(3)semop

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);

semop函数用于设置信号量的值,成功返回0,失败返回-1并设置错误码

关于sops参数,struct sembuf的结构如下:

struct sembuf{
    unsigned short sem_num;  /* semaphore number */
    short          sem_op;   /* semaphore operation */
    short          sem_flg;  /* operation flags */
};

其中:

  • sem_num为指定信号量在信号量集中的编号,如果在创建信号量集时nsems参数设置为1,则这里填0即可
  • sem_op:若该值为正数则为释放信号量操作,sem_op的值会加到信号量的值上;值为负数则为申请信号量操作,若信号量的值不足以支撑该申请操作且sem_flg不为IPC_NOWAIT,则阻塞;若sem_op值为0,且sem_flg不为IPC_NOWAIT,则进程阻塞直到信号量的值变为0
  • sem_flg:若设置为IPC_NOWAIT且信号的某个操作无法正常进行时,则进程不会阻塞,而是出错并返回;若设置为IPC_UNDO,则当进程异常终止没有释放信号量时,由操作系统释放,避免了因进程未释放信号量导致其他进程无法申请的问题

nsops参数指定了需要修改的信号量个数,即sops数组中的元素个数,一般我们只需要对一个信号量进行设置,所以设置为1即可

完.

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

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

相关文章

注意!新增一本期刊解除On Hold!仍有37本无法检索慎投!

On Hold期刊 2024年9月29日&#xff0c;小编从WOS数据库查到新增一本ESCI期刊《SOCAR Proceedings》解除On Hold标识 目前共37本期刊被标记&#xff0c;期刊整理如下&#xff0c;请注意避雷&#xff01; 01 解除风险期刊 SOCAR Proceedings • ISSN&#xff1a;2218-6867 •…

RAR解压缩软件的全面指南:压缩、加密、修复功能一应俱全

在日常文件管理中&#xff0c;压缩文件格式与解压缩工具是不可或缺的组成部分。而RAR格式&#xff0c;凭借其高效的压缩率、丰富的功能和灵活的文件管理方式&#xff0c;成为了用户最常使用的压缩格式之一。 作为处理RAR格式的专业工具&#xff0c;RAR解压缩软件具备压缩、加密…

马哥亲讲k8s集群搭建

文章目录 Docker和K8s安装1.docker安装2.安装cri-dockerd3.安装kubelet、kubeadm、kubectl4.整合kubelet和cri-dockerd 集群节点部署1.配置国内镜像站2.方式一&#xff1a;命令初始化1.kubeadm init2.保存初始化token3.拷贝/etc/kubernetes/admin.conf4.部署网络插件5.kubectl …

reactNative本地调试localhost踩坑

本地调试请求localhost的时候 1.要和电脑处在同一局域网下面&#xff08;同一个wifi&#xff09; 2.把baseURL的localhost改成命令行中ipconfig查询到的IPv4 地址 . . . . . . . . . . . . : &#xff08;例如&#xff09;192.168.1.103 如果报错Net Work Error&#xff0c;可…

算法知识点————贪心

贪心&#xff1a;只考虑局部最优解&#xff0c;不考虑全部最优解。有时候得不到最优解。 DP&#xff1a;考虑全局最优解。DP的特点&#xff1a;无后效性&#xff08;正在求解的时候不关心前面的解是怎么求的&#xff09;&#xff1b; 二者都是在求最优解的&#xff0c;都有最优…

如何扫描HTTP代理:步骤与注意事项

HTTP代理是一个复杂的过程&#xff0c;通常用于寻找可用的代理服务器&#xff0c;以便在网络中实现匿名或加速访问。虽然这个过程可以帮助用户找到适合的代理&#xff0c;但也需要注意合法性和道德问题。本文将介绍如何扫描HTTP代理&#xff0c;并提供一些建议和注意事项。 什…

剖析十大经典二叉树题目:C 语言代码实现与深度解读

&#x1f4af;前言 二叉树是数据结构中的重要概念&#xff0c;在算法和编程中有着广泛的应用。以下是十大经典的二叉树题目及其解析与 C 语言代码实现&#xff0c;同时也会说明题目来源。 二叉树的基本概念&#x1f449;【剖析二叉树】 目录 &#x1f4af;二叉树的遍历 ⭐前…

AI大模型真的是大龄程序员的新的出路吗?_大龄程序员ai创业

前言 在IT行业的高速运转中&#xff0c;许多资深程序员到了一定年龄后&#xff0c;会发现自己陷入了职业发展的瓶颈。尤其是在北京这样的大厂&#xff0c;业务波动、部门调整以及裁员风险&#xff0c;都让“40”的程序员们感受到了前所未有的压力。当昔日的技术热情逐渐消退&a…

在C#中使用适配器Adapter模式和扩展方法解决面向对象设计问题

之前有阵子在业余时间拓展自己的一个游戏框架&#xff0c;结果在实现的过程中发现一个设计问题。这个游戏框架基于MonoGame实现&#xff0c;在MonoGame中&#xff0c;所有的材质渲染&#xff08;Texture Rendering&#xff09;都是通过SpriteBatch类来完成的。举个例子&#xf…

新书速览|你好,C++

《你好&#xff0c;C》 本书内容 《你好&#xff0c;C》主要介绍C开发环境的搭建、基础语法知识、面向对象编程思想以及标准模板库的应用&#xff0c;特别针对初学者在学习C过程中可能遇到的难点提供了解决方案。全书共分13章&#xff0c;以一个工资程序的不断优化和完善为线索…

ChatGPT助力文献综述写作:提升效率与写作技巧!

文献综述在论文写作中占有举足轻重的地位。它不仅帮助我们梳理已有的研究成果&#xff0c;还能为自己的研究奠定基础。许多同学在撰写文献综述时常常感到头疼&#xff1a;如何处理海量的信息&#xff1f;如何将不同的观点有条理地整合起来&#xff1f;再加上学术语言的高要求&a…

定时任务。

引入 1.启动类上加上注解 2.新建一个定时任务的管理类&#xff0c;交给Spring管理 案例 案例1&#xff1a;fixedRate //上次任务开始到下次任务开始的时间间隔为5秒 //每隔5秒执行一次,不需要等上个任务执行完 Scheduled(fixedRate 5000) public void mask01() throws Inte…

python:web自动化工具selenium安装和配置(1)

UI自动化测试 UI自动化测试&#xff08;User Interface Automation Testing&#xff09;是一种通过编写脚本或使用自动化测试工具&#xff0c;对界面&#xff08;UI&#xff09;进行自动化测试的方法。原理主要是模拟用户打开客户端或网页的UI界面&#xff0c;自动化执行用户界…

【Java 问题】基础——泛型

接上文 泛型 47.Java 泛型了解么&#xff1f;什么是类型擦除&#xff1f;介绍一下常用的通配符&#xff1f; 47.Java 泛型了解么&#xff1f;什么是类型擦除&#xff1f;介绍一下常用的通配符&#xff1f; 什么是泛型&#xff1f; Java 泛型&#xff08;generics&#xff09;是…

REINFORCEMENT LEARNING THROUGH ACTIVE INFERENCE

摘要 强化学习&#xff08;RL&#xff09;的核心原则是智能体寻求最大化累积奖励之和。相比之下&#xff0c;主动推理&#xff0c;认知和计算神经科学中的一个新兴框架&#xff0c;提出代理人采取行动&#xff0c;以最大限度地提高有偏见的生成模型的证据。在这里&#xff0c;…

上门安装维修系统小程序开发详解及源码示例

随着智能家居和设备的普及&#xff0c;消费者对上门安装和维修服务的需求日益增加。为了满足这一市场需求&#xff0c;开发一款上门安装维修系统小程序成为了一种有效的解决方案。本文将详细介绍上门安装维修系统小程序的开发过程&#xff0c;并提供一个简单的源码示例&#xf…

人工智能的未来

引言 人工智能的未来发展将是科技与人类社会深度融合的过程。随着技术的不断进步&#xff0c;AI将在全球经济、文化、政治及道德伦理等领域产生深远影响。本文将探讨人工智能在未来可能的技术进步、应用领域、社会影响、伦理挑战&#xff0c;以及对全球未来的展望。 一、技术前…

数据结构之——二叉树

一、二叉树的基本概念 二叉树是数据结构中的重要概念&#xff0c;每个节点最多有两个子树&#xff0c;分别为左子树和右子树。这种结构具有明确的层次性和特定的性质。 二叉树有五种基本形态&#xff1a; 空二叉树&#xff1a;没有任何节点。只有一个根结点的二叉树&#xff…

【HTTPS】深入解析 https

我的主页&#xff1a;2的n次方_ 1. 背景介绍 在使用 http 协议的时候是不安全的&#xff0c;可能会出现运营商劫持等安全问题&#xff0c;运营商通过劫持 http 流量&#xff0c;篡改返回的网页内容&#xff0c;例如广告业务&#xff0c;可能会通过 Referer 字段 来统计是…

kubernetes get pods的STATUS字段显示ImagePullBackOff 的解决办法

问题&#xff1a; [rootmaster ingress]# kubectl -n ingress-nginx get pods NAME READY STATUS RESTARTS AGE ingress-nginx-admission-create-mcrc6 0/1 ImagePullBackOff 0 37m ingress-…