|
教你编写最简单的CM3操作系统,160行实现基本任务创建与切换,助你学习CM3与RTOS的精髓.
[复制链接]
如题,任务创建与上下文切换是跟硬件息息相关的,而这恰恰是RTOS编写的最难点,抛开这些功能,剩下的就是双向链表增删改操作了,本例用最精简的方式实现了任务创建与切换,OS启动等功能,并运用了Cortex-M3的先进特性,非传统关中断实现方式,助你学习CM3与RTOS的精髓
关于RTOS的理论性知识,实在是无力多讲,资料太多了,请自行寻找吧!
关于Cortex-M3,它实在是太优秀了,它的很多特性就是为了RTOS而生的
你可以下载代码,在keil下执行软件仿真调试即可,区区160行代码(不算注释),相信你能搞明白的
本例固定4个任务,main函数是第一个任务,在main函数再创建2个任务,简单轮转调度,任务管理不在本文讨论内容,请见谅
注释偏少,不懂请问!
直接上代码:
OS核心部分
- #include "os.h"
- typedef struct TCB{
- int reserve[3];
- void* stack; // 任务栈顶地址 汇编使用,offset 0x0C
- int deleteFlag;
- //...
- }TCB;
- struct _OS{
- TCB* run; // 正在运行的任务 汇编使用,offset 0x00
- TCB* rdy; // 就绪的任务 汇编使用,offset 0x04
- // ...
- }os;
- __asm void SVC_Handler (void){
- PRESERVE8
- THUMB
- PUSH {LR}
- MRS R0,PSP // Read PSP
- LDM R0,{R0-R3,R12} // Read R0-R3,R12 from stack
- BLX R12 // Call SVC Function
- MRS R1,PSP // Read PSP
- STR R0,[R1,#0x0] // set return values
- POP {PC}
- ALIGN
- }
- __asm void PendSV_Handler (void){
- PRESERVE8
- THUMB
- IMPORT os
- IMPORT OS_Sched
-
- PUSH {LR}
- MRS R0,PSP
- STMFD R0!,{R4-R11}
- MSR PSP,R0
-
- BL OS_Sched // C语言任务调度
-
- // 切换任务,看C示意代码
- // os.run->stack = PSP;
- LDR R2,=os
- LDR R1,[R2,#0x0]
- MRS R3,PSP
- STR R3,[R1,#0xC]
- // os.run = os.rdy;
- LDR R0,[R2,#0x4]
- STR R0,[R2,#0x0]
- // PSP = os.rdy->stack
- LDR R1,[R2,#0x4]
- LDR R0,[R1,#0xC]
- MSR PSP,R0
-
- MRS R0,PSP
- LDMFD R0!,{R4-R11}
- MSR PSP,R0
- POP {PC}
- ALIGN
- }
- /**
- * [url=home.php?mod=space&uid=159083]@brief[/url] 悬起PendSV,由于此中断优先级最低,所以若是在SVC_Handler或更高级别中断函数中设置的,
- * PendSV_Handler函数不会立即执行, [url=home.php?mod=space&uid=418085]@see[/url] <Cortex-M3权威指南>
- */
- __asm void OS_PendSV (void){
- PRESERVE8
- THUMB
- NVIC_INT_CTRL EQU 0xE000ED04
- NVIC_PENDSVSET EQU 0x10000000
- LDR R0, =NVIC_INT_CTRL
- LDR R1, =NVIC_PENDSVSET
- STR R1, [R0]
- BX LR
- ALIGN
- }
- void _OS_TaskDeleteSelf(void){
- // 本例没有实现 ...
- // 一般做法是将该TCB从链表中移除
- os.run->deleteFlag = 1;
- OS_PendSV();
- }
- /**
- * @brief 当任务函数执行返回时,跳转到此函数, @see OS_TaskStackInit
- */
- __asm void ASM_TaskDelete(void){
- PRESERVE8
- THUMB
- IMPORT _OS_TaskDeleteSelf
- LDR R12,=_OS_TaskDeleteSelf
- SVC 0
- ALIGN
- }
- /**
- * @brief 堆栈初始化
- */
- __asm void* OS_TaskStackInit(void (*taskFun)(void),void* stk,void* argv){
- PRESERVE8
- THUMB
- MOV R3,#0x01000000 // 初始化PSR的值
- STMFD R1!, {R3} // push xPSR
- STMFD R1!, {R0} // push PC = taskFun
- LDR R0,=ASM_TaskDelete
- STMFD R1!,{R0} // push LR = 删除任务函数的地址
- MOV R0,#0x0
- STMFD R1!,{R0} // push R12
- STMFD R1!,{R0} // push R3
- STMFD R1!,{R0} // push R2
- STMFD R1!,{R0} // push R1
- STMFD R1!,{R2} // push R0 = argv
- STMFD R1!,{R0} // push R11
- STMFD R1!,{R0} // push R10
- STMFD R1!,{R0} // push R9
- STMFD R1!,{R0} // push R8
- STMFD R1!,{R0} // push R7
- STMFD R1!,{R0} // push R6
- STMFD R1!,{R0} // push R5
- STMFD R1!,{R0} // push R4
- // 返回新栈顶
- MOV R0, R1
- BX LR
- ALIGN
- }
-
- /**
- * @brief 更改CPU模式,并设置PendSV的优先级为最低
- */
- __asm void OS_ChangeCpuMode (void* stk){
- PRESERVE8
- THUMB
-
- NVIC_SYSPRI2 EQU 0xE000ED20 // 系统优先级寄存器(2)
- NVIC_PENDSV_PRI EQU 0xFFFF0000 // PendSV中断和系统节拍中断 (都为最低,0xff).
- MSR PSP,R0
- // 设置中断优先级,将PendSV设为最低
- LDR R0, =NVIC_SYSPRI2
- LDR R1, =NVIC_PENDSV_PRI
- STR R1, [R0]
- // 更换CPU模式,使线程模式运行在特权级,#0x3则运行在用户级
- MOV R0,#0x2
- MSR CONTROL,R0
- // 开中断
- CPSIE I
-
- BX LR
- ALIGN
- }
- /**
- * @brief 切换到下一个任务
- */
- void _OS_TaskPass(void){
- OS_PendSV();
- }
- /// 引用配置文件中的定义,os是一个模块,是要被编译成lib文件的,所以这里只要声明即可
- extern int const IDLE_TASK_TACK_SIZE;
- extern uint64_t IdleTaskTackBuff[];
-
- extern int const FIRST_TASK_TACK_SIZE;
- extern uint64_t FirstTaskTackBuff[];
- /// 本例固定定义4个TCB,实际编写时应采用声明,由os配置文件做具体定义,同上
- /// 这样在编译OS时不依赖任何应用相关的配置
- TCB tcbArray[2+2];
- int tcbIndex = 0;
- int osSchedIndex;
- // 这是一个简单实现函数
- void OS_Sched(void){
- // 查找一个没有被删除的任务
- do{
- osSchedIndex++;
- if(osSchedIndex >= 4){
- osSchedIndex = 0;
- }
- }while(tcbArray[osSchedIndex].deleteFlag == 1);
-
- os.rdy = &tcbArray[osSchedIndex];
- }
- /**
- * @brief 这是一个简单实现函数,一般要做链表的插入操作
- */
- int _OS_TaskCreate(void (*taskFun)(void),void* stk,int prio_stkSize,void* argv){
- if(tcbIndex >= 4){
- return -1;
- }
- // 取优先级参数,此例暂不使用
- //int prio = (prio_stkSize & 0xff);
- // 取堆栈size
- prio_stkSize = prio_stkSize>>8;
- // 初始化任务堆栈
- stk = (void*)((int)stk + prio_stkSize);
- stk = OS_TaskStackInit(taskFun,stk,argv);
- // 初始化TCB,此例没有任务管理功能,仅设置栈顶即可
- tcbArray[tcbIndex].stack = stk;
- tcbArray[tcbIndex].deleteFlag = 0;
- // 返回任务ID
- return tcbIndex++;
- }
- // 这是一个简单实现函数
- void OS_Start(void (*taskFun)(void)){
- extern void OS_IdleTask(void);
-
- // 初始化空闲任务,默认使用tcbArray[0]
- // 空闲任务的堆栈不需要初始化,多单步运行几次你就会明白的
- tcbArray[0].stack = (void*)((int)IdleTaskTackBuff + IDLE_TASK_TACK_SIZE);
- tcbArray[0].deleteFlag = 0;
-
- // 创建第一个任务
- tcbIndex = 1;
- _OS_TaskCreate(taskFun,FirstTaskTackBuff,0|(FIRST_TASK_TACK_SIZE<<8),0);
-
- // os.run指向空闲任务
- osSchedIndex = 0;
- os.run = &tcbArray[0];
-
- // 启动任务
- OS_ChangeCpuMode(os.run->stack);
- _OS_TaskPass();
-
- OS_IdleTask();
- }
- __weak void OS_IdleTask(void){
- // 注意:本列没有实现定时器,所以要主动切换任务,不然就一直运行在这里
- // 若要实现定时器,只需要在处理完时间事务后调用OS_PendSV()函数即可切换任务
- while(1)
- os_pass();
- }
复制代码
头文件部分
- #include "stdint.h"
- // 注意:不能直接使用这个函数,要经过SVC才可以,参考下面的例子,其它函数也一样,请自行举一反三
- extern int _OS_TaskCreate(void (*taskFun)(void),void* stk,int prio_stkSize,void* argv);
- extern void _OS_TaskPass(void);
- /**
- * @brief 堆栈定义宏
- */
- #define TACK_DEF(pool,size) unsigned long long pool[((size)+7)/8]
- #define __SVC_0 __svc_indirect(0)
- extern int _os_tsk_create (uint32_t p,void (*task)(void ),void* stk,int prio_stkSize,void* argv) __SVC_0;
- extern int _os_tsk_create_ex(uint32_t p,void (*task)(void*),void* stk,int prio_stkSize,void* argv) __SVC_0;
- #define os_tsk_create(taskFun,prio,stk,stkSize) \
- _os_tsk_create((uint32_t)_OS_TaskCreate,taskFun,stk,prio|(stkSize<<8),(void*)0)
-
- #define os_tsk_create_ex(taskFun,prio,stk,stkSize,argv) \
- _os_tsk_create_ex((uint32_t)_OS_TaskCreate,taskFun,stk,prio|(stkSize<<8),argv)
-
- extern void _os_pass (uint32_t p) __SVC_0;
- #define os_pass() _os_pass((uint32_t)_OS_TaskPass) // 注意:这只是一个SVC调用的例子,实际上此函数不需要经过SVC
复制代码
应用部分
- #include "os.h"
-
- // 配置空闲任务堆栈与第一个任务堆栈的例子,请使用Configuration Wizard窗口,不过此功能仅keil才有
- //-------- <<< Use Configuration Wizard in Context Menu >>> -----------------
- // <o>Idle Task stack size [bytes] <128-4096:128><#/4>
- // <i> Default: 512
- #ifndef OS_STKSIZE
- #define OS_STKSIZE 64
- #endif
- // <o>First Task stack size [bytes] <128-4096:128><#/4>
- // <i> Default: 512
- #ifndef MAIN_TASK_STKSIZE
- #define MAIN_TASK_STKSIZE 256
- #endif
- //------------- <<< end of configuration section >>> -----------------------
- int const IDLE_TASK_TACK_SIZE = OS_STKSIZE*4;
- int const FIRST_TASK_TACK_SIZE = MAIN_TASK_STKSIZE*4;
- TACK_DEF(IdleTaskTackBuff,IDLE_TASK_TACK_SIZE);
- TACK_DEF(FirstTaskTackBuff,FIRST_TASK_TACK_SIZE);
- // 以上代码应专门放在一个配置文件里面,随项目一起,
- // 以下为应用代码,仅能再创建2个任务,加上空闲任务与第一个任务共4个任务
- TACK_DEF(Task1Stk,256);
- TACK_DEF(Task2Stk,256);
- void Task2(void* argv){
- int x = 0;
-
- if((int)argv == 1){
- // ...
- }else{
- // ...
- }
- while(1){
- x++;
- os_pass();
- }
- }
- void Task1(void){
- int x = 0;
- while(1){
- x++;
- os_pass();
- }
- }
- int main(void){
- os_tsk_create(Task1,0,Task1Stk,sizeof(Task1Stk));
- os_tsk_create_ex(Task2,0,Task2Stk,sizeof(Task2Stk),(void*)0);
- // 此函数退出后,这个任务将会被删除...
- }
- /**
- * @brief 修改lib启动过程,在执行分散加载后由os接管,main函数作为第一个任务
- */
- #ifdef __MICROLIB
- void _main_init (void) __attribute__((section(".ARM.Collect$00000007")));
- __asm void _main_init (void) {
- #else
- __asm void __rt_entry (void) {
- #endif
- PRESERVE8
- IMPORT main
- IMPORT OS_Start
- IMPORT __heap_base
- IMPORT __heap_limit
-
- #ifdef __MICROLIB
- #else
- IMPORT __rt_lib_init
- LDR R0,=__heap_base
- LDR R1,=__heap_limit
- BL __rt_lib_init
- #endif
-
- LDR R0,=main
- BL OS_Start
- ALIGN
- }
复制代码
|
-
-
os_demo.rar
9.89 KB, 下载次数: 139
教你编写最简单的CM3操作系统,160行实现基本任务创建与切换,助你学习CM3与RTOS的精髓.
赞赏
-
1
查看全部赞赏
-
|