LV05-05-进程通信-05-消息队列
本文主要是进程通信——消息队列的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
参考方向 | 参考原文 |
--- | --- |
点击查看相关文件下载
--- | --- |
一、System V 消息队列
前边我们知道System V IPC
经过优化出现了POSIX IPC
,消息队列的话也就会有两种,这里所学习的是System V IPC
的消息队列。
1. 相关概念
消息队列是System V IPC
中的一种,它的底层是一个链队列,与共享内存的不同是,内核要保证消息队列的FIFO
性质,因此当有多个接收方进程接收消息队列中的消息的时候,不会产生冲突,由内核来协调他们的执行顺序。由于队列性质,队尾写,队头读,所以读写也不会存在冲突。
进程间通过IPC
消息队列通信时,进程产生的每条消息都被发送到一个IPC
消息队列中,这个消息一直存放在队列中,直到另外一个进程将其读走,故消息只适用于两个进程间通信。消息是由固定大小的首部,该首部是一个long int
型,用来存放消息类型和可变长度的正文组成。
2. 消息队列特点
消息队列有如下特点:
消息队列由消息队列
ID
来唯一标识。消息队列就是一个消息的列表,用户可以在消息队列中添加消息、读取消息等。
消息队列可以按照类型来发送或者接收消息。
3. 消息队列在内核的形式
4. 如何使用消息队列?
4.1 发送消息端
消息队列发送端的使用步骤一般如下:
(1)申请key
,用于不同的进程的话,可以使用ftok()
函数实现,若是用于具有血缘关系的进程,则也可以是IPC_PRIVATE
。
(2)打开或者创建消息队列,通过msgget()
函数实现。
(3)向消息队列发送消息,通过msgsnd
实现。
4.2 接收消息端
消息队列接收端的是哦用步骤一般如下:
(1)申请key
,用于不同的进程的话,可以使用ftok()
函数实现,若是用于具有血缘关系的进程,则也可以是IPC_PRIVATE
。保证与发送端相同,以使用同一个IPC
对象ID
。
(2)打开或者创建消息队列,通过msgget()
函数实现。
(3)从消息队列接收消息,通过 msgrcv()
函数实现。
(4)控制(删除)消息队列,通过msgctl()
函数实现。
二、消息队列基本操作
1. 打开或创建消息队列
1.1 msgget()
在linux
下可以使用man 2 msgget
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数打开或者创建一个消息队列,获取消息队列的IPC
对象ID
。
【函数参数】
key
:key_t
类型,通过ftok
创建的key
(键)值或者是IPC_PRIVATE
(这样的话只能用于具有血缘关系的进程通信)。shmflg
:int
类型,共享内存标志位,使用时需要与IPC
对象存取权限(如0666
)进行|
运算来确定读写权限。一般是需要创建共享内存时为IPC_CREAT|0666
,若不需要创建,直接打开时,使用0666
即可。
点击查看常用可取的值及含义
IPC_CREAT | 如果共享内存不存在,则创建一个共享内存,否则打开操作。 |
IPC_EXCL | 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。 |
如果单独使用
IPC_CREAT
,msgget()
函数要么返回一个已经存在的消息队列的标识符,要么返回一个新建的消息队列的标识符。如果将
IPC_CREAT
和IPC_EXCL
标志一起使用,msgget()
将返回一个新建的消息队列的标识符;如果该消息队列已存在,就会返回错误。
【返回值】int
类型,成功返回消息队列的标识符,也就是消息队列ID
,失败返回-1
,并设置errno
。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】none
1.2 使用实例
暂无。
2. 发送消息
2.1 msgsnd()
在linux
下可以使用man 2 msgsnd
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数向消息队列发送一个消息。
【函数参数】
msqid
:int
类型,消息队列ID
。msgp
:void *
类型,要发送的消息的缓冲区地址,一般是指向一个我们自定义的特定格式的结构体变量,这个结构体变量中存储的是消息的类型和消息的正文。
点击查看指向的结构体一般格式
1 | struct msgbuf |
msgsz
:size_t
类型,消息正文长度。msgflg
:int
类型,一些标志位,一般的话选择0
。
点击查看常用可取的值及含义
0 | 当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列 |
IPC_NOWAIT | 当消息队列已满的时候,msgsnd函数不等待立即返回 |
IPC_NOERROR | 若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程 |
如果队列中的可用空间不足,那么msgsnd()
的缺省行为是阻塞,直到空间可用为止。如果在msgflg
中指定了IPC_NOWAIT
,那么消息队列已满的时候调用将会失败并返回错误EAGAIN
。
【返回值】int
类型,成功返回0
,失败返回-1
,并设置errno
。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】
(1)消息结构必须有long
类型的msg_type
字段,表示消息的类型。
(2)消息长度不包括首类型 long
。
(3)msgsnd()
解除阻塞的条件三个条件:第一,不满足消息队列满或个数满两个条件,即消息队列中有容纳该消息的空间;第二,msqid
代表的消息队列被删除。第三,调用msgsnd
函数的进程被信号中断。
2.2 消息格式
我们发送消息之前,需要先了解一下消息的格式,有上边的消息发送函数以及消息在内核中的存储形式,我们知道,消息有类型和正文两个属性,于是我们就可以自定义一个结构体作为消息的格式,但是我们还需要知道消息正文内容的长度,这时候可以用宏定义。
1 | /* 消息格式结构体 */ |
2.3 使用实例
暂无。
3. 接收消息
3.1 msgrcv()
在linux
下可以使用man 2 msgrcv
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数从消息队列读取一个消息。
【函数参数】
msqid
:int
类型,消息队列ID
。msgp
:void *
类型,要发送的消息的缓冲区地址,一般是指向一个我们自定义的特定格式的结构体变量,这个结构体变量中存储的是消息的类型和消息的正文。msgsz
:size_t
类型,指定要接收的消息正文长度。msgtyp
:long
类型,指定接收的消息类型。
点击查看 msgtyp 取值范围的含义
msgtype = 0 | 收到的第一条消息,任意类型。 |
msgtype > 0 | 收到的第一条 msg_type类型的消息。 |
msgtype < 0 | 接收类型等于或者小于msgtype绝对值的第一个消息。 |
例如,如果msgtype=-4
,则只接受类型是1
、2
、3
、4
的消息
msgflg
:int
类型,一些标志位,一般的话选择0
。
点击查看常用可取的值及含义
0 | 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待 |
IPC_NOWAIT | 如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG |
MSG_EXCEPT | 与msgtype配合使用返回队列中第一个类型不为msgtype的消息 |
【返回值】ssize_t
类型,成功时返回收到的消息长度,失败返回-1
,并设置errno
。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】
(1)消息结构必须有long
类型的msg_type
字段,表示消息的类型。
(2)消息长度不包括首类型 long
。
3.2 消息格式
我们读取的消息格式自然要与发送的消息格式相同啦。
1 | /* 消息格式结构体 */ |
3.3 使用实例
暂无。
4. 消息控制
4.1 msgctl()
在linux
下可以使用man 2 msgctl
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于控制消息队列,例如,修改一个消息队列控制权限,删除一个消息队列。
【函数参数】
msqid
:int
类型,消息队列ID
。cmd
:int
类型,表示要执行的操作。
点击查看 cmd 常用值几含义
IPC_STAT | 把msqid_ds结构中的数据设置为消息队列的当前关联值,和*buf参数配合获取msqid消息队列信息 |
IPC_SET | 在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给的值 |
IPC_RMID | 删除消息队列 |
buf
:struct msqid_ds
类型结构体指针变量,指向存储消息队列相关信息的结构体,一般是设置为NULL
。
点击查看 struct msqid_ds 成员
1 | struct msqid_ds |
【返回值】int
类型,成功时,cmd
取IPC_STAT
, IPC_SET
或者 IPC_RMID
返回0
,失败返回-1
,并设置errno
。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】该函数的返回值还有其他的情况,只是我们一般是使用上边写出的三个参数,需要使用其他参数的话,可以查看帮助手册。
4.2 使用实例
暂无。
三、消息队列实例
1. msg_send.c
点击查看详情
1 | /* 头文件 */ |
2. msg_receive.c
点击查看详情
1 | /* 头文件 */ |
3. makefile
点击查看详情
1 | CC = gcc |
4. 编译与测试
在终端执行以下命令编译程序:
1 | make # 生成可执行文件 |
然后,终端会有以下信息显示:
1 | 执行 ./msg_send |