zoukankan      html  css  js  c++  java
  • 【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[3];// 保留,避免对齐问题
    
            //~Task();
    
            void ShowStatus();        // 显示状态
    };
    
    // 任务调度器
    class TaskScheduler
    {
    private:
            FixedArray<Task, 32> _Tasks;
            uint _gid;        // 总编号
    
            friend class Task;
    
    public:
            string        Name;                // 系统名称
            int                Count;                // 任务个数
            Task*        Current;        // 正在执行的任务
            bool        Running;        // 是否正在运行
            byte        Reversed[3];// 保留,避免对齐问题
    
            TaskScheduler(string name = NULL);
            ~TaskScheduler();
    
            // 创建任务,返回任务编号。dueTime首次调度时间us,period调度间隔us,-1表示仅处理一次
            uint Add(Action func, void* param, ulong dueTime = 0, long period = 0);
            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                = 0;
            CpuTime                = 0;
            SleepTime        = 0;
            Cost                = 0;
            Enable                = true;
    }
    
    /*Task::~Task()
    {
            if(ID) _Scheduler->Remove(ID);
    }*/
    
    // 显示状态
    void Task::ShowStatus()
    {
            debug_printf("Task::Status 任务 %d [%d] 执行 %dus 平均 %dus
    ", ID, Times, CpuTime, Cost);
    }
    
    TaskScheduler::TaskScheduler(string name)
    {
            Name = name;
    
            _gid = 1;
    
            Running = false;
            Current        = NULL;
            Count = 0;
    }
    
    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
    ", Name, task->ID, func, dueTime, period);
            if(period >= 1000)
            {
                    uint dt = dueTime / 1000;
                    int  pd = period > 0 ? period / 1000 : period;
                    debug_printf("%s::添加任务%d 0x%08x FirstTime=%ums Period=%dms
    ", Name, task->ID, func, dt, pd);
            }
            else
                    debug_printf("%s::添加任务%d 0x%08x FirstTime=%uus Period=%dus
    ", Name, task->ID, func, (uint)dueTime, (int)period);
    #endif
    
            return task->ID;
    }
    
    void TaskScheduler::Remove(uint taskid)
    {
            int i = -1;
            while(_Tasks.MoveNext(i))
            {
                    Task* task = _Tasks[i];
                    if(task->ID == taskid)
                    {
                            _Tasks.RemoveAt(i);
                            debug_printf("%s::删除任务%d 0x%08x
    ", Name, task->ID, task->Callback);
                            // 首先清零ID,避免delete的时候再次删除
                            task->ID = 0;
                            delete task;
                            break;
                    }
            }
    }
    
    void TaskScheduler::Start()
    {
            if(Running) return;
    
    #if DEBUG
            //Add(ShowTime, NULL, 2000000, 2000000);
            Add(ShowStatus, this, 10000000, 30000000);
    #endif
            debug_printf("%s::准备就绪 开始循环处理%d个任务!
    
    ", Name, Count);
    
            Running = true;
            while(Running)
            {
                    Execute(0xFFFFFFFF);
            }
            debug_printf("%s停止调度,共有%d个任务!
    ", Name, Count);
    }
    
    void TaskScheduler::Stop()
    {
            debug_printf("%s停止!
    ", 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 = -1;
            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 = 0;
    
                            Current = task;
                            task->Callback(task->Param);
                            Current = NULL;
    
                            // 累加任务执行次数和时间
                            task->Times++;
                            int cost = (int)(Time.Current() - now2);
                            if(cost < 0) cost = -cost;
                            //if(cost > 0)
                            {
                                    task->CpuTime += cost - task->SleepTime;
                                    task->Cost = task->CpuTime / task->Times;
                            }
    
    #if DEBUG
                            if(cost > 500000) debug_printf("Task::Execute 任务 %d [%d] 执行时间过长 %dus 睡眠 %dus
    ", task->ID, task->Times, cost, task->SleepTime);
    #endif
    
                            // 如果只是一次性任务,在这里清理
                            if(task->Period < 0) 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
    ", (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 = -1;
            while(ts->_Tasks.MoveNext(i))
            {
                    Task* task = ts->_Tasks[i];
                    if(task) task->ShowStatus();
            }
    }
    
    Task* TaskScheduler::operator[](int taskid)
    {
            int i = -1;
            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 >= 1000))
            {
                    // 记录当前正在执行任务
                    Task* task = _Scheduler->Current;
    
                    ulong start = Time.Current();
                    // 1ms一般不够调度新任务,留给硬件等待
                    ulong end = start + us - 1000;
                    // 如果休眠时间足够长,允许多次调度其它任务
                    int cost = 0;
                    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 > 0) return;
    
                    us -= cost;
            }
            if(us) Time.Sleep(us);
    }
    
    void TSys::Sleep(uint ms)
    {
            // 优先使用线程级睡眠
            if(OnSleep)
                    OnSleep(ms);
            else
            {
    #if DEBUG
                    if(ms > 1000) debug_printf("Sys::Sleep 设计错误,睡眠%dms太长,超过1000ms建议使用多线程Thread!", ms);
    #endif
    
                    TimeSleep(ms * 1000);
            }
    }
    
    void TSys::Delay(uint us)
    {
            // 如果延迟微秒数太大,则使用线程级睡眠
            if(OnSleep && us >= 2000)
                    OnSleep((us + 500) / 1000);
            else
            {
    #if DEBUG
                    if(us > 1000000) debug_printf("Sys::Sleep 设计错误,睡眠%dus太长,超过1000ms建议使用多线程Thread!", us);
    #endif
    
                    TimeSleep(us);
            }
    }
  • 相关阅读:
    在数据库中加字段方法
    【原创】AE套用模板教程
    mysql 在windows server下发生系统错误 1067, 进程意外终止的解决方法
    对unidbgrid的单元格操作
    unigui与uniurlframe的互动
    推荐ajaxfilemanager for tiny_mce 比较完善的tiny_mce编辑器的图片上传及图片管理插件PHP版 支持中文
    html编辑器的调用
    mysqldump导出格式
    Gmail,QMail,163邮箱的 IMAP/SMTP/POP3 地址
    Delphi程序的自动升级功能的实现(AutoUpdate使用指南)
  • 原文地址:https://www.cnblogs.com/nnhy/p/smartos_task.html
Copyright © 2011-2022 走看看