MTK平臺任務(Task)與定時器開發詳解
2022-06-10由 曙海高階技術專家 發表于 林業
qid操作頻繁怎麼辦
引子
不管什麼系統平臺,任務都是一個很重要的概念,系統平臺可以利用任務處理一些比較複雜的工作,本章我們對任務的建立和使用的流程進行詳細的闡述。
9。1 MTK中任務的概念
在MTK中,所謂任務,是指具有一定封裝性的軟體模組,可以理解為一個程序。MTK 的基本執行單元是task,從作業系統的角度來理解,task 有些像執行緒而不是程序,程序之間的地址空間是相互隔離的,說白點就是程序之間的全域性變數是不相互干擾的,
而執行緒之間則是用同一個地址空間,MTK 的task 之間的地址空間也是共同的,也就是在MTK 程式設計裡,定義了一個全域性變數,那麼在任何一個task裡面都能引用,
所以說,MTK 的task 更像執行緒,MTK 用的是實時作業系統 nucleus,是非搶佔式作業系統,也就是當高優先順序的task 在執行時,底優先順序的task是得不到執行時間的,除非等高優先順序的task 因為種種原因掛起。
MTK 還有一個跟task 想關的概念 叫 module,它跟task 之間的關係是:一個task 可以對應多個module。task 主要表示是一個執行單元,module 主要是用於傳遞訊息,在MTK 中,訊息傳遞是module 為單位 src_mod – > des_mod,而不是以task為單位。
雖然MTK手機,是feature phone(功能機),不像symbian 6 那樣可以同時執行多個應用。但是MTK還是 有許多task組成。平時MTK的後臺播放MP3就是一由一個task 完成的。具體以後分析。現在來看看MTK 最主要的task,MMI task,MTK 的應用程式都是在該task裡面執行,它有一整套開發MTK 應用的framework。
9。2、任務的建立
1。 修改Custom_config。h檔案,增加索引和ID,修改部分如下(注意修改了兩處):
typedef enum {
INDX_CUSTOM1 = RPS_CUSTOM_TASKS_BEGIN,
INDX_CUSTOM2,
INDEX_DEMO, //
增加該條目
RPS_CUSTOM_TASKS_END
} custom_task_indx_type;
/*************************************************************************
* [Very Important Message]
* 1。 Component task‘s module id (Please add before system service)
* 2。 Customers are allowed to create at most 16 task module ID as defined
* in config\include\stack_config。h (MAX_CUSTOM_MODS = 16)
*************************************************************************/
typedef enum {
MOD_CUSTOM1 = MOD_CUSTOM_BEGIN,
MOD_CUSTOM2,
MOD_DEMO,//
增加該條目
MOD_CUSTOM_END
} custom_module_type;
注意:custom_module_type列舉裡面的成員不能超過16個。
2, 修改Custom_config。c,新增對映
custom_task_indx_type custom_mod_task_g[ MAX_CUSTOM_MODS ] =
{
INDX_CUSTOM1, /* MOD_CUSTOM1 */
INDX_CUSTOM2, /* MOD_CUSTOM2 */
INDX_DEMO,
//
增加該條目
INDX_NIL /* Please end with INDX_NIL element */
};
注意:條目的順序要和標頭檔案保持一致。
3,配置Task
const comptask_info_struct custom_comp_config_tbl[ MAX_CUSTOM_TASKS ] =
{
/* INDX_CUSTOM1 */
{“CUST1”, “CUST1 Q”, 210, 1024, 10, 0,
#ifdef CUSTOM1_EXIST
custom1_create, KAL_FALSE
#else
NULL, KAL_FALSE
#endif
},
/* INDX_CUSTOM2 */
{“CUST2”, “CUST2 Q”, 211, 1024, 10, 0,
#ifdef CUSTOM2_EXIST
custom2_create, KAL_FALSE
#else
NULL, KAL_FALSE
#endif
},
{“TEST”, “TEST, 210, 1024, 10, 0,demo_task_create,KAL_FALSE}
};
注意:{”TEST“, ”TEST, 210, 1024, 10, 0,demo_task_create,KAL_FALSE}
}裡引數的含義如下:
引數一:任務名稱
引數二:外部佇列的名稱
引數三:任務優先順序
引數四:外部佇列的大小
引數五:內部佇列大小
引數六:建立任務的入口函式
引數七:函式指標,要求返回kal_bool
經過上面的工作,一個新的完整任務的框架建立起來了,下面我們演示一下怎樣使用任務。
16 。3 任務的使用流程
1、 由於任務是基於訊息驅動的,所以使用任務前,應確定可向任務請求哪些訊息,訊息完成的服務,請求訊息時要提供的資訊。
首先我們把訊息放到佇列,這個步驟需要用到send_ILM(src_mod,dest_mod,sap,ilm_ptr)函式,示例如下:
Void vid_send_play_finish_ind(kal_int16 result)
{
ilm_struct *ilm_ptr=NULL;
ilm_ptr=allocate_ilm(MOD_MMI);
ilm_ptr->msg_id=(msg_type)MSG_ID_DEMO; //建立的訊息的ID
ilm_ptr->local_para_ptr=NULL;
ilm_ptr->peer_buff_ptr=NULL;
SEND_ILM(MOD_MMI,MOD_DEMO,0,ilm_ptr); //MOD_DEMO就是我們建立的任務的模組
}
2、 建立一個任務
Kal_bool demo_task_create(comptask_handler_struct **handle)
{
static void demo_main(task_entry_struct *task_entry_ptr)
{
ilm_struct current_ilm;
while(1)
{
receive_msg_ext_q(task_info_g[task_entry_ptr->task_indx]。task_ext_qid,¤t_ilm);
switch (current_ilm。msg_id)
case MSG_ID_DEMO_START: //和我們建立的任務相關的訊息
//訊息處理
break;
default:
break;
}
free_ilm(¤t_ilm);
}
}
注意上面的的函式是在配置任務時被呼叫的,即下面的程式碼:
{“TEST”, “TEST, 210, 1024, 10, 0,demo_task_create,KAL_FALSE}
};
9。3 Task 應用用例項
看了上面Task使用的完整過程後,我再舉個例子以加深對Task的理解。
先來看建立MMI task的函式,詳細程式碼如下:
kal_bool mmi_create(comptask_handler_struct **handle)
{
/*————————————————————————————————*/
/* Local Variables */
/*————————————————————————————————*/
static comptask_handler_struct mmi_handler_info =
{
MMI_task, /* task entry function */
MMI_Init, /* task initialization function */
NULL,
NULL, /* task reset handler */
NULL, /* task termination handler */
};
/*————————————————————————————————*/
/* Code Body */
/*————————————————————————————————*/
*handle = &mmi_handler_info;
return KAL_TRUE;
}
這個函式的結構,是MTK 建立task的基本結構,系統初始化時,會呼叫該函式。看裡面的結構體
typedef struct {
kal_task_func_ptr comp_entry_func; //task 的入口函式
task_init_func_ptr comp_init_func; //task 的初始化函式
task_cfg_func_ptr comp_cfg_func; //task 的配置函式
task_reset_func_ptr comp_reset_func; //task 的重置函式
task_end_func_ptr comp_end_func; //task 的終止函式
} comptask_handler_struct;
task 的入口函式是必須的,這個函式告訴系統,初始化完相應的task 控制塊後,就要進入該函式來執行。
task 初始化函式,是在進入 task 入口函式之前被呼叫,用來初始化可能需要的資源,可選。
task 終止函式是,當task 結束是要呼叫,用來釋放資源,可選。
先看MMI task 的初始化函式。
MMI_BOOL MMI_Init(task_indx_type task_indx)
{
//建立一個mutex(互斥體)
mmi_mutex_trace = kal_create_mutex(”mmi_trace“);
//這個是初始化 2step 按鍵, 2step 按鍵是指 有一些按鍵具有半按下狀態
//比如照相功能,按下一半進行聚焦,再按下一半拍照
mmi_frm_get_2step_keys();
//初始化timer,具體可以看 MTK timer 小結 系列
L4InitTimer();
//初始化 UI 相關資訊,裡面有許多畫點,圖等函式
setup_UI_wrappers();
return MMI_TRUE;
}
初始化函式比較簡單。
下面來看MMI 的入口函式,這個函式是整個MMI 執行的核心。
//為了簡單,刪除了大部分宏控制程式
void MMI_task(oslEntryType *entry_param)
{
MYQUEUE Message;
oslMsgqid qid;
U32 my_index;
U32 count = 0;
U32 queue_node_number = 0;
// 獲得task的外部訊息佇列id,透過這個id,獲得別的task 往MMI task傳送的訊息
// MMI task 有兩個訊息,外部訊息佇列和內部訊息佇列
// 外部訊息佇列的訊息不直接處理,只是簡單的存放到內部訊息佇列,
// 這樣使內部訊息佇列的優先順序稍微高一點
qid = task_info_g[entry_param->task_indx]。task_ext_qid;
mmi_ext_qid = qid;
// 初始化 event 處理函式,這個幾個event 必須在獲得訊息前就進行註冊
// 不讓可能使得這個event 丟棄。具體event 事件,下次介紹
InitEventHandlersBeforePowerOn();
//進入task 的while 迴圈
// task 的while(1) 迴圈使得這個task 不會結束,只有掛起或者執行
while (1)
{
{
// 判斷是否有 key 事件需要處理
if (g_keypad_flag == MMI_TRUE)
{
mmi_frm_key_handle(NULL);
}
// 獲得外部訊息佇列裡,訊息的個數
msg_get_ext_queue_info(mmi_ext_qid, &queue_node_number);
// 如果沒有任何訊息需要處理(內部訊息和外部訊息都沒有,同時也沒有按鍵需要處理)
// OslNumOfCircularQMsgs 獲得內部訊息佇列訊息的個數
if ((queue_node_number == 0) && (OslNumOfCircularQMsgs() == 0) && (g_keypad_flag == MMI_FALSE))
{
U8 flag = 0;
ilm_struct ilm_ptr;
//去外部訊息佇列裡獲得訊息,這是一個阻塞函式,也就是說,如果外部訊息佇列裡,
//沒有任何訊息,那麼這個task 將被阻塞,或者說掛起,也就是不在執行,
//直到有訊息到達,才會被喚醒, 看過作業系統原理的,應該不難理解這個意思和這個本質
OslReceiveMsgExtQ(qid, &Message);
//如果有訊息,獲得task 的index
OslGetMyTaskIndex(&my_index);
// 設定該task的獲得mod 為MMI mod。
OslStackSetActiveModuleID(my_index, MOD_MMI);
//儲存該訊息,用於放入到內部佇列
ilm_ptr。src_mod_id = Message。src_mod_id;
ilm_ptr。dest_mod_id = Message。dest_mod_id;
ilm_ptr。msg_id = Message。msg_id;
ilm_ptr。sap_id = Message。sap_id;
ilm_ptr。local_para_ptr = Message。local_para_ptr;
ilm_ptr。peer_buff_ptr = Message。peer_buff_ptr;
//放入內部佇列
// 這個內部佇列是個簡單的迴圈佇列
flag = OslWriteCircularQ(&ilm_ptr);
// 對 timer 訊息進行特殊處理
if (Message。src_mod_id != MOD_TIMER)
{
hold_local_para(ilm_ptr。local_para_ptr);
hold_peer_buff(ilm_ptr。peer_buff_ptr);
OslFreeInterTaskMsg(&Message);
}
}
else
{
// 把外部訊息放入到內部訊息
mmi_frm_fetch_msg_from_extQ_to_circularQ();
}
//處理內部訊息
count = OslNumOfCircularQMsgs();
while (count > 0)
{
OslGetMyTaskIndex(&my_index);
OslStackSetActiveModuleID(my_index, MOD_MMI);
if (OslReadCircularQ(&Message))
{
CheckAndPrintMsgId((U16) (Message。msg_id));
//是否是 wap 的訊息
// 這裡就體現了一個task 可以對應多個mod
if (Message。dest_mod_id == MOD_WAP)
{
}
else
{
switch (Message。msg_id)
{
//timer 訊息 具體看 MTK timer 小結 2
case MSG_ID_TIMER_EXPIRY:
{
kal_uint16 msg_len;
//處理stack timer訊息
EvshedMMITimerHandler(get_local_para_ptr(Message。oslDataPtr, &msg_len));
}
break;
//開機訊息
//具體分析 見後文
case MSG_ID_MMI_EQ_POWER_ON_IND:
{
mmi_eq_power_on_ind_struct *p = (mmi_eq_power_on_ind_struct*) Message。oslDataPtr;
/* To initialize data/time */
SetDateTime((void*)&(p->rtc_time));
gdi_init();
g_pwr_context。PowerOnMMIStatus = MMI_POWER_ON_INDICATION;
switch (p->poweron_mode)
{
case POWER_ON_KEYPAD:
OslMemoryStart(MMI_TRUE);
g_charbat_context。PowerOnCharger = 0;
g_pwr_context。PowerOnMode = POWER_ON_KEYPAD;
DTGetRTCTime(&StartUpTime);
memset(&LastDuration, 0, sizeof(LastDuration));
mmi_bootup_entry_disk_check();
break;
case POWER_ON_PRECHARGE:
case POWER_ON_CHARGER_IN:
g_pwr_context。PowerOnMode = p->poweron_mode;
InitializeChargingScr();
if (!g_charbat_context。isChargerConnected)
{
QuitSystemOperation();
}
break;
case POWER_ON_ALARM:
g_pwr_context。PowerOnMode = POWER_ON_ALARM;
gdi_layer_clear(GDI_COLOR_BLACK);
AlmInitRTCPwron();
break;
case POWER_ON_EXCEPTION:
g_pwr_context。PowerOnMode = POWER_ON_EXCEPTION;
gdi_layer_clear(GDI_COLOR_BLACK);
OslMemoryStart(MMI_TRUE);
SetAbnormalReset();
InitializeAll();
OslDumpDataInFile();
ClearInputEventHandler(MMI_DEVICE_ALL);
ClearKeyHandler(KEY_END, KEY_LONG_PRESS);
InitNvramData();
InitAllApplications();
mmi_pwron_exception_check_display();
break;
default:
break;
}
}
break;
// event 時間,這個也是MMI task 的一個重點
default:
ProtocolEventHandler(
(U16) Message。oslMsgId,
(void*)Message。oslDataPtr,
(int)Message。oslSrcId,
(void*)&Message);
break;
}
}
OslFreeInterTaskMsg(&Message);
}
msg_get_ext_queue_info(mmi_ext_qid, &queue_node_number);
count——;
}
}
}
}
9。4 MTK 定時器的使用
在使用MTK 定時器前,我們先分析一下定時器的工作機制。
9。4。1
MTK
定時器基本分析
接下來,我從下面幾個要點對MTK定時器進行分析。
1。 資料結構
(1)。 stack_timer_struct
定時器型別的資訊結構( 其主要作用似乎是用以裝載待發送的定時器訊息資料 )
(2)。 TIMERTABLE
定時器佇列節點結構( 其由主要元素mmi_frm_timer_type結構及連結串列指標兩個元素組成 )
(3)。 event_scheduler
佇列資訊結構
(4)。 mmi_frm_timer_type
定時器資訊結構
2。 L4定時器初始化
(1)。 步驟
。。——> 建立MMI Task ——> 設定MMI Task初始化函式 ——> 在該函式中呼叫 L4InitTimer
(2)。 作用
初始化定時器佇列並設定基本定時器1,2
3。 傳送定時器訊息
(1)。 步驟
StartTimer -> L4StartTimer
(2)。 兩種型別的定時器
MTK中有兩種型別的定時器
a。 NO_ALIGNMENT
非佇列式的,即要求立即執行的定時器,時間到了就自動被reset。
b。 ALIGNMENT
佇列式的, 即可以透過佇列操作,有一定的延時容忍的定時器 。
c。 除了觸控式螢幕和手寫,其他情況下的定時器一般都是佇列式的。
(3)。 L4StartTimer的作用
判斷將要傳送的定時器ID,根據是否是佇列型別傳遞給不同的佇列結構(event_sheduler1/event_sheduler2) ;
(4)。 TimerExpiry
這是作為引數傳遞給L4StartTimer的回撥函式,由於MTK做了一定的封裝,因此其內部具體回撥觸發過程
無法得知,但根據猜測,應該是在定時時間一到,以中斷的方式發出訊息(MSG_ID_TIMER_EXPIRY),並將其寫到
MMI的迴圈佇列。
該函式可能是在L4CallBackTimer中呼叫的,L4CallBackTimer的作用如下:
a。 重置當前定時器資訊結構(mmi_frm_timer_type) ;
b。 執行定時器到點後的執行函式(TimerExpiry) ;
c。 講Timer訊息寫到MMI迴圈佇列中 。
4。 與StartTimer對應的StopTimer
(1)。 具體實現透過呼叫L4StopTimer操作。
(2)。 作用: 找出指定要停止的定時器ID在佇列中的位置,然後使用evshed_cancel_event將指定定時器節點從佇列中刪除。
5。 定時器訊息的處理
(1)。 步驟
。。-> 建立MMI Task -> 設定MMI Task入口函式 -> 呼叫 EvshedMMITimerHandler
(2)。 evshed_timer_handler( ) -> 處理具體的定時器事件
9。4。2 MTK定時器訊息處理機制
一、基本概念及Neclus核心定時器初始化
expires
:
指定定時器到期的時間,這個時間被表示成自系統啟動以來的時鐘滴答計數(也即時鐘節拍數)。當一個定時器的expires值小於或等於jiffies變數時,我們就說這個定時器已經超時或到期了。在初始化一個定時器後,通常把它的expires域設定成當前expires變數的當前值加上某個時間間隔值(以時鐘滴答次數計。
typedef struct timertable
{ /* store the timer_id。 MSB(Most Significant Bit) is align_timer_mask */
U16 timer_id[SIMULTANEOUS_TIMER_NUM];
/* store the event_id that returns from evshed_set_event() */
eventid event_id[SIMULTANEOUS_TIMER_NUM];
/* store the timer_expiry_func */
oslTimerFuncPtr callback_func[SIMULTANEOUS_TIMER_NUM];
/* point to the next TIMERTABLE data */
struct timertable *next;
} TIMERTABLE;
typedef lcd_dll_node *eventid;
struct lcd_dll_node {
void *data;
lcd_dll_node *prev;
lcd_dll_node *next;
};
(1)timer_id:定時器id最多同時12個。
(2)雙向連結串列元素event_id:用來將多個定時器排程動作連線成一條雙向迴圈佇列。
(3)函式指標callback_func:指向一個可執行函式。當定時器到期時,核心就執行function所指定的函式,產生expires 訊息。
//L4 init the timer
/*****************************************************************************
* FUNCTION
* L4InitTimer
* DESCRIPTION
* This function is to init the timer while task create。
*
* PARAMETERS
* a IN void
* RETURNS
* VOID。
* GLOBALS AFFECTED
* external_global
*****************************************************************************/
void L4InitTimer(void)
{
/*————————————————————————————————*/
/* Local Variables */
/*————————————————————————————————*/
TIMERTABLE *p;
TIMERTABLE *pp;
/*————————————————————————————————*/
/* Code Body */
/*————————————————————————————————*/
/* Try to free TIMERTABLE list exclude g_timer_table */
p = g_timer_table。next;
pp = NULL;
do
{
if (p != NULL)
{
pp = p->next;
OslMfree(p);
}
p = pp;
} while (p != NULL);
/* reset g_timer_talbe */
memset(&g_timer_table, 0, sizeof(TIMERTABLE));
g_timer_table_size = SIMULTANEOUS_TIMER_NUM;
g_timer_table_used = 0;
/* Initiate the clock time callback function。 */
get_clocktime_callback_func = NULL;
set_clocktime_callback_func = NULL;
/* Initate the no alignment stack timer */
stack_init_timer (&base_timer1, ”MMI_Base_Timer1“, MOD_MMI);
/* Create a no alignment timer schedule */
event_scheduler1_ptr = new_evshed(&base_timer1,
L4StartBaseTimer, L4StopBaseTimer,
0 , kal_evshed_get_mem, kal_evshed_free_mem, 0);
/* Initate the alignment stack timer */
stack_init_timer (&base_timer2, ”MMI_Base_Timer2“, MOD_MMI);
/* Create an alignment timer schedule */
event_scheduler2_ptr = new_evshed(&base_timer2,
L4StartBaseTimer, L4StopBaseTimer,
0, kal_evshed_get_mem, kal_evshed_free_mem, 255);
}
typedef struct stack_timer_struct_t {
module_type dest_mod_id;
kal_timerid kal_timer_id;
kal_uint16 timer_indx;
stack_timer_status_type timer_status;
kal_uint8 invalid_time_out_count;
} stack_timer_struct;
/*************************************************************************
* Exported Function Prototypes
*************************************************************************/
/*
* Important:
* Current implementation max_delay_ticks _disibledevent=”text-indent: 24pt; line-height: 150%“>
二、Linux動態核心定時器機制的原理
Linux是怎樣為其核心定時器機制提供動態擴充套件能力的呢?其關鍵就在於“定時器向量”的概念。所謂“定時器向量”就是指這樣一條雙向迴圈定時器佇列(對列中的每一個元素都是一個timer_list結構):對列中的所有定時器都在同一個時刻到期,也即對列中的每一個timer_list結構都具有相同的expires值。顯然,可以用一個timer_list結構型別的指標來表示一個定時器向量。
顯然,定時器expires成員的值與jiffies變數的差值決定了一個定時器將在多長時間後到期。在32位系統中,這個時間差值的最大值應該是0xffffffff。因此如果是基於“定時器向量”基本定義,核心將至少要維護0xffffffff個timer_list結構型別的指標,這顯然是不現實的。
另一方面,從核心本身這個角度看,它所關心的定時器顯然不是那些已經過期而被執行過的定時器(這些定時器完全可以被丟棄),也不是那些要經過很長時間才會到期的定時器,而是那些當前已經到期或者馬上就要到期的定時器(注意!時間間隔是以滴答次數為計數單位的)。
基於上述考慮,並假定一個定時器要經過interval個時鐘滴答後才到期(interval=expires-jiffies),則Linux採用了下列思想來實現其動態核心定時器機制:對於那些0≤interval≤255的定時器,Linux嚴格按照定時器向量的基本語義來組織這些定時器,也即Linux核心最關心那些在接下來的255個時鐘節拍內就要到期的定時器,因此將它們按照各自不同的expires值組織成256個定時器向量。而對於那些256≤interval≤0xffffffff的定時器,由於他們離到期還有一段時間,因此核心並不關心他們,而是將它們以一種擴充套件的定時器向量語義(或稱為“鬆散的定時器向量語義”)進行組織。所謂“鬆散的定時器向量語義”就是指:各定時器的expires值可以互不相同的一個定時器佇列。
三、MTK Linux動態核心定時器機制的原理
MTK核心只需要同時維護12個型別的定時器,而且限定了其interval≤255,列為最關心處理的定時器,那麼我們只需要為每個型別的定時器分配不同的expires值,因為它們按照不同的expires值組織成定時器向量。MTK核心裡還分為兩種不同型別的定時器,一種允許delay的,一種不允許,其他這些在初始化排程函式指標時就已經指定好了,核心對兩者的處理是一樣的。一般使用的是允許delay的定時器,防止一些突發性錯誤。
四、MTK具體程式碼級分析
MMITask。c中:
void MMI_task(oslEntryType * entry_param)
{
……
switch (Message。msg_id)
{
case MSG_ID_TIMER_EXPIRY:
{
kal_uint16 msg_len;
EvshedMMITimerHandler(get_local_para_ptr(Message。oslDataPtr, &msg_len));
}
break;
……
}
}
在這裡或者我們在trace資訊的時候經常可能看到一類比較特殊的訊息,MSG_ID_TIMER_EXPIRY。(定時器時間到的訊息ID)。Expiry是滿,終結的意思。那麼這個訊息應該是我們在設定定時器的時候發給L4層的,然後再轉交到MMI或者其他的模組的,被MMITASK所獲取讀到訊息佇列裡的,然後針對其進行時間上的再分配和排程。
在MTK裡面,存在兩種性質定時時間,一種是很精確的(no alignment timer),一種允許調整校準(delay)(allow alignment timer)。而timer發出的訊息不類似其他任務或程序發出的訊息,並不是訊號量,訊號量的話一般發出來被其他程序得到(處理完後)可能就消亡了,而timer 的動作則需要重複的產生和排程,對兩者的處理不一樣。()/*
* NoteXXX:
* In evshed_timer_handler(), system would execute event regisited timeout callback function。
* Caller should reset saved event id in regisited timeout callback function,
* or cause potential bug to cancel wrong timer event。
*/
見以下程式碼:
(MMI Timer Event Scheduler)
//MMI定時器動作排程函式
void EvshedMMITimerHandler(void *dataPtr)
{
/*————————————————————————————————*/
/* Local Variables */
/*————————————————————————————————*/
stack_timer_struct* stack_timer_ptr;
stack_timer_ptr = (stack_timer_struct*)dataPtr;
/*————————————————————————————————*/
/* Code Body */
/*————————————————————————————————*/
if (stack_timer_ptr == &base_timer1)
{
if (stack_is_time_out_valid(&base_timer1))
{
evshed_timer_handler(event_scheduler1_ptr);
}
stack_process_time_out(&base_timer1);
}
else if (stack_timer_ptr == &base_timer2)
{
if (stack_is_time_out_valid(&base_timer2))
{
evshed_timer_handler(event_scheduler2_ptr);
}
stack_process_time_out(&base_timer2);
}
}
上面講的是如何對訊息進行處理,那麼設定定時器後這訊息是如何發出去呢?下面對該過程進行講解,具體程式碼如下:
void StartTimer(U16 timerid, U32 delay, FuncPtr funcPtr)
{
StartMyTimer(timerid, delay, (oslTimerFuncPtr)funcPtr);
}
再往下:
#define StartMyTimer(nTimerId,nTimeDuration,TimerExpiryFunction)\
StartMyTimerInt(nTimerId,nTimeDuration,TimerExpiryFunction,0)
再往下:
U16 StartMyTimerInt(U16 nTimerId,U32 nTimeDuration,oslTimerFuncPtr TimerExpiry, U8 alignment)
{
if(TimerExist[nTimerId])
{
OslStopSoftTimer(nTimerId);
}
OslStartSoftTimer(nTimerId,TimerExpiry, nTimerId, nTimeDuration,alignment);
TimerExist[nTimerId]=1;
return TRUE;
}
L4 timer://將定時器訊息由L4傳送給MMI
#define OslStartSoftTimer(nTimerId,TimerExpiry, nTimerId1, nTimeDuration,alignment)\
L4StartTimer(nTimerId,TimerExpiry,(void*)nTimerId1,nTimeDuration,alignment)
void L4StartTimer(unsigned short nTimerId, oslTimerFuncPtr TimerExpiry, void * funcArg, unsigned long nTimeDurationInTicks, unsigned char alignment)
{
/*————————————————————————————————*/
/* Local Variables */
/*————————————————————————————————*/
TIMERTABLE *pTable = NULL;
U16 i = 0;
U32 temp;
/*————————————————————————————————*/
/* Code Body */
/*————————————————————————————————*/
if (TimerExpiry == NULL)
{ /* If TimerExpiry is NULL, we don’t start the timer */
MMI_ASSERT(0);
return ;
}
MMI_ASSERT(nTimerId if (L4TimerUsePreciseTick(nTimerId)) { temp = ( nTimeDurationInTicks * KAL_TICKS_10_MSEC ) / 10; if (temp == 0) { temp = KAL_TICKS_10_MSEC; } alignment = TIMER_IS_NO_ALIGNMENT;//非對列的,需要緊急處理,處理完立即reset。 } else { if (nTimeDurationInTicks == 1000) { temp = KAL_TICKS_1_SEC-4; } else { temp = (nTimeDurationInTicks / 100) * KAL_TICKS_100_MSEC; } if (temp==0) { temp = KAL_TICKS_100_MSEC; } } /* if (L4TimerUsePreciseTick(nTimerId)) */ /* * Because the handset doesn’t camp _disibledevent=”text-indent: 21。6pt“>* it influences MMI alignment timer are inaccurate。 * We change all of MMI timer to non-alignment timer in flight mode。 */ if ( mmi_bootup_get_active_flight_mode() == 1 ) { alignment = TIMER_IS_NO_ALIGNMENT; } MMI_TRACE((MMI_TRACE_G1_FRM, MI_FRM_INFO_L4DRV_STARTTIMER_HDLR, nTimerId, TimerExpiry, temp, alignment)); pTable = &g_timer_table; if (g_timer_table_used >= g_timer_table_size) { /* * TIMERTABLE list doesn’t have enough space, allocate the memory * * If we need to allocate the memeory, it means that MMI may have * such many timers run simultaneously。 We won’t free the memory * after we allocate more memory in TIMERTABLE list。 */ do { if (pTable->next == NULL) { pTable->next = OslMalloc(sizeof(TIMERTABLE)); memset(pTable->next, 0, sizeof(TIMERTABLE)); g_timer_table_size += SIMULTANEOUS_TIMER_NUM; pTable = pTable->next; i = 0; break; } pTable = pTable-> next; } while (pTable != NULL); } else { /* find the empty record in g_timer_table list */ i = 0; do { if (pTable->event_id[i] == NULL) { /* find the empty space */ break; } i++; if (i >= SIMULTANEOUS_TIMER_NUM ) { pTable = pTable->next; i = 0; } } while (pTable != NULL); if (pTable == NULL) { /* Can’t find the empty space in TIMERTABLE list, assert!!! */ MMI_ASSERT(0); } } /* if (g_timer_table_used >= g_timer_table_size) */ /* * already find the empty record, and then start timer * event_sheduler1 = NO alignment scherulder * event_sheduler2 = alignment scherulder (low power) */ if (alignment == TIMER_IS_NO_ALIGNMENT) { /* MSB(Most Significant Bit) is align_timer_mask */ pTable->timer_id[i] = nTimerId | NO_ALIGNMENT_TIMER_MASK; pTable->event_id[i] = evshed_set_event (event_scheduler1_ptr, (kal_timer_func_ptr)L4CallBackTimer, (void *)nTimerId, temp); pTable->callback_func[i] = TimerExpiry; g_timer_table_used ++; } else if (alignment == TIMER_IS_ALIGNMENT) { /* MSB(Most Significant Bit) is align_timer_mask */ pTable->timer_id[i] = nTimerId | ALIGNMENT_TIMER_MASK; pTable->event_id[i] = evshed_set_event (event_scheduler2_ptr, (kal_timer_func_ptr)L4CallBackTimer, (void *)nTimerId,temp ); pTable->callback_func[i] = TimerExpiry; g_timer_table_used ++; } } TimerExpiry 型別為void (*oslTimerFuncPtr)(void*),根據我個人猜測訊息應該在回撥函式TimerExpiry裡(定時時間一到,而且很有可能是中斷的方式)發出MSG_ID_TIMER_EXPIRY。 Evshed_set_event()這個函式有點類似SetProtocolevent()設定協議動作函式,類似就象設定非同步串列埠時只要相應訊息一到就會跳到相應的處理函數里處理。在定時器timer裡面,用的是L4CallBackTimer這個函式,在這個函數里面: // 將使用過的TimerID和EventID給復位(reset)。 *****************************************************************************/ void L4CallBackTimer(void *p) { U32 nTimerId = (U32)p; TIMERTABLE *pTable = &g_timer_table; U32 i = 0; oslTimerFuncPtr pTimerExpiry = NULL; MMI_ASSERT(nTimerId < MAX_TIMERS); /* find the nTimerId in TIMERTABLE list */ do { /* MSB(Most Significant Bit) of timer_id[i] is align_timer_mask */ if ((pTable->timer_id[i] & (~NO_ALIGNMENT_TIMER_MASK)) == (U16)nTimerId) { /* find out nTimerId */ if (pTable->callback_func[i] != NULL) { pTimerExpiry = pTable->callback_func[i]; MMI_TRACE((MMI_TRACE_G1_FRM, MMI_FRM_INFO_L4DRV_CBTIMER_HDLR, nTimerId, pTimerExpiry)); g_timer_table_used ——; pTable->event_id[i] = 0; pTable->timer_id[i] = 0; pTable->callback_func[i] = NULL; /* * we process g_timer_table_used, event_id and timer_id … first * because the user may call stoptimer() in the timer_expiry_func */ pTimerExpiry(p);//此定時nTimerId已經執行完 } break; } i++; if (i >= SIMULTANEOUS_TIMER_NUM ) { pTable = pTable->next; i = 0; } } while (pTable != NULL); /* can‘t find nTimerId, do nothing */ mmi_frm_fetch_msg_from_extQ_to_circularQ(); /* * Because we modify MMI process protocol events and key events mechanism, * we don’t need to process key events here。 */ } mmi_frm_fetch_msg_from_extQ_to_circularQ();將對所有由MOD_TIMER(receive the message from MMI queue and put in circular queue)傳送給MMI的訊息放進迴圈佇列……,而這個函式在MMI_task()函式while(1)迴圈一開始就用於獲取內部佇列訊息到迴圈佇列中(fetch the message from external queue and put in the circular queue)。 另外我們可以在Stoptimer()裡面存在evshed_cancel_event()函式,這是與Starttimer()函式相對應。 /* * Important * System will allocate memory for event id, and return to caller。 * If caller need to save event id, please be careful to reset when * cancel the event or the event expired。 */ extern eventid evshed_set_event(event_scheduler *es, kal_timer_func_ptr event_hf, void *event_hf_param,kal_uint32 elapse_time); /* * Important * System would reset *eid to NULL before return, however, caller * should pay attention to saved event id。 */ extern kal_int32 evshed_cancel_event(event_scheduler *es, eventid *eid); 二、總結分析 定時器訊息一般分為兩類: (1)佇列型,這類訊息一旦有事件處理它就會一直執行下去,就會出現超時現象(產生MSG_ID_TIMER_EXPIRY),接下去儲存timer id和event id 繼續再執行下去。直到定時器被停止或事件處理完,才會被reset。 (2)非佇列型:這類訊息一發出去,而且一般時間很緊急,時間到了就自動被reset。 void L4CallBackTimer(void *p)函式。 基於這個思路,作為MTK平臺下一般是對列型定時器,佇列定時器一般是允許調整校準的時間(根據EXPIRYS值來調整),一旦定時器開啟後,一直在執行過程中,實際時間將超時(不超時可能會導致某些問題),系統計數時間到將產生MSG_ID_TIMER_EXPIRY訊息,然後In evshed_timer_handler()函式處理,註冊一個超時回撥函式並復位定時器/儲存event id。這樣這個定時器一旦開啟如何不被Stop將一直執行下去。 * NoteXXX: * In evshed_timer_handler(), system would execute event regisited timeout callback function。 * Caller should reset saved event id in regisited timeout callback function, * or cause potential bug to cancel wrong timer event。 */ 四、深入總結 關於佇列與非佇列型以及寬鬆佇列型timer可以總結如下: 1。 佇列型:相當於執行正常的定時器,只允許稍微有偏差影響不大,比如延時500ms,實際超個1ms是沒有問題。 2。 非佇列型:一般是立即執行,時間很短,MTK上為10ms,這時間相差個1ms問題很大。 3。 寬鬆佇列:只存在linux(軟實時核心),延時1分鐘,相差個10ms問題不大。 作業系統一般會對於這些定時器timer的緊急程度,分配不同的expires值進行調整的,以達到目的。寬鬆佇列和佇列型一般放在佇列中進行處理,有一個等待的過程,而非對列型處理可能要緊急。 9。4。3 MTK定時器使用舉例一 下面以一個具體的例子來說明MTK定時器的使用方法,理解了這個例子就能基本上掌握定時器的使用方法了,我們一步步來介紹。 第一步:定義自己的Timer 定義一個下面的結構體。 typedef struct MyTimerItem { const int index;//多個timer時使用,在這個例子裡是沒有用的 int delay; //1000 相當於1秒,這是定時器定時工作的週期 unsigned char used;//是否被使用, FuncPtr timerFunc;//執行的函式 int isCircle;//timer是否迴圈 }MyTimer; 第二步:修改檔案TimerEvents。h 在檔案TimerEvents。h裡有一個enum,叫做MMI_TIMER_IDS,它存放了所有timer的索引,如果想要使用一個自己的timer,就要在這個enum時加上自己的一項。一般加在後面,即MAX_TIMERS的前一個。 typedef enum { // Start for for Keypad based timer。 KEY_TIMER_ID_NONE = 0, KEY_TIMER_ID0 = 1, KEY_TIMER_ID1, KEY_TIMER_ID2, KEY_TIMER_ID3, …… 中間忽略無數個 MY_TEMER_BASE_ID, MY_TEMER_END_ID = My_TEMER_BASE_ID + 5, MAX_TIMERS } MMI_TIMER_IDS; */ my_timer_baseid = MY_TEMER_BASE_ID; 第三步:建立檔案Events。c並在其中構建函式 //函式StartTimer和StopTimer在檔案Events。c裡實現。 static MyTimer mytimer = { 0, 1000, 0, MyUpdateTimerHanler, 1 }; //注意上面大括弧中的“MyUpdateTimerHanler”,這是定時器工作時要呼叫的函式 //timer處理函式 void MyUpdateTimerHanler() { MyTimer * t = &mytimer; //寫下你要的操作 // 使定時器,繼續工作, MTK 的定時器執行一次就會關閉 if(t->isCircle) StartTimer((UINT16)(my_timer_baseid), t->delay, t->timerFunc); } //開始計時 int MyUpdateTimerStart() { MyTimer * t = &mytimer; if(!t) return 0; StartTimer((UINT16)(my_timer_baseid), t->delay, t->timerFunc); return 0; } //關掉timer int UpdateTimerStop() { MyTimer * t = &mytimer; if(!t) return 0; StopTimer((UINT16)(my_timer_baseid)); return 0; } 9。4。4 定時器使用案例二: 本節例子原始碼請見原始碼光碟“第9章的例子”資料夾下“9。4。4節定時器例子”資料夾。 在第一次開發過程,從簡單的處理,這種方式邏輯簡單,這也是很多人一拿來到專案就想到的方式。這種定時器是根據傳送資料後只有兩種狀態。 1。接收到資料 2。資料超時 這種定時器方式的頻率和傳送資料次有關,例如:傳送N次資料,就startTime和stopTimeN次。 設計過程圖如下圖9。1所示: 圖9。1 資料傳送和接收流程圖 模擬程式碼如下: //模擬程式碼 int g_timeout = 3; void SendData() { StartTimer(SCM_TIME_ID, TimeOut, SCM_SendDataTimeOut); //。。。 } void Handle_RecvData() { g_timeout ——; StopTimer(SCM_TIME_ID); //接收到資料後,再發送下幀資料 SendData(); } void SCM_SendDataTimeOut() { if(g_timeout ==0) Exit_Print();//超時次數達到3次,退出列印 g_timeout ——; //重發資料 SendData(); } //模擬程式碼 int g_timeout = 3; void SendData() { StartTimer(SCM_TIME_ID, TimeOut, SCM_SendDataTimeOut); //。。。 } void Handle_RecvData() { g_timeout ——; StopTimer(SCM_TIME_ID); //接收到資料後,再發送下幀資料 SendData(); } void SCM_SendDataTimeOut() { if(g_timeout ==0) Exit_Print();//超時次數達到3次,退出列印 g_timeout ——; //重發資料 SendData(); } 方式2如下圖9。2所示: 圖9。2 資料傳輸方式2流程圖 這種定時器的設計是大眾使用比較多的方式透過這種方式可以減少開啟和關閉定時器的次數,這種定時器打關的次數與設定的超時時間有關。 int g_timeout = 3; int TimeOut = 1000; void TaskStart() { g_timeout = 3; StartTimer(SCM_TIME_ID,TimeOut,SCM_SendDataTimeOut); } void SendData() { //這裡只做傳送資料,就沒有開啟定時器的操作 //。。。。 } void SCM_Handle_RecvData() { g_timeout = 3; //接收到資料後,再發送下幀資料 SendData(); } void SCM_SendDataTimeOut() { if(g_timeout==0) Exit_Print();//超時計數為0,退出列印 g_timeout——; StartTimer(SCM_TIME_ID,TimeOut,SCM_SendDataTimeOut); } 結語: 學習本章的內容,關鍵是理解任務建立的流程,任務部分要關注訊息在其中所起的作用,同時定時器在工作中使用的也很頻繁,也要重點掌握。