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
队列式的, 即可以通过队列操作,有一定的延时容忍的定时器 . y
其基本执行流程: 执行定时器 --> 超时? --> 保存timer id,event id -- timer stop || no event ?----> END ;
| Y N|
| |
----------------------------------------------------------
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( ) -> 处理具体的定时器事件
6. 小结
简单分析MTK定时器消息事件,由于只是简单的分析,谬误定然甚多,忘包涵包涵并不吝指正.
-
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%" align="left">
二、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<MAX_TIMERS);
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()函数相对应。
这样一来对TIMER 消息的处理流程就清楚了许多。当然整个理解过程中可能还有一些缺陷,希望大家指出。
/*
* 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可以总结如下:
队列型:相当于执行正常的定时器,只允许稍微有偏差影响不大,比如延时500ms,实际超个1ms是没有问题。
非队列型:一般是立即执行,时间很短,MTK上为10ms,这时间相差个1ms问题很大。
宽松队列:只存在linux(软实时内核),延时1分钟,相差个10ms问题不大。
操作系统一般会对于这些定时器timer的紧急程度,分配不同的expires值进行调整的,以达到目的。宽松队列和队列型一般放在队列中进行处理,有一个等待的过程,而非对列型处理可能要紧急。