[恒玄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
在讲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的代码作者也接触不久,有很多不完善的地方欢迎大家多多指正。目录内容作者会慢慢填充可以关注下再走,感谢~~~~