Linux systemV消息队列和信号量详解
目录
- 一、消息队列
- 1、实现原理
- 2、系统调用接口
- (一)创建获取一个消息队列
- (二)控制消息队列
- (三)发送消息
- (四)在消息队列中获取数据块
- 二、信号量
- 1、原理
- 2、系统调用接口
- (一)创建获取一个信号量
- (二)控制信号量
- (三)PV操作
- 三、systemV IPC方法的比较
- 1、描述IPC资源的结构体
- 2、操作系统对IPC资源进行管理
- 总结
一、消息队列
1、实现原理
操作系统在内核建立一个队列,通信的两个进程AB以数据块的形式将需要发送的数据pushback到队列中,数据块是一个结构体,其中有字段标识该数据块是谁发送的,所以我们只要让不同的进程看到同一个队列就可以了
2、系统调用接口
(一)创建获取一个消息队列
msgget
函数的主要功能是创建一个www.devze.com新的消息队列或者获取一个已经存在的消息队列的标识符
#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 | 0666) |
msgget | IPC_EXCL | 通常与 IPC_CREAT 一起使用,若同时设置这两个标志,当消息队列已经存在时,msgget 调用会失败并返回 -1,errno 会被设置为 EEXIST | msgget(key, IPC_CREAT | IPC_EXCL | 0666) |
msgget | 0600 | 消息队列的所有者具有读写权限,所属组和其他用户没有任何权限 | msgget(key, 0600) |
msgget | 0660 | 消息队列的所有者和所属组具有读写权限,其他用户没有权限 | msgget(key, 0660) |
msgget | 0666 | 消息队列的所有者、所属组和其他用户都具有读写权限 | msgget(key, 0666) |
(二)控制消息队列
msgctl
用于控制消息队列的系统调用函数,通常用于对消息队列执行各种管理操作,如获取消息队列状态、设置消息队列属性以及删除消息队列等
#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
用于向消息队列发送消息的系统调用函数,它允许进程将一个消息添加到指定的消息队列中
#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
用于从消息队列接收消息的系统调用函数,它允许进程从指定的消息队列中获取消息
#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
是用于创建或获取信号量集的系统调用函数
#include <sys/types.h> #include <sys/ipc.h> #include <sy编程客栈s/sem.h> int semget(key_t key, int nsems, int semflg);
返回值:成功返回信号量标识符semid
,失败返回-1
nsems
:表示要创建或获取的信号量集中信号量的数量,如果是创建新的信号量集则必须大于 0,如果是获取已有的信号量集则可以为0semflg
:标志位,用于指定创建或获取信号量集的方式和权限
(二)控制信号量
semctl
是用于控制信号量集的系统调用函数,它可以对信号量集进行多种操作,如初始化信号量的值、获取信号量的状态、删除信号量集等
#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 操作不需要针对特定的信号量(如删除整个信号量集),则可以忽略该参数,通常将其设为 0cmd
:要执行的命令,指定了对信号量集或特定信号量的操作类型
(三)PV操作
semop
用于对信号量集执行操作的系统调用函数,它允许进程对一个或多个信号量进行原子性的 P和 V操作,从而实现进程间的同步与互斥
#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
资源结构体:
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
资源结构体:
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
资源结构体:
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
类型的
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
资源的核心数据结构
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结构体的指针 };
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
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
精彩评论