操作系統是很多人每天必須打交道的東西,因為在你打開電腦的一剎那,隨着bios自檢結束,你的windows系統已經開始運行了。如果問大家操作系統是什么?可能有的人會說操作系統就是windows,就是那些可以放大、縮小、移動的窗口。對曾經是計算機專業的朋友來說,這個答案還要稍微復雜一些,操作系統可能還有linux、unix、ios、sun solaris、aix等。如果再細化一點,對嵌入式工具比較解的朋友還會有新的補充,因為在他們看來,vxworks、eCos、ucos也都是操作系統,雖然它們好多系統連界面都沒有。
既然操作系統稱之為一個系統,那么它必然是由好多的部件組成的。有過linux嵌入式開發經驗的朋友都知道,要想使一個linux在arm芯片上真正跑起來,它必須有三個部分組成,即boot + 內核 + 文件系統。而真正內核的東西其實很少,也就是cpu初始化、線程調度、內存分配、文件系統、網絡協議棧、驅動這些部分組成。那么是不是所有的芯片都需要跑操作系統呢?我們可以舉個例子。
現在有一個簡單的溫度測量電路,它由三部分組成:1、單片機;2、溫度傳感器模塊;3、無線發射模塊。我們設計這么一個溫度測量電路其實就是一個目的,那就是為了實時獲取當前的溫度信息。那么,這么一個簡單的電路應該怎么設計程序呢?其實很簡單。
void sleep(int value) { int outer; int inner; for(; outer < value; outer++) { for(inner = 0; inner < 1000; inner++) ; } } void main() { while(1) { /* read temperature from port*/ sleep(1000); /* send temperature to wireless module */ sleep(1000); } }
如果我們需要cpu干的事情很少,甚至極端一點說只有一件事情,那么根本沒有設計操作系統的必要。我們設計出操作系統,主要是想在單位時間內完成幾件事情。打個比方來說,你完全可以在工作的時候一遍寫文檔、一遍收發電子郵件,偶爾還能開個小差休息一會。 所以操作系統就是為了共享資源而存在的。
認識操作系統的用途不難,關鍵是如何把操作系統用代碼寫出來。也許有人會跟你說,免費的代碼一大堆,Linux就不錯,你下載下來直接讀就好了。但是我告訴你,最新的Linux內核版本已經輕松的越過了3.0,整個代碼的長度遠在千萬行之上,你可能從哪看起都不知道。可能此時又有人不同意了,看不懂高版本的linux,可以看看linux低版本的代碼,0.11版本的代碼就不錯,因為趙炯就是怎么推薦的。我要說的是,0.11的代碼固然好,但是怎么編譯版本、怎么修改代碼、怎么構造文件系統、怎么跑起來是我們繞不過的一道難題。對於很多朋友來說,閱讀linux代碼尚且困難,更不要說后面還需要完成的一大攤子爛事了。
說了這么多,我們需要的的內核代碼是什么樣的?其實在我看來,很簡單。它只要滿足下面兩個條件就可以了,
(1)像用戶軟件一樣可以運行;
(2)像用戶軟件一樣可以單步調試。
要解決這些問題,對linux系統來說上不難解決。要解決os的運行和調試問題,關鍵就在於如何仿真中斷和實現os的任務切換。至於任務的開啟、運行和掛起,內存分配,互斥量,信號量,文件系統,tcp/ip協議棧,GUI操作,這些其實都是可以在linux上進行仿真和操作的,朋友們可以盡請放心。這部分的內容,我們會在以后的博客中陸續展開。
為了能夠更好地閱讀后面發表的博文,我建議你鞏固一下下面這些知識,這樣會對你的理解有很大的裨益。
(1)cpu 結構,了解中斷流程就行;
(2)linux 匯編語言;
(3)函數堆棧格式和內容;
(4)互斥量、信號量的使用方法;
(5)調度的基本策略;
(6)內存分配的基本方法;
(7)tcp/ip socket編程;
(8)gui編程方法,可以參考windows的方法;
(9)系統中的內存布局、編譯原理等等。
互斥量
學過操作系統課程的朋友對這個詞匯肯定不會很陌生。和信號量相比,互斥保護的資源一般是唯一的。也就是說,資源就一份,你占有了,我就沒有辦法占有;當然如果你釋放了,此時我就有機會占有了。
一切的一切看上去沒有什么問題。但是,我們都知道在實時嵌入式系統當中,線程之間的調度是嚴格按照優先級來進行調度。比方說,優先級為10的任務必須比優先級為11的任務優先得到調度。那么,有同學會問了,那優先級為11的任務什么時候才能得到調度呢,其實這個要求還是蠻苛刻的。要想優先級為11的任務得到調度,此時必須沒有優先級10的任務、或者任務pend到資源上了、或者自身delay、或者被人suspend了。否則,優先級為10的任務會這么一直運行下去。那,這和我們的互斥量有什么關系呢?請聽我一一講來。
我們假設現在有兩個任務都准備運行,分別人任務A、B,優先級依次是10、11。某一段時間后,優先級為10和優先級為11的任務都在嘗試獲取某個資源。本來按照優先級的先后順序,優先級為10的任務應該率先獲取資源,這都沒問題。但是,假設在嘗試獲取資源前,優先級為10的任務開了個小差,sleep一會,那么這個時候優先級為11的任務就可以開始運行了。等到優先級為10的任務蘇醒過來,想重新獲取資源的時候,驚訝地發現資源早就被別人給占了。因為資源目前只有一份,所以它只好把自己pend到等待隊列里面,慢慢等待好心人能快點把資源釋放出來。一切的一切看上去沒有什么問題,但是這卻和實時系統設計的初衷是相違背的。前面我們規定高優先級的任務必須優先得到運行的機會,而目前這種情況和我們的設計原則是背道而馳的。
當然這個問題很早就被大家發現了,大家也在嘗試不同的方法來解決。目前使用的比較多的就是兩種方法,一種是給互斥量設定一個優先級,另外一種就是對優先級進行繼承處理。看上去是兩種方法,其實目的只有一個,就是讓那些占有互斥量的thread提高優先級,趕快運行結束,把資源還給后面真正需要的人。看上去一切解決得都很完美,但是大家有沒有考慮過這樣一個問題,如果線程連續占有多個互斥量,優先級又該怎么處理?如果pend的任務被修改了優先級該怎么處理?如果這兩種方法一起被使用,那又該怎么處理?我想,這就是作者在后期對互斥量代碼進行重構的原因吧。當然了,上面討論的內容已經是比較深的了,大家可以看看早期互斥量是怎么設計的,慢慢來,這樣才會對作者的設計意圖更加了解一些。
老規矩,我們首先看看互斥量是怎么設計的,
typedef struct RAW_MUTEX { RAW_COMMON_BLOCK_OBJECT common_block_obj; RAW_U8 count; /*ponit to occupy task*/ RAW_TASK_OBJ *occupy; /*occupy task original priority*/ RAW_U8 occupy_original_priority; } RAW_MUTEX;
看上去互斥量的東西多一點,其實也還可以,只要大家明白了互斥量處理邏輯再回頭來看看這些東西的時候,認識就會更加深刻。我們看看,數據結構里面都有什么,
(1)通用互斥結構,這在前面信號量的時候已經介紹過一遍;
(2)計數,判斷資源是否還在;
(3)當前所屬的任務;
(4)該任務原來的優先級。
說好了基本結構,我們看看互斥量的構造、申請、釋放、刪除函數是怎么設計的,首先當然還是初始化函數,
RAW_U16 raw_mutex_create(RAW_MUTEX *mutex_ptr, RAW_U8 *name_ptr) { #if (RAW_MUTEX_FUNCTION_CHECK > 0) if (mutex_ptr == 0) return RAW_NULL_OBJECT; #endif /*Init the list*/ list_init(&mutex_ptr->common_block_obj.block_list); mutex_ptr->common_block_obj.block_way = 0; mutex_ptr->common_block_obj.name = name_ptr; /*No one occupy mutex yet*/ mutex_ptr->occupy = 0; /*resource is available at init state*/ mutex_ptr->count = 1; mutex_ptr->occupy_original_priority = 0; return RAW_SUCCESS; }
初始化的函數還是比較簡單的,主要做了下面的流程,
(1)初始化互斥結構的公共屬性,比如名字、阻塞方式等等;
(2)初始化當前資源數量;
(3)初始化占有資源的線程指針,還有就是線程的優先級。
創建了互斥量之后,我們就要看看互斥量是怎么申請的?代碼有點長,同學們可以心理調整一下了,
RAW_U16 raw_mutex_get(RAW_MUTEX *mutex_ptr, RAW_U32 wait_option) { RAW_U16 error_status; RAW_SR_ALLOC(); #if (RAW_MUTEX_FUNCTION_CHECK > 0) if (mutex_ptr == 0) { return RAW_NULL_OBJECT; } if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } #endif RAW_CRITICAL_ENTER(); /* mutex is available */ if (mutex_ptr->count) { mutex_ptr->occupy = raw_task_active; mutex_ptr->occupy_original_priority = raw_task_active->priority; mutex_ptr->count = 0; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*if the same task get the same mutex again, it causes deadlock*/ if (raw_task_active == mutex_ptr->occupy) { #if (CONFIG_RAW_ASSERT > 0) RAW_ASSERT(0); #endif RAW_CRITICAL_EXIT(); return RAW_MUTEX_DEADLOCK; } /*Cann't get mutex, and return immediately if wait_option is RAW_NO_WAIT*/ if (wait_option == RAW_NO_WAIT) { RAW_CRITICAL_EXIT(); return RAW_NO_PEND_WAIT; } /*system is locked so task can not be blocked just return immediately*/ if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } /*if current task is a higher priority task and block on the mutex *priority inverse condition happened, priority inherit method is used here*/ if (raw_task_active->priority < mutex_ptr->occupy->priority) { switch (mutex_ptr->occupy->task_state) { case RAW_RDY: /*remove from the ready list*/ remove_ready_list(&raw_ready_queue, mutex_ptr->occupy); /*raise the occupy task priority*/ mutex_ptr->occupy->priority = raw_task_active->priority; /*readd to the ready list head*/ add_ready_list_head(&raw_ready_queue, mutex_ptr->occupy); break; case RAW_DLY: case RAW_DLY_SUSPENDED: case RAW_SUSPENDED: /*occupy task is not on any list, so just change the priority*/ mutex_ptr->occupy->priority = raw_task_active->priority; break; case RAW_PEND: /* Change the position of the task in the wait list */ case RAW_PEND_TIMEOUT: case RAW_PEND_SUSPENDED: case RAW_PEND_TIMEOUT_SUSPENDED: /*occupy task is on the block list so change the priority on the block list*/ mutex_ptr->occupy->priority = raw_task_active->priority; change_pend_list_priority(mutex_ptr->occupy); break; default: RAW_CRITICAL_EXIT(); return RAW_INVALID_STATE; } } /*Any way block the current task*/ raw_pend_object(&mutex_ptr->common_block_obj, raw_task_active, wait_option); RAW_CRITICAL_EXIT(); /*find the next highest priority task ready to run*/ raw_sched(); /*So the task is waked up, need know which reason cause wake up.*/ error_status = block_state_post_process(raw_task_active, 0); return error_status; }
這段代碼其實開頭都還好,關鍵是末尾要結束的時候有一段代碼比較費解。我想,這就是我前面說過的優先級反轉問題。為了解決這一問題,在rawos版本中采取了優先級繼承的方法。我們還是詳細看一下邏輯本身是怎么樣的,
(1)判斷參數合法性;
(2)判斷資源是否可取,如果可取,則在記錄當前線程和優先級后返回;
(3)如果資源被自己重復申請,返回;
(4)如果線程不願等待,返回;
(5)如果此時禁止調度,返回;
(6)如果此時優先級大於互斥量占有者的優先級,分情況處理
a)占有者處於ready的狀態,那么修改它的優先級,重新加入調度隊列;
b)占有者處於sleep的狀態,直接修改優先級即可;
c)占有者也被pend到別的資源上面了,那么修改那個資源的pend列表,可能設計到調度順序問題。
(7)線程把自己pend到互斥量等待隊列上面;
(8)線程調用系統調度函數,切換到其他線程運行;
(9)線程再次得到運行的機會,從task獲取結果后返回。
基本上上面的介紹算得上是很詳細了,那么互斥量的釋放基本上是一個逆操作的過程,朋友也可以思考一下應該怎么解決才好,
RAW_U16 raw_mutex_put(RAW_MUTEX *mutex_ptr) { LIST *block_list_head; RAW_SR_ALLOC(); #if (RAW_MUTEX_FUNCTION_CHECK > 0) if (mutex_ptr == 0) { return RAW_NULL_OBJECT; } #endif block_list_head = &mutex_ptr->common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*Must release the mutex by self*/ if (raw_task_active != mutex_ptr->occupy) { RAW_CRITICAL_EXIT(); return RAW_MUTEX_NOT_RELEASE_BY_OCCYPY; } /*if no block task on this list just return*/ if (is_list_empty(block_list_head)) { mutex_ptr->count = 1; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*if priority was changed, just change it back to original priority*/ if (raw_task_active->priority != mutex_ptr->occupy_original_priority) { remove_ready_list(&raw_ready_queue, raw_task_active); raw_task_active->priority = mutex_ptr->occupy_original_priority; add_ready_list_end(&raw_ready_queue, raw_task_active); } /* there must have task blocked on this mutex object*/ mutex_ptr->occupy = list_entry(block_list_head->next, RAW_TASK_OBJ, task_list); /*the first blocked task became the occupy task*/ mutex_ptr->occupy_original_priority = mutex_ptr->occupy->priority; /*mutex resource is occupied*/ mutex_ptr->count = 0; /*Wake up the occupy task, which is the highst priority task on the list*/ raw_wake_object(mutex_ptr->occupy); RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; }
和之前的信號量釋放相比,互斥量的釋放要復雜一切,關鍵就在於修改優先級的問題。我們來梳理一下,
(1)判斷參數合法性;
(2)判斷線程是否為互斥量的占有線程,不是則返回;
(3)判斷等待隊列是否為空,為空的話則返回;
(4)判斷占有任務的優先級有沒有發生變化,如果有則需要重新修改優先級,重新加入調度隊列中;
(5)選擇下一個可以調度的線程;
(6)函數返回。
說了這么些,就剩下最后一個刪除互斥量了,大家再接再厲,一起去學習。
RAW_U16 raw_mutex_delete(RAW_MUTEX *mutex_ptr) { LIST *block_list_head; RAW_TASK_OBJ *mutex_occupy; RAW_SR_ALLOC(); #if (RAW_MUTEX_FUNCTION_CHECK > 0) if (mutex_ptr == 0) { return RAW_NULL_OBJECT; } #endif block_list_head = &mutex_ptr->common_block_obj.block_list; RAW_CRITICAL_ENTER(); mutex_occupy = mutex_ptr->occupy; /*if mutex is occupied and occupy priority is not the original priority*/ if ((mutex_occupy) && (mutex_occupy->priority != mutex_ptr->occupy_original_priority)) { switch (mutex_occupy->task_state) { case RAW_RDY: /*remove from the ready list*/ remove_ready_list(&raw_ready_queue, mutex_ptr->occupy); /*raise the occupy task priority*/ mutex_occupy->priority = mutex_ptr->occupy_original_priority; /*readd to the ready list head*/ add_ready_list_end(&raw_ready_queue, mutex_ptr->occupy); break; case RAW_DLY: case RAW_SUSPENDED: case RAW_DLY_SUSPENDED: /*occupy task is not on any list, so just change the priority*/ mutex_occupy->priority = mutex_ptr->occupy_original_priority; break; case RAW_PEND: case RAW_PEND_TIMEOUT: case RAW_PEND_SUSPENDED: case RAW_PEND_TIMEOUT_SUSPENDED: /*occupy task is on the block list so change the priority on the block list*/ mutex_occupy->priority = mutex_ptr->occupy_original_priority; change_pend_list_priority(mutex_occupy); break; default: RAW_CRITICAL_EXIT(); return RAW_STATE_UNKNOWN; } } /*All task blocked on this queue is waken up*/ while (!is_list_empty(block_list_head)) { delete_pend_obj(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list)); } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; }
互斥量的操作在實際情形下未必是存在的,所以作者在設計的時候添加了一個編譯宏。不過刪除所做的工作也不難理解,一個是處理好當前占有者的關系,一個是處理好等待隊列的關系。我們來細看一下流程,
(1)判斷當前參數合法性;
(2)判斷占有者的情況,修改任務優先級,這里的情形和上面申請互斥量的處理方式是一樣的,不再贅述;
(3)喚醒所有的等待線程,如果線程已經suspend掉了,那么繼續suspend;
(4)調度到其他線程,防止有優先級高的任務已經被釋放出來了;
(5)函數返回,結束。
信號量
之前因為工作的原因,操作系統這塊一直沒有繼續寫下去。一方面是自己沒有這方面的經歷,另外一方面就是操作系統比較復雜和瑣碎,調試起來比較麻煩。目前在實際項目中,使用的實時操作系統很多,很多國內的朋友也寫過操作系統,有些項目現在還在維護和修改中,這是十分難得的。就我知道和熟悉的就有三個系統,比如
(1)RT-THREAD
(2)RAW-OS
(3)ClearRTOS
這里有比較介紹一下,這三個系統是國內的三位朋友開發的。其中rt-thread時間比較久一點,模塊也比較全,bsp、cpu、fs、lwip、gui等輔助的代碼也比較多,有興趣的朋友可以到網站上面下載代碼看一看。raw-os是我今年才發現的一個實時系統,從網站的注冊時間和軟件版本號上來看,系統開發的時間不是很長,不過整個系統代碼的結構非常清晰,是我重點推薦閱讀的代碼。如果朋友們自己download下來,好好看一下其中的代碼,肯定會有不少的收獲。最后一個代碼是作者李雲在編寫《專業嵌入式軟件開發》這本書的時候,為了說明os的基本原理而開發的軟件,前后設計了線程、互斥、內存、定時器、驅動框架等內容,值得一讀。
當然有了這么多優秀的代碼,我覺得現在自己的工作就不是重新造一個車輪了,而是和大家分享這些優秀的代碼是如何設計的。理解代碼本身不是目的,關鍵是理解代碼背后的基本思路。就我個人看過來,rt-thread和raw-os都可以用來學習,不過raw-os更好一些,主要是因為作者將raw-os移植到的vc上面,學起來也十分方便,要是個人在使用過程中有什么疑問,可以通過郵件和作者及時交流。不過由於raw-os的版本在一直在update之中,所以部分代碼在前后稍微有點差別,不過這些都不是重點,暫時不了解的內容可以通過后面的了解和學習逐步掌握,不會成為太大的障礙。
就像今天的題目一樣,我們重點介紹一下信號量的設計原理。首先看一下信號量的數據結構是怎么樣的,
typedef struct RAW_SEMAPHORE
{
RAW_COMMON_BLOCK_OBJECT common_block_obj;
RAW_U32 count;
} RAW_SEMAPHORE;
這些代碼都是從raw-os上面摘抄下來的,這個版本是0.94版本,和最新的0.96c版本有點差別。首先分析一下信號量的基本結構,其實非常簡單,就兩個變量,其中comm_block_obj是一個通用類型,記錄了當前結構的名稱、類型和阻塞隊列,而count就是計數,判斷是否還有釋放的資源。
說到了信號量的操作,無非就是信號量的創建、獲取、釋放、刪除操作,當然這里作者考慮的比較詳細,在信號量釋放的時候還分成了 WAKE_ONE_SEM和WAKE_ALL_SEM兩種類型。意思很簡單,就是當信號量來臨的時候是喚醒一個等待線程呢,還是喚醒所有的等待線程呢,就是這么回事。下面,我們就按照順序介紹這幾個函數,首先是創建函數,
RAW_U16 raw_semaphore_create(RAW_SEMAPHORE *semaphore_ptr, RAW_U8 *name_ptr, RAW_U32 initial_count)
{
#if (RAW_SEMA_FUNCTION_CHECK > 0)
if (semaphore_ptr == 0) {
return RAW_NULL_OBJECT;
}
if (initial_count == 0xffffffff) {
return RAW_SEMOPHORE_OVERFLOW;
}
#endif
/*Init the list*/
list_init(&semaphore_ptr->common_block_obj.block_list);
/*Init resource*/
semaphore_ptr->count = initial_count;
semaphore_ptr->common_block_obj.name = name_ptr;
semaphore_ptr->common_block_obj.block_way = 0;
return RAW_SUCCESS;
}
看着初始化函數,我們發現信號量的初始化其實也非常簡單,基本工作主要有:
(1)判斷參數合法性;
(2)初始化阻塞隊列、名稱等;
(3)初始化信號量的計數。
說完了這些,我們看看信號量的獲取是怎么完成的,代碼可能長度稍微長一些,不過也不用太緊張,
RAW_U16 raw_semaphore_get(RAW_SEMAPHORE *semaphore_ptr, RAW_U32 wait_option)
{
RAW_U16 error_status;
RAW_SR_ALLOC();
#if (RAW_SEMA_FUNCTION_CHECK > 0)
if (semaphore_ptr == 0) {
return RAW_NULL_OBJECT;
}
if (raw_int_nesting) {
return RAW_NOT_CALLED_BY_ISR;
}
#endif
RAW_CRITICAL_ENTER();
if (semaphore_ptr->count) {
semaphore_ptr->count--;
RAW_CRITICAL_EXIT();
return RAW_SUCCESS;
}
/*Cann't get semphore, and return immediately if wait_option is RAW_NO_WAIT*/
if (wait_option == RAW_NO_WAIT) {
RAW_CRITICAL_EXIT();
return RAW_NO_PEND_WAIT;
}
if (raw_sched_lock) {
RAW_CRITICAL_EXIT();
return RAW_SCHED_DISABLE;
}
raw_pend_object(&semaphore_ptr->common_block_obj, raw_task_active, wait_option);
RAW_CRITICAL_EXIT();
raw_sched();
error_status = block_state_post_process(raw_task_active, 0);
return error_status;
}
信號量的獲取情況比較復雜一些,這在長度上也體現出來了。不過沒關系,我們一步一步看函數做了什么,
(1)判斷參數合法性;
(2)判斷當前函數是否處於中斷處理的流程中,如果是選擇返回;
(3)判斷當前count是否為0,如果不為 0,則減1返回;
(4)如果當前count是0,且線程不願意等待,那么選擇返回;
(5)如果當前禁止調度,那么依然選擇返回;
(6)當前線程將自己掛起,從ready隊列中刪除,把自己pend到信號量的阻塞隊列中;
(7)阻塞的線程再次獲得了運行的機會,我們從task數據結構獲得返回結果,此時也不一定是因為獲得了資源的緣故哦。
上面的get函數看上去比較復雜,但是所有的同步函數基本上都是這樣設計的,看多了反而有一種八股文的感覺。剛開始看的同學可能覺得不是很習慣。不要緊,每天多看兩眼,時間長了就ok了。好了,接着我們繼續去看看信號量的釋放函數是怎么處理的,大家做好心理准備哦,
static RAW_U16 internal_semaphore_put(RAW_SEMAPHORE *semaphore_ptr, RAW_U8 opt_wake_all)
{
LIST *block_list_head;
RAW_SR_ALLOC();
#if (RAW_SEMA_FUNCTION_CHECK > 0)
if (semaphore_ptr == 0) {
return RAW_NULL_OBJECT;
}
#endif
block_list_head = &semaphore_ptr->common_block_obj.block_list;
RAW_CRITICAL_ENTER();
/*if no block task on this list just return*/
if (is_list_empty(block_list_head)) {
if (semaphore_ptr->count == 0xffffffff) {
RAW_CRITICAL_EXIT();
return RAW_SEMOPHORE_OVERFLOW;
}
/*increase resource*/
semaphore_ptr->count++;
RAW_CRITICAL_EXIT();
return RAW_SUCCESS;
}
/*wake all the task blocked on this semphore*/
if (opt_wake_all) {
while (!is_list_empty(block_list_head)) {
raw_wake_object(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list));
}
}
else {
/*Wake up the highest priority task block on the semaphore*/
raw_wake_object(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list));
}
RAW_CRITICAL_EXIT();
raw_sched();
return RAW_SUCCESS;
}
看上去,信號量的釋放函數也比較長,不過只要有耐心,都是可以看明白的,我們就來具體分析一下,
(1)判斷參數的合法性;
(2)判斷當前是否有等待隊列,如果沒有,則count自增,函數返回,當然如果count達到了0xffffffff也要返回,不過概率極低;
(3) 當前存在等待隊列,根據opt_wake_all的要求是喚醒一個線程還是喚醒所有的線程;
(4)調用系統調度函數,讓高優先級任務及時得到運行的機會;
(5)當前線程再次得到運行的機會,函數返回。
有了上面的講解,我們發現os的代碼其實也沒有那么恐怖。所以,請大家一鼓作氣,看看信號量是怎么刪除的吧,
RAW_U16 raw_semaphore_delete(RAW_SEMAPHORE *semaphore_ptr)
{
LIST *block_list_head;
RAW_SR_ALLOC();
#if (RAW_SEMA_FUNCTION_CHECK > 0)
if (semaphore_ptr == 0) {
return RAW_NULL_OBJECT;
}
#endif
block_list_head = &semaphore_ptr->common_block_obj.block_list;
RAW_CRITICAL_ENTER();
/*All task blocked on this queue is waken up*/
while (!is_list_empty(block_list_head)) {
delete_pend_obj(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list));
}
RAW_CRITICAL_EXIT();
raw_sched();
return RAW_SUCCESS;
}
信號量刪除的工作其實很少,也很簡單,同樣我們也來梳理一下,
(1)判斷參數合法性;
(2)喚醒阻塞隊列中的每一個線程;
(3)調用系統調度函數,因為高優先級的任務很有可能剛剛從阻塞隊列中釋放出來;
(4)當前線程再次運行,函數返回。
通過上面幾個函數的講解,我們發現關於os互斥部分的代碼其實也不復雜。只要對系統本身和中斷有一些了解,其實代碼都是可以看懂的。當然,上面的代碼我們還是講的比較粗糙,所以有些細節還是要補充一下,
(1)全局變量操作的函數必須在關中斷的情況下進行操作;
(2)實時系統的搶占是每時每刻都在進行的,比如中斷返回時、信號量釋放時、調用延時函數、調用調度函數的時候,所以大家心中要有搶占的概念;
(3)互斥函數中大量使用了鏈表的結構,建議大家好好掌握鏈表的相關算法;
(4)關於os的代碼一定要多看、多思考、多練習才會有進步和提高,紙上得來終覺淺、絕知此事要躬行。
參考blog:https://blog.csdn.net/feixiaoxing/article/details/7976857