# MiniRTOS **Repository Path**: szuhnu/MiniRTOS ## Basic Information - **Project Name**: MiniRTOS - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2024-11-02 - **Last Updated**: 2024-11-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MiniRTOS a MiniRTOS , a lot of comment help you understand ## 缘起 玩嵌入式三、四年了,我一直想搞明白rtos到底是怎样工作的,ucos freertos一直在工作中使用,书籍也看了不少,但总是觉得意犹未尽的样子,他们说的我都懂,每个部分也还算论述详尽,但我无法在头脑中架起一个rtos,或者说我闭上眼,却看不到rtos工作中的样子,这几个月难得清闲,我决定写(抄)一个自己的操作系统,开始时,无从下手,但写完任务切换后,就只剩惊喜了。 好吧,让我们开始吧。 ## 如何用最简单的话描述rtos 试想一下,你有好几个while(1)循环,每个循环都在执行相对独立的功能,这样对于软件架构来说无疑是好事,但问题来了,我很难用裸机程序去控制这些东西,我懒得想何时执行跳转,何时回退,一旦出现问题怎么办。 **操作系统就是帮你在多个while(1)循环中跳转的架构**,她由中断驱动,在中断中执行相应代码,来帮助你跳转到一个while(1)循环中,这样工程师从繁琐的架构问题中解放出来,专心实现任务代码,我么可以说while(1)循环就是一个任务(task)。 ``` void task1() { while(1) { any code --------------------------------> rtos 决定执行此处代码 } } void task2() { while(1) { any code } } void task3() { while(1) { any code } } ``` ## 任务堆栈 (完全不精准的自问自答) 问:何谓栈? 答:栈就是一片内存区域。 问:堆栈究竟有何作用。 答:入栈保存调用子程序时,当前的寄存器状态。子程序调用完毕后,出栈用来恢复调用前的寄存器状态。 问:什么是寄存器状态? 答:无论用何种语言写程序,最终都被编译,成为寄存器之间的一个个操作。pc 寄存器保存当前程序的地址,lr 寄存器保存返回地址, sp寄存器保存堆栈地址......... 调用子程序前,这些寄存器的当前值被保存于栈中,从子程序返回后,这些寄存器又被由栈保存的值所覆盖。 如果我们能从栈中恢复所有寄存器的状态,这样,cpu自然而然回到了调用前的状态,也就顺利从子程序返回。 若想弄清什么是堆栈,最好多写几份汇编。尤其进行程序调用时,汇编自然便求助于堆栈这个概念。 好吧,你我写汇编的机会其实不多,如果想彻底搞定,强烈建议读王爽版《汇编语言》。 栈很重要,在rtos中我们需要为每个任务创建一个专属栈,任务被切换,栈就保存一次任务被切换前的状态,任务被恢复时,专属栈就会把最近一次切换前的状态,赋值给cpu寄存器,自然而然,cpu就又继续执行该任务的代码了。 专属栈作为一片内存区域,C语言用数组即可表示。 而这种切换又被称为为 context switch (上下文切换)。如下所示, context switch 本质上就是在各个任务堆栈间进行切换, 任务被挂起时,状态被保存在专属栈之中,任务被切换为运行状态前,context swicth 负责从专属栈中恢复上一次挂起时的状态。 ``` void task1() { while(1) { any code ---------------Task1Stack[1000] <-------------------------- | } | } | | | | | void task2() | { | while(1) | { | any code----------------Task2Stack[1000] <-------------------------- context switch } | } | | | | | void task3() | { | while(1) | { | any code---------------Task3Stack[1000] <-------------------------- } } ``` ## context switch(上下文切换) 最简单的上下文切换在systic(系统时钟中断)中写好即可,但cortex-m4 芯片为操作系统设计了很多feature,pend_sv中断就是其中之一。 目前主流操作系统都在pend_sv中断中实现context switch, 当然这并不意味着systic(系统时钟中断)就没作用了,实际上在我们的操作系统中,systic(时钟中断)主要实现任务调度器,当任务调度器决定进行切换时,便触发一个pend_sv. 回到任务中,任务本身被挂起时也会触发一个pend_sv,告诉系统,可以切换为下一个任务啦! ## Task Block Control (任务块管理) 任务不单单要有专属堆栈,也许我们要给任务起个名字,或是指定任务的priority(优先级), 因此我们对每个任务都定义了一个任务管理块(TCB),便于管理。 TCB 在源文件中定义如下: ``` typedef struct taskTaskControlBlock { volatile StackType_t *pxTopOfStack;// 栈顶 ListItem_t xStateListItem; /*the tcb is belong in an List*/ StackType_t *pxStack;//栈边界 char pcTaskName[configMAX_TASK_NAME_LEN];//任务名称 TickType_t xTicksToDelay;//任务延时时间,已经弃用 UBaseType_t uxPriority;// 任务优先级 }tskTCB; ``` 尤其是要注意的其实是 **xStateListItem** , 这定义为链表的一个节点,也就是说,一个现代操作系统,其TCB其实是由一个个链表管理的。 当前MiniRTOS实现了两个链表 分别是 readyList 和 delayList。 假如任务需要延时,readyList会移除该任务TCB, delayList则会将该任务TCB增加于其之上。 在MiniRTOS种,链表以如下方式管理TCB。 ``` readyList ---------------------- | priority1 |---------TCB0---------------TCB2----------TCB3 ---------------------- | priority2 | ---------------------- | priority3 |------TCB1---------------TCB4 ---------------------- | priority4 | ---------------------- | priority5 | ---------------------- | priority6 | ---------------------- | priority7 | ---------------------- | priority8 |--------TCB5 ---------------------- | priority9 | ---------------------- ``` readyList本身作为一个数组,其索引就是任务优先级,相同优先级的任务被存储于同一链表下,任务产生延时请求后,TCB(任务控制块)会被暂时踢出readyList,随后则被delayList接收,在任务调度时,优先执行高优先级的任务,只有当高优先级任务执行完毕后(都处在延时状态),任务调度器才会执行相对更低优先级的任务。 ## read the fuck code 启动代码,是cubemx基于nucleo-f401自动生成的,不用管,不用修改。 list.c 完全照搬freertos , 只是用来做链表管理TCB之用,比较稳定,无实现的必要。 osKernel.s 已经完全弃用。 osKernel.c 主要定义了两个最重要的中断函数 pend_sv 和 systic systic 作为系统时钟中断,每次发生后都要运行系统调度器。 task.c 其实实现了该操作系统几乎所有主要功能, 诚然不够软件工程化,许多东西其实应该分开写在不同源文件中,但我希望文件结构越简单越好,便于理解,毕竟这只是一个业余系统。帮助你我一窥究竟而已。 ## 硬件 真的只需要一块 stm32 neucleo-f401