社区导航

 
查看: 9927|回复: 21

[原创] 教你编写最简单的CM3操作系统,160行实现基本任务创建与切换,助你学习CM3与RTOS的精髓.

  [复制链接]

134

TA的帖子

1

TA的资源

一粒金砂(高级)

Rank: 3Rank: 3

发表于 2016-5-25 15:46:35 | 显示全部楼层 |阅读模式
如题,任务创建与上下文切换是跟硬件息息相关的,而这恰恰是RTOS编写的最难点,抛开这些功能,剩下的就是双向链表增删改操作了,本例用最精简的方式实现了任务创建与切换,OS启动等功能,并运用了Cortex-M3的先进特性,非传统关中断实现方式,助你学习CM3与RTOS的精髓

关于RTOS的理论性知识,实在是无力多讲,资料太多了,请自行寻找吧!
关于Cortex-M3,它实在是太优秀了,它的很多特性就是为了RTOS而生的

你可以下载代码,在keil下执行软件仿真调试即可,区区160行代码(不算注释),相信你能搞明白的
本例固定4个任务,main函数是第一个任务,在main函数再创建2个任务,简单轮转调度,任务管理不在本文讨论内容,请见谅


注释偏少,不懂请问!

直接上代码:

OS核心部分

  1. #include "os.h"

  2. typedef struct TCB{        
  3.         int reserve[3];
  4.         void* stack;        // 任务栈顶地址    汇编使用,offset 0x0C
  5.         int deleteFlag;
  6.         //...
  7. }TCB;

  8. struct _OS{
  9.         TCB* run;                 // 正在运行的任务  汇编使用,offset 0x00
  10.         TCB* rdy;                 // 就绪的任务      汇编使用,offset 0x04
  11.         // ...
  12. }os;


  13. __asm void SVC_Handler (void){
  14.   PRESERVE8
  15.         THUMB
  16.         PUSH        {LR}

  17.         MRS                R0,PSP                                        // Read PSP
  18.         LDM                R0,{R0-R3,R12}        // Read R0-R3,R12 from stack
  19.         BLX                R12                                                // Call SVC Function

  20.         MRS                R1,PSP                                        // Read PSP
  21.         STR                R0,[R1,#0x0]                // set return values

  22.         POP                {PC}        
  23.         ALIGN
  24. }

  25. __asm void PendSV_Handler (void){        
  26.   PRESERVE8
  27.         THUMB
  28.         IMPORT         os
  29.         IMPORT         OS_Sched
  30.         
  31.         PUSH         {LR}
  32.         MRS   R0,PSP
  33.         STMFD R0!,{R4-R11}
  34.         MSR   PSP,R0
  35.         
  36.         BL OS_Sched        // C语言任务调度
  37.         
  38.         // 切换任务,看C示意代码
  39.         // os.run->stack = PSP;
  40.         LDR                R2,=os
  41.         LDR                R1,[R2,#0x0]
  42.         MRS                R3,PSP
  43.         STR                R3,[R1,#0xC]
  44.         // os.run = os.rdy;
  45.         LDR                R0,[R2,#0x4]        
  46.         STR                R0,[R2,#0x0]        
  47.         // PSP = os.rdy->stack
  48.         LDR                R1,[R2,#0x4]
  49.         LDR                R0,[R1,#0xC]
  50.         MSR                PSP,R0
  51.         
  52.         MRS   R0,PSP
  53.         LDMFD R0!,{R4-R11}
  54.         MSR   PSP,R0
  55.         POP   {PC}        
  56.         ALIGN
  57. }


  58. /**
  59. * [url=home.php?mod=space&uid=159083]@brief[/url]        悬起PendSV,由于此中断优先级最低,所以若是在SVC_Handler或更高级别中断函数中设置的,
  60. *                                 PendSV_Handler函数不会立即执行, [url=home.php?mod=space&uid=418085]@see[/url] <Cortex-M3权威指南>
  61. */
  62. __asm void OS_PendSV (void){
  63.   PRESERVE8
  64.         THUMB
  65. NVIC_INT_CTRL           EQU     0xE000ED04
  66. NVIC_PENDSVSET          EQU     0x10000000
  67.         LDR     R0, =NVIC_INT_CTRL         
  68.         LDR     R1, =NVIC_PENDSVSET
  69.         STR     R1, [R0]        
  70.         BX                LR
  71.         ALIGN
  72. }

  73. void _OS_TaskDeleteSelf(void){
  74.         // 本例没有实现 ...
  75.         // 一般做法是将该TCB从链表中移除
  76.         os.run->deleteFlag = 1;
  77.         OS_PendSV();
  78. }

  79. /**
  80. * @brief        当任务函数执行返回时,跳转到此函数, @see OS_TaskStackInit
  81. */
  82. __asm void ASM_TaskDelete(void){
  83.   PRESERVE8
  84.         THUMB
  85.         IMPORT _OS_TaskDeleteSelf
  86.         LDR                R12,=_OS_TaskDeleteSelf
  87.         SVC                0
  88.         ALIGN
  89. }

  90. /**
  91. * @brief        堆栈初始化
  92. */
  93. __asm void* OS_TaskStackInit(void (*taskFun)(void),void* stk,void* argv){
  94.   PRESERVE8
  95.         THUMB
  96.         MOV                R3,#0x01000000        // 初始化PSR的值
  97.         STMFD        R1!, {R3}                // push xPSR         
  98.         STMFD        R1!, {R0}                // push PC = taskFun
  99.         LDR                R0,=ASM_TaskDelete               
  100.         STMFD        R1!,{R0}                        // push LR = 删除任务函数的地址
  101.         MOV                R0,#0x0                           
  102.         STMFD        R1!,{R0}                        // push R12
  103.         STMFD        R1!,{R0}                        // push R3
  104.         STMFD        R1!,{R0}                        // push R2
  105.         STMFD        R1!,{R0}                        // push R1
  106.         STMFD        R1!,{R2}                        // push R0 = argv

  107.         STMFD        R1!,{R0}                        // push R11
  108.         STMFD        R1!,{R0}                        // push R10
  109.         STMFD        R1!,{R0}                        // push R9
  110.         STMFD        R1!,{R0}                        // push R8
  111.         STMFD        R1!,{R0}                        // push R7
  112.         STMFD        R1!,{R0}                        // push R6
  113.         STMFD        R1!,{R0}                        // push R5
  114.         STMFD        R1!,{R0}                        // push R4        
  115.         // 返回新栈顶
  116.         MOV                R0, R1                                
  117.         BX                LR
  118.         ALIGN
  119. }
  120.         
  121. /**
  122. * @brief        更改CPU模式,并设置PendSV的优先级为最低
  123. */
  124. __asm void OS_ChangeCpuMode (void* stk){
  125.   PRESERVE8
  126.         THUMB        
  127.         
  128. NVIC_SYSPRI2            EQU     0xE000ED20  // 系统优先级寄存器(2)
  129. NVIC_PENDSV_PRI         EQU     0xFFFF0000  // PendSV中断和系统节拍中断 (都为最低,0xff).

  130.         MSR                PSP,R0
  131.         // 设置中断优先级,将PendSV设为最低
  132.         LDR     R0, =NVIC_SYSPRI2
  133.         LDR     R1, =NVIC_PENDSV_PRI
  134.         STR     R1, [R0]
  135.         // 更换CPU模式,使线程模式运行在特权级,#0x3则运行在用户级
  136.         MOV                R0,#0x2
  137.         MSR                CONTROL,R0
  138.         // 开中断
  139.         CPSIE        I               
  140.         
  141.         BX                LR
  142.         ALIGN
  143. }

  144. /**
  145. * @brief        切换到下一个任务
  146. */
  147. void _OS_TaskPass(void){
  148.         OS_PendSV();
  149. }

  150. /// 引用配置文件中的定义,os是一个模块,是要被编译成lib文件的,所以这里只要声明即可
  151. extern int const IDLE_TASK_TACK_SIZE;
  152. extern uint64_t IdleTaskTackBuff[];

  153. extern int const FIRST_TASK_TACK_SIZE;
  154. extern uint64_t FirstTaskTackBuff[];

  155. /// 本例固定定义4个TCB,实际编写时应采用声明,由os配置文件做具体定义,同上
  156. /// 这样在编译OS时不依赖任何应用相关的配置
  157. TCB        tcbArray[2+2];
  158. int tcbIndex = 0;
  159. int osSchedIndex;

  160. // 这是一个简单实现函数
  161. void OS_Sched(void){
  162.         // 查找一个没有被删除的任务
  163.         do{
  164.                 osSchedIndex++;
  165.                 if(osSchedIndex >= 4){
  166.                         osSchedIndex = 0;
  167.                 }
  168.         }while(tcbArray[osSchedIndex].deleteFlag == 1);
  169.         
  170.         os.rdy = &tcbArray[osSchedIndex];
  171. }

  172. /**
  173. * @brief        这是一个简单实现函数,一般要做链表的插入操作
  174. */
  175. int _OS_TaskCreate(void (*taskFun)(void),void* stk,int prio_stkSize,void* argv){
  176.         if(tcbIndex >= 4){               
  177.                 return -1;
  178.         }
  179.         // 取优先级参数,此例暂不使用
  180.         //int prio = (prio_stkSize & 0xff);        
  181.         // 取堆栈size
  182.         prio_stkSize = prio_stkSize>>8;        
  183.         // 初始化任务堆栈
  184.         stk = (void*)((int)stk + prio_stkSize);
  185.         stk = OS_TaskStackInit(taskFun,stk,argv);
  186.         // 初始化TCB,此例没有任务管理功能,仅设置栈顶即可
  187.         tcbArray[tcbIndex].stack = stk;
  188.         tcbArray[tcbIndex].deleteFlag = 0;
  189.         // 返回任务ID
  190.         return tcbIndex++;
  191. }

  192. // 这是一个简单实现函数
  193. void OS_Start(void (*taskFun)(void)){
  194.         extern void OS_IdleTask(void);
  195.         
  196.         // 初始化空闲任务,默认使用tcbArray[0]
  197.         // 空闲任务的堆栈不需要初始化,多单步运行几次你就会明白的
  198.         tcbArray[0].stack         = (void*)((int)IdleTaskTackBuff + IDLE_TASK_TACK_SIZE);
  199.         tcbArray[0].deleteFlag = 0;
  200.         
  201.         // 创建第一个任务
  202.         tcbIndex = 1;
  203.         _OS_TaskCreate(taskFun,FirstTaskTackBuff,0|(FIRST_TASK_TACK_SIZE<<8),0);
  204.         
  205.         // os.run指向空闲任务
  206.         osSchedIndex = 0;
  207.         os.run = &tcbArray[0];
  208.         
  209.         // 启动任务
  210.         OS_ChangeCpuMode(os.run->stack);
  211.         _OS_TaskPass();
  212.         
  213.         OS_IdleTask();
  214. }

  215. __weak void OS_IdleTask(void){
  216.         // 注意:本列没有实现定时器,所以要主动切换任务,不然就一直运行在这里
  217.         // 若要实现定时器,只需要在处理完时间事务后调用OS_PendSV()函数即可切换任务
  218.         while(1)
  219.                 os_pass();
  220. }
复制代码


头文件部分

  1. #include "stdint.h"

  2. // 注意:不能直接使用这个函数,要经过SVC才可以,参考下面的例子,其它函数也一样,请自行举一反三
  3. extern int _OS_TaskCreate(void (*taskFun)(void),void* stk,int prio_stkSize,void* argv);
  4. extern void _OS_TaskPass(void);

  5. /**
  6. * @brief        堆栈定义宏
  7. */
  8. #define TACK_DEF(pool,size)  unsigned long long pool[((size)+7)/8]


  9. #define __SVC_0                         __svc_indirect(0)

  10. extern int  _os_tsk_create   (uint32_t p,void (*task)(void ),void* stk,int prio_stkSize,void* argv) __SVC_0;
  11. extern int  _os_tsk_create_ex(uint32_t p,void (*task)(void*),void* stk,int prio_stkSize,void* argv) __SVC_0;

  12. #define os_tsk_create(taskFun,prio,stk,stkSize)    \
  13.                 _os_tsk_create((uint32_t)_OS_TaskCreate,taskFun,stk,prio|(stkSize<<8),(void*)0)        
  14.                
  15. #define os_tsk_create_ex(taskFun,prio,stk,stkSize,argv)    \
  16.                 _os_tsk_create_ex((uint32_t)_OS_TaskCreate,taskFun,stk,prio|(stkSize<<8),argv)        
  17.                
  18. extern void _os_pass (uint32_t p) __SVC_0;
  19. #define os_pass()    _os_pass((uint32_t)_OS_TaskPass)        // 注意:这只是一个SVC调用的例子,实际上此函数不需要经过SVC

复制代码


应用部分

  1. #include "os.h"
  2.                
  3. // 配置空闲任务堆栈与第一个任务堆栈的例子,请使用Configuration Wizard窗口,不过此功能仅keil才有
  4. //-------- <<< Use Configuration Wizard in Context Menu >>> -----------------
  5. //   <o>Idle Task stack size [bytes] <128-4096:128><#/4>
  6. //   <i> Default: 512
  7. #ifndef OS_STKSIZE
  8. #define OS_STKSIZE         64
  9. #endif

  10. // <o>First Task stack size [bytes] <128-4096:128><#/4>
  11. // <i> Default: 512
  12. #ifndef MAIN_TASK_STKSIZE
  13. #define MAIN_TASK_STKSIZE         256
  14. #endif
  15. //------------- <<< end of configuration section >>> -----------------------
  16. int const IDLE_TASK_TACK_SIZE = OS_STKSIZE*4;
  17. int const FIRST_TASK_TACK_SIZE = MAIN_TASK_STKSIZE*4;
  18. TACK_DEF(IdleTaskTackBuff,IDLE_TASK_TACK_SIZE);
  19. TACK_DEF(FirstTaskTackBuff,FIRST_TASK_TACK_SIZE);
  20. // 以上代码应专门放在一个配置文件里面,随项目一起,


  21. // 以下为应用代码,仅能再创建2个任务,加上空闲任务与第一个任务共4个任务

  22. TACK_DEF(Task1Stk,256);
  23. TACK_DEF(Task2Stk,256);

  24. void Task2(void* argv){
  25.         int x = 0;
  26.         
  27.         if((int)argv == 1){
  28.                 // ...
  29.         }else{
  30.                 // ...
  31.         }
  32.         while(1){
  33.                 x++;
  34.                 os_pass();
  35.         }
  36. }

  37. void Task1(void){
  38.         int x = 0;
  39.         while(1){
  40.                 x++;
  41.                 os_pass();
  42.         }
  43. }

  44. int main(void){
  45.         os_tsk_create(Task1,0,Task1Stk,sizeof(Task1Stk));
  46.         os_tsk_create_ex(Task2,0,Task2Stk,sizeof(Task2Stk),(void*)0);
  47.         // 此函数退出后,这个任务将会被删除...
  48. }


  49. /**
  50. * @brief        修改lib启动过程,在执行分散加载后由os接管,main函数作为第一个任务
  51. */
  52. #ifdef __MICROLIB
  53. void _main_init (void)                 __attribute__((section(".ARM.Collect$00000007")));
  54. __asm void _main_init (void) {
  55. #else
  56. __asm void __rt_entry (void) {
  57. #endif
  58.         PRESERVE8
  59.         IMPORT  main        
  60.         IMPORT  OS_Start
  61.         IMPORT  __heap_base
  62.         IMPORT  __heap_limit
  63.         
  64. #ifdef __MICROLIB
  65. #else
  66.         IMPORT  __rt_lib_init        
  67.         LDR                R0,=__heap_base
  68.         LDR                R1,=__heap_limit
  69.         BL    __rt_lib_init        
  70. #endif
  71.                
  72.         LDR                R0,=main
  73.         BL                OS_Start

  74.         ALIGN
  75. }
复制代码



os_demo.rar

9.89 KB, 下载次数: 98

教你编写最简单的CM3操作系统,160行实现基本任务创建与切换,助你学习CM3与RTOS的精髓.

评分

1

查看全部评分


回复

使用道具 举报

6212

TA的帖子

121

TA的资源

管理员

Rank: 13Rank: 13Rank: 13Rank: 13

发表于 2016-5-25 16:01:32 | 显示全部楼层
很厉害。楼主可以多讲讲这方面的知识,对这一块感兴趣的网友应该是挺多的。

回复 支持 反对

使用道具 举报

371

TA的帖子

43

TA的资源

管理员

Rank: 13Rank: 13Rank: 13Rank: 13

发表于 2016-5-25 16:01:35 | 显示全部楼层
楼主有心了!
在路上……

EEworld 有你有我

回复 支持 反对

使用道具 举报

1104

TA的帖子

4

TA的资源

纯净的硅(中级)

Rank: 5Rank: 5

发表于 2016-5-26 10:27:44 | 显示全部楼层
天地庄周马;江湖范蠡船。
个性签名还是放QQ号吧,2060347305,添加说明EEworld好友

回复 支持 反对

使用道具 举报

90

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2016-5-28 11:38:36 | 显示全部楼层
牛!

回复 支持 反对

使用道具 举报

6

TA的帖子

0

TA的资源

一粒金砂(初级)

Rank: 1

发表于 2016-5-28 14:37:38 | 显示全部楼层
高手。值得学习研究。

点评

谢谢,单步执行,研究精髓. 其它OS入门太难,我这个精简到只有160行,能有多难!  详情 回复 发表于 2016-5-30 08:24

回复 支持 反对

使用道具 举报

134

TA的帖子

1

TA的资源

一粒金砂(高级)

Rank: 3Rank: 3

 楼主| 发表于 2016-5-30 08:24:40 | 显示全部楼层
louis_xm 发表于 2016-5-28 14:37
高手。值得学习研究。

谢谢,单步执行,研究精髓.
其它OS入门太难,我这个精简到只有160行,能有多难!

回复 支持 反对

使用道具 举报

1

TA的帖子

1

TA的资源

一粒金砂(初级)

Rank: 1

发表于 2016-5-31 14:10:09 | 显示全部楼层
厉害!

回复 支持 反对

使用道具 举报

87

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2016-5-31 14:16:38 | 显示全部楼层
好厉害!膜拜

点评

不用膜拜,你研究透这几行代码以后你也可以自己编写RTOS了  详情 回复 发表于 2016-5-31 15:24

回复 支持 反对

使用道具 举报

134

TA的帖子

1

TA的资源

一粒金砂(高级)

Rank: 3Rank: 3

 楼主| 发表于 2016-5-31 15:24:06 | 显示全部楼层

不用膜拜,你研究透这几行代码以后你也可以自己编写RTOS了

回复 支持 反对

使用道具 举报

1505

TA的帖子

0

TA的资源

五彩晶圆(初级)

Rank: 7Rank: 7Rank: 7

发表于 2016-5-31 15:40:59 | 显示全部楼层
值得好好研究学习一下

回复 支持 反对

使用道具 举报

3

TA的帖子

0

TA的资源

一粒金砂(初级)

Rank: 1

发表于 2016-6-1 13:43:03 | 显示全部楼层
楼主有心了!

回复 支持 反对

使用道具 举报

3

TA的帖子

0

TA的资源

一粒金砂(初级)

Rank: 1

发表于 2016-6-1 13:43:25 | 显示全部楼层
楼主有心了!

回复 支持 反对

使用道具 举报

16

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2016-6-20 16:28:02 | 显示全部楼层
学习了,感谢分享…………

点评

几句代码而已,不客气  详情 回复 发表于 2016-6-20 18:01

回复 支持 反对

使用道具 举报

134

TA的帖子

1

TA的资源

一粒金砂(高级)

Rank: 3Rank: 3

 楼主| 发表于 2016-6-20 18:01:27 | 显示全部楼层
淡定独钓翁 发表于 2016-6-20 16:28
学习了,感谢分享…………

几句代码而已,不客气

回复 支持 反对

使用道具 举报

230

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2016-7-6 23:05:16 | 显示全部楼层
mark一下,明天看

回复 支持 反对

使用道具 举报

568

TA的帖子

2

TA的资源

版主

Rank: 6Rank: 6

发表于 2016-7-16 17:24:01 来自手机 | 显示全部楼层
可以,我的毕设也是在stn32做了一个操作系统,不过是非抢占的,没时间改成抢占的了,2000行核心代码作业

回复 支持 反对

使用道具 举报

568

TA的帖子

2

TA的资源

版主

Rank: 6Rank: 6

发表于 2016-7-16 17:24:47 来自手机 | 显示全部楼层
可以,我也做了一个小操作系统内核,包含图像用户界面,2000行左右。

回复 支持 反对

使用道具 举报

118

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2016-7-27 13:22:12 | 显示全部楼层
牛!

回复 支持 反对

使用道具 举报

15

TA的帖子

3

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2016-11-4 23:37:22 | 显示全部楼层
教你编写最简单的CM3操作系统,160行实现基本任务创建与切换,助你学习CM3与RTOS的精髓.

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

  • 论坛活动 E手掌握

    扫码关注
    EEWORLD 官方微信

  • EE福利  唾手可得

    扫码关注
    EE福利 唾手可得

小黑屋|手机版|Archiver|电子工程世界 ( 京ICP证 060456

GMT+8, 2017-5-23 01:25 , Processed in 0.554136 second(s), 17 queries , Redis On.

快速回复 返回顶部 返回列表
关闭