RTOS 任務(task / thread)
若即時作業系統是指揮數名表演者的舞台監督,那每位表演者就是一個任務:一項有自己工作的獨立活動,例如「讀感測器」或「驅動顯示器」。你把任務寫成一個看起來平凡、帶有自己迴圈的函式,而 RTOS 讓你的任務看起來彷彿獨佔整個處理器,即使其實是好幾個任務輪流共用一個核心。
在底層,每個任務都有「自己的」堆疊(一塊 RAM,放它的區域變數與呼叫框)以及一組保存的暫存器值(它的上下文)。任一瞬間只有一個任務真正在核心上執行;其餘的都被暫停、上下文被凍結。當排程器決定切換時,它執行一次上下文切換:把正在跑的任務的暫存器存進它的任務控制區塊、載入另一個任務保存的暫存器,並讓那個任務從它停下處恰好接續。任務總是處於幾種狀態之一:執行中(現在在核心上)、就緒(可執行、等輪到它)或阻塞(在等某件事——延遲到期、訊息到達、互斥鎖釋放)。當任務阻塞(例如呼叫延遲或在佇列上等待)時,它自願放棄核心,好讓其他任務執行;這正是合作共存得以可能的原因。每個任務帶有一個優先順序,供排程器決定誰執行。
它之所以重要,是因為任務讓你把一個糾結的「全塞進一個迴圈」程式拆解成清晰、獨立的單元——每個任務只就自己的工作推理,並透過佇列與號誌和別人溝通。誠實的提醒:RTOS 裡的任務通常「共享」同一個定址空間(在小 MCU 上彼此之間通常沒有記憶體保護),所以一個任務的臭蟲可能破壞另一個任務的記憶體——這和彼此隔離的完整作業系統行程不同。每個任務的堆疊是你事先選定的「固定」大小,把它溢出(深層遞迴、大型區域陣列)會默默破壞相鄰記憶體,是惡名昭彰的嵌入式當機。而因為任務並行執行、可在任意點被搶占,任務間共享的任何資料都需要正確的同步(互斥鎖或佇列),就和執行緒一樣。
// 任務是一個帶自己迴圈的函式;它以阻塞來讓出核心。 void uartTask(void *params) { char msg[32]; for (;;) { // 在此阻塞直到有訊息到達——這期間其他任務執行 xQueueReceive(rxQueue, msg, portMAX_DELAY); process(msg); } } // 堆疊大小(例如 256 個字組)在你建立任務時就固定。
任務在佇列上阻塞以讓出核心;它自己的堆疊與保存的暫存器讓排程器能從它暫停處恰好恢復它。
每個任務的堆疊是你事先選定的固定大小;把它溢出會默默破壞鄰近記憶體,是典型又難找的當機。而小 MCU 上的任務共享同一個定址空間,所以它們「不」像作業系統行程那樣彼此隔離。