本文共 6974 字,大约阅读时间需要 23 分钟。
系统先从启动文件开始运行,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口 main(),如下图所示:
系统启动后先从汇编代码 startup_stm32f429xe.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统启动,最后进入用户程序入口 main()。
①从系统初始化开始执行,将函数地址赋给R0寄存器,跳转到R0地址执行并返回此处(BLX是带链接的跳转,即带返回的跳转)。 ②将main函数地址给R0,将函数地址赋给R0,跳转到R0地址执行,不返回(BX是跳转,不返回)。 ③跳转到了$Sub$$main 【在 __CC_ARM 编译器环境下,使用了$Sub$ $ 与 $Super$ $ 的“补丁”功能。这是一种特殊模式:用于有一个已经存在且不能被改变的函数的情况。使用这两个模式可以帮原函数打补丁。如存在一个函数foo();$Sub$ $foo定义的新功能函数,在foo()函数之前或者后使用$Sub$ $foo 可以添加一些新的程序代码。$Super$ $foo就是原始的未修补的foo函数,使用这个$Super$ $foo函数将直接跳转到foo()函数。】关于 $Sub$ $ 和 $Super$ $ 扩展功能的使用,详见:
$Sub$$main 中主要是一些系统启动代码(系统初始化)。
④在rtthread_startup中,主要实现了板级初始化(初始化外设和驱动);打印RT-Thread的logo和版本信息;初始化系统定时器;初始化调度器;创建application线程(这里将用户main函数作为一个线程,用户main里面是空的);初始化软件定时器;创建空闲线程;启动系统调度(启用调度后,main函数就会参与调度开始运行)。 【所以说 $Sub$$main在main之前干的活就是进行rt-thread系统初始化,为了让用户更方便的使用,让用户不要操心的太多】 ⑤以下是在rt_application_init()函数中创建的main函数线程:⑥$Super$$mian 可以直接跳到main()函数; 用户可以在main中写一些应用代码:
可以这样使用给main函数打补丁:
int $Sub$$main(void){ //添加补丁函数 $Super$$main(); //使用本句直接转到main()运行}
当然,main()函数也可以是自己的其他函数,操作都是一样的,换一下函数名就好了
首先查看这一章节,明白RT-Thread链表及线程的实现方法。
线程创建好之后,我们需要把线程添加到就绪列表里面, 表示线程已经就绪,系统随时可以调度。
就绪列表实际上就是一个 rt_list_t 类型的数组,数组的大小由决定最大线程优先级的宏 RT_THREAD_PRIORITY_MAX 决 定 ,RT_THREAD_PRIORITY_MAX 在 rtconfig.h 中默认定义为 32。 数组的下标对应了线程的优先级,同一优先级的线程统一插入到就绪列表的同一条链表中。
定义就绪列表:rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
初始化就绪列表:
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++) { rt_list_init(&rt_thread_priority_table[offset]); }
rt_thread_startup()----->rt_thread_resume()----->rt_schedule_insert_thread()----->rt_list_insert_before()
void rt_schedule_insert_thread(struct rt_thread *thread){ register rt_base_t temp; RT_ASSERT(thread != RT_NULL); /* disable interrupt */ temp = rt_hw_interrupt_disable(); /* it's current thread, it should be RUNNING thread */ if (thread == rt_current_thread) { thread->stat = RT_THREAD_RUNNING | (thread->stat & ~RT_THREAD_STAT_MASK); goto __exit; } /* READY thread, insert to ready queue */ thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK); /* insert thread to ready list */ rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]), &(thread->tlist)); RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("insert thread[%.*s], the priority: %d\n", RT_NAME_MAX, thread->name, thread->current_priority)); /* set priority mask */#if RT_THREAD_PRIORITY_MAX > 32 rt_thread_ready_table[thread->number] |= thread->high_mask;#endif rt_thread_ready_priority_group |= thread->number_mask;__exit: /* enable interrupt */ rt_hw_interrupt_enable(temp);}
调度器是操作系统的核心,其主要功能就是实现线程的切换,即从就绪列表里面找到优先级最高的线程,然后去执行该线程。
rt_system_scheduler_init();
void rt_system_scheduler_start(void){ register struct rt_thread *to_thread; rt_ubase_t highest_ready_priority; to_thread = _get_highest_priority_thread(&highest_ready_priority);#ifdef RT_USING_SMP to_thread->oncpu = rt_hw_cpu_id();#else rt_current_thread = to_thread;#endif /*RT_USING_SMP*/ rt_schedule_remove_thread(to_thread); to_thread->stat = RT_THREAD_RUNNING; /* switch to new thread */#ifdef RT_USING_SMP rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp, to_thread);#else rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);#endif /*RT_USING_SMP*/ /* never come back */}
rt_hw_context_switch_to PROC EXPORT rt_hw_context_switch_to ; r0 的值是一个指针,该指针指向 to 线程的线程控制块的 SP 成员 ; 将 r0 寄存器的值保存到 rt_interrupt_to_thread 变量里 LDR r1, =rt_interrupt_to_thread STR r0, [r1] ; 设置 from 线程为空,表示不需要从保存 from 的上下文 LDR r1, =rt_interrupt_from_thread MOV r0, #0x0 STR r0, [r1] ; 设置标志为 1,表示需要切换,这个变量将在 PendSV 异常处理函数里切换的时被清零 LDR r1, =rt_thread_switch_interrupt_flag MOV r0, #1 STR r0, [r1] ; 设置 PendSV 异常优先级为最低优先级 LDR r0, =NVIC_SYSPRI2 LDR r1, =NVIC_PENDSV_PRI LDR.W r2, [r0,#0x00] ; read ORR r1,r1,r2 ; modify STR r1, [r0] ; write-back ; 触发 PendSV 异常 (将执行 PendSV 异常处理程序) LDR r0, =NVIC_INT_CTRL LDR r1, =NVIC_PENDSVSET STR r1, [r0] ; 放弃芯片启动到第一次上下文切换之前的栈内容,将 MSP 设置启动时的值 LDR r0, =SCB_VTOR LDR r0, [r0] LDR r0, [r0] MSR msp, r0 ; 使能全局中断和全局异常,使能之后将进入 PendSV 异常处理函数 CPSIE F CPSIE I ; 不会执行到这里 ENDP
rt_hw_context_switch_to() 只有目标线程,没有来源线程。这个函数里实现切换到指定线程的功能。
rt_hw_context_switch_to() ;在 Cortex-M 处理器架构里,基于自动部分压栈和 PendSV 的特性,上下文切换可以实现地更加简洁。 线程之间的上下文切换,如下图表示: 中断到线程的上下文切换可以用下图表示:
系统调度就是在就绪列表中寻找优先级最高的就绪线程,然后去执行该线程。
rt_schedule();
使用rt_hw_context_switch/rt_hw_context_switch_interrupt() 进行线程的切换,函数 rt_hw_context_switch() 和函数 rt_hw_context_switch_interrupt() 都有两个参数,分别是 from 线程和 to 线程。它们实现从 from 线程切换到 to 线程的功能。下图是具体的流程图:
rt_hw_context_switch_interrupt EXPORT rt_hw_context_switch_interruptrt_hw_context_switch PROC EXPORT rt_hw_context_switch ; 检查 rt_thread_switch_interrupt_flag 变量是否为 1 ; 如果变量为 1 就跳过更新 from 线程的内容 LDR r2, =rt_thread_switch_interrupt_flag LDR r3, [r2] CMP r3, #1 BEQ _reswitch ; 设置 rt_thread_switch_interrupt_flag 变量为 1 MOV r3, #1 STR r3, [r2] ; 从参数 r0 里更新 rt_interrupt_from_thread 变量 LDR r2, =rt_interrupt_from_thread STR r0, [r2]_reswitch ; 从参数 r1 里更新 rt_interrupt_to_thread 变量 LDR r2, =rt_interrupt_to_thread STR r1, [r2] ; 触发 PendSV 异常,将进入 PendSV 异常处理函数里完成上下文切换 LDR r0, =NVIC_INT_CTRL LDR r1, =NVIC_PENDSVSET STR r1, [r0] BX LR
PendSV 中断处理函数是 PendSV_Handler()。在 PendSV_Handler() 里完成线程切换的实际工作,下图是具体的流程图:
PendSV_Handler PROC EXPORT PendSV_Handler ; 关闭全局中断 MRS r2, PRIMASK CPSID I ; 检查 rt_thread_switch_interrupt_flag 变量是否为 0 ; 如果为零就跳转到 pendsv_exit LDR r0, =rt_thread_switch_interrupt_flag LDR r1, [r0] CBZ r1, pendsv_exit ; pendsv already handled ; 清零 rt_thread_switch_interrupt_flag 变量 MOV r1, #0x00 STR r1, [r0] ; 检查 rt_thread_switch_interrupt_flag 变量 ; 如果为 0,就不进行 from 线程的上下文保存 LDR r0, =rt_interrupt_from_thread LDR r1, [r0] CBZ r1, switch_to_thread ; 保存 from 线程的上下文 MRS r1, psp ; 获取 from 线程的栈指针 STMFD r1!, { r4 - r11} ; 将 r4~r11 保存到线程的栈里 LDR r0, [r0] STR r1, [r0] ; 更新线程的控制块的 SP 指针switch_to_thread LDR r1, =rt_interrupt_to_thread LDR r1, [r1] LDR r1, [r1] ; 获取 to 线程的栈指针 LDMFD r1!, { r4 - r11} ; 从 to 线程的栈里恢复 to 线程的寄存器值 MSR psp, r1 ; 更新 r1 的值到 psppendsv_exit ; 恢复全局中断状态 MSR PRIMASK, r2 ; 修改 lr 寄存器的 bit2,确保进程使用 PSP 堆栈指针 ORR lr, lr, #0x04 ; 退出中断函数 BX lr ENDP
转载地址:http://bdnii.baihongyu.com/