启动流程
一个开发板上的RT-Thread的启动流程可能是首先从bsp当中链接脚本指定的startup_xxx.S中的入口函数(ENTRY)或者复位异常处理函数(ResetHandler)开始运行,这部分我们在讲bsp支持时会详细讲解。
之后跳入entry函数(GCC,使用不同编译器会进入不同的函数)执行,这里也可以跳入用户自己的main函数,但是需要用户自己完成rtthread_startup的工作。
这个函数十分简单,首先先关中断(关中断操作由cpu支持部分提供),然后进入RT-Thread的全局初始化中。
#if defined (__CC_ARM)
extern int Super$main(void);
/* re-define main function /
int Sub$main(void)
{
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
#elif defined( ICCARM )
extern int main(void);
/ __low_level_init will auto called by IAR cstartup /
extern void __iar_data_init3(void);
int __low_level_init(void)
{
// call IAR table copy function.
__iar_data_init3();
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
#elif defined( GNUC )
extern int main(void);
/ Add -eentry to arm-none-eabi-gcc argument */
int entry(void)
{
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
#endif
rtthread_startup是启动流程当中的关键,首先rtthread_startup会先调用rt_hw_board_init,这个函数也由bsp支持部分提供,一般来说主要完成例如初始化中断向量表、系统时钟的初始化,设置输出控制台,同时初始化系统堆内存。
紧接着会调用rt_show_version打印RT-Thread内核的系统版本信息,其中主要是利用控制台(rt_printf)进行输出,通常来说是用户在bsp支持中提供串口的注册来实现的。以RT-Thread Simulator 例程来说,会通过设备驱动一路调用到bsp支持部分提供的串口输出。
void rt_kprintf(const char *fmt, ...) ->
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) ->
static rt_size_t rt_serial_write(struct rt_device *dev, rt_off_t pos, const void *buffer, rt_size_t size) ->
rt_inline int _serial_poll_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length) ->
static int stm32_putc(struct rt_serial_device *serial, char c)
然后会调用rt_system_timer_init初始化系统定时器链表,这个函数比较简单,主要就是数据结构进行初始化。
紧接着会调用rt_system_scheduler_init初始化RT-Thread系统调度器相关的数据结构:
线程优先级链表:每一个优先级对应一个链表,通过rt_thread结构中的tlist成员来进行相同优先级线程的连接
当前线程优先级
当前线程控制块
rt_thread_ready_priority_group中的每一位代表1个优先级,该位为1表示该优先级下有就绪线程,该位为0表示该优先级下没有就绪线程
僵尸线程链表
void rt_system_scheduler_init(void)
{
register rt_base_t offset;
rt_scheduler_lock_nest = 0;
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("start scheduler: max priority 0x%02xn",
RT_THREAD_PRIORITY_MAX));
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&rt_thread_priority_table[offset]);
}
rt_current_priority = RT_THREAD_PRIORITY_MAX - 1;
rt_current_thread = RT_NULL;
/* initialize ready priority group /
rt_thread_ready_priority_group = 0;
#if RT_THREAD_PRIORITY_MAX > 32
/ initialize ready table /
rt_memset(rt_thread_ready_table, 0, sizeof(rt_thread_ready_table));
#endif
/ initialize thread defunct */
rt_list_init(&rt_thread_defunct);
}
接下来会调用rt_application_init初始化一个main主线程(并不会马上运行),主要完成组件的初始化以及多核的处理,最后跳入用户的main函数
完成组件初始化的实现与rt_components_board_init类似,在BSP支持部分讲解。
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_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);
}
void main_thread_entry(void parameter)
{
extern int main(void);
extern int Super$main(void);
/ RT-Thread components initialization /
rt_components_init();
/ invoke system main function /
#if defined (__CC_ARM)
Super$main(); / for ARMCC. */
#elif defined( ICCARM ) || defined( GNUC )
main();
#endif
}
void rt_components_init(void)
{
#if RT_DEBUG_INIT
int result;
const struct rt_init_desc *desc;
rt_kprintf("do components intialization.n");
for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++)
{
rt_kprintf("initialize %s", desc->fn_name);
result = desc->fn();
rt_kprintf(":%d donen", result);
}
#else
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
{
(*fn_ptr)();
}
#endif
}
然后,rt_system_timer_thread_init主要是初始化软件定时器的列表,并且创建软件定时器线程。而rt_thread_idle_init创建空闲线程,在系统没有任何用户线程调度的时候,就会被调度起来,这个空闲线程主要是检查系统有没有已经消亡的线程,如果有,则把消亡线程的资源进行回收,如果系统使能了电源管理,则会让系统进行低功耗模式。
最后通过rt_system_scheduler_start开启调度器。
BSP支持
首先需要提供startup_xxx.S类似的启动文件,一般来说可能包含中断向量表以及默认的中断服务函数,以及选择入口函数,一般可能为_start或者ResetHandler。
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1_2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD 0 ; Reserved
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SPI1_IRQHandler ; SPI1
DCD 0 ; Reserved
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD 0 ; Reserved
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
; Reset handler routine
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
bsp支持主要提供对开发板的硬件初始化(通过rt_hw_board_init向上提供接口)以及各类外设的驱动。
以RT-Thread Simulator 例程为例:rt_hw_board_init中主要完成HAL库的初始化、时钟配置和RT-Thread系统堆的初始化,以及rt_components_board_init会完成硬件的初始化。
#define RT_DEBUG_INIT 0
/**
- RT-Thread Components Initialization for board
*/
void rt_components_board_init(void)
{
#if RT_DEBUG_INIT
int result;
const struct rt_init_desc *desc;
for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++)
{
rt_kprintf("initialize %s", desc->fn_name);
result = desc->fn();
rt_kprintf(":%d donen", result);
}
#else
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_USED: attribute ((used)),标识不允许编译器进行优化
init_fn_t:typedef int (init_fn_t)(void),函数指针
##:连接符
SECTION: attribute ((section(x))),执行输入节名称
所以INIT_EXPORT(rti_board_start, "0.end")等价于__attribute ((used)) const init_fn_t __rt_init_rti_board_start attribute ((section(".rti_fn.""0.end"))) = rti_board_start
static int rti_board_start(void)
{
return 0;
}
INIT_EXPORT(rti_board_start, "0.end");
#define INIT_EXPORT(fn, level) RT_USED const init_fn_t _ rt_init ##fn SECTION(".rti_fn."level) = fn
rt_board_end同理,所以rt_components_board_init的含义则为执行__rt_init_rti_board_start到__rt_init_rti_board_end之间函数
指定节.rti_fn.1,根据链接器节放置规则,将放置在.rti_fn.0.end节和.rti_fn.1.end节之间。
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
所以RT-Thread提供了另一个宏,它的主要作用就是用来在初始化硬件时调用相应的函数。所以一些外设驱动初始化都展开了这个宏。以RT-Thread Simulator 例程为例:
INIT_BOARD_EXPORT(rt_hw_usart_init);
INIT_BOARD_EXPORT(rt_hw_pin_init);
总的来说,一个基本的BSP主要任务是建立让操作系统运行的基本环境,所以大致需要完成的主要工作是:
初始化CPU内部寄存器,设定RAM工作时序。
实现时钟驱动及中断控制器驱动,完善中断管理。
实现串口和 GPIO 驱动。
初始化动态内存堆,实现动态堆内存管理。
CPU支持
这部分官方文档很详细,可参考内核移植 (rt-thread.org)
在嵌入式领域有多种不同 CPU 架构,例如 Cortex-M、ARM920T、MIPS32、RISC-V 等等。为了使 RT-Thread 能够在不同 CPU 架构的芯片上运行,RT-Thread 提供了一个 libcpu 抽象层来适配不同的 CPU 架构。libcpu 层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。
RT-Thread 的 libcpu 抽象层向下提供了一套统一的 CPU 架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容。下表是 CPU 架构移植需要实现的接口和变量。
libcpu 移植相关 API