RT_thread 线程间通信

邮箱控制块

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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容