RT-Thread--内核移植
内核移植
- 内核移植就是指将 RT-Thread 内核在不同的芯片架构、不同的板卡上运行起来,能够具备线程管理和调度,内存管理,线程间同步和通信、定时器管理等功能。移植可分为 CPU 架构移植和 BSP(Board support package,板级支持包)移植两部分。
CPU移植
- 为了使 RT-Thread 能够在不同 CPU 架构的芯片上运行,RT-Thread 提供了一个 libcpu 抽象层来适配不同的 CPU 架构。libcpu 层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。
- RT-Thread 的 libcpu 抽象层向下提供了一套统一的 CPU 架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容。下表是 CPU 架构移植需要实现的接口和变量。
函数和变量 描述 rt_base_t rt_hw_interrupt_disable(void); 关闭全局中断 void rt_hw_interrupt_enable(rt_base_t level); 打开全局中断 rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit); 线程栈的初始化,内核在线程创建和线程初始化里面会调用这个函数 void rt_hw_context_switch_to(rt_uint32 to); 没有来源线程的上下文切换,在调度器启动第一个线程的时候调用,以及在 signal 里面会调用 void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); 从 from 线程切换到 to 线程,用于线程和线程之间的切换 void rt_hw_context_switch_interrupt(rt_uint32 from, rt_uint32 to); 从 from 线程切换到 to 线程,用于中断里面进行切换的时候使用 rt_uint32_t rt_thread_switch_interrupt_flag; 表示需要在中断里进行切换的标志 rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread; 在线程进行上下文切换时候,用来保存 from 和 to 线程
实现全局中断开关
- 无论内核代码还是用户的代码,都可能存在一些变量,需要在多个线程或者中断里面使用,如果没有相应的保护机制,那就可能导致临界区问题。RT-Thread 里为了解决这个问题,提供了一系列的线程间同步和通信机制来解决。但是这些机制都需要用到 libcpu 里提供的全局中断开关函数。分别是:
/* 关闭全局中断 */
rt_base_t rt_hw_interrupt_disable(void); /* 打开全局中断 */
void rt_hw_interrupt_enable(rt_base_t level);Cortex-M 架构上如何实现这两个函数,前文中曾提到过,Cortex-M 为了快速开关中断,实现了 CPS 指令,可以用在此处
CPSID I ;PRIMASK=, ; 关中断
CPSIE I ;PRIMASK=, ; 开中断
实现全局中断开关
在 rt_hw_interrupt_disable() 函数里面需要依序完成的功能是:
保存当前的全局中断状态,并把状态作为函数的返回值。
关闭全局中断。
- 在 context_rvds.S 中实现
;/*
; * rt_base_t rt_hw_interrupt_disable(void);
; */
rt_hw_interrupt_disable PROC ;PROC 伪指令定义函数
EXPORT rt_hw_interrupt_disable ;EXPORT 输出定义的函数,类似于 C 语言 extern
MRS r0, PRIMASK ; 读取 PRIMASK 寄存器的值到 r0 寄存器
CPSID I ; 关闭全局中断
BX LR ; 函数返回
ENDP ;ENDP 函数结束
打开全局中断
在 rt_hw_interrupt_enable(rt_base_t level) 里,将变量 level 作为需要恢复的状态,覆盖芯片的全局中断状态。在 context_rvds.S 中实现;
;/*
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable PROC ; PROC 伪指令定义函数
EXPORT rt_hw_interrupt_enable ; EXPORT 输出定义的函数,类似于 C 语言 extern
MSR PRIMASK, r0 ; 将 r0 寄存器的值写入到 PRIMASK 寄存器
BX LR ; 函数返回
ENDP ; ENDP 函数结束
实现线程栈初始化
- 在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init(),_rt_thread_init() 函数会调用栈初始化函数 rt_hw_stack_init(),在栈初始化函数里会手动构造一个上下文内容,这个上下文内容将被作为每个线程第一次执行的初始值。上下文在栈里的排布如下图所示:
下代码是栈初始化的代码:在栈里构建上下文;在 cpuport.c中实现;
rt_uint8_t *rt_hw_stack_init(void *tentry,
void *parameter,
rt_uint8_t *stack_addr,
void *texit)
{
struct stack_frame *stack_frame;
rt_uint8_t *stk;
unsigned long i; /* 对传入的栈指针做对齐处理 */
stk = stack_addr + sizeof(rt_uint32_t);
stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, );
stk -= sizeof(struct stack_frame); /* 得到上下文的栈帧的指针 */
stack_frame = (struct stack_frame *)stk; /* 把所有寄存器的默认值设置为 0xdeadbeef */
for (i = ; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
{
((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
} /* 根据 ARM APCS 调用标准,将第一个参数保存在 r0 寄存器 */
stack_frame->exception_stack_frame.r0 = (unsigned long)parameter;
/* 将剩下的参数寄存器都设置为 0 */
stack_frame->exception_stack_frame.r1 = ; /* r1 寄存器 */
stack_frame->exception_stack_frame.r2 = ; /* r2 寄存器 */
stack_frame->exception_stack_frame.r3 = ; /* r3 寄存器 */
/* 将 IP(Intra-Procedure-call scratch register.) 设置为 0 */
stack_frame->exception_stack_frame.r12 = ; /* r12 寄存器 */
/* 将线程退出函数的地址保存在 lr 寄存器 */
stack_frame->exception_stack_frame.lr = (unsigned long)texit;
/* 将线程入口函数的地址保存在 pc 寄存器 */
stack_frame->exception_stack_frame.pc = (unsigned long)tentry;
/* 设置 psr 的值为 0x01000000L,表示默认切换过去是 Thumb 模式 */
stack_frame->exception_stack_frame.psr = 0x01000000L; /* 返回当前线程的栈地址 */
return stk;
}
实现上下文切换
- 在不同的 CPU 架构里,线程之间的上下文切换和中断到线程的上下文切换,上下文的寄存器部分可能是有差异的,也可能是一样的。在 Cortex-M 里面上下文切换都是统一使用 PendSV 异常来完成,切换部分并没有差异。但是为了能适应不同的 CPU 架构,RT-Thread 的 libcpu 抽象层还是需要实现三个线程切换相关的函数:
rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用。
rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。
rt_hw_context_switch_interrupt ():在中断环境下,从当前线程切换到目标线程。
- 线程环境下,如果调用 rt_hw_context_switch() 函数,那么可以马上进行上下文切换;而在中断环境下,需要等待中断处理函数完成之后才能进行切换;
- 在 ARM9 等平台,rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 的实现并不一样。在中断处理程序里如果触发了线程的调度,调度函数里会调用 rt_hw_context_switch_interrupt() 触发上下文切换。中断处理程序里处理完中断事务之后,中断退出之前,检查 rt_thread_switch_interrupt_flag 变量,如果该变量的值为 1,就根据 rt_interrupt_from_thread 变量和 rt_interrupt_to_thread 变量,完成线程的上下文切换。
- 在 Cortex-M 处理器架构里,基于自动部分压栈和 PendSV 的特性,上下文切换可以实现地更加简洁。
- 线程之间的上下文切换,如下图表示:
- 硬件在进入 PendSV 中断之前自动保存了 from 线程的 PSR、PC、LR、R12、R3-R0 寄存器,然后 PendSV 里保存 from 线程的 R11\~R4 寄存器,以及恢复 to 线程的 R4\~R11 寄存器,最后硬件在退出 PendSV 中断之后,自动恢复 to 线程的 R0\~R3、R12、LR、PC、PSR 寄存器。
- 中断到线程的上下文切换可以用下图表示:
- 硬件在进入中断之前自动保存了 from 线程的 PSR、PC、LR、R12、R3-R0 寄存器,然后触发了 PendSV 异常。在 PendSV 异常处理函数里保存 from 线程的 R11\~R4 寄存器,以及恢复 to 线程的 R4\~R11 寄存器,最后硬件在退出 PendSV 中断之后,自动恢复 to 线程的 R0\~R3、R12、PSR、PC、LR 寄存器。
- 显然,在 Cortex-M 内核里 rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 功能一致,都是在 PendSV 里完成剩余上下文的保存和回复。所以我们仅仅需要实现一份代码,简化移植的工作。
实现 rt_hw_context_switch_to()
- rt_hw_context_switch_to() 只有目标线程,没有来源线程。这个函数里实现切换到指定线程的功能,下图是流程图:
- rt_hw_context_switch_to() 实现
;/*
; * void rt_hw_context_switch_to(rt_uint32 to);
; * r0 --> to
; * this fucntion is used to perform the first thread switch
; */
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] ; 设置标志为 ,表示需要切换,这个变量将在 PendSV 异常处理函数里切换的时被清零
LDR r1, =rt_thread_switch_interrupt_flag
MOV r0, #
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()/ rt_hw_context_switch_interrupt()实现:
- 函数 rt_hw_context_switch() 和函数 rt_hw_context_switch_interrupt() 都有两个参数,分别是 from 线程和 to 线程。它们实现从 from 线程切换到 to 线程的功能。下图是具体的流程图:
- rt_hw_context_switch()/rt_hw_context_switch_interrupt() 实现:、
;/*
; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
; * r0 --> from
; * r1 --> to
; */
rt_hw_context_switch_interrupt
EXPORT rt_hw_context_switch_interrupt
rt_hw_context_switch PROC
EXPORT rt_hw_context_switch ; 检查 rt_thread_switch_interrupt_flag 变量是否为
; 如果变量为 就跳过更新 from 线程的内容
LDR r2, =rt_thread_switch_interrupt_flag
LDR r3, [r2]
CMP r3, #
BEQ _reswitch
; 设置 rt_thread_switch_interrupt_flag 变量为
MOV r3, #
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 中断
- 在 Cortex-M3 里,PendSV 中断处理函数是 PendSV_Handler()。在 PendSV_Handler() 里完成线程切换的实际工作,下图是具体的流程图:
- PendSV_Handler 实现:
; r0 --> switch from thread stack
; r1 --> switch to thread stack
; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
PendSV_Handler PROC
EXPORT PendSV_Handler ; 关闭全局中断
MRS r2, PRIMASK
CPSID I ; 检查 rt_thread_switch_interrupt_flag 变量是否为
; 如果为零就跳转到 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 变量
; 如果为 ,就不进行 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 的值到 psp pendsv_exit
; 恢复全局中断状态
MSR PRIMASK, r2 ; 修改 lr 寄存器的 bit2,确保进程使用 PSP 堆栈指针
ORR lr, lr, #0x04
; 退出中断函数
BX lr
ENDP
实现时钟节拍
- 有了开关全局中断和上下文切换功能的基础,RTOS 就可以进行线程的创建、运行、调度等功能了。有了时钟节拍支持,RT-Thread 可以实现对相同优先级的线程采用时间片轮转的方式来调度,实现定时器功能,实现 rt_thread_delay() 延时函数等等。
- libcpu 的移植需要完成的工作,就是确保 rt_tick_increase() 函数会在时钟节拍的中断里被周期性的调用,调用周期取决于 rtconfig.h 的宏 RT_TICK_PER_SECOND 的值。
- 在 Cortex M 中,实现 SysTick 的中断处理函数即可实现时钟节拍功能。
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter(); rt_tick_increase(); /* leave interrupt */
rt_interrupt_leave();
}
BSP移植
- RT-Thread 提供了 BSP 抽象层来适配常见的板卡。如果希望在一个板卡上使用 RT-Thread 内核,除了需要有相应的芯片架构的移植,还需要有针对板卡的移植,也就是实现一个基本的 BSP。主要任务是建立让操作系统运行的基本环境,需要完成的主要工作是:
初始化 CPU 内部寄存器,设定 RAM 工作时序。
实现时钟驱动及中断控制器驱动,完善中断管理。
实现串口和 GPIO 驱动。
初始化动态内存堆,实现动态堆内存管理。
参考
- 《RT-Thread 编程指南》
RT-Thread--内核移植的更多相关文章
- linux 3.4.103 内核移植到 S3C6410 开发板 移植失败 (问题总结,日本再战!)
linux 3.4.103 内核移植到 S3C6410 开发板 这个星期差点儿就搭在这里面了,一開始感觉非常不值得,移植这样的浪费时间的事情.想立刻搞定,然后安安静静看书 & coding. ...
- 【转】 linux内核移植和网卡驱动(二)
原文网址:http://blog.chinaunix.net/uid-29589379-id-4708911.html 一,内核移植步骤: 1, 修改顶层目录下的Makefile ARCH ...
- 【转】 linux内核移植和驱动添加(三)
原文网址:http://blog.chinaunix.net/uid-29589379-id-4708909.html 原文地址:linux内核移植和驱动添加(三) 作者:genehang 四,LED ...
- Linux内核移植
实验步骤:(1)准备工作(2)修改顶层Makefile(3)修改falsh 分区(4)配置编译内核 下面以Linux2.6.30.4内核移植到gec2440为例: 一.准备工作:建立工作目录,下载内核 ...
- Linux内核移植到JZ2440
一.准备工作:1.Linux内核:Linux2.6.22.6,可从www.kernel.org上下载:2.交叉工具编译链:arm-linux-gcc-3.4.5-glibc-2.3.6:3.yaffs ...
- ARM-Linux内核移植之(二)——Linux2.6.22内核移植
平台:mini2440 交叉工具链:arm-linux-gcc-4.3.2 一.内核移植基本知识 移植内核也叫构建BSP(boardsupprot packet).BSP的作用有两个:一是为内核运行 ...
- linux 内核移植和根文件系统的制作【转载】
原文地址:http://www.cnblogs.com/hnrainll/archive/2011/06/09/2076214.html 1.1 Linux内核基础知识 在动手进行Linux内核移植之 ...
- linux 内核移植和根文件系统的制作
1.1 Linux内核基础知识 在动手进行Linux内核移植之前,非常有必要对Linux内核进行一定的了解,下面从Linux内核的版本和分类说起. 1.1.1 Linux版本 Linux内核的版本号 ...
- linux内核(二)内核移植(DM365-DM368开发攻略——linux-2.6.32的移植)
一.介绍linux-2.6.32: Linux-2.6.32的网上介绍:增添了虚拟化内存 de-duplicacion.重写了 writeback 代码.改进了 Btrfs 文件系统.添加了 ATI ...
- LINUX为什么要进行内核移植 内核移植的作用
LINUX为什么要进行内核移植 内核移植的作用,不移植能用么? LZ的问题应该是为什么要重新编译内核吧.既然你已经可以跑了,证明你现在用的内核已经移植到你用的硬件上,自然你也不需要做什么移植.通常 ...
随机推荐
- 【线段树】HDU 1166 敌兵布阵
这道题目是线段树里面最基础的单点更新问题. 设计的知识点包括线段树的单点更新和区间查询. 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1166 G++ ...
- 静态站点生成器-md-mkdocs
推荐指数:
- 【Leetcode_easy】1170. Compare Strings by Frequency of the Smallest Character
problem 1170. Compare Strings by Frequency of the Smallest Character 参考 1. Leetcode_easy_1170. Compa ...
- VUE中事件修饰符:stop prevent self capture
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...
- SPSS 2019年10月17日 21:46:38 今日学习总结
数据库: 开放数据库链接是为了解决异构数据库间的数据共享而产生的,现已成为WOSA的主要部分和基于Windows环境的一种数据库访问接口标准ODBC为异构数据库访问提供一个接口,允许应用程序以SQL为 ...
- mac tar 解压
1.下载mac上对应rar版本 http://www.rarlab.com/download.htm2.利用tar名解压下载的rarosx-5.4.0.tar.gz,版本可能会更新tar xzvf a ...
- bam文件格式说明
bam文件说明 bam文件和sam文件内容其实是一样的,只是bam是二进制的压缩文件,需要通过特定的软件来进行查看,bam文件通常可以理解为12个字段组成 BAM格式分为header section( ...
- MySQL(八)事务的隔离级别
一.事务的并发问题 1.脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据 2.不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并 ...
- [转帖]MyCat教程【简单介绍】
MyCat教程[简单介绍] 2019-10-15 10:27:23 波波烤鸭 阅读数 618 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. ...
- [转帖]HAProxy 7层 负载均衡
HAProxy 7层 负载均衡 https://www.cnblogs.com/jicki/p/5546902.html HAProxy 系统 CentOS 5.8 x64 wget http://h ...