一、邮箱介绍
邮箱在rtthread操作系统中是一种常用的IPC通信方式, 邮箱可以在线程与线程之间,中断与线程之间进行消息的传递,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针, 即可以容纳一个处理函数指针).
以下是我对邮箱的一些简单见解
发送邮箱是将每个事件触发时暂不处理, 放在某个线程同一处理. 便于可以将事件触发函数和事件处理函数分开.
同时不同邮箱事件集中爆发时,能记住邮箱事件发生的前后顺序.
二、邮箱的运作机制
RT-Thread操作系统的邮箱对象由多个元素组成,当邮箱被创建时,它就被分配了邮箱控制块;邮箱名称;邮箱缓冲区起始地址;邮箱大小等。同时,每个邮箱对象中包含着多个邮件框,每个邮件框可以存放一封邮件,所有邮箱中的邮件框总数既是邮箱的大小,这个大小可在邮箱创建时指定。
三、邮箱控制块
在 RT-Thread 中,邮箱控制块是操作系统用于管理邮箱的一个数据结构,由结构体 struct rt_mailbox 表示。另外一种 C 表达方式 rt_mailbox_t,表示的是邮箱的句柄,在 C 语言中的实现是邮箱控制块的指针。邮箱控制块结构的详细定义请见以下代码:
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; /* 发送线程的挂起等待队列 */
};
以上是整个邮箱控制块
msg_pool, 消息池的首地址, 将每个邮件发送的value保存在消息池中, (消息池放着所有的油件)
size , 消息池的总大小, 一个消息4字节(32位),
entry, 消息池中邮件的个数
in_offset, out_offset, 进出指针同于管理邮件的发送和接收在环中保存和取出的顺序.
suspend_sender_thread, 发送邮件时因为消息池满了而挂起的线程.
四、邮箱创建
RT-Thread 中邮箱的创建其实就是邮箱控制块的创建. 并将创建的邮箱控制块进行初值的赋值.
//静态创建方式 需要外面创建好消息控制块 rt_mailbox_t rt_mailbox;
//需要外面创建好消息池 unsigned long msgpool[MSG_POOL_SIZE];
//#define MSG_POOL_SIZE 256;
rt_err_t rt_mb_init(rt_mailbox_t mb, //创建好的消息控制块 rt_mailbox
const char *name, //邮箱名字(IPC)使用 APP_MAILBOX
void *msgpool, //消息池首地址 msgpool
rt_size_t size, //消息池大小 MSG_POOL_SIZE
rt_uint8_t flag) //FIFO方式.
{
RT_ASSERT(mb != RT_NULL);
/* initialize object */
rt_object_init(&(mb->parent.parent), RT_Object_Class_MailBox, name);
/* set parent flag */
mb->parent.parent.flag = flag;
/* initialize ipc object 初始化OBJECT链表指针*/
rt_ipc_object_init(&(mb->parent));
/* initialize mailbox 将函数传过来的值赋值到邮箱控制块中*/
mb->msg_pool = (rt_ubase_t *)msgpool;
mb->size = size;
mb->entry = 0;
mb->in_offset = 0;
mb->out_offset = 0;
/* initialize an additional list of sender suspend thread
初始化发送挂起链表指针*/
rt_list_init(&(mb->suspend_sender_thread));
return RT_EOK;
}
//动态创建邮箱控制块, 并通过函数返回值返回
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)
{
rt_mailbox_t mb; //创建一个邮箱控制块
RT_DEBUG_NOT_IN_INTERRUPT;
/* allocate object 申请ram空间给到mb*/
mb = (rt_mailbox_t)rt_object_allocate(RT_Object_Class_MailBox, name);
if (mb == RT_NULL)
return mb;
/* set parent */
mb->parent.parent.flag = flag;
/* initialize ipc object 初始化OBJECT链表指针*/
rt_ipc_object_init(&(mb->parent));
/* initialize mailbox 将消息池大小赋值进邮箱控制块中 */
mb->size = size;
//申请消息池ram空间
mb->msg_pool = (rt_ubase_t *)RT_KERNEL_MALLOC(mb->size * sizeof(rt_ubase_t));
if (mb->msg_pool == RT_NULL)
{
/* delete mailbox object */
rt_object_delete(&(mb->parent.parent));
return RT_NULL;
}
mb->entry = 0;
mb->in_offset = 0;
mb->out_offset = 0;
/* initialize an additional list of sender suspend thread
初始化发送挂起链表指针*/
rt_list_init(&(mb->suspend_sender_thread));
return mb;
}
五、邮件发送
线程或者中断服务程序都可以给邮箱发送邮件,非阻塞方式的邮件发送过程能够安全地应用于中断服务中,中断服务函数、定时器向线程发送消息的有效手段,而阻塞方式的邮件发送只能应用线程中,当发送邮件时,当且仅当邮箱中还没满邮件的时候才能进行发送,如果邮箱已满,可以根据用户设定的等待时间进行等待,当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送的过程,当等待时间到了,还没有完成发送邮件,或者未设定等待时间,此时发送邮件失败,发送邮件的线程或者中断程序会收到一个错误码(-RT_EFULL),线程发送邮件可以带阻塞,但在中断中不能采用任何带阻塞的方式发送邮件。
rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
rt_ubase_t value,
rt_int32_t timeout)
{
struct rt_thread *thread;
register rt_ubase_t temp;
rt_uint32_t tick_delta;
/* parameter check */
RT_ASSERT(mb != RT_NULL);
RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
tick_delta = 0;
thread = rt_thread_self(); //获取当前发送邮件的线程
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mb->parent.parent)));
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* for non-blocking call */
if (mb->entry == mb->size && timeout == 0) //邮件满了且超时时间到了
{
rt_hw_interrupt_enable(temp);
return -RT_EFULL; //返回错误
}
/* mailbox is full */
while (mb->entry == mb->size) //邮件满了,超时时间没到(需要挂起该线程)
{
/* reset error number in thread */
thread->error = RT_EOK;
/* no waiting, return timeout */
if (timeout == 0) //超时时间到了 (唤醒线程后如果还mb->entry == mb->size)
{
/* enable interrupt */
rt_hw_interrupt_enable(temp); //返回错误
return -RT_EFULL;
}
RT_DEBUG_IN_THREAD_CONTEXT;
/* suspend current thread */
rt_ipc_list_suspend(&(mb->suspend_sender_thread),
thread,
mb->parent.parent.flag); //将当前线程放在挂起线程链表中
/* has waiting time, start thread timer */
if (timeout > 0) //还有时间没到
{
/* get the start tick of timer */
tick_delta = rt_tick_get(); //获取当前时间
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_send_wait: start timer of thread:%s\n",
thread->name));
/* reset the timeout of thread timer and start it */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout); //设置发送线程唤醒时间为timeout
rt_timer_start(&(thread->thread_timer)); //开启定时器
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
/* re-schedule */
rt_schedule(); //开启调度
/* resume from suspend state */
if (thread->error != RT_EOK) //当timeout唤醒线程后(会重新循环这个while)
{
/* return error */
return thread->error;
}
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* if it's not waiting forever and then re-calculate timeout tick */
if (timeout > 0)
{
tick_delta = rt_tick_get() - tick_delta;
timeout -= tick_delta;
if (timeout < 0)
timeout = 0;
}
}
//没有消息池没有满
/* set ptr */
mb->msg_pool[mb->in_offset] = value; //将信件放进消息池
/* increase input offset */
++ mb->in_offset; //输入偏移量++
if (mb->in_offset >= mb->size) //将消息池环起来
mb->in_offset = 0;
if(mb->entry < RT_MB_ENTRY_MAX) //
{
/* increase message entry */
mb->entry ++; //信件个数++
}
else //执行不到
{
rt_hw_interrupt_enable(temp); /* enable interrupt */
return -RT_EFULL; /* value overflowed */
}
/* resume suspended thread */ //消息池没有满, 需要将因为满件挂起的线程恢复调度
if (!rt_list_isempty(&mb->parent.suspend_thread)) //判断满件挂起的线程链表是否不空
{
rt_ipc_list_resume(&(mb->parent.suspend_thread)); //恢复挂起的线程
/* enable interrupt */
rt_hw_interrupt_enable(temp);
rt_schedule(); //恢复调度
return RT_EOK;
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
return RT_EOK;
}
//没有超时时间, 满件后返回-RT_EFULL; 中断函数中只能使用这个发送信件
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value)
{
return rt_mb_send_wait(mb, value, 0);
}
六、邮件接收
接收邮件时,根据邮箱控制块中的entry判断队列是否接收邮件,如果邮箱的邮件非空,那么可以根据out_offset找到最先发送的邮箱中的邮件进行接收,在接收时如果邮箱为空,如果用户设置了等待超时时间,系统会将当前线程挂起,当达到设置的超时时间,邮箱依然未收到邮件,那么线程将被唤醒并返回-RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的4个字节邮件到接收线程中,通常来说,邮件收取过程中可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。
//timeout可以是永久接收的.
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)
{
struct rt_thread *thread;
register rt_ubase_t temp;
rt_uint32_t tick_delta;
/* parameter check */
RT_ASSERT(mb != RT_NULL);
RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
/* initialize delta tick */
tick_delta = 0;
/* get current thread */
thread = rt_thread_self(); //获取当前接收时的线程
RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mb->parent.parent)));
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* for non-blocking call */
if (mb->entry == 0 && timeout == 0) //如果邮件中邮件个数为0(没有信件), 且超时
{
rt_hw_interrupt_enable(temp);
return -RT_ETIMEOUT; //返回错误
}
/* mailbox is empty */
while (mb->entry == 0) //如果邮件中邮件个数为0(没有信件)
{
/* reset error number in thread */
thread->error = RT_EOK;
/* no waiting, return timeout */
if (timeout == 0) //超时(用于判断恢复后的线程超时), 且邮件个数为0
{
/* enable interrupt */
rt_hw_interrupt_enable(temp);
thread->error = -RT_ETIMEOUT;
return -RT_ETIMEOUT; //返回错误
}
RT_DEBUG_IN_THREAD_CONTEXT;
/* suspend current thread */
rt_ipc_list_suspend(&(mb->parent.suspend_thread),
thread,
mb->parent.parent.flag); //挂起线程
/* has waiting time, start thread timer */
if (timeout > 0) //没有超时
{
/* get the start tick of timer */
tick_delta = rt_tick_get();
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_recv: start timer of thread:%s\n",
thread->name));
/* reset the timeout of thread timer and start it */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout); //设置线程唤醒时间
rt_timer_start(&(thread->thread_timer)); //开启定时器
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
/* re-schedule */
rt_schedule(); //开启调度
/* resume from suspend state 线程恢复后*/
if (thread->error != RT_EOK)
{
/* return error */
return thread->error;
}
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* if it's not waiting forever and then re-calculate timeout tick */
if (timeout > 0)
{
tick_delta = rt_tick_get() - tick_delta;
timeout -= tick_delta;
if (timeout < 0)
timeout = 0;
}
}
/* fill ptr 消息池不空*/
*value = mb->msg_pool[mb->out_offset]; //将消息中的数据给到Value (先进先出)
/* increase output offset */
++ mb->out_offset;
if (mb->out_offset >= mb->size) //将消息池环起来
mb->out_offset = 0;
/* decrease message entry */
if(mb->entry > 0) //消息不为空
{
mb->entry --; //消息个数减一
}
/* resume suspended thread 消息池没有满, 需要将因为满件挂起的线程恢复调度*/
if (!rt_list_isempty(&(mb->suspend_sender_thread)))
{
rt_ipc_list_resume(&(mb->suspend_sender_thread));
/* enable interrupt */
rt_hw_interrupt_enable(temp);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
rt_schedule();
return RT_EOK;
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
return RT_EOK;
}
参考资料
RT-Thread邮箱 CSDN : https://blog.csdn.net/qq_43460068/article/details/127040110