消息队列与信号量(基本概念及操作接口介绍)

一、消息队列

基本概念

        System V消息队列是Unix系统中一种进程间通信(IPC)机制,它允许进程互相发送和接收数据块(消息)

        操作系统可以在内部申请一个消息队列,可以让不同的进程向消息队列中发送数据块,为了让进程拿到其他进程发送的数据块,所以数据块一定会有一个标记标识是谁发送的数据,不同进程可以根据这个标记拿到自己想要的数据,这样进程间就可以通信了。

操作

这些接口与共享内存的接口都十分相似,因为他们都遵循System V标准

1. 获取消息队列: msgget函数

【解释】:

  • key是由用户传入,让内核识别消息队列唯一性的标识
  • msgflg表示创建消息队列的方式,可传入 IPC_CREATE   IPC_EXCL
  • 返回值:成功则返回消息队列的msgid,失败返回-1

2.设置消息队列:msgctl函数

 (包括获取消息队列的状态、设置消息队列的属性以及删除消息队列)

  • msqid:这是要操作的消息队列的标识符,由之前调用 msgget()成功返回。
  • cmd:指定要执行的操作类型,可以是以下几个值之一:
    • IPC_STAT:将消息队列的当前状态复制到buf指向的结构中。
    • IPC_SET:使用buf指向的结构中的某些成员设置消息队列的属性(如权限)。
    • IPC_RMID:删除该消息队列。
  • buf:这是一个指向struct msqid_ds结构的指针,用于存放或设置消息队列的属性。当执行IPC_STAT时,该结构会被填充;执行IPC_SET时,会根据结构中的有效成员来设置队列的属性。

ps.通过指令也可以删除消息队列

//查看当前所有的消息队列信息
ipcs -q
//删除消息队列
ipcrm -q msgid
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

int main() {
    int msqid;
    struct msqid_ds buf;

    // 假设msqid是你已经知道的有效消息队列ID
    msqid = /* your valid message queue ID */;

    // 获取消息队列状态
    if (msgctl(msqid, IPC_STAT, &buf) == -1) {
        perror("msgctl IPC_STAT");
        exit(EXIT_FAILURE);
    }

    printf("Message Queue Information:\n");
    printf("  msqid: %d\n", buf.msg_perm.__key);  // 注意: msg_perm.__key 在一些系统上可能不直接提供消息队列的key
    printf("  Mode: %o\n", buf.msg_perm.mode);
    printf("  Owner: %d\n", buf.msg_perm.uid);
    printf("  Group: %d\n", buf.msg_perm.gid);
    printf("  Bytes in queue: %lu\n", (unsigned long)buf.msg_qbytes);
    printf("  Number of messages: %ld\n", buf.msg_cbytes / sizeof(long));

    return 0;
}

3.向消息队列发送信息及收取信息:msgsnd函数 msgrcv函数

msgsnd

  • msqid:消息队列的标识符,由msgget()调用返回。
  • msgp:指向要发送消息内容的指针。
  • msgsz:消息的字节大小。
  • msgflg:标志位,通常用于指定消息发送的行为。如果设置了IPC_NOWAIT,则调用将立即返回,而不是等待队列有空闲空间。
  • 返回值:成功返回0,失败返回-1,并设置errno

使用举例:

        在这个例子中,我们定义了一个结构体my_msgbuf_t来组织消息内容,包含一个类型字段(mtype)和一个文本内容字段(mtext)。然后,我们使用msgsnd()函数将这个结构体实例发送到消息队列中。注意,消息的大小是以字节为单位计算的,但不包括类型字段的大小(通常是一个长整型,即long)。        

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

typedef struct my_msgbuf {
    long mtype;      // 消息类型
    char mtext[100]; // 消息内容
} my_msgbuf_t;

int main() {
    int msqid; // 假设这是有效的消息队列ID
    my_msgbuf_t message;
    int retval;

    // 初始化消息内容
    message.mtype = 1; // 消息类型
    strcpy(message.mtext, "Hello, this is a test message.");

    // 发送消息
    if ((retval = msgsnd(msqid, &message, sizeof(message) - sizeof(long), 0)) == -1) {
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }
    printf("Message sent successfully.\n");

    return 0;
}

magrcv

  • msqid:消息队列的标识符,由 msgget() 调用获得。
  • msgp:指向接收消息缓冲区的指针,消息的实际内容将被复制到这里。
  • msgsz:指定接收消息的最大字节数(不包括消息类型)。
  • msgtyp:指定要接收的消息类型。可以是特定的消息类型或者使用 MSG_ANY 来接收队列中的第一条消息(不论类型)。
  • msgflg:控制消息接收的选项,可以是以下标志的组合:
    • MSG_NOERROR:如果消息长度超过 msgsz,超出部分将被丢弃,不会产生错误。
    • IPC_NOWAIT:如果队列中没有符合条件的消息,立即返回,而不是等待。
  • 返回值:成功返回接收到的消息的实际字节数(包括消息类型在内的总字节数)。失败返回-1,并设置errno

使用举例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

typedef struct my_msgbuf {
    long mtype;      // 消息类型
    char mtext[100]; // 消息内容
} my_msgbuf_t;

int main() {
    int msqid; // 假设这是有效的消息队列ID
    my_msgbuf_t message;
    ssize_t bytes_received;

    // 接收消息
    if ((bytes_received = msgrcv(msqid, &message, sizeof(message.mtext), 1, 0)) == -1) { // 假设接收类型为1的消息
        perror("msgrcv");
        exit(EXIT_FAILURE);
    } else {
        printf("Received message: Type = %ld, Text = \"%s\", Bytes received = %zd\n",
               message.mtype, message.mtext, bytes_received);
    }

    return 0;
}

二、信号量

基本概念
  • 进程间通信的前提是两个进程看到同一份空间,这种多执行流都可以看到的资源称为共享资源
  • 像申请的共享内存,当它正在被一个进程访问时,另一个进程想要访问它就要先等待,这种被互斥保护起来的资源称为临界资源,访问临界资源的代码称为临界区
  • 程序员不能对资源进行保护,保护资源的本质就是保护临界区

        信号量(Semaphore)是一种用于操作系统中管理共享资源访问和实现进程间同步与互斥的机制。它是一个包含一个整数值的数据结构,该值表示系统中某种资源的可用数量,其本质就是一个计数器。

        我们访问临界资源的步骤一般是:申请信号量---->访问临界资源---->释放信号量

        例如,我们将共享内存看做是一份资源,及资源的可用数量为1,当一个进程想要访问该共享内存时,先申请信号量,此时S--,当另一个进程想要访问这个共享内存时,信号量为0,没有可用资源,此时就要等待,直到进程访问完成后释放信号量,等待的进程才可以访问共享内存。所以信号量是用来保护临界资源的。

        信号量要保护临界资源,那它一定要让多个进程可以看到他,及信号量自己本身就是一个共享资源,为了保护信号量自己的安全,信号量的操作(PV)一定是原子的,及要么做要么不做、要么成功要么失败,不会做一半被打断。

  • P操作(wait/signal_wait/lock):当一个进程想要访问一个受信号量保护的资源时,它会执行P操作。如果信号量的值大于0,P操作会将信号量减1,并允许进程继续执行。如果信号量为0,则进程会被阻塞,直到信号量变为非零值。

  • V操作(signal/signal_release/unlock):当一个进程完成对共享资源的访问后,会执行V操作,将信号量的值加1。如果此时有其他进程因等待该信号量而被阻塞,至少会有一个进程被唤醒。

操作

1.创建信号量 semget

  • key:一个键值,用于标识信号量集。可以使用ftok()函数生成一个唯一的键值,或者使用预定义的键值(如IPC_PRIVATE)来创建私有信号量集。

  • nsems:信号量集中信号量的数量。如果是创建新的信号量集,该参数指定了信号量的数量;如果是打开现有信号量集,则该参数应与现有信号量集的大小相匹配,否则可能导致错误。

  • semflg:控制信号量集创建和访问的标志,IPC_CREAT IPC_EXCL

  • 返回值:成功时,返回信号量集的标识符(一个非负整数),出错时,返回-1,并设置errno全局变量。

代码举例:

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

int main() {
    key_t key = ftok("pathname", 'a'); // 使用ftok生成一个键值,'pathname'应该替换为实际的路径名
    int semid;

    // 尝试获取或创建一个包含3个信号量的信号量集
    if ((semid = semget(key, 3, IPC_CREAT | 0666)) == -1) { // 使用0666设置读写权限
        perror("semget");
        exit(EXIT_FAILURE);
    }
    printf("Successfully obtained semaphore set with id: %d\n", semid);

    // 接下来可以使用semctl(), semop()等函数进一步操作信号量集...

    return 0;
}

2.设置信号量 semctl

  • semid:由semget()调用返回的信号量集的标识符。

  • semnum:信号量集中的信号量编号,从0开始。指定要操作的特定信号量。

  • cmd:指定要执行的操作类型,可以是以下几种:

    • SETVAL:设置信号量的值。
    • GETVAL:获取信号量的当前值。
    • GETALL:获取信号量集所有信号量的值。
    • SETALL:设置信号量集所有信号量的值。
    • IPC_RMID:删除信号量集。
  • arg(可变参数):传递给命令的具体参数,类型为union semun。这个联合体的结构依赖于cmd的值。例如,当cmdSETVAL时,arg应包含一个新的整数值。

  • 返回值:成功时,返回执行命令的结果,其类型和含义依赖于cmd,出错时,返回-1,并设置errno全局变量。

代码举例:

        在这个示例中,我们首先创建或打开一个信号量集,然后使用semctl()SETVAL命令初始化信号量的值,接着使用GETVAL命令获取并打印该信号量的当前值。

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

union semun {
    int val;                  /* Value for SETVAL */
    struct semid_ds *buf;     /* Buffer for IPC_STAT, IPC_SET */
    unsigned short *array;    /* Array for GETALL, SETALL */
    struct seminfo *__buf;    /* Buffer for IPC_INFO */
};

int main() {
    key_t key = ftok("somefile", 'a'); // 使用ftok生成一个键值
    int semid;
    
    // 获取或创建信号量集
    if ((semid = semget(key, 1, IPC_CREAT | 0666)) == -1) {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    // 初始化信号量为1
    union semun arg;
    arg.val = 1;
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("semctl SETVAL");
        exit(EXIT_FAILURE);
    }
    printf("Semaphore initialized.\n");

    // 获取并打印信号量的值
    int semval;
    arg.val = 0; // Initialize to avoid undefined behavior
    if ((semval = semctl(semid, 0, GETVAL, arg)) == -1) {
        perror("semctl GETVAL");
        exit(EXIT_FAILURE);
    }
    printf("Semaphore value: %d\n", semval);

    return 0;
}

3.对信号量集进行操作 semop

  • semid:信号量集的标识符,由semget()调用获得。

  • sops:指向sembuf结构体数组的指针,每个结构体定义了一个对信号量的操作。

  • nsopssembuf结构体数组的长度,即要执行的操作数量。

  • 返回值:成功时,返回0,出错时,返回-1,并设置errno全局变量。

sembuf结构体:

struct sembuf {
    short sem_num;    // 要操作的信号量编号,在信号量集中的位置
    short sem_op;     // 操作类型,正数为V操作(增加),负数为P操作(减少),0为查询但不改变值
    short sem_flg;    // 操作标志,通常使用SEM_UNDO来自动解除因进程异常终止导致的锁定
};

代码举例:

        在这个例子中,程序首先通过semget()获取或创建一个信号量集。然后,它使用semop()执行P操作来等待信号量(这可能会阻塞进程直到信号量的值大于0)。模拟了一些临界区操作后,程序执行V操作来释放信号量,允许其他等待该信号量的进程继续执行。注意,这里使用了SEM_UNDO标志来确保在进程异常终止时,系统能自动回滚信号量的操作,避免死锁。

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#define SEM_KEY 1234         // 用于semget的键值
#define SEM_COUNT 1          // 信号量集中信号量的数量
#define SEM_OP_WAIT -1       // P操作
#define SEM_OP_SIGNAL 1      // V操作

int main() {
    key_t key = (key_t)SEM_KEY;
    int semid;

    // 创建信号量集(如果不存在)
    if ((semid = semget(key, SEM_COUNT, IPC_CREAT | 0666)) == -1) {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    // 执行P操作(等待信号量)
    struct sembuf op_wait = {0, SEM_OP_WAIT, SEM_UNDO}; // 对第0号信号量执行P操作
    if (semop(semid, &op_wait, 1) == -1) {
        perror("semop wait");
        exit(EXIT_FAILURE);
    }
    printf("Process waiting...\n");

    // 模拟一些需要互斥访问的临界区操作
    sleep(5); // 示例中简单睡眠5秒代表临界区操作

    // 执行V操作(释放信号量)
    struct sembuf op_signal = {0, SEM_OP_SIGNAL, SEM_UNDO}; // 对第0号信号量执行V操作
    if (semop(semid, &op_signal, 1) == -1) {
        perror("semop signal");
        exit(EXIT_FAILURE);
    }
    printf("Process released semaphore.\n");

    return 0;
}

4. 指令

//查看所有信号量信息
ipcs -s

//删除信号量
ipcrm -s semid

三、OS如何管理共享内存、消息队列、信号量等资源的

        我们这几篇文章所讲的共享内存、消息队列、信号量等都是遵循System V标准的,我们发现他们存在shmid、semid等信息,操作的函数名及参数都是相似的。而描述共享内存、消息队列、信号量的结构体等也都是相似的,重要的是他们的结构体的第一个成员都是struct ipc_perm的结构体

         操作系统内部有一个结构体内部存在一个struct kern_ipc_perm的柔性指针数组,指向的是XXX_id_ds结构体中ipc_perm结构体,也就是XXX_id_ds结构体的第一个成员,相当于XXX_id_ds的地址,这样做意味着我们将所有的IPC结构都统一管理了,不论他是共享内存还是消息队列,而数组的下标就是我们shmid msgid semid

                在ipc_perm中,有一个mode变量,存放着创建时的类型,所以访问XXX_id_ds结构体中的其他成员也很简单,只要将指针强转成对应类型的指针,然后通过XXX_ipc_perm->的形式访问数据了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux Systemd基础教程

一、什么是systemd&#xff1f; systemd是Linux系统的一套基本构建模块。它提供了一个系统和服务管理器&#xff0c;作为PID 1运行并启动系统的其余部分。 systemd提供积极的并行化功能&#xff0c;使用套接字和D-Bus激活来启动服务&#xff0c;提供按需启动守护进程&#xf…

《自动机理论、语言和计算导论》阅读笔记:p352-P401

《自动机理论、语言和计算导论》学习第 12 天&#xff0c;p352-P401总结&#xff0c;总计 50 页。 一、技术总结 1.Turing Machine ™ 2.undecidability ​ a.Ld(the diagonalization language) 3.reduction p392, In general, if we have an algorithm to convert insta…

Git系列:config 配置

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

面试中算法(最大公约数)

高效求出两个整数的最大公约数&#xff0c;要尽量优化算法的性能。 def getDiv(a,b):mamax(a,b)mimin(a,b)#判断能被整除if ma%mi0:return mi#递归return getDiv(ma%mi,mi)if __name__ __main__:# print(getDiv(10, 25))print(getDiv(1000, 50))没错&#xff0c;这确实是辗转…

C++笔试强训day14

目录 1.乒乓球框 2.组队竞赛 3.删除相邻数字的最⼤分数 1.乒乓球框 链接 哈希表直接秒了&#xff1a; #include <iostream> #include <string> using namespace std; int main() {string s1, s2;while (cin >> s1 >> s2) { // 未知组数的输⼊int h…

新芯计划(1)时钟资源——MMCM与PLL

系列文章目录 1、同步设计——亚稳态 文章目录 系列文章目录前言一、时钟管理资源二、MMCM与PLLMMCM内部结构&#xff1a;PLL内部结构:区别 前言 本节围绕时钟资源展开&#xff0c;主要描述和比较MMCM和PLL&#xff0c;若内容有误&#xff0c;欢迎和感谢各位指正 参考视频&am…

IoTDB 入门教程 基础篇③——基于Linux系统快速安装启动和上手

文章目录 一、前文二、下载三、解压四、上传五、启动六、执行七、停止八、参考 一、前文 IoTDB入门教程——导读 二、下载 下载二进制可运行程序&#xff1a;https://dlcdn.apache.org/iotdb/1.3.1/apache-iotdb-1.3.1-all-bin.zip 历史版本下载&#xff1a;https://archive.…

Mysql中索引的概念

索引相关概念 基础概念&#xff1a; 在MySQL中&#xff0c;索引是一种数据结构&#xff0c;用于加快数据库查询的速度和性能。索引可以帮助MySQL快速定位和访问表中的特定数据&#xff0c;就像书籍的索引一样&#xff0c;通过存储指向数据行的指针&#xff0c;可以快速…

《老相册》读后感

外面在下着瓢泼大雨&#xff0c;豆粒大的雨点打在窗户上&#xff0c;发出啪啪的巨响。这样的雨天&#xff0c;是不适宜外出的&#xff0c;最惬意的方式就是一个人待在宿舍里&#xff0c;打开一本书&#xff0c;慢慢地看&#xff0c;静静地想&#xff0c;让所有的烦恼融化在这雨…

二叉树的迭代遍历 | LeetCode 144. 二叉树的前序遍历、LeetCode 94. 二叉树的中序遍历、LeetCode 145. 二叉树的后序遍历

二叉树的前序遍历&#xff08;迭代法&#xff09; 1、题目 题目链接&#xff1a;144. 二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3]示例 2&#x…

Docker Compose 部署若依前后端分离版

准备一台服务器 本次使用虚拟机&#xff0c;虚拟机系统 Ubuntu20.04&#xff0c;内存 4G&#xff0c;4核。 确保虚拟机能连接互联网。 Ubuntu20.04 安装 Docker 添加 Docker 的官方 GPG key&#xff1a; sudo apt-get update sudo apt-get install ca-certificates curl su…

1850H-The Third Letter

题目链接&#xff1a;The Third Letter 本道题目就是带权并查集的模板题&#xff0c;但又好久没学忘了&#xff0c;再复习一遍。。。 路径压缩函数模板&#xff1a; int root(int x){if(pre[x]!x){int troot(pre[x]);d[x]d[pre[x]];pre[x]t;}return pre[x]; } 之后就模拟一…

eNSP-浮动静态路由配置

ip route-static 192.168.1.0 24 192.168.3.2 preference 60 #设置路由 目标网络地址 和 下一跳地址 preference值越大 优先级越低 一、搭建拓扑结构 二、主机配置 pc1 pc2 三、配置路由器 1.AR1路由器配置 <Huawei>sys #进入系统视图 [Huawei]int g0/0/0 #进入接…

【DevOps】Jenkins 集成Docker

目录 1. 安装 Docker 和 Jenkins 2. 在 Jenkins 中安装 Docker 插件 3. 配置 Docker 连接 4. 创建 Jenkins Pipeline 5. 示例 Pipeline 脚本 6. 运行 Jenkins Job 7. 扩展功能 8、docker配置测试连接的时候报错处理 将 Docker 与 Jenkins 集成可以实现持续集成和持续交…

Java学习第05天-编程思维与编程能力

文章目录 综合应用案例&#xff1a;找素数数组元素的复制数字加密模拟双色球 综合应用 涉及的知识点&#xff1a; 变量、数组运算符&#xff1a;基本运算符、关系运算符、逻辑运算符流程控制&#xff1a;if、switch、for、while、死循环、循环嵌套跳转关键字&#xff1a;break、…

初识C语言——第十一天

操作符&#xff1a; 1. 算数操作符&#xff1a; - * / % 2. 移位操作符&#xff1a; >> &#xff08;右移&#xff09; << &#xff08;左移&#xff09; 移动的是二进制位 例如&#xff1a; int ba<<1; 3. 位操作符&#xff1a; & 按位与 | 按位…

数仓开发:DIM层数据处理

一、了解DIM层 这个就是数仓开发的分层架构 我们现在是在DIM层&#xff0c;从ods表中数据进行加工处理&#xff0c;导入到dwd层&#xff0c;但是记住我们依然是在DIM层&#xff0c;而非是上面的ODS和DWD层。 二、处理维度表数据 ①先确认hive的配置 -- 开启动态分区方案 -- …

Unity技术学习:RenderMesh、RenderMeshInstanced

叠甲&#xff1a;本人比较菜&#xff0c;如果哪里不对或者有认知不到的地方&#xff0c;欢迎锐评&#xff08;不玻璃心&#xff09;&#xff01; 导师留了个任务&#xff0c;渲染大量的、移动的物体。 当时找了几个解决方案&#xff1a; 静态批处理&#xff1a; 这东西只对静…

Springboot+Vue项目-基于Java+MySQL的校园二手书交易平台系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

Netty 网络编程深入学习【一】:ByteBuffer 源码解析

ByteBuffer源码阅读 ByteBuffer是一个用于处理字节数据的缓冲区类。它是Java NIO 包的一部分&#xff0c;提供了一种高效的方式来处理原始字节数据。 ByteBuffer 可以用来读取、写入、修改和操作字节数据&#xff0c;它是一种直接操作字节的方式&#xff0c;比起传统的 InputSt…