当前位置: 首页 > news >正文

实现 RTOS 操作系统 【零】内核编程实践

一、概述

Cortx-M3 内核是 ARM 公司开发的 CPU 内核,完整的 MCU 芯片集成了 Cortex-M3 内核以及其他组件。

其内核部分和调试系统由 ARM 设计,通过内部总线和芯片厂设计的外设部分通讯。

Cortex-M3权威指南 图 1.1

1.1 工作模式以及权限级别分类

1.1.1 两种工作模式 (左边绿色框)

线程模式 (Thread mode):非异常状态下 (正常运行程序) 工作,一般主程序、RTOS 任务就是运行在这个模式下。

处理器模式 (Handler mode):进入异常 (比如中断、Fault、SVC 调用) 时进入,只能是特权级,绝对不会是用户级。中断服务函数、异常处理函数就是运行在这个模式里。

1.1.2 两种特权级 (右边紫色/蓝色框)

特权级 (Privileged level):可以访问所有寄存器、所有内存区域。可以访问所有存储区域 (包括外设寄存器、系统控制块、调试寄存器等)。可以使用 MSP (主堆栈指针) 和 PSP (进程堆栈指针),并且可以自由切换。

中断应用程序必须是特权级的,主程序可以是特权级的也可以是用户级的。处理器复位后在特权级模式下运行。在特权级模式下可以通过修改 CONTROL 寄存器进入用户级代码。用户级代码只能通过SVC中断,出发SVC异常才能重新进入特权级。

Cortex-M3权威指南 图 2.4 Cortex‐M3 下的操作模式特权级别

例如:MRS、MSR 指令只可以在特权级模式下使用。需要通过 MRS、MSR 访问的特殊功能寄存器,除了APSR 可以在用户级访问:

Cortex-M3权威指南 表 4.31MRS/MSR 可以使用的特殊功能寄存器

下面给出一个指定 PSP 进行更新的例子:

LDR R0, =0x20008000
MSR PSP, R0
BX LR         ;如果是从异常回到线程状态,则使用新的PSP的值作为栈顶指针

用户级 (Unprivileged level):权限受限:不能访问受保护的寄存器或地址空间。常用于提高系统安全性,避免应用代码乱改硬件寄存器。

二、寄存器组

1.1 寄存器组概述

任务在执行他的代码时,必须要用到这些内核寄存器做一些算术、逻辑等运算处理,这部分寄存器相当于任务运行状态的一部分。在进行任务切换时,我们需要在切换代码中将这部分寄存器的值保存/恢复。

寄存器组:

Cortex-M3 权威指南 图 3.1 Cortex‐M3 的寄存器组

特殊功能寄存器:

Cortex-M3 权威指南 图 3.1 Cortex‐M3 的寄存器组


其中比较重要的有:

R15 程序计数器 (PC)保存了当前代码执行的指令位置地址。
R14 连接寄存器 (LR)则保存了当前函数执行完成后返回的指令位置地址。
R13 寄存器 (MSP)指明当前堆栈位置地址。
R13 主堆栈指针 (MSP)是我们正常程序所使用的,进程堆栈指针(PSP)是任务所使用的,我们可以通过对相关寄存器置位进行切换。

其他都是临时变量寄存器,编译器把 C 语言代码会转化成汇编会自动使用这些寄存器。

1.2 MSP 和 PSP 寄存器

对于 ARM Cortx-M3 是如何实现中断后保存现场和恢复,其使用了 R13 双堆栈机制:

除了简单了解其有 R0-R15 外,还要特别注意双堆栈寄存器 R13,用于实现中断/异常所用的栈与任务所用的栈相分离,互不干扰。硬件自动切换到 MSP 指向的堆栈来配合执行相应的处理程序,而退出后,自动切换到 PSP 指向的堆栈空间再执行任务代码。

无论是哪种堆栈,其均使用下面这幅图的增长模式:每次压栈,堆栈地址递减。且堆栈指针 SP(MSP/PSP)。特别注意:SP 总是指向最后压栈的单元。

1.3 程序状态寄存器

图 3.3 Cortex‐M3 中的程序状态寄存器 (xPSR)
图 3.4 合体后的程序状态寄存器 (xPSR)

1.4 异常屏蔽寄存器

表 3.2 Cortex-M3 的屏蔽寄存器

1.5 储存映射

Cortex-M3 权威指南 图 5.1 Cortex‐M3 存储器映射

三、堆栈

Cortex-M3使用的是"向下生长的满栈"模型。堆栈指针 SP 指向最后一个被压入堆栈的 32 位数值。在下一次压栈时,SP 先自减 4,再存入新的数值。

3.1 压栈操作

压栈后,首先将 SP 寄存器地址自减 4,然后压入。

Cortex-M3 权威指南 图 3.12

3.2 出栈操作

出栈后,首先将 SP 寄存器地址自增 4,然后弹出。

Cortex-M3 权威指南 图 3.13

四、异常/中断响应序列

4.1 系统异常编号

Cortex-M3 权威指南 图 7.1 系统异常清单

外部中断列表

Cortex-M3 权威指南 图 7.2 外部中断清单

4.2 进入异常中断

步骤一:硬件将 xPSR、PC、LR、R12 和 R0~R3 自动压入当前堆栈,其它寄存器根据需要由 ISR 自行保存。

Cortex-M3 权威指南 表 9.1 入栈顺序以及入栈后堆栈中的内容

步骤二:从中断向量表取入口地址。

Cortex-M3 权威指南 表 7.6 上电后的向量表
  • SP:入栈后保存
  • PC:更新为中断服务入口地址
  • LR:更新为特殊的 EXC_RETURN 值

4.2 退出异常中断

步骤一:执行返回指令,如 BX、LR
步骤二:恢复先前入栈的寄存器。出栈顺序与入栈时的相对应,堆栈指针的值也改回去。
步骤三:从原中断发生位置继续往下运行。
注:在返回时,会根据 EXC_RETURN 值来决定返回动作。

Cortex-M3 权威指南 表 9.3 EXC RETURN 位段详解
Cortex-M3 权威指南 表9.4 合法的 EXC RETURN 值及其功能

4.3 复位异常响应序列

MCU 复位响应如下,将 0x000000000 和 0x000000004 的内容分别赋值给 PC 和 MSP 寄存器,这样程序就开始运行了。

4.4 PendSV 异常

在 PendSV 中执行 RTOS 上下文切换(即不同任务间切换)。工作原理:配置为最低优先级,上下文切的请求将自动延迟到到其它的 ISR 都完成后才处理,并且可被其它异常/中断抢占。

也就是说 PendSV 是一个中断异常,那 PendSV 和其他的中断异常有什么区别呢? 

Cortex-M3 权威指南 图 7.17 使用 PendsV 控制上下文切换

个中事件的流水账记录如下:

  1. 任务A呼叫 SVC 来请求任务切换 (例如,等待某些工作完成)。
  2. OS 接收到请求,做好上下文切换的准备,并且悬起一个 PendSV 异常。
  3. 当 CPU 退出 SVC后,它立即进入 PendSV,从而执行上下文切换。
  4. 当 PendSV 执行完毕后,将返回到任务 B,同时进入线程模式。
  5. 发生了一个中断,并且中断服务程序开始执行。
  6. 在 ISR 执行过程中,发生 SysTick 异常,并且抢占了该 ISR。
  7. OS 执行必要的操作,然后悬起 PendsV 异常以作好上下文切换的准备。
  8. 当 SysTick 退出后,回到先前被抢占的 ISR 中,ISR继续执行。
  9. ISR 执行完毕并退出后,PendsV 服务例程开始执行,并且在里面执行上下文切换9。
  10. 当 PendSV 执行完毕后,回到任务 A,同时系统再次进入线程模式。

如果我们仔细看上图会发现步骤 8 的时候,SysTick 会先回到之前抢占的 ISR 而不是,而不是立刻进入 PendSV 中 (在 RTOS 中 SysTick 中都会调用 PendSV 中断)。

这是因为 PendSV 可以被悬起,触发 PendSV 后他会等到目前所有 ISR 中断结束再去中断。避免打断其他的中断,破坏 RTOS 的实时性。因为其他中断可能很紧急,不容被滞后。

所以PendSV的最大特点就是,它是系统级别的异常,但它又天生支持缓期执行。

五、指令详解

Cortex-M3 使用的是 Thumb-2 指令集。长度可为 16 位或者 32 位。指令可以携带后缀,如有条件的执行。示例:

CBZ R0,label

如果 R0 为 0,则跳转;否则什么都不做。

5.1 典型写法

操作码 操作数1、操作数2...    ;注释

示例:

MOV R0,#0x12    ;R0 0x12
MOV R1,#'A'     ;R1字的ASCII码

5.2 指令分类

5.3 储存器访问

LDR/LDRB Rd, = LABEL    ;加载符号LABEL对应的地址,储存到Rd中
LDR/LDRB Rd,    [Rs]    ;从RS寄存器中取出地址,读取相应的32位/8位数据,存储到Rd寄存器
STR/STRB Rd,    [RS]    ;从RS寄存器中取出地址,将Rd中的32位/8位数据存储到相应的地址处

5.4 批量储存器访问

LDMIA Rd!, {Rn, .... Rm}    ;从Rd处连续多次递增地址读取32位数据,存储到Rn.…Rm寄存器列表
STMDB Rd!, {Rn,. Rm}        ;从Rd处连续多次递减地址存储32位数据,数据来自Rn..,Rm寄存器列表

IA(Increase After):在操作完成后递增地址;

! 操作结束后,将最终的地址保存到 Rd 寄存器中;

DB(Decrease Before):在操作开始前递减地址;

5.5 MRS 和 MSR

用于访问 xPSR、PSP、MSP 等

MRS Rn,    <Sreg>      ;加载能寄存器的值到 Rn
MSR <Sreg>, Rn         ;存储 Rn 的值到能寄存器

5.6 中断开关

CPSID I    ;关中断
CPISE I    ;开中断

5.7 无条件跳转

BX Rn    ;移到 寄存器 reg 给出的地址,例BX LR可用于子程序的返回

5.8 比较并件跳条转

CBZ  Rn,<label>     ;如果Rn寄存器值为0,则跳转到label对应的指令,否则执行下一条指令
CBNZ Rn,<label>    ;如果Rn寄存器值不为0,则跳转到label对应的指令,否则执行下一条指令

六、内核编程实例

任务目标:使用 PendSVC 触发异常,在异常处理函数中,保存 R4~R11 寄存器到缓冲区,再恢复R4~R11 寄存器,以模拟任务切换时的寄存器保存与恢复。

6.1 系统异常优先级寄存器

我们根据手册将 PendSV 的优先级降至最低:

Cortex-M3 权威指南 表D.16 系统异常优先级寄存器
#define NVIC_SYSPRI2        0xE000ED22      // 系统优先级寄存器
#define NVIC_PENDSV_PRI     0x000000FF      // 配置优先级
MEM8(NVIC_SYSPRI2) = NVIC_PENDSV_PRI;   // 向NVIC_SYSPRI2写NVIC_PENDSV_PRI,设置其为最低优先级

6.2 中断控制及状态寄存器 ICSR

我们需要悬起 PendSVC 中断,我们根据手册地址定义出中断控制寄存器的宏定义:

Cortex-M3 权威指南 表8.5 中断控制及状态寄存器 ICSR
#define NVIC_INT_CTRL       0xE000ED04      // 中断控制及状态寄存器
#define NVIC_PENDSVSET      0x10000000      // 触发软件中断的值
MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET;    // 向NVIC_INT_CTRL写NVIC_PENDSVSET,用于PendSV

6.3 阶段代码总结

此段代码包括将 PendSVC 中断优先级降至最低,并且悬起 PendSVC 中断,也就是让内核进入中断,并且进入 PendSVC 的异常中断函数。

#define NVIC_INT_CTRL       0xE000ED04      // 中断控制及状态寄存器
#define NVIC_PENDSVSET      0x10000000      // 触发软件中断的值
#define NVIC_SYSPRI2        0xE000ED22      // 系统优先级寄存器
#define NVIC_PENDSV_PRI     0x000000FF      // 配置优先级#define MEM32(addr)         *(volatile unsigned long *)(addr)
#define MEM8(addr)          *(volatile unsigned char *)(addr)void triggerPendSVC (void) 
{MEM8(NVIC_SYSPRI2) = NVIC_PENDSV_PRI;   // 向NVIC_SYSPRI2写NVIC_PENDSV_PRI,设置其为最低优先级MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET;    // 向NVIC_INT_CTRL写NVIC_PENDSVSET,用于PendSV
}int main () 
{triggerPendSVC();for (;;) {__nop();}return 0;
}__asm void PendSV_Handler ()
{BX   LR
}  

6.3 PendSVC 自动执行的步骤

如果我们保存现场,并不是所有的寄存器都需要我们手动保存再写入,PendSV 中断会像普通中断一样会帮我们自动保存当退出时,会帮我们自动恢复这些寄存器。

响应异常的第一个行动,就是自动保存现场的必要部分:依次把 xPSR、PC、LR、R12 以及 R3‐R0 由硬件自动压入适当的堆栈中。如果当响应异常时,当前的代码正在使用 PSP,则压入 PSP,即使用线程堆栈˗否则压入 MSP,使用主堆栈。一进入了服务例程,就将一直使用主堆栈。 

为什么不压栈 R4‐R11 寄存器呢,因为 ARM 上,有一套的C语言编译调用标准约定 (C/C++ Procedure Call Standard for the ARM ArchitectureNJ, AAPCS, Ref5) 它使得中断服务例程能用 C语言编写。使汇编后的文件符合标准。

下图为进入中断异常后 ARM 内核自动保存的寄存器:

Cortex-M3  表 9.1 入栈顺序以及入栈后堆栈中的内容

现在我们知道了 PendSV 会帮我们自动压栈 xPSR、PC、LR、R12 以及 R3‐R0,然后等我们执行完毕 PendSV 中的代码后,退出 PendSV 时中断时则会自动回弹。当然,我们我们需要实现保存完整的现场,则需要手动压栈 R4‐R11 并且恢复。

6.5 修改后的 PendSVC 函数

在阅读下面这段汇编的时候,我们先有一个顺序捋清:

blockPtr 的值 = block 的地址

block 的值 = stackBuffer[1024] 的地址

__asm void PendSV_Handler ()
{//相当于c语言extren 导入blockPtr这个变量IMPORT  blockPtr// 加载寄存器存储地址LDR     R0, =blockPtr   //R0等于blockPtr变量地址LDR     R0, [R0]        //blockPtr解地址 此时R0等于BlockPtr的值,也就是block的地址LDR     R0, [R0]        //这还没完 此时R0的值只是block的地址,还需再解一次才能得到stackBuffer[1024]的地址// 保存寄存器STMDB   R0!, {R4-R11}   //递减读取进数组中,所以我们用stackBuffer[1024]的地址// 将最后的地址写入到blockPtr中LDR     R1, =blockPtr   //R1等于blockPtr变量地址LDR     R1, [R1]        //blockPtr解地址 此时R1等于blockPtr的值,也就是block的地址STR     R0, [R1]        //此时R0是栈顶,也就是stackBuffer[1024-7]的地址 此时将stackBuffer[1024-7]的地址赋给block的值// 修改部分寄存器,用于测试ADD R4, R4, #1ADD R5, R5, #1// 恢复寄存器LDMIA   R0!, {R4-R11}   //弹出寄存器 恢复到R4-R11// 异常返回BX      LR  //LR保存了子程序返回的代码地址 BX返回
}

6.6 效果演示

在压栈前 R4-R11 寄存器的值

测试修改 R4 R5 的值

在出栈后 R4-R11 寄存器的值

http://www.dtcms.com/a/446504.html

相关文章:

  • 做产品推广哪个网站好欧米茄表官网
  • 带后台的免费网站模板网站建设培训班上的讲话
  • 【学习笔记】泊松表面重建探讨
  • 做彩票网站需要学习什么深圳招工包吃住8000元
  • 株洲网站建设 磐石网络中国环球贸易网
  • 如何买域名发布网站电子商务网站建设类论文
  • 6. linux shell命令(2)基本系统维护命令与用户管理
  • 我的智能清洁日记:一台有鹿机器人的社区见闻录
  • 建英文网站费用找外包开发一个小程序需要多少钱
  • C++中的装饰器模式变体
  • 【代码随想录day 35】 力扣 416. 分割等和子集
  • 网站的关键字 设置php 网站后台管理系统
  • 沈阳手机端建站模板大数据适合什么人学
  • C语言-字符函数和字符串函数
  • 济南制作网站制作公司策划wordpress自动加载链接
  • 网站怎么设置标题国外企业网址
  • page指令元素
  • Postgresql源码(149)SIMD应用与性能测试
  • 知名网站建设加盟合作wordpress 腾讯视频插件下载
  • 重庆知名网站建设公司怎么去推广自己的网站
  • 深入 Pytest:用 Fixture 解锁高效 Python 测试之道
  • 一般做个网站需要多少钱建立网站需要做什么
  • Linux内核进程管理子系统有什么第六十二回 —— 进程主结构详解(58)
  • 博客系统-性能测试报告
  • 英文网站建设技术怎么建立博客网站
  • LangChain入门实践3:PromptTemplate提示词模板详解
  • 9.Spring ai alibaba 运维助手实战
  • 网站编写流程wordpress云采集
  • 找人建个网站多少钱淘宝客如何做免费的网站
  • maven-setting配置