【SmartOS】輕量級多任務調度系統
SmartOS是一個完全由新生命團隊設計的嵌入式操作系統,主要應用於智能家居、物聯網、工業自動化控制等領域。
ARM Cortex-M系列微處理器幾乎全都做成單核心,對於業務邏輯較復雜的物聯網就顯得難以使用,因此SmartOS設計了兩個多任務調度系統:
1,多線程調度,重量級,逼近PC操作系統多線程用法。使用上需要特別小心,要合理分配每一個線程的棧空間大小,任務越多越容易出問題
2,大循環,輕量級。每個任務註冊一個函數指針,然後由主線程輪詢各個任務函數,輪流執行
本文主要講解第二種,輕量級多任務調度系統。
TaskScheduler是任務調度中心,Task表示單個任務。
SmartOS啟動後會進入C/C++標準的main函數,在這裏需要初始化各個模塊,各個模塊在初始化的時候,通過Sys.AddTask向系統註冊任務函數。
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】輕量級多任務調度系統