百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

教你编写最简单的CM3操作系统,160行实现任务创建与切换

csdh11 2025-03-14 15:58 1 浏览

如题,任务创建与上下文切换是跟硬件息息相关的,而这恰恰是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. PUSH {LR}

  31. MRS R0,PSP

  32. STMFD R0!,{R4-R11}

  33. MSR PSP,R0

  34. BL OS_Sched // C语言任务调度

  35. // 切换任务,看C示意代码

  36. // os.run->stack = PSP;

  37. LDR R2,=os

  38. LDR R1,[R2,#0x0]

  39. MRS R3,PSP

  40. STR R3,[R1,#0xC]

  41. // os.run = os.rdy;

  42. LDR R0,[R2,#0x4]

  43. STR R0,[R2,#0x0]

  44. // PSP = os.rdy->stack

  45. LDR R1,[R2,#0x4]

  46. LDR R0,[R1,#0xC]

  47. MSR PSP,R0

  48. MRS R0,PSP

  49. LDMFD R0!,{R4-R11}

  50. MSR PSP,R0

  51. POP {PC}

  52. ALIGN

  53. }

  54. /**

  55. * [url=home.php?mod=space&uid=159083]@brief[/url] 悬起PendSV,由于此中断优先级最低,所以若是在SVC_Handler或更高级别中断函数中设置的,

  56. * PendSV_Handler函数不会立即执行, [url=home.php?mod=space&uid=418085]@see[/url]

  57. */

  58. __asm void OS_PendSV (void){

  59. PRESERVE8

  60. THUMB

  61. NVIC_INT_CTRL EQU 0xE000ED04

  62. NVIC_PENDSVSET EQU 0x10000000

  63. LDR R0, =NVIC_INT_CTRL

  64. LDR R1, =NVIC_PENDSVSET

  65. STR R1, [R0]

  66. BX LR

  67. ALIGN

  68. }

  69. void _OS_TaskDeleteSelf(void){

  70. // 本例没有实现 ...

  71. // 一般做法是将该TCB从链表中移除

  72. os.run->deleteFlag = 1;

  73. OS_PendSV();

  74. }

  75. /**

  76. * @brief 当任务函数执行返回时,跳转到此函数, @see OS_TaskStackInit

  77. */

  78. __asm void ASM_TaskDelete(void){

  79. PRESERVE8

  80. THUMB

  81. IMPORT _OS_TaskDeleteSelf

  82. LDR R12,=_OS_TaskDeleteSelf

  83. SVC 0

  84. ALIGN

  85. }

  86. /**

  87. * @brief 堆栈初始化

  88. */

  89. __asm void* OS_TaskStackInit(void (*taskFun)(void),void* stk,void* argv){

  90. PRESERVE8

  91. THUMB

  92. MOV R3,#0x01000000 // 初始化PSR的值

  93. STMFD R1!, {R3} // push xPSR

  94. STMFD R1!, {R0} // push PC = taskFun

  95. LDR R0,=ASM_TaskDelete

  96. STMFD R1!,{R0} // push LR = 删除任务函数的地址

  97. MOV R0,#0x0

  98. STMFD R1!,{R0} // push R12

  99. STMFD R1!,{R0} // push R3

  100. STMFD R1!,{R0} // push R2

  101. STMFD R1!,{R0} // push R1

  102. STMFD R1!,{R2} // push R0 = argv

  103. STMFD R1!,{R0} // push R11

  104. STMFD R1!,{R0} // push R10

  105. STMFD R1!,{R0} // push R9

  106. STMFD R1!,{R0} // push R8

  107. STMFD R1!,{R0} // push R7

  108. STMFD R1!,{R0} // push R6

  109. STMFD R1!,{R0} // push R5

  110. STMFD R1!,{R0} // push R4

  111. // 返回新栈顶

  112. MOV R0, R1

  113. BX LR

  114. ALIGN

  115. }

  116. /**

  117. * @brief 更改CPU模式,并设置PendSV的优先级为最低

  118. */

  119. __asm void OS_ChangeCpuMode (void* stk){

  120. PRESERVE8

  121. THUMB

  122. NVIC_SYSPRI2 EQU 0xE000ED20 // 系统优先级寄存器(2)

  123. NVIC_PENDSV_PRI EQU 0xFFFF0000 // PendSV中断和系统节拍中断 (都为最低,0xff).

  124. MSR PSP,R0

  125. // 设置中断优先级,将PendSV设为最低

  126. LDR R0, =NVIC_SYSPRI2

  127. LDR R1, =NVIC_PENDSV_PRI

  128. STR R1, [R0]

  129. // 更换CPU模式,使线程模式运行在特权级,#0x3则运行在用户级

  130. MOV R0,#0x2

  131. MSR CONTROL,R0

  132. // 开中断

  133. CPSIE I

  134. BX LR

  135. ALIGN

  136. }

  137. /**

  138. * @brief 切换到下一个任务

  139. */

  140. void _OS_TaskPass(void){

  141. OS_PendSV();

  142. }

  143. /// 引用配置文件中的定义,os是一个模块,是要被编译成lib文件的,所以这里只要声明即可

  144. extern int const IDLE_TASK_TACK_SIZE;

  145. extern uint64_t IdleTaskTackBuff[];

  146. extern int const FIRST_TASK_TACK_SIZE;

  147. extern uint64_t FirstTaskTackBuff[];

  148. /// 本例固定定义4个TCB,实际编写时应采用声明,由os配置文件做具体定义,同上

  149. /// 这样在编译OS时不依赖任何应用相关的配置

  150. TCB tcbArray[2+2];

  151. int tcbIndex = 0;

  152. int osSchedIndex;

  153. // 这是一个简单实现函数

  154. void OS_Sched(void){

  155. // 查找一个没有被删除的任务

  156. do{

  157. osSchedIndex++;

  158. if(osSchedIndex >= 4){

  159. osSchedIndex = 0;

  160. }

  161. }while(tcbArray[osSchedIndex].deleteFlag == 1);

  162. os.rdy = &tcbArray[osSchedIndex];

  163. }

  164. /**

  165. * @brief 这是一个简单实现函数,一般要做链表的插入操作

  166. */

  167. int _OS_TaskCreate(void (*taskFun)(void),void* stk,int prio_stkSize,void* argv){

  168. if(tcbIndex >= 4){

  169. return -1;

  170. }

  171. // 取优先级参数,此例暂不使用

  172. //int prio = (prio_stkSize & 0xff);

  173. // 取堆栈size

  174. prio_stkSize = prio_stkSize>>8;

  175. // 初始化任务堆栈

  176. stk = (void*)((int)stk + prio_stkSize);

  177. stk = OS_TaskStackInit(taskFun,stk,argv);

  178. // 初始化TCB,此例没有任务管理功能,仅设置栈顶即可

  179. tcbArray[tcbIndex].stack = stk;

  180. tcbArray[tcbIndex].deleteFlag = 0;

  181. // 返回任务ID

  182. return tcbIndex++;

  183. }

  184. // 这是一个简单实现函数

  185. void OS_Start(void (*taskFun)(void)){

  186. extern void OS_IdleTask(void);

  187. // 初始化空闲任务,默认使用tcbArray[0]

  188. // 空闲任务的堆栈不需要初始化,多单步运行几次你就会明白的

  189. tcbArray[0].stack = (void*)((int)IdleTaskTackBuff + IDLE_TASK_TACK_SIZE);

  190. tcbArray[0].deleteFlag = 0;

  191. // 创建第一个任务

  192. tcbIndex = 1;

  193. _OS_TaskCreate(taskFun,FirstTaskTackBuff,0|(FIRST_TASK_TACK_SIZE<<8),0);

  194. // os.run指向空闲任务

  195. osSchedIndex = 0;

  196. os.run = &tcbArray[0];

  197. // 启动任务

  198. OS_ChangeCpuMode(os.run->stack);

  199. _OS_TaskPass();

  200. OS_IdleTask();

  201. }

  202. __weak void OS_IdleTask(void){

  203. // 注意:本列没有实现定时器,所以要主动切换任务,不然就一直运行在这里

  204. // 若要实现定时器,只需要在处理完时间事务后调用OS_PendSV()函数即可切换任务

  205. while(1)

  206. os_pass();

  207. }

复制代码

头文件部分

  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. #define os_tsk_create_ex(taskFun,prio,stk,stkSize,argv) \

  15. _os_tsk_create_ex((uint32_t)_OS_TaskCreate,taskFun,stk,prio|(stkSize<<8),argv)

  16. extern void _os_pass (uint32_t p) __SVC_0;

  17. #define os_pass() _os_pass((uint32_t)_OS_TaskPass) // 注意:这只是一个SVC调用的例子,实际上此函数不需要经过SVC

复制代码

应用部分

  1. #include "os.h"

  2. // 配置空闲任务堆栈与第一个任务堆栈的例子,请使用Configuration Wizard窗口,不过此功能仅keil才有

  3. //-------- <<< use configuration wizard in context menu>>> -----------------

  4. // Idle Task stack size [bytes] <128-4096:128><#/4>

  5. // Default: 512

  6. #ifndef OS_STKSIZE

  7. #define OS_STKSIZE 64

  8. #endif

  9. // First Task stack size [bytes] <128-4096:128><#/4>

  10. // Default: 512

  11. #ifndef MAIN_TASK_STKSIZE

  12. #define MAIN_TASK_STKSIZE 256

  13. #endif

  14. //------------- <<< end of configuration section>>> -----------------------

  15. int const IDLE_TASK_TACK_SIZE = OS_STKSIZE*4;

  16. int const FIRST_TASK_TACK_SIZE = MAIN_TASK_STKSIZE*4;

  17. TACK_DEF(IdleTaskTackBuff,IDLE_TASK_TACK_SIZE);

  18. TACK_DEF(FirstTaskTackBuff,FIRST_TASK_TACK_SIZE);

  19. // 以上代码应专门放在一个配置文件里面,随项目一起,

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

  21. TACK_DEF(Task1Stk,256);

  22. TACK_DEF(Task2Stk,256);

  23. void Task2(void* argv){

  24. int x = 0;

  25. if((int)argv == 1){

  26. // ...

  27. }else{

  28. // ...

  29. }

  30. while(1){

  31. x++;

  32. os_pass();

  33. }

  34. }

  35. void Task1(void){

  36. int x = 0;

  37. while(1){

  38. x++;

  39. os_pass();

  40. }

  41. }

  42. int main(void){

  43. os_tsk_create(Task1,0,Task1Stk,sizeof(Task1Stk));

  44. os_tsk_create_ex(Task2,0,Task2Stk,sizeof(Task2Stk),(void*)0);

  45. // 此函数退出后,这个任务将会被删除...

  46. }

  47. /**

  48. * @brief 修改lib启动过程,在执行分散加载后由os接管,main函数作为第一个任务

  49. */

  50. #ifdef __MICROLIB

  51. void _main_init (void) __attribute__((section(".ARM.Collect$00000007")));

  52. __asm void _main_init (void) {

  53. #else

  54. __asm void __rt_entry (void) {

  55. #endif

  56. PRESERVE8

  57. IMPORT main

  58. IMPORT OS_Start

  59. IMPORT __heap_base

  60. IMPORT __heap_limit

  61. #ifdef __MICROLIB

  62. #else

  63. IMPORT __rt_lib_init

  64. LDR R0,=__heap_base

  65. LDR R1,=__heap_limit

  66. BL __rt_lib_init

  67. #endif

  68. LDR R0,=main

  69. BL OS_Start

  70. ALIGN

  71. }

复制代码

以上图文内容均是EEWORLD论坛网友:samos2011 原创,在此感谢。

如有疑问可到EEWORLD论坛或通过微博、微信提出。

欢迎微博@EEWORLD

如果你也写过此类原创干货请关注微信公众号:EEWORLD(电子工程世界)回复“投稿”,也可将你的原创发至:bbs_service@eeworld.com.cn,一经入选,我们将帮你登上头条!

与更多行业内网友进行交流请登陆EEWORLD论坛。

相关推荐

PromptDA:4K分辨率精准深度估计!(分辨率4k是多少p)

这里是FoxFeed,一个专注于科技的内容平台。背景介绍在计算机视觉领域,深度估计一直是一个重要的研究方向。近日,由DepthAnything团队开发的...

m4a怎么转换成mp3?教你这样转换音频格式

m4a怎么转换成mp3?M4A是MPEG-4音频标准的文件的扩展名,它可以存储各种类型的音频内容,运用比较广泛,尽管m4a被很多媒体应用兼容,但仍有很多应用无法打开它,将m4a转换成mp3就是一个很不...

“讲述初心故事 传递使命情怀”2019第五届江苏医院微电影节启动

“讲述初心故事传递使命情怀”,2019第五届江苏医院微电影节9月16日启动。江苏医院微电影节由新华网江苏有限公司和江苏省医院协会联合举办,扬子江药业集团协办,秉承“讲述初心故事传递使命情怀”为活动...

短视频宝贝=慢?阿里巴巴工程师这样秒开短视频

前言随着短视频兴起,各大APP中短视频随处可见,feeds流、详情页等等。怎样让用户有一个好的视频观看体验显得越来越重要了。大部分feeds里面滑动观看视频的时候,有明显的等待感,体验不是很好。针对这...

阿里巴巴工程师这样秒开短视频(阿里巴巴的工程师多少钱一个月)

前言随着短视频兴起,各大APP中短视频随处可见,feeds流、详情页等等。怎样让用户有一个好的视频观看体验显得越来越重要了。大部分feeds里面滑动观看视频的时候,有明显的等待感,体验不是很好。针对这...

旗鱼浏览器1.0 RC正式版候选版:增账户同步等

从9月19日发布第一个Beta版至今,约80天的时间便这么飞走了,作为2015年底的一个答卷,今天旗鱼浏览器1.0RC(正式版候选版)发布,如果没有意外,明天我们将发布电脑版和安卓版的第一个1.0正...

5种方法,教你将m3u8转换为mp4格式

m3u8格式在许播放器中不受支持,只能在浏览器中进行在线观看,然而,在线观看可能会不大方便,如果网络卡顿的话就会影响观感。想要将...

kgma格式怎么转换为mp3?试试这5种简单的音频转换方法!

由于kgma格式的特殊性和平台限制,除了专属的音乐平台外,其他设备和网络平台是无法识别或播放kgma格式的音乐的,因此为了方便使用,我们就必须将kgma格式转换为mp3。接下来,小编就为大家推荐5种简...

500+本程序员值得看的书籍,7大类,1大合集,收藏,日后有用

一、Golang书籍推荐入门《Go入门指南》...

教你编写最简单的CM3操作系统,160行实现任务创建与切换

如题,任务创建与上下文切换是跟硬件息息相关的,而这恰恰是RTOS编写的最难点,抛开这些功能,剩下的就是双向链表增删改操作了,本例用最精简的方式实现了任务创建与切换,OS启动等功能,并运用了Cortex...

Hot 3D 人体姿态估计 HPE Demo复现过程

视频讲解...

各编程语言相互调用示例,代码简单,生成的软件体积也很小

aardio支持混入很多不同的编程语言,代码简单,生成的软件体积也很小。下面看示例。...

你知道shell脚本中$0 $1 $# $@ $* $? $$ 都是什么意思吗?

一、概述shell中有两类字符:普通字符、元字符。1.普通字符...

NDK打印调用堆栈(logger.error打印堆栈信息)

虽然android源码里有android::CallStack用来打印堆栈,但是NDK里面并没有包含它,所以不能直接调用它,所以要尝试用动态调用的方式来实现。我测试的手机是安卓8.1.0版本,...

小白都能看得懂的Cgo入门教程(cgo2.0教程)

在Go语言开发过程中,尽管Go本身功能强大,但仍然有许多C语言库可以复用,如操作系统API、高性能计算库、数据库驱动等。Go提供了一种强大的机制——Cgo,让我们可以在Go代码中调用C...