一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头。RT-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一启动入口。一般执行顺序是:系统先从启动文件开始运行,例如,STM32平台的汇编语言编写的启动文件,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口 main()。如下图(此图是使用RT-Thread官网的图片)所示。
下面我们就以开发工具使用MDK为例来讲解系统的启动流程。以 MDK-ARM 为例,用户程序入口为 main() 函数,位于 main.c 文件中。系统启动后先从汇编代码 startup_stm32f103xe.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统启动,最后进入用户程序入口 main()。
为了在进入 main() 之前完成 RT-Thread 系统功能初始化,我们使用了 MDK 的扩展功能 $Sub$$ 和 $Super$$。可以给 main 添加 $Sub$$ 的前缀符号作为一个新功能函数$Sub$$main,这个 $Sub$$main 可以先调用一些要补充在 main 之前的功能函数(这里添加 RT-Thread 系统启动,进行系统一系列初始化),再调用 $Super$$main 转到 main() 函数执行,这样可以让用户不用去管 main() 之前的系统初始化操作。
在系统的components.c文件中可以看到int $Sub$$main(void)函数的定义;代码如下:
int $Sub$$main(void)
{
rtthread_startup();
return 0;
}
从上面的代码可以知道,只调用了rtthread_startup()函数执行;此函数的内容如下代码所示;我们会逐个分析每个代码的作用是什么。
/* 此函数是在系统开始的时候执行,根据你使用的编译环境的不同而从不同的函数中调用此函数
* 主要完成以下相关的工作:
* 1.禁止中断,全局方式禁止中断;
* 2.板级初始化,例如,初始化系统时钟,初始化打印log使用的串口等工作;
* 3.打印系统版本信息;
* 4.初始化系统中硬Timer使用到的链表;
* 5.初始化系统的线程调度器;
* 6.创建main线程,此线程启动时会调用我们自己定义的main()函数;
* 7.如果使用软Timer,则初始化使用到的链表,并创建软Timer使用的线程;
* 8.创建系统使用中的IDLE线程;并开启它;
* 9.开启线程调度器;注意:此函数不会返回的,因为最后开启了线程调度器;
* 备注:此函数一般情况下是板级厂商提供;因为有很多工作是和板子相关;
*/
int rtthread_startup(void)
{
//1.禁止中断,全局方式禁止中断;
rt_hw_interrupt_disable();
/* board level initialization
* NOTE: please initialize heap inside board initialization.
* 2.板级初始化,例如,初始化系统时钟,初始化打印log使用的串口等工作;
* 此函数是和项目使用芯片和板子强相关的,根据项目情况进行处理。
*/
rt_hw_board_init();
//3.打印系统版本信息;
rt_show_version();
//4.初始化系统中硬Timer使用到的链表;
rt_system_timer_init();
//5.初始化系统的线程调度器;
rt_system_scheduler_init();
#ifdef RT_USING_SIGNALS
/* signal system initialization */
rt_system_signal_init();
#endif
//6.创建main线程,此线程启动时会调用我们自己定义的main()函数;
rt_application_init();
//7.如果使用软Timer,则初始化使用到的链表,并创建软Timer使用的线程;
rt_system_timer_thread_init();
//8.创建系统使用中的IDLE线程;并开启它;
rt_thread_idle_init();
//9.开启线程调度器;注意:此函数不会返回的,因为最后开启了线程调度器;
rt_system_scheduler_start();
/* 程序永远也不会执行到这里,否则,系统就出错啦!!! */
return 0;
}
int rtthread_startup(void)函数注意完成以下几项工作;
1.禁止中断,全局方式禁止中断;会在启动线程调度器的时候去开启全局中断;
2.板级初始化,例如,初始化系统时钟,初始化打印log使用的串口等工作;
3.打印系统版本信息;
4.初始化系统中硬Timer使用到的链表;
5.初始化系统的线程调度器;
6.创建main线程,此线程启动时会调用我们自己定义的main()函数;
7.如果使用软Timer,则初始化使用到的链表,并创建软Timer使用的线程;
8.创建系统使用中的IDLE线程;并开启它;
9.开启线程调度器;注意:此函数不会返回的,因为最后开启了线程调度器;
备注:此函数一般情况下是板级厂商提供;因为有很多工作是和板子相关;
下面我们逐个分析函数的作用;
1.禁止全局中断
rt_hw_interrupt_disable();
;/*
; * rt_base_t rt_hw_interrupt_disable();
; * 全局关中断函数;并返回当前PRIMASK寄存器中断的值
; */
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
MRS r0, PRIMASK
CPSID I
BX LR
ENDP
上面的这个函数是通过汇编语言进行编写的,通过操作PRIMASK寄存器,禁止中断。
2.系统板级初始化;主要初始化系统时钟
void rt_hw_board_init()
{
//首先,应该配置系统时钟;
/* 更新系统时钟 */
SystemCoreClockUpdate();
/* 配置SysTick系统定时器;并开启它 */
_SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
/* Call components board initial (use INIT_BOARD_EXPORT())
* 初始化组件相关的初始化;由于,Nano版本的软件默认不使用,我先不介绍
*/
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
/* 如果用户使用自定义main()函数,并且使用动态内存,则初始化动态内存要使用的RAM空间 */
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}
函数主要做了以下几个工作:初始化系统时钟,更新系统时钟;配置SysTick系统定时器,此定时器用来提供系统时钟节拍;如果使用初始化组件,则调用执行;如果使用动态内存堆,则初始化内存堆的空间。这个函数和具体的平台强相关的,还应该初始化打印log使用的串口,方便调试工作。
3.打印系统版本号
void rt_show_version(void)
{
rt_kprintf("\n \\ | /\n");
rt_kprintf("- RT - Thread Operating System\n");
rt_kprintf(" / | \\ %d.%d.%d build %s\n",
RT_VERSION, RT_SUBVERSION, RT_REVISION, __DATE__);
rt_kprintf(" 2006 - 2019 Copyright by rt-thread team\n");
}
函数主要使用rt_kprintf()系统函数打印系统的版本信息。
4.初始化系统中硬Timer使用到的链表
void rt_system_timer_init(void)
{
int i;
for (i = 0; i < sizeof(rt_timer_list) / sizeof(rt_timer_list[0]); i++)
{
rt_list_init(rt_timer_list + i);
}
}
此函数只做了一项工作,就是初始化系统中硬Timer的链表头,使用此链表来管理系统中所有的Timer。
5.初始化线程调度器
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%02x\n",
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);
}
此函数的具体内容还没有做解读,只是知道是初始化线程调度器。
6.创建main线程,此线程启动时会调用我们自己定义的main()函数
void rt_application_init(void)
{
rt_thread_t tid;
//动态方式创建main线程
#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 //静态方式创建main线程
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);
}
此函数做的内容很简单,就是创建一个名称为main的线程,并启动它。注意,启动它之后,线程的处理函数不会立即得到执行,因为还没有开启线程调度器。注意,我们自己写的main函数,就会从此线程的处理函数中被调用的。
7.初始化软Timer相关的工作
void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT
int i;
/* 初始化软Timer使用到的链表 */
for (i = 0; i < sizeof(rt_soft_timer_list) / sizeof(rt_soft_timer_list[0]); i++)
{
rt_list_init(rt_soft_timer_list + i);
}
/* 创建软Timer使用到的线程 */
rt_thread_init(&timer_thread,
"timer",
rt_thread_timer_entry, //线程处理函数
RT_NULL,
&timer_thread_stack[0], //线程栈的起始地址
sizeof(timer_thread_stack), //线程栈的大小;单位字节
RT_TIMER_THREAD_PRIO, //线程的优先级
10); //时间片大小,单位tick
/* 开启线程 */
rt_thread_startup(&timer_thread);
#endif
}
如果开启了RT_USING_TIMER_SOFT宏,则可以使用系统中的软Timer组件,就是从这里初始化使用到的链表,并创建一个线程,专门用来执行软Timer定时器超时函数。也就是说软Timer的超时函数时在线程环境进行执行的。而硬Timer的处理环境是中断上下文中。后面我们会详细介绍内核中的Timer组件。
8.创建IDLE线程
void rt_thread_idle_init(void)
{
/* 初始化IDLE任务 */
rt_thread_init(&idle,
"tidle",
rt_thread_idle_entry,
RT_NULL,
&rt_thread_stack[0],
sizeof(rt_thread_stack),
RT_THREAD_PRIORITY_MAX - 1, //IDLE线程的优先级是最低的
32);
/* 启动IDLE任务 */
rt_thread_startup(&idle);
}
创建IDLE线程,此线程一定要被创建,是系统级别的线程。建议不要在其回调函数中做过多工作,默认就是清理僵尸线程占用的内存空间。
9.开启线程调度器
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
register rt_ubase_t highest_ready_priority;
#if RT_THREAD_PRIORITY_MAX > 32
register rt_ubase_t number;
number = __rt_ffs(rt_thread_ready_priority_group) - 1;
highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
#else
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#endif
/* get switch to thread */
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
rt_current_thread = to_thread;
/* switch to new thread */
rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
/* never come back */
}
开启线程调度器,主要工作就是找到优先级最高的线程,并执行它。注意,不会从此函数中返回的,因为已经启动了线程调度器,系统就会找到最高优先级的线程进行执行。
注意:因为上面创建了名称为main的线程,下面开启了线程调度器,调度器开启之后就会执行main线程的处理函数,我们来看函数的内容。
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) || defined(__CLANG_ARM)
$Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
//调用我们自己写的int main(void)函数;
main();
#endif
}
看代码就知道,调用执行用户写的int main()函数。
本文完毕!