核調(diào)度機(jī)制解析:從裸機(jī)到多任務(wù)的思維躍遷)
1. 從裸機(jī)到RTOS一個(gè)嵌入式開(kāi)發(fā)者的思維躍遷最近在整理韋東山老師的RTOS訓(xùn)練營(yíng)第七講的筆記感觸頗深。這一講的內(nèi)容與其說(shuō)是講某個(gè)具體的API或功能不如說(shuō)是一次思維模式的“破壁”。很多從單片機(jī)裸機(jī)開(kāi)發(fā)轉(zhuǎn)向RTOS的工程師包括我自己在內(nèi)初期最大的障礙往往不是代碼怎么寫(xiě)而是“為什么需要這么寫(xiě)”。第七講恰好就戳中了這個(gè)痛點(diǎn)它沒(méi)有停留在“如何用”的層面而是深入探討了“為何用”以及RTOS內(nèi)核中最核心的調(diào)度機(jī)制是如何運(yùn)作的。如果你正在學(xué)習(xí)RTOS感覺(jué)概念都懂但寫(xiě)起代碼來(lái)總感覺(jué)別扭或者對(duì)任務(wù)切換、優(yōu)先級(jí)這些底層邏輯心存疑惑那么這一講的內(nèi)容可能就是解開(kāi)你心結(jié)的關(guān)鍵。接下來(lái)我會(huì)結(jié)合自己的理解把這一講的核心脈絡(luò)和那些“恍然大悟”的瞬間掰開(kāi)揉碎了分享給你。2. 核心矛盾裸機(jī)“超級(jí)循環(huán)”的瓶頸與RTOS的破局思路在深入RTOS內(nèi)核之前我們必須先搞清楚我們?yōu)槭裁匆獟仐壥煜さ穆銠C(jī)編程模式。裸機(jī)開(kāi)發(fā)尤其是基于“超級(jí)循環(huán)”Super Loop的模式其代碼結(jié)構(gòu)通常是一個(gè)永不結(jié)束的while(1)大循環(huán)里面依次調(diào)用各個(gè)功能函數(shù)。void main(void) { hardware_init(); // 硬件初始化 while(1) { task_key_scan(); // 任務(wù)1按鍵掃描 task_led_process(); // 任務(wù)2LED處理 task_lcd_refresh(); // 任務(wù)3LCD刷新 // ... 更多任務(wù) // 可能還有一個(gè)非阻塞的延時(shí) delay_ms_no_block(10); } }這種模式簡(jiǎn)單直觀在任務(wù)簡(jiǎn)單、實(shí)時(shí)性要求不高的場(chǎng)合完全夠用。但是當(dāng)系統(tǒng)復(fù)雜度上升它的弊端就暴露無(wú)遺這構(gòu)成了我們轉(zhuǎn)向RTOS的核心驅(qū)動(dòng)力。2.1 實(shí)時(shí)性無(wú)法保障高優(yōu)先級(jí)任務(wù)被“排隊(duì)”在超級(jí)循環(huán)中所有任務(wù)的執(zhí)行順序是固定的。假設(shè)task_key_scan()只需要1mstask_led_process()需要5ms而task_lcd_refresh()需要20ms。那么一次循環(huán)的總時(shí)間至少是26ms。這意味著即使有一個(gè)非常緊急的任務(wù)比如響應(yīng)一個(gè)外部中斷要求立刻熄滅LED以指示故障它也必須等待當(dāng)前這一次循環(huán)中排在它前面的所有任務(wù)特別是耗時(shí)的LCD刷新執(zhí)行完畢才能得到處理。在最壞情況下響應(yīng)延遲可能高達(dá)26ms這對(duì)于許多實(shí)時(shí)控制系統(tǒng)是不可接受的。2.2 任務(wù)間耦合度過(guò)高牽一發(fā)而動(dòng)全身所有任務(wù)函數(shù)都在同一個(gè)循環(huán)和棧空間中執(zhí)行它們共享全局變量作為通信渠道。這導(dǎo)致任務(wù)之間的邊界非常模糊。修改task_led_process()里的一個(gè)延時(shí)可能會(huì)意外影響task_lcd_refresh()的刷新率。調(diào)試時(shí)一個(gè)問(wèn)題可能出現(xiàn)在A函數(shù)但根因卻在B函數(shù)對(duì)某個(gè)全局變量的錯(cuò)誤寫(xiě)入。這種緊密耦合使得代碼維護(hù)、功能擴(kuò)展和團(tuán)隊(duì)協(xié)作變得異常困難。2.3 阻塞式調(diào)用導(dǎo)致系統(tǒng)“假死”這是超級(jí)循環(huán)最致命的弱點(diǎn)之一。如果某個(gè)任務(wù)函數(shù)內(nèi)部包含了一個(gè)阻塞式延時(shí)如delay_ms(1000)或者等待某個(gè)外部條件如while(!uart_rx_ready());那么整個(gè)循環(huán)就會(huì)停在那里其他所有任務(wù)都無(wú)法執(zhí)行。系統(tǒng)就像“死”了一樣直到這個(gè)阻塞調(diào)用完成。為了解決這個(gè)問(wèn)題我們不得不把所有的阻塞操作都改寫(xiě)成“狀態(tài)機(jī)”非阻塞模式這大大增加了編程的復(fù)雜性。注意這里說(shuō)的“狀態(tài)機(jī)”是裸機(jī)下應(yīng)對(duì)復(fù)雜邏輯的常用手段但編寫(xiě)和維護(hù)狀態(tài)機(jī)本身就是一個(gè)挑戰(zhàn)容易出錯(cuò)且代碼可讀性差。RTOS的引入正是為了解決上述矛盾。它的核心思想是“并發(fā)”與“隔離”并發(fā)通過(guò)任務(wù)調(diào)度器讓多個(gè)任務(wù)線(xiàn)程在宏觀上看起來(lái)是同時(shí)運(yùn)行的調(diào)度器根據(jù)優(yōu)先級(jí)決定下一刻該誰(shuí)運(yùn)行。隔離每個(gè)任務(wù)擁有獨(dú)立的??臻g和上下文一個(gè)任務(wù)的崩潰不會(huì)直接影響另一個(gè)任務(wù)。任務(wù)間通過(guò)RTOS提供的隊(duì)列、信號(hào)量、事件標(biāo)志等機(jī)制進(jìn)行標(biāo)準(zhǔn)化的、安全的通信而不是隨意讀寫(xiě)全局變量。第七講的內(nèi)容就是深入這個(gè)調(diào)度器的內(nèi)部看它是如何實(shí)現(xiàn)“并發(fā)”這個(gè)魔術(shù)的。3. 任務(wù)切換的魔法上下文保存與恢復(fù)詳解RTOS最核心的魔法就是任務(wù)切換。在單核CPU上任何時(shí)刻都只能執(zhí)行一條指令所謂“多任務(wù)同時(shí)運(yùn)行”只是一種錯(cuò)覺(jué)本質(zhì)是CPU時(shí)間被快速、輪流地分配給各個(gè)任務(wù)。實(shí)現(xiàn)這一點(diǎn)的關(guān)鍵在于“上下文”Context的保存與恢復(fù)。3.1 什么是“上下文”你可以把任務(wù)的上下文理解為這個(gè)任務(wù)被“打斷”那一瞬間的“現(xiàn)場(chǎng)快照”。對(duì)于ARM Cortex-M這類(lèi)處理器這個(gè)快照主要包括CPU核心寄存器包括通用寄存器 R0-R12以及一些特殊寄存器。程序計(jì)數(shù)器PC當(dāng)前執(zhí)行到了哪條指令。鏈接寄存器LR函數(shù)返回地址。程序狀態(tài)寄存器xPSR包含了條件標(biāo)志位如零標(biāo)志、進(jìn)位標(biāo)志等。當(dāng)調(diào)度器決定從任務(wù)A切換到任務(wù)B時(shí)它必須做兩件事把任務(wù)A的上述寄存器值保存到任務(wù)A私有的“?!崩铩0讶蝿?wù)B之前保存的寄存器值從任務(wù)B的棧里恢復(fù)出來(lái)裝載到CPU對(duì)應(yīng)的寄存器中。完成這兩步后CPU就會(huì)從任務(wù)B上次被打斷的地方繼續(xù)執(zhí)行仿佛從未離開(kāi)過(guò)。這個(gè)過(guò)程就是“上下文切換”。3.2 PendSV為切換而生的異常在Cortex-M內(nèi)核中上下文切換通常由一個(gè)名為PendSV可掛起的系統(tǒng)調(diào)用的異常來(lái)實(shí)現(xiàn)。為什么不用普通的SysTick中斷直接切換呢這里有一個(gè)重要的設(shè)計(jì)考量避免在中斷服務(wù)程序中發(fā)生任務(wù)切換。SysTick是系統(tǒng)的時(shí)鐘節(jié)拍它定期觸發(fā)用于更新系統(tǒng)時(shí)間、檢查任務(wù)延時(shí)是否到期等。如果我們?cè)赟ysTick的中斷服務(wù)函數(shù)ISR里直接進(jìn)行復(fù)雜的上下文切換會(huì)帶來(lái)兩個(gè)問(wèn)題中斷延遲增加上下文切換本身比較耗時(shí)會(huì)延長(zhǎng)SysTick ISR的執(zhí)行時(shí)間導(dǎo)致其他高優(yōu)先級(jí)硬件中斷的響應(yīng)被延遲。復(fù)雜度提升中斷嵌套場(chǎng)景下的上下文切換會(huì)變得極其復(fù)雜。因此通用的做法是SysTick中斷只做“標(biāo)記”工作。例如將系統(tǒng)時(shí)鐘計(jì)數(shù)器加1檢查是否有任務(wù)延時(shí)到期如果有就簡(jiǎn)單地“掛起”P(pán)end一個(gè)PendSV異常。PendSV異常擁有最低的優(yōu)先級(jí)通常設(shè)置為最低。當(dāng)CPU退出所有中斷服務(wù)程序后才會(huì)來(lái)執(zhí)行這個(gè)PendSV異常。在PendSV的中斷服務(wù)函數(shù)中完成實(shí)際的、安全的上下文切換工作。這種“標(biāo)記-處理”的分離設(shè)計(jì)保證了中斷響應(yīng)的實(shí)時(shí)性又將任務(wù)切換的復(fù)雜性封裝在了一個(gè)安全的上下文中。在課堂的代碼分析中跟蹤SysTick_Handler和PendSV_Handler這兩個(gè)函數(shù)的調(diào)用關(guān)系是理解調(diào)度器啟動(dòng)過(guò)程的關(guān)鍵。3.3 任務(wù)控制塊TCB任務(wù)的身份證每個(gè)任務(wù)在RTOS中都有一個(gè)對(duì)應(yīng)的任務(wù)控制塊Task Control Block, TCB。這是一個(gè)數(shù)據(jù)結(jié)構(gòu)相當(dāng)于任務(wù)的“身份證”或“檔案袋”。TCB里至少包含以下關(guān)鍵信息任務(wù)棧頂指針sp指向該任務(wù)私有棧的當(dāng)前棧頂。上下文就保存在這個(gè)棧里。任務(wù)狀態(tài)就緒Ready、運(yùn)行Running、阻塞Blocked、掛起Suspended等。任務(wù)優(yōu)先級(jí)。任務(wù)名稱(chēng)、事件等待列表等。在上下文切換時(shí)調(diào)度器當(dāng)前運(yùn)行任務(wù)的TCB中更新棧頂指針保存現(xiàn)場(chǎng)然后從下一個(gè)要運(yùn)行任務(wù)的TCB中取出棧頂指針恢復(fù)現(xiàn)場(chǎng)。TCB是RTOS內(nèi)核管理任務(wù)的基石。4. 調(diào)度策略核心優(yōu)先級(jí)與就緒列表的運(yùn)作機(jī)制理解了任務(wù)如何切換接下來(lái)就要看調(diào)度器“決定”切換誰(shuí)。這是RTOS調(diào)度的策略核心主要圍繞優(yōu)先級(jí)和就緒列表展開(kāi)。4.1 優(yōu)先級(jí)搶占式調(diào)度大多數(shù)實(shí)時(shí)RTOS如FreeRTOS RT-Thread都采用“基于優(yōu)先級(jí)的搶占式調(diào)度”。其規(guī)則很簡(jiǎn)單高優(yōu)先級(jí)任務(wù)永遠(yuǎn)優(yōu)先于低優(yōu)先級(jí)任務(wù)運(yùn)行?!皳屨肌比绻粋€(gè)高優(yōu)先級(jí)任務(wù)進(jìn)入了就緒狀態(tài)例如它等待的延時(shí)到了或者它等待的信號(hào)量被釋放了它會(huì)立刻搶占當(dāng)前正在運(yùn)行的低優(yōu)先級(jí)任務(wù)的CPU使用權(quán)。這就完美解決了我們之前在裸機(jī)中提到的“高優(yōu)先級(jí)任務(wù)被排隊(duì)”的問(wèn)題。高優(yōu)先級(jí)任務(wù)一旦就緒幾乎能立即得到響應(yīng)延遲僅取決于當(dāng)前中斷是否被屏蔽以及上下文切換的時(shí)間通常在微秒級(jí)。4.2 就緒列表的實(shí)現(xiàn)位圖與鏈表調(diào)度器如何快速知道當(dāng)前哪個(gè)優(yōu)先級(jí)有任務(wù)就緒呢這里用到了一個(gè)非常高效的數(shù)據(jù)結(jié)構(gòu)組合位圖Bitmap 鏈表List。優(yōu)先級(jí)位圖一個(gè)整型變量如32位的uint32_t它的每一個(gè)位bit代表一個(gè)優(yōu)先級(jí)。如果某個(gè)優(yōu)先級(jí)下有任務(wù)就緒對(duì)應(yīng)的位就被置為1。通過(guò)CPU的“前導(dǎo)零計(jì)數(shù)”CLZ或類(lèi)似指令可以在常數(shù)時(shí)間內(nèi)O(1)找到當(dāng)前置位的最高優(yōu)先級(jí)是幾。這是調(diào)度器做出決策的第一步速度極快。就緒任務(wù)鏈表每個(gè)優(yōu)先級(jí)對(duì)應(yīng)一個(gè)雙向鏈表。所有處于“就緒”狀態(tài)的同一個(gè)優(yōu)先級(jí)的任務(wù)都會(huì)掛載到這個(gè)優(yōu)先級(jí)的鏈表上。當(dāng)調(diào)度器通過(guò)位圖找到最高優(yōu)先級(jí)后就可以從這個(gè)優(yōu)先級(jí)的鏈表中取出一個(gè)任務(wù)來(lái)運(yùn)行通常是鏈表頭的任務(wù)即等待時(shí)間最長(zhǎng)的任務(wù)這實(shí)現(xiàn)了同優(yōu)先級(jí)下的時(shí)間片輪轉(zhuǎn)。在課堂的代碼逐行分析中你會(huì)看到類(lèi)似pxReadyTasksLists[priority]這樣的數(shù)組每個(gè)元素就是一個(gè)鏈表頭。而uxTopReadyPriority這個(gè)變量及其相關(guān)的位操作函數(shù)就是用來(lái)管理和查找最高就緒優(yōu)先級(jí)的。4.3 同優(yōu)先級(jí)時(shí)間片輪轉(zhuǎn)如果最高優(yōu)先級(jí)上有多個(gè)任務(wù)都處于就緒狀態(tài)怎么辦這時(shí)就會(huì)采用時(shí)間片輪轉(zhuǎn)Round Robin調(diào)度。每個(gè)任務(wù)被分配一個(gè)固定的時(shí)間片比如10個(gè)SysTick周期。任務(wù)運(yùn)行滿(mǎn)一個(gè)時(shí)間片后調(diào)度器會(huì)觸發(fā)一次任務(wù)切換讓同優(yōu)先級(jí)的下一個(gè)就緒任務(wù)運(yùn)行。這保證了公平性。在代碼中這通常通過(guò)在SysTick中斷中遞減任務(wù)的時(shí)間片計(jì)數(shù)器并在計(jì)數(shù)器歸零時(shí)進(jìn)行任務(wù)切換來(lái)實(shí)現(xiàn)。5. 從理論到代碼剖析一個(gè)極簡(jiǎn)調(diào)度器的啟動(dòng)流程理論講再多不如看一行代碼。第七講最精彩的部分就是帶領(lǐng)我們跟蹤了一個(gè)極簡(jiǎn)RTOS內(nèi)核從啟動(dòng)到第一次任務(wù)切換的全過(guò)程。這個(gè)過(guò)程就像觀看火箭發(fā)射的倒計(jì)時(shí)每一步都至關(guān)重要。5.1 硬件與堆棧初始化一切始于main函數(shù)。在調(diào)用任何RTOS API之前需要進(jìn)行硬件初始化時(shí)鐘、外設(shè)等。緊接著是RTOS內(nèi)核初始化初始化任務(wù)就緒列表和位圖將所有優(yōu)先級(jí)的鏈表頭初始化將優(yōu)先級(jí)位圖清零。創(chuàng)建空閑任務(wù)RTOS必須至少有一個(gè)永遠(yuǎn)可運(yùn)行的任務(wù)空閑任務(wù)它的優(yōu)先級(jí)最低。當(dāng)所有用戶(hù)任務(wù)都阻塞時(shí)就運(yùn)行它??臻e任務(wù)也負(fù)責(zé)一些系統(tǒng)清理工作如刪除已終止的任務(wù)。創(chuàng)建用戶(hù)任務(wù)通過(guò)xTaskCreate之類(lèi)的函數(shù)創(chuàng)建我們的應(yīng)用任務(wù)。在這個(gè)過(guò)程中為任務(wù)分配棧空間。初始化任務(wù)的TCB將棧頂指針指向一個(gè)精心準(zhǔn)備的“初始上下文”看起來(lái)像是這個(gè)任務(wù)剛被中斷過(guò)一樣。將任務(wù)根據(jù)其優(yōu)先級(jí)插入到對(duì)應(yīng)的就緒鏈表中并更新優(yōu)先級(jí)位圖。5.2 啟動(dòng)調(diào)度器vTaskStartScheduler()這是點(diǎn)燃引擎的點(diǎn)火按鈕。這個(gè)函數(shù)會(huì)做幾件關(guān)鍵事情配置SysTick定時(shí)器設(shè)置中斷周期這就是系統(tǒng)的心跳節(jié)拍。配置PendSV異常優(yōu)先級(jí)將其設(shè)為最低如前所述。手動(dòng)觸發(fā)第一次上下文切換這是最巧妙的一步。因?yàn)榈侥壳盀橹笴PU還在“裸機(jī)模式”下運(yùn)行跑在特權(quán)級(jí)的線(xiàn)程模式使用的是主棧MSP。而我們的用戶(hù)任務(wù)需要運(yùn)行在線(xiàn)程模式使用自己的進(jìn)程棧PSP。為了切換到第一個(gè)任務(wù)內(nèi)核會(huì)“偽造”一次中斷返回。它不會(huì)真的去觸發(fā)一個(gè)PendSV中斷而是直接從當(dāng)前最高優(yōu)先級(jí)就緒鏈表中取出第一個(gè)任務(wù)假設(shè)是任務(wù)A。將任務(wù)A的棧頂指針SP的值直接加載到CPU的進(jìn)程棧指針PSP寄存器中。然后執(zhí)行一條“異常返回”指令在ARM匯編中通常是BX LR或DSB; ISB;配合特殊的LR值。CPU在執(zhí)行這條指令時(shí)會(huì)硬件自動(dòng)地從PSP指向的棧中將之前保存的上下文PC, LR, xPSR, R0-R12等彈出到寄存器中。于是CPU的狀態(tài)瞬間被“恢復(fù)”成了任務(wù)A的初始上下文程序計(jì)數(shù)器PC指向了任務(wù)A的入口函數(shù)。從此CPU正式進(jìn)入了由RTOS管理的多任務(wù)世界從代碼視角看就像是vTaskStartScheduler()這個(gè)函數(shù)永不返回直接“跳轉(zhuǎn)”到了任務(wù)A的代碼中開(kāi)始執(zhí)行。5.3 心跳與切換SysTick和PendSV的協(xié)作任務(wù)A開(kāi)始運(yùn)行后系統(tǒng)就進(jìn)入了常態(tài)每隔一個(gè)時(shí)間片例如10msSysTick中斷發(fā)生。SysTick_Handler被調(diào)用它更新系統(tǒng)時(shí)鐘檢查任務(wù)延時(shí)。如果發(fā)現(xiàn)任務(wù)A的時(shí)間片用完了或者有更高優(yōu)先級(jí)的任務(wù)B就緒了它就設(shè)置PendSV的掛起位。SysTick中斷處理完畢并退出。由于PendSV被掛起且優(yōu)先級(jí)最低CPU隨即進(jìn)入PendSV_Handler。在PendSV_Handler中保存任務(wù)A的上下文壓入任務(wù)A的棧。更新任務(wù)A的TCB中的棧頂指針。從就緒列表中選出下一個(gè)要運(yùn)行的任務(wù)比如任務(wù)B。從任務(wù)B的TCB中取出棧頂指針恢復(fù)任務(wù)B的上下文從任務(wù)B的棧中彈出。通過(guò)異常返回指令跳轉(zhuǎn)到任務(wù)B的代碼繼續(xù)執(zhí)行。這個(gè)過(guò)程周而復(fù)始多任務(wù)并發(fā)的幻象就此誕生。6. 學(xué)習(xí)RTOS的常見(jiàn)誤區(qū)與實(shí)戰(zhàn)心得結(jié)合第七講的內(nèi)容和我個(gè)人的踩坑經(jīng)歷有幾個(gè)常見(jiàn)的誤區(qū)和心得值得分享。6.1 誤區(qū)一過(guò)度關(guān)注API忽視內(nèi)核機(jī)制很多初學(xué)者一上來(lái)就埋頭苦記xQueueCreate,xSemaphoreTake等API的用法卻對(duì)任務(wù)是如何被創(chuàng)建、調(diào)度、切換的一無(wú)所知。這導(dǎo)致一旦程序出現(xiàn)異常比如棧溢出、優(yōu)先級(jí)反轉(zhuǎn)、死鎖完全無(wú)從下手調(diào)試。第七講的價(jià)值就在于它強(qiáng)迫你把目光從API的表面移開(kāi)深入到內(nèi)核機(jī)制。理解了上下文切換和調(diào)度策略你再看那些API就會(huì)明白它們本質(zhì)上都是在操作TCB、操作就緒列表、操作各種等待隊(duì)列心里會(huì)有底得多。6.2 誤區(qū)二濫用高優(yōu)先級(jí)和延時(shí)理解了搶占機(jī)制后新手容易犯的另一個(gè)錯(cuò)誤是濫用高優(yōu)先級(jí)。給所有任務(wù)都設(shè)成高優(yōu)先級(jí)或者讓一個(gè)任務(wù)在循環(huán)里不停地vTaskDelay(1)延時(shí)1個(gè)tick這會(huì)導(dǎo)致系統(tǒng)頻繁地進(jìn)行無(wú)意義的任務(wù)切換增加開(kāi)銷(xiāo)反而可能讓系統(tǒng)整體性能下降。正確的做法是事件驅(qū)動(dòng)讓任務(wù)在等待事件信號(hào)量、隊(duì)列、通知等時(shí)阻塞而不是空轉(zhuǎn)延時(shí)。合理劃分優(yōu)先級(jí)真正緊急的、對(duì)實(shí)時(shí)性要求苛刻的任務(wù)如電機(jī)控制、安全檢測(cè)用高優(yōu)先級(jí)人機(jī)界面、日志上傳等任務(wù)可以用低優(yōu)先級(jí)。6.3 實(shí)戰(zhàn)心得調(diào)試技巧當(dāng)你的RTOS程序跑飛了如何定位檢查棧空間這是新手第一殺手。任務(wù)棧溢出會(huì)破壞其他內(nèi)存區(qū)域包括TCB導(dǎo)致各種詭異崩潰。在FreeRTOS中可以使用uxTaskGetStackHighWaterMark()函數(shù)來(lái)檢查任務(wù)運(yùn)行過(guò)程中棧使用的峰值據(jù)此合理設(shè)置棧大小。韋東山老師在課程中也強(qiáng)調(diào)了根據(jù)函數(shù)調(diào)用深度、局部變量大小來(lái)估算棧空間的方法。理解“當(dāng)前運(yùn)行任務(wù)”在調(diào)試器里查看RTOS內(nèi)核的全局變量比如指向當(dāng)前任務(wù)TCB的指針在FreeRTOS里是pxCurrentTCB??纯此遣皇悄闫谕哪莻€(gè)任務(wù)。如果不是思考為什么調(diào)度器切換了它模擬單步調(diào)試的局限性在RTOS環(huán)境下單步調(diào)試Step Over可能會(huì)因?yàn)橛|發(fā)SysTick中斷而導(dǎo)致任務(wù)切換讓你覺(jué)得代碼在“亂跳”。這時(shí)多用斷點(diǎn)Breakpoint和觀察點(diǎn)Watchpoint關(guān)注關(guān)鍵數(shù)據(jù)如隊(duì)列內(nèi)容、信號(hào)量計(jì)數(shù)、任務(wù)狀態(tài)的變化。利用空閑任務(wù)鉤子函數(shù)如果系統(tǒng)看起來(lái)“卡住”了但沒(méi)復(fù)位可以檢查是否所有用戶(hù)任務(wù)都因?yàn)槟撤N原因阻塞了而空閑任務(wù)在正常運(yùn)行。在空閑任務(wù)的鉤子函數(shù)里點(diǎn)個(gè)燈可以幫你確認(rèn)內(nèi)核是否還在運(yùn)轉(zhuǎn)。第七講的內(nèi)容是RTOS學(xué)習(xí)道路上的一個(gè)分水嶺。它把那個(gè)神秘的“黑盒子”打開(kāi)了讓你看到了齒輪是如何咬合轉(zhuǎn)動(dòng)的。這份理解能讓你在后續(xù)使用信號(hào)量、互斥鎖、消息隊(duì)列這些高級(jí)特性時(shí)不僅知其然更能知其所以然從而寫(xiě)出更健壯、更高效的嵌入式多任務(wù)程序。當(dāng)你再遇到任務(wù)調(diào)度不合預(yù)期時(shí)你腦海里的第一反應(yīng)不再是盲目地翻查API手冊(cè)而是會(huì)去想此刻的就緒列表是什么狀態(tài)誰(shuí)的優(yōu)先級(jí)最高是不是有任務(wù)該阻塞的沒(méi)阻塞這種思維方式的轉(zhuǎn)變才是這門(mén)課帶給我們的最大財(cái)富。