【SmartOS】轻量级多任务调度系统
SmartOS是一个完全由新生命团队设计的嵌入式操作系统,主要应用于智能家居、物联网、工业自动化控制等领域。
ARM Cortex-M系列微处理器几乎全都做成单核心,对于业务逻辑较复杂的物联网就显得难以使用,因此SmartOS设计了两个多任务调度系统:
1,多线程调度,重量级,逼近PC操作系统多线程用法。使用上需要特别小心,要合理分配每一个线程的栈空间大小,任务越多越容易出问题
2,大循环,轻量级。每个任务注册一个函数指针,然后由主线程轮询各个任务函数,轮流执行
本文主要讲解第二种,轻量级多任务调度系统。
TaskScheduler是任务调度中心,Task表示单个任务。
SmartOS启动后会进入C/C++标准的main函数,在这里需要初始化各个模块,各个模块在初始化的时候,通过Sys.AddTask向系统注册任务函数。
一切就绪以后,在main最后一行,使用Sys.Start()进入大循环,开始调度。
Sys.Start实际上调用TaskScheduler.Start,从后面代码可以看出,这个Start内部有一个死循环。
每一个任务都需要指定4大参数:函数指针、回调参数、开始时间、调度周期。
调度中心将会维护并计算每一个任务的“下一次调度”时间。
显然,每一个任务函数获得CPU时间开始执行的时候,其它所有任务都没有机会执行。
原则上,当然是每个任务都尽量不要占用太长的时间。但是随着智能设备越来越复杂,应用系统也日渐复杂,为了满足需求,开发人员很希望在一个任务里面完成一系列连贯动作,获得跟PC上一样的体验,让任务假设自己独占CPU。
常规的大循环调度根本无法满足以上要求。
我们在这个基础上做了一点点改进,允许某个任务在休眠等待的时候,分出时间去调度其它函数。
例如,A、B、C多个任务正在工作。
其中A是主要业务逻辑,B是以太网驱动,定时询问网卡要数据。
A里面有一个功能,需要向服务器发送一个指令,然后等待响应。
如果这个时候A阻塞CPU,它永远也拿不到响应数据,即使响应数据已经到来!
因为CPU被A独占了,B没有机会去问网卡要数据,也就不能把数据交给A。
我们把A的等待做一点点调整,A在调用Sys.Sleep等待一定时间的时候,调度中心不要浪费了这点时间,安排去调度其它任务,那么B就有机会执行,网络响应数据上冒到A业务附近的函数,最终被A获取,达到业务需求。
头文件
#ifndef __Task_H__
#define __Task_H__ #include "Sys.h"
#include "List.h" class TaskScheduler; // 任务
class Task
{
private:
TaskScheduler* _Scheduler; friend class TaskScheduler; Task(TaskScheduler* scheduler); public:
uint ID; // 编号
Action Callback; // 回调
void* Param; // 参数
long Period; // 周期us
ulong NextTime; // 下一次执行时间
uint Times; // 执行次数
uint CpuTime; // 总耗费时间
uint SleepTime; // 当前睡眠时间
uint Cost; // 平均执行时间
bool Enable; // 是否启用
byte Reversed[];// 保留,避免对齐问题 //~Task(); void ShowStatus(); // 显示状态
}; // 任务调度器
class TaskScheduler
{
private:
FixedArray<Task, > _Tasks;
uint _gid; // 总编号 friend class Task; public:
string Name; // 系统名称
int Count; // 任务个数
Task* Current; // 正在执行的任务
bool Running; // 是否正在运行
byte Reversed[];// 保留,避免对齐问题 TaskScheduler(string name = NULL);
~TaskScheduler(); // 创建任务,返回任务编号。dueTime首次调度时间us,period调度间隔us,-1表示仅处理一次
uint Add(Action func, void* param, ulong dueTime = , long period = );
void Remove(uint taskid); void Start();
void Stop();
// 执行一次循环。指定最大可用时间
void Execute(uint usMax); static void ShowStatus(void* param); // 显示状态 Task* operator[](int taskid);
}; #endif
源代码
#include "Task.h" /* */ Task::Task(TaskScheduler* scheduler)
{
_Scheduler = scheduler; Times = ;
CpuTime = ;
SleepTime = ;
Cost = ;
Enable = true;
} /*Task::~Task()
{
if(ID) _Scheduler->Remove(ID);
}*/ // 显示状态
void Task::ShowStatus()
{
debug_printf("Task::Status 任务 %d [%d] 执行 %dus 平均 %dus\r\n", ID, Times, CpuTime, Cost);
} TaskScheduler::TaskScheduler(string name)
{
Name = name; _gid = ; Running = false;
Current = NULL;
Count = ;
} TaskScheduler::~TaskScheduler()
{
Current = NULL;
_Tasks.DeleteAll().Clear();
} // 创建任务,返回任务编号。dueTime首次调度时间us,period调度间隔us,-1表示仅处理一次
uint TaskScheduler::Add(Action func, void* param, ulong dueTime, long period)
{
Task* task = new Task(this);
task->ID = _gid++;
task->Callback = func;
task->Param = param;
task->Period = period;
task->NextTime = Time.Current() + dueTime; Count++;
_Tasks.Add(task); #if DEBUG
// 输出长整型%ld,无符号长整型%llu
//debug_printf("%s添加任务%d 0x%08x FirstTime=%lluus Period=%ldus\r\n", Name, task->ID, func, dueTime, period);
if(period >= )
{
uint dt = dueTime / ;
int pd = period > ? period / : period;
debug_printf("%s::添加任务%d 0x%08x FirstTime=%ums Period=%dms\r\n", Name, task->ID, func, dt, pd);
}
else
debug_printf("%s::添加任务%d 0x%08x FirstTime=%uus Period=%dus\r\n", Name, task->ID, func, (uint)dueTime, (int)period);
#endif return task->ID;
} void TaskScheduler::Remove(uint taskid)
{
int i = -;
while(_Tasks.MoveNext(i))
{
Task* task = _Tasks[i];
if(task->ID == taskid)
{
_Tasks.RemoveAt(i);
debug_printf("%s::删除任务%d 0x%08x\r\n", Name, task->ID, task->Callback);
// 首先清零ID,避免delete的时候再次删除
task->ID = ;
delete task;
break;
}
}
} void TaskScheduler::Start()
{
if(Running) return; #if DEBUG
//Add(ShowTime, NULL, 2000000, 2000000);
Add(ShowStatus, this, , );
#endif
debug_printf("%s::准备就绪 开始循环处理%d个任务!\r\n\r\n", Name, Count); Running = true;
while(Running)
{
Execute(0xFFFFFFFF);
}
debug_printf("%s停止调度,共有%d个任务!\r\n", Name, Count);
} void TaskScheduler::Stop()
{
debug_printf("%s停止!\r\n", Name);
Running = false;
} // 执行一次循环。指定最大可用时间
void TaskScheduler::Execute(uint usMax)
{
ulong now = Time.Current() - Sys.StartTime; // 当前时间。减去系统启动时间,避免修改系统时间后导致调度停摆
ulong min = UInt64_Max; // 最小时间,这个时间就会有任务到来
ulong end = Time.Current() + usMax; // 需要跳过当前正在执行任务的调度
//Task* _cur = Current; int i = -;
while(_Tasks.MoveNext(i))
{
Task* task = _Tasks[i];
//if(task && task != _cur && task->Enable && task->NextTime <= now)
if(task && task->Enable && task->NextTime <= now)
{
// 不能通过累加的方式计算下一次时间,因为可能系统时间被调整
task->NextTime = now + task->Period;
if(task->NextTime < min) min = task->NextTime; ulong now2 = Time.Current();
task->SleepTime = ; Current = task;
task->Callback(task->Param);
Current = NULL; // 累加任务执行次数和时间
task->Times++;
int cost = (int)(Time.Current() - now2);
if(cost < ) cost = -cost;
//if(cost > 0)
{
task->CpuTime += cost - task->SleepTime;
task->Cost = task->CpuTime / task->Times;
} #if DEBUG
if(cost > ) debug_printf("Task::Execute 任务 %d [%d] 执行时间过长 %dus 睡眠 %dus\r\n", task->ID, task->Times, cost, task->SleepTime);
#endif // 如果只是一次性任务,在这里清理
if(task->Period < ) Remove(task->ID);
} // 如果已经超出最大可用时间,则退出
if(!usMax || Time.Current() > end) return;
} // 如果有最小时间,睡一会吧
now = Time.Current(); // 当前时间
if(min != UInt64_Max && min > now)
{
min -= now;
#if DEBUG
//debug_printf("TaskScheduler::Execute 等待下一次任务调度 %uus\r\n", (uint)min);
#endif
//// 最大只允许睡眠1秒,避免Sys.Delay出现设计错误,同时也更人性化
//if(min > 1000000) min = 1000000;
//Sys.Delay(min);
Time.Sleep(min);
}
} // 显示状态
void TaskScheduler::ShowStatus(void* param)
{
TaskScheduler* ts = (TaskScheduler*)param; int i = -;
while(ts->_Tasks.MoveNext(i))
{
Task* task = ts->_Tasks[i];
if(task) task->ShowStatus();
}
} Task* TaskScheduler::operator[](int taskid)
{
int i = -;
while(_Tasks.MoveNext(i))
{
Task* task = _Tasks[i];
if(task && task->ID == taskid) return task;
} return NULL;
}
外部注册函数
// 任务
#include "Task.h"
// 任务类
TaskScheduler* _Scheduler; // 创建任务,返回任务编号。priority优先级,dueTime首次调度时间us,period调度间隔us,-1表示仅处理一次
uint TSys::AddTask(Action func, void* param, ulong dueTime, long period)
{
// 屏蔽中断,否则可能有线程冲突
SmartIRQ irq; if(!_Scheduler) _Scheduler = new TaskScheduler("系统"); return _Scheduler->Add(func, param, dueTime, period);
} void TSys::RemoveTask(uint taskid)
{
assert_ptr(_Scheduler); _Scheduler->Remove(taskid);
} void TSys::SetTask(uint taskid, bool enable)
{
Task* task = (*_Scheduler)[taskid];
if(task) task->Enable = enable;
} void TSys::Start()
{
if(!_Scheduler) _Scheduler = new TaskScheduler("系统"); #if DEBUG
//AddTask(ShowTime, NULL, 2000000, 2000000);
#endif
if(OnStart)
OnStart();
else
_Scheduler->Start();
} void TSys::StartInternal()
{
_Scheduler->Start();
} void TSys::Stop()
{
_Scheduler->Stop();
} void TimeSleep(uint us)
{
// 在这段时间里面,去处理一下别的任务
if(_Scheduler && (!us || us >= ))
{
// 记录当前正在执行任务
Task* task = _Scheduler->Current; ulong start = Time.Current();
// 1ms一般不够调度新任务,留给硬件等待
ulong end = start + us - ;
// 如果休眠时间足够长,允许多次调度其它任务
int cost = ;
while(true)
{
ulong start2 = Time.Current(); _Scheduler->Execute(us); ulong now = Time.Current();
cost += (int)(now - start2); // us=0 表示释放一下CPU
if(!us) return; if(now >= end) break;
} if(task)
{
_Scheduler->Current = task;
task->SleepTime += cost;
} cost = (int)(Time.Current() - start);
if(cost > ) return; us -= cost;
}
if(us) Time.Sleep(us);
} void TSys::Sleep(uint ms)
{
// 优先使用线程级睡眠
if(OnSleep)
OnSleep(ms);
else
{
#if DEBUG
if(ms > ) debug_printf("Sys::Sleep 设计错误,睡眠%dms太长,超过1000ms建议使用多线程Thread!", ms);
#endif TimeSleep(ms * );
}
} void TSys::Delay(uint us)
{
// 如果延迟微秒数太大,则使用线程级睡眠
if(OnSleep && us >= )
OnSleep((us + ) / );
else
{
#if DEBUG
if(us > ) debug_printf("Sys::Sleep 设计错误,睡眠%dus太长,超过1000ms建议使用多线程Thread!", us);
#endif TimeSleep(us);
}
}
【SmartOS】轻量级多任务调度系统的更多相关文章
- 在PHP中使用协程实现多任务调度
PHP5.5一个比较好的新功能是加入了对迭代生成器和协程的支持.对于生成器,PHP的文档和各种其他的博客文章已经有了非常详细的讲解.协程相对受到的关注就少了,因为协程虽然有很强大的功能但相对比较复杂, ...
- 资源管理与调度系统-YARN的基本架构与原理
资源管理与调度系统-YARN的基本架构与原理 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 为了能够对集群中的资源进行统一管理和调度,Hadoop2.0引入了数据操作系统YARN. ...
- 资源管理与调度系统-资源管理系统Mesos
资源管理与调度系统-资源管理系统Mesos 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Mesos是诞生于UC Berkeley的一个研究项目,它的设计动机是解决编程模型和计算框 ...
- 资源管理与调度系统-YARN资源隔离及以YARN为核心的生态系统
资源管理与调度系统-YARN资源隔离及以YARN为核心的生态系统 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是资源隔离 资源隔离是指为不同任务提供可独立使用的计算资源以 ...
- 工作流调度系统Azkaban的简介和使用
1 概述 1.1 为什么需要工作流调度系统 l 一个完整的数据分析系统通常都是由大量任务单元组成: shell脚本程序,java程序,mapreduce程序.hive脚本等 l 各任务单元之间存在时间 ...
- hadoop工作流调度系统
常见工作流调度系统 Oozie, Azkaban, Cascading, Hamake 各种调度工具特性对比 特性 Hamake Oozie Azkaban Cascading 工作流描述语言 XML ...
- 黑马程序员:Java编程_7K面试题之银行业务调度系统
=========== ASP.Net+Android+IOS开发..Net培训.期待与您交流!=========== 模拟实现银行业务调度系统逻辑,具体需求如下: 银行内有6个业务窗口,1 - 4号 ...
- 黑马程序员_JAVA之银行业务调度系统
------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 1.模拟实现银行业务调度系统逻辑,具体需求如下: 银行内有6个业务窗口,1 - 4号窗口为普通窗 ...
- Java——银行业务调度系统
需求: 模拟实现银行业务调度系统逻辑,具体需求如下: Ø 银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口. Ø 有三种对应类型的客户:VIP客户,普通 ...
随机推荐
- 关于MySql中使用IFNULL()函数失效的问题。
今天在学习时,碰到一个问题:在联表查询取得结果后,如果取得的结果是空值,则给一个默认值,如果不是空值,则返回这个值. 下面我们来看看业务场景: 在menu表中: 存储的是前端页面的菜单配置,注意成员权 ...
- freemarker常见语法大全
推荐freemarker系列教程:http://swiftlet.net/archives/category/freemarker FreeMarker的插值有如下两种类型:1,通用插值${expr} ...
- 大数据学习(5)MapReduce切片(Split)和分区(Partitioner)
MapReduce中,分片.分区.排序和分组(Group)的关系图: 分片大小 对于HDFS中存储的一个文件,要进行Map处理前,需要将它切分成多个块,才能分配给不同的MapTask去执行. 分片的数 ...
- charles支持https抓包配置
自从公司站点全部启用https后,使用charles就不能像以前那样愉快的抓包啦!不过没关系,这里教你怎么配置charles,使其支持https抓包.之前有一篇介绍charles的使用,参考这篇:ht ...
- java 拦截器
一.前言 这是一篇关于 java 拦截器的文章,是我的写 java web 所遇见的问题.当我们写好一个网站,必须要通过登陆页面才可以进入这个系统.那么我们就得写个 java 拦截器,如果是通过登录 ...
- lua中怎么替换掉字符串中的$^特殊字符?
Lua 常规替换字符串如何替换 s = string.gsub("Lua is good", "good", "bad") print(s) ...
- 如何录屏制作gif图片
LICEcap简介: LICEcap是一款屏幕录制制作gif图片的工具,支持导出 GIF 动画图片格式,轻量级.使用简单,录制过程中可以随意改变录屏范围. LICEcap 直观易用,功能灵活,支持 W ...
- js文件加载优化
在js引擎部分,我们可以了解到,当渲染引擎解析到script标签时,会将控制权给JS引擎,如果script加载的是外部资源,则需要等待下载完后才能执行. 所以,在这里,我们可以对其进行很多优化工作. ...
- MyBatis中批量插入数据对插入记录数的限制
<基于Mybatis框架的批量数据插入的性能问题的探讨>(作者:魏静敏 刘欢杰 来源:<计算机光盘软件与应用> 2013 年第 19 期)中提到批量插入的记录数不能超过1000 ...
- Golang 中的坑 一
Golang 中的坑 短变量声明 Short variable declarations 考虑如下代码: package main import ( "errors" " ...