RT - thread学习(二) 内核解析

文章详细阐述了MCU(以芯海科技CS32F031为例)的启动过程,包括从startup_cs32f031xx.S开始,设置栈和堆大小,中断向量表的配置。在Cortex-M3处理器中,复位后会从0x08000000地址取得栈顶地址和复位中断函数。__main函数在启动流程中起到关键作用,负责初始化和调用main函数。RT-thread操作系统则是通过重定义main函数,调用rtthread_startup()来启动,包括硬件初始化、系统定时器、调度器等一系列内核初始化工作。
摘要由CSDN通过智能技术生成

三、RT - thread启动方式

这边以芯海科技的MCU-CS32F031为例:

程序从startup_cs32f031xx.S这个启动文件开始执行。

    • 设置系统栈大小,对齐方式,访问权限

;0x00000400用标记Stack_Size表示
Stack_Size      EQU     0x00000400
;定义一个名叫STACK的区域 不初始化,可读可写,8字节对齐
                AREA    STACK, NOINIT, READWRITE, ALIGN=3   
;偏移Stack_Size大小   
Stack_Mem       SPACE   Stack_Size
;栈顶地址,因为栈是从顶上而下存取的,只需保存栈顶地址即可
__initial_sp                              
    • 设置系统堆大小,对齐方式,访问权限

Heap_Size       EQU     0x00000000
                AREA    HEAP, NOINIT, READWRITE, ALIGN=3        ;不初始化,可读可写,8字节对齐
__heap_base                                                     ;堆开始地址
Heap_Mem        SPACE   Heap_Size                               ;偏移Heap_Size大小  
__heap_limit                                                    ;堆结束地址
    • 设置中断向量表

;定义名叫RESET的数据段,只读,
                AREA    RESET, DATA, READONLY
;声明以下3个变量,以便外部文件使用
                EXPORT  __Vectors                  ;向量表起始地址
                EXPORT  __Vectors_End              ;向量表结束地址
                EXPORT  __Vectors_Size             ;向量表大小
;设置向量表的内容
__Vectors       DCD     __initial_sp               ; Top of Stack  分配4字节的空间给到栈顶地址
                DCD     Reset_Handler              ; Reset Handler 分配4字节的空间复位中断函数地址
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
;........(省略)
__Vectors_End
__Vectors_Size  EQU  __Vectors_End - __Vectors

AREA    |.text|, CODE, READONLY                   ;定义一个名叫只读代码段

Cortex-M3权威指南,在复位后,有如下动作

由于是从FLASH,那么会进行寄存器的映射。由0x0000000映射到0x08000000,

复位后,1. MCU会从0x08000000的地址中取出栈顶地址MSP

2. MCU会从0x08000004的地址中取出复位中断向量函数地址赋值给PC指针,并执行复位中断函数。

    • 复位中断函数的实现

; Reset handler routine
Reset_Handler    PROC
                 EXPORT  Reset_Handler                 [WEAK]  ;声明复位中断函数
        IMPORT  __main                                         ;从其他文件中导入main函数         
        IMPORT  SystemInit                                     ;从其他文件中导入SystemInit函数   
                LDR     R0, =SystemInit                        ;将SystemInit的地址赋值到R0
                BLX     R0                                     ;跳转到寄存区reg给出的目的地址。
                LDR     R0, =__main                            ;将main函数的地址赋值到R0    
                BX      R0                                     ;跳转到寄存区reg给出的目的地址。
                ENDP                                           ;结束复位中断 

这里有两个很有趣的问题,

①__main函数(即是用户程序)在复位中断里面执行,一直没有退出,难道这个合理吗?

解释:Cortex‐M3 支持两种模式和两个特权等级。

两种模式:

  • Handler 模式

  • Thread 模式

两个等级:

  • 特权级

  • 用户级

一般情况下中断会处在特权级和Handler 模式,但是复位中断比较例外简单的说就是,他在执行过程中是处在特权级和Thread 模式。其实这个Cortex-M3权威指南有给出说明,

答案:是的。__main函数会一直在复位中断里面执行,首先,MCU启动时会处在特权级和Handler 模式,但是执行复位中断函数后会变成特权级和Thread 模式。所有__main函数会运行在这个等级和模式中。所以:复位中断(Reset_Handler)和普通中断(SysTick_Handler)的操作模式不一样。

②__main函数和main函数有什么不同呢,为什么会多两个下划线呢?

解释:其实__main函数和main函数时不一样的函数。

调用__main函数比调用main函数多一些功能。以下就是说明__main函数的作用。

调用过程:

stm32在启动后先进入重启中断函数Reset_Handler,其中会先后调用SystemInit和__main函数,

__main函数属于c库函数,其内部依次进行三步工作,即先初始化rw段,然后初始化zi段,最后调用另一个c库函数__rt_entry(),

__rt_entry()该函数先初始化堆栈和库函数,然后即调用主函数main(),从而进入用户程序。可以看出主函数main()若退出,则在__rt_entry()最后会再调用exit()函数进行退出操作。

答案:stm32启动文件里面Reset_Handler最后调用了__main,而在__main里面最后调用了__rt_entry(),然后__rt_entry()在做完堆栈和库函数初始化工作之后才调用main()。如果没有用__main函数而用main函数则1.2步骤设置的堆栈都白设置了。

    • 用户堆栈初始化

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
                 IF      :DEF:__MICROLIB                    
                 ;如果定义了微库__microLIB, 那么会按1.2步骤初始化堆栈,并声明3个变量方便其他文件使用   
                 EXPORT  __initial_sp                       
                 EXPORT  __heap_base                        
                 EXPORT  __heap_limit                       
                
                 ELSE
                 ;否则程序需要外部实现__use_two_region_memory函数,并提供__user_initial_stackheap函数供外部调用
                 IMPORT  __use_two_region_memory
                 EXPORT  __user_initial_stackheap
                 
__user_initial_stackheap

                 LDR     R0, =  Heap_Mem
                 LDR     R1, =(Stack_Mem + Stack_Size)
                 LDR     R2, = (Heap_Mem +  Heap_Size)
                 LDR     R3, = Stack_Mem
                 BX      LR

                 ALIGN

                 ENDIF

讲完了MCU的启动流程,为什么没有看到RT-THREAD的启动呢,也是从main()启动后在执行RT-THREAD的内核吗?

答案是否的。

以下就来真正说明RTTHREAD的启动:

int $Super$$main(void) 和 int $Sub$$main(void)

是KEIL编译器中可以使用的两个特殊符号。

int $Sub$$main(void) 表示当执行到main();函数时改执行 int $Sub$$main(void) { } 这个函数。

int $Super$$main(void) 表示当执行 $Super$$main()函数时,执行原main(){}定义的的函数。

rtthread则定义了int $Sub$$main(void)函数,所有在执行到__main函数中的main函数时,会改为执行int $Sub$$main(void)函数,即这就是内核函数开始的地方。

/* re-define main function */
int $Sub$$main(void)
{
    rtthread_startup();                            //调用rtthread_startup()启动函数
    return 0;
}
int rtthread_startup(void)
{
    rt_hw_interrupt_disable();                 //关闭全局中断

    /* board level initialization
     * NOTE: please initialize heap inside board initialization.
     */
    rt_hw_board_init();                        //硬件驱动初始化函数
    /* show RT-Thread version */
    rt_show_version();                          //打印rt-thread-log

    /* timer system initialization */
    rt_system_timer_init();                     //系统时间链表初始化

    /* scheduler system initialization */
    rt_system_scheduler_init();                 //rt-thread调度器初始化 初始化进程链表,优先级链表

    /* create init_thread */
    rt_application_init();                      //创建了main函数线程,并加入调度器

    /* timer thread initialization */
    rt_system_timer_thread_init();              //rt-thread软件定时器处理线程,用于处理到时后的回调函数  

    /* idle thread initialization */
    rt_thread_idle_init();                      //rt-thread空闲线程初始化。

    /* start scheduler */
    rt_system_scheduler_start();                //启动调度器。

    /* never reach here */
    return 0;
}

这时因为就只有两个线程main线程和idel线程,main优先级比idle高,所以会从main_thread_entry()开始执行

/* the system main thread */
void main_thread_entry(void *parameter)
{
    extern int main(void);
    extern int $Super$$main(void);                     //声明函数

#ifdef RT_USING_COMPONENTS_INIT
    /* RT-Thread components initialization */
    rt_components_init();                               
#endif
    $Super$$main(); /* for ARMCC. */                   //执行原main()函数
}

这时rt-thread的启动就写完了。

参考资料

https://wenku.baidu.com/view/ab98f8025bfb770bf78a6529647d27284b7337ad.html?_wkts_=1672975653822&bdQuery=stm32%E5%90%AF%E5%8A%A8%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值