转自:http://blog.youkuaiyun.com/yanwuxufeng/article/details/5777849
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。
先来看创建MMI task的函数
kal_bool mmi_create(comptask_handler_struct **handle)
{
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 */
};
*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运行的核心。
为了简单,删除了大部分宏控制程序:
voidMMI_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();
//初始化主副SIM卡
MMI_MTPNP_master_init();
MMI_MTPNP_slave_init();
//进入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(即MOD_WAP和MOD_MMI)
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;
//开机消息,具体分析,见后文
caseMSG_ID_MMI_EQ_POWER_ON_IND:
{
gdi_init(); // GDI初始化
switch (p->poweron_mode) //判断power on的模式
{
casePOWER_ON_KEYPAD://以后重点阐述
case POWER_ON_PRECHARGE:
case POWER_ON_CHARGER_IN:
case POWER_ON_ALARM:
case POWER_ON_EXCEPTION:
OslMemoryStart(MMI_TRUE);
SetAbnormalReset();
InitializeAll();
OslDumpDataInFile();
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--;
}
}
}
}
MMI task 内容比较多,删除了一些代码,留下主要的骨干。
总体来看,1.把外部消息放入内部消息队列
2.处理各种消息,开机消息,按键消息和event机制注册的各种其他消息
上面大概描述了下 MMI task的工作方式:从外部队列获取消息放入内部消息队列,内部消息队列根据消息类型所注册的回调函数,进行调用(event机制,这个又是MMI framework的主要部分之一)。
在MTK上,用户(开发人员)可以根据需要,创建task。
创建一个task分为 4步:
1 增加一个task index到 custom_task_indx_type
2 增加一个mod index到 custom_module_type
3 把mod关联到相应的 task上,因为一个task可以对应多个mod,所以需要把mod挂载到 task上。
(用挂载这个词,应该就比较好理解了,task是MTK执行的基本单位,所以一个mod要能独立运行,就要挂载到某个task上,为什么不一个mod一个task呢,我想task越多,多系统效率影响就越大。那么就可以考虑互斥的mod挂载到一个task上,反正互斥的,不会同时需要运行,就像音乐,视频,照相机一样,不会同时运行)
4 创建task基本信息到 custom_comp_config_tbl
下面来具体看一个例子。
1 添加 task index
typedef enum {
INDX_CUSTOM1 = RPS_CUSTOM_TASKS_BEGIN,
INDX_CUSTOM2,
#ifdef TASK_CREATE_TEST
INDX_TASK_TEST,
#endif
RPS_CUSTOM_TASKS_END
} custom_task_indx_type;
我们增加了一个 task index INDX_TASK_TEST
2 添加一个 mod index
typedef enum {
MOD_CUSTOM1 = MOD_CUSTOM_BEGIN,
MOD_CUSTOM2,
#ifdef TASK_CREATE_TEST
MOD_TASK_TEST,
#endif
MOD_CUSTOM_END
} custom_module_type;
我们增加了一个mod indexMOD_TASK_TEST
3 挂载mod到 task上
custom_task_indx_type custom_mod_task_g[ MAX_CUSTOM_MODS ] =
{
INDX_CUSTOM1, /* MOD_CUSTOM1 */
INDX_CUSTOM2, /* MOD_CUSTOM2 */
#ifdef TASK_CREATE_TEST
INDX_TASK_TEST,
#endif
INDX_NIL /* Please end with INDX_NIL element */
};
这样就把 MOD_TASK_TEST挂载到 INDX_TASK_TEST上面了,这里的映射关系是通过index来控制的,也就是说要得到MOD_TASK_TEST对应的task index,只要这样task index = custom_mod_task_g[MOD_TASK_TEST];,所以创建过程中,顺序一定要对应好,不然容易出错。
4 创建task信息
constcomptask_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
},
#ifdef TASK_CREATE_TEST
/* INDX_TASK_TEST */
{"TAST_TEST", "TASK_TEST Q", 212, 1024, 10, 0,
task_test_create, KAL_FALSE
},
#endif
};
这样就创建好了task的信息,这里说task需要的信息
typedef struct {
kal_char *comp_name_ptr; //task的name
kal_char *comp_qname_ptr;//外部队列name
kal_uint32 comp_priority; //优先级
kal_uint16 comp_stack_size;//stack大小
kal_uint8 comp_ext_qsize; //外部队列大小
kal_uint8 comp_int_qsize; //内部队列大小
kal_create_func_ptr comp_create_func;//task创建函数
kal_bool comp_internal_ram_stack;//是否是internal_ram_stack
} comptask_info_struct;
task 的优先级是数值越大,优先级越低。由于是MTK用的是实时操作系统,高优先级的task只要需要,就会先运行,一直运行,所以task的优先级定义时需要考虑清楚。comp_internal_ram_stack表示是否使用internal ram stack,internal ram相对速度要快,但是数量很有限,一般自己创建的不要去使用,容易引起问题。
下面需要介绍创建task 信息的函数。
kal_bool task_test_create(comptask_handler_struct **handle)
{
staticconst comptask_handler_struct task_test_handler_info =
{
task_test_main, /* task entry function */
NULL, /* task initialization function */
NULL, /* task configuration function */
NULL, /* task reset handler */
NULL, /* task termination handler */
};
*handle = (comptask_handler_struct *)&task_test_handler_info;
return KAL_TRUE;
}
这个函数的结构是不是很眼熟,对,就是MTK task小结 2 介绍MMI task创建函数 mmi_create,创建函数的格式都是一样的,具体结构体的说明就看MTK task 小结2 ,相应的函数可以补充,简单期间就有一个入口函数,不过一般都够了。
voidtask_test_main(task_entry_struct * task_entry_ptr)
{
//消息实体
ilm_struct current_ilm;
//消息队列id
oslMsgqid qid = task_info_g[task_entry_ptr->task_indx].task_ext_qid;
//初始化一些信息,这里只会被执行一次
tast_test_init()
//进入消息循环
while ( 1 ) {
//接受消息,如果没有消息,挂起该 task
receive_msg_ext_q(qid , ¤t_ilm);
//根据消息 id 处理各种消息
switch (current_ilm.msg_id) {
case MSG_ID_TASK_TEST_XX:
break;
default:
break;
}
free_ilm(¤t_ilm);
}
}
这样,一个task就建立完成了。如果想给自己的task建立一个强大的 timer功能,那么就可以根据MTK timer小结 3里介绍的那样,建立一个event scheduler timer,注意,这个stack timer里的mod要写成 MOD_TASK_TEST而不是 MOD_MMI,这样 MSG_ID_TIMER_EXPIRY就会发送到这个task里面,只要进行相应的处理就ok了。
task 之间的交互是通过消息来传递的,当然也可以通过全局变量,共享内存等等(这里不是linux那样的共享内存,因为MTK里面,整个系统的地址空间只有一个,那么一个task里的内存,可以被另一个task访问,只要知道地址,没有限制,具体可以看MTK内存管理简单总结)。只要自己控制好(互斥问题),还是比较灵活的。
比如想要在这个task里面处理按键事件,比较繁琐,先要在MMI task里面接受按键事件,然后根据自己定义的消息,把这个按键事件发送到这个task里面,这个task里面根据相应的消息处理函数,分发处理。
一般来说,task做一些后台处理比较方便(跟io有关的),如果想实现并行(linux时间片那样系统自动切换task),那是不现实,因为这个不是抢占式系统,优先级高task的除非自己挂起,否则会一直运行。虽然说如果两个task如果优先级一样,或进行轮询,但是我自己试了,没有实现,不知道是否是当时没有用对。如果有实现的童鞋一定要告诉我。
关于task之间的消息传递,第一是要添加一个消息id,在custom_sap.h里面,
/* Add customization message id here */
MSG_ID_CUSTOM1_CUSTOM2 = CUSTOM_MSG_CODE_BEGIN,
MSG_ID_CUSTOM2_CUSTOM1,
MSG_ID_MSG_TEST, //添加了一个消息id
接下来是发送
MTK 发送消息是mod到mod,因为mod是挂载到task上,那么最终还是发送到task上。
MTK 发送消息是有一套固定的接口,对消息实体也有固定要求。
消息的主体是 ilm_struct结构:
typedef struct ilm_struct {
module_type src_mod_id; //源 mod id
module_type dest_mod_id; //目的 mod id
sap_type sap_id; // Service Access Pointer Identifier不清楚干什么用的
msg_type msg_id; //消息 id就是上面创建的消息id
local_para_struct *local_para_ptr; //消息信息载体 1
peer_buff_struct *peer_buff_ptr; //消息信息载体 2
} ilm_struct;
这个消息结构不是随便malloc出来的,是由专门的函数allocate_ilm来创建,这个函数会从一个pool去申请,这样做可以方便控制,同时也可以减少内存分配释放操作,碎片的产生。
创建完毕后,可以填完剩下的信息,注意 allocate_ilm参数是当前要发送消息的mod id,不要搞错了。
比如:
ilm_struct *ilm_ptr = allocate_ilm(MOD_TASK_TEST);//创建一个消息结构
ilm_ptr->src_mod_id = MOD_TASK_TEST; //设定源mod id
ilm_ptr->sap_id = 0;
ilm_ptr->dest_mod_id = MOD_MMI; //设定目的mod id
ilm_ptr->msg_id = (msg_type) MSG_ID_MSG_TEST; //设定消息id
接着需要一个消息实体,也就是搭载具体消息信息的结构体
typedef struct local_para_struct {
LOCAL_PARA_HDR
} local_para_struct;
其实这个结构体只有一个头信息
#define LOCAL_PARA_HDR /
kal_uint8 ref_count; /
kal_uint16 msg_len;
分别用来记录该信息的引用次数以及长度。
在实际开发中,其实是扩展这个消息,头结构保持一致就可以(有点像C++的父类子类的意思);
比如:
typedef struct
{
LOCAL_PARA_HDR
char msg_str[10];
int msg_int;
}msg_test_struct;
当然这个结构体内存也不是随便分配的,有专门的函数construct_local_para,貌似有情况,不要混淆C++的父类子类关系
local_para_struct *local_para = construct_local_para(sizeof(msg_test_strct), TD_CTRL);//第二个参数现在不用。
strcpy(local_para->msg_str,”msg”);
local_para->msg_int = 12;
Ok现在可以发送这个消息了
ilm_ptr->local_para_ptr = local_para;
msg_send_ext_queue(ilm_ptr) ;
说明:发送消息函数是往task的外部消息队列里发送,这样task就可以通过 receive_msg_ext_q获得,
还有一对函数是处理内部消息队列的 msg_send_int_queue和 receive_msg_int_q。这两者的不同在于,当调用接收外部消息函数时,如果没有外部消息(外部消息队列为空),那么task将挂起,直到有消息到达,而内部消息队列则不会挂起,只是返回fasle。
今天忽然想到一个配置文件,跟task有些关系,在实际过程中可能会用到。
文件是custom_config.c,里面有些配置函数,用来配置系统信息,其中最常用的是改变MMI task stack的大小。
当集成一些库的时候,用的stack可能比较大,这时候就需要修改task的大小,函数custom_config_task_stack_size
kal_uint32 custom_config_task_stack_size(task_indx_type task_indx)
{
switch (task_indx) {
case INDX_MMI: // MMI task 的index
return (32 * 1024); //stack 的大小,这里32k
default:
return 0; //返回 0,表示使用原有的大小
}
return 0;
}
这里比较方便的就改变了 MMI task stack的大小,不过有时候,这么改,会开不了机,原因是系统分不到那么多内存。
修改也比较方便,修改 GLOBAL_MEM_SIZE大小就可以,只是有太多的宏包含,要找到哪个是哪个,还是比较头疼的。
其他的也没有 custom_config_ctrl_buff_info 这个在MTK内存管理小结说到过,是配置 control buffer 的,这里就不具体再说了。