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

560次阅读
没有评论

共计 3380 个字符,预计需要花费 9 分钟才能阅读完成。

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

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

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

它让代码看起来有点莫名其妙,因为你看到的是多线程从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的初始化函数。

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

如之前所说,我们的单片机在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_HEAP
ALIGN(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函数中创建很多的新线程后,整个系统的调度才开始执行起来。

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

最后看一下原本的main函数

int main(void)
{
    return 0;
}

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

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

正文完
 0
评论(没有评论)