拓冰建站拓冰建站
首页 / 资讯中心 / 正文

手写C语言实时操作系统:任务调度与上下文切换核心实现解析

简介这是一份面向嵌入式初学者与RTOS入门开发者的轻量级C语言实时操作系统实践代码聚焦任务调度、中断响应与时间管理等核心机制的理解与手写实现。资源共7个文件含2个C源文件实现RTOS内核逻辑与主程序、2个头文件定义双链表结构、任务控制块及API接口、1个Qt项目配置文件.pro、1个用户配置文件.user和1个VS Code调试配置c_cpp_properties.json整体仅6KB结构精简、无冗余依赖便于逐行阅读与移植验证。已有551人学习下载适合在STM32等裸机平台快速部署调试。读者可完整掌握基于双向链表的任务就绪队列构建、优先级调度策略实现、系统滴答定时器配置及中断上下文切换流程是深入理解操作系统内核机制不可多得的微型参考范例。 拿到这个“手写c语言实时操作系统代码.rar”压缩包的时候我第一反应不是急着解压看代码而是先在心里问了自己一句现在FreeRTOS、RT-Thread、μC/OS这些现成系统满天飞为什么还有人愿意从零手写一个RTOS这个问题我琢磨了很久。早些年我为了搞懂任务调度和上下文切换前前后后折腾过好几版迷你内核从裸机状态机一路改到抢占式调度。说实话用现成RTOS写业务代码是一回事亲手把一个内核从无到有搭起来是完全另一回事。手写RTOS最值钱的地方不在于“我又造了一个轮子”而在于你把操作系统最核心的那几个机制——任务切换、就绪表、临界区保护、信号量——彻底吃透了。这篇博文就以这份手写RTOS代码包为蓝本拆一拆一个完整的RTOS项目应该包含哪些模块、每个模块在干什么、核心代码是怎么设计的以及在学习和复现的过程中会遇到哪些坑。适合正在学嵌入式操作系统、准备入门RTOS、或者想自己写一个小内核练手的同学参考。哪怕你现在还在裸机开发阶段看完这篇也能对“操作系统到底在干什么”有一个非常具体的认识。1. RTOS底层的核心概念与手写思路1.1 实时操作系统在解决什么问题很多初学者对RTOS的理解停留在“能跑多任务”这个层面但“多任务”只是表象。RTOS真正解决的是两个问题一个是时间确定性一个是资源隔离与协同。时间确定性就是说系统能保证某个任务在指定的时间内被调度执行。裸机前后台系统靠一个大循环加中断任务之间的切换完全依赖代码写死的顺序如果某一个任务执行时间过长后续所有任务都会被拖住。这就是我们常说的“阻塞”风险。而RTOS通过优先级抢占调度让高优先级任务能够打断低优先级任务从而保证关键任务的时间确定性。资源协同则是说多个任务共享CPU、内存、外设时如何保证互不干扰、数据不错乱。这靠的是任务调度器、临界区、信号量、互斥量、消息队列这些机制。手写RTOS的过程本质上就是把这两个问题的答案用C语言一行一行实现出来。1.2 手写RTOS的三个核心设计目标如果你问我手写一个RTOS最重要的是什么我会总结成三句话调度要快、切换要稳、临界区要严。调度要快是指每次找“当前最高优先级就绪任务”的时间必须是确定的最好是O(1)时间复杂度。不允许用遍历链表的方式找最高优先级任务那样任务一多调度时间就不可控了。切换要稳是指任务切换时寄存器的保存和恢复必须完整可靠不能漏寄存器不能错栈指针。在Cortex-M系列内核上这通常依赖PendSV异常和硬件自动压栈来完成。临界区要严是指多任务访问共享资源时中断屏蔽的粒度必须准确。关中断时间长系统实时性变差关中断时间短或漏关共享数据可能被其他任务或中断破坏。手写RTOS最常出问题的地方往往就是临界区保护做得不够严密。1.3 RTOS代码包的整体模块划分拿到这份rar包之后我建议你先不要急着看main函数先按照模块把文件过一遍。一个完整的RTOS工程通常分成以下几个部分模块主要职责典型文件内核核心任务管理、调度器、时间管理os_core.c、os_sched.c、os_time.c同步与通信信号量、互斥量、消息队列、事件标志os_sem.c、os_mutex.c、os_queue.c内存管理静态内存池、动态内存分配os_mem.c移植层上下文切换、中断底层的CPU相关代码port.c、port_asm.s配置与头文件内核参数配置、数据类型定义os_cfg.h、os_type.h拿到代码先按这个框架去归类。如果这份代码的模块划分和上面不完全一致也没有关系关键是你要能分辨出每个文件解决的是哪一类问题。我见过不少初学者一上来就盯着一两百行的main函数看看半天也不知道系统在干什么。正确的打开方式是先读os_cfg.h里的配置宏再看任务创建和调度器的实现最后才轮到业务代码。1.4 手写RTOS的适用场景和学习价值有人会问我学RTOS为什么不用FreeRTOS非要自己写这个问题的答案取决于你的目标。如果只是想用RTOS做产品直接上FreeRTOS或RT-Thread省时省力生态也成熟。但如果你是搞嵌入式底层开发的或者对“操作系统到底如何工作”有执念手写一遍的价值就体现出来了。面试的时候面试官问“上下文切换的过程是什么”“优先级反转怎么解决”“信号量和互斥量有什么区别”你如果只是背过答案很难经得住追问。但如果你实打实写过一遍任务切换的汇编代码调通过栈溢出导致的HardFault这些问题根本不用背——因为你是真的踩过坑、真的懂原理。我这些年面试过的嵌入式候选人里凡是能把手写RTOS的上下文切换讲清楚的基础都差不了。2. 代码包的内容架构与核心数据结构2.1 任务控制块TCBRTOS的心脏任务控制块Task Control BlockTCB是RTOS里最重要的数据结构。每一个任务都对应一个TCB里面保存了这个任务的全部上下文信息栈指针、任务状态、优先级、延时计数、事件等待信息等等。可以说TCB就是任务在操作系统里的“身份证”和“档案袋”。一段典型的TCB定义长这样typedef struct tcb { uint32_t *stack_ptr; /* 当前栈指针指向任务栈的栈顶 */ struct tcb *next; /* 链表节点用于就绪链表/等待链表 */ uint32_t priority; /* 任务优先级数值越小优先级越高 */ uint32_t state; /* 任务状态就绪、运行、阻塞、挂起 */ uint32_t delay_ticks; /* 延时时钟节拍数 */ uint32_t timeout; /* 等待事件超时时间 */ void (*task_entry)(void *arg); /* 任务入口函数 */ void *arg; /* 任务参数 */ uint8_t stack_fill; /* 栈填充标志用于栈溢出检测 */ } os_tcb_t;你去看Linux内核里的task_struct虽然字段多到你能看晕但其本质和这个简单的TCB是一样的保存任务的运行状态、调度信息和资源信息。理解了TCB你就理解了整个操作系统的数据结构基础。在写RTOS的时候TCB的存储方式有两种选择一种是静态数组分配一种是链表动态分配。静态数组简单可控任务数量上限在编译期就确定了适合MCU场景链表灵活但分配和释放需要额外的内存管理机制。手写RTOS建议先选静态数组等把核心逻辑跑通了再考虑动态分配不然你还要先处理堆碎片问题调试难度直接翻倍。2.2 调度策略抢占式、时间片与协作式的取舍调度器是RTOS的第二个核心。调度器的职责只有一个——在所有就绪任务中挑出下一个应该运行的任务。调度策略决定了“按什么标准挑”。目前主流的调度策略有三种优先级抢占式调度Preemptive Scheduling每个任务有一个优先级系统永远运行当前就绪任务中优先级最高的那个。高优先级任务一旦就绪低优先级任务立刻被抢占。这是大多数RTOS的主策略强调实时性和响应速度。时间片轮转调度Round-Robin Scheduling对相同优先级的多个任务每个任务运行一个固定的时间片时间片耗尽后切换到下一个同优先级任务。这种策略强调公平性适合多个同等重要任务交替运行的场景。协作式调度Cooperative Scheduling任务主动让出CPU时才发生切换。优点是实现简单、没有抢占带来的同步问题缺点是如果一个任务不主动让出CPU其他任务就永远得不到运行。适合逻辑非常简单、任务交互不频繁的系统。手写RTOS最常见的做法是以优先级抢占式调度为主给相同优先级的任务附加时间片轮转。这样既保证了关键任务的高实时性又避免了多个相同优先级任务之间互相“饿死”的问题。调度策略选型的逻辑其实可以类比生活中的排队场景。抢占式调度就像急诊室危重病人高优先级任务来了立即插队处理时间片轮转就像银行柜台每个窗口依次叫号谁也不能一直霸占窗口协作式调度就像会议室大家自觉轮流发言主持人任务不点名谁也不能抢话。2.3 任务状态机就绪、运行、阻塞与挂起一个RTOS任务的生命周期包含多个状态。掌握任务状态之间如何迁移是理解RTOS代码的关键。典型的状态划分如下状态含义进入条件离开条件就绪态任务已具备运行条件等待调度器分配CPU任务创建完成、延时结束、等待的事件到达被调度器选中运行运行态任务正在占用CPU执行调度器从就绪表中选择该任务被更高优先级任务抢占、主动延时、等待事件阻塞态任务因等待某个条件而暂停运行调用延时函数、等待信号量/消息队列/互斥量等待条件满足、超时挂起态任务被其他代码显式暂停其他任务调用任务挂起接口其他任务调用任务恢复接口让我用一个日常场景来解释这些状态你坐在工位上写代码运行态顺手订了一杯外卖咖啡调用延时函数等待外卖喝完了继续写延时结束回到就绪态等待调度器分配CPU。如果这时领导突然安排你处理一个线上问题更高优先级任务就绪你会立刻放下手头的代码去处理紧急问题运行态被抢占。等处理完了你再回来看之前写的代码恢复运行。手写RTOS时任务状态的维护主要靠TCB里的state字段和对应的状态迁移函数。每当你调用os_delay、os_sem_pend、os_mutex_pend这样的接口时内核内部都会做同样几件事把当前任务从就绪表里摘除设置任务的新的状态然后触发一次调度让出CPU。3. 核心模块实现与关键代码思路3.1 任务创建与栈初始化细节任务创建是RTOS的第一个动作。任务创建接口要做的事情包括分配TCB、分配任务栈、初始化任务栈、把任务加入就绪表。任务栈初始化是整个过程中最需要细看的地方。因为任务第一次被调度执行时CPU并不是从函数入口开始跑而是从栈里恢复现场。所以我们在创建任务时需要伪造一个“看起来像是任务刚被中断打断后压栈的现场”。在Cortex-M3/Cortex-M4内核上任务栈初始化的关键代码如下uint32_t *os_task_stack_init(void (*task_entry)(void *arg), void *arg, uint32_t *stack_top) { uint32_t *sp stack_top; /* 模拟异常压栈后的栈帧布局 */ *--sp (uint32_t)task_entry; /* PC任务入口地址 */ *--sp 0x01000000L; /* xPSR默认使用Thumb模式 */ *--sp 0; /* R0任务参数 */ *--sp 0; /* R1 */ *--sp 0; /* R2 */ *--sp 0; /* R3 */ *--sp 0; /* R12 */ *--sp 0; /* LR */ *--sp 0; /* 通用寄存器 R4-R11 的初始值 */ ... return sp; /* 返回新的栈指针 */ }这里的顺序不能写错。Cortex-M内核在响应异常时硬件会自动压栈xPSR、PC、LR、R12、R3-R0剩下的R4-R11由软件在PendSV里手动压栈。任务第一次启动时PC指向任务入口函数一旦调度器加载这个伪栈帧CPU就会从任务的第一条指令开始运行。我之前见过有人手写RTOS任务创建后第一次调度直接HardFault排查了半天最后发现是栈指针初始化时少留了8个字节的空白区导致硬件压栈时越界。所以栈帧初始化宁多勿少每一步都要和硬件手册对照。3.2 上下文切换任务切换的“换人”机制上下文切换是RTOS的灵魂。任务A切换到任务B本质就是把A当前的所有寄存器值保存到A的TCB栈指针指向的位置然后从B的TCB栈指针指向的位置恢复B的寄存器值再跳转到B上次停止的地方继续执行。在Cortex-M上PendSV异常是专门为上下文切换设计的。为什么不用SVC因为SVC需要软件主动触发而且一旦在中断里触发会出问题。PendSV可以被配置为最低优先级这样它会在所有其他中断处理完成后才执行避免在中断处理过程中切换任务导致关键中断被延迟。核心切换代码的流程是这样的/* 以下为Cortex-M3/4内核的PendSV_Handler汇编实现 */ __asm void PendSV_Handler(void) { IMPORT os_get_next_task_sp /* 获取下一个要运行任务的栈指针 */ IMPORT os_current_tcb /* 当前任务的TCB指针 */ /* 保存当前任务的上下文 */ MRS R0, PSP /* 读取当前任务栈指针 */ STMDB R0!, {R4-R11} /* 手动压栈R4-R11 */ LDR R1, os_current_tcb LDR R1, [R1] STR R0, [R1] /* 将新栈指针保存到当前任务TCB */ /* 切换任务 */ BL os_get_next_task_sp /* 找到最高优先级就绪任务 */ /* 恢复新任务的上下文 */ LDR R1, os_current_tcb STR R0, [R1] LDMIA R0!, {R4-R11} /* 弹出R4-R11 */ MSR PSP, R0 /* 更新任务栈指针 */ ORR LR, LR, #0x04 /* 确保返回后使用PSP */ BX LR /* 返回到新任务 */ }这段汇编看起来不长但每一句都值得反复揣摩。其中最关键的是最后那三行MSR PSP指令把新任务的栈指针写回CPUORR LR指令让异常返回时使用线程栈指针PSP而不是主栈指针MSPBX LR触发异常返回。整个切换过程就完成了“保存旧任务现场、加载新任务现场”的闭环。上下文切换的最经典问题就是为什么任务函数里直接写返回语句会导致系统崩溃因为任务入口函数不应该返回一旦返回PC就会被设置成栈里的某个垃圾值系统直接跑飞。所以每个任务函数的最外层几乎都有一个死循环或者调用任务删除接口后让调度器切换到其他任务。3.3 调度器实现就绪表和位图查找算法调度器要在所有就绪任务里找到最高优先级的任务。如果任务数量不多最简单的办法是遍历就绪数组但这样时间复杂度是O(n)任务一多就不可控了。标准做法是用就绪表优先级位图的方案。就绪表的核心数据结构是两个数组uint32_t os_ready_prio_mask; /* 每个优先级对应的位图1表示有任务就绪 */ uint8_t os_ready_tbl[OS_PRIO_GROUP_NUM]; /* 每个优先级组内哪些优先级有就绪任务 */比如os_ready_prio_mask一共支持32个优先级bit0表示优先级0是否有任务就绪bit1表示优先级1是否有任务就绪以此类推。查找最高优先级任务时只要数一数位图里最低的那个1在哪里就能直接得到结果。在Cortex-M3/M4上可以用CLZ指令来数前导零一条指令就搞定。在普通C语言中可以用查表法或者二分法实现。uint32_t os_get_highest_ready_prio(void) { uint32_t mask os_ready_prio_mask; uint32_t prio 0; while ((mask 0x01) 0) { prio; mask 1; } return prio; }这段代码虽然简单但它的时间复杂度是O(1)不会因为任务数量增加而变慢。这是RTOS调度器最典型的设计思路用空间换时间用位运算换遍历。我见过有人把就绪表实现成一个有序链表每次插入任务时按优先级排序。任务少的时候问题不大但一旦任务到几十个调度器的查找时间就开始变得不可控。这个设计在工业级RTOS里是绝对不能接受的。3.4 时间管理SysTick、系统节拍与延时实现RTOS的系统节拍SysTick是操作系统的“心跳”。它通常由一个硬件定时器周期性触发中断我们把两次中断之间的时间称为一个tick。系统的所有时间相关的功能——任务延时、超时检测、时间片轮转——都建立在这个心跳之上。SysTick中断服务函数里要做的事情我总结为三步遍历所有任务把处于延时状态的任务的delay_ticks减1减到0就唤醒它检查是否有等待超时的任务超时则报错或唤醒最后触发调度器看看是否有更高优先级的任务变成就绪态需要立即抢占。os_delay的实现逻辑非常直观void os_delay(uint32_t ticks) { os_enter_critical(); os_current_tcb-delay_ticks ticks; /* 设置延时计数 */ os_set_task_state(os_current_tcb, TASK_STATE_BLOCKED); /* 任务进入阻塞态 */ os_exit_critical(); os_schedule(); /* 主动让出CPU触发调度 */ }延时不是让CPU空转而是把任务自己挂起到阻塞态等SysTick把计数减到0后再唤醒。这个机制保证了CPU在任务延时期间能够去执行其他任务。关于系统节拍频率的选择经验是100Hz到1000Hz之间比较常见。节拍频率越高时间精度越高但CPU被SysTick中断打断的次数也越多整体开销越大。做电机控制或高速采集时我会用到1000Hz做温湿度采集这种慢速应用100Hz足够了。3.5 同步与通信机制信号量、互斥量和消息队列多任务环境下任务之间经常需要协同工作。信号量用来做任务同步和资源计数互斥量专门用来保护共享资源消息队列用来在任务之间传递数据。信号量的核心实现并不复杂typedef struct os_sem { uint32_t count; /* 当前信号量计数 */ os_tcb_t *wait_list; /* 等待该信号量的任务链表 */ } os_sem_t; void os_sem_post(os_sem_t *sem) { os_enter_critical(); if (sem-wait_list ! NULL) { /* 有任务在等待直接唤醒最高优先级的等待任务 */ os_tcb_t *tcb os_find_highest_prio_task(sem-wait_list); os_remove_from_list(sem-wait_list, tcb); os_set_task_ready(tcb); } else { sem-count; } os_exit_critical(); os_schedule(); } void os_sem_pend(os_sem_t *sem, uint32_t timeout) { os_enter_critical(); if (sem-count 0) { sem-count--; os_exit_critical(); } else { /* 任务阻塞等待信号量 */ os_set_task_blocked(os_current_tcb, timeout); os_add_to_list(sem-wait_list, os_current_tcb); os_exit_critical(); os_schedule(); } }互斥量和信号量的主要区别是互斥量自带优先级继承机制。优先级继承的意思是当一个低优先级任务持有互斥量时如果有高优先级任务来等待这个互斥量系统会临时把低优先级任务的优先级提升到与高优先级任务相同避免高优先级任务等待太久——这就是经典的优先级反转问题的解决方案。消息队列的本质是“用内存换解耦”。一个任务生产数据另一个任务消费数据双方不需要知道对方什么时候执行只要往队列里扔/取数据就行。这在传感器数据采集、命令解析等场景里非常常用。手写这些同步机制时最重要的细节是在操作等待链表和计数时必须进入临界区或者关中断。如果忘了保护在中断里正好有任务调用os_sem_post而当前任务正在执行os_sem_pend的临界区操作就会出现数据竞争轻则信号量计数错乱重则整个链表指针被破坏系统直接跑飞。4. 调试方法与常见问题排查4.1 栈溢出手写RTOS最容易踩的坑栈溢出是RTOS调试中最常见、也最难排查的问题。裸机程序只有一个栈栈溢出表现为整个系统跑飞定位起来还相对容易RTOS里每个任务都有自己的栈哪个任务溢出了你根本看不出来因为系统可能正常运行几天后才随机崩溃一次。我推荐两个方法组合使用。第一个方法是在创建任务时把任务栈的每个字节都填充成固定的魔数比如0xAA。任务运行一段时间后通过调试器查看任务栈尾部的魔数是否被覆盖。如果被覆盖说明这个任务的栈不够用需要调大。第二个方法是在上下文切换时检查栈指针的边界。每次PendSV切换任务时都检查一下当前任务栈指针是否越界。可以把检查放到任务切换入口处if (sp task-stack_base || sp task-stack_base task-stack_size) { /* 栈溢出进入错误处理 */ os_error_handler(ERR_STACK_OVERFLOW); }任务栈大小的估算也是有经验的。粗略估法栈大小 函数调用嵌套深度 × 单层栈帧消耗 中断嵌套层数 × 中断栈帧消耗 128字节安全余量。如果你不确定宁可给大一点。MCU的RAM通常有几十到几百KB多给几个任务分配256字节对整体内存占用影响不大但对于系统稳定性来说这几百字节可能就是生与死的区别。4.2 优先级反转与中断嵌套问题优先级反转是RTOS里的经典问题。简单说就是高优先级任务A等待一个被低优先级任务C持有的互斥量而中等优先级任务B一直抢占C的运行时间导致A迟迟拿不到互斥量系统实时性大打折扣。解决优先级反转的标准做法是优先级继承。在互斥量实现中当一个任务成功获取互斥量时检查有没有更高优先级的任务在等待这个互斥量。如果有就把当前持有互斥量的任务优先级临时提升到等待任务的优先级等释放互斥量后再恢复原来的优先级。中断嵌套则是另一个常见的坑。Cortex-M内核支持中断嵌套但如果你在RTOS中打开了中断嵌套需要确保所有中断里调用的RTOS API都是安全的而且SysTick中断的优先级必须比所有使用RTOS API的中断优先级要低数值上要大。否则当中断A正在执行os_sem_post时如果SysTick抢先触发任务切换可能会出现上下文切换发生在内核数据结构操作中间导致链表被破坏。我的建议是在使用RTOS的项目里中断服务函数尽量只做两件事——标志位置位和消息发送。剩下的业务处理都放到任务中完成。这样做的好处是中断处理时间短系统实时性好也不容易出同步问题。4.3 常见问题速查表调试手写RTOS的过程中我把经常遇到的问题整理成了下面的速查表问题现象可能原因排查思路系统上电后直接HardFault任务栈初始化错误、栈指针越界、任务函数返回检查栈帧布局和任务入口函数是否有死循环运行一段时间后随机崩溃任务栈溢出、临界区未保护共享数据用0xAA填充法检查栈溢出审查共享数据的保护高优先级任务迟迟得不到调度优先级反转、中断优先级配置错误使用优先级继承互斥量检查SysTick优先级任务切换非常慢调度器用了遍历查找、每个tick都做无谓调度改用位图法查找最高优先级任务信号量等待超时但任务未唤醒延时计数和超时计数混用、时间管理错误检查SysTick中断里对delay_ticks的处理逻辑中断里调用RTOS API导致死锁在中断里调用了阻塞型API中断中禁止调用pend类函数改用post类函数这套排查方法在你调试自己的RTOS代码时几乎每天都会用到。尤其是栈溢出和临界区保护这两个问题几乎每个手写RTOS的人都会遇到。我早期写RTOS的时候因为临界区里少了一条关中断指令导致信号量计数被中断里的post操作反复修改排查了整整两天才找到问题从那以后我写临界区代码都是先写注释、再写代码、最后逐行检查锁保护范围。5. 工程化落地与代码包组织经验5.1 代码包目录结构与工程管理这份rar包解压之后应该是一个完整的工程目录。我拿到手写RTOS代码包之后一般会按下面的结构重新组织一遍方便学习和后续扩展rtos_project/ ├── app/ │ ├── main.c # 应用入口 │ └── task_demo.c # 示例任务 ├── kernel/ │ ├── os_core.c # 内核初始化和任务管理 │ ├── os_sched.c # 调度器实现 │ ├── os_time.c # 时间管理和延时 │ ├── os_sem.c # 信号量 │ ├── os_mutex.c # 互斥量 │ ├── os_queue.c # 消息队列 │ └── os_mem.c # 内存管理 ├── port/ │ ├── port.c # 移植层C代码 │ └── port_asm.s # 上下文切换汇编 ├── include/ │ └── os_cfg.h # 内核配置头文件 └── build/ ├── Makefile └── startup.s # 启动文件这个结构的好处是内核代码和应用代码分离移植层独立将来如果要换到不同芯片只需要修改port目录下的代码。工程管理我建议直接用Makefile或CMake不要依赖IDE的图形化配置因为图形化配置无法固化成文本记录到了团队协作或版本迁移的时候会很痛苦。5.2 rar压缩包的组织与分享建议题目里的“.rar”后缀提醒了我一点这类资源在分享和分发时的组织方式也决定了别人能不能“顺利跑起来”。压缩包里的代码我建议一定要包含三个东西完整的工程源码、README说明文档、以及一个demo演示工程。README里至少要写清楚适用的芯片型号、开发环境版本、编译步骤、以及demo任务的运行现象。如果能在文档或代码里附上任务栈大小的参考值对后来学习的人帮助更大。压缩时还应该注意确认源码目录里没有临时文件、编译产物和IDE自动生成的配置文件。这些文件会占用体积还会让人误以为这是工程必需的部分。打包前把build目录和*.o文件清干净压缩包体积小别人解压后也不会遇到路径引用错乱的问题。如果压缩包太大可以考虑分卷压缩或只保留源码和必要配置文件。但无论怎么压缩最好在分享前自己先完整解压一遍跑一遍编译和烧录流程验证一下。自己都没跑通的工程分享出去只会浪费别人的时间。5.3 从手写RTOS到工业级RTOS的学习路线写完一个能跑的手写RTOS之后下一步该怎么走我的建议是分三步走。第一步把FreeRTOS的源码拿来做对比阅读。重点看它的任务切换、就绪表和队列实现对比一下你的实现和工业级实现的差距在哪里。你会发现很多细节比如FreeRTOS的链表节点嵌在TCB里而不是用指针串起来比如它用configMINIMAL_STACK_SIZE来保证最小栈深度这些都是实际工程中沉淀出来的经验。第二步给手写RTOS增加功能模块。可以试着加一个软件定时器模块或者加一个带优先级继承的互斥量版本再或者加一个内存池分配器。每加一个模块你对RTOS的理解就深一层。第三步尝试把RTOS移植到不同的芯片平台上。从Cortex-M系列移植到RISC-V或者STM32的不同型号你会理解哪些代码是CPU相关的、哪些是平台相关的这才是真正的移植能力。我自己当初学RTOS的路径也是这样先写一个能跑的最小内核再对照FreeRTOS源码精读再把RT-Thread的调度器源码通读了一遍。这个过程走下来之后用任何RTOS做项目都变得非常轻松因为底层原理已经通了剩下的只是API层面的差异而已。最后再说一个我实际使用中的体会。手写RTOS最忌讳的是“写完就扔”。如果你写完了内核只跑了个点灯demo就收工那这个学习过程的价值至少打了五折。我建议你把这份代码当成一个持续迭代的“玩具内核”项目每隔一段时间给它加一个新功能比如加一个互斥锁、加一个消息邮箱、加一个内核态的时间统计工具。每次增加功能你都会对操作系统调度、内存管理和任务通信有更深的理解。这才是手写RTOS能带给你的最大回报——不是那一堆代码而是代码背后那些永远忘不掉的原理和教训。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门