• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 消息队列
2
3- [基本概念](#基本概念)
4- [运行机制](#运行机制)
5  - [队列控制块](#队列控制块)
6  - [队列运作原理](#队列运作原理)
7- [开发指导](#开发指导)
8  - [接口说明](#接口说明)
9  - [开发流程](#开发流程)
10- [编程实例](#编程实例)
11  - [实例描述](#实例描述)
12  - [编程示例](#编程示例)
13  - [结果验证](#结果验证)
14
15## 基本概念
16
17队列又称消息队列,是一种常用于任务间通信的数据结构。队列接收来自任务或中断的不固定长度消息,并根据不同的接口确定传递的消息是否存放在队列空间中。
18
19任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。
20
21可以通过调整读队列和写队列的超时时间来调整读写接口的阻塞模式,如果将读队列和写队列的超时时间设置为0,就不会挂起任务,接口会直接返回,这就是非阻塞模式。反之,如果将都队列和写队列的超时时间设置为大于0的时间,就会以阻塞模式运行。
22
23消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用,可以使用队列实现任务异步通信,队列具有如下特性:
24
25- 消息以先进先出的方式排队,支持异步读写。
26
27- 读队列和写队列都支持超时机制。
28
29- 每读取一条消息,就会将该消息节点设置为空闲。
30
31- 发送消息类型由通信双方约定,可以允许不同长度(不超过队列的消息节点大小)的消息。
32
33- 一个任务能够从任意一个消息队列接收和发送消息。
34
35- 多个任务能够从同一个消息队列接收和发送消息。
36
37- 创建队列时所需的队列空间,接口内系统自行动态申请内存。
38
39
40## 运行机制
41
42
43### 队列控制块
44
45```
46/**
47  * 队列控制块数据结构
48  */
49typedef struct {
50    UINT8 *queueHandle; /**< Pointer to a queue handle */
51    UINT16 queueState;  /**< Queue state */
52    UINT16 queueLen;    /**< Queue length */
53    UINT16 queueSize;   /**< Node size */
54    UINT32 queueID;     /**< queueID */
55    UINT16 queueHead;   /**< Node head */
56    UINT16 queueTail;   /**< Node tail */
57    UINT16 readWriteableCnt[OS_QUEUE_N_RW];   /**< Count of readable or writable resources, 0:readable, 1:writable */
58    LOS_DL_LIST readWriteList[OS_QUEUE_N_RW]; /**< the linked list to be read or written, 0:readlist, 1:writelist */
59    LOS_DL_LIST memList;                      /**< Pointer to the memory linked list */
60} LosQueueCB;
61```
62
63每个队列控制块中都含有队列状态,表示该队列的使用情况:
64
65- OS_QUEUE_UNUSED:队列未被使用。
66
67- OS_QUEUE_INUSED:队列被使用中。
68
69
70### 队列运作原理
71
72- 创建队列时,创建队列成功会返回队列ID。
73
74- 在队列控制块中维护着一个消息头节点位置Head和一个消息尾节点位置Tail,用于表示当前队列中消息的存储情况。Head表示队列中被占用的消息节点的起始位置。Tail表示被占用的消息节点的结束位置,也是空闲消息节点的起始位置。队列刚创建时,Head和Tail均指向队列起始位置。
75
76- 写队列时,根据readWriteableCnt[1]判断队列是否可以写入,不能对已满(readWriteableCnt[1]为0)队列进行写操作。写队列支持两种写入方式:向队列尾节点写入,也可以向队列头节点写入。尾节点写入时,根据Tail找到起始空闲消息节点作为数据写入对象,如果Tail已经指向队列尾部则采用回卷方式。头节点写入时,将Head的前一个节点作为数据写入对象,如果Head指向队列起始位置则采用回卷方式。
77
78- 读队列时,根据readWriteableCnt[0]判断队列是否有消息需要读取,对全部空闲(readWriteableCnt[0]为0)队列进行读操作会引起任务挂起。如果队列可以读取消息,则根据Head找到最先写入队列的消息节点进行读取。如果Head已经指向队列尾部则采用回卷方式。
79
80- 删除队列时,根据队列ID找到对应队列,把队列状态置为未使用,把队列控制块置为初始状态,并释放队列所占内存。
81
82**图1** 队列读写数据操作示意图
83
84![zh-cn_image_0000001132875772](figures/zh-cn_image_0000001132875772.png)
85
86上图对读写队列做了示意,图中只画了尾节点写入方式,没有画头节点写入,但是两者是类似的。
87
88
89## 开发指导
90
91
92### 接口说明
93
94| 功能分类 | 接口**名称** | 描述 |
95| -------- | -------- | -------- |
96| 创建/删除消息队列 | LOS_QueueCreate | 创建一个消息队列,由系统动态申请队列空间 |
97|  | LOS_QueueDelete |根据队列ID删除一个指定队列|
98| 读/写队列(不带拷贝) | LOS_QueueRead | 读取指定队列头节点中的数据(队列节点中的数据实际上是一个地址) |
99|  | LOS_QueueWrite |向指定队列尾节点中写入入参bufferAddr的值(即buffer的地址)|
100|  | LOS_QueueWriteHead |向指定队列头节点中写入入参bufferAddr的值(即buffer的地址)|
101| 读/写队列(带拷贝) | LOS_QueueReadCopy | 读取指定队列头节点中的数据 |
102|  | LOS_QueueWriteCopy |向指定队列尾节点中写入入参bufferAddr中保存的数据|
103|  | LOS_QueueWriteHeadCopy |向指定队列头节点中写入入参bufferAddr中保存的数据|
104| 获取队列信息 | LOS_QueueInfoGet | 获取指定队列的信息,包括队列ID、队列长度、消息节点大小、头节点、尾节点、可读节点数量、可写节点数量、等待读操作的任务、等待写操作的任务 |
105
106
107### 开发流程
108
1091. 用LOS_QueueCreate创建队列。创建成功后,可以得到队列ID。
110
1112. 通过LOS_QueueWrite或者LOS_QueueWriteCopy写队列。
112
1133. 通过LOS_QueueRead或者LOS_QueueReadCopy读队列。
114
1154. 通过LOS_QueueInfoGet获取队列信息。
116
1175. 通过LOS_QueueDelete删除队列。
118
119> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
120> - 系统支持的最大队列数是指:整个系统的队列资源总个数,而非用户能使用的个数。例如:系统软件定时器多占用一个队列资源,那么用户能使用的队列资源就会减少一个。
121>
122> - 创建队列时传入的队列名和flags暂时未使用,作为以后的预留参数。
123>
124> - 队列接口函数中的入参timeOut是相对时间。
125>
126> - LOS_QueueReadCopy和LOS_QueueWriteCopy及LOS_QueueWriteHeadCopy是一组接口,LOS_QueueRead和LOS_QueueWrite及LOS_QueueWriteHead是一组接口,每组接口需要配套使用。
127>
128> - 鉴于LOS_QueueWrite和LOS_QueueWriteHead和LOS_QueueRead这组接口实际操作的是数据地址,用户必须保证调用LOS_QueueRead获取到的指针所指向的内存区域在读队列期间没有被异常修改或释放,否则可能导致不可预知的后果。
129>
130> - LOS_QueueRead和LOS_QueueReadCopy接口的读取长度如果小于消息实际长度,消息将被截断。
131>
132> - 鉴于LOS_QueueWrite和LOS_QueueWriteHead和LOS_QueueRead这组接口实际操作的是数据地址,也就意味着实际写和读的消息长度仅仅是一个指针数据,因此用户使用这组接口之前,需确保创建队列时的消息节点大小,为一个指针的长度,避免不必要的浪费和读取失败。
133
134
135## 编程实例
136
137
138### 实例描述
139
140创建一个队列,两个任务。任务1调用写队列接口发送消息,任务2通过读队列接口接收消息。
141
1421. 通过LOS_TaskCreate创建任务1和任务2。
143
1442. 通过LOS_QueueCreate创建一个消息队列。
145
1463. 在任务1 SendEntry中发送消息。
147
1484. 在任务2 RecvEntry中接收消息。
149
1505. 通过LOS_QueueDelete删除队列。
151
152
153### 编程示例
154
155示例代码如下:
156
157```
158#include "los_task.h"
159#include "los_queue.h"
160static UINT32 g_queue;
161#define BUFFER_LEN 50
162
163VOID SendEntry(VOID)
164{
165    UINT32 ret = 0;
166    CHAR abuf[] = "test message";
167    UINT32 len = sizeof(abuf);
168
169    ret = LOS_QueueWriteCopy(g_queue, abuf, len, 0);
170    if(ret != LOS_OK) {
171        printf("send message failure, error: %x\n", ret);
172    }
173}
174
175VOID RecvEntry(VOID)
176{
177    UINT32 ret = 0;
178    CHAR readBuf[BUFFER_LEN] = {0};
179    UINT32 readLen = BUFFER_LEN;
180
181    //休眠1s
182    usleep(1000000);
183    ret = LOS_QueueReadCopy(g_queue, readBuf, &readLen, 0);
184    if(ret != LOS_OK) {
185        printf("recv message failure, error: %x\n", ret);
186    }
187
188    printf("recv message: %s\n", readBuf);
189
190    ret = LOS_QueueDelete(g_queue);
191    if(ret != LOS_OK) {
192        printf("delete the queue failure, error: %x\n", ret);
193    }
194
195    printf("delete the queue success!\n");
196}
197
198UINT32 ExampleQueue(VOID)
199{
200    printf("start queue example\n");
201    UINT32 ret = 0;
202    UINT32 task1, task2;
203    TSK_INIT_PARAM_S initParam = {0};
204
205    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)SendEntry;
206    initParam.usTaskPrio = 9;
207    initParam.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
208    initParam.pcName = "SendQueue";
209
210    LOS_TaskLock();
211    ret = LOS_TaskCreate(&task1, &initParam);
212    if(ret != LOS_OK) {
213        printf("create task1 failed, error: %x\n", ret);
214        return ret;
215    }
216
217    initParam.pcName = "RecvQueue";
218    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)RecvEntry;
219    ret = LOS_TaskCreate(&task2, &initParam);
220    if(ret != LOS_OK) {
221        printf("create task2 failed, error: %x\n", ret);
222        return ret;
223    }
224
225    ret = LOS_QueueCreate("queue", 5, &g_queue, 0, 50);
226    if(ret != LOS_OK) {
227        printf("create queue failure, error: %x\n", ret);
228    }
229
230    printf("create the queue success!\n");
231    LOS_TaskUnlock();
232    return ret;
233}
234```
235
236
237### 结果验证
238
239编译运行得到的结果为:
240
241```
242start test example
243create the queue success!
244recv message: test message
245delete the queue success!
246```
247