在嵌入式作業系統複習中,我們瞭解了μC/OS-II的相關基礎知識,在任務排程這一節,我們提到了
優先順序點陣圖演算法
,本文詳細介紹該演算法的原理和實現。 說明: 本文參考了這篇文章,加入了一些自己的理解,如有侵權,請聯絡刪除:原文連結
1、μC/OS-II任務優先順序相關簡介:μC/OS-II中共有64個優先順序(0~63級,數字越小優先順序越高)。因為是實時系統,所以對應每個任務就分配一個優先順序。
2、2進位制和10進位制轉換基礎
這裡先介紹一個數學知識,二進位制如何變為十進位制,比如十進位制26,其8位二進位制表示為:00 011 010
。當十進位制為0~63時,前兩位無作用,所以只看後6位——011 010
.怎麼計算成十進位制呢?很簡單:把這個十進位制數,分為兩個部分,高三位和低三位,這個十進位制數的大小就等於高三位的十進位制8+低三位的十進位制數(其實就是二進位制轉八進位制再轉十進位制)。高三位的011=3
,低三位的010=2
,所以26=3x8+2=(011)<<3+(010).即將高三位左移三位
就是8再加上低三位。下面要介紹的演算法也是這個數學方法。
3、優先順序任務排程過程
建立任務並分配優先順序 通過演算法,作業系統對建立完成的任務(即 就緒任務
)進行標記。並通過標記來查詢當中任務中優先順序最高的任務呼叫排程函式進行排程,讓最高優先順序任務執行
優先順序建立
μC/OS-II中建立任務的函式原型:
INT8U OSTASKCeate(void (*task)(void *pd),void *pdata,os_stk *ptos,INT8U prio)
,從這個函式可以看出,最後一個引數就是優先順序,所以結論是,在建立任務的同時就要確定任務的優先順序,並且是該優先順序是8位的(0~2^8-1),這裡也可以看出為什麼會有64個優先順序。
因為使用者可以指定任務的優先順序,但實時作業系統最大的好處就是高優先順序的任務可以搶佔低優先順序的任務,那怎麼實現的呢?當然是通過優先順序實現。 既然使用者在呼叫系統函式建立任務的同時指定了任務的優先順序,一旦建立了任務,該任務就會立即成為就緒狀態,作業系統就會將該任務的優先順序
標誌位置位1
,相當於做個記號,作業系統心裡就會想,哦,這個優先順序的任務已經就緒了,同樣建立了其他的任務,作業系統都會在某個地方做好標記表明對應優先順序的任務已經就緒,然後在排程函式的排程下進行排程,那麼在哪個地方做個標記呢?既然是實時作業系統,作業系統用什麼演算法去查詢優先順序最高的任務呢?
任務優先順序的標定
什麼是優先順序的標定:就是作業系統要知道哪個任務已經就緒了,然後就在這些就緒了的任務裡面切換排程。所以第一步就是要知道哪些任務就緒了,然後就可以操作了。
這裡要先介紹兩個資料結構,:OSRdyGrp、OSRdyTbl[]
,這兩個變數協同完成優先順序的標定。
OSRdyGrp
:優先順序就緒組
這是一個8位的變數。每一個變數對應於OSRdyTbl[]中的一行(實際上是一個元素,但也可以理解為一行)。
OSRdyTbl[]
:優先順序就緒表
這是一個陣列,有8個成員,每個成員都是8位的變數,所以就可以構成了8*8的矩陣了。所以64個優先順序都是標定在這個陣列中的。
從上圖可以明顯看出,這個圖有64個空格(64個位),每個空格對應一個數字,該數字就是優先順序的標號,但是這個是人為的標上的,實際上這是64個空格,現在要做的事情就是將就緒任務的優先順序相對應的標號置1,表示這個優先順序任務就緒了,比如剛建立了一個任務,它的優先順序是7,所以往表格中數字為7的空格寫入1就表明該優先順序的任務就緒了,可以被排程了。另外當所有需要建立任務都建立完畢後,各個就緒任務的相應數字空格都會置1,表明這些任務都就緒了,比如,現在建立了5個任務,優先順序分別為4,7,9,10,24.所以在建立完這些任務後,這個優先順序就緒表中的相對應的數字空格都會被置1,要特別注意上圖的優先順序順序,0的優先順序最高,63的優先順序最低。
那到底怎麼往空格里置1的呢? 這裡就要分析這個優先順序就緒表了
:
1.這個表的資料結構是陣列,也就是說,這個陣列有8個成員,每個成員都是8位的變數。 2.通過二級查詢實現對就緒任務的標定的。這裡可以理解為一個矩陣,找某個數,肯定是先找行,再找列。從而找到這個元素位置。思想就是這個。 怎麼找行呢?這裡的一個變數OSRdyGrp,是一個8位的變數。每一位對應上圖的一行,當某一位置1時,就表明就緒表對應的行有就緒任務,然後再查詢列,就可以找到哪個任務就緒了。現在舉個列子來說明:
建立一個任務,它的優先順序為 prio=11(這是使用者指定的),此優先順序用二進位制表示(8位):最前面兩位無用處,前面已說過 00 001 011
。那麼怎麼在對應的OSRdyTbl[]優先順序就緒表中進行標定呢?
把這個二進位制數分為兩個部分:高3位 (001)和低3位(011); 高三位負責找到陣列中的行,低三位負責找到陣列中的列(其實這裡不是列,是一個變數的8個位,也可以按列理解),這樣配合就可以定址,往相應數字標號裡寫1。 對上面這個數來說 001 =1說明是第1行(陣列從0行開始),011=3說明在第3個位置要寫入1,合在一起就是第一行的第三個位置寫入1
,這樣就完成了對應數字優先順序標號的標記。
那從上面的思路來看,我們只要知道陣列中的第幾行和第幾列的值就可以了,所以又引進了一個對映陣列
:
OSMapTbl[]
,其具體內容如下。下標0對應的就是0位為1,下標1對應的就是1位為1,然後把這個數賦值給OSRdyGrp優先順序就緒組。則OSRdyGrp哪個位為1則說明就是就緒表哪個行有就緒任務了。這樣做的好處就是快。這也就是這個陣列就是個對映陣列,方便操作而已。
下標 | 二進位制值 |
---|---|
0 | 00000001 |
1 | 00000010 |
2 | 00000100 |
3 | 00001000 |
4 | 00010000 |
5 | 00100000 |
6 | 01000000 |
7 | 10000000 |
至此,以上涉及3個資料結構了,現在來總結一下:
1.OSRdyGrp
優先順序就緒組:第幾位被置1,就說明就緒表中第幾行有就緒任務。比如OSRdyGrp=0000 0001
。說明OSRdyTbl[0]
行有就緒任務。具體是這行的哪個列還要根據低三位
的值來決定.
2.OSRdyTbl[]
優先順序就緒表:行+列就能標定就緒任務的優先順序.
3. OSMapTbl[]
優先順序對映表:用來方便生成第幾行,第幾列的一個轉換.
下面來看ucos中的原始碼怎麼實現的: 任務就緒原始碼如下:
OSRdyGrp |= OSMapTbl[prio>>3];
OSRdyTbl[prio>>3] |= OSMapTbl[prio&0x07];
程式碼解釋:prio>>3是獲取優先順序的高3位
,prio&0x07是獲取優先順序的低3位
。然後在通過OSMapTbl的對映分別獲得了就緒表中的行和就緒表中的列, 然後查詢就緒演算法:
y = OSUnMapTbl[OSRdyGrp];
x = OSUnMapTbl[OSRdyTbl[y]];
prio = (y << 3) + x;
舉個例子:
建立一個任務,且prio=11=001 011
的情況分析:
高3位
:001=1
通過OSMapTbl
對映後,OSRdyGrp=0000 0010,即是就緒表中第1行有任務就緒。
低3位
:011=3,通過OSMapTbl
對映後
//低三位的對映
OSMapTbl[prio&0x07] = OSMapTbl[3] = 0000 1000;
OSRdyTbl[prio>>3] = OSRdyTbl[1] = 0000 1000;
通過這句程式碼就往就緒表第一行(從OSRdyTbl[1]看到)第3個位置(從右往左看0000 1000,第一個為1的位,0開始)寫入1,表明該任務就緒了。
這樣就完成了單個任務優先順序的標定。
多工優先順序設定
這裡引入一個表格:優先順序判定表OSUnMapTbl[]
,這個表的作用是為了節省時間,這樣查表的話,耗的時間是一定的,很好的滿足了實時性。下面來分析這個表的作用。
1.先看最旁邊的註釋,說明的是該陣列中對應的位置。
2.解釋這個陣列中內容,這些數字怎麼來的。
舉例:0x53 如上圖所示的位置,為什麼是0呢?我們把0x53變成二進位制來看: 0101 0011,從右往左看,第一個出現1的位,就是0位所以為0. 為什麼是從右往左看呢?因為高優先順序的數字低,你應該懂的。
例子 : 有4個任務 ,優先順序分別為6,10,11,17.。把上面就緒任務演算法再貼一遍:前面2位不管。
6對應二進位制:000 110
高3位:000=0 通過OSMapTbl對映後,
OSMapTbl[prio>>3]= OSMapTbl[0]=0000 0001
低3位:110=6,通過OSMapTbl對映後
OSMapTbl[6]=0100 0000
OSRdyTbl[prio>>3]= OSRdyTbl[0]=0100 0000
10對應二進位制:001 010
高3位:001=1 通過OSMapTbl對映後,
OSMapTbl[prio>>3]= OSMapTbl[1]=0000 0010.
低3位:010=2,通過OSMapTbl對映後
OSMapTbl[2]=0000 0100
OSRdyTbl[prio>>3]= OSRdyTbl[1]=0000 0100
11對應二進位制:001 011
高3位:001=1 通過OSMapTbl對映後,
OSMapTbl[prio>>3]= OSMapTbl[1]=0000 0010.
低3位:011=3,通過OSMapTbl對映後
OSMapTbl[3]=0000 1000
OSRdyTbl[prio>>3]= OSRdyTbl[1]=0000 1000
17對應二進位制:010 001
高3位:010=2 通過OSMapTbl對映後,
OSMapTbl[prio>>3]= OSMapTbl[2]=0000 0100.
低3位:001=1,通過OSMapTbl對映後
OSMapTbl[1]=0000 0010
OSRdyTbl[prio>>3]= OSRdyTbl[2]=0000 0010
通過就緒任務演算法:
OSRdyGrp |= OSMapTbl[prio>>3];
OSRdyTbl[prio>>3] |= OSMapTbl[prio&0x07];
最終OSRdyGrp的值就是將所有的OSMapTbl[prio>>3]進行位或運算:
OSRdyGrp=
000 00001
|0000 0010
|0000 0010
|0000 0100
=0000 0111 = 0x07
OSRdyTbl[0]=0100 0000
OSRdyTbl[1]=
0000 0100
|0000 1000
=0000 1100(相同的列進行位或運算)
OSRdyTbl[2]=0000 0010
然後查詢優先順序判定表OSUnMapTbl[]
OSRdyGrp=0x07
Y=OSUnMapTbl[0x07]=0
說明是最高優先順序在第0組。
OSRdyTbl[0]=0100 0000=0x40
X = OSUnMapTbl[0x40]=6
最高優先順序為:prio= y*8+x=6
至此,最高優先順序就選出來了。然後排程此任務執行就是了,另外,刪除任務就是將對應就緒列表位的置的1清零就是。
if ((OSRdyTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0)
OSRdyGrp &= ~OSMapTbl[prio >> 3];
看到這裡,這行程式碼理解應該沒有問題,就是反操作而已。