一、介绍说明

目前常见流行的 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 一样,不需要额外的显式增加奇怪的函数,也不会每次任务调度都会重新执行任务函数。

最新文章

  1. SqlSugar ORM已经支持读写分离
  2. ZERO 笔试
  3. vs安装mvc
  4. 数组对象Vector用法
  5. HDU4647+贪心
  6. sharepoint online
  7. kafka环境搭建2-broker集群+zookeeper集群(转)
  8. 系统报错 hppatusg01
  9. WordPress 4.3 Beta 1 全新发布,改进了后台功能和用户体验
  10. 商誉专题RN及H5项目总结
  11. 小程序 公众号/h5相互跳转-webview
  12. spring事务源码分析结合mybatis源码(一)
  13. 5、faker.js数据模拟
  14. 在树莓派3B、Ubuntu 18.04关闭板载Wifi、蓝牙
  15. [MySQL Code]Innodb 锁分配和锁冲突判断
  16. 浅谈python函数签名
  17. MySQL 之迁移用户及权限
  18. Python使用MySQLConnector/Python操作MySQL、MariaDB数据库
  19. IIS开多个HTTPS站点
  20. Spring_错误 java.sql.SQLException: Lock wait timeout exceeded | CannotAcquireLockException 的解决

热门文章

  1. python 爬虫 TCL SSL 安全证书问题
  2. 出现The server time zone value ‘�й���׼ʱ��‘ is unrecognized的解决方法
  3. python-代码编程规范
  4. [CS61A] Lecture 5&amp;6&amp;7. Environments &amp; Design &amp; Functions Examples &amp; Homework 2: Higher Order Functions
  5. Linux下用rm误删除文件的三种恢复方法
  6. 【Java SE进阶】Day10 缓冲流、转换流、序列化流 、打印流
  7. 为什么Git远程仓库中要配置公钥?
  8. linux系统部署微服务项目
  9. 万字长文详解 YOLOv1-v5 系列模型
  10. SQLSERVER 居然也能调 C# 代码 ?