【Linux】systemV消息队列和信号量

个人主页~


消息队列和信号量

一、消息队列

1、实现原理

操作系统在内核建立一个队列,通信的两个进程AB以数据块的形式将需要发送的数据pushback到队列中,数据块是一个结构体,其中有字段标识该数据块是谁发送的,所以我们只要让不同的进程看到同一个队列就可以了

2、系统调用接口

(一)创建获取一个消息队列

msgget函数的主要功能是创建一个新的消息队列或者获取一个已经存在的消息队列的标识符

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);

返回值:成功返回一个msgid,失败返回-1
key:ftok函数的返回值
msgflg:标识符

函数 msgflg 作用 示例
msgget IPC_CREAT 如果指定键对应的消息队列不存在,则创建一个新的消息队列;若已存在,则直接返回该消息队列的标识符 `msgget(key, IPC_CREAT
msgget IPC_EXCL 通常与 IPC_CREAT 一起使用,若同时设置这两个标志,当消息队列已经存在时,msgget 调用会失败并返回 -1,errno 会被设置为 EEXIST `msgget(key, IPC_CREAT
msgget 0600 消息队列的所有者具有读写权限,所属组和其他用户没有任何权限 msgget(key, 0600)
msgget 0660 消息队列的所有者和所属组具有读写权限,其他用户没有权限 msgget(key, 0660)
msgget 0666 消息队列的所有者、所属组和其他用户都具有读写权限 msgget(key, 0666)

(二)控制消息队列

msgctl用于控制消息队列的系统调用函数,通常用于对消息队列执行各种管理操作,如获取消息队列状态、设置消息队列属性以及删除消息队列等

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf);

返回值:返回0表示操作成功,返回-1表示操作失败
msgid:消息队列标识符,msgget函数返回值
cmdmsgctl函数的cmd参数常用命令如下:

命令 说明
IPC_STAT 获取消息队列的状态信息,将信息存储在buf指向的msqid_ds结构中。这些信息包括消息队列的权限、所有者信息、消息队列的大小、当前消息数量等
IPC_SET 根据buf指向的msqid_ds结构中的值,设置消息队列的属性。可以设置的属性包括消息队列的权限、队列的最大字节数等
IPC_RMID 删除指定的消息队列。调用该命令后,消息队列将被立即删除,所有排队的消息都会被丢弃,并且与该消息队列相关的资源也会被释放
MSG_INFO 获取与消息队列相关的系统资源使用信息,例如当前系统中消息队列的总数、系统允许的最大消息队列数等
MSG_STAT 该命令与IPC_STAT类似,但它返回的是一个指向struct msg_info结构的指针,该结构包含了更多关于消息队列的统计信息,如发送和接收消息的字节数等

buf:一个指向msgid_ds结构体的指针,用于存储或提供消息队列的相关信息,msqid_ds结构包含了消息队列的各种属性,如队列的权限、所有者信息、消息队列的大小等

(三)发送消息

msgsnd用于向消息队列发送消息的系统调用函数,它允许进程将一个消息添加到指定的消息队列中

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);

返回值:成功返回0,失败返回-1
msgid:消息队列标识符,msgget函数返回值
msgp:指向要发送的消息结构体的指针,该结构体的第一个成员必须是 long 类型,用于指定消息的类型,后续可以包含消息的数据部分
msgsz:消息数据部分的长度,即msgp所指向结构体中除第一个long类型成员之外的数据长度
msgflg:该位置为0就是不设置

函数 msgflg 作用 示例
msgsnd IPC_NOWAIT 非阻塞发送消息,当消息队列已满,无法立即发送消息时,如果设置了该标志,msgsnd 函数会立即返回 -1,errno 被设置为 EAGAIN;若未设置该标志,msgsnd 函数会阻塞,直到消息队列有空间可以发送消息 msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), IPC_NOWAIT)

(四)在消息队列中获取数据块

msgrcv用于从消息队列接收消息的系统调用函数,它允许进程从指定的消息队列中获取消息

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

返回值:成功返回实际收到的消息数据部分的字节数,不包括最前面的long

前两个参数与前面相同
msgsz:接收消息时用于存储消息数据部分的缓冲区的最大长度
msgtyp:如果等于0,那该函数只接收消息队列中的第一条消息,如果大于0,接收消息队列中消息类型为msgtyp的第一条消息,如果小于0,接收消息队列中消息类型小于等于msgtyp绝对值的最小类型的第一条消息
msgflg:该位置为0就是不设置

函数 msgflg 作用 示例
msgrcv IPC_NOWAIT 当消息队列中没有符合要求的消息时,如果设置了该标志,msgrcv 函数会立即返回 -1,errno 被设置为 ENOMSG;若未设置该标志,msgrcv 函数会阻塞,直到有符合要求的消息进入消息队列 msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgtype, IPC_NOWAIT)
msgrcv MSG_NOERROR 如果接收到的消息长度超过了指定的缓冲区大小,若设置了该标志,消息会被截断为缓冲区大小,多余部分会被丢弃,msgrcv 函数正常返回;若未设置该标志,msgrcv 函数会返回 -1,errno 被设置为 E2BIG msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgtype, MSG_NOERROR)

二、信号量

1、原理

信号量是一种用于实现进程间同步与互斥的机制,信号量本质上是一个整数变量,用于控制对共享资源的访问,它可以看作是一种特殊的计数器,其值表示当前可用的共享资源数量,信号量的值可以被多个进程或线程读取和修改,通过对信号量的操作,进程或线程可以协调对共享资源的访问

信号量的工作基于两个基本操作:P操作(wait操作)和V操作(signal操作)
P操作:当一个进程或线程需要访问共享资源时,它会执行 P 操作。P 操作会将信号量的值减 1,如果减 1 后信号量的值大于等于 0,表示当前有可用的资源,进程或线程可以继续访问;如果减 1 后信号量的值小于 0,表示没有可用的资源,进程或线程会被阻塞,直到有其他进程或线程释放资源

V 操作:当一个进程或线程使用完共享资源后,它会执行 V 操作,V 操作会将信号量的值加 1,如果加 1 后信号量的值小于等于 0,表示有其他进程或线程正在等待该资源,此时会唤醒一个等待的进程或线程

2、系统调用接口

(一)创建获取一个信号量

semget是用于创建或获取信号量集的系统调用函数

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

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

返回值:成功返回信号量标识符semid,失败返回-1
nsems:表示要创建或获取的信号量集中信号量的数量,如果是创建新的信号量集则必须大于 0,如果是获取已有的信号量集则可以为0
semflg:标志位,用于指定创建或获取信号量集的方式和权限

(二)控制信号量

semctl是用于控制信号量集的系统调用函数,它可以对信号量集进行多种操作,如初始化信号量的值、获取信号量的状态、删除信号量集等

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

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

返回值:取决于cmd的当前值,对于 GETVAL 命令,返回指定信号量的当前值,对于 IPC_STATIPC_SETIPC_RMID 等命令,返回 0 表示成功
semid:信号量标识符,semget函数返回
semnum:信号量集中信号量的编号,编号从 0 开始,如果 cmd 操作不需要针对特定的信号量(如删除整个信号量集),则可以忽略该参数,通常将其设为 0
cmd:要执行的命令,指定了对信号量集或特定信号量的操作类型

(三)PV操作

semop用于对信号量集执行操作的系统调用函数,它允许进程对一个或多个信号量进行原子性的 P和 V操作,从而实现进程间的同步与互斥

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

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

返回值:成功返回0,失败返回-1
sops:指向struct sembuf结构体数组的指针,该数组包含了要对信号量集执行的操作序列
nsopssops数组中元素的数量,即要执行的操作序列的长度

三、systemV IPC方法的比较

1、描述IPC资源的结构体

描述共享内存IPC资源结构体:

c 复制代码
struct shmid_kernel /* private to the kernel */
{	
	struct kern_ipc_perm	shm_perm;
	struct file *		    shm_file;
	int						id;
	unsigned long			shm_nattch;
	unsigned long			shm_segsz;
	time_t					shm_atim;
	time_t					shm_dtim;
	time_t					shm_ctim;
	pid_t					shm_cprid;
	pid_t					shm_lprid;
	struct user_struct		*mlock_user;
};

描述消息队列IPC资源结构体:

c 复制代码
struct msg_queue {
	struct kern_ipc_perm q_perm;
	time_t 				 q_stime;			/* last msgsnd time */
	time_t 				 q_rtime;			/* last msgrcv time */
	time_t				 q_ctime;			/* last change time */
	unsigned long 		 q_cbytes;			/* current number of bytes on queue */
	unsigned long		 q_qnum;			/* number of messages in queue */
	unsigned long		 q_qbytes;			/* max number of bytes on queue */
	pid_t				 q_lspid;			/* pid of last msgsnd */
	pid_t				 q_lrpid;			/* last receive pid */

	struct list_head 	 q_messages;
	struct list_head	 q_receivers;
	struct list_head	 q_senders;
};

描述信号量IPC资源结构体:

c 复制代码
struct sem_array {
	struct kern_ipc_perm	sem_perm;			/* permissions .. see ipc.h */
	time_t					sem_otime;			/* last semop time */
	time_t					sem_ctime;			/* last change time */
	struct sem				*sem_base;			/* ptr to first semaphore in array */
	struct sem_queue		*sem_pending;		/* pending operations to be processed */
	struct sem_queue		**sem_pending_last; /* last pending operation */
	struct sem_undo			*undo;				/* undo requests on this array */
	unsigned long			sem_nsems;			/* no. of semaphores in array */
};

他们有一个同样的特点就是第一个参数都是struct kern_ipc_perm类型的

c 复制代码
struct kern_ipc_perm
{
	spinlock_t		lock;
	int				deleted;
	key_t			key;
	uid_t			uid;
	gid_t			gid;
	uid_t			cuid;
	gid_t			cgid;
	mode_t			mode; 
	unsigned long	seq;
	void			*security;
};

2、操作系统对IPC资源进行管理

所有的IPC资源都有一个struct kern_ipc_perm结构,所以操作系统通过数组将这些struct kern_ipc_perm结构组织起来

ipc_ids是 Linux 内核中用于管理IPC资源的核心数据结构

c 复制代码
struct ipc_ids {
    int             in_use;//记录当前系统中正在使用的IPC资源的数量
    int             max_id;//表示系统中允许的最大IPC标识符值
    unsigned short  seq;//是一个序列号,用于生成唯一的IPC标识符
    unsigned short  seq_max;//是序列号的最大值
    struct          semaphore sem;//这是一个信号量,用于对IPC资源的并发访问进行同步控制    
    struct          ipc_id_ary nullentry;//一个空的ipc_id_ary结构
    struct          ipc_id_ary* entries;//指向ipc_id_ary结构体的指针
};
c 复制代码
struct ipc_id_ary {
    int    size;
    struct kern_ipc_perm *p[0];
};

这里的柔性数组p的作用就是维护当前操作系统中所有IPC资源,我们通过强制类型转换来通过这个数组里存的struct ipc_id_ary*找到具体的IPC对象,因为kern_ipc_perm是这三个结构体中的第一个成员,我们只要知道了一个kern_ipc_perm的地址,就相当于知道了某个具体IPC对象的起始地址,然后通过强制类型转换就可以访问到该IPC对象中的所有成员属性,这样就实现了对一个具体IPC对象的访问,如((struct shmid_kernel*)p[0])->q_stime,在kern_ipc_perm中有字段来标识该kern_ipc_perm是属于哪种IPC资源,操作系统就知道要将其强制转化成什么类型了,我们在用户层面上使用的:shmidmsqidsemid在内核上看就是p数组的下标

ipc_id_arry属于操作系统,不属于任何进程,数组下标是线性递增的,但不会因为IPC资源的释放而改变它的递增属性,即当前操作系统中最后一个IPC资源的下标是100,释放掉这个IPC资源,下一次再创建IPC资源的时候它的下标是101,而不是100,当递增到一定值的时候,会回到0


今日分享就到这里了~

相关推荐
小武~13 分钟前
嵌入式网络编程深度优化 --网络协议栈配置实战指南
linux·网络·网络协议
二进制星轨19 分钟前
在 Ubuntu 上快速配置 Node.js 环境(附问题说明)
linux·ubuntu·node.js
利刃大大34 分钟前
【高并发服务器:HTTP应用】十四、Util工具类的设计与实现
服务器·http·高并发·项目·cpp
kblj555534 分钟前
学习Linux——网络——网卡
linux·网络·学习
暖阳之下35 分钟前
学习周报二十
人工智能·深度学习·学习
zhanglianzhao36 分钟前
基于云服务器自建Rustdesk远程桌面——以京东云为例
运维·服务器·京东云
Physicist in Geophy.1 小时前
新版ubuntu中sac安装问题(缺少libncurses5)
linux·运维·ubuntu
可乐大数据1 小时前
Docker安装(基于云服务器ECS实例 CentOS 7.9系统)
服务器·docker·centos
菲兹园长1 小时前
微服务组件(E、L、N、O、G)
linux·服务器·gateway
朝新_1 小时前
【SpringBoot】玩转 Spring Boot 日志:级别划分、持久化、格式配置及 Lombok 简化使用
java·spring boot·笔记·后端·spring·javaee