uCOSII源代碼剖析—終極篇

啓動工作原理

剛接觸操作系統的時候覺得這個最神祕,到底裏面做了什麼,怎麼就成了個操作系統,它到底有什麼用,爲什麼要引進來着個東東。學了之後才知道,原來最根本的思想還是源於彙編裏面的跳轉和壓棧,以調用一個函數爲例,編譯後的彙編肯定是先通過SP壓入當前代碼段地址然後就是保存一些寄存器的值放棧裏面(51單片機好像不是這樣),然後執行程序,完了之後,出棧把寄存器恢復,最後把原來存的代碼段地址付給PC然後回到原來的程序,這是彙編執行函數的做法,而操作系統人爲強行的模擬這樣操作,把代碼寫成不同代碼塊假定爲AB,要相互之間執行似乎不受影響,是這麼實現的,假設首先代碼在A中執行,代碼段一直往下指,如果我要執行B,首先把A的代碼段壓入棧,所有的寄存器值存入A的棧,然後讓SP強行指到B的棧執行出棧操作,把寄存器恢復成B的,B的代碼段地址放入PC,這樣就B在執行,如果又要A接着上次執行,又強行把B的寄存器、當前代碼段地址壓入B的棧,然後SP強行指到A的棧恢復的時候恢復成A最近一次保存的東西(和函數調用不同每次切換棧裏面的東西是隨機的),這樣,可以很自由的在AB中切換,如果切換足夠快,AB看以來好像同時在執行,這就是並行,AB就是任務。如果這個切操作放到定時器函數中來做,就可以嚴格按照時間來切換,這就是操作系統雛形。另外,各個任務之間有存在一定的關係,有邏輯上的先後等,必須引進全局的結構體、變量來標記一些信息,全局的這些數據是不會被釋放的,所以所有的任務可以去通過讀、寫這些數據來實現各個程序塊交流信息,實現所謂的同步、互斥。這就是操作系統的原理,而這些不同的通信方式按功能細分就成事件管理、內存管理啥玩意。有這些基本的管理就是一個只有內核操作系統了。配上文件系統、圖形界面這些個模塊功能就能做出想Window這樣的東東。只有引入操作系統才能更好的寫程序,才能讓性能發揮到極致。具體到uCOSII也是這樣:首先是主函數,然後是OSInit(),這個函數就是對那些全局的數據結構初始化,建立希望的鏈表等數據結構,爲後面全局變量通信做好準備,並且創建了1-2個系統任務(空閒任務必須,統計任務可選),而所謂的創建任務OSTaskCreate(另外在這個系統裏還有個OSTaskCreateExt也是一種創建任務函數,只不過多了些檢測棧、清楚棧的功能而已)就是把一個函數,的函數地址、自己的棧建立聯繫、優先級啥弄好爲任務切換做好準備。設置好定時切換的相關信息類似定時器,按照節拍在中斷中進行任務切換判斷、發生切換,這個時候還沒有開啓開關,所以的任務創建完成後,啓動多任務函數OSStart(),這個函數是讓SP指到其中的一個站然後出棧就跳到一個任務函數裏去了,接下來就是正常的任務運行了。

內存管理部分

函數名

功能簡介

OS_MemInit();

初始化分區控制塊OS_MEM構建空閒內存控制塊MCB空閒鏈表,OSInit()內部調用。

OSMemCreate();

實際上是定義一個二維數組,一維也是可以的只是沒那麼直觀方便而已,通過一個從空閒內存控制塊鏈表上取一個OS_MEM來管理這塊內存,做法是按用戶參數分成小塊內存(一般大小爲第二維大小,直觀),填寫控制塊的相關信息,用戶調用

OSMemGet()

從指定的分區上取一個內存塊

OSMemPut();

釋放一個內存塊到指定的分區

OSMemQuery();

查詢某個指定的分區信息

OSMemNameSet();

設置某個指定分區的名

OSMemNameGet();

得到某個指定分區的名

事件管理部分

公用函數:

函數名

功能簡介

OS_InitEvenList()

初始化事件控制塊OS_EVENT鏈表,OSInit()內部調用

OS_EventWaitListInit()

僅僅清零指定事件控制塊的等待任務表,等待任務組,實際上我覺得這個函數沒有必要,因爲創建鏈表的時候所有ECB全部清0,被系統用過後返回回去時都是調用OS_EventTaskRdy或刪除事件函數,也都進行過清0,所以從空閒ECB鏈表取下來的ECB肯定等待表、組都是0

OS_EventTaskWait()

阻塞當前任務,登記就緒表、組,等待任務表、組,TCB指向ECB

OS_EventTaskRemove()

僅僅清指定ECB的等待任務表、組

OS_EventTaskRdy()

選出指定事件最高等待任務,讓其就緒,TCB域設置,如果沒有掛起的標誌還要清就緒組、表,ECB清相應的等待任務表、組

普通信號量:

函數名

功能簡介

OSSemCreate()

ECB鏈表上取一個ECB,設置事件類型、信號量數,清ECB指針域爲0OS_EventWaitListInit()清掉等待任務表、組。返回ECB*

OSSemSet()

一般不用,特殊情況非得用,必須要求沒有任務被阻塞該事件上。

OSSemDel()

有個op參數爲OS_DEL_NO+PEND只有在沒有任務等待是才允許

OS_DEL_ALWAYS即使有任務在等待都要清掉

首先標記是否有任務被阻塞,再判斷是那種操作類型

第一種:沒有任務等待,返回ECB到空閒鏈表,否則,錯誤返回

第二種:如果有任務等待時,OS_EventTaskRdy()讓所有任務以等到該事件方式全部就緒,然後返回ECB到空閒鏈表,調OS_Sched()。如果沒有任務等待,直接返回ECB到空閒鏈表。

OSSemPend()

請求信號量,如果該ECB有信號量,直接減1,返回;

如果沒有,設置TCB3個域,超時(如果是0,就不存在時間滴答1-0的過程所以死等到有事件發生爲止),OSTCBCur->Stat標誌標記上信號量,OSTCBCur->StatPend默認設置爲OS_STAT_PEND_OK,調用OS_EventTaskWait()

然後掉OS_Sched()任務切換

切換回來的時候:做個判斷是那種原因切換回來的

如果是超時到,一定是OSTimeDlyResume或中斷中的

OSTimeTick 2個函數做過處理,設置OSTCBCur->Stat(被清掉)和OSTCBCur->StatPend=OS_STAT_PEND_TO,但是ECB中的任務等待表、組還沒請所以調用OS_EventTaskRemove()

如果是放棄等待或正常等到了事件,那麼只需要標記返回信息,在切換回來的時候都已經被OS_EventTaskRdy()恢復過了。

最後統一把當前TCB3個相關域設置爲OS_STAT_RDYOS_STAT_PEND_OK、指向ECB的指針變量清0,退出該函數

OSSemAccept()

無等待的請求信號量,如果有信號量減1,沒有的話就直接退出,返回值是做減之前的信號量值,很明顯如果不是0說明有信號量。

OSSemPendAbort()

放棄信號量等待,參數是某個信號量類型ECB指針,如果有效的ECB沒有任務被阻塞,那麼直接退出,如果有任務被阻塞,參數opt 2中取值決定操作:

OS_PEND_OPT_BROADCASE 廣播方式,所有被阻塞的任務都被以放棄等待的方式就緒(實現方式如果就緒組不爲0就一直循環執行OS_EventTaskRdy函數,裏面有選最高優先級的功能)。

OS_PEND_OPT_NONE非廣播方式,OS_EventTaskRdy函數只被執行一次,選當前被該事件阻塞的最高優先級任務就緒。

因爲可能有比當前任務優先級更高的任務就緒所以調OSSched()

OSSemPost()

提交信號量,如果就緒組不爲0(有任務被阻塞)以等到事件的方式執行OS_EventTaskRdy,就緒被阻塞最高優先級的任務;如果爲0(沒有任務被阻塞),單純的讓信號量數加1就行了。

OSSemQuery()

和內存管理查詢類似,有個OS_SEM_DATA類型結構體,主要是存一下指定的某個信號量類型的ECB的就緒組、表和信號量數3個信息。

互斥信號量:

函數名

功能簡介

OSMutexCreate()

創建帶升級優先級的互斥類型信號量,如果指定的優先級未被佔用那麼把OSTCBPrioTbl[Pric]=1,佔着防止別人搶,爲了後面升級用。另外ECB的域OSEventCnt意義不同了,16位高8位爲待升級到的優先級,低8爲默認0Xff,表示還沒有任務佔用該互斥信號量,不是0xFF表示佔用該ECB的任務的優先級。另外ECB的域(void *OSEventPtr可以指向佔用他的任務的TCB指針了,源代碼中請求事件成功後只有ECB指向TCBTCB沒有指向ECB,TCBECB指針要指向ECB必須該任務被ECB事件阻塞起來,在該域初始化後默認爲0.

OSMutexDel

刪除指定互斥類型的ECB,首先判斷並標記是否有任務被阻塞因該事件,參數opt決定2種操作方式:

OS_DEL_NO_PEND(沒有被阻塞時才進行刪除操作)如果有事件被阻塞錯誤退出;如果沒有,首先找到升級優先級的TCB指針OSTCBPrioTbl[],內容從1清爲0(是1的原因:創建時如果成功就被1佔着,即使該ECB被某任務佔着,如果升級了一定會有阻塞任務,當然阻塞瞭如果比佔着的優先級低就只阻塞不升級,因爲沒有阻塞的任務所以一定不會升級使用,所以還是1),然後,初始化ECB的域後放到空閒的ECB鏈表中。

OS_DEL_ALWAYS任何情況都刪除該ECB

首先取得升級優先級和實際優先級(可能爲0xFF未用),如果確實發生了升級通過OSMutex_RdyAtPro(ptcb,prio)恢復爲prio優先級,然後如果有被阻塞的任務讓所有因該事件被阻塞的任務全部就緒,

讓升級優先級的OSTCBPrioTbl[]TCB指針清0,然後設置ECB的域,返回給ECB空閒鏈表,調用OSSched()

OSMutex_RdyAtPro()

讓升級了的任務優先級恢復原優先級並使之就緒,首先清0這個任務在就緒表、組,然後從新設置TCB中的相關域,設置全局變量

就緒表、組標記爲原優先級的就緒狀態,優先級指針表存TCB *

OSMutexPend()

 

爲什麼要優先級升級?

假如較低優先級的任務50請求互斥信號量A,請求到了,較高優先級3去請求發現被佔,只能掛起,必須等待優先級爲50的任務釋放,但是如果這個過程中像2030優先級的任務發生就緒,50的優先級必須讓出CPU,如果2030執行還未完,78優先級又就緒,導致50號任務遲遲得不到CPU,高優先級3相對重要的任務一直得不到執行,這是不允許的,所以採用優先級升級。優先級升級思想就是,創建互斥信號量時指定一個優先級2(假如),如果被低優先級50佔,有高優先級3請求,50的優先級會升到2,待釋放互斥信號量、或要刪除該信號量時才恢復成50,這樣可以避免2030這樣的任務搶CPU

請求互斥信號量,如果域OSEventCnt8位位0xFF表示未被任務佔用,設置ECB的相關域,返回。如果被其他任務佔用,從ECB中取得佔用的TCB指針和優先級,判斷只有當佔用ECB的優先級比升級優先級和當前請求優先級都低的時候才進行升級,然後就是升級操作(不需要升級就不操作這塊),然後操作和普通信號量類似都是設置TCB3個域,超時(如果是0,就不存在時間滴答1-0的過程所以死等到有事件發生爲止),OSTCBCur->Stat標誌標記上互斥信號量,OSTCBCur->StatPend默認設置爲OS_STAT_PEND_OK,

調用OS_EventTaskWait();然後掉OS_Sched()任務切換

切換回來的時候:做個判斷是那種原因切換回來的,

如果是超時到,一定是OSTimeDlyResume或中斷中的

OSTimeTick 2個函數做過處理,設置OSTCBCur->Stat(被清掉)和OSTCBCur->StatPend=OS_STAT_PEND_TO,但是ECB中的任務等待表、組還沒請所以調用OS_EventTaskRemove()

如果是放棄等待或正常等到了事件,那麼只需要標記返回信息,在切換回來的時候都已經被OS_EventTaskRdy()恢復過了。

最後統一把當前TCB3個相關域設置爲OS_STAT_RDYOS_STAT_PEND_OK、指向ECB的指針變量清0,退出該函數

升級操作具體:

首先判斷升級後的任務是否就緒並標記,如果就緒直接清就緒表、組,如果被掛起的,判斷TCBECB指針是否爲0推斷是否因事件而掛起,是0表示不是等待事件而掛起(有可能是被掛起函數直接掛起),非0表示因事件而掛起(如果是因事件而掛起,要清該事件的等待任務表、組),然後設置其TCB中相關域,按照原來的任務狀態設置就緒表、組,ECB任務等待表、組的值。恢復後的優先級的TCB指針表存該TCB指針。

OSMutexPost()

判斷是否進行了優先級升級如果有恢復,判斷是否有任務在等待該互斥信號量:

沒有的話就設置該ECB 2個參數OSEventCnt8位爲0xFFOSEventPtr=0,然後退出;如果有任務在等待,那麼就執行一次事件恢複函數OS_EventTaskRdy(),設置ECB 2 2個參數OSEventCnt8位爲任務優先級,OSEventPtr指向其TCB,然後發生任務切換,切換回來退出。

OSMutexAccept()

無等待請求,判斷是否互斥信號量被佔,如果沒被佔就使用,如果被佔就標記錯誤返回信息,直接退出,不需要升級操作。

OSMutexQuery()

查詢互斥信號,OS_MUTEX_DATA結構體中存5個域,等待任務表、組,原來優先級、升級優先級、邏輯變量是否進行過升級。

郵箱

函數名

功能簡介

OSMboxCreate()

創建郵箱,很簡單,取一個空閒的ECB,然後,標記事件類型,OSEvenPtr域爲存消息的地方,創建時參數pmsg作爲參數傳進,所以創建時可以有消息也可沒有,其餘域清0

OSMboxPend()

請求指定消息類型ECB消息,如果消息域OSEventPtr不爲空,取出消息,清零,消息作爲返回值返回;如果爲0,處理和普通信號量類似,設置當前TCB 3個域,等待時間域(如果是0,就不存在時間滴答1-0的過程所以死等到有事件發生爲止),OSTCBCur->Stat標誌標記上郵箱,OSTCBCur->StatPend默認設置爲OS_STAT_PEND_OK,調用OS_EventTaskWait()

然後掉OS_Sched()任務切換

切換回來的時候:做個判斷是那種原因切換回來的

如果是超時到,一定是OSTimeDlyResume或中斷中的

OSTimeTick 2個函數做過處理,設置OSTCBCur->Stat(被清掉)和OSTCBCur->StatPend=OS_STAT_PEND_TO,但是ECB中的任務等待表、組還沒請所以調用OS_EventTaskRemove(),注意設置返回存消息的變量pmsg0

如果是放棄等待,設置返回存消息的變量pmsg0

如果是正常等到了事件,從當前TCBOSTCBMsg域取消息存入返回值,和普通信號量不同吧,因爲提交信號量的函數把消息已經存入了當時處於等待任務表中最高優先級的該任務的TCB消息域中。

最後統一把當前TCB4個相關域設置爲OS_STAT_RDYOS_STAT_PEND_OK、指向ECB的指針變量清0TCB存消息的域清0(這是比普通信號量多出的操作),然後返回存消息的變量pmsg(有消息返回的就是消息,沒有返回的就是0),然後退出函數,到底是3種原因的那種原因通過傳入的參數變量來存儲信息,外部知道。

信號量和郵箱請求的區別:主要是返回處理,郵箱要多出對TCB的消息域清空處理,填寫存返回消息的變量。

OSMboxPost()

往指定郵箱類型ECB存消息,返回結果信息,分3種情況

情況1:等待任務表不爲0,表示有任務被事件掛起,要調用事件恢複函數OS_EventTaskRdy(),傳入的參數包括消息pmsg,以等到了消息的方式就緒。然後掉OS_Sched()任務切換,回來時退出

情況2:等待任務表爲0,但ECB存消息的域OSEventPtr不爲0,表示有消息在ECB,只能標記郵箱已滿,退出

情況3:沒有消息、也沒有任務在等,直接投到ECB中,情況13都是正常的提交,而情況2爲郵箱已滿錯誤退出

OSMboxDel()

通過參數opt判斷是無任務等待才刪除OS_DEL_NO_PEND

還是OS_DEL_ALWAYS強行刪除。首先判斷ECB的等待任務組是否爲0來知道是否有任務被掛起並通過變量標記,然後

第一種情況:判斷標記變量爲沒有任務等待就初始化ECB然後返還給ECB空閒鏈表,如果有就錯誤退出。

第二種情況:只要等待任務組不爲0就循環執行事件恢複函數OS_EventTaskRdy(),傳入的參數包括消息pmsg=0,以等到了消息的方式就緒。之後也是初始化ECB返還給空閒的鏈表,做個判斷如果標記變量表明確實之前有任務時被掛起的,那麼剛纔肯定有新任務被就緒了,所以執行OSSched()調度下。

OSMboxPendAbort()

講指定郵箱類型的處於等待被掛起的任務就緒,和普通信號量操作非常類似,首先通過該ECB的等待任務組判斷有沒有任務在等待,如果沒有就不需要回復直接退出。

如果有任務被掛起那麼通過操作變量opt(連操作碼都一樣)判斷:

OS_PEND_OPT_BROADCASE 廣播方式,所有被掛起的任務都被以放棄等待的方式就緒(實現方式如果就緒組不爲0就一直循環執行OS_EventTaskRdy函數,裏面有選最高優先級的功能),注意消息參數傳0

OS_PEND_OPT_NONE非廣播方式,OS_EventTaskRdy函數只被執行一次,選當前被該事件阻塞的最高優先級任務就緒,注意消息參數傳0

因爲可能有比當前任務優先級更高的任務就緒所以調OSSched()

OSMboxAccept()

無等待請求郵箱消息,很簡單如果沒有就錯誤退出,如果有就取ECB消息,並將ECB存消息的域清0,正常退出

OSMboxQuery()

OS_MBOX_DATA的填寫信息域3個存消息void* OSMsg,實際上是個地址,真正的消息在這個地址所指向的地方,任務等待表、組。

注意:消息這裏實際上只是void*指針,真正的消息是這個指針所指向的內容,具體存消息地方自己去定義啦,另外選void*好可以存任意類型的消息地址,如果信息量比較大可消息可能是結構體,使用消息的時候強制轉換爲該類型結構體地址。

 

消息隊列:

函數名

功能簡介

OS_QInit()

初始化消息隊列,和前面3種事件管理不同,消息隊列引入了新的數據結構隊列控制塊QCB,這些結構體也要建立空閒連,每個QCB可以管理一個消息隊列,OS_Q OSQTbl[OS_MAX_OS]ucos_ii。中定義,這個函數主要做的事是:把定義的這個結構體數組內容全部清0,然後構建鏈表,OSQFreeList指向空閒隊列鏈表表頭。

OSQCreate()

傳入的參數是用戶定義的存消息指針的地址的一塊內存區地址(可見是指針的指針類型,如果做加法操作實際地址浮動4個自己(32CPU,32位地址線))和存消息地址的尺寸。

創建消息隊列函數,首先要從ECB空閒鏈表中取一個ECB,從空閒的隊列鏈表中取一個QCB,ECB中類型設置爲消息隊列OS_EVENT_TYPE_Q,ECB的指針域指向QCB,QCB中設置相關域有:

OSQStart域賦值用戶定義的存消息指針的地址,第一個參數

OSQEnd域存通過第一、二參數算出來的存消息地址的末地址

OSQIn下次插入消息地址的地址

OSQOut下次取消息地址的地址

OSQSize隊列的尺寸,就是第二個參數

OSQEnteries以及當前存了多小個消息地址

當然是用ECB還是調用了OS_EventWaitListInit()ECB等待任務組、表

OSQPost()

提交消息到隊列,首先通過ECB判斷等待任務組是否爲0來得知是否有任務被該事件掛起:

若不爲0不需要把消息投到隊列而是直接投給當前等待任務表中最高優先級的任務,調用事件就緒函數

OS_EventTaskRdy(),傳入的參數包括消息pmsg,以等到了消息的方式就緒,然後就是調用OSSched()任務切換,返回來時正常退出。

若爲0,通過ECB的指針域獲得QCB指針來操作,如果當前的隊列有的消息數大於等於尺寸數(實際上最多就是等於)就錯誤返回,返回錯誤爲,隊列已經滿了,如果消息隊列未滿,就如下代碼:

非常經典的代碼:

*pq->OSQIn++=pmsg 消息存入插入消息的位置

*pq->OSQEntries++ 隊列消息數目加1

然後是if(pq->OSQIn==pq->OSQEnd)

{

pq->OSQIn=pq->OSQSart

}這段代碼很關鍵,說明是先進先出的循環隊列,只有插入點到了隊列地址的末尾就插到最前面去。最後退出

OSQPend()

請求指定消息隊列的消息,首先通過參數ECB指針獲得QCB指針,

判斷隊列中是否有消息,如果有取一個消息地址,存入返回變量中,然後對隊列調整,QCB相關參數設置,正常退出。

如果發現消息隊列沒有消息,處理和郵箱、普通信號量很類似了,

設置當前TCB 3個域,等待時間域(如果是0,就不存在時間滴答1-0的過程所以死等到有事件發生爲止),OSTCBCur->Stat標誌標記上隊列,OSTCBCur->StatPend默認設置爲OS_STAT_PEND_OK,調用OS_EventTaskWait()

然後掉OS_Sched()任務切換

切換回來的時候:和郵箱處理一模一樣,下面爲粘貼過來的

如果是超時到,一定是OSTimeDlyResume或中斷中的

OSTimeTick 2個函數做過處理,設置OSTCBCur->Stat(被清掉)和OSTCBCur->StatPend=OS_STAT_PEND_TO,但是ECB中的任務等待表、組還沒請所以調用OS_EventTaskRemove(),注意設置返回存消息的變量pmsg0

如果是放棄等待,設置返回存消息的變量pmsg0

如果是正常等到了事件,從當前TCBOSTCBMsg域取消息存入返回值,和普通信號量不同吧,因爲提交信號量的函數把消息已經存入了當時處於等待任務表中最高優先級的該任務的TCB消息域中。

最後統一把當前TCB4個相關域設置爲OS_STAT_RDYOS_STAT_PEND_OK、指向ECB的指針變量清0TCB存消息的域清0(這是比普通信號量多出的操作),然後返回存消息的變量pmsg(有消息返回的就是消息,沒有返回的就是0),然後退出函數,到底是3種原因的那種原因通過傳入的參數變量來存儲信息,外部知道。

信號量和郵箱請求的區別:主要是返回處理,郵箱要多出對TCB的消息域清空處理,填寫存返回消息的變量。

OSQDel()

刪除消息隊列中當前被掛起的任務,也是首先通過ECB的任務等待組來判斷並標記是否有任務在等待,

通過參數opt(和郵箱、互斥信號量掩碼都一樣)判斷是否無任務等待才刪除OS_DEL_NO_PEND

還是OS_DEL_ALWAYS強行刪除。

對於操作碼OS_DEL_NO_PEND

如果標記變量表面有任務在等待錯誤退出,如果沒有,初始化該QCB,,ECB分別放入各種的空閒鏈表中。

對於操作碼OS_DEL_ALWAYS

只要等待任務組不爲0就循環執行事件恢複函數OS_EventTaskRdy(),傳入的參數包括消息pmsg=0,以等到了消息的方式就緒,時間類型爲消息隊列這和郵箱幾乎一模一樣。之後就初始化該QCB,,ECB分別放入各種的空閒鏈表中。

做個判斷如果標記變量表明確實之前有任務時被掛起的,那麼剛纔肯定有新任務被就緒了,所以執行OSSched()調度下(如果沒有就不需要了)。一模一樣和郵箱操作。

 

比較郵箱和消息隊列刪除的不同:

唯一操作上的不同就是釋放是郵箱僅僅是講ECB放回ECB空閒鏈表,而隊列多了一個將QCB放回QCB空閒鏈表,其餘一模一樣。

OSQQuery()

查詢指定消息隊列信息,OS_Q_DATA的信息域5

即將被取到的消息void *OSMsg

消息隊列中消息數目

消息隊列尺寸

事件的任務等待表、組

事件標誌組:

數據結構介紹:事件標誌組這個東東比較怪,雖然掛着事件2個事,執行的是事件管理,但壓根就沒有用事件控制塊ECB來管理,完全是自己的一套數據結構,數據結構不同了,自然那些公用函數用不到了,自己有自己的掛起函數OS_FlagBlock()

和就緒函數OS_FlagTaskRdy()

關鍵數據結構式事件標誌組OS_FLAG_GRP,包含4個域,

INT8U OSFlagType事件標誌類型用的是填宏 OS_EVEN_TYPE_FLAG

Void *OSFlgWaitList 這個參數當在事件標誌組空閒鏈表時指向下一事件標誌組,

當在作爲真正管理事件時指向事件標誌節點鏈表的表頭

OS_FLAGS OSFlagFlags 事件標誌實際上,OS_FLAGS就是8位、16位或32位類型重定義

具體是那種取決你想怎麼管理,事件多小。

關鍵數據結構式事件標誌節點OS_FLAG_NODE包含6個域

節點的前後驅鏈指針,void *OSFlagNodeNext void *OSFlagNodePrev

指向等待任務的TCB void *OSFlagNodeTCB;

指向被管理的時間標誌組Void *OSlagNodeFlagGrp

OS_FLAGS OSFlagNodeFlags;該任務需要等待的哪些事件標誌組合

INT8U OSFlagNodeWaitType 取值是4種宏

OS_FLAG_WAIT_SET_ALL 表示請求的事件標誌位全部置1才能算等到事件

OS_FLAG_WAIT_ SET_ANY 表示請求的事件標誌位只要有任意一個1就算等到事件

OS_FLAG_WAIT_CLR_ALL 表示請求的事件標誌位全部爲0才能算等到事件

OS_FLAG_WAIT_CLR_ANY 表示請求的事件標誌位只要有任意一個0就算等到事件

函數名

功能簡介

OSFlagInit()

OSInit中被調用屬於內部函數,主要做的事就是清0所有實際標誌組數組內存,構建空閒事件標誌組鏈表,然後讓OSFlagFreeList指向鏈首。

OSFlagCreate()

從時間標誌組空閒鏈表表頭去一個GRP然後設置類型和初始事件

OS_FlagBlock()

內部函數,功能是設置TCB中相關域

3+1OSTCBCur->OSTCBStat|=OS_STAT_FLAG

OSTCBCur->OSTCBStatPend=OS_STAT_PEND_OK

OSTCBCur->OSTCBDly=timeout

這三個域設置和以前事件請求函數中一模一樣

OSTCBCur->OSTCBFlagNode=pnode指向標誌節點

這個功能類似於以前的TCB指向ECBOSEventPtr,以前是劃分到OS_EventTaskWait()事件掛起函數中的。另外就是清就緒組、表中的標誌。

可見這部分功能劃分不一樣了把原來的事件請求函數中的部分劃到了標誌組事件掛起函數中來了。

還有部分操作就是事件標誌節點的域設置,是節點指向TCB,實現真正的互指。

 

OS_EventTaskWait()比較:OS_EventTaskWait清就緒表、組,設置ECB的等待任務表,任務組,完成TCBECB的指向;

OS_FlagBlock進行TCB的本該在請求事件函數中設置的三項,然清就緒表、組,完成TCB中到事件標誌節點的指向,

並沒有清等待任務表任務組操作(因爲壓根就沒有用ECB),但是它還有其他操作,就是對事件標誌節點設置,使標誌節點插入事件標誌節點鏈表,並且同時指向TCB和事件標誌組GRP

 

問題來了既然沒有像ECB一樣的等待任務表、組,他是怎麼實現選擇、查找人任務並讓其就緒的,原來它是通過循環遍歷時間標誌節點,符合條件的任務都就緒,還有問題如果是消費類型的事件標誌在後面查找到的任務豈非很不公平?

OSFlagPend()

事件標誌組請求函數,首先通過判斷等待類型參數是否有清除標誌並記錄在另外定義的局部變量中,如果有消費類型的標誌在標記變量後要清掉,swich分支判斷:

A如果匹配OS_FLAG_WAIT_SET_ALL:

用請求的flags事件組合同GPR事件標記做與運算,如果結果和flags相同(即GPR包含flags的全部事件),等到了事件組,如果有清除標誌就清了GPR中的相應事件組,並把flags存入當前TCB的域OSTCBFlagsRdy中,然後正常退出。但與結果同flags不相等,那麼調用OS_FlagBlock(pgrp,&node,flags,wait_type,timeout),該任務就被掛起來了。

B如果匹配OS_FLAG_WAIT_SET_ANY

作與運算只要不爲0,就算成功,如果有消費標誌,把GPR中共同部分標誌事件記錄到當前TCB的域OSTCBFlagsRdy中,同時清掉它在GPR中的共同位清掉,退出。如果沒有匹配成功也該任務就被掛起來了OS_FlagBlock(pgrp,&node,flags,wait_type,timeout),

C如果匹配OS_FLAG_WAIT_CLR_ALL

GPR的事件標誌位取反同flags作與運算,如果和flags相同,表面匹配成功,如果又有消費標誌就把GPRflags的項全部置1,也記錄到TCB中,正常退出。如果匹配失敗也是任務就被掛起來了OS_FlagBlock(pgrp,&node,flags,wait_type,timeout),

D如果匹配OS_FLAG_WAIT_CLR_ANY

GPR的事件標誌位取反同flags作與運算,如果結果不爲0,說明原來GPR中對於flags至少有1位爲0,表示匹配成功,如果有消費標誌把共同的部分位置1,退出。如果匹配失敗,也是任務就被掛起來了OS_FlagBlock(pgrp,&node,flags,wait_type,timeout),

這樣switch4種情況就匹配完了,出來的時候必定是沒有匹配成功,將當前任務掛起來了,所以調用OSSched()

切換回來的時候:判斷是什麼原因切換回來,情況一:如果是超時到或放棄等待那麼,對OSTCBCur->OSTCBStat=OS_STAT_RDY

OSTCBCur->OSTCBStatPend=OS_STAT_PEND_OK

調用OS_FlagUnlink()使得OSTCBCurOSTCBFlagNode如果做了記錄就會清0,最後退出,退出返回的錯誤碼錶示到底是超時到還是放棄等待。情況二:正常等待了滿足條件的標誌,如果不是消費類型就,返回正常退出信息碼退出,如果有消費類型標誌,還得從TCB中域OSTCBFlagsRdy存的那個請求組合,該清00該置11

OS_FlagUnlink()

參數是處於鏈表中的事件標誌節點地址,功能是把該節點移除鏈表,如果使能了刪除事件標誌組的宏OS_TASK_DEL_EN,那麼還要把TCB指向該節點的域OSTCBFlagNode0

OSFlagDel()

首先就通過GPR的域OSFlagWaitList是否爲0來判斷有沒有任務處於等待狀態並通過標誌變量標記下。

通過參數opt(和郵箱、互斥信號量掩碼都一樣)判斷是否無任務等待才刪除OS_DEL_NO_PEND

還是OS_DEL_ALWAYS強行刪除。

對於操作碼OS_DEL_NO_PEND

如果標誌變量表明有任務在等待就退出,返回錯誤碼信息;如果沒有任務等待就把該GPR初始化然後放回到事件標誌組空閒鏈表中。

對於操作碼OS_DEL_ALWAYS

首先不管三七二十一,有沒有任務等待都取GPROSFlagWaitList

存入臨時變量,只要不爲0就循環遍歷下去,遍歷到節點就調用

OS_FlagTaskRdy(pnode,(OS_FLAGS)0);就緒完所有的任務之後初始化GPR,返回給空閒GPR鏈表如果有就緒的任務就調用函數

OS_SChed();

OS_FlagTaskRdy()

一般用在提交事件標誌組合刪除事件標誌組中常用,清楚該節點對於的任務等待事件標誌組這樣操作,並不表示他就沒有等待其他的操作,首先設置TCB4個相關域

OSTCBCur->OSTCBStat&=~OS_STAT_FLAG

OSTCBCur->OSTCBStatPend=OS_STAT_PEND_OK

OSTCBCur->OSTCBDly=0

賦值給域OSTCBFlagsRdy,顯然刪除的時候直接賦0的。

實際上有5個域還有TCB對節點指針在OS_FlagUnlink()0

如果發現清掉等待事件標誌組的實際狀態就變成OS_STAT_RDY那麼真的要設置就緒表、組,讓其調用一次OS_SChed();和事件就緒函數完全不相同

OSFlagAccept()

無等待請求,這個函數看起來複雜實際很簡單,首先標記是否是消費類型,然後按照4中匹配方式去請求事件,如果成功就按照OSFlagPend()ABCD4種方法去消費處理,如果沒有當然就直接返回錯誤碼退出撒。

OSFlagPost()

參數4個,指定事件標誌組GPR指針,OS_FLAG_GPR *pgrp

提交的位組合OS_FLAGS flags

提交方式INT8U opt是清除OS_FLAG_CLR

還是置位OS_FLAG_SET

如果是前者,那麼就把GPROSFlagFlags域同~flags與清除

後者,就同flags或運算置位

定義標記變量致使是否切換任務,默認不切換,通過GPR遍歷事件標誌節點,如果滿足條件就就緒,就緒函數只要調用過就把標誌變量標記爲要切換,出來的時候就通過標誌判斷需不需要發生切換,如果要OS_Sched(),之後退出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章