回顾:

Q3:关于RTOS编写,要解决哪些核心问题
A3:
 a. 系统心跳:SysTick初始化
 b. SysTick和PendSV的优先级设置
 c. 任务控制块TCB结构与堆栈初始化
 d. 上下文切换:PendSV_Handler函数
 e. 系统延时函数(阻塞,调度)
 f. 任务调度:SysTick_Handler函数

理清问题关键所在后,开始动手。

 首先,最基本的功能要先保证,如SysTick初始化,任务堆栈初始化,上下文切换;
 然后,开始时步子不能迈太大,我当时就差点裂开了。开始时只想着时间片轮转调度,选择了循环队列来管理列表,结果发现延时列表不能适用,它需要将延时短的任务先取出;
  然后又想到用min_heap来实现优先级队列,它好像很适合动态管理数据,结果因为要判断”叶“的位置,需要额外的空间,多次malloc后失败,因为Heap_Size只有0x00000200 = 512字节(搞来搞去,结构体指针数组初始化,非法访问,数据被破坏,,,)。最后直接用一个结构体指针数组+任务状态来管理了。先搞出来,然后才能谈其它数据结构,优化调度算法。

上面说的有几个问题:(当时刚接触操作系统和数据结构不久,望体谅)
1、512字节应该是可以实现小顶堆的,在数组式堆中辅助长度可以判断左右孩子是否存在(leftchild = 2*i + 1) < len;没有孩子节点或父节点优先权最小时,下沉结束;(只有动态分配的TCB才占用heap,一个任务控制块32字节,理论上可创建管理16个任务)
2、Heap_size可以在startup_stm32f10x_md.s中设置,注意几个地方:芯片具体Flash容量,Target中的IROM1的size,和代码实际使用的内存大小(Project.map)。

1、SysTick

SysTick的作用
 通过SysTick异常周期性地切入系统,进行任务调度。 (SysTick 的最大使命,就是定期地产生异常请求,作为系统的时基。 OS 需要这种“滴答” 来推动任务和时间的管理。—— CM3权威指南)

关于SysTick,我们需要知道什么?
 所有Cortex-M3芯片内部都包含了SysTick定时器(简化了在CM3器件间的软件移植工作),该定时器的时钟源可以是内部时钟,或者是外部时钟。
SysTick定时器是一个24位的倒计数定时器,当计数到0时,将从RELOAD寄存器中自动重载定时器初值,开始新的一轮计数。STM32的延时一般就是通过内部的SysTick来实现的。

STM32基础例程中有关SysTick定时器的初始化设置,可以在delay.c文件(源自正点原子FreeRTOS例程)中查看。delay_init()中也有关于SysTick时钟源的说明:SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); 由此可知,SysTick的时钟源是HCLK。

HCLK :AHB总线时钟,由系统时钟SYSCLK分频得到,一般不分频,等于系统时钟(72MHZ),HCLK是高速外设时钟,是给外部设备的,比如内存,flash,DMA。

SysTick控制及状态寄存器:

初始化代码:

void SysTick_Init(void)
{char *Systick_priority = (char *)0xe000ed23;  //Systick中断优先级寄存器*Systick_priority = 0x00;                     //设置SysTick中断优先级最高SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);//选择外部时钟  HCLKSysTick->LOAD  = ( SystemCoreClock / configTICK_RATE_HZ) - 1UL; //定时周期1msSysTick->VAL   = 0;                                //Systick定时器计数器清0//SysTick->CTRL: bit0-定时器使能 bit1-中断使能 bit2-时钟源选择(=1系统主频,=0系统主频/8)SysTick->CTRL = 0x7;  //选择外部时钟,允许中断,开启定时器
}

敲重点:
①SysTick->CTRL: bit0-定时器使能 bit1-中断使能 bit2-时钟源选择(=1系统主频,=0系统主频/8);
重装载值计算原理:系统时钟频率为72MHz,1秒钟计数72M次;现SysyTick时钟为72M,但只计数72M/1000次(计数范围:72M/1000-1 ~ 0),可得1ms(1/1000秒)重装载一次。

2、PendSV

PendSV作用:任务切换时保存上下文(将一些寄存器和变量的值保存到任务堆栈,或将任务堆栈出栈,恢复过去任务的运行环境)

将任务堆栈出栈,就是将要运行的任务的堆栈的栈顶赋给sp,只有知道了sp我们才能找到对应的任务堆栈,才能找到堆栈保存任务信息;
恢复任务过去的运行环境(如通过恢复PC的值,我们可以知道上一次这个任务运行到哪了)

悬起PendSV的方法是:手工往NVIC的PendSV悬起寄存器中写1。

int* NVIC_INT_CTRL= (int *)0xE000ED04;  //中断控制寄存器地址
void SetPendSV(void)//挂起PendSV
{*NVIC_INT_CTRL=0x10000000;//1<<28
}

③PendSV的中断优先级

void PendSVPriority_Init(void)
{char* NVIC_SYSPRI14= (char *)0xE000ED22;          //PendSV优先级寄存器地址*NVIC_SYSPRI14=0xFF;                           //设置PendSV中断优先级最低
}

3、TCB与任务堆栈

######task.h文件######typedef enum
{eTask_Running = 0,eTask_Ready,eTask_Suspended,eTask_Blocked,eTask_Deleted,
}eTaskSta;#define OS_MAX_TASK 8 //最大任务数量
typedef struct _TaskControlBlock
{stk32 *StkPtr;char name[16];int state; //任务状态int prio;int DlyTim; //任务阻塞时间
}TCB,*pTCB;######task.c文件######pTCB TASK_LIST[OS_MAX_TASK];
int created_task_num = 0;//全局变量//任务堆栈初始化
//入栈顺序:栈顶->栈尾 xPSR,PC,LR,R12,R3-R0,R4-R11共16个(SP(R13)保存在TCB首个成员变量中)。
stk32* task_stk_init(void* func, stk32 *TopOfStack)
{stk32 *stk;stk = TopOfStack;*(stk)    = (u32)0x01000000L;//xPSR 程序状态寄存器,xPSR T位(第24位)必须置1,否则第一次执行任务时进入Fault中断                                                     *(--stk)  = (u32)func;   //PC   初使化时指向任务函数首地址(任务切换时,可能指向任务函数中间某地址)            *(--stk)  = (u32)0xFFFFFFFEL;//LR   保存着各种跳转的返回连接地址,初使化为最低4位为E,是一个非法值,主要目的是不让使用R14,即任务是不能返回的              stk-=13;    return stk;
}void create_new_task( void *func, char name[], int prio, stk32 *TopOfStack, pTCB *tcb)
{int name_len = strlen(name);irq_disable();if(created_task_num == OS_MAX_TASK){printf("Create Task Fail\r\n");}if(tcb){*tcb = (pTCB)malloc(sizeof(TCB));if(*tcb){(*tcb)->StkPtr = task_stk_init(func,TopOfStack);memcpy((*tcb)->name,name,sizeof(char)*name_len);(*tcb)->state = eTask_Ready;(*tcb)->prio = prio;(*tcb)->DlyTim = 0;   TASK_LIST[created_task_num] = *tcb;created_task_num++;}}irq_enable();
}//数字越大,任务优先权越高
extern pTCB pTCB_IDLE;
pTCB GetHighRdyTask(void)
{int i = 0;pTCB pTtmp ,pTrdy = pTCB_IDLE;for(i = 0; i < created_task_num; i++){if(TASK_LIST[i]->state == eTask_Ready){pTtmp = TASK_LIST[i];if(pTtmp->prio > pTrdy->prio)pTrdy = pTtmp;}}return pTrdy;
}

敲重点:
①“stk32 *StkPtr;”必须放在任务控制块的首位;
②入栈顺序与PendSV保存和恢复现场过程有关
③没有另外维护就绪、延时列表,只用了一个结构体指针数组,根据任务的状态来管理任务调度

4、上下文切换:PendSV_Handler函数

 IMPORT pTCB_CurIMPORT pTCB_Rdy  EXPORT PendSV_HandlerEXPORT SP_INITPRESERVE8                  ;//字节对齐关键词,指定当前文件八字节对齐。AREA |.text|, CODE, READONLY ;//定义一个代码段或数据段。THUMB                      ;//指定以下指令都是THUMB指令集(ARM汇编有多种指令集)SP_INIT                        ;初始化PSP指针                      CPSID    I                 ;//关闭全局中断 LDR R4,=0x0                ;//R4装载立即数0(不直接给PSP赋值0而是经进R寄存器作为媒介是因为PSP只能和R寄存器打交道)            MSR     PSP, R4            ;//PSP(process stack pointer)程序堆栈指针赋值0。PSP属用户级(特级权下为MSP),双堆栈结构。 CPSIE    I                 ;//打开全局中断(此时若没有其他中断在响应,则立即进入PendSV中断函数)  BX    LR  ;/******************PendSV_Handler************/
PendSV_HandlerCPSID    I                            ; OS_ENTER_CRITICAL();MRS     R0, PSP                            ; R0 = PSP;CBZ     R0, PendSV_Handler_NoSave          ; if(R0 == 0) goto PendSV_Handler_NoSave;SUB     R0, R0, #0x20            ; R0 = R0 - 0x20;; easy methodSTM     R0, {R4-R11}LDR     R1, =pTCB_Cur            ; R1 = OSTCBCur;LDR     R1, [R1]                 ; R1 = *R1;(R1 = OSTCBCur->OSTCBStkPtr)STR     R0, [R1]                 ; *R1 = R0;(*(OSTCBCur->OSTCBStkPrt) = R0)PendSV_Handler_NoSave                ;每次都会进去(因为PendSV_Handler_NoSave不是函数,而是中间的一个标签,用于跳转);实质就是pTCB_Cur = pTCB_Rdy;每次运行PendSV_Handler,都会使pTCB_Cur指向pTCB_Rdy,所以调度时只需从任务数组中获取pTCB_RdyLDR     R0, =pTCB_Cur           ; R0 = OSTCBCur;LDR     R1, =pTCB_Rdy           ; R1 = OSTCBNext;LDR     R2, [R1]                ; R2 = OSTCBNext->OSTCBStkPtr;STR     R2, [R0]                ; *R0 = R2;(OSTCBCur->OSTCBStkPtr = OSTCBNext->OSTCBStkPtr)LDR     R0, [R2]                 ; R0 = *R2;(R0 = OSTCBNext->OSTCBStkPtr)LDM     R0, {R4-R11}ADD    R0, R0, #0x20MSR     PSP, R0                 ; PSP = R0;(PSP = OSTCBNext->OSTCBStkPtr)ORR     LR, LR, #0x04           ; LR = LR | 0x04;CPSIE     I                     ; OS_EXIT_CRITICAL();BX    LR                        ; return;                                       ; Enable interrupts at processor levelalign 4                    ;//内存对齐指令(编译器提供的),以4个字节(32位)对齐end                        ;//伪指令,放在程序行的最后,告诉编译器编译程序到此结束

kernel.asm汇编代码可以不用自己实现,但要理解它的流程与作用;这里有几个要注意的点:
①PendSV_Handler在kernel.asm中定义了,需要注释掉原来的函数(在stm32f10x_it.h)
②PendSV_Handler_NoSave不是函数,而是中间的一个标签,用于跳转
③每次运行PendSV_Handler,都会使pTCB_Cur指向pTCB_Rdy,所以调度中只需操作pTCB_Rdy
④SP_INIT是PSP指针初始化函数,用汇编写的,在其它地方被调用

5、OS_Start与系统延时函数(阻塞,调度)


void OS_Start(void)
{PendSVPriority_Init(); SysTick_Init();pTCB_Rdy = GetHighRdyTask();pTCB_Rdy->state = eTask_Running;SP_INIT();SetPendSV();while(1);//等待调度}//任务创建举例:
create_new_task(Print_Task,"Print_Task",1,&STK_PRINT_TASK[STK_SIZE-1],&pT2);
void Print_Task(void)
{while(1){printf("print task\r\n");OSDelayTicks(1000);}
}void OSDelayTicks(int ticks)
{pTCB_Cur->state = eTask_Blocked;pTCB_Cur->DlyTim = ticks-1;OS_Schedule();while(pTCB_Cur->DlyTim != 0);//不能是while(1),否则下次任务解阻塞后,继续运行while(1);
}

6、任务调度:SysTick_Handler函数


void OS_Schedule(void)
{   pTCB pT = GetHighRdyTask();//检测是否需要任务切换,如果需要则挂起PendSV中断if(pT != pTCB_Cur){if(pTCB_Cur->state == eTask_Running)pTCB_Cur->state = eTask_Ready;pTCB_Rdy = pT;pTCB_Rdy->state = eTask_Running;SetPendSV();}
}void SysTick_Handler(void)
{   int i = 0; os_cpu_interrupt_disable();if(GetTaskNum(eTask_Blocked) != 0)//延时任务列表中是否有阻塞任务{for(i = 0; i < created_task_num; i++){if(TASK_LIST[i]->state == eTask_Blocked){if(TASK_LIST[i]->DlyTim == 0)//延时时间到了,解除阻塞{TASK_LIST[i]->state = eTask_Ready;}elseTASK_LIST[i]->DlyTim--;}}}if(GetTaskNum(eTask_Ready) != 0)OS_Schedule();os_cpu_interrupt_enable();
}

踩了不少坑,终于成功迈出了第一步:编写了一个具有多任务功能的RTOS;后续还有很多可以改动的地方(可参考FreeRTOS的机制)

①基础:
 如用双向链表(或其它数据结构)优化调度和管理,不阻塞调度的延时函数,阻塞调度的绝对延时函数,补充挂起和删除的操作。
②进阶:
 内存管理、共享资源(消息队列、信号量、任务通知)的访问、中断管理等。

结束:

  这个是去年5月份做的一个小项目,后面去学Linux系统编程了,也就没有再去花时间和精力将其继续扩展完善。这里将当时的项目过程和笔记分享出来,算是抛转引玉吧。希望能帮助大家对M3内核和RTOS调度有更好的理解。

附上:实现多任务调度的demo

【编写自己的RTOS】搞定任务调度相关推荐

  1. 在Android Studio编写代码时,使用MaterialCardView的项目无法正常运行怎么办?简简单单搞定它_莫韵乐与bug的奇妙冒险

    在Android Studio编写代码时,使用MaterialCardView布局的项目闪退怎么办?简简单单搞定它 当我想用MaterialCardView去做一个好看的界面的时候,应用居然闪退了 经 ...

  2. python数学公式编辑工具_1行代码搞定Latex公式编写,这个4.6M的Python小插件,堪称论文必备神器...

    原标题:1行代码搞定Latex公式编写,这个4.6M的Python小插件,堪称论文必备神器 来源:量子位 关注前沿科技 萧箫 发自 凹非寺 量子位 报道 | 公众号 QbitAI 萧箫 发自 凹非寺 ...

  3. 1行代码搞定Latex公式编写,这个4.6M的Python小插件,堪称论文必备神器

    萧箫 发自 凹非寺 量子位 报道 | 公众号 QbitAI 写论文时,手敲成堆的计算公式,被虐到头秃? 做讲课PPT时,几十页的计算推理公式,恨不得直接手写拍照? 现在,解放双手的时刻来了,只需要掌握 ...

  4. 论文必备神器,1行代码搞定Latex公式编写,这个4.6M的Python小插件

    点上方蓝字计算机视觉联盟获取更多干货 在右上方 ··· 设为星标 ★,与你不见不散 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:量子位 报道 | 公众号 QbitAI AI博士笔记系列推荐 ...

  5. tex中让公式和文字在一行_1行代码搞定LaTeX公式编写,这个4.6M的Python小插件,堪称论文必备神器...

    点击上方"深度学习工坊",选择加"星标" 重磅干货,第一时间送达 萧箫 发自 凹非寺 本文转载自:量子位(QbitAI) 写论文时,手敲成堆的计算公式,被虐到头 ...

  6. 搞定 docker k8s,这 2 套视频就够了

    点击关注公众号[Java 充电社],帮你挑选更多高质量的学习视频,每天带你充电,如果本文对你有帮助,点个赞.帮忙分享下,感谢您的支持! 今天给大家精选了 2 套 docker 和 k8s 的视频教程. ...

  7. python查看微信撤回消息_想查看微信好友撤回的消息?Python帮你搞定

    要说微信最让人恶心的发明,消息撤回绝对能上榜. 比如你现在正和女朋友用微信聊着天,或者跟自己喜欢的女孩子聊着天,一个不留神,你没注意到对方发的消息就被她及时撤回了,这时你很好奇,好奇她到底发了什么?于 ...

  8. 机器学习建模神器PyCaret已开源!提升效率,几行代码轻松搞定模型

    Datawhale干货 编译:张峰,Datawhale成员 寄语:PyCaret,是一款 Python中的开源低代码(low-code)机器学习库,支持在「低代码」环境中训练和部署有监督以及无监督的机 ...

  9. 酸爽!我用这套无人值守安装系统瞬间搞定上百台服务器

    来自:DBAplus社群 作者介绍: 季城希,甜橙金融运维工程师,多年IDC运维经验.擅长IDC中服务器批量高效快速集成交付,精通各品牌型号服务器硬件产品及维护. 一.前言 为啥要用无人值守安装系统? ...

最新文章

  1. Kali Linux 2017.1脚本gerix.py修复
  2. python怎么用拼音-又一个奇葩要求,Python是如何将“中文”转“拼音”的?
  3. node.js java web_Node.js 做 Web 后端优势为什么这么大?
  4. Python 打包 exe 程序避坑指南:没有安装包也能运行小程序啦~开心
  5. 你是一个有价值的产品经理吗?
  6. aqs java_Java并发之AQS详解
  7. java控制关键字continue,break,return
  8. linux root 设置中文,ubuntu 8.04 root用户下的中文环境配置-Linux频道-中国IT实验室
  9. NG-ZORRO 7.3.0 发布,Ant Design 的 Angular 实现
  10. bzoj 2844: albus就是要第一个出场(线性基)
  11. 你不能忽视的HTML语言
  12. 笔记:C# log4net App.config 配置系统未能初始化问题的一种处理方法
  13. 一次完整的软件工程课程设计
  14. DongDong认亲戚(字符串之间的并查集应用)
  15. win7怎么清理java缓存文件夹_win7c盘内存清理最彻底的方法
  16. 肝了一宿才收集的48个超炫酷的 CSS 文字特效,绝对值得收藏!!!
  17. 如何编制试算平衡表_利用Excel制作总账表试算平衡表
  18. MAC下格式化移动硬盘
  19. 喜欢你,才不顾一切的作践自己:QQ伤感日志
  20. 第一章 1.3误差定性分析与避免误差危害

热门文章

  1. 磨刀不误砍柴 - 配置适合工作学习的桌面环境
  2. 赣州php微信群,PHP微信群加群强制分享转发裂变源码
  3. python实现简单的ps色阶调整过程
  4. 巴鲁夫使用CAE数据扩展其产品目录
  5. 基于SSH的实验室预约管理系统
  6. GitLab之创建项目组及上传项目
  7. [FPGA] 1、Artix-7 35T Arty FPGA 评估套件学习
  8. 如何 使用 apache 访问 本地目录及本地文件
  9. python按字典顺序输出单词频率_用python编写一段程序,输入若干单词,按照单词长短进行排序,并统计所有单词中每个字母(a-z)出现的次数...
  10. java httpClient使用代理实现互联网公网访问