[恒玄BES - 2700] 线程创建与app_thread

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值