邮箱控制块
struct rt_mailbox
{
struct rt_ipc_object parent;
rt_uint32_t* msg_pool; /* 邮箱缓冲区的开始地址 */
rt_uint16_t size; /* 邮箱缓冲区的大小 */
rt_uint16_t entry; /* 邮箱中邮件的数目 */
rt_uint16_t in_offset, out_offset; /* 邮箱缓冲的进出指针 */
rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */
};
typedef struct rt_mailbox* rt_mailbox_t;
邮箱的管理方式
邮箱控制块是一个结构体,其中含有事件相关的重要参数,在邮箱的功能实现中起重要的作用。邮箱的相关接口如下图所示,对一个邮箱的操作包含:创建 / 初始化邮箱、发送邮件、接收邮件、删除 / 脱离邮箱。
创建和删除邮箱
创建邮箱
动态创建一个邮箱对象可以调用如下的函数接口:
rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag);
创建邮箱对象时会先从对象管理器中分配一个邮箱对象,然后给邮箱动态分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4 字节)与邮箱容量的乘积,接着初始化接收邮件数目和发送邮件在邮箱中的偏移量。下表描述了该函数的输入参数与返回值:
rt_mb_create() 的输入参数和返回值
参数 | 描述 |
---|---|
name | 邮箱名称 |
size | 邮箱容量 |
flag | 邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO |
返回 | —— |
RT_NULL | 创建失败 |
邮箱对象的句柄 | 创建成功 |
源码分析
/**
1353 * This function will create a mailbox object from system resource
1354 *
1355 * @param name the name of mailbox
1356 * @param size the size of mailbox
1357 * @param flag the flag of mailbox
1358 *
1359 * @return the created mailbox, RT_NULL on error happen
1360 */
1361 rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)
1362 {
1363 rt_mailbox_t mb;
1364
1365 RT_DEBUG_NOT_IN_INTERRUPT;
1366
1367 /* allocate object */
//分配一个邮箱对象:系统首先需要根据对象类型来获取对象信息,而后从内存堆中分配对象所对应大小的内存空间
1368 mb = (rt_mailbox_t)rt_object_allocate(RT_Object_Class_MailBox, name);
1369 if (mb == RT_NULL)
1370 return mb;
1371
1372 /* set parent */
//设置邮箱object的flag值
1373 mb->parent.parent.flag = flag;
1374
1375 /* initialize ipc object */
//初始化ipc object
1376 rt_ipc_object_init(&(mb->parent));
1377
1378 /* initialize mailbox */
//初始化邮箱控制块的相关参数
1379 mb->size = size;
1380 mb->msg_pool = (rt_ubase_t *)RT_KERNEL_MALLOC(mb->size * sizeof(rt_ubase_t));
1381 if (mb->msg_pool == RT_NULL)
1382 {
1383 /* delete mailbox object */
//如果内存申请失败就删除mailbox object
1384 rt_object_delete(&(mb->parent.parent));
1385
1386 return RT_NULL;
1387 }
1388 mb->entry = 0;
1389 mb->in_offset = 0;
1390 mb->out_offset = 0;
1391
1392 /* initialize an additional list of sender suspend thread */
//初始化一个发送线程的挂起等待队列
1393 rt_list_init(&(mb->suspend_sender_thread));
1394
1395 return mb;
1396 }
1397 RTM_EXPORT(rt_mb_create);
删除邮箱
当用 rt_mb_create() 创建的邮箱不再被使用时,应该删除它来释放相应的系统资源,一旦操作完成,邮箱将被永久性的删除。删除邮箱的函数接口如下:
rt_err_t rt_mb_delete (rt_mailbox_t mb);
删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是 - RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。下表描述了该函数的输入参数与返回值:
rt_mb_delete() 的输入参数和返回值
参数 | 描述 |
---|---|
mb | 邮箱对象的句柄 |
返回 | —— |
RT_EOK | 成功 |
源码分析
1399 /**
1400 * This function will delete a mailbox object and release the memory
1401 *
1402 * @param mb the mailbox object 1403 *
1404 * @return the error code
1405 */
1406 rt_err_t rt_mb_delete(rt_mailbox_t mb)
1407 {
1408 RT_DEBUG_NOT_IN_INTERRUPT;
1409
1410 /* parameter check */
//参数检查
1411 RT_ASSERT(mb != RT_NULL);1412 RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
1413 RT_ASSERT(rt_object_is_systemobject(&mb->parent.parent) == RT_FALSE);
1414
1415 /* resume all suspended thread */
//如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是 - RT_ERROR)
1416 rt_ipc_list_resume_all(&(mb->parent.suspend_thread));
1417
1418 /* also resume all mailbox private suspended thread */
1419 rt_ipc_list_resume_all(&(mb->suspend_sender_thread));
1420
1421 /* free mailbox pool */
//释放内存
1422 RT_KERNEL_FREE(mb->msg_pool);
1423
1424 /* delete mailbox object */
//删除mailbox object
1425 rt_object_delete(&(mb->parent.parent));
1426
1427 return RT_EOK;
1428 }
1429 RTM_EXPORT(rt_mb_delete);
初始化和脱离邮箱
初始化邮箱
初始化邮箱跟创建邮箱类似,只是初始化邮箱用于静态邮箱对象的初始化。与创建邮箱不同的是,静态邮箱对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中,其余的初始化工作与创建邮箱时相同。函数接口如下:
rt_err_t rt_mb_init(rt_mailbox_t mb,
const char* name,
void* msgpool,
rt_size_t size,
rt_uint8_t flag)
初始化邮箱时,该函数接口需要获得用户已经申请获得的邮箱对象控制块,缓冲区的指针,以及邮箱名称和邮箱容量(能够存储的邮件数)。下表描述了该函数的输入参数与返回值:
rt_mb_init() 的输入参数和返回值
参数 | 描述 |
---|---|
mb | 邮箱对象的句柄 |
name | 邮箱名称 |
msgpool | 缓冲区指针 |
size | 邮箱容量 |
flag | 邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO |
返回 | —— |
RT_EOK | 成功 |
这里的 size 参数指定的是邮箱的容量,即如果 msgpool 指向的缓冲区的字节数是 N,那么邮箱容量应该是 N/4。
源码分析
1282 /**
1283 * This function will initialize a mailbox and put it under control of resource
1284 * management.
1285 *
1286 * @param mb the mailbox object
1287 * @param name the name of mailbox
1288 * @param msgpool the begin address of buffer to save received mail
1289 * @param size the size of mailbox
1290 * @param flag the flag of mailbox
1291 *
1292 * @return the operation status, RT_EOK on successful
1293 */
1294 rt_err_t rt_mb_init(rt_mailbox_t mb,
1295 const char *name,
1296 void *msgpool,
1297 rt_size_t size,
1298 rt_uint8_t flag)
1299 {
1300 RT_ASSERT(mb != RT_NULL);
1301
1302 /* initialize object */
//初始化object
1303 rt_object_init(&(mb->parent.parent), RT_Object_Class_MailBox, name);
1304
1305 /* set parent flag */
//设置邮箱object的flag值
1306 mb->parent.parent.flag = flag;
1307
1308 /* initialize ipc object */
//初始化ipc object
1309 rt_ipc_object_init(&(mb->parent));
1310
1311 /* initialize mailbox */
//初始化邮箱控制块的相关参数
1312 mb->msg_pool = (rt_ubase_t *)msgpool;
1313 mb->size = size;
1314 mb->entry = 0;
1315 mb->in_offset = 0;
1316 mb->out_offset = 0;
1317
1318 /* initialize an additional list of sender suspend thread */
//初始化一个发送线程的挂起等待队列
1319 rt_list_init(&(mb->suspend_sender_thread));
1320
1321 return RT_EOK;
1322 }
1323 RTM_EXPORT(rt_mb_init);
脱离邮箱
脱离邮箱将把静态初始化的邮箱对象从内核对象管理器中脱离。脱离邮箱使用下面的接口:
rt_err_t rt_mb_detach(rt_mailbox_t mb);
使用该函数接口后,内核先唤醒所有挂在该邮箱上的线程(线程获得返回值是 - RT_ERROR),然后将该邮箱对象从内核对象管理器中脱离。下表描述了该函数的输入参数与返回值:
rt_mb_detach() 的输入参数和返回值
参数 | 描述 |
---|---|
mb | 邮箱对象的句柄 |
返回 | —— |
RT_EOK | 成功 |
源码分析
/**
1326 * This function will detach a mailbox from resource management
1327 *
1328 * @param mb the mailbox object
1329 *
1330 * @return the operation status, RT_EOK on successful
1331 */
1332 rt_err_t rt_mb_detach(rt_mailbox_t mb)
1333 {
1334 /* parameter check */
//参数检查
1335 RT_ASSERT(mb != RT_NULL);
1336 RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
1337 RT_ASSERT(rt_object_is_systemobject(&mb->parent.parent));
1338
1339 /* resume all suspended thread */
//如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是 - RT_ERROR)
1340 rt_ipc_list_resume_all(&(mb->parent.suspend_thread));
1341 /* also resume all mailbox private suspended thread */
1342 rt_ipc_list_resume_all(&(mb->suspend_sender_thread));
1343
1344 /* detach mailbox object */
// 脱离mailbox object
1345 rt_object_detach(&(mb->parent.parent));
1346
1347 return RT_EOK;
1348 }
1349 RTM_EXPORT(rt_mb_detach);
发送邮件
线程或者中断服务程序可以通过邮箱给其他线程发送邮件,发送邮件函数接口如下:
rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value);
发送的邮件可以是 32 位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到 -RT_EFULL 的返回值。下表描述了该函数的输入参数与返回值:
rt_mb_send() 的输入参数和返回值
参数 | 描述 |
---|---|
mb | 邮箱对象的句柄 |
value | 邮件内容 |
返回 | —— |
RT_EOK | 发送成功 |
-RT_EFULL | 邮箱已经满了 |
源码分析
1564 /**
1565 * This function will send a mail to mailbox object, if there are threads
1566 * suspended on mailbox object, it will be waked up. This function will return
1567 * immediately, if you want blocking send, use rt_mb_send_wait instead.
1568 *
1569 * @param mb the mailbox object
1570 * @param value the mail
1571 *
1572 * @return the error code
1573 */
1574 rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value)
1575 {
1576 return rt_mb_send_wait(mb, value, 0); // 调用rt_mb_send_wait
1577 }
1578 RTM_EXPORT(rt_mb_send);
等待方式发送邮件
用户也可以通过如下的函数接口向指定邮箱发送邮件:
rt_err_t rt_mb_send_wait (rt_mailbox_t mb,
rt_uint32_t value,
rt_int32_t timeout);
rt_mb_send_wait() 与 rt_mb_send() 的区别在于有等待时间,如果邮箱已经满了,那么发送线程将根据设定的 timeout 参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。下表描述了该函数的输入参数与返回值:
rt_mb_send_wait() 的输入参数和返回值
参数 | 描述 |
---|---|
mb | 邮箱对象的句柄 |
value | 邮件内容 |
timeout | 超时时间 |
返回 | —— |
RT_EOK | 发送成功 |
-RT_ETIMEOUT | 超时 |
-RT_ERROR | 失败,返回错误 |
源码分析
1432 /**
1433 * This function will send a mail to mailbox object. If the mailbox is full,
1434 * current thread will be suspended until timeout.
1435 *
1436 * @param mb the mailbox object
1437 * @param value the mail
1438 * @param timeout the waiting time
1439 *
1440 * @return the error code
1441 */
1442 rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
1443 rt_ubase_t value,
1444 rt_int32_t timeout)
1445 {
1446 struct rt_thread *thread;
1447 register rt_ubase_t temp;
1448 rt_uint32_t tick_delta;
1449
1450 /* parameter check */
// 参数检查
1451 RT_ASSERT(mb != RT_NULL);
1452 RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
1453
1454 /* initialize delta tick */
// 定义一个delta ticky并初始化,后面计时使用
1455 tick_delta = 0;
1456 /* get current thread */
// 获取当前的task控制块
1457 thread = rt_thread_self();
1458
// 钩子函数
1459 RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mb->parent.parent)));
1460
1461 /* disable interrupt */
// 关闭中断,进入临界区
1462 temp = rt_hw_interrupt_disable();
1463
1464 /* for non-blocking call */
1465 if (mb->entry == mb->size && timeout == 0)
1466
1467 rt_hw_interrupt_enable(temp);
1468
1469 return -RT_EFULL;
1470 }
1471
1472 /* mailbox is full */
//当mailbox为满时
1473 while (mb->entry == mb->size)
1474 {
1475 /* reset error number in thread */
//设置task控制块的error值
1476 thread->error = RT_EOK;
1477
1478 /* no waiting, return timeout */
//如果阻塞等待时间为0,则打开中断退出临界区,返回错误值
1479 if (timeout == 0)
1480 {
1481 /* enable interrupt */
1482 rt_hw_interrupt_enable(temp);
1483
1484 return -RT_EFULL;
1485 }
1486
1487 RT_DEBUG_IN_THREAD_CONTEXT;
1488 /* suspend current thread */
// 如果阻塞时间不为0,挂起当前进程,添加到mb->suspend_sender_thread发送线程的挂起等待队列
1489 rt_ipc_list_suspend(&(mb->suspend_sender_thread),
1490 thread,
1491 mb->parent.parent.flag);
1492
1493 /* has waiting time, start thread timer */
//当队列满时,延时等待时间不为0
1494 if (timeout > 0)
1495 {
1496 /* get the start tick of timer */
//获取当前时间
1497 tick_delta = rt_tick_get();
1498
1499 RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_send_wait: start timer of thread:%s\n",
1500 thread->name));
1501
1502 /* reset the timeout of thread timer and start it */
//重置task计时器的超时并启动它
1503 rt_timer_control(&(thread->thread_timer),
1504 RT_TIMER_CTRL_SET_TIME,
1505 &timeout);
1506 rt_timer_start(&(thread->thread_timer));
1507 }
1508
1509 /* enable interrupt */
//打开中断,退出临界区
1510 rt_hw_interrupt_enable(temp);
1511
1512 /* re-schedule */
//重启调度器
1513 rt_schedule();
1514
1515 /* resume from suspend state */
//从挂起状态恢复
1516 if (thread->error != RT_EOK)
1517 {
1518 /* return error */
1519 return thread->error;
1520 }
1521
1522 /* disable interrupt */
1523 temp = rt_hw_interrupt_disable();
1524
1525 /* if it's not waiting forever and then re-calculate timeout tick */
//如果不是一直等待则更新等待时间
1526 if (timeout > 0)
1527 {
1528 tick_delta = rt_tick_get() - tick_delta;
1529 timeout -= tick_delta;
1530 if (timeout < 0)
1531 timeout = 0;
1532 }
1533 }
1534
1535 /* set ptr */
1536 mb->msg_pool[mb->in_offset] = value;
1537 /* increase input offset */
1538 ++ mb->in_offset;
1539 if (mb->in_offset >= mb->size)
1540 mb->in_offset = 0;
1541 /* increase message entry */
1542 mb->entry ++;
1543
1544 /* resume suspended thread */
//恢复挂起的task
1545 if (!rt_list_isempty(&mb->parent.suspend_thread))
1546 {
1547 rt_ipc_list_resume(&(mb->parent.suspend_thread));
1548
1549 /* enable interrupt */
//打开中断,退出临界区
1550 rt_hw_interrupt_enable(temp);
1551
//重启调度器
1552 rt_schedule();
1553
1554 return RT_EOK;
1555 }
1556
1557 /* enable interrupt */
//打开中断,退出临界区
1558 rt_hw_interrupt_enable(temp);
1559
1560 return RT_EOK;
1561 }
1562 RTM_EXPORT(rt_mb_send_wait);
接收邮件
只有当接收者接收的邮箱中有邮件时,接收者才能立即取到邮件并返回 RT_EOK 的返回值,否则接收线程会根据超时时间设置,或挂起在邮箱的等待线程队列上,或直接返回。接收邮件函数接口如下:
rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);
接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及最多能够等待的超时时间。如果接收时设定了超时,当指定的时间内依然未收到邮件时,将返回 - RT_ETIMEOUT。下表描述了该函数的输入参数与返回值:
rt_mb_recv() 的输入参数和返回值
参数 | 描述 |
---|---|
mb | 邮箱对象的句柄 |
value | 邮件内容 |
timeout | 超时时间 |
返回 | —— |
RT_EOK | 发送成功 |
-RT_ETIMEOUT | 超时 |
-RT_ERROR | 失败,返回错误 |
源码分析
1580 /**
1581 * This function will receive a mail from mailbox object, if there is no mail
1582 * in mailbox object, the thread shall wait for a specified time.
1583 *
1584 * @param mb the mailbox object
1585 * @param value the received mail will be saved in
1586 * @param timeout the waiting time
1587 *
1588 * @return the error code
1589 */
1590 rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)
1591 {
1592 struct rt_thread *thread;
1593 register rt_ubase_t temp;
1594 rt_uint32_t tick_delta;
1595
1596 /* parameter check */
// 参数检查
1597 RT_ASSERT(mb != RT_NULL);
1598 RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
1599
1600 /* initialize delta tick */
1601 tick_delta = 0;
// 定义一个delta tick并初始化,后面计时使用
1602 /* get current thread */
// 获取当前的task控制块
1603 thread = rt_thread_self();
1604
1605 RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mb->parent.parent)));
1606
1607 /* disable interrupt */
// 关闭中断,进入临界区
1608 temp = rt_hw_interrupt_disable();
1609
1610 /* for non-blocking call */
1611 if (mb->entry == 0 && timeout == 0)
1612 {
1613 rt_hw_interrupt_enable(temp);
1614
1615 return -RT_ETIMEOUT;
1616 }
1617
1618 /* mailbox is empty */
//当mailbox为空时
1619 while (mb->entry == 0)
1620 {
1621 /* reset error number in thread */
//设置task控制块的error值
1622 thread->error = RT_EOK;
1623
1624 /* no waiting, return timeout */
//如果阻塞等待时间为0,则打开中断退出临界区,返回错误值
1625 if (timeout == 0)
1626 {
1627 /* enable interrupt */
1628 rt_hw_interrupt_enable(temp);
1629
1630 thread->error = -RT_ETIMEOUT;
1631
1632 return -RT_ETIMEOUT;
1633 }
1634
1635 RT_DEBUG_IN_THREAD_CONTEXT;
1636 /* suspend current thread */
// 如果阻塞时间不为0,挂起当前进程,添加到mb->suspend_sender_thread发送线程的挂起等待队列
1637 rt_ipc_list_suspend(&(mb->parent.suspend_thread),
1638 thread,
1639 mb->parent.parent.flag);
1640
1641 /* has waiting time, start thread timer */
//当队列满时,延时等待时间不为0
1642 if (timeout > 0)
1643 {
1644 /* get the start tick of timer */
//获取当前时间
1645 tick_delta = rt_tick_get();
1646
1647 RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_recv: start timer of thread:%s\n",
1648 thread->name));
1649
1650 /* reset the timeout of thread timer and start it */
//重置task计时器的超时并启动它
1651 rt_timer_control(&(thread->thread_timer),
1652 RT_TIMER_CTRL_SET_TIME,
1653 &timeout);
1654 rt_timer_start(&(thread->thread_timer));
1655 }
1656
1657 /* enable interrupt */
//打开中断,退出临界区
1658 rt_hw_interrupt_enable(temp);
1659
1660 /* re-schedule */
//重启调度器
1661 rt_schedule();
1662
1663 /* resume from suspend state */
//从挂起状态恢复
1664 if (thread->error != RT_EOK)
1665 {
1666 /* return error */
1667 return thread->error;
1668 }
1669
1670 /* disable interrupt */
1671 temp = rt_hw_interrupt_disable();
1672
1673 /* if it's not waiting forever and then re-calculate timeout tick */
//如果不是一直等待则更新等待时间
1674 if (timeout > 0)
1675 {
1676 tick_delta = rt_tick_get() - tick_delta;
1677 timeout -= tick_delta;
1678 if (timeout < 0)
1679 timeout = 0;
1680 }
1681 }
1682
1683 /* fill ptr */
1684 *value = mb->msg_pool[mb->out_offset];
1685
1686 /* increase output offset */
1687 ++ mb->out_offset;
1688 if (mb->out_offset >= mb->size)
1689 mb->out_offset = 0;
1690 /* decrease message entry */
1691 mb->entry --;
1692
1693 /* resume suspended thread */
//恢复挂起的task
1694 if (!rt_list_isempty(&(mb->suspend_sender_thread)))
1695 {
1696 rt_ipc_list_resume(&(mb->suspend_sender_thread));
1697
1698 /* enable interrupt */
//打开中断,退出临界区
1699 rt_hw_interrupt_enable(temp);
1700
1701 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
1702
1703 rt_schedule();
//重启调度器
1704
1705 return RT_EOK;
1706 }
1707
1708 /* enable interrupt */
//打开中断,退出临界区
1709 rt_hw_interrupt_enable(temp);
1710
1711 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
1712
1713 return RT_EOK;
1714 }
1715 RTM_EXPORT(rt_mb_recv);
消息队列控制块
在 RT-Thread 中,消息队列控制块是操作系统用于管理消息队列的一个数据结构,由结构体 struct rt_messagequeue 表示。另外一种 C 表达方式 rt_mq_t,表示的是消息队列的句柄,在 C 语言中的实现是消息队列控制块的指针。消息队列控制块结构的详细定义请见以下代码:
struct rt_messagequeue
{
struct rt_ipc_object parent;
void* msg_pool; /* 指向存放消息的缓冲区的指针 */
rt_uint16_t msg_size; /* 每个消息的长度 */
rt_uint16_t max_msgs; /* 最大能够容纳的消息数 */
rt_uint16_t entry; /* 队列中已有的消息数 */
void* msg_queue_head; /* 消息链表头 */
void* msg_queue_tail; /* 消息链表尾 */
void* msg_queue_free; /* 空闲消息链表 */
rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */
};
typedef struct rt_messagequeue* rt_mq_t;
rt_messagequeue 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。
消息队列的管理方式
消息队列控制块是一个结构体,其中含有消息队列相关的重要参数,在消息队列的功能实现中起重要的作用。消息队列的相关接口如下图所示,对一个消息队列的操作包含:创建消息队列 - 发送消息 - 接收消息 - 删除消息队列。
创建和删除消息队列
创建消息队列
消息队列在使用前,应该被创建出来,或对已有的静态消息队列对象进行初始化,创建消息队列的函数接口如下所示:
rt_mq_t rt_mq_create(const char* name, rt_size_t msg_size,
rt_size_t max_msgs, rt_uint8_t flag);
创建消息队列时先从对象管理器中分配一个消息队列对象,然后给消息队列对象分配一块内存空间,组织成空闲消息链表,这块内存的大小 =[消息大小 + 消息头(用于链表连接)的大小]X 消息队列最大个数,接着再初始化消息队列,此时消息队列为空。下表描述了该函数的输入参数与返回值:
rt_mq_create() 的输入参数和返回值
参数 | 描述 |
---|---|
name | 消息队列的名称 |
msg_size | 消息队列中一条消息的最大长度,单位字节 |
max_msgs | 消息队列的最大个数 |
flag | 消息队列采用的等待方式,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO |
返回 | —— |
RT_EOK | 发送成功 |
消息队列对象的句柄 | 成功 |
RT_NULL | 失败 |
源码分析
1861 /**
1862 * This function will create a message queue object from system resource
1863 *
1864 * @param name the name of message queue
1865 * @param msg_size the size of message
1866 * @param max_msgs the maximum number of message in queue
1867 * @param flag the flag of message queue
1868 *
1869 * @return the created message queue, RT_NULL on error happen
1870 */
1871 rt_mq_t rt_mq_create(const char *name,
1872 rt_size_t msg_size,
1873 rt_size_t max_msgs,
1874 rt_uint8_t flag)
1875 {
1876 struct rt_messagequeue *mq;
1877 struct rt_mq_message *head;
1878 register rt_base_t temp;
1879
1880 RT_DEBUG_NOT_IN_INTERRUPT;
1881
1882 /* allocate object */
//分配一个邮箱对象:系统首先需要根据对象类型来获取对象信息,而后从内存堆中分配对象所对应大小的内存空间
1883 mq = (rt_mq_t)rt_object_allocate(RT_Object_Class_MessageQueue, name);
1884 if (mq == RT_NULL)
1885 return mq;
1886
1887 /* set parent */
//设置邮箱object的flag值
1888 mq->parent.parent.flag = flag;
1889
1890 /* initialize ipc object */
//初始化ipc object
1891 rt_ipc_object_init(&(mq->parent));
1892
1893 /* initialize message queue */
//初始化邮箱控制块的相关参数
1894
1895 /* get correct message size */
//初始化消息队列的相关参数
1896 mq->msg_size = RT_ALIGN(msg_size, RT_ALIGN_SIZE);
1897 mq->max_msgs = max_msgs;
1898
1899 /* allocate message pool */
//如果内存申请失败就删除mailbox object
1900 mq->msg_pool = RT_KERNEL_MALLOC((mq->msg_size + sizeof(struct rt_mq_message)) * mq->max_msgs);
1901 if (mq->msg_pool == RT_NULL)
1902 {
1903 rt_object_delete(&(mq->parent.parent));
1904
1905 return RT_NULL;
1906 }
1907
1908 /* initialize message list */
//初始化消息队列链表头和消息链表尾
1909 mq->msg_queue_head = RT_NULL;
1910 mq->msg_queue_tail = RT_NULL;
1911
1912 /* initialize message empty list */
//初始化空闲消息队列链表
1913 mq->msg_queue_free = RT_NULL;
1914 for (temp = 0; temp < mq->max_msgs; temp ++)
1915 {
1916 head = (struct rt_mq_message *)((rt_uint8_t *)mq->msg_pool +
1917 temp * (mq->msg_size + sizeof(struct rt_mq_message)));
1918 head->next = (struct rt_mq_message *)mq->msg_queue_free;
1919 mq->msg_queue_free = head;
1920 }
1921
1922 /* the initial entry is zero */
//初始化队列中已有的消息为0
1923 mq->entry = 0;
1924
1925 /* initialize an additional list of sender suspend thread */
//初始化一个发送线程的挂起等待队列
1926 rt_list_init(&(mq->suspend_sender_thread));
1927
1928 return mq;
1929 }
1930 RTM_EXPORT(rt_mq_create);
删除消息队列
当消息队列不再被使用时,应该删除它以释放系统资源,一旦操作完成,消息队列将被永久性地删除。删除消息队列的函数接口如下:
rt_err_t rt_mq_delete(rt_mq_t mq);
删除消息队列时,如果有线程被挂起在该消息队列等待队列上,则内核先唤醒挂起在该消息等待队列上的所有线程(线程返回值是 - RT_ERROR),然后再释放消息队列使用的内存,最后删除消息队列对象。下表描述了该函数的输入参数与返回值:
rt_mq_delete() 的输入参数和返回值
参数 | 描述 |
---|---|
mq | 消息队列对象的句柄 |
返回 | —— |
RT_EOK | 成功 |
源码分析
1932 /**
1933 * This function will delete a message queue object and release the memory
1934 *
1935 * @param mq the message queue object
1936 *
1937 * @return the error code
1938 */
1939 rt_err_t rt_mq_delete(rt_mq_t mq)
1940 {
1941 RT_DEBUG_NOT_IN_INTERRUPT;
1942
1943 /* parameter check */
//参数检查
1944 RT_ASSERT(mq != RT_NULL);
1945 RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
1946 RT_ASSERT(rt_object_is_systemobject(&mq->parent.parent) == RT_FALSE);
1947
1948 /* resume all suspended thread */
//如果有线程被挂起在该消息队列对象上,内核先唤醒挂起在该消息队列上的所有线程(线程返回值是 - RT_ERROR)
1949 rt_ipc_list_resume_all(&(mq->parent.suspend_thread));
1950 /* also resume all message queue private suspended thread */
1951 rt_ipc_list_resume_all(&(mq->suspend_sender_thread));
1952
1953 /* free message queue pool */
//释放内存
1954 RT_KERNEL_FREE(mq->msg_pool);
1955
1956 /* delete message queue object */
//删除queue object
1957 rt_object_delete(&(mq->parent.parent));
1958
1959 return RT_EOK;
1960 }
1961 RTM_EXPORT(rt_mq_delete);
初始化和脱离消息队列
初始化消息队列
初始化静态消息队列对象跟创建消息队列对象类似,只是静态消息队列对象的内存是在系统编译时由编译器分配的,一般放于读数据段或未初始化数据段中。在使用这类静态消息队列对象前,需要进行初始化。初始化消息队列对象的函数接口如下:
rt_err_t rt_mq_init(rt_mq_t mq, const char* name,
void *msgpool, rt_size_t msg_size,
rt_size_t pool_size, rt_uint8_t flag);
初始化消息队列时,该接口需要用户已经申请获得的消息队列对象的句柄(即指向消息队列对象控制块的指针)、消息队列名、消息缓冲区指针、消息大小以及消息队列缓冲区大小。如下图所示,消息队列初始化后所有消息都挂在空闲消息链表上,消息队列为空。下表描述了该函数的输入参数与返回值:
rt_mq_init() 的输入参数和返回值
参数 | 描述 |
---|---|
mq | 消息队列对象的句柄 |
name | 消息队列的名称 |
msgpool | 指向存放消息的缓冲区的指针 |
msg_size | 消息队列中一条消息的最大长度,单位字节 |
pool_size | 存放消息的缓冲区大小 |
flag | 消息队列采用的等待方式,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO |
返回 | —— |
RT_EOK | 成功 |
源码分析
1768 /**
1769 * This function will initialize a message queue and put it under control of
1770 * resource management.
1771 *
1772 * @param mq the message object
1773 * @param name the name of message queue
1774 * @param msgpool the beginning address of buffer to save messages
1775 * @param msg_size the maximum size of message
1776 * @param pool_size the size of buffer to save messages
1777 * @param flag the flag of message queue
1778 *
1779 * @return the operation status, RT_EOK on successful
1780 */
1781 rt_err_t rt_mq_init(rt_mq_t mq,
1782 const char *name,
1783 void *msgpool,
1784 rt_size_t msg_size,
1785 rt_size_t pool_size,
1786 rt_uint8_t flag)
1787 {
1788 struct rt_mq_message *head;
1789 register rt_base_t temp;
1790
1791 /* parameter check */
//参数检查
1792 RT_ASSERT(mq != RT_NULL);
1793
1794 /* initialize object */
//初始化object
1795 rt_object_init(&(mq->parent.parent), RT_Object_Class_MessageQueue, name);
1796
1797 /* set parent flag */
//设置信息object的flag值
1798 mq->parent.parent.flag = flag;
1799
1800 /* initialize ipc object */
//初始化ipc object
1801 rt_ipc_object_init(&(mq->parent));
1802
//初始化邮箱控制块的相关参数
1803 /* set message pool */
1804 mq->msg_pool = msgpool;
1805
1806 /* get correct message size */
1807 mq->msg_size = RT_ALIGN(msg_size, RT_ALIGN_SIZE);
1808 mq->max_msgs = pool_size / (mq->msg_size + sizeof(struct rt_mq_message));
1809
1810 /* initialize message list */
//初始化消息队列链表头和消息链表尾
1811 mq->msg_queue_head = RT_NULL;
1812 mq->msg_queue_tail = RT_NULL;
1813
1814 /* initialize message empty list */
//初始化空闲消息队列链表
1815 mq->msg_queue_free = RT_NULL;
1816 for (temp = 0; temp < mq->max_msgs; temp ++)
1817 {
1818 head = (struct rt_mq_message *)((rt_uint8_t *)mq->msg_pool +
1819 temp * (mq->msg_size + sizeof(struct rt_mq_message)));
1820 head->next = (struct rt_mq_message *)mq->msg_queue_free;
1821 mq->msg_queue_free = head;
1822 }
1823
1824 /* the initial entry is zero */
//初始化队列中已有的消息为0
1825 mq->entry = 0;
1826
1827 /* initialize an additional list of sender suspend thread */
//初始化一个发送线程的挂起等待队列
1828 rt_list_init(&(mq->suspend_sender_thread));
1829
1830 return RT_EOK;
1831 }
1832 RTM_EXPORT(rt_mq_init);
脱离消息队列
脱离消息队列将使消息队列对象被从内核对象管理器中脱离。脱离消息队列使用下面的接口:
rt_err_t rt_mq_detach(rt_mq_t mq);
使用该函数接口后,内核先唤醒所有挂在该消息等待队列对象上的线程(线程返回值是 -RT_ERROR),然后将该消息队列对象从内核对象管理器中脱离。下表描述了该函数的输入参数与返回值:
rt_mq_detach() 的输入参数和返回值
参数 | 描述 |
---|---|
mq | 消息队列对象的句柄 |
返回 | —— |
RT_EOK | 成功 |
源码分析
/**
1835 * This function will detach a message queue object from resource management
1836 *
1837 * @param mq the message queue object
1838 *
1839 * @return the operation status, RT_EOK on successful
1840 */
1841 rt_err_t rt_mq_detach(rt_mq_t mq)
1842 {
1843 /* parameter check */
//参数检查
1844 RT_ASSERT(mq != RT_NULL);
1845 RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
1846 RT_ASSERT(rt_object_is_systemobject(&mq->parent.parent));
1847
1848 /* resume all suspended thread */
//如果有线程被挂起在该消息队列对象上,内核先唤醒挂起在该消息队列上的所有线程(线程返回值是 - RT_ERROR)
1849 rt_ipc_list_resume_all(&mq->parent.suspend_thread);
1850 /* also resume all message queue private suspended thread */
1851 rt_ipc_list_resume_all(&(mq->suspend_sender_thread));
1852
1853 /* detach message queue object */
// 脱离queue object
1854 rt_object_detach(&(mq->parent.parent));
1855
1856 return RT_EOK;
1857 }
1858 RTM_EXPORT(rt_mq_detach);
发送消息
线程或者中断服务程序都可以给消息队列发送消息。当发送消息时,消息队列对象先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部。当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。发送消息的函数接口如下:
rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size);
发送消息时,发送者需指定发送的消息队列的对象句柄(即指向消息队列控制块的指针),并且指定发送的消息内容以及消息大小。如下图所示,在发送一个普通消息之后,空闲消息链表上的队首消息被转移到了消息队列尾。下表描述了该函数的输入参数与返回值:
rt_mq_send() 的输入参数和返回值
参数 | 描述 |
---|---|
mq | 消息队列对象的句柄 |
buffer | 消息内容 |
size | 消息大小 |
返回 | —— |
RT_EOK | 成功 |
-RT_EFULL | 消息队列已满 |
-RT_ERROR | 失败,表示发送的消息长度大于消息队列中消息的最大长度 |
源码分析
2128 /**
2129 * This function will send a message to message queue object, if there are
2130 * threads suspended on message queue object, it will be waked up.
2131 *
2132 * @param mq the message queue object
2133 * @param buffer the message
2134 * @param size the size of buffer
2135 *
2136 * @return the error code
2137 */
2138 rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size)
2139 {
2140 return rt_mq_send_wait(mq, buffer, size, 0); //// 调用rt_mq_send_wait
2141 }
2142 RTM_EXPORT(rt_mq_send);
等待方式发送消息
用户也可以通过如下的函数接口向指定的消息队列中发送消息:
rt_err_t rt_mq_send_wait(rt_mq_t mq,
const void *buffer,
rt_size_t size,
rt_int32_t timeout);
rt_mq_send_wait() 与 rt_mq_send() 的区别在于有等待时间,如果消息队列已经满了,那么发送线程将根据设定的 timeout 参数进行等待。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。下表描述了该函数的输入参数与返回值:
rt_mq_send_wait() 的输入参数和返回值
参数 | 描述 |
---|---|
mq | 消息队列对象的句柄 |
buffer | 消息内容 |
size | 消息大小 |
timeout | 超时时间 |
返回 | —— |
RT_EOK | 成功 |
-RT_EFULL | 消息队列已满 |
-RT_ERROR | 失败,表示发送的消息长度大于消息队列中消息的最大长度 |
源码分析
1964 /**
1965 * This function will send a message to message queue object. If the message queue is full,
1966 * current thread will be suspended until timeout.
1967 *
1968 * @param mq the message queue object
1969 * @param buffer the message
1970 * @param size the size of buffer
1971 * @param timeout the waiting time
1972 *
1973 * @return the error code
1974 */
1975 rt_err_t rt_mq_send_wait(rt_mq_t mq,
1976 const void *buffer,
1977 rt_size_t size,
1978 rt_int32_t timeout)
1979 {
1980 register rt_ubase_t temp;
1981 struct rt_mq_message *msg;
1982 rt_uint32_t tick_delta;
1983 struct rt_thread *thread;
1984
1985 /* parameter check */
// 参数检查
1986 RT_ASSERT(mq != RT_NULL);
1987 RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
1988 RT_ASSERT(buffer != RT_NULL);
1989 RT_ASSERT(size != 0);
1990
1991 /* greater than one message size */
// 如果发送消息的大小大于消息队列的大小则返回错误
1992 if (size > mq->msg_size)
1993 return -RT_ERROR;
1994
1995 /* initialize delta tick */
// 定义一个delta tick并初始化,后面计时使用
1996 tick_delta = 0;
1997 /* get current thread */
1998 thread = rt_thread_self();
1999
2000 RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mq->parent.parent)));
2001
2002 /* disable interrupt */
// 关闭中断,进入临界区
2003 temp = rt_hw_interrupt_disable();
2004
2005 /* get a free list, there must be an empty item */
2006 msg = (struct rt_mq_message *)mq->msg_queue_free;
2007 /* for non-blocking call */
2008 if (msg == RT_NULL && timeout == 0)
2009 {
2010 /* enable interrupt */
2011 rt_hw_interrupt_enable(temp);
2012
2013 return -RT_EFULL;
2014 }
2015
2016 /* message queue is full */
//当queue 为满时
2017 while ((msg = mq->msg_queue_free) == RT_NULL)
2018 {
2019 /* reset error number in thread */
//设置task控制块的error值
2020 thread->error = RT_EOK;
2021
2022 /* no waiting, return timeout */
//如果阻塞等待时间为0,则打开中断退出临界区,返回错误值
2023 if (timeout == 0)
2024 {
2025 /* enable interrupt */
2026 rt_hw_interrupt_enable(temp);
2027
2028 return -RT_EFULL;
2029 }
2030
2031 RT_DEBUG_IN_THREAD_CONTEXT;
2032 /* suspend current thread */
// 如果阻塞时间不为0,挂起当前进程,添加到mb->suspend_sender_thread发送线程的挂起等待队列
2033 rt_ipc_list_suspend(&(mq->suspend_sender_thread),
2034 thread,
2035 mq->parent.parent.flag);
2036
2037 /* has waiting time, start thread timer */
//当队列满时,等待时间不为0
2038 if (timeout > 0)
2039 {
2040 /* get the start tick of timer */
//获取当前时间
2041 tick_delta = rt_tick_get();
2042
2043 RT_DEBUG_LOG(RT_DEBUG_IPC, ("mq_send_wait: start timer of thread:%s\n",
2044 thread->name));
2045
2046 /* reset the timeout of thread timer and start it */
//重置task计时器的超时并启动它
2047 rt_timer_control(&(thread->thread_timer),
2048 RT_TIMER_CTRL_SET_TIME,
2049 &timeout);
2050 rt_timer_start(&(thread->thread_timer));
2051 }
2052
2053 /* enable interrupt */
//打开中断,退出临界区
2054 rt_hw_interrupt_enable(temp);
2055
2056 /* re-schedule */
//重启调度器
2057 rt_schedule();
2058
2059 /* resume from suspend state */
//从挂起状态恢复
2060 if (thread->error != RT_EOK)
2061 {
2062 /* return error */
2063 return thread->error;
2064 }
2065
2066 /* disable interrupt */
2067 temp = rt_hw_interrupt_disable();
2068
2069 /* if it's not waiting forever and then re-calculate timeout tick */
//如果不是一直等待则更新等待时间
2070 if (timeout > 0)
2071 {
2072 tick_delta = rt_tick_get() - tick_delta;
2073 timeout -= tick_delta;
2074 if (timeout < 0)
2075 timeout = 0;
2076 }
2077 }
2078
2079 /* move free list pointer */
2080 mq->msg_queue_free = msg->next;
2081
2082 /* enable interrupt */
2083 rt_hw_interrupt_enable(temp);
2084
2085 /* the msg is the new tailer of list, the next shall be NULL */
2086 msg->next = RT_NULL;
2087 /* copy buffer */
2088 rt_memcpy(msg + 1, buffer, size);
2089
2090 /* disable interrupt */
2091 temp = rt_hw_interrupt_disable();
2092 /* link msg to message queue */
2093 if (mq->msg_queue_tail != RT_NULL)
2094 {
2095 /* if the tail exists, */
2096 ((struct rt_mq_message *)mq->msg_queue_tail)->next = msg;
2097 }
2098
2099 /* set new tail */
2100 mq->msg_queue_tail = msg;
2101 /* if the head is empty, set head */
2102 if (mq->msg_queue_head == RT_NULL)
2103 mq->msg_queue_head = msg;
2104
2105 /* increase message entry */
2106 mq->entry ++;
2107
2108 /* resume suspended thread */
//恢复挂起的task
2109 if (!rt_list_isempty(&mq->parent.suspend_thread))
2110 {
2111 rt_ipc_list_resume(&(mq->parent.suspend_thread));
2112
2113 /* enable interrupt */
//打开中断,退出临界区
2114 rt_hw_interrupt_enable(temp);
2115
//重启调度器
2116 rt_schedule();
2117
2118 return RT_EOK;
2119 }
2120
2121 /* enable interrupt */
//打开中断,退出临界区
2122 rt_hw_interrupt_enable(temp);
2123
2124 return RT_EOK;
2125 }
2126 RTM_EXPORT(rt_mq_send_wait)
发送紧急消息
发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,从空闲消息链表上取下来的消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。发送紧急消息的函数接口如下:
rt_err_t rt_mq_urgent(rt_mq_t mq, void* buffer, rt_size_t size);
下表描述了该函数的输入参数与返回值:
rt_mq_urgent() 的输入参数和返回值
参数 | 描述 |
---|---|
mq | 消息队列对象的句柄 |
buffer | 消息内容 |
size | 消息大小 |
返回 | —— |
RT_EOK | 成功 |
-RT_EFULL | 消息队列已满 |
-RT_ERROR | 失败 |
源码分析
2144 /**
2145 * This function will send an urgent message to message queue object, which
2146 * means the message will be inserted to the head of message queue. If there
2147 * are threads suspended on message queue object, it will be waked up.
2148 *
2149 * @param mq the message queue object
2150 * @param buffer the message
2151 * @param size the size of buffer
2152 *
2153 * @return the error code
2154 */
2155 rt_err_t rt_mq_urgent(rt_mq_t mq, const void *buffer, rt_size_t size)
2156 {
2157 register rt_ubase_t temp;
2158 struct rt_mq_message *msg;
2159
2160 /* parameter check */
// 参数检查
2161 RT_ASSERT(mq != RT_NULL);
2162 RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
2163 RT_ASSERT(buffer != RT_NULL);
2164 RT_ASSERT(size != 0);
2165
2166 /* greater than one message size */
// 如果发送消息的大小大于消息队列的大小则返回错误
2167 if (size > mq->msg_size)
2168 return -RT_ERROR;
2169
2170 RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mq->parent.parent)));
2171
2172 /* disable interrupt */
// 关闭中断,进入临界区
2173 temp = rt_hw_interrupt_disable();
2174
2175 /* get a free list, there must be an empty item */
2176 msg = (struct rt_mq_message *)mq->msg_queue_free;
2177 /* message queue is full */
//当queue 为满时
2178 if (msg == RT_NULL)
2179 {
2180 /* enable interrupt */
2181 rt_hw_interrupt_enable(temp);
2182
2183 return -RT_EFULL;
2184 }
2185 /* move free list pointer */
2186 mq->msg_queue_free = msg->next;
2187
2188 /* enable interrupt */
2189 rt_hw_interrupt_enable(temp);
2190
2191 /* copy buffer */
2192 rt_memcpy(msg + 1, buffer, size);
2193
2194 /* disable interrupt */
2195 temp = rt_hw_interrupt_disable();
2196
2197 /* link msg to the beginning of message queue */
2198 msg->next = (struct rt_mq_message *)mq->msg_queue_head;
2199 mq->msg_queue_head = msg;
2200
2201 /* if there is no tail */
2202 if (mq->msg_queue_tail == RT_NULL)
2203 mq->msg_queue_tail = msg;
2204
2205 /* increase message entry */
2206 mq->entry ++;
2207
2208 /* resume suspended thread */
//恢复挂起的task
2209 if (!rt_list_isempty(&mq->parent.suspend_thread))
2210 {
2211 rt_ipc_list_resume(&(mq->parent.suspend_thread));
2212
2213 /* enable interrupt */
//打开中断,退出临界区
2214 rt_hw_interrupt_enable(temp);
2215
//重启调度器
2216 rt_schedule();
2217
2218 return RT_EOK;
2219 }
2220
2221 /* enable interrupt */
//打开中断,退出临界区
2222 rt_hw_interrupt_enable(temp);
2223
2224 return RT_EOK;
2225 }
2226 RTM_EXPORT(rt_mq_urgent);
接收消息
当消息队列中有消息时,接收者才能接收消息,否则接收者会根据超时时间设置,或挂起在消息队列的等待线程队列上,或直接返回。接收消息函数接口如下:
rt_err_t rt_mq_recv (rt_mq_t mq, void* buffer,
rt_size_t size, rt_int32_t timeout);
接收消息时,接收者需指定存储消息的消息队列对象句柄,并且指定一个内存缓冲区,接收到的消息内容将被复制到该缓冲区里。此外,还需指定未能及时取到消息时的超时时间。如下图所示,接收一个消息后消息队列上的队首消息被转移到了空闲消息链表的尾部。下表描述了该函数的输入参数与返回值:
rt_mq_recv() 的输入参数和返回值
参数 | 描述 |
---|---|
mq | 消息队列对象的句柄 |
buffer | 消息内容 |
size | 消息大小 |
timeout | 指定的超时时间 |
返回 | —— |
RT_EOK | 成功收到 |
-RT_ETIMEOUT | 超时 |
-RT_ERROR | 失败,返回错误 |
源码分析
2228 /**
2229 * This function will receive a message from message queue object, if there is
2230 * no message in message queue object, the thread shall wait for a specified
2231 * time.
2232 *
2233 * @param mq the message queue object
2234 * @param buffer the received message will be saved in
2235 * @param size the size of buffer
2236 * @param timeout the waiting time
2237 *
2238 * @return the error code
2239 */
2240 rt_err_t rt_mq_recv(rt_mq_t mq,
2241 void *buffer,
2242 rt_size_t size,
2243 rt_int32_t timeout)
2244 {
2245 struct rt_thread *thread;
2246 register rt_ubase_t temp;
2247 struct rt_mq_message *msg;
2248 rt_uint32_t tick_delta;
2249
2250 /* parameter check */
// 参数检查
2251 RT_ASSERT(mq != RT_NULL);
2252 RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
2253 RT_ASSERT(buffer != RT_NULL);
2254 RT_ASSERT(size != 0);
2255
2256 /* initialize delta tick */
// 定义一个delta tick并初始化,后面计时使用
2257 tick_delta = 0;
2258 /* get current thread */
2259 thread = rt_thread_self();
2260 RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mq->parent.parent)));
2261
2262 /* disable interrupt */
// 关闭中断,进入临界区
2263 temp = rt_hw_interrupt_disable();
2264
2265 /* for non-blocking call */
2266 if (mq->entry == 0 && timeout == 0)
2267 {
2268 rt_hw_interrupt_enable(temp);
2269
2270 return -RT_ETIMEOUT;
2271 }
2272
2273 /* message queue is empty */
//当queue 为空时
2274 while (mq->entry == 0)
2275 {
2276 RT_DEBUG_IN_THREAD_CONTEXT;
2277
2278 /* reset error number in thread */
2279 thread->error = RT_EOK;
//设置task控制块的error值
2280
2281 /* no waiting, return timeout */
//如果阻塞等待时间为0,则打开中断退出临界区,返回错误值
2282 if (timeout == 0)
2283 {
2284 /* enable interrupt */
2285 rt_hw_interrupt_enable(temp);
2286
2287 thread->error = -RT_ETIMEOUT;
2288
2289 return -RT_ETIMEOUT;
2290 }
2291
2292 /* suspend current thread */
// 如果阻塞时间不为0,挂起当前进程,添加到mb->suspend_sender_thread发送线程的挂起等待队列
2293 rt_ipc_list_suspend(&(mq->parent.suspend_thread),
2294 thread,
2295 mq->parent.parent.flag);
2296
2297 /* has waiting time, start thread timer */
//当队列满时,等待时间不为0
2298 if (timeout > 0)
2299 {
2300 /* get the start tick of timer */
//获取当前时间
2301 tick_delta = rt_tick_get();
2302
2303 RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\n",
2304 thread->name));
2305
2306 /* reset the timeout of thread timer and start it */
//重置task计时器的超时并启动它
2307 rt_timer_control(&(thread->thread_timer),
2308 RT_TIMER_CTRL_SET_TIME,
2309 &timeout);
2310 rt_timer_start(&(thread->thread_timer));
2311 }
2312
2313 /* enable interrupt */
//打开中断,退出临界区
2314 rt_hw_interrupt_enable(temp);
2315
2316 /* re-schedule */
//重启调度器
2317 rt_schedule();
2318
2319 /* recv message */
//从挂起状态恢复
2320 if (thread->error != RT_EOK)
2321 {
2322 /* return error */
2323 return thread->error;
2324 }
2325
2326 /* disable interrupt */
2327 temp = rt_hw_interrupt_disable();
2328
2329 /* if it's not waiting forever and then re-calculate timeout tick */
//如果不是一直等待则更新等待时间
2330 if (timeout > 0)
2331 {
2332 tick_delta = rt_tick_get() - tick_delta;
2333 timeout -= tick_delta;
2334 if (timeout < 0)
2335 timeout = 0;
2336 }
2337 }
2338
2339 /* get message from queue */
//从队列中获取消息
2340 msg = (struct rt_mq_message *)mq->msg_queue_head;
2341
2342 /* move message queue head */
2343 mq->msg_queue_head = msg->next;
2344 /* reach queue tail, set to NULL */
2345 if (mq->msg_queue_tail == msg)
2346 mq->msg_queue_tail = RT_NULL;
2347
2348 /* decrease message entry */
2349 mq->entry --;
2350
2351 /* enable interrupt */
2352 rt_hw_interrupt_enable(temp);
2353
2354 /* copy message */
//复制消息队列的内容
2355 rt_memcpy(buffer, msg + 1, size > mq->msg_size ? mq->msg_size : size);
2356
2357 /* disable interrupt */
2358 temp = rt_hw_interrupt_disable();
2359 /* put message to free list */
//释放消息队列被接收的消息
2360 msg->next = (struct rt_mq_message *)mq->msg_queue_free;
2361 mq->msg_queue_free = msg;
2362
2363 /* resume suspended thread */
//恢复挂起的task
2364 if (!rt_list_isempty(&(mq->suspend_sender_thread)))
2365 {
2366 rt_ipc_list_resume(&(mq->suspend_sender_thread));
2367
2368 /* enable interrupt */
//打开中断,退出临界区
2369 rt_hw_interrupt_enable(temp);
2370
2371 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mq->parent.parent)));
2372
//重启调度器
2373 rt_schedule();
2374
2375 return RT_EOK;
2376 }
2377
2378 /* enable interrupt */
//打开中断,退出临界区
2379 rt_hw_interrupt_enable(temp);
2380
2381 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mq->parent.parent)));
2382
2383 return RT_EOK;
2384 }
2385 RTM_EXPORT(rt_mq_recv);