在裸机编程中,经常会使用全局变量进行功能间的通信。如在中断处理函数中改变一个全局变量的值,在main函数中对此全局变量进行读取,根据读取到的全局变量值执行相应的动作,达到通信协作的目的。这其实也是一种通信的方式。在多线程中,主要提供了以下几种多线程的通信方式:邮箱、消息队列、信号。今天就来说说邮箱的方式
2什么是邮箱邮箱服务是实时操作系统中一种典型的线程间通信方法。举一个简单的例子,按键控制LED灯的亮灭。在裸机编程中,最简单的方式就是轮询了,伪代码如下所示:
int main(int argc,char *argv[]){ //硬件相关初始化 //按键读取采用非中断的方式 while(1) { if(按键的状态)//即IO读取的状态 { //按下 执行灯亮操作 } else { //弹起 执行灯灭操作 } }}
上诉的这个代码,看不到有通信的概念在里面,让我们来看一个中断版的伪代码:
static rt_uint8_t g_key_status = 0;//按键中断的处理函数void Exit_IRQ_Handler(void){ if(irq_happen) { //清除中断标志 g_key_status = 1;//1代表有键按下 }}int main(int argc,char *argv[]){ //硬件相关初始化 //按键读取采用中断的方式 while(1) { if(g_key_status)//读取全局变量的值 { //按下 g_key_status = 0;//清零 执行灯亮操作 } else { //弹起 执行灯灭操作 } }}
由读取IO的状态改为读取全局变量的状态,全局变量的状态是在中断函数中进行修改的,这里就有一点点通信的概念的。
如果用多线程,应该是怎样设计的呢?
有两个线程,分别为线程1和线程2。线程1将按键的状态作为邮件发送到邮箱,线程2在邮箱中读取邮件获得按键状态并对LED执行亮灭操作。
代码先不给出,代码在文末的例子里,这里主要说在线程中使用邮箱进行通信的方法。下面我们来看看在RTT中,邮箱的简单使用吧,我们先来看看它的工作机制。
3RTT中邮箱的工作机制RTT中定义的邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。发送和接收的方式都分为阻塞和非阻塞两种;非阻塞方式的邮件发送能够安全的应用于中断服务中,是线程、中断服务、定时器向线程发送消息的有效手段。通常来说,邮件收取过程可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。当邮箱中不存在邮件且超时时间不为 0 时,邮件收取过程将变成阻塞方式。在这类情况下,只能通过线程进行邮件的收取。
4RTT中邮箱的常用函数RTT的邮箱结构体定义如下:
/** * mailbox structure */struct rt_mailbox{ struct rt_ipc_object parent; /**< inherit from ipc_object */ rt_uint32_t *msg_pool; /**< start address of message buffer */ rt_uint16_t size; /**< size of message pool */ rt_uint16_t entry; /**< index of messages in msg_pool */ rt_uint16_t in_offset; /**< input offset of the message buffer */ rt_uint16_t out_offset; /**< output offset of the message buffer */ rt_list_t suspend_sender_thread; /**< sender thread suspended on this mailbox */};typedef struct rt_mailbox *rt_mailbox_t;
rt_mailbox 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。
常用的api函数如下所示:
5示例代码#include static struct rt_mailbox static_mail_box;static rt_uint8_t mail_box_pool[256] = {0};//一共能放下256/4封邮件static rt_uint8_t mail_box_key_down[]="key_dowm_mb";static rt_uint8_t mail_box_key_up[]="key_up_mb";static rt_uint8_t mail_box_stop[]="stop";/*按键检测,并发送邮箱消息*/static void key_check_thread_entry(void *parameter){ rt_uint8_t count = 0; while(count < 2) { rt_kprintf("key down!count=%d\r\n",count); /*发送key_down邮箱信息*/ rt_mb_send(&static_mail_box,(rt_uint32_t)&mail_box_key_down); rt_thread_delay(100); rt_kprintf("key up!count=%d\r\n",count); /*发送key_up邮箱信息*/ rt_mb_send(&static_mail_box,(rt_uint32_t)&mail_box_key_up); rt_thread_delay(100); count += 1; } rt_mb_send(&static_mail_box,(rt_uint32_t)&mail_box_stop); rt_kprintf("%s will return!\r\n",rt_thread_self()->name);}/*接收邮箱消息,根据接收到的消息控制led灯的亮灭*/static void led_thread_entry(void *parameter){ rt_uint8_t *str = RT_NULL; while(1) { rt_mb_recv(&static_mail_box,(rt_uint32_t *)&str,RT_WAITING_FOREVER); if(str == mail_box_key_down) { rt_kprintf("key down,led will turn on!\r\n"); } else if(str == mail_box_key_up) { rt_kprintf("key up,led will turn off!\r\n"); } else if(str == mail_box_stop) { rt_kprintf("%s will return!\r\n",rt_thread_self()->name); return ; } }}static void mail_box_example_init(void){ rt_err_t err = 0; err = rt_mb_init(&static_mail_box, "mb", mail_box_pool, sizeof(mail_box_pool) / 4, RT_IPC_FLAG_FIFO); if(err != RT_EOK) { rt_kprintf("init mail box error"); RT_ASSERT(0); } rt_thread_t key_check_thread = rt_thread_create("key_t", key_check_thread_entry, RT_NULL, 1024, 10, 10); if(key_check_thread != RT_NULL) { rt_thread_startup(key_check_thread); } rt_thread_t led_thread = rt_thread_create("led_t", led_thread_entry, RT_NULL, 1024, 11, 10); if(led_thread != RT_NULL) { rt_thread_startup(led_thread); }}/* 导出到 msh 命令列表中 */MSH_CMD_EXPORT(mail_box_example_init, mail box sample);
进入软件仿真:
在串口窗口输入help命令
紧接着输入mail_box_example_init,支持tab键补全。执行两次后,线程退出。
原创不易,据说点「在看」会更好看哦~