1. 程式人生 > >【SmartOS】輕量級多任務調度系統

【SmartOS】輕量級多任務調度系統

tint sys 多線程 remove 希望 opera div 開始時間 調度

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\r\n", 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\r\n", 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\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 = -1;
        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 = 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個任務!\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 = -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\r\n", 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\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 = -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);
        }
}

【SmartOS】輕量級多任務調度系統