• 正文
  • 相关推荐
申请入驻 产业图谱

RT-Thread中的$Sub$$main和$Super$$main

07/06 08:34
438
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

我第一次看RT-Thread的代码时,产生了一个直觉判断:这东西就是比FreeRTOS复杂一点,启动流程多了一层包装,本质还是main函数负责初始化,调度器最后启动。不过最后看完启动过程后,发现他用了两个好玩的扩展用法,也就是$Sub$$main和$Super$$main。这两个扩展用法仅限于MDK内部集成的ARMCC编译器

这两个扩展用法做到了一个什么效果呢?

它让代码看起来有点莫名其妙,因为你看到的是多线程从main函数创建并进行调度,但是看不到RTT本身的调度器和组件是从哪里初始化的。

先来看看这个扩展用法是什么意思,有什么功能。

void ExtraFunc(void);     /* 用户自己实现的外部函数*/{    //这里执行一些你想要执行的代码。}
void $Sub$$function(void){    ExtraFunc();                 /* 可以做一些前置的设置工作 */    $Super$$function();          /* 跳回到原始的function函数 */}
/* 在执行function函数时,执行会先执行function的扩展函数$Sub$$function,在扩展函数里面执行一些你想要的前置代码,当前置操作完成后,最后必须调用$Super$$function函数通过它回到我们原始的function函数 */void function(void){    /* 原本的函数实体 */}

通过上面的函数可以看出,这个扩展用法主要是用于在执行特定函数之前,做一些必要的前置工作。它可以神不知鬼不觉的执行一些让调用者无需关心的事情。

要注意的是$Sub$$main和$Super$$main必须成对出现,也就是:

void $Sub$$fun(void){    $Super$$fun();}

如果像上面这么写,就等于脱裤子放屁——多此一举了。但是如果在回调Super之前增加一点东西,就等于偷偷的做了一些操作。

下面看一下RT-Thread的初始化是怎么运用这个扩展的。在RTT工程中,初始化代码集中在kernel/rt-thread/src/components.c文件里面。

int $Sub$$main(void){    rt_hw_interrupt_disable();                            rtthread_startup();                                   return 0;}

在component文件中可以找到Sub-main的定义,他首先执行一个关闭全局中断操作后,就开始了RTTHread的初始化函数。

如之前所说,我们的单片机在startup初始化汇编代码最后会调用__main标号,也就是我们的main函数。

Reset_Handler   PROC                EXPORT  Reset_Handler             [WEAK]                IMPORT  SystemInit                IMPORT  __main
                CPSID   I               ; 关中断                LDR     R0, =0xE000ED08                LDR     R1, =__Vectors                STR     R1, [R0]                LDR     R2, [R1]                MSR     MSP, R2                LDR     R0, =SystemInit                BLX     R0                CPSIE   i               ; 开中断                LDR     R0, =__main                BX      R0                ENDP

因此,当代码要执行__main时,就会先跳转到上面的C语言函数,这里就会增加RTT的启动函数。根据扩展用法,RTT的启动函数里面最终一定会调用$Super$$main();再回到main函数中去。

把rtthread_startup函数贴出来

int rtthread_startup(void){    /* 关闭中断 */    rt_hw_interrupt_disable();                               (1)
    /* 板级硬件初始化    * 注意: 在板级硬件初始化函数中把要堆初始化好(前提是使用动态内存)    */    rt_hw_board_init();                                      (2)
    /* 打印 RT-Thread 版本号 */    rt_show_version();                                       (3)
    /* 定时器初始化 */    rt_system_timer_init();                                  (4)
    /* 调度器初始化 */    rt_system_scheduler_init();                              (5)
#ifdef RT_USING_SIGNALS    /* 信号量初始化 */    rt_system_signal_init();                                 (6)#endif
    /* 创建初始线程 */    rt_application_init();                                   (7)
    /* 定时器线程初始化 */    rt_system_timer_thread_init();                           (8)
    /* 空闲线程初始化 */    rt_thread_idle_init();                                   (9)
    /* 启动调度器 */    rt_system_scheduler_start();                             (10)
    /* 绝对不会回到这里 */    return 0;                                                (11)}

在上面函数中可以看到,先执行了rtapplicationinit()创建初始线程,然后会调用rtsystemscheduler_start()调度器。这里要记住这个顺序

/* 使用动态内存时需要用到的宏:rt_config.h中定义 *///                 #define RT_USING_USER_MAIN#define RT_MAIN_THREAD_STACK_SIZE     256#define RT_THREAD_PRIORITY_MAX        32
/* 使用静态内存时需要用到的宏和变量:在component.c定义 */ //           #ifdef RT_USING_USER_MAIN#ifndef RT_MAIN_THREAD_STACK_SIZE#define RT_MAIN_THREAD_STACK_SIZE     2048#endif#endif
#ifndef RT_USING_HEAPALIGN(8)static rt_uint8_t main_stack[RT_MAIN_THREAD_STACK_SIZE];struct rt_thread main_thread;#endif
void rt_application_init(void){    rt_thread_t tid;
#ifdef RT_USING_HEAP    /* 使用动态内存 */ //                                              tid =        rt_thread_create("main",                        main_thread_entry,                        RT_NULL,                        RT_MAIN_THREAD_STACK_SIZE,                        RT_THREAD_PRIORITY_MAX / 3,     (初始线程优先级)                        20);    RT_ASSERT(tid != RT_NULL);#else    /* 使用静态内存 */ //                                              rt_err_t result;
    tid = &main_thread;    result =        rt_thread_init(tid,                    "main",                    main_thread_entry,                    RT_NULL,                    main_stack,                    sizeof(main_stack),                    RT_THREAD_PRIORITY_MAX / 3,      (初始线程优先级)                    20);    RT_ASSERT(result == RT_EOK);    (void)result;#endif
    /* 启动线程 */    rt_thread_startup(tid);//                                  }

上面的rtapplicationinit函数中创建了一个初始线程,名字叫main,函数名称为mainthreadentry。因此我们要继续看一下mainthreadentry中做了什么。

void main_thread_entry(void *parameter)//                      {    extern int main(void);    extern int $Super$$main(void);
    /* RT-Thread 组件初始化 */    rt_components_init();
    /* 调用$Super$$main()函数,去到main */    $Super$$main();}
 

在mainthreadentry中执行了组件初始化和Super-main的回调,也就是跑到了整个项目中的main函数中去了。

函数到此为止,其实调度器还没有开始执行。我们在原始的main函数中创建很多的新线程后,整个系统的调度才开始执行起来。

最后看一下原本的main函数

int main(void){    return 0;}

到此为止,如果我们直接从main函数中开始创建线程任务做应用,其实完全不需要关心RTT的启动和组件的初始化,这种写法确实不错,至少不用修改MDK的启动文件了。

如果换成gcc的话,其实就是简单的改一下汇编代码就可以了,让汇编代码来控制先调用RTT的startup函数,然后startup函数中再去调用用户的main函数即可。

相关推荐

多年硬件从业经验,专注分享从研发到供应链,再到精益制造过程中的经验和感悟!