1 前言
事件主要用於線程間的同步,與信號量不同,它的特點是可以實現一對多,多對多的同步。即一個線程可等待多個事件的觸發:可以是其中任一一個事件進行觸發喚醒線程進行事件的處理操作;也可以是幾個事件都到達後才觸發喚醒線程進行後續的處理。同樣,事件也可以是多個線程同步多個事件。這種多個事件的集合可以用一個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的相應事件標志位被重新置位為零。
2 事件控制塊
事件的控件塊如下定義:
[cpp]
/*
* 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;
由上源碼可知,事件的控制塊也是從IPC對象派生。
3 接口實現源碼分析
3.1 初始化事件
[cpp]
/**
* 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;
}
3.2 創建事件
[cpp]
/**
* 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;
}
3.3 脫離事件
[cpp]
/**
* 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;
}
3.4 刪除事件
[cpp]
/**
* 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;
}
3.5 發送事件
[cpp]
/**
* 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;
}
發送事件首先會將事件保存到事件控制內部,然後遍歷事件控制塊內所有因等待事件的接收線程,如果條件符合則喚醒它。
3.6 接收事件
[cpp]
/**
* 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。