【学习笔记】一种特别有意思的 RTOS 任务切换方法
一、介绍说明
目前常见流行的 RTOS 实现方式,如 FreeRTOS、uCosII、RT-Thread 等等,它们的内部的任务切换实现原理都差不多,都是通过借助汇编,根据不同的情况读写 CPU 寄存器(R0~R15)来实现保护现场和恢复现场以及指令跳转,效率很高,但也就意味着很难做到跨平台使用。
前段时间朋友向我推荐了一款非常精巧的 OS (cocoOS),无意中发现其内部实现的任务切换机制特别地有意思,竟然未涉及到 CPU 的寄存器,纯靠 C 语言的语法实现任务切换。这就意味着很容易地跨平台使用,除了需要提供时基以外,几乎不需要做任何改动即可投入使用,这着实让我惊奇不已。
官方网站:www.cocoos.net 代码仓库:github.com/cocoOS/cocoOS
总结来说:cocoOS 是通过代码行号 __LINE__ 和借助 switch() 和 case 实现执行位置的记录和跳转,再通过 return 实现中断任务。
二、原理解析
以下是一份可以在 Linux 跑的完整示例代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <cocoos.h>
static void *ticker(void* arg)
{
struct timespec req = {.tv_sec = 0,.tv_nsec = 1000000};
while(1) {
nanosleep(&req, NULL);
os_tick();
}
return (void*)0;
}
static void system_setup(void)
{
pthread_t tid;
if (pthread_create(&tid, NULL, &ticker, NULL)) {
printf("can't create thread\n");
while(1) usleep(1000);
}
return ;
}
static void myTask1()
{
task_open();
for (;;)
{
printf("[myTask1] -> sleep 1000 ms\n");
task_wait(1000);
printf("[myTask1] -> sleep done\n")
}
task_close();
}
static void myTask2()
{
task_open();
for (;;)
{
printf("[myTask2] -> sleep 3000 ms\n");
task_wait(3000);
printf("[myTask2] -> sleep done\n")
}
task_close();
}
int main(void) {
system_setup();
os_init();
task_create( myTask1, NULL, 10, 0, 0, 0 );
task_create( myTask2, NULL, 20, 0, 0, 0 );
os_start();
return 0;
}
执行的结果:

与其它 RTOS 不一样的是,每个任务实体中多出了 task_open()\task_close(),它俩实际上是宏定义:
#define task_open() OS_BEGIN
#define OS_BEGIN uint16_t os_task_state = os_task_internal_state_get(running_tid);\
switch ( os_task_state )\
{ \
case 0:
#define task_close() OS_END
#define OS_END os_task_kill(running_tid);\
running_tid = NO_TID;\
return;}
再看 task_wait() 的实现,其实际也是一个宏定义:
#define task_wait(x) OS_WAIT_TICKS(x,0)
#define OS_WAIT_TICKS(x,y) do {\
os_task_wait_time_set( running_tid, y, x );\
OS_SCHEDULE(0);\
} while ( 0 )
#define OS_SCHEDULE(ofs) os_task_internal_state_set(running_tid, __LINE__+ofs);\
running_tid = NO_TID;\
return;\
case (__LINE__+ofs):
从这两个宏的展开实现,就可以看出任务调度的实现原理,可以通过 gcc 来看 myTask1 任务宏展开后的代码对比:

第 3 行的 os_task_state 会在创建任务时被默认设置为 0,因此任务首次执行时会进入到 for 循环中,当任务进入 for 循环后调用 task_wait(100) 时,会执行 11 ~ 15 行代码,通过 __LINE__ 来得到当前代码行号为 34,然后调用 os_task_internal_state_set() 保存,并作为 case 34 条件,然后设置任务状态为 WAITING_TIME,再通过 15行的 return 返回,该任务结束运行,回归 os 调度下一个任务。
uint8_t task_create( taskproctype taskproc, void *data, uint8_t prio, Msg_t *msgPool, uint8_t poolSize, uint16_t msgSize ) {
......
task->internal_state = 0; // 默认设置为 0
task->taskproc = taskproc;
......
return task->tid;
}
uint16_t os_task_internal_state_get( uint8_t tid ) {
return task_list[ tid ].internal_state;
}
void os_task_internal_state_set( uint8_t tid, uint16_t state ) {
task_list[ tid ].internal_state = state;
}
void os_task_wait_time_set( uint8_t tid, uint8_t id, uint32_t time ) {
os_assert( tid < nTasks );
os_assert( time > 0 );
task_list[ tid ].clockId = id;
task_list[ tid ].time = time;
task_waiting_time_set( tid );
}
static void task_waiting_time_set( uint8_t tid ) {
task_list[ tid ].state = WAITING_TIME;
}
与此同时 os_tick() 会间隔 1 毫秒检查这些任务,myTask1 的等待时间到达,会被设置为就绪态 READY。OS 开始调度 myTask1 ,此时 myTask1 函数会再次调用,通过 os_task_internal_state_get() 来获取之前设置的行号 34,作为 switch(34) 的条件,满足 case 34 条件从而跳转到所设置行号的空指令执行。接者就执行到了第 19 行代码。
这里是 main() 调用的 os_start() 的实现:
void os_start( void ) {
running = 1;
os_enable_interrupts();
for (;;) {
os_schedule(); // 不停的进行 OS 任务执行和调度
}
}
static void os_schedule( void ) {
running_tid = NO_TID;
#ifdef ROUND_ROBIN
/* Find next ready task */
running_tid = os_task_next_ready_task();
#else
/* Find the highest prio task ready to run */
running_tid = os_task_highest_prio_ready_task(); // 寻找已就绪的最高优先级任务
#endif
if ( running_tid != NO_TID ) { // NO_TID 为 255
os_task_run(); // 运行任务
}
else {
os_cbkSleep();
}
}
void os_task_run( void ) {
os_assert( running_tid < nTasks );
task_list[ running_tid ].taskproc(); // 调用任务函数
}
三、优缺点
缺点:
从 cocoOS 的任务切换实现原理可以确定,该内核并非是抢占式内核,也未曾实现互斥锁机制,并且每个任务函数在被调度时都会被重新调用,因此在应用时,任务内部的变量最好是静态变量。任务切换的效率上自然比不上目前流行的 CPU 寄存器保存和恢复现场以及代码跳转。
优点:
cocoOS 尽管有上述不足,但凭着其巧妙的设计完完全全的避开了不同芯片平台之间的差异,几乎是拿来即可编译使用。cocoOS 也支持信号量、事件、消息队列等基本的任务通信机制,并且可裁剪,使用的是静态数组,对硬件资源占用非常小,内核实现也很简单易懂。在一些硬件资源比较紧张的 MCU 上,需要实现一些较为复杂的业务逻辑,都可以上这套 cocoOS。
四、思考
这 OS 唯一让我觉得比较别扭的是每次任务调度,都会重头开始执行任务函数。我觉得应该可以通过 setjmp() / longjmp() 来实现记录和跳转,这样任务实体就和其它常见的 RTOS 一样,不需要额外的显式增加奇怪的函数,也不会每次任务调度都会重新执行任务函数。
【学习笔记】一种特别有意思的 RTOS 任务切换方法的更多相关文章
- WebGL three.js学习笔记 6种类型的纹理介绍及应用
WebGL three.js学习笔记 6种类型的纹理介绍及应用 本文所使用到的demo演示: 高光贴图Demo演示 反光效果Demo演示(因为是加载的模型,所以速度会慢) (一)普通纹理 计算机图形学 ...
- C#数字图像处理算法学习笔记(一)--C#图像处理的3中方法
C#数字图像处理算法学习笔记(一)--C#图像处理的3中方法 Bitmap类:此类封装了GDI+中的一个位图,次位图有图形图像及其属性的像素数据组成.因此此类是用于处理像素数据定义的图形的对象.该类的 ...
- [ 原创 ]学习笔记-三种向ListView中填充简单文本的方法
Android 中ListView是很重要的一块内容 掌握ListView的基本用法 对学习安卓起着举足轻重的作用 今天就介绍一下三种向ListView 填充简单文本的方法 填充其他数据类型的用法之后 ...
- Dynamic CRM 2013学习笔记(十七)JS读写各种类型字段方法及技巧
我们经常要对表单里各种类型的字段进行读取或赋值,下面列出各种类型的读写方法及注意事项: 1. lookup 类型 清空值 var state = Xrm.Page.getAttribute(" ...
- Dynamic CRM 2013学习笔记(二十七)无代码 复制/克隆方法
前面介绍过二种复制/克隆方法:<Dynamic CRM 2013学习笔记(十四)复制/克隆记录> 和<Dynamic CRM 2013学习笔记(二十五)JS调用web service ...
- JNI学习笔记_Java调用C —— 非Android中使用的方法
一.学习笔记 1.java源码中的JNI函数本机方法声明必须使用native修饰. 2.相对反编译 Java 的 class 字节码文件来说,反汇编.so动态库来分析程序的逻辑要复杂得多,为了应用的安 ...
- 【Unity 3D】学习笔记三十五:游戏实例——摄像机切换镜头
摄像机切换镜头 在游戏中常常会切换摄像机来观察某一个游戏对象,能够说.在3D游戏开发中,摄像头的切换是不可或缺的. 这次我们学习总结下摄像机怎么切换镜头. 代码: private var Camera ...
- .NET 5学习笔记(10)——Entity Framework Core之切换SQLServer和SQLite
上一篇我们梳理了CodeFist的一般流程,本篇我们讨论如何在一套代码中,支持SQL Server和SQLite的切换.同时从本篇开始,我们从.NET Core 3.1 迁移到.NET 5.相信.NE ...
- Redis学习笔记--五种数据类型的使用场景
String 1.String 常用命令: 除了get.set.incr.decr mget等操作外,Redis还提供了下面一些操作: 获取字符串长度 往字符串append内容 设置和获取字符串的某一 ...
- Python学习笔记——几种数据类型
1. 列表list: Python内置的一种数据类型是列表:list,用中括号[]表示.list是一种有序的集合,可以随时添加和删除其中的元素,而且元素的类型不必相同.list可以通过下标来访问,范围 ...
随机推荐
- SIP会话发起协议 - 先知道是什么(一)
少年,思无邪,最最动人. 协议概述 SIP会话发起协议是VoIP技术中最常用的协议之一.它是一种应用层协议,与其它应用层协议协同工作,通过Internet控制多媒体通信会话. SIP采用SDP(会话描 ...
- springMVC实现文件的上传和下载
文件的下载功能 @RequestMapping("/testDown")public ResponseEntity<byte[]> testResponseEntity ...
- 这篇关于Oracle内存管理方式的介绍太棒了!我必须要转发,很全面。哈哈~
"Oracle内存管理可分为两大类,自动内存管理和手动内存管理.其中手动内存管理又可分为自动共享内存管理,手动共享内存管理,自动PGA内存管理以及手动PGA内存管理.本文会简单的介绍不同的内 ...
- Docker | 专栏文章整理🎉🎉
Docker Docker系列文章基本已经更新完毕,这是我从去年的学习笔记中整理出来的. 笔记稍微有点杂乱.随意,把它们整理成文章花费了不少力气.整理的过程也是我的一个再次学习的过程,同时也是为了方便 ...
- Microsoft Office MSDT代码执行漏洞(CVE-2022-30190)漏洞复现
目录 免责声明: CVE-2022-30190漏洞复现 漏洞概述: 影响版本: 漏洞复现: 使用方法: 利用: 修复建议: 参考: 免责声明: 本文章仅供学习和研究使用,严禁使用该文章内容对互联网其他 ...
- HDLBits答案——Verilog Language
Verilog Language 1 Basics 1.1 Wire module top_module( input in, output out ); assign out = in; endmo ...
- 关于 Windows6.1-KB2999226-x64.msu 此更新不适用你的计算机解决办法
前言 今天被这个破问题坑了很长时间,网上一大堆扯跳过那个检查,通过提取 cab 文件然后直接用命令安装,我可以明确的告诉你不是那样的解决的,因为我实际用命令装过也装不上(这里我吐槽一下,我猜你最初的问 ...
- go slice不同初始化方式性能&数组比较
go语言开发中,slice是我们常用的数据类型之一,也是因为它的灵活性,自己也很少使用数组,当然我也知道它的一些特性,不过没有真实的去验证它,因为大多数使用场景没必要对code太过苛刻,但是如果封装作 ...
- Function源码解析与实践
作者:陈昌浩 1 导读 if-else-在代码中经常使用,听说可以通过Java 8的Function接口来消灭if-else-!Function接口是什么?如果通过Function接口接口消灭if-e ...
- Jmeter——结合Allure展示测试报告
在平时用jmeter做测试时,生成报告的模板,不是特别好.大家应该也知道allure报告,页面美观. 先来看效果图,报告首页,如下所示: 报告详情信息,如下所示: 运行run.py文件,运行成功,如下 ...