
消息队列和信号量
一、消息队列
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
函数返回值
cmd
:msgctl
函数的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_STAT
、IPC_SET
和 IPC_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
结构体数组的指针,该数组包含了要对信号量集执行的操作序列
nsops
:sops
数组中元素的数量,即要执行的操作序列的长度
三、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
资源,操作系统就知道要将其强制转化成什么类型了,我们在用户层面上使用的:shmid
、msqid
、semid
在内核上看就是p
数组的下标
ipc_id_arry
属于操作系统,不属于任何进程,数组下标是线性递增的,但不会因为IPC
资源的释放而改变它的递增属性,即当前操作系统中最后一个IPC
资源的下标是100,释放掉这个IPC
资源,下一次再创建IPC
资源的时候它的下标是101,而不是100,当递增到一定值的时候,会回到0
今日分享就到这里了~
