事件主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程可等待多个事件的触发:可以是其中任一一个事件进行触发唤醒线程进行事件的处理操作;也可以是几个事件都到达后才触发唤醒线程进行后续的处理。同样,事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个32位无符号整型变量来表示,变量中的一位代表一个事件,线程通过“逻辑与”或“逻辑或”与一个或多个事件建立关联形成一个事件集。
事件的“逻辑或”也称为是独立型同步,指的是线程与任何事件之一发生同步;事件“逻辑与”也称为是关联型同步,指的是线程与若干事件都发生同步。
RT-Thread定义的事件有以下特点:
1. 事件只与线程相关,事件间相互独立:RT-Thread 定义的每个线程拥有32个事件标志,用一个32-bit无符号整型数记录,每一个bit代表一个事件。若干个事件构成一个事件集;
2. 事件仅用于同步,不提供数据传输功能;
3. 事件无排队性,即多次向线程发送同一事件(如果线程还未来得及读走),其效果等同于只发送一次。
在RT-Thread实现中, 每个线程还拥有一个事件信息标记(见:http://blog.csdn.net/flydream0/article/details/8584362 一文的第1章线程控制块的说明), 它有三个属性,分别是RT_EVENT_FLAG_AND (逻辑与), RT_EVENT_FLAG_OR (逻辑或) 以及RT_EVENT_FLAG_CLEAR (清除标记)。当线程等待事件同步时, 就可以通过32个事件标志和一个事件信息标记来判断当前接收的事件是否满足同步条件。
如上图所示,线程1的事件标志中第三位和第十位被置位,如果事件信息标记位设为逻辑与,则表示线程1只有在事件3和事件10都发生以后才会被触发唤醒,如果事件信息标记位设为逻辑或,则事件3或事件10中的任意一个发生都会触发唤醒线程1。如果信息标记同时设置了清除标记位,则发生的事件会导致线程1的相应事件标志位被重新置位为零。
事件的控件块如下定义:
/* * event structure */ struct rt_event { struct rt_ipc_object parent; /**< inherit from ipc_object *///从IPC对象派生 rt_uint32_t set; /**< event set *///保存接收到的事件集 }; typedef struct rt_event *rt_event_t;
/** * This function will initialize an event and put it under control of resource * management. * * @param event the event object * @param name the name of event * @param flag the flag of event * * @return the operation status, RT_EOK on successful */ rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag) { RT_ASSERT(event != RT_NULL); /* init object */ rt_object_init(&(event->parent.parent), RT_Object_Class_Event, name);//初始化事件的内核对象 /* set parent flag */ event->parent.parent.flag = flag;//设置内核对象的标志 /* init ipc object */ rt_ipc_object_init(&(event->parent));//初始化事件的IPC对象 /* init event */ event->set = 0;//初始化事件收到的事件集为空(每一个位表示一个事件) return RT_EOK; }
/** * This function will create an event object from system resource * * @param name the name of event * @param flag the flag of event * * @return the created event, RT_NULL on error happen */ rt_event_t rt_event_create(const char *name, rt_uint8_t flag) { rt_event_t event; RT_DEBUG_NOT_IN_INTERRUPT;//确保此函数不是在ISR中 /* allocate object */ event = (rt_event_t)rt_object_allocate(RT_Object_Class_Event, name);//动态分配事件的内核对象 if (event == RT_NULL) return event; /* set parent */ event->parent.parent.flag = flag;//设置事件的内核对象标志 /* init ipc object */ rt_ipc_object_init(&(event->parent));//初始化事件的IPC对象 /* init event */ event->set = 0;//初始化事件的接收到的事件集为空 return event; }
/** * This function will detach an event object from resource management * * @param event the event object * * @return the operation status, RT_EOK on successful */ rt_err_t rt_event_detach(rt_event_t event) { /* parameter check */ RT_ASSERT(event != RT_NULL); /* resume all suspended thread */ rt_ipc_list_resume_all(&(event->parent.suspend_thread));//唤醒所有因此事件而挂起的线程 /* detach event object */ rt_object_detach(&(event->parent.parent));//脱离事件的内核对象 return RT_EOK; }
/** * This function will delete an event object and release the memory * * @param event the event object * * @return the error code */ rt_err_t rt_event_delete(rt_event_t event) { /* parameter check */ RT_ASSERT(event != RT_NULL); RT_DEBUG_NOT_IN_INTERRUPT;//确保此函数不是在ISR中使用 /* resume all suspended thread */ rt_ipc_list_resume_all(&(event->parent.suspend_thread));//唤醒所有因此事件而挂起的线程 /* delete event object */ rt_object_delete(&(event->parent.parent));//删除事件的内核对象 return RT_EOK; }
/** * This function will send an event to the event object, if there are threads * suspended on event object, it will be waked up. * * @param event the event object * @param set the event set * * @return the error code */ rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set) { struct rt_list_node *n; struct rt_thread *thread; register rt_ubase_t level; register rt_base_t status; rt_bool_t need_schedule; /* parameter check */ RT_ASSERT(event != RT_NULL); if (set == 0)//无任何事件,则直接返回 return -RT_ERROR; need_schedule = RT_FALSE;//需要调度标志初始化为不需要 RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(event->parent.parent))); /* disable interrupt */ level = rt_hw_interrupt_disable();//关中断 /* set event */ event->set |= set;//事件的接收事件集或上发送的事件,即在事件控制块内保存接收到的事件 if (!rt_list_isempty(&event->parent.suspend_thread))//如果事件的挂起线程链表不为空 { /* search thread list to resume thread */ n = event->parent.suspend_thread.next;//遍历所有挂起的线程 while (n != &(event->parent.suspend_thread)) { /* get thread */ thread = rt_list_entry(n, struct rt_thread, tlist);//得到挂起的线程控制块 status = -RT_ERROR; if (thread->event_info & RT_EVENT_FLAG_AND)//如果此挂起的线程为事件过滤方式为逻辑与 { if ((thread->event_set & event->set) == thread->event_set)//即判断所有事件是否者触发 { /* received an AND event */ status = RT_EOK; } } else if (thread->event_info & RT_EVENT_FLAG_OR)//事件过滤方式为逻辑或 { if (thread->event_set & event->set)//判断是否触发任一关心的事件 { /* save recieved event set */ thread->event_set = thread->event_set & event->set;//将接收到的事件保存到线程中 /* received an OR event */ status = RT_EOK; } } /* move node to the next */ n = n->next;//指向下一挂起的线程 /* condition is satisfied, resume thread */ if (status == RT_EOK)//符合唤醒条件 { /* clear event */ if (thread->event_info & RT_EVENT_FLAG_CLEAR)//是否需要清除接收到的事件 event->set &= ~thread->event_set;//清除接收到的事件 /* resume thread, and thread list breaks out */ rt_thread_resume(thread);//唤醒此线程 /* need do a scheduling */ need_schedule = RT_TRUE;//设置需要重新调度标志 } } } /* enable interrupt */ rt_hw_interrupt_enable(level);//开中断 /* do a schedule */ if (need_schedule == RT_TRUE) rt_schedule();//重新调试线程 return RT_EOK; }
发送事件首先会将事件保存到事件控制内部,然后遍历事件控制块内所有因等待事件的接收线程,如果条件符合则唤醒它。
/** * This function will receive an event from event object, if the event is * unavailable, the thread shall wait for a specified time. * * @param event the fast event object * @param set the interested event set * @param option the receive option * @param timeout the waiting time * @param recved the received event * * @return the error code */ rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set,//感光趣的事件集 rt_uint8_t option,//过滤模式 rt_int32_t timeout, rt_uint32_t *recved) { struct rt_thread *thread; register rt_ubase_t level; register rt_base_t status; RT_DEBUG_NOT_IN_INTERRUPT;//确保此函数不在ISR中使用 /* parameter check */ RT_ASSERT(event != RT_NULL); if (set == 0)//如果不对任何事件感兴趣则直接返回 return -RT_ERROR; /* init status */ status = -RT_ERROR; /* get current thread */ thread = rt_thread_self();//获取当前线程 /* reset thread error */ thread->error = RT_EOK;//重置线程的err码 RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(event->parent.parent))); /* disable interrupt */ level = rt_hw_interrupt_disable();//关中断 /* check event set */ if (option & RT_EVENT_FLAG_AND)//如果过滤选项为逻辑与 { if ((event->set & set) == set)//判断是否已接收到所有感兴趣的事件 status = RT_EOK; } else if (option & RT_EVENT_FLAG_OR)//如果过滤选项为逻辑或 { if (event->set & set)//判断是否接收到任一感兴趣的事件 status = RT_EOK; } if (status == RT_EOK)//如果接收事件已成功 { /* set received event */ *recved = (event->set & set);//保存接收到的事件集到recved指向的空间 /* received event */ if (option & RT_EVENT_FLAG_CLEAR)//清除接收到的事件集 event->set &= ~set; } else if (timeout == 0)//如果没有接收到事件,且等待参数为0,则立即返回错误 { /* no waiting */ thread->error = -RT_ETIMEOUT; } else//如果没有接收到事件,且等待参数不为0,则需要阻塞当前线程 { /* fill thread event info */ thread->event_set = set; thread->event_info = option; /* put thread to suspended thread list */ rt_ipc_list_suspend(&(event->parent.suspend_thread),//挂起当前线程 thread, event->parent.parent.flag); /* if there is a waiting timeout, active thread timer */ if (timeout > 0) { /* 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(level);//开中断 /* do a schedule */ rt_schedule();//开始重新调度,此时当前线程才真正挂起 if (thread->error != RT_EOK)//只有两种可能才能运行到这:1 定时器超时,当前线程还是没有等到事件的到达,此时,定时器的超时回调处理函数内会将thread的error设置为-RT_ETIMOUT;2:事件到达,当前线程被唤醒,此时thread的error还是保持原值不变 { /* return error */ return thread->error;//没有等到事件到达,则直接返回错误码 } /* received an event, disable interrupt to protect */ level = rt_hw_interrupt_disable();//开中断,此时已等到事件的到来 /* set received event */ *recved = thread->event_set;//保存接收到的事件集到recved指向的内存 } /* enable interrupt */ rt_hw_interrupt_enable(level);//开中断 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(event->parent.parent))); return thread->error; }
接收线程比较简单,如果接收到事件则判断它是否为它关心的事件,如果是,则保存事件,如果不是,则当没有接收到事件情况一起处理。接下来就是没有事件到达的情况,还是老规矩,先判断时间参数是否为0,如果是则直接返回超时,如果不是,则设置一定时器然后启动它,接着重新调度线程,然后根据当前线程的error值是否为RT_EOK来判断是否有新的并且符合条件的事件到达,如果不是,则返回错误码,如果是,则保存事件,最终返回OK。
完!