博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
RT-Thread代码启动过程与线程切换的实现
阅读量:4092 次
发布时间:2019-05-25

本文共 6974 字,大约阅读时间需要 23 分钟。

文章目录

1、RT-Thread代码启动过程

1.1 启动流程图

系统先从启动文件开始运行,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口 main(),如下图所示:

在这里插入图片描述

1.2 以MDK为例

系统启动后先从汇编代码 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中写一些应用代码:

在这里插入图片描述

1.3 总结

可以这样使用给main函数打补丁:

int $Sub$$main(void){
//添加补丁函数 $Super$$main(); //使用本句直接转到main()运行}

当然,main()函数也可以是自己的其他函数,操作都是一样的,换一下函数名就好了

2、RT-Thread线程切换过程

首先查看这一章节,明白RT-Thread链表及线程的实现方法。

2.1 实现就绪列表

线程创建好之后,我们需要把线程添加到就绪列表里面, 表示线程已经就绪,系统随时可以调度。

2.1.1. 实现就绪列表

就绪列表实际上就是一个 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]); }

在这里插入图片描述

2.1.2 将线程插入就绪列表

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);}

在这里插入图片描述

2.2 实现调度器

调度器是操作系统的核心,其主要功能就是实现线程的切换,即从就绪列表里面找到优先级最高的线程,然后去执行该线程。

2.2.1 调度器初始化

rt_system_scheduler_init();

2.2.2 启动调度器

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 */}

2.2.2 第一次线程切换

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 的特性,上下文切换可以实现地更加简洁。
线程之间的上下文切换,如下图表示:
在这里插入图片描述
中断到线程的上下文切换可以用下图表示:
在这里插入图片描述

2.2.3 系统调度

系统调度就是在就绪列表中寻找优先级最高的就绪线程,然后去执行该线程。

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

2.2.4 线程切换

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/

你可能感兴趣的文章
最受欢迎的前端框架Bootstrap 入门
查看>>
JavaScript编程简介:DOM、AJAX与Chrome调试器
查看>>
通过Maven管理项目依赖
查看>>
通过Spring Boot三分钟创建Spring Web项目
查看>>
Spring的IoC(依赖注入)原理
查看>>
Guava快速入门
查看>>
Java编程基础:static的用法
查看>>
Java编程基础:抽象类和接口
查看>>
Java编程基础:异常处理
查看>>
Java编程基础:了解面向对象
查看>>
新一代Java模板引擎Thymeleaf
查看>>
Spring MVC中使用Thymeleaf模板引擎
查看>>
Spring Boot构建简单的微博应用
查看>>
Spring处理表单提交
查看>>
Spring MVC异常处理
查看>>
Leetcode 1180. Count Substrings with Only One Distinct Letter [Python]
查看>>
PHP 7 的五大新特性
查看>>
php使用 memcache 来存储 session
查看>>
php实现socket(转)
查看>>
PHP底层的运行机制与原理
查看>>