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

news2024/5/18 19:42:41

一、消息队列

基本概念

        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->的形式访问数据了

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

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

相关文章

Java中使用RediSearch进行高效数据检索

RediSearch是一款构建在Redis上的搜索引擎&#xff0c;它为Redis数据库提供了全文搜索、排序、过滤和聚合等高级查询功能。通过RediSearch&#xff0c;开发者能够在Redis中实现复杂的数据搜索需求&#xff0c;而无需依赖外部搜索引擎。本文将介绍如何在Java应用中集成并使用Red…

2024抖音直播带货-直播间拆解:抖店运营从入门到精通(56节课)

起号原理方式以及节点处理 类目的选择选品思路 付费流量投放原理 直播间进阶玩法 课程内容 直播间搭建标准自然起号(0-1)原理 方式 以及节点处理 老号重启(0-1)原理 方式 以及节点处理 账号在线人数稳定 原理 方式 以及节点处理 账号销售额放大 原理 方式 以及节点处理…

【Linux】namespace 隔离、cgroup 控制

文章目录 五、namespace 隔离dd -- 读取、转换并输出数据mkfs -- 格式化文件系统df -- 显示文件系统磁盘使用情况mount -- 加载文件系统到指定的加载点unshare -- 创建子进程&#xff0c;同时与父程序不共享namespace一个 demo 六、cgroup(Control Group) 相关命令pidstat -- 监…

Stable Diffusion AI绘画

我们今天来了解一下最近很火的SD模型 ✨在人工智能领域&#xff0c;生成模型一直是研究的热点之一。随着深度学习技术的飞速发展&#xff0c;一种名为Stable Diffusion的新型生成模型引起了广泛关注。Stable Diffusion是一种基于概率的生成模型&#xff0c;它可以学习数据的潜…

nginx变量自定义日志收集

内置变量 $remote_addr&#xff1b;存放了客户端的地址&#xff0c;注意是客户端的公网IP&#xff0c;也就是一家人访问一个网站&#xff0c;则会显示为路由器的公网IP。 $args&#xff1b;变量中存放了URL中的指令 [rootlocalhost conf.d]# cat pc.conf server {listen 80;se…

二.Django项目之电商购物商城 -- 校验用户输入密码是否合法

Django项目之电商购物商城 – 校验用户输入密码是否合法 需要开发文档和前端资料的可私聊 一. 创建用户逻辑操作 1. 创建用户app – users python manage.py startapp users2.注册app users.apps.UsersConfig,3. 创建视图 from django.shortcuts import render from djan…

mysql 指定根目录 迁移根目录

mysql 指定根目录 迁移根目录 1、问题描述2、问题分析3、解决方法3.1、初始化mysql前就手动指定mysql根目录为一个大的分区(支持动态扩容)&#xff0c;事前就根本上解决mysql根目录空间不够问题3.1.0、方法思路3.1.1、卸载mariadb3.1.2、下载Mysql安装包3.1.3、安装Mysql 8.353…

华为机考入门python3--(22)牛客22- 汽水瓶

分类&#xff1a;数字 知识点&#xff1a; 整除符号// 5//3 1 取余符号% 5%3 2 题目来自【牛客】 import sysdef calc_soda_bottles(n):if n 0: # 结束输入&#xff0c;不进行处理returnelse:# 循环进行汽水换算total_drunk 0 # 记录总共喝了多少瓶汽水while…

手把手教你安装 Garnet

什么是 Garnet Garnet 是微软使用 C# 编写的 Redis 客户端兼容缓存组件&#xff0c;你可以使用 Redis 客户端的连接方式来连接 Garnet 但是 Garnet 需要 .NET8 运行环境才可以使用&#xff0c;为了便捷性&#xff0c;可以考虑构建成 Docker 镜像 裸机部署 首先&#xff0c;…

初始数据类型

注释补充 在我们编写任何代码的时候&#xff0c;都有一个叫做注释的功能 在golang中有两种 单行注释 // 如下图所示 加入了注释的话&#xff0c;代码在执行的时候会自动忽视这段内容 //fmt.Println("天上") //fmt.Println("天下") //fmt.Println("唯…

Web端重叠路径可视化

近几年来&#xff0c;由于信息技术的发展&#xff0c;大数据成为了这个时代的代名词之一&#xff0c;“数据可视化”风靡一时。得益于HTML5提供的新标签“canvas”&#xff0c;Web端也能分“数据可视化”一杯羹。 随着越来越多的可视化方案和需求&#xff0c;需要解决问题也越来…

Java中接口的默认方法

为什么要使用默认方法 当我们把一个程序的接口写完后 用其他的类去实现&#xff0c;此时如果程序需要再添加一个抽象方法的时候我们只有两种选择 将抽象方法写在原本的接口中 但是这样写会导致其他所有改接口的实现类都需要实现这个抽象方法比较麻烦 写另一个接口 让需要的实…

Sqli-labs第一关到第四关

目录 一&#xff0c;了解PHP源代码 二&#xff0c;破解第一关 2.1在了解完源码之后&#xff0c;我们重点看一下 2.2破解这道题表中有几列 2.3查看表中哪一列有回显 2.4查询库&#xff0c;表&#xff0c;列信息 三&#xff0c;总结 前提&#xff1a; 之所以把1234关…

2024年5月5日 十二生肖 今日运势

小运播报&#xff1a;2024年5月5日&#xff0c;星期日&#xff0c;农历三月廿七 &#xff08;甲辰年己巳月己巳日&#xff09;&#xff0c;法定节假日。 红榜生肖&#xff1a;牛、猴、鸡 需要注意&#xff1a;鼠、虎、猪 喜神方位&#xff1a;东北方 财神方位&#xff1a;正…

eSIM IoT vs M2M vs Consumer

有任何关于GSMA\IOT\eSIM\RSP\业务应用场景相关的问题&#xff0c;欢迎W: xiangcunge59 一起讨论, 共同进步 (加的时候请注明: 来自CSDN-iot). 截至2023年5月&#xff0c;全球移动通信系统协会&#xff08;GSMA&#xff09;发布了三个关键的eSIM技术规范&#xff0c;这些规…

2000-2020年县域创业活跃度数据

2000-2020年县域创业活跃度数据 1、时间&#xff1a;2000-2020年 2、指标&#xff1a;地区名称、年份、行政区划代码、经度、纬度、所属城市、所属省份、年末总人口万人、户籍人口数万人、当年企业注册数目、县域创业活跃度1、县域创业活跃度2、县域创业活跃3 3、来源&#…

【前端项目——分页器】手写分页器实现(JS / React)

组件介绍 用了两种方式实现&#xff0c;注释详细~ 可能代码写的不够简洁&#xff0c;见谅&#x1f641; 1. 包含内容显示的分页器 网上看了很多实现&#xff0c;很多只有分页器部分&#xff0c;没和内容显示联动。 因此我增加了模拟content的显示&#xff0c;这里模拟了32条数…

JavaEE初阶Day 15:文件IO(1)

目录 Day 15&#xff1a;文件IO&#xff08;1&#xff09;IO文件1. 路径2. 文件的分类3. 使用Java针对文件系统进行操作3.1 属性3.2 构造方法3.3 方法 Day 15&#xff1a;文件IO&#xff08;1&#xff09; IO I&#xff1a;Input输入 O&#xff1a;Output输出 输入输出规则…

使用机器学习确定文本的编程语言

导入必要的库 norman Python 语句&#xff1a;import <span style"color:#000000"><span style"background-color:#fbedbb"><span style"color:#0000ff">import</span> pandas <span style"color:#0000ff&quo…

onedrive下載zip檔案有20G限制,如何解決

一般來說&#xff0c;OneDrive網頁版對文件下載大小的限制如下圖所示&#xff0c;更多資訊&#xff0c;請您參考這篇文章&#xff1a;OneDrive 和 SharePoint 中的限制 - Microsoft Support 因此我們推薦您使用OneDrive同步用戶端來同步到本地電腦&#xff0c;您也可以選擇只同…