开发者

Linux systemV消息队列和信号量详解

开发者 https://www.devze.com 2025-04-01 10:39 出处:网络 作者: s_little_monster_
目录一、消息队列1、实现原理2、系统调用接口(一)创建获取一个消息队列(二)控制消息队列(三)发送消息(四)在消息队列中获取数据块二、信号量1、原理2、系统调用接口(一)创建获取一个信号量(二)控制信号量
目录
  • 一、消息队列
    • 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作用示例
          msggetIPC_CREAT如果指定键对应的消息队列不存在,则创建一个新的消息队列;若已存在,则直接返回该消息队列的标识符msgget(key, IPC_CREAT | 0666)
          msggetIPC_EXCL通常与 IPC_CREAT 一起使用,若同时设置这两个标志,当消息队列已经存在时,msgget 调用会失败并返回 -1,errno 会被设置为 EEXISTmsgget(key, IPC_CREAT | IPC_EXCL | 0666)
          msgget0600消息队列的所有者具有读写权限,所属组和其他用户没有任何权限msgget(key, 0600)
          msgget0660消息队列的所有者和所属组具有读写权限,其他用户没有权限msgget(key, 0660)
          msgget0666消息队列的所有者、所属组和其他用户都具有读写权限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函数返回值
          • 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用于向消息队列发送消息的系统调用函数,它允许进程将一个消息添加到指定的消息队列中

          #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作用示例
          msgsndIPC_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作用示例
          msgrcvIPC_NOWAIT当消息队列中没有符合要求的消息时,如果设置了该标志,msgrcv 函数会立即返回 -1,errno 被设置为 ENOMSG;若未设置该标志,msgrcv 函数会阻塞,直到有符合要求的消息进入消息队列msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgtype, IPC_NOWAIT)
          msgrcvMSG_NOERROR如果接收到的消息长度超过了指定的缓冲区大小,若设置了该标志,消息会被截断为缓冲区大小,多余部分会被丢弃,msgrcv 函数正常返回;若未设置该标志,msgrcv 函数会返回 -1,errno 被设置为 E2BIGmsgrcv(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,如果是获取已有的信号量集则可以为0
          • semflg:标志位,用于指定创建或获取信号量集的方式和权限

          (二)控制信号量

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

          #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操作,从而实现进程间的同步与互斥

          #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资源结构体:

          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资源,操作系统就知道要将其强制转化成什么类型了,我们在用户层面上使用的:shmidmsqidsemid在内核上看就是p数组的下标

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

          总结

          以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

          0

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          关注公众号