1. 程式人生 > >OSAL多工資源分配機制

OSAL多工資源分配機制

一、概述
OSAL (Operating System Abstraction Layer),翻譯為作業系統抽象層”。
 在ZigBee協議中,協議本身已經定義了大部分內容。在基於ZigBee協議的應用開發中,使用者只需要實現應用程式框架即可。應用程式框架中包含了最多240個應用程式物件。如果我們把一個應用程式物件看做為一個任務的話,那麼應用程式框架將包含一個支援多工的資源分配機制。於是OSAL便有了存在的必要性,它正是Z-Stack為了實現這樣一個機制而存在的。
     OSAL就是以實現多工為核心的系統資源管理機制。所以OSAL與標準的作業系統還是有很大的區別的。
簡單而言,
OSAL實現了類似作業系統的某些功能,但並不能稱之為真正意義上的作業系統

二、OSAL執行方式
     在GenericApp的工程的workspace裡面可以看到三個檔案,分別是“GenericApp.c”“GenericApp.h”“OSAL_GenericApp.c”。我們整個程式所實現的功能都在這三個檔案當中。

   首先開啟GenericApp.c這個檔案。我們首先看到的是比較重要的兩個函式:GenericApp_InitGenericApp_ProcessEvent從函式名稱上我們很容易得到的資訊便是,GenericApp_Init是任務的初始化函式,而GenericApp_ProcessEvent則負責處理傳遞給此任務的事件,此函式的主要功能是判斷由引數傳遞的事件型別,然後執行相應的事件處理函式


 當有一個事件發生的時候,OSAL負責將此事件分配給能夠處理此事件的任務,然後此任務判斷事件的型別,呼叫相應的事件處理程式進行處理
三、 OSAL的事件傳遞機制
      OSAL是如何傳遞事件給任務的?
首先介紹一下tasksArr 、tasksEvents(在OSAL_GenericApp.c檔案中)。
const pTaskEventHandlerFn tasksArr[] = {   macEventLoop,   nwk_event_loop,   Hal_ProcessEvent, #if defined( MT_TASK )   MT_ProcessEvent, #endif
  APS_event_loop, #if defined ( ZIGBEE_FRAGMENTATION )   APSF_ProcessEvent, #endif   ZDApp_event_loop, #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )   ZDNwkMgr_event_loop, #endif    GenericApp_ProcessEvent };
const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] );
uint16 *tasksEvents;
TaskArr這個數組裡存放了所有任務的事件處理函式的地址,在這裡事件處理函式就代表了任務本身,也就是說事件處理函式標識了與其對應的任務。tasksCnt這個變數儲存了當前的任務個數。
tasksEvents是一個指向陣列的指標,此陣列儲存了當前任務的狀態。osal每個任務可以有16個事件,其中SYS_EVENT_MSG定義為0x8000,為系統事件,使用者可以定義剩餘的15個事件。
tasksEvents這個陣列存放的是從序號為0到tasksCnt,每個任務在本次迴圈中是否要被執行,需要執行的任務其值非0(用橙色表示),否則為0。而tasksArr陣列則存放了對應每個任務的入口地址,只有在tasksEvents中記錄的需要執行的任務,在本次迴圈中才會被呼叫到。tasksEvents和tasksArr[]裡的順序是一一對應的, tasksArr[ ]中的第i個  事件處理函式對應於tasksEvents中的第i個任務的事件. 
 
Zmain.c->osal_init_system()->osal_initTasks()中:
void osalInitTasks( void ) {   uint8 taskID = 0;   tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);   osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));   macTaskInit( taskID++ );   nwk_init( taskID++ );   Hal_Init( taskID++ ); #if defined( MT_TASK )   MT_TaskInit( taskID++ ); #endif   APS_Init( taskID++ ); #if defined ( ZIGBEE_FRAGMENTATION )   APSF_Init( taskID++ ); #endif   ZDApp_Init( taskID++ ); #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )   ZDNwkMgr_Init( taskID++ ); #endif   SampleApp_Init( taskID ); }
 tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); 

為當前osal中的各任務分配 緩衝區(實際上是一個數組,每個任務的大小是sizeof( uint16 )即4個位元組,tasksCnt為任務數,sizeof( uint16 ) * tasksCnt為分配緩衝區的位元組數),函式返回指向任務緩衝區的指標,此指標的基本型別是  uint16。
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
把開闢的記憶體全部設定為0;sizeof( uint16 )是2個位元組,即一個任務的長度(同樣是uint16定義),乘以任務數量tasksCnt,即全部記憶體空間 tasksEvents 是緩衝區的首地址。

Zmain.c->osal_start_system(),此函式為一個死迴圈,在這個迴圈中,完成了所有的事件分配。
首先我們來看這樣一段程式碼:{
        do
        {
                if (tasksEvents[idx])
                {
                        break;
                }
        } while (++idx < tasksCnt);
}
  當tasksEvents這個陣列中的某個元素不為0,即代表此任務有事件需要相應,事件型別取決於這個元素的值。這個do-while迴圈會選出當前優先順序最高的需要響應的任務,{
        events = (tasksArr[idx])( idx, events )
}
  此語句呼叫tasksArr數組裡面相應的事件處理函式來響應事件。如果我們新新增的任務有了需要響應的事件,那麼此任務的事件處理程式將會被呼叫。  就這樣,OSAL就將需要響應的事件傳遞給了對應的任務處理函式進行處理。
四、事件的捕獲
事件是如何被捕獲的?直觀一些來說就是,tasksEvents這個數組裡的元素是什麼時候被設定為非零數,來表示有事件需要處理的?
下面以GenericApp這個例程中響應按鍵的過程來進行說明。
首先,OSAL專門建立了一個任務來對硬體資源進行管理,這個任務的事件處理函式是Hal_ProcessEvent。在這個函式中通過呼叫osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);這個函式使得每隔100毫秒就會執行一次HalKeyPoll()函式。HalKeyPoll()獲取當前按鍵的狀態,並且通過呼叫OnBoard_KeyCallback函式向GenericApp任務傳送一個按鍵訊息,並且設定tasksEventsGenericApp所對應的值為非零

OSAL將硬體的管理也作為一個任務來處理。那麼我們很自然的去尋找Hal_ProcessEvent這個事件處理函式,看看它究竟是如何管理硬體資源的。“HAL\Commen\ hal_drivers.c”這個檔案中,我們找到了這個函式。我們直接分析與按鍵有關的一部分。{
        if (events & HAL_KEY_EVENT)
        {
                #if (defined HAL_KEY) && (HAL_KEY == TRUE)
                /* Check for keys */
                HalKeyPoll();
                /* if interrupt disabled, do next polling */
                if (!Hal_KeyIntEnable)
                {
                        osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
                }
                #endif // HAL_KEY
                return events ^ HAL_KEY_EVENT;
        }
}
在事件處理函式接收到HAL_KEY_EVENT這樣一個事件後,首先執行HalKeyPoll()函式。由於這個例程的按鍵採用查詢的方法獲取,所以是禁止中斷的,於是表示式(!Hal_KeyIntEnable)的值為真。那麼osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100)得以執行。osal_start_timerEx這是一個很常用的函式,它在這裡的功能是經過100毫秒後,向Hal_TaskID這個ID所標示的任務(也就是其本身)傳送一個HAL_KEY_EVENT事件。這樣以來,每經過100毫秒,Hal_ProcessEvent這個事件處理函式都會至少執行一次來處理HAL_KEY_EVENT事件。也就是說每隔100毫秒都會執行HalKeyPoll()函式。
那麼我們來看看HalKeyPoll函式,在接近函式末尾的地方, keys變數(在函式起始位置定義的)獲得了當前按鍵的狀態。最後,有一個十分重要的函式呼叫。
(pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
pHalKeyProcessFunction這個函式指標指向了void OnBoard_KeyCallback ( uint8 keys, uint8 state ),因為在HalKeyConfig函式中有一句話pHalKeyProcessFunction = cback;cback是HalKeyConfig所傳進來的引數,所以,想要知道它所指向的函式,必須找到其呼叫的地方。經過簡單的搜尋我們不難找出答案。在main函式中有這樣一個函式呼叫:InitBoard( OB_READY );此函式中做了如下呼叫:
  {
        HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
  }

在 void OnBoard_KeyCallback 函式中按鍵的狀態資訊被封裝到了一個訊息結構體中。最後有一個極其重要的函式被呼叫了。osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );其中 registeredKeysTaskID 指的就是GenericApp。
osal_msg_send函式中osal_set_event( destination_task, SYS_EVENT_MSG )設定了任務事件。
通過呼叫osal_msg_send函式向GenericApp傳送了一個訊息,這個訊息記錄了這個事件的附加資訊。在GenericApp_ProcessEvent中,通過}
  獲取了這樣一個訊息,然後再通過GenericApp_HandleKeys進一步處理。
五、系統時鐘
      首先介紹一下定時器,協議棧裡面有兩類定時器:一類是硬體定時器,對應幾個Timer,硬體時鐘定時器即硬體時鐘。另一類是軟體定時, 通過osal_start_timerEx()新增到軟定時器連結串列,再由系統時鐘進行統一減計數。

時間管理

對事件進行時間管理,OSAL採用了連結串列的方式進行,有時發生一個要被處理的事件,就啟動一個邏輯上的定時器,並將此定時器新增到連結串列當中。利用硬體定時器作為時間操作的基本單元。設定時間操作的最小精度為1ms,每1ms硬體定時器便產生一個時間中斷,在時間中斷處理程式中去更新定時器連結串列。每次更新,就將連結串列中的每一項時間計數減1,如果發現定時器連結串列中有某一表項時間計數已減到0,則將這個定時器從連結串列中刪除,並設定相應的事件標誌。這樣任務排程程式便可以根據事件標誌進行相應的事件處理。

時間管理函式:
extern byte osal_start_timerEx(byte task_id, uint16 event_id, uint16 timeout_value);
這個函式為事件event_id設定超時等待時間timeout_value。一旦等待結束,便為task_id所對應的任務設定相應的事件發生標記,進行相應處理.
這段話中的硬體定時器,即為系統時鐘定時器,定時時間為“TICK_TIME—系統時間片”。

系統時間是多少?
在OnBoard.h中定義:
/* OSAL timer defines */
#define TICK_TIME 1000 // Timer per tick - in micro-sec
1000us,即1ms,

系統時鐘什麼時候初始化?

在OnBoard.c中InitBoard():
/***************************************
void InitBoard( byte level )//在zmain.c中先OB_COLD啟動,後OB_READY啟動
{
if ( level == OB_COLD )//冷啟動
{
…………
/* Timer2 for Osal timer
* This development board uses ATmega128 Timer/Counter3 to provide
* system clock ticks for the OSAL scheduler. These functions perform
* the hardware specific actions required by the OSAL_Timers module.
*/
OnboardTimerIntEnable = FALSE;
 //對系統時鐘定時器來說,預設為非中斷方式 
HalTimerConfig (OSAL_TIMER, // 8bit timer2
HAL_TIMER_MODE_CTC, // Clear Timer on Compare
HAL_TIMER_CHANNEL_SINGLE, // Channel 1 - default
HAL_TIMER_CH_MODE_OUTPUT_COMPARE, // Output Compare mode
OnboardTimerIntEnable, // FALSE
Onboard_TimerCallBack); // Channel Mode

}
…………
}
/***************************************

 OnBoard.h中初始化
/* OSAL Timer define */
#define OSAL_TIMER HAL_TIMER_2
而通過halTimerRemap()知道HAL_TIMER_2對應HW Timer 4 

定時器非中斷方式函式呼叫流程:
osal_timer_activate( TRUE )開啟系統時鐘定時器——系統主迴圈函式osal_start_system()——呼叫Hal_ProcessPoll()輪詢硬體——呼叫HalTimerTick()輪詢定時器——定時器採用非中斷方式則呼叫halProcessTimer()判斷定時器是否溢位——溢位則呼叫halTimerSendCallBack()來發送訊息給相應定時器的回撥函式——呼叫各定時器的回撥函式,如系統時鐘定時器的Onboard_TimerCallBack()——呼叫osal_update_timers()來更新軟體定時器連結串列中各定時器的計數值(每次減1ms)——如有軟體定時器溢位呼叫osal_set_event()觸發事件。
 

總結起來就是每1ms系統時鐘都會跑到軟體定時器連結串列中去把各定時器的計數值減1. 

看下 osalTimerUpdate()這個函式

/*********************************************************************
* @fn osalTimerUpdate
*
* @brief Update the timer structures for a timer tick.
*
* @param none
*
* @return none
*********************************************************************/
static void osalTimerUpdate( uint16 updateTime )
{
…………

// Update the system time
osal_systemClock += updateTime; //系統時間,1ms往上加

// Look for open timer slot
if ( timerHead != NULL )
{
// Add it to the end of the timer list
srchTimer = timerHead;
prevTimer = (void *)NULL;

// Look for open timer slot
while ( srchTimer )
{
// Decrease the correct amount of time
if (srchTimer->timeout <= updateTime) //小於等於
srchTimer->timeout = 0;
else //大於
srchTimer->timeout = srchTimer->timeout - updateTime; //減1ms

// When timeout, execute the task
if ( srchTimer->timeout == 0 )
{
osal_set_event( srchTimer->task_id, srchTimer->event_flag );
//設定事件發生標誌

…………
}
…………
}

…………
}

/*********************************************************************

osalTimerUpdate()來以ms為單位對軟定時器連結串列中的“軟定時器”減計數,溢位時,即呼叫osal_set_event觸發事件。

系統時鐘定時器什麼時候開啟?
osalTimerInit()函式中初始化timerActive = false,因此會在osal_start_timerEx()中執行osal_timer_activate( TRUE )。 

系統什麼時候開始執行第一個osal_start_timerEx(),就什麼時候開啟系統時鐘。即當系統的軟定時器連結串列中新增第一個軟體定時器時,就開啟系統時鐘。那什麼時候開始執行第一個osal_start_timerEx() ?一般是在任務的ProcessEvent函式中,如SampleApp_ProcessEvent和Hal_ProcessEvent。