DIY简单的RTOS(一)

说明

在看了很多关于RTOS的文章,一直想做一个简单的RTOS,苦于现有资料非常少,在看了很多关于现有RTOS的底层实现,再结合相关文章,完成一个简单的RTOS demo。代码难免有不合理之处,仅当个人学习做笔记使用。

项目代码



什么是RTOS

实时操作系统(RTOS)是指当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统做出快速响应,调度一切可利用的资源完成实时任务,并控制所有实时任务协调一致运行的操作系统。提供及时响应和高可靠性是其主要特点。




PenSV异常

在RTOS内核中,一个任务可通过手动触发PendSV异常,在PendSV异常服务函数中实现任务切换(切换到下一个任务)

我们从相关手册找到设置PendSV异常的寄存器中断及状态控制寄存器ICSR

在这里插入图片描述
只需要将该寄存器的28位置1即可。
若当前没有更高的优先级中断,则会进入到PendSV异常处理函数中;否则等待中断完成进入到异常处理函数。


设置PendSV的优先级
在这里插入图片描述
通常来说,PendSV的优先级都没有中断的优先级高,因此设置PendSV优先级最低,从而避免在外部中断服务函数中产生任务切换。
可以设置成0-255的优先级,这是设置成0xFF。




相关汇编指令

import:翻译为进口或引入,表明要调用的函数为外部文件定义

export:翻译为出口或输出,表明该符号可以被外部模块使用,类似于C中的extern功能。

这里使用IMPORT导入C语言中的变量。


LDR{条件} 目的寄存器 <存储器地址>
作用:将存储器地址所指地址处连续的4个字节(1个字)的数据传送到目的寄存器中。

  • LDR R0, = blockPtr //把blockPtr表示的值加载到R0寄存器中
  • LDR R0,[R0] //将存储器地址为R0的字数据读入寄存器R0

如果blockPtr是一个地址,如数组的首地址,那么首先R0中存储的就是数组的首地址,接着把地址R0的字数据读入R0,相当于R0中保存的是数组的第一个元素的值。

stmdb(地址先减而后完成操作)和ldmia指令一般配对使用,stmdb用于将寄存器压栈,ldmia用于将寄存器弹出栈,作用是保存使用到的寄存器。


指令:stmdb sp!,{r0-r12,lr}
含义:sp = sp - 4,先压lr,sp = lr(即将lr中的内容放入sp所指的内存地址)。sp = sp - 4,再压r12,sp = r12。sp = sp - 4,再压r11,sp = r11…sp = sp - 4,最后压r0,sp = r0。


LDMIA , LDMIA R0!,{R1,R2}
是指将R0指向的单元中的数据读出到R1,R2中(R0自动加1)



相关代码

main.c

#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){
	//设置pendsv优先级最低和置1.
	MEM8(NVIC_SYSPRI2) = NVIC_PENDSV_PRI;
	MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET;
}
typedef struct _BlockType_T{
	unsigned long *stackPtr;
}BlockType_t;
BlockType_t *blockPtr;
unsigned long stackBuffer[1024];
BlockType_t block;
block.stackPtr = &stackBuffer[1024];	//指向最后一个单元
blockPtr = &block;

switch.c

/**
 * 保存R4-R11寄存器
 * 内嵌汇编
*/
__asm void PendSV_Handler(void){
    //映射到pendsv_handler中
    IMPORT blockPtr
    LDR R0, = blockPtr
    LDR R0, [R0]
    LDR R0, [R0]//获取stackPtr的值

    STMDB R0!,{R4-R11}   //批量写寄存器(高-低)
    
    LDR R1,=blockPtr
    LDR R1,[R1]
    LDR R0,[R1]

    //测试代码
    ADD R4,R4, #1
    ADD R5,R5, #1

    //恢复寄存器
    LDMIA R0!,{R4-R11}
    //退出异常
    BX LR
}

创建一个.c文件即可,开头是__asm指明是汇编操作,因为C语言无法完成对寄存器的操作。




代码说明

在汇编代码中,我们连续用了3条LDR指令,其中有两条是重复的。
在main.c文件中,首先获取blockPtr的地址送到R0,接着读取地址R0对应的值(也是地址),接着读取地址R0的值(数组地址)。
例:
在这里插入图片描述

  1. 把blockPtr的地址(0x20000000)保存到R0
  2. 取地址对应的值(0x20000008) 保存到R0
  3. 取地址对应的值(0x20001024) 保存到R0

stackPtr的值时0x20001024,指向数组stackBuffer第1025个元素(1开始算起)的地址。


接着保存R11-R4寄存器的内容到R0指向的内容中。 此时R0指向第1025个元素的地址,而我们的数组大小才1024个,在STMDB指令中,地址先自减,在存储对应的寄存器的值。

接着把R0的值仍然设置为0x20001024(数组第1025个元素地址)
R4 = R4 +1
R5 = R5 + 1

使用LDMIA 指令做测试,把R0的值存储到寄存器R4-R11中。

在这里插入图片描述
地址自增32/4 = 8个寄存器(R4-R11),且R4和R5寄存器的值被修改成0(这里做测试使用)。




运行说明

这里基于ARMCM3仿真实现,以后会在单片机上运行。仿真的时候我们看是否跳入到了PendSV服务函数中,是否保存了相应的数据。任务的切换关键在于上下文的保存,可通过压栈和出栈来实现。
在这里插入图片描述




参考

汇编:stmdb和ldmia指令

参考01课堂:从0到1实现RTOS

猜你喜欢

转载自blog.csdn.net/qq_40318498/article/details/107522207