赞
踩
[恒玄BES - 2700] 线程创建与app_thread
在讲线程创建的之前,先简述一下ROTS中的线程。线程(Thread)通常被定义为一个功能清晰的小程序或任务,它是RTOS调度的基本单元。线程具有自己的程序计数器和栈内存,能够独立执行特定的任务或功能。RTOS通过管理这些线程,实现多任务并发执行,从而提高系统的实时性和效率。
我们来看下重点:
1.线程是基于RTOS的任务调度系统,将时间片进行切割,每个任务占用切割后的时间片,实现任务的快速切换,达到看似同时运行的效果。
2.每个任务之间相互独立,都有各自的栈空间,利用消息队列等机制进行线程之间的通信。
3.一个线程中可包含多个消息发送与接收模块,但接收模块多需阻塞等待,所以线程中一般不过出现多个接收模块。
BES的SDK是基于RTX5 RTOS(实时操作系统),但是经过了CMSIS_RTOS风格的API进行抽象,所以我们可以不用关注底层的实现。下面我们看下如何进行线程的创建:
我们这边以app_thread的线程创建举例,暂时不用关注app_thread线程的具体作用,后续会对app_thread线程进行分析。
a) 线程定义
static void app_thread(void const *argument);
在创建线程的时候,先声明一个线程loop函数,后续可进行函数的定义。下面的线程的定义:
osThreadDef(app_thread, osPriorityHigh, 1, APP_THREAD_STACK_SIZE, "app_thread");
我们来看看osThreadDef中做了啥:
#define osThreadDef(name, priority, instances, stacksz, task_name) \
uint64_t os_thread_def_stack_##name [(8*((stacksz+7)/8)) / sizeof(uint64_t)]; \
const osThreadDef_t os_thread_def_##name = \
{ (name), \
{ task_name, osThreadDetached, NULL, 0U, os_thread_def_stack_##name, 8*((stacksz+7)/8), (priority), 1U, 0U } }
name:线程loop函数的名称,也就是此函数的地址
priority:线程优先级
instances:线程实例的数量。1表示创建一个静态实例,其他值表示创建动态实例。在给到作者的SDK中,在osThreadDef宏里并没有使用到此参数。
stacksz:线程栈大小
task_name:线程名称。区别第一个name参数,此名称为一个字符串,而name是线程函数地址。
我们可以看到osThreadDef就定义了两个变量:
uint64_t os_thread_def_stack_##name [(8*((stacksz+7)/8)) / sizeof(uint64_t)]
也就是定义了一个类型为uint64_t的全局整型数组,(8*((stacksz+7)/8)) 就是八位向上取整,例如我定义了stacksz为1023,8*((1023+7)/8)=1024 (此处/8会去除小数点),sizeof(uint64_t)就是8字节。说白了这就是定义了一个类型是uint64_t(即8字节)的数组,大小是设置的栈空间(向上8字节取整)/8字节。
const osThreadDef_t os_thread_def_##name = \ //就是os_thread_def_app_thread
{ (name), \
{ task_name, osThreadDetached, NULL, 0U, os_thread_def_stack_##name, 8*((stacksz+7)/8), (priority), 1U, 0U } }
typedef struct os_thread_def { //osThreadDef_t 的类似的定义
os_pthread pthread; ///< start address of thread function
osThreadAttr_t attr; ///< thread attributes
} osThreadDef_t;
定义了一个名为os_thread_def_##name的结构体变量,其中包括线程函数的地址和线程各种属性参数。
b) 线程创建
osThreadId app_thread_tid = osThreadCreate(osThread(app_thread), NULL);
#define osThread(name) &os_thread_def_##name
// Thread
osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument) {
if (thread_def == NULL) {
return (osThreadId)NULL;
}
return osThreadNew((osThreadFunc_t)thread_def->pthread, argument, &thread_def->attr);
}
可以看到使用osThreadCreate函数进行线程的创建,其中包括两个参数。第一个参数就是我们刚刚在函数定义时候的结构体变量,第二个参数是传入线程的参数,即线程loop函数的形参。最后调用osThreadNew创建线程,参数分别为线程loop函数地址,线程loop函数的参数和线程的属性参数。
快速上手
//创建一个xxx线程
// 1. 线程loop函数声明
static void xxx_thread(void const *argument);
// 2. 线程定义 osPriorityHigh为SDK自带的优先级宏定义 APP_THREAD_STACK_SIZE为用户自定义的栈空间大小
osThreadDef(xxx_thread, osPriorityHigh, 1, APP_THREAD_STACK_SIZE, "xxx_thread");
// 3. 线程loop函数定义
static void app_thread(void const *argument)
{
while(1){
//xxx;
}
}
// 4. 在初始化中进行线程创建
osThreadId xxx_thread_tid = osThreadCreate(osThread(xxx_thread), NULL);
在讲app_thread之前肯定要知道其用途吧,看以下枚举类型:
#ifndef CHIP_SUBSYS_SENS enum APP_MODUAL_ID_T { APP_MODUAL_KEY = 0, APP_MODUAL_AUDIO, APP_MODUAL_BATTERY, APP_MODUAL_BT, APP_MODUAL_FM, APP_MODUAL_SD, APP_MODUAL_LINEIN, APP_MODUAL_USBHOST, APP_MODUAL_USBDEVICE, APP_MODUAL_WATCHDOG, APP_MODUAL_ANC, APP_MODUAL_VOICE_ASSIST, APP_MODUAL_SMART_MIC, #ifdef __PC_CMD_UART__ APP_MODUAL_CMD, #endif #ifdef TILE_DATAPATH APP_MODUAL_TILE, #endif APP_MODUAL_MIC, #ifdef VOICE_DETECTOR_EN APP_MODUAL_VOICE_DETECTOR, #endif #ifdef AUDIO_HEARING_COMPSATN APP_MODUAL_HEAR_COMP, #endif APP_MODUAL_OHTER, APP_MODUAL_CUSTOM0, APP_MODUAL_CUSTOM1, APP_MODUAL_CUSTOM2, APP_MODUAL_CUSTOM3, APP_MODUAL_CUSTOM4, APP_MODUAL_CUSTOM5, APP_MODUAL_NUM };
我们可以看到app_thread中做了许多功能,例如KEY、BATTERY 、WATCHDOG等。app_thread主要用于处理应用层的各种信息与事件。
我们以key处理为例,分析下app_thread的执行流程:
static int key_event_process(uint32_t key_code, uint8_t key_event)
{
uint32_t app_keyevt;
APP_MESSAGE_BLOCK msg;
msg.mod_id = APP_MODUAL_KEY;
APP_KEY_SET_MESSAGE(app_keyevt, key_code, key_event);
msg.msg_body.message_id = app_keyevt;
msg.msg_body.message_ptr = (uint32_t)NULL;
app_mailbox_put(&msg);
return 0;
}
在有按下按键执行流程中,最后会执行key_event_process函数,key_code是按键的值(即哪个按键按下),key_event是按下的状态(即长按、短按等)。在key_event_process中会封装一个类型为APP_MESSAGE_BLOCK 的结构体变量msg。结构体定义如下图所示,其中有两个主要的变量,一个是mod_id其为开始中提到的各种模块的枚举类型,第二个是msg_body,其为事件处理函数所要处理的参数。此处mod_id为APP_MODUAL_KEY,APP_KEY_SET_MESSAGE函数将按键值和按下状态分装到app_keyevt中,并将app_keyevt放入message_id中。我们可以看到APP_MESSAGE_BODY类型中有message_id、message_ptr、message_Param0等参数,它们都可以作为事件处理函数的参数,具体赋值给哪个参数可以用户自定义。
typedef struct { uint32_t message_id; uint32_t message_ptr; uint32_t message_Param0; uint32_t message_Param1; uint32_t message_Param2; float message_Param3; } APP_MESSAGE_BODY; typedef struct { uint32_t src_thread; uint32_t dest_thread; uint32_t system_time; uint32_t mod_id; APP_MESSAGE_BODY msg_body; } APP_MESSAGE_BLOCK;
将APP_MESSAGE_BLOCK msg封装好后,利用app_mailbox_put(&msg)函数将消息发送到app_thread线程。接着让我来看看app_mailbox_put是怎么发送到app_thread线程的,我们来看看函数代码,如下图所示。
int app_mailbox_put(APP_MESSAGE_BLOCK* msg_src) { osStatus status; APP_MESSAGE_BLOCK *msg_p = NULL; msg_p = (APP_MESSAGE_BLOCK*)osMailAlloc(app_mailbox, 0); if (!msg_p){ osEvent evt; TRACE_IMM(0,"osMailAlloc error dump"); for (uint8_t i=0; i<APP_MAILBOX_MAX; i++){ evt = osMailGet(app_mailbox, 0); if (evt.status == osEventMail) { TRACE_IMM(9,"cnt:%d mod:%d src:%08x tim:%d id:%8x ptr:%08x para:%08x/%08x/%08x", i, ((APP_MESSAGE_BLOCK *)(evt.value.p))->mod_id, ((APP_MESSAGE_BLOCK *)(evt.value.p))->src_thread, ((APP_MESSAGE_BLOCK *)(evt.value.p))->system_time, ((APP_MESSAGE_BLOCK *)(evt.value.p))->msg_body.message_id, ((APP_MESSAGE_BLOCK *)(evt.value.p))->msg_body.message_ptr, ((APP_MESSAGE_BLOCK *)(evt.value.p))->msg_body.message_Param0, ((APP_MESSAGE_BLOCK *)(evt.value.p))->msg_body.message_Param1, ((APP_MESSAGE_BLOCK *)(evt.value.p))->msg_body.message_Param2); }else{ TRACE_IMM(2,"cnt:%d %d", i, evt.status); break; } } TRACE_IMM(0,"osMailAlloc error dump end"); } ASSERT(msg_p, "osMailAlloc error"); msg_p->src_thread = (uint32_t)osThreadGetId(); msg_p->dest_thread = (uint32_t)NULL; msg_p->system_time = hal_sys_timer_get(); msg_p->mod_id = msg_src->mod_id; msg_p->msg_body.message_id = msg_src->msg_body.message_id; msg_p->msg_body.message_ptr = msg_src->msg_body.message_ptr; msg_p->msg_body.message_Param0 = msg_src->msg_body.message_Param0; msg_p->msg_body.message_Param1 = msg_src->msg_body.message_Param1; msg_p->msg_body.message_Param2 = msg_src->msg_body.message_Param2; status = osMailPut(app_mailbox, msg_p); return (int)status; }
首先使用osMailAlloc函数申请一个消息块,也就是从初始化好的内存池中申请一块内存空间,其需要主动释放。如果申请失败则会将Mailbox中的所有元素进行打印。当正常申请内存块后,将实参各个值赋给msg_p,这边为啥要赋值呢,因为函数的参数在key_event_process中是个局部变量,运行结束后空间会被释放,而msg_p需要主要释放。将msg_p发送给app_mailbox,到此就完成了一次事件的发送。
int app_mailbox_get(APP_MESSAGE_BLOCK** msg_p) { osEvent evt; evt = osMailGet(app_mailbox, osWaitForever); if (evt.status == osEventMail) { *msg_p = (APP_MESSAGE_BLOCK *)evt.value.p; return 0; } return -1; } static void app_thread(void const *argument) { while(1){ APP_MESSAGE_BLOCK *msg_p = NULL; if (!app_mailbox_get(&msg_p)) { if (msg_p->mod_id < APP_MODUAL_NUM) { if (mod_handler[msg_p->mod_id]) { int ret = mod_handler[msg_p->mod_id](&(msg_p->msg_body)); if (ret) TRACE(2,"mod_handler[%d] ret=%d", msg_p->mod_id, ret); } } app_mailbox_free(msg_p); } } }
下面我们看消息块的接收,首先创建一个局部变量APP_MESSAGE_BLOCK *msg_p作为存放接收到数据的容器。利用函数app_mailbox_get进行阻塞等待app_mailbox中是否有消息块(mail)数据。当有消息块时判定msg_p->mod_id的合法性,再判断mod_handler是否为非空,那这个mod_handler是哪来的呢?我们先全局搜索看看mod_handler的类型定义:
static APP_MOD_HANDLER_T mod_handler[APP_MODUAL_NUM]; //全局数组,元素类型为函数指针
typedef int (*APP_MOD_HANDLER_T)(APP_MESSAGE_BODY *); //数组中的元素类型,为函数指针
我们可以看到mod_handler为一个全局数组,数据中元素个数为模块枚举量的有效数量,数组的元素类型是函数指针,其参数为APP_MESSAGE_BODY *,返回值为int。我们知道了mod_handler的类型,现在需要找到数组各个元素被赋值的具体函数。
在初始化的时候,各个模块中都会调用app_set_threadhandle函数,这个函数中会将对应的事件处理函数赋值给数组中的对应位置。
app_set_threadhandle(APP_MODUAL_KEY, app_key_handle_process);
int app_set_threadhandle(enum APP_MODUAL_ID_T mod_id, APP_MOD_HANDLER_T handler)
{
if (mod_id>=APP_MODUAL_NUM)
return -1;
mod_handler[mod_id] = handler;
return 0;
}
回到app_thread函数中,当mod_handler[msg_p->mod_id]为非空时,mod_handler[msg_p->mod_id] (&(msg_p->msg_body))语句调用对应的事件处理函数。从下图中看到app_key_handle_process接收到msg_body后,对msg_body中的数据进行解析,再对解析出的按键值和按键事件做出对应的处理。
static int app_key_handle_process(APP_MESSAGE_BODY *msg_body) { APP_KEY_STATUS key_status; APP_KEY_HANDLE *key_handle = NULL; APP_KEY_GET_CODE(msg_body->message_id, key_status.code); APP_KEY_GET_EVENT(msg_body->message_id, key_status.event); APP_KEY_TRACE(3,"%s code:%d event:%d",__func__,key_status.code, key_status.event); key_event_cnt--; key_handle = app_key_handle_find(&key_status); if (key_handle != NULL && key_handle->function!= NULL) ((APP_KEY_HANDLE_CB_T)key_handle->function)(&key_status,key_handle->param); return 0; }
快速上手
在app_thread中添加一个自定义的事件和对应处理函数:
// 1. 在APP_MODUAL_ID_T中添加一个枚举类 enum APP_MODUAL_ID_T { ... APP_MODUAL_XXX, APP_MODUAL_NUM } // 2. 定义事件发送函数 int xxx_event_process() //事件发送函数可自定义返回类型和参数类型及个数 { APP_MESSAGE_BLOCK msg; msg.mod_id = APP_MODUAL_XXX; msg.msg_body.message_id = xxx; msg.msg_body.message_ptr = xxx; // 可根据需要自行填充msg_body的数据 } // 3. 定义事件处理函数 int app_xxx_handle_process(APP_MESSAGE_BODY *msg_body) { xxx; // 解析msg_body xxx; // 解析后的数据作出对应的事件处理 } // 4. 在模块初始化中注册事件处理函数 app_set_threadhandle(APP_MODUAL_XXX, app_xxx_handle_process);
由于BES的代码作者也接触不久,有很多不完善的地方欢迎大家多多指正。目录内容作者会慢慢填充可以关注下再走,感谢~~~~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。