概述
任務可以認為是一段獨享CPU的運行程序,而應用是完成特定功能的多個任務的集合。任務管理就是為多任務環境中的每個任務分配一個上下文(context)(上下文(context)是指當任務被調度執行的所必不可少的一組數據,包括前任務的CPU指令地址(PC指針),當前任務的棧空間,當前任務的CPU寄存器狀態等),在任務相繼執行過程中,將切出任務的信息保存在任務上下文中,將切入任務的上下文信息恢復,使其得以執行。為維護任務上下文、狀態、棧等相關信息,操作系統內核為每個任務定義了一組數據結構,即任務控制塊(Task Control Block),來存放這些信息。 任務調度負責將處理器資源分配給關鍵任務,讓其優先運行。所以系統中的每個任務需要根據關鍵程度分配不同的優先級,那些執行關鍵操作的任務被賦予高優先級,而一些非關鍵性任務賦予低優先級。當系統發生調度時,高優先級任務可以搶占低優先級任務的處理器資源得到調度執行。系統在無任務可調度時,就運行空閑任務,其優先級最低。 任務被創建時,需要為任務指定執行體入口地址、棧大小、優先級等信息,創建過程中內核為任務分配任務控制塊(TCB)。任務棧空間可以在任務創建時由用戶程序指定,也可以由內核根據用戶指定大小來動態分配。操作系統內核提供基于軟件的棧溢出檢測方法,用戶可根據需要配置或關閉該功能。
任務狀態
任務狀態是反映當前系統中任務所處的狀況,操作系統內核需要維護所有任務的當前狀態。AliOS Things為了充分描述任務在系統中所處的狀態以及引發狀態遷移的條件差異,將任務狀態分為就緒狀態、掛起狀態、休眠狀態、阻塞狀態、運行狀態和刪除狀態。當任務通過aos_task_create()創建時,任務處于掛起狀態,當任務通過aos_task_del()刪除時,任務處于刪除狀態,具體的轉化過程如下圖:
(1)阻塞狀態是指因等待資源而處于等待狀態,如調用aos_mutex_lock()獲取互斥量時互斥量已經被鎖定、調用aos_queue_recv()獲取隊列數據時隊列為空、調用aos_sem_wait()等待信號量時信號量計數為0、調用aos_event_get()獲取事件時,事件還未發生; (2)掛起狀態是因任務被其他或自身調用掛起函數aos_task_suspend()后,將無條件地停止運行,被掛起的任務只能通過其他任務調用恢復函數aos_task_resume()使其恢復到就緒狀態; (3)休眠狀態是因任務在調用任務休眠函數aos_msleep()后,進入一種延遲執行的狀態,直到休眠時間到達任務被重新調度恢復運行。 (4)刪除狀態是因任務運行完成調用任務退出函數aos_task_exit()或被調用任務刪除函數aos_task_del()時被設置的一種狀態。 (5)就緒狀態是在任務被創建或任務解除阻塞或延遲到期時,任務被置為就緒狀態。只有當任務處于就緒狀態時,才能被系統調度進入運行狀態。 (6)運行狀態是獲取處理器執行權的就緒任務所處的狀態,單處理器系統,任意時刻只有一個任務可以運行。
AliOS Things允許任務處于組合狀態,如阻塞掛起狀態:任務在阻塞狀態下,被其他任務掛起,則進入阻塞掛起狀態。該狀態下,若任務被恢復則保持阻塞狀態;若任務解除阻塞則保持掛起狀態。 用戶可通過tasklist命令查看任務狀態,任務狀態描述符號和含義如下表:
狀態符號 | 描述 |
RDY | 任務已在就緒隊列或已被調度運行,處于就緒狀態或運行狀態 |
PEND | 任務因等待資源或事件的發生而處于阻塞狀態 |
SUS | 任務因被其他或自身調用掛起函數aos_task_suspend()后所處的掛起狀態 |
SLP | 任務處于休眠狀態 |
PEND_SUS | 任務在阻塞狀態下,被其他任務掛起,處于阻塞掛起狀態 |
SLP_SUS | 任務在休眠狀態下,被其他任務掛起,處于休眠掛起狀態 |
DELETED | 任務處于刪除狀態 |
任務調度
任務調度是為多任務環境中的就緒任務分配處理器資源。AliOS Things操作系統內核支持兩種調度策略:
基于優先級的搶占式調度
該調度策略下,每個任務優先級都維護了一個FIFO模式的就緒隊列(ready queue),里面包含了當前所有可運行的任務列表,此列表中的任務都處于就緒狀態,當處理器可用時,最高優先級的就緒隊列的第一個任務得到處理器被執行。當有一個高優先級任務進入就緒隊列,正在運行的低優先級任務會立即被喚出,將處理器執行權交給高優先級任務。此種調度機制存在一個潛在問題,如果存在多個優先級相同的任務,其中一個任務強占處理器,則其他同等優先級的任務將無法被執行。時間片輪轉調度可以避免這個問題。
基于時間片的輪轉調度
時間片輪轉調度使用時間片控制每個任務的執行時間,同等優先級的任務依次獲得處理器被調度執行,每個任務可以運行的時間片是固定的,當任務的時間片用完后,該任務被放在對應優先級就緒隊列的隊尾,然后調度就緒隊列第一個位置上的任務來執行。
優先級分配
AliOS Things操作系統內核允許的任務優先級分配范圍默認為0~62,值越大表示優先級越低,其中空閑任務的優先級是61,即系統最低優先級。
任務棧保護
任務棧保護采用棧統計的方法在任務切換時,對當前任務棧的使用情況進行統計,當檢測到任務棧使用量超過了預先分配的空間,則拋出異常。
空閑任務
空閑任務是一個無限循環函數,在進入循環前或循環體內允許用戶配置鉤子函數。
任務管理函數
AliOS Things操作系統內核提供了任務創建、任務刪除、任務延遲、獲取任務名稱等任務相關的服務函數,提供給應用程序調用,具體描述如下:
函數名 | 描述 |
aos_task_create() | 任務創建函數(推薦) |
aos_task_new() | 任務創建函數(兼容3.1) |
aos_task_new_ext() | 任務創建函數(兼容3.1) |
aos_task_exit() | 任務退出函數 |
aos_task_delete() | 任務刪除函數 |
aos_task_resume() | 任務恢復函數 |
aos_task_suspend() | 任務掛起函數 |
aos_task_yield() | 任務讓出CPU函數 |
aos_task_self() | 獲取當前任務的句柄 |
aos_task_name_get() | 獲取任務名稱 |
常用配置
任務優先級最大值:默認62, 最大不能超過256,如需修改,在YAML中修改RHINO_CONFIG_PRI_MAX配置
def_config:
RHINO_CONFIG_PRI_MAX: 127
任務棧溢出檢測:默認關閉, 如需修改,在YAML中修改RHINO_CONFIG_TASK_STACK_OVF_CHECK配置
def_config:
RHINO_CONFIG_TASK_STACK_OVF_CHECK: 1
空閑任務棧大小:默認100Bytes, 如需修改,在YAML中修改RHINO_CONFIG_IDLE_TASK_STACK_SIZE配置
def_config:
RHINO_CONFIG_IDLE_TASK_STACK_SIZE: 1024
時間片輪轉調度策略:默認使能, 如需修改,在YAML中修改RHINO_CONFIG_SCHED_RR配置
def_config:
RHINO_CONFIG_SCHED_RR: 0
完全公平調度策略:默認關閉, 如需修改,在YAML中修改RHINO_CONFIG_SCHED_CFS配置
def_config:
RHINO_CONFIG_SCHED_CFS: 1
API說明
使用示例
示例代碼參考example/task_example.c,該示例使用任務管理函數來控制任務的執行狀態,具體場景為任務2因等待某個信號量進入阻塞狀態,而此時被任務1將其掛起,則任務2仍然是處于阻塞狀態,如果在此過程中等到信號量,則任務2會解除阻塞進入掛起狀態;如果未等到信號量,則任務2恢復狀態后仍然處于阻塞狀態。 示例說明如下:
在t0時刻,任務task1、task2是通過aos_task_create()函數調用被創建,之后task1進入就緒狀態,而task2處于掛起狀態。
Task1得到運行后,在t1時刻調用aos_task_resume()將task2恢復,task2進入就緒狀態,之后task1通過調用aos_msleep()進入休眠狀態,task2因為task1休眠而獲得CPU執行權,task2運行后因等待信號量進入阻塞狀態。
Task1在t2時刻因延遲到期得到運行,并調用aos_task_suspend()將task2掛起,task2此時的狀態為阻塞掛起。之后task1通過調用aos_msleep()進入休眠狀態。
Task2在t3時刻因延遲到期得到運行,并調用aos_task_resume()將task2恢復,此時task2的狀態為阻塞狀態。之后task1通過調用aos_msleep()進入休眠狀態。
Task1在t4時刻因延遲到期得到運行,并調用aos_sem_signal()釋放信號量,這時task2因等到信號量而進入就緒狀態。待到task1再次進入休眠轉改后task2得到運行,進入運行狀態。
該示例可配置到helloworld_demo案例中運行,相關代碼的下載、編譯和固件燒錄均依賴AliOS Things配套的開發工具 ,所以首先需要參考《AliOS Things集成開發環境使用說明之搭建開發環境》,下載安裝 。 待開發環境搭建完成后,可以按照以下步驟進行示例的測試。
步驟1 創建或打開工程
打開已有工程
如果用于測試的案例工程已存在,可參考《AliOS Things集成開發環境使用說明之打開工程》打開已有工程。
創建新的工程
組件的示例代碼可以通過編譯鏈接到AliOS Things的任意案例(solution)來運行,這里選擇helloworld_demo案例。helloworld_demo案例相關的源代碼下載可參考《AliOS Things集成開發環境使用說明之創建工程》。
步驟2 添加組件
案例下載完成后,需要在helloworld_demo的package.yaml中添加
depends:
- osal_aos: dev_aos # helloworld_demo中引入osal_aos組件
步驟3 下載組件
在已安裝了 的開發環境工具欄中,選擇Terminal -> New Terminal啟動終端,并且默認工作路徑為當前工程的workspace,此時在終端命令行中輸入:
aos install osal_aos
上述命令執行成功后,組件源碼則被下載到了./components/osal_aos路徑中。
步驟4 添加示例
在osal_aos組件的package.yaml中添加example示例代碼:
depends:
- rhino: dev_aos
- cli: dev_aos # 添加cli依賴
source_file:
- "*.c"
- "example/task_example.c" # 添加 task_example.c
步驟5 編譯固件
在示例代碼已經添加至組件的配置文件,并且helloworld_demo已添加了對該組件的依賴后,就可以編譯helloworld_demo案例來生成固件了,具體編譯方法可參考《AliOS Things集成開發環境使用說明之編譯固件》。
步驟6 燒錄固件
helloworld_demo案例的固件生成后,可參考《AliOS Things集成開發環境使用說明之燒錄固件》來燒錄固件。
步驟7 打開串口
固件燒錄完成后,可以通過串口查看示例的運行結果,打開串口的具體方法可參考《AliOS Things集成開發環境使用說明之查看日志》。
當串口終端打開成功后,可在串口中輸入help來查看已添加的測試命令。
步驟8 測試示例
CLI命令行輸入:
task_example
關鍵日志:
[aos_task_example]task1 is running!
[aos_task_example]task1 resume task2!
[aos_task_example]task1 start to sleep and release CPU!
[aos_task_example]task2 is running!
[aos_task_example]task1 suspend task2!
[aos_task_example]task1 start to sleep and release CPU!
[aos_task_example]task1 resume task2 again!
[aos_task_example]task1 start to sleep and release CPU!
[aos_task_example]task1 signal a semphone!
[aos_task_example]task1 start to sleep and release CPU!
[aos_task_example]task2 get semphone and is running!
注意事項
時間片輪轉調度也是基于優先級策略的,如果有一個高優先級任務就緒了,無論當前任務的時間片是否用完,處理器都會立即去執行高優先級任務,當被打斷的任務恢復執行時,它將繼續執行剩下的時間片。
tasklist查看運行任務的狀態標識符為RDY,內核并沒有為運行狀態定義特定的標識符。
FAQ
Q1: 如何避免棧溢出?
答:任務棧必須滿足應用程序對函數嵌套調用所需的空間,用戶可以通過tasklist查看棧的使用情況,根據需要調整棧大小,這樣可以在防止棧溢出的同時,也可避免棧空間分配過大造成浪費。
Q2: aos_task_yield()和aos_task_suspend()都能夠使任務退出運行狀態,使用上有什么差異嗎?
答:aos_task_yield()僅僅是讓出CPU,它仍然處于就緒狀態,因為該操作只是將任務放置到就緒隊列的隊 尾,若無更高優先級任務在就緒隊列中,它就可以被調度執行。而aos_task_suspend()是將任務掛起停 止運行,任務會從就緒隊列中去除,只有當任務被其他任務調用aos_task_resume()才能恢復。