什麼是任務搶占?
實時作業系統大多都是基於優先順序排程的搶占式的核心,這句話每本關於rtos的資料都有。樓主最怕這種每個字都認識,但連到一起就很不好理解的書面語言。直白點說,就是:
1 每個任務都有自己的優先順序,一般都是在create_task的時候以函式入參的形式確定,有的rtos也提供api允許應用程式在task執行起來後,動態修改其優先順序(
xx_create_task,xx_
change_priority)
2 任務一般分為幾種狀態:就緒、執行、阻塞、掛起等等。為方便理解,就認為是兩種情況,可執行和不可執行。
那task什麼時候不可執行呢?例如task自己呼叫了suspend函式把自己掛起;或者本文用到的任務阻塞在乙個訊息佇列上等待訊息,而此時訊息佇列裡沒有內容,這種情況下任務就會處於不可執行狀態
3 如果有處於可執行狀態的高優先順序task,那麼低優先順序的task只能等待到它執行結束或者轉為不可執行的狀態,才有可能獲得cpu
4 也是最有意思的地方,高優先順序的task一旦獲得的可繼續執行的資源,就會立刻打斷低優先順序任務的執行。
不過這是前提的,就是該低優先順序是「允許搶占的」。
任務搶占的演示
本文以幾個簡單的實驗來引入這些概念,並清楚的演示什麼是「搶占式」核心,使用的rtos是nucleus。
在main函式(系統啟動後的第乙個任務)裡建立兩個task,task_send和task_receive,和乙個訊息佇列test_queue用於這兩個task之間的通訊
nu_create_queue(&test_queue,
"test_q",
queue_buf,
queue_size,
nu_fixed_size,
1, nu_fifo);
nu_create_task(&sendtask,
"",task_send,
0,nu_null,
task_stack,
stack_size,
51,0,
nu_preempt, nu_start);
nu_create_task(&receivetask, "" ,task_receive, 0, nu_null, task_stack, stack_size, 50, 0,nu_preempt, nu_start);
請特別注意標註顏色的引數。
紅色引數表示為任務的優先順序,數字越小代表優先順序越高。這裡task_send是51,task_receive是50,那麼task_receive的優先順序較高
綠色引數表明了該任務是否允許被搶占,nu_preempt表示允許,nu_no_preempt相反
這兩個task的執行函式分別如下:
void task_receive(unsigned argc, void *argv)}
void task_send(unsigned argc, void *argv)}
程式很簡單,其執行起來後,輸出的log如下:
[0:0:1:419] before send msg 1
[0:0:1:419] recved a msg 1
[0:0:1:419] aftersend msg 1
[0:0:3:919] before send msg2
[0:0:3:920] recved a msg 2
[0:0:3:920] after
send msg2
......
從log中可以清晰的看到,
task_send首先列印「before send msg」,然後在
task_send傳送了乙個訊息之後,等待在該訊息佇列上的
task
_receive立刻解除阻塞,並且判斷當前task
_receive的是系統中優先順序最高的task,於是發生了一次「任務搶占」,task
_receive瞬間獲得cpu,將task
_send推在一邊開始自己執行,並輸出了log,緊接著又再次阻塞在訊息佇列上。核心再排程task_send繼續執行,列印「
after
send msg
」,然後睡眠2秒,開始下次迴圈。
如果在task建立的時候將優先順序和是否允許搶占的引數修改一下,情況就會變得不同
nu_create_task(&sendtask,
"",task_send,
0,nu_null,
task_stack,
stack_size,
50,0,
nu_preempt, nu_start);
nu_create_task(&receivetask, "" ,task_receive, 0, nu_null, task_stack, stack_size, 50, 0,nu_preempt, nu_start);
如上,將兩個task的優先順序設定為相同,再次執行,log輸出情況就會變成:
[0:0:1:419] before send msg 1
[0:0:1:419] after send msg 1
[0:0:1:419] recved a msg 1
[0:0:3:919] before send msg 2
[0:0:3:920] after send msg 2
[0:0:3:920] recved a msg 2
......
task_send首先列印「
before send msg
」,傳送乙個訊息給佇列,核心將訊息傳到佇列之後,發現等待在佇列上的任務優先順序並不比當前執行的task要高,則不會重新執行一次任務排程,而讓task_send繼續執行。task_send列印「
after send msg
」之後,開始進入睡眠,主動放棄cpu,這時task
_receive得到執行,輸出log "recved a msg",緊接著再次阻塞在佇列上,進入下次迴圈。
如果建立task的時候,將入參改為下面這樣,得到的實驗結果和前面一次也是一樣的
nu_create_task(&sendtask,
"",task_send,
0,nu_null,
task_stack,
stack_size,
51,0,
nu_no_preempt, nu_start);
nu_create_task(&receivetask, "" ,task_receive, 0, nu_null, task_stack, stack_size, 50, 0,nu_preempt, nu_start);
task_send設定了不可搶占屬性,這樣即使傳送訊息以後,nucleus核心判斷到task_receive的優先順序較高,但由於task_send不可被搶占,只能等到task_send主動放棄cpu的時候(nu_sleep)才能得到執行。
任務搶占是怎麼做到的?
現在來看看nucleus的核心裡是怎麼實現的這套機制,原始碼面前,了無秘密
nu_receive_from_queue-->
quc_receive_from_queue-->
/* determine if there are messages in the queue. */ //判斷當前訊息佇列裡是否有訊息
if (queue -> qu_messages)
else //沒有訊息,判斷是否需要阻塞
} nu_send_to_queue-->
quc_send_to_queue-->
在該函式裡,nucleus會根據msg queue結構體裡的qu_suspension_list來判斷是否有訊息佇列阻塞,如果有,將其喚醒
/* wakeup the waiting task and check for preemption. */
preempt = tcc_resume_task((nu_task *) suspend_ptr -> qu_suspended_task,nu_queue_suspend);
/* determine if preemption needs to take place. */
if (preempt)
tct_control_to_system(); //它類似於linux的schedule函式,將控制權交還給核心,由核心來決定下乙個要執行的task是誰
tcc_resume_task函式的返回值決定了是否需要發生任務搶占,它內部最關鍵的**片段如下:
/* determine if this newly ready task is higher priority
than the current task. */
if ((int) (task -> tc_priority) < tcd_highest_priority) }
return status;
over
實時作業系統的任務排程示例之優先順序反轉
1 什麼是優先順序反轉?目前市面流行的實時作業系統都是採用可搶占式的基於優先順序的排程方式,其保證了處於就緒狀態的優先順序高的任務可以先於優先順序低的任務而執行。但這並不是絕對的,優先順序反轉是實時系統中的乙個經典特例。其大體流程如下 假設系統中有3個task,優先順序分別為高 中 低,以task ...
ucosii實時作業系統的任務排程
嵌入式作業系統的任務排程演算法好壞在很大程度上決定了該系統的執行效率,由於其執行的頻率極高,所以在任務排程函式的實現上,對於效率的要求可以用苛刻來形容。基於優先順序的任務排程總結下來就是做了兩件事 1 找到優先順序最高的就緒態任務 2 切換任務上下文並開始執行該任務 第二步的切換上下文對個各個rto...
實時作業系統之任務
任務可以以下列狀態之一存在 有效的任務狀態轉換 任務優先順序 每個任務都分配了乙個從0到 configmax priorities 1 的優先順序,其中configmax priorities在freertosconfig.h中定義。如果正在使用的埠實現了使用 計數前導零 型別指令 用於在單條指令中...