探索RT-Thread启动流程:揭秘线程创建与设备初始化的奥秘


layout: post
title: “RT-Thread启动流程”
date: 2024-1-30 15:39:08 +0800
tags: RT-Thread


RT-Thread启动流程

image-20240127123137747

开始的时候rttread_startup()函数是RTThread的统一的入口, 一般的顺序是系统先从启动文件开始运行,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口 main()

image-20240127123607178

image-20240127133652325

image-20240127133749303

实际的文件

image-20240127134016114

/**
 * @brief  This is the code that gets called when the processor first
 *          starts execution following a reset event. Only the absolutely
 *          necessary set is performed, after which the application
 *          supplied main() routine is called.
 * @param  None
 * @retval : None
*/

  .section .text.Reset_Handler
  .weak Reset_Handler
  .type Reset_Handler, %function
Reset_Handler:

/* Copy the data segment initializers from flash to SRAM
把数据段拷贝到SRAM*/
  movs r1, #0
  b LoopCopyDataInit

CopyDataInit:
  ldr r3, =_sidata
  ldr r3, [r3, r1]
  str r3, [r0, r1]
  adds r1, r1, #4

LoopCopyDataInit:
  ldr r0, =_sdata
  ldr r3, =_edata
  adds r2, r0, r1
  cmp r2, r3
  //比较一下, 进行循环拷贝
  bcc CopyDataInit
  ldr r2, =_sbss
  b LoopFillZerobss
/* Zero fill the bss segment.
把BSS段清零, 这一段是存储未初始化全局变量和静态变量的特殊数据段*/
FillZerobss:
  movs r3, #0
  str r3, [r2], #4

LoopFillZerobss:
  ldr r3, = _ebss
  cmp r2, r3
  bcc FillZerobss

/* Call the clock system intitialization function.进入时钟初始化函数*/
    bl  SystemInit
/* Call static constructors */
    /* bl __libc_init_array */
/* Call the application's entry point. 进入C语言的函数*/
  bl  entry
  bx lr
.size Reset_Handler, .-Reset_Handler

Cotex-M3的芯片在上电以后会运行0x00000004位置的函数, 通常这个位置是中断向量表的位置, 第一个函数是Reset_Handler, 所以上电以后会进入的是reset中断

/**
  * @brief  Setup the microcontroller system
  *         Initialize the Embedded Flash Interface, the PLL and update the 
  *         SystemCoreClock variable.
  主要是实现时钟的初始化, 这一个函数设置值一般会在板子初始化的时候被覆盖
  * @note   This function should be used only after reset.
  * @param  None
  * @retval None
  */
void SystemInit (void)
{
	//这一个实际需要根据实际的芯片进行分析
    ...
    
}
//这一个是gcc编译器进入的函数, Keil进入$Sub$$main(后面Keil部分有讲解)
int entry(void)
{
    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();

    /* timer system initialization 初始化一个用于时钟的链表*/
    rt_system_timer_init();

    /* scheduler system initialization 初始化一个用于线程管理的链表*/
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* signal system initialization 信号量的初始化*/
    rt_system_signal_init();
#endif

    /* create init_thread app的线程, 也是main函数之后会被执行的线程*/
    rt_application_init();

    /* timer thread initialization 定时器线程的初始化*/
    rt_system_timer_thread_init();

    /* idle thread initialization 空闲任务线程初始化*/
    rt_thread_idle_init();

#ifdef RT_USING_SMP
    rt_hw_spin_lock(&_cpus_lock);
#endif /*RT_USING_SMP*/

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

    /* never reach here */
    return 0;
}
//主要的作用是开启执行main函数的一个线程
void rt_application_init(void)
{
    rt_thread_t tid;
//看看是否使用堆
#ifdef RT_USING_HEAP
    //使用动态的方式进行初始化
    //一个叫做main的任务, 使用的函数是main_thread_entry, 没有参数, 栈大小2048, 优先级10, 时钟20
    //会在这一个线程里面进行软件的相关的初始化
    tid = rt_thread_create("main", main_thread_entry, RT_NULL,
                           RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 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_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(result == RT_EOK);

    /* if not define RT_USING_HEAP, using to eliminate the warning */
    (void)result;
#endif
	//启动线程
    rt_thread_startup(tid);
}
/* 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    
#ifdef RT_USING_SMP
    //多处理器的时候用的
    rt_hw_secondary_cpu_up();
#endif
    /* invoke system main function */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
    $Super$$main(); /* for ARMCC. 这个是在Keil里面调用实际的用户定义的main函数*/
#elif defined(__ICCARM__) || defined(__GNUC__)
    //用户层的main函数的接口
    main();
#endif
}

这里使用的 S u p e r Super Super$main()的用法会在后面的Keil启动进行讲解

Keil启动

在使用Keil的代码的时候为了在进入main函数之前进行初始化, 使用了MDK的扩展功能

//$Super$$以及$Sub$$
/* re-define main function */
int $Sub$$main(void)
{
    rtthread_startup();
    return 0;
}

使用这一个符号的时候, 会在main函数之前进行调用 S u b Sub Sub m a i n 这一个函数 , 如果想使用原来的 m a i n 函数 , 需要使用 main这一个函数, 如果想使用原来的main函数, 需要使用 main这一个函数,如果想使用原来的main函数,需要使用Super$$main(), 在函数之前加上这一个前缀

这一个功能的作用主要是用来在一些不能更改的函数前面进行更改, 比如说库函数

组件以及模块的初始化

RT_Thread使用一些宏定义把需要的初始化的时候调用的函数做成一张表

  • 这里使用的是设备的进行分析

需要使用一个宏定义用来把这一个函数进行注册

INIT_BOARD_EXPORT(rt_wdt_init);
//进行展开
/***************1**************/
#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")
INIT_EXPORT(rt_wdt_init, "1")
/***************2*************/
#define INIT_EXPORT(fn, level)                                                       \
	RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn." level) = fn
RT_USED const init_fn_t __rt_init_rt_wdt_init SECTION(".rti_fn." "1") = rt_wdt_init
/***************3*************/
#define RT_USED                     __attribute__((used))
#define SECTION(x)                  __attribute__((section(x)))
 __attribute__((used)) const init_fn_t __rt_init_rt_wdt_init __attribute__((section(".rti_fn.1"))) = rt_wdt_init
/*
    attribute((used)) 其作用是告诉编译器避免被链接器因为未用过而被优化掉。
    attribute((section(“name”))) 其作用是将作用的函数或数据放入指定名为"section_name"对应的段中
*/

宏替换完之后,就是定义了一个指向函数的指针变量 __rt_init_rt_hw_spi_init,该变量值为 rt_hw_spi_init,同时该变量位于 .rti_fn.1 段, 该符号段位于内存分配的 RO 段中。

/**
 * @brief  Onboard components initialization. In this function, the board-level
 *         initialization function will be called to complete the initialization
 *         of the on-board peripherals.
 */
void rt_components_board_init(void)
{
    volatile const init_fn_t *fn_ptr;
	//会在这里被调用
    for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
    {
        (*fn_ptr)();
    }
#endif /* RT_DEBUGING_INIT */
}

__rt_init_rti_board_start__rt_init_rti_board_end 这 2 个变量没有在代码中定义, 这里还有一些用于排序的函数的初始化

static int rti_start(void)
{
 return 0;
}
INIT_EXPORT(rti_start, "0");

static int rti_board_start(void)
{
 return 0;
}
INIT_EXPORT(rti_board_start, "0.end");

static int rti_board_end(void)
{
 return 0;
}
INIT_EXPORT(rti_board_end, "1.end");

static int rti_end(void)
{
 return 0;
}
INIT_EXPORT(rti_end, "6.end");

通过链接文件进行排序

__rt_init_start = .;
KEEP(*(SORT(.rti_fn*)))
__rt_init_end = .;KEEP(*(SORT(.rti_fn*)))

语句将所有的 .rti_fn* 的段,排序后放在 rt_init_start 和 rt_init_end 之间,KEEP 关键字强制链接器保留某些特定部分。

实际的排序

/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")

/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

这一些是Thread里面的一些宏定义, 通过上面的示例可以知道在RT-Thread的初始化的时候这一些函数的顺序如下(这里的宏定义主要是按照这几个数值创建一系列的常量放在对应的段里面, 同时防止编译器优化)

  1. rti_start()
  2. rti_board_start()
  3. 使用INIT_BOARD_EXPORT初始化的设备的硬件初始化函数
  4. rti_board_end()这是一个分割线, 不会实际调用
  5. 使用INIT_PREV_EXPORT的纯软件的初始化、没有太多依赖的函数
  6. 使用INIT_DEVICE_EXPORT的外设驱动初始化相关,比如网卡设备
  7. 使用INIT_COMPONENT_EXPORT的组件初始化,比如文件系统或者LWIP
  8. 使用INIT_ENV_EXPORT系统环境初始化,比如挂载文件系统
  9. 使用INIT_APP_EXPORT的应用初始化,比如GUI应用
  10. rti_end()

实际的调用

在rt_components_board_init里面会调用第一部分的硬件初始化

for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
    (*fn_ptr)();
}

rt_components_init()函数会在操作系统运行起来之后创建的main线程里被调用执行

for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
{
    (*fn_ptr)();
}

调用剩下的一些函数

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值