一、介绍说明

目前常见流行的 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 任务切换方法的更多相关文章

  1. WebGL three.js学习笔记 6种类型的纹理介绍及应用

    WebGL three.js学习笔记 6种类型的纹理介绍及应用 本文所使用到的demo演示: 高光贴图Demo演示 反光效果Demo演示(因为是加载的模型,所以速度会慢) (一)普通纹理 计算机图形学 ...

  2. C#数字图像处理算法学习笔记(一)--C#图像处理的3中方法

    C#数字图像处理算法学习笔记(一)--C#图像处理的3中方法 Bitmap类:此类封装了GDI+中的一个位图,次位图有图形图像及其属性的像素数据组成.因此此类是用于处理像素数据定义的图形的对象.该类的 ...

  3. [ 原创 ]学习笔记-三种向ListView中填充简单文本的方法

    Android 中ListView是很重要的一块内容 掌握ListView的基本用法 对学习安卓起着举足轻重的作用 今天就介绍一下三种向ListView 填充简单文本的方法 填充其他数据类型的用法之后 ...

  4. Dynamic CRM 2013学习笔记(十七)JS读写各种类型字段方法及技巧

    我们经常要对表单里各种类型的字段进行读取或赋值,下面列出各种类型的读写方法及注意事项: 1. lookup 类型 清空值 var state = Xrm.Page.getAttribute(" ...

  5. Dynamic CRM 2013学习笔记(二十七)无代码 复制/克隆方法

    前面介绍过二种复制/克隆方法:<Dynamic CRM 2013学习笔记(十四)复制/克隆记录> 和<Dynamic CRM 2013学习笔记(二十五)JS调用web service ...

  6. JNI学习笔记_Java调用C —— 非Android中使用的方法

    一.学习笔记 1.java源码中的JNI函数本机方法声明必须使用native修饰. 2.相对反编译 Java 的 class 字节码文件来说,反汇编.so动态库来分析程序的逻辑要复杂得多,为了应用的安 ...

  7. 【Unity 3D】学习笔记三十五:游戏实例——摄像机切换镜头

    摄像机切换镜头 在游戏中常常会切换摄像机来观察某一个游戏对象,能够说.在3D游戏开发中,摄像头的切换是不可或缺的. 这次我们学习总结下摄像机怎么切换镜头. 代码: private var Camera ...

  8. .NET 5学习笔记(10)——Entity Framework Core之切换SQLServer和SQLite

    上一篇我们梳理了CodeFist的一般流程,本篇我们讨论如何在一套代码中,支持SQL Server和SQLite的切换.同时从本篇开始,我们从.NET Core 3.1 迁移到.NET 5.相信.NE ...

  9. Redis学习笔记--五种数据类型的使用场景

    String 1.String 常用命令: 除了get.set.incr.decr mget等操作外,Redis还提供了下面一些操作: 获取字符串长度 往字符串append内容 设置和获取字符串的某一 ...

  10. Python学习笔记——几种数据类型

    1. 列表list: Python内置的一种数据类型是列表:list,用中括号[]表示.list是一种有序的集合,可以随时添加和删除其中的元素,而且元素的类型不必相同.list可以通过下标来访问,范围 ...

随机推荐

  1. 如何在.NET程序崩溃时自动创建Dump?

    今天在浏览张队转载文章的留言时,遇到一个读者问了这样的问题,如下图所示: 首先能明确的一点是"程序崩溃退出了是不能用常规的方式dump的",因为整个进程树都已经退出.现场已经无法使 ...

  2. 计算机系统大作业:Hello的一生

    计算机系统大作业 题 目 程序人生-Hello's P2P 专 业 计算机科学与技术 学 号 班 级 学 生 江水为竭 指导教师 刘宏伟 计算机科学与技术学院 2022年5月 摘 要 HelloWor ...

  3. 2022-11-14 Acwing每日一题

    本系列所有题目均为Acwing课的内容,发表博客既是为了学习总结,加深自己的印象,同时也是为了以后回过头来看时,不会感叹虚度光阴罢了,因此如果出现错误,欢迎大家能够指出错误,我会认真改正的.同时也希望 ...

  4. 安卓APP和小程序渗透测试技巧总结

    安卓APP和小程序渗透测试技巧总结 免责声明: 安卓7以上抓取https流量包 证书信任 首先安装OpenSSL,此步骤不再赘述,可以参考百度. 然后安装模拟器(我使用的是夜神模拟器). 导出需要的证 ...

  5. Ueditor、FCKeditor、Kindeditor编辑器漏洞

    Ueditor.FCKeditor.Kindeditor编辑器漏洞 免责声明: Ueditor编辑器漏洞 文件上传漏洞 XSS漏洞 SSRF漏洞 FCKeditor编辑器漏洞 查看FCKeditor版 ...

  6. XTDrone和PX4学习期间问题记录(一)

    XTDrone和PX4学习期间问题记录(一) Written By PiscesAlpaca 前言: 出现问题可以去官方网站http://ceres-solver.org/index.html查看文档 ...

  7. Class文件解析

    1 准备工作 获取class文件byte[] public static byte[] getFileBytes(File file) { try (FileInputStream fileInput ...

  8. Day30.1:Math的常用方法

    Math 1.1 Math概述 Math类在Java.lang包下,不需要导包 public final class Math extends Object Math含有基本的数字运算方法,没有构造器 ...

  9. ATM项目

    ATM项目实战 项目需求分析: 1.注册(密码要加密) 2.登陆 3.查看余额 4.提现(可自定手续费) 5.还款 6.转账 7.查看流水 8.添加购物车功能 (商品可配置) 9.查看购物车功能 10 ...

  10. js 获取当前时间转换时间戳 (毫秒)

    js 当前时间转换毫秒数 五种方式   var date = new Date().getTime(); var date = new Date().valueOf(); var date = +ne ...