MTK Task

来源:互联网 发布:狼雨seo查询工具 编辑:程序博客网 时间:2024/05/16 08:29

转自:http://blog.csdn.net/yanwuxufeng/article/details/5777849

 

MTK 基本执行单元是task从操作系统的角度来理解,task有些像线程而不是进程,进程之间的地址空间是相互隔离的,说白点就是进程之间的全局变量是不相互干扰的,而线程之间则是用同一个地址空间,MTK的所有task之间的地址空间也是共同的,也就是在MTK编程里,定义了一个全局变量,那么在任何一个task里面都能引用,(这里举个例子,在实际编程过程中最好不要用全局变量,实在没有办法避开,那么全局变量也要分模块化,进行封装,扯远了)。所以说,MTKtask更像线程,MTK用的是实时操作系统nucleus,是非抢占式操作系统,也就是当高优先级的task在运行时,低优先级的task是得不到运行时间的,除非等高优先级的task因为种种原因挂起。

MTK 还有一个跟task相关的概念module,它跟task之间的关系是:一个task可以对应多个moduletask主要表示是一个执行单元,module主要是用于传递消息,在MTK中,消息传递是以module为单位,如src_mod – > des_mod,而不是以task为单位。

虽然MTK手机,是feature phone(功能机),不像symbian 6 那样可以同时运行多个应用。但是MTK还是有许多task组成。平时MTK的后台播放MP3就是一由一个task完成的。具体以后分析。现在来看看MTK最主要的taskMMI taskMTK的应用程序都是在该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上。

(用挂载这个词,应该就比较好理解了,taskMTK执行的基本单位,所以一个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 stackinternal 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 发送消息是modmod,因为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 的,这里就不具体再说了。

原创粉丝点击