线程间通信
在裸机编程中,通信是使用全局变量,一些程序可能会因为一段操作改变全局变量的值,而另一些程序对这些全局变量进行读取,某些值会导致一些程序进行运行,从而达到多功能之间的协作。
邮箱
邮箱服务是实时操作系统中一种典型的线程间通信方法,比如线程1启动后,线程2要随之启动,就可以用邮箱传递信息
重点
1.create-delete 中有分配内存,释放内存,init-detach 中没有。
与创建邮箱不同的是,静态邮箱对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中,其余的初始化工作与创建邮箱时相同。
2.删除/脱离邮箱,要把其中的等待发送到邮箱线程和等待发送线程返回失败。
3.邮件大小4个字节,缓冲区的字节数是 N,那么邮箱容量应该是 N/4。
4.关中断后的程序有多个路径,记得每个路径最后都要使能中断。
邮箱的工作机制
邮箱用于线程间的通信(异步通信),使用邮箱的优点是时间短,高效。发送的邮件是4个字节,也就是一个指针的大小,邮箱也就是一个信息交换的存储点。
线程或中断服务例程把一则4字节长度(典型的是一个指针)的邮件发送到邮箱中。而一个或多个线程可以从邮箱中接收这些邮件进行处理。
1邮箱控制块
struct rt_mailbox
{
struct rt_ipc_object parent; //ipc对象
rt_uint32_t *msg_pool; //邮件缓冲区地址
rt_uint16_t size; //邮箱可以存储的邮件数
rt_uint16_t entry; //当前邮箱中存放的消息条数
rt_uint16_t in_offset; //消息存入的偏移量
rt_uint16_t out_offset; //消息取出的偏移量
rt_list_t suspend_sender_thread; //当邮箱满了,等待发送信件的挂起线程
};
typedef struct rt_mailbox *rt_mailbox_t;
2.邮箱的初始化
rt_err_t rt_mb_init(rt_mailbox_t mb,
const char *name,
void *msgpool,
rt_size_t size,
rt_uint8_t flag)
{
RT_ASSERT(mb != RT_NULL);//邮箱控制块是否存在
/* 对象初始化 */
rt_object_init(&(mb->parent.parent), RT_Object_Class_MailBox, name);
/* 确定对象标志位 */
mb->parent.parent.flag = flag;
/* 初始化IPC对象 */
rt_ipc_object_init(&(mb->parent));
/* 邮箱初始化 */
mb->msg_pool = msgpool;
mb->size = size;
mb->entry = 0;
mb->in_offset = 0;
mb->out_offset = 0;
/* 初始化(当邮箱满了,等待发送信件的挂起线程)的链表 */
rt_list_init(&(mb->suspend_sender_thread));
return RT_EOK;
}
RTM_EXPORT(rt_mb_init);
3.邮箱的脱离
rt_err_t rt_mb_detach(rt_mailbox_t mb)
{
/*核查是否为邮箱 */
RT_ASSERT(mb != RT_NULL);
RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
RT_ASSERT(rt_object_is_systemobject(&mb->parent.parent));
/* 因为要删除邮箱,恢复所有挂起的线程,这些线程都未 接收 邮件则返回失败 */
rt_ipc_list_resume_all(&(mb->parent.suspend_thread));
/*因为要删除邮箱,恢复所有挂起的线程,这些线程都未 发送 邮件则返回失败*/
rt_ipc_list_resume_all(&(mb->suspend_sender_thread));
/* 邮箱对象脱离对象链表 */
rt_object_detach(&(mb->parent.parent));
return RT_EOK;
}
RTM_EXPORT(rt_mb_detach);
4.邮箱的创建
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;//分配内存不能在关中断中运行,运行时间过久
/* 对象容器中建立邮箱对象 */
mb = (rt_mailbox_t)rt_object_allocate(RT_Object_Class_MailBox, name);
if (mb == RT_NULL)
return mb;
/* 设置标志位 */
mb->parent.parent.flag = flag;
/* 初始化IPC对象 */
rt_ipc_object_init(&(mb->parent));
/* 初始化邮箱 */
mb->size = size;//设置内存大小即邮件数量最大值
mb->msg_pool = RT_KERNEL_MALLOC(mb->size * sizeof(rt_uint32_t));//分配内存,返回内存起始地址
if (mb->msg_pool == RT_NULL)
{
/* 没有分配到内存就删除邮箱 */
rt_object_delete(&(mb->parent.parent));
return RT_NULL;
}
mb->entry = 0;//当前邮箱中存放的消息条数为0
mb->in_offset = 0;//消息存入的偏移量为0
mb->out_offset = 0;//消息取出的偏移量为0
/* 初始化等待发送邮件的线程链表 */
rt_list_init(&(mb->suspend_sender_thread));
return mb;
}
RTM_EXPORT(rt_mb_create);
5.邮箱的删除
rt_err_t rt_mb_delete(rt_mailbox_t mb)
{
RT_DEBUG_NOT_IN_INTERRUPT;
/*核查是否为邮箱 */
RT_ASSERT(mb != RT_NULL);
RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
RT_ASSERT(rt_object_is_systemobject(&mb->parent.parent) == RT_FALSE);
/* 唤醒所有挂起的线程,因为要删除邮箱,这些线程都返回失败 */
rt_ipc_list_resume_all(&(mb->parent.suspend_thread));
/* 醒所有挂起要发送给邮箱的线程,因为要删除邮箱,这些线程都返回失败 */
rt_ipc_list_resume_all(&(mb->suspend_sender_thread));
/* 释放内存 */
RT_KERNEL_FREE(mb->msg_pool);
/* 删除邮箱对象 */
rt_object_delete(&(mb->parent.parent));
return RT_EOK;
}
RTM_EXPORT(rt_mb_delete);
6.发送邮件(含延时)
rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
rt_uint32_t value,
rt_int32_t timeout)
{
struct rt_thread *thread;
register rt_ubase_t temp;
rt_uint32_t tick_delta;
/* 核查类型 */
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)));
/* 关中断 */
temp = rt_hw_interrupt_disable();
/* 非阻塞呼叫,无内存或无时延*/
if (mb->entry == mb->size && timeout == 0)//邮箱满了并且线程不等待,返回满了错误码
{
rt_hw_interrupt_enable(temp);
return -RT_EFULL;
}
/* 邮箱满了的操作 */
while (mb->entry == mb->size)
{
/* 重置线程中的错误号 */
thread->error = RT_EOK;
/* 线程不等待,直接退出返回,错误码返回满了 */
if (timeout == 0)
{
/* 开中断 */
rt_hw_interrupt_enable(temp);
return -RT_EFULL;
}
RT_DEBUG_IN_THREAD_CONTEXT;//确保在进程上文运行
/* 挂起当前线程 */
rt_ipc_list_suspend(&(mb->suspend_sender_thread),
thread,
mb->parent.parent.flag);
/* 有等待时间,启动线程计时器 */
if (timeout > 0)
{
/*获取计时器的数值 */
tick_delta = rt_tick_get();
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_send_wait: start timer of thread:%s\n",
thread->name));
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout);//设置线程的定时器
rt_timer_start(&(thread->thread_timer));//启动定时器
}
/* 开中断 */
rt_hw_interrupt_enable(temp);
/* 打开调度器 */
rt_schedule();
if (thread->error != RT_EOK)//错误码不为RT_EOK,即挂起线程失败
{
/* return error */
return thread->error;
}
/* 关中断 */
temp = rt_hw_interrupt_disable();
/* 此判断在while循环中,判断经过的时间是否大于timeout,大于则timeout归零,回到timeout==0的判断中*/
if (timeout > 0)
{
tick_delta = rt_tick_get() - tick_delta;
timeout -= tick_delta;
if (timeout < 0)
timeout = 0;
}
}
//上方while循环为邮箱满的操作,下方为未满操作
/* 将value的地址存入邮箱的第in_offset个位置 */
mb->msg_pool[mb->in_offset] = value;
++ mb->in_offset;//存放的偏移量加一
if (mb->in_offset >= mb->size)//存放的偏移量大于邮箱邮件的最大数,也就是存满了
mb->in_offset = 0;
mb->entry ++;//当前邮箱的邮件数加一
/*判断是否有挂起线程,有则恢复线程 */
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;
}
/* 使能中断 */
rt_hw_interrupt_enable(temp);
return RT_EOK;
}
RTM_EXPORT(rt_mb_send_wait);
7.发送邮件(不含延时)
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value)
{
return rt_mb_send_wait(mb, value, 0);//延时为0的邮件发送函数
}
RTM_EXPORT(rt_mb_send);
8.接收邮件
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout)
{
struct rt_thread *thread;
register rt_ubase_t temp;
rt_uint32_t tick_delta;
/*核查是否邮箱 */
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_trytake_hook, (&(mb->parent.parent)));
/* 关中断1*/
temp = rt_hw_interrupt_disable();
/* 邮箱中没有信件并无延时(没有延时表示不会再收到信件) */
if (mb->entry == 0 && timeout == 0)
{
rt_hw_interrupt_enable(temp);
return -RT_ETIMEOUT;
}
/* 邮箱为空的运行 */
while (mb->entry == 0)
{
/* 重置线程中的错误号*/
thread->error = RT_EOK;
/* 不会再收到信件,返回线程错误码 */
if (timeout == 0)
{
/* 开中断1 */
rt_hw_interrupt_enable(temp);
thread->error = -RT_ETIMEOUT;
return -RT_ETIMEOUT;
}
/* 令当前线程不能使用中断服务 */
RT_DEBUG_IN_THREAD_CONTEXT;//确保在进程上文运行
/* 没有邮件接收,挂起当前线程 */
rt_ipc_list_suspend(&(mb->parent.suspend_thread),
thread,
mb->parent.parent.flag);
/* 若设置了等待时间,设置线程定时器 */
if (timeout > 0)
{
/*获得当前时钟 */
tick_delta = rt_tick_get();
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_recv: start timer of thread:%s\n",
thread->name));
/* 重置线程计时器的超时并启动它 */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout);//设置线程定时器
rt_timer_start(&(thread->thread_timer));//启动线程定时器
}
/* 开中断1 */
rt_hw_interrupt_enable(temp);
/* 启动调度器 */
rt_schedule();
/* 线程状态检测,挂起线程没成功就返回错误码 */
if (thread->error != RT_EOK)
{
/* 不为EOK返回当前状态*/
return thread->error;
}
/* 关中断2 */
temp = rt_hw_interrupt_disable();
/* 此判断在while循环中,判断经过的时间是否大于timeout,大于则timeout归零,回到timeout==0的判断中 */
if (timeout > 0)
{
tick_delta = rt_tick_get() - tick_delta;
timeout -= tick_delta;
if (timeout < 0)
timeout = 0;
}
}
/* 取出邮箱信件 */
*value = mb->msg_pool[mb->out_offset];
/* 取出偏移量加一*/
++ mb->out_offset;
if (mb->out_offset >= mb->size)//取出偏移量大于邮箱大小就回到邮箱起点(环形,首尾相连)
mb->out_offset = 0;
/*当前邮箱邮件数减一 */
mb->entry --;
/* 当前线程接收完邮箱,轮到下一个线程,存在下一个线程就唤醒 */
if (!rt_list_isempty(&(mb->suspend_sender_thread)))
{
rt_ipc_list_resume(&(mb->suspend_sender_thread));
/* 开中断2*/
rt_hw_interrupt_enable(temp);
/*设置线程获取内核对象钩子函数 该函数将设置一个钩子函数,当线程获取到内核对象后将调用该钩子函数。*/
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
rt_schedule();
return RT_EOK;
}
/* 开中断2 */
rt_hw_interrupt_enable(temp);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
return RT_EOK;
}
RTM_EXPORT(rt_mb_recv);
9.对邮箱进行参数设置
rt_err_t rt_mb_control(rt_mailbox_t mb, int cmd, void *arg)
{
rt_ubase_t level;
/* 核查类型*/
RT_ASSERT(mb != RT_NULL);
RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
if (cmd == RT_IPC_CMD_RESET)
{
/* 关中断1 */
level = rt_hw_interrupt_disable();
/* 唤醒所有等待线程,使其返回失败 */
rt_ipc_list_resume_all(&(mb->parent.suspend_thread));
/* 唤醒所有等待发送给邮箱的线程,使其返回失败 */
rt_ipc_list_resume_all(&(mb->suspend_sender_thread));
/* 重置邮箱 */
mb->entry = 0;
mb->in_offset = 0;
mb->out_offset = 0;
/*开中断1 */
rt_hw_interrupt_enable(level);
rt_schedule();
return RT_EOK;
}
return -RT_ERROR;
}
RTM_EXPORT(rt_mb_control);