本人從事zigbee的研發(fā)工作已接有多年,在這幾年的技術(shù)之路上收獲了很多,也失去了很多。幾年之后,離開了zigbee研發(fā)崗位,決定寫點(diǎn)什么作為紀(jì)念,另外也希望給后來的學(xué)習(xí)zigbee的同盟們留下一點(diǎn)“磚塊”。 不復(fù)雜的小系統(tǒng)一般設(shè)計(jì)成如圖1所示的樣子,這種系統(tǒng)一般稱作為前后臺(tái)系統(tǒng)或超循環(huán)系統(tǒng)。整個(gè)應(yīng)用程序(整個(gè)工程)是一個(gè)無限循環(huán),對(duì)于應(yīng)用中的具體操作既是在這個(gè)無限循環(huán)中不停地調(diào)用各個(gè)相應(yīng)的函數(shù)來實(shí)現(xiàn),這部分可以叫做后臺(tái)行為。后臺(tái)也叫做任務(wù)級(jí)。前臺(tái)也叫中斷級(jí)。要求實(shí)時(shí)響應(yīng)的操作一般由中斷服務(wù)來保證。如果前后臺(tái)程序需要修改,原先整體的循環(huán)會(huì)被打亂的凌亂不堪,循環(huán)的時(shí)序也會(huì)受到影響,從而也就不能保證修改后的程序繼續(xù)能夠正常無誤工作。這樣就給中大型程序的升級(jí),應(yīng)用的增加,工程的管理帶來的不可避免的麻煩,最壞的情況就是一個(gè)程序的升級(jí)相當(dāng)于一個(gè)工程的重新編寫。 (原文件名:圖片1.JPG) 圖 1 此時(shí)就需要引進(jìn)操作系統(tǒng)了,有了操作系統(tǒng)后,整個(gè)工程可以被劃分成許多小的模塊(任務(wù))互相協(xié)調(diào)合作共同完成整個(gè)項(xiàng)目,程序修改或者升級(jí)時(shí)只需要修改對(duì)應(yīng)的任務(wù)即可完成,不必改動(dòng)整個(gè)工程,這是筆者認(rèn)為在使用操作系統(tǒng)時(shí)最顯而易見的好處。另外,使用操作系統(tǒng)使得程序的各個(gè)功能模塊化,利用各種調(diào)度方法(不同操作系統(tǒng)可能不同)實(shí)現(xiàn)整個(gè)工程,從而也使得大型的程序、雜亂無章的循環(huán)變的盡然有序,程序運(yùn)行更加安全可靠?,F(xiàn)在在嵌入式領(lǐng)域被廣泛關(guān)注和認(rèn)同的操作系統(tǒng)有uCOS,linux,windowsCE,UCLINUX,等等,據(jù)筆者了解(才疏學(xué)淺),應(yīng)用于8位單片機(jī)并得到了實(shí)踐證明的暫時(shí)聽說了UCOS以及RTX51就是KEIL公司針對(duì)51開發(fā)的一個(gè)小型RTOS。RTOS只提供了庫接口函數(shù),對(duì)于學(xué)者和開發(fā)者并不開源,所以使用者和探討者的人數(shù)相對(duì)于UCOS并不多,UCOS是一款開源的實(shí)時(shí)操作系統(tǒng), UCOS一直受到學(xué)者和開發(fā)者的青睞,但是由于任務(wù)中加入ucos操作系統(tǒng)后編譯所占用的code區(qū)以及XDATA區(qū)增大較多,不適合移植到小容量的單片機(jī)上使用。筆者有幸于2008年接觸到TI公司應(yīng)用與zigbee協(xié)議棧的一款非搶占操作系統(tǒng),下面將其叫做LTOS(little TI OS or LiTie OS),由于它應(yīng)用簡單方便,,開發(fā)項(xiàng)目穩(wěn)定可靠,便于理解和學(xué)習(xí),使得操作系統(tǒng)初學(xué)者可以很容易的對(duì)操作系統(tǒng)整體有一個(gè)全面的了解,所以筆者決定將該操作系統(tǒng)移植出來,放入STC12C60S2單片機(jī)中使用。記錄下該文檔拋磚引玉,使更多人能更快地理解該操作系統(tǒng)并得到的該小型操作系統(tǒng)更好的發(fā)展和應(yīng)用。 一:對(duì)TI操作系統(tǒng)初步分析 1.1任務(wù)、事件、消息 剛拿到TI-MAC1.2.1時(shí)被該程序搞蒙了,TI-MAC1.2.1的程序竟然是基于他們自己編寫的OS操作系統(tǒng)運(yùn)行的。說到LTOS操作系統(tǒng),不得不說說他的任務(wù)、事件和消息機(jī)制。據(jù)筆者理解,任務(wù)就是程序編寫員將預(yù)實(shí)現(xiàn)的功能分成不同的模塊,這些模塊之間分工明確并且相互合作,共同完成程序員預(yù)完成的某個(gè)項(xiàng)目的整個(gè)功能;事件是這些任務(wù)中要處理的某個(gè)小功能的口令,比如老師說張三你站起來或坐下,張三聽到站起來就站起來,聽到坐下就坐下,同樣道理,某個(gè)任務(wù)得到處理器后,先判斷自己的事件是什么,如果是URAT_Writer則任務(wù)知道是串口寫;而如果是LED_STOP,則任務(wù)知道是小燈停;消息是任務(wù)之間相互通信的方式,任務(wù)之間的數(shù)據(jù)傳輸一是通過消息來實(shí)現(xiàn),二是通過延時(shí)設(shè)置任務(wù)來完成。任務(wù)內(nèi)部消息就是一個(gè)系統(tǒng)事件。 在進(jìn)入LTOS系統(tǒng)前,先利用osal_init_system()等初始化程序?qū)⒉僮飨到y(tǒng)初始化,主要功能就是內(nèi)存分配函數(shù)的初始化、定時(shí)器的初始化以及為任務(wù)的加入。任務(wù)初始化時(shí)將任務(wù)按預(yù)先設(shè)定分配了不同的優(yōu)先級(jí),LTOS系統(tǒng)按照賦值的優(yōu)先級(jí)順序從高到底不停的掃描這些任務(wù),查看他們是否被設(shè)置了事件,如果該任務(wù)被設(shè)置了事件,則操作系統(tǒng)將馬上進(jìn)入這個(gè)該任務(wù)對(duì)應(yīng)的pFnEventProcessor(處理任務(wù)函數(shù))中執(zhí)行該任務(wù)中的事件。 初始化和任務(wù)加入完成之后就開始進(jìn)入任務(wù)調(diào)度函數(shù)osalNextActiveTask( void )。進(jìn)入任務(wù)調(diào)度函數(shù)首先掃描定時(shí)器和串口,查看定時(shí)器和串口的變化,然后利用osalNextActiveTask()函數(shù)查看任務(wù)列表中是否有被設(shè)置了事件的任務(wù)。以下是該函數(shù)的原型: osalTaskRec_t *osalNextActiveTask( void ) { osalTaskRec_t *srchTask; // Start at the beginning srchTask = tasksHead; // When found or not while ( srchTask ) { if (srchTask->events) { //判斷最高優(yōu)先級(jí)中有無事件 return srchTask; } srchTask = srchTask->next; } return NULL; } 進(jìn)入該函數(shù)后,讓srchTask指向任務(wù)列表的頭(tasksHead),然后利用if(srch->events)查看改任務(wù)中是否有事件,如果沒有事件則srchTask指向任務(wù)鏈表的下一個(gè)元素,繼續(xù)以上的工作,一旦查到某個(gè)任務(wù)有事件就返回任務(wù)的任務(wù)ID。然后利用retEvents = (activeTask->pfnEventProcessor)( activeTask->taskID, events )函數(shù)進(jìn)入改任務(wù)中執(zhí)行事先編寫的函數(shù)。值得注意的是任務(wù)在執(zhí)行完成之后一定要記得將任務(wù)的事件清空,不然返回的retEvents會(huì)跟activeTask->events相或(activeTask->events |= retEvents),假如該任務(wù)的優(yōu)先級(jí)最高,這樣每當(dāng)程序進(jìn)入下一次的調(diào)度時(shí)總會(huì)進(jìn)入該任務(wù)中(因?yàn)樵撊蝿?wù)的事件不曾清空),這樣其余的任務(wù)即使有置位的事件也不會(huì)被執(zhí)行。具體的分析將在下一章中利用實(shí)驗(yàn)詳細(xì)講解。 1.2加入自己的任務(wù) 上一節(jié)中講到了操作系統(tǒng)的基本運(yùn)行方式,運(yùn)行中涉及的任務(wù)的初始化和運(yùn)行,下面主要介紹如何加入自己的任務(wù)。 在TI-MAC1.2.1中全部的任務(wù)加入是利用osalAddTasks( void )在初始化時(shí)完成的,該函數(shù)屬于應(yīng)用層和OS之間的接口函數(shù),而單個(gè)的任務(wù)加入就在osalAddTasks( void )函數(shù)中利用osalTaskAdd (Task_Init,Task_ProcessEvent, OSAL_TASK_PRIORITY)加入的,其中Task-Init(uint8 task_id)是任務(wù)的初始化函數(shù),該函數(shù)中系統(tǒng)為任務(wù)分配特定的唯一的ID號(hào);Task_ProcessEvent(uint8 task_id, uint16 events)是任務(wù)的執(zhí)行函數(shù),該函數(shù)中程序表達(dá)的就是要實(shí)現(xiàn)的功能;最后一個(gè)參數(shù)OSAL_TASK_PRIORITY是任務(wù)的優(yōu)先級(jí)別。 下面以一個(gè)小的實(shí)驗(yàn)例子來說明自己的任務(wù)的加入: 該實(shí)驗(yàn)實(shí)現(xiàn)一個(gè)簡單的功能——LED小燈的閃爍,首先是任務(wù)的初始化函數(shù) void LED1Init(uint8 taskId) { LED1Id=taskId; } taskId是OS系統(tǒng)為該函數(shù)分配的任務(wù)ID,利用該初始化函數(shù)將taskId賦值給LED1Id。 接下來就是任務(wù)的執(zhí)行函數(shù)的編寫 uint16 LED2_ProcessEvent(uint8 taskId, uint16 events) { if(events&MSA_SEND_EVENT) { HalLedSet (HAL_LED_2, HAL_LED_MODE_ON); delay(5000); HalLedSet (HAL_LED_2, HAL_LED_MODE_OFF); osal_start_timerEx(LED1Id,MSA_SEND_EVENT,100); return (events ^ MSA_SEND_EVENT); } return 0; } 最后就利用osalTaskAdd()函數(shù)將該任務(wù)加入到操作系統(tǒng)中去 void osalAddTasks( void ) { /* HAL Drivers Task */ osalTaskAdd (Hal_Init, Hal_ProcessEvent, OSAL_TASK_PRIORITY_LOW); osalTaskAdd(LED1Init,LED1_ProcessEvent,OSAL_TASK_PRIORITY_MED ); } 二:TI操作系統(tǒng)的運(yùn)行方式 OS操作系統(tǒng)不是一個(gè)完整的操作系統(tǒng),任務(wù)與任務(wù)之間也不能搶占,只是簡單地利用定時(shí)器管理和任務(wù)事件設(shè)置來周而復(fù)始地調(diào)用任務(wù),與其他的操作系統(tǒng)一樣(ucos,linux)它同樣需要定時(shí)器來確定一個(gè)系統(tǒng)時(shí)鐘tick,在cc2430中是占用一個(gè)硬件定時(shí)器來定時(shí)。每次一個(gè)任務(wù)執(zhí)行完后系統(tǒng)都會(huì)從高優(yōu)先級(jí)到低優(yōu)先級(jí)掃描任務(wù)是否被設(shè)置了事件,當(dāng)有任務(wù)被設(shè)置事件時(shí),就馬上進(jìn)入該任務(wù)中。OS操作系統(tǒng)的思想是:保證高優(yōu)先級(jí)的任務(wù)有事件時(shí)最先得到處理器。在任務(wù)的優(yōu)先級(jí)賦值時(shí),MAC Task一般賦最高的優(yōu)先級(jí),這樣是為了使得無線電的發(fā)射和接受提高到最重要的地位。 2.1一些主要的函數(shù) OS操作系統(tǒng)的運(yùn)行依靠許多重要的函數(shù),下面介紹一些主要函數(shù)以及其中的參數(shù)。 1 byte osal_set_event(byte task_id,uint16 event_flag) 說明:該函數(shù)與任務(wù)的運(yùn)行至關(guān)重要,它是為任務(wù)設(shè)置事件的函數(shù),該函數(shù)被利用為任務(wù)設(shè)置事件標(biāo)示符。 參數(shù):task_id:欲設(shè)置事件的這個(gè)任務(wù)ID,一旦寫入,將為該任務(wù)ID的任務(wù)設(shè)置事件。 event_falg:事件標(biāo)示,該事件標(biāo)示占2個(gè)字節(jié),每個(gè)位指定一個(gè)事件,只能有一個(gè)系統(tǒng)事件,其余的事件位在接受任務(wù)中自行定義。 2 osal_start_timer(uint16 event_id,uint16 timeout_value) 說明:該函數(shù)啟動(dòng)一個(gè)計(jì)時(shí)器,timeout_value單位時(shí)間后為這個(gè)函數(shù)現(xiàn)在所處的任務(wù)設(shè)置event_id事件標(biāo)示。 參數(shù):event_id:同上。 Timeout_value:設(shè)置的時(shí)間毫秒數(shù),當(dāng)時(shí)間到是設(shè)置事件。 這個(gè)函數(shù)用的不多,為了精確地給出為哪個(gè)任務(wù)ID的任務(wù)設(shè)置事件,這個(gè)函數(shù)升級(jí)為osal_start_timerEx(byte taskID,uint16 event_id,uint16 timeout_value) 其中taskID就是所指出的預(yù)設(shè)置的事件的任務(wù)。也就是說,osal_start_timer()只能為自己所在的任務(wù)設(shè)置事件,而osal_start_timerEx()不僅可以為自己所在的任務(wù)設(shè)置事件,也可以為其余的任務(wù)設(shè)置事件。 3 byte *osal_msg_allocate( uint16 len ) 說明:分配一個(gè)消息緩沖器,供任務(wù)之間利用osal_msg_send()傳送消息。 參數(shù):len:消息緩沖器的長度。 4 byte osal_msg_deallocate( byte *msg_ptr ) 說明:當(dāng)用消息接受完成之后,取消掉分配的消息緩沖器。 參數(shù):*msg_ptr:指向預(yù)取消的消息緩沖器的指針。 5 byte osal_msg_send( byte destination_task, byte *msg_ptr ) 說明:該函數(shù)用于源任務(wù)向目的任務(wù)發(fā)送命令,數(shù)據(jù)信息等,目的任務(wù)的標(biāo)示符必須給出一個(gè)有效的系統(tǒng)任務(wù)ID,當(dāng)消息發(fā)送成功后會(huì)給目的任務(wù)設(shè)置一個(gè)事件,該事件為系統(tǒng)事件——SYS_EVENT_MSG. 參數(shù):destination_task:目的任務(wù)的任務(wù)標(biāo)示 *msg_ptr:指向預(yù)發(fā)送的消息的指針 6 byte *osal_msg_receive( byte task_id ) 說明:該函數(shù)用于一個(gè)任務(wù)去接收消息緩沖器中的消息,接收完成之后最好利用osal_msg_deallocate()取消消息緩沖器。 參數(shù):task_id:接收者的任務(wù)標(biāo)示號(hào),這里要注意的就是task_id并不是發(fā)送消息的任務(wù)的任務(wù)ID而是接收任務(wù)的任務(wù)ID,比如說在MSA的任務(wù)標(biāo)示為MSA_TaskId,在該任務(wù)中接收其余的任務(wù)發(fā)給它的消息就應(yīng)該是osal_msg_receive(MSA_TaskId)。 理解了以上幾個(gè)函數(shù)之后,基本上就可以實(shí)現(xiàn)一些小的任務(wù)的加入,任務(wù)的執(zhí)行和消息的發(fā)送與接收了。 2.2小實(shí)驗(yàn)驗(yàn)證系統(tǒng)的運(yùn)行方式 猜測(cè):OS系統(tǒng)按照任務(wù)的優(yōu)先級(jí)從高到底不停的掃描這些任務(wù),查看他們是否被設(shè)置了事件,如果該任務(wù)被設(shè)置了事件,則操作系統(tǒng)將馬上進(jìn)入這個(gè)任務(wù)的pFnEventProcessor(處理任務(wù)函數(shù))中執(zhí)行程序員預(yù)先編制好的程序。高優(yōu)先級(jí)的任務(wù)處理完成后必須取消該任務(wù)的事件,否則處理器一直進(jìn)入該高優(yōu)先級(jí)的任務(wù)中,不能正常執(zhí)行低優(yōu)先級(jí)的任務(wù)。 實(shí)驗(yàn)?zāi)康模候?yàn)證以上猜測(cè)是否正確 實(shí)驗(yàn)器材:zigbee實(shí)驗(yàn)板一套 TI-MAC程序(或者使用移植出來的LTOS以及STC12C60S2實(shí)驗(yàn)板) 實(shí)驗(yàn)步驟: 1:設(shè)置兩個(gè)任務(wù),TASK_LED1和TASK_LED2,TASK_LED1的優(yōu)先級(jí)低,TASK_LED2的優(yōu)先級(jí)高。 void osalAddTasks( void ) { /* HAL Drivers Task */ osalTaskAdd (Hal_Init, Hal_ProcessEvent, OSAL_TASK_PRIORITY_LOW); /* MAC Task */ osalTaskAdd( LED1Init, LED1_ProcessEvent, OSAL_TASK_PRIORITY_MED ); /* Application Task */ osalTaskAdd( LED2Init, LED2_ProcessEvent, OSAL_TASK_PRIORITY_HIGH ); } 2:在任務(wù)TASK_LED1中為TASK_LED2設(shè)置開燈關(guān)燈事件,并且在TASK_LED2執(zhí)行完任務(wù)后清除事件標(biāo)志。在任務(wù)TASK_LED2中為TASK_LED1設(shè)置開燈關(guān)燈事件,并且TASK_LED1執(zhí)行完成后清除事件標(biāo)志(注意程序中標(biāo)I和II的語句)。運(yùn)行結(jié)果:兩小燈交替閃爍。 uint16 LED2_ProcessEvent(uint8 taskId, uint16 events) { if(events&LED_START_EVENT) { HalLedSet (HAL_LED_2, HAL_LED_MODE_ON); delay(5000); HalLedSet (HAL_LED_2, HAL_LED_MODE_OFF); osal_start_timerEx(LED1Id,MSA_SEND_EVENT,100); return (events ^ MSA_SEND_EVENT); (I) } return 0; (I) } uint16 LED1_ProcessEvent(uint8 taskId, uint16 events) { if(events & LED_START_EVENT) { HalLedSet (HAL_LED_1, HAL_LED_MODE_ON); delay(5000); HalLedSet (HAL_LED_1, HAL_LED_MODE_OFF); osal_start_timerEx(LED2Id,MSA_SEND_EVENT,100); return (events ^ MSA_SEND_EVENT); (II) } return 0; (II) } 3:TASK_LED1,TASK_LED2互相為對(duì)方設(shè)置開燈關(guān)燈事件,并且TASK_LED2執(zhí)行完成后清除事件標(biāo)志,而TASK_LED1運(yùn)行完成后不清除(去掉I的語句)。運(yùn)行結(jié)果:兩小燈交替閃爍。 4:TASK_LED1,TASK_LED2互相為對(duì)方設(shè)置開燈關(guān)燈事件,并且TASK_LED1執(zhí)行完成后清除事件標(biāo)志,而TASK_LED2運(yùn)行完成后不清除(去掉II的語句)。運(yùn)行結(jié)果:LED1和LED2各閃爍一下,不再閃爍,處理器一直進(jìn)入TASK_LED2中。 實(shí)驗(yàn)分析:在初始化時(shí),通過osal_start_TimerEx(1, LED_START_EVENT,100)為任務(wù)TASK_LED1設(shè)置了LED_START_EVENT事件標(biāo)示,于是程序掃描TASK_LED1時(shí)知道該任務(wù)中設(shè)置了事件,就進(jìn)入任務(wù)TASK_LED1中,TASK_LED1為TASK_LED2設(shè)置了事件且運(yùn)行完成后自己的事件標(biāo)志清零了,當(dāng)任務(wù)鏈表從頭掃到尾時(shí),掃到TASK_LED1中沒事件而TASK_LED2中有事件,則進(jìn)入TASK_LED2中,而TASK_LED2中為TASK_LED1設(shè)置了LED_START_EVENT事件,則TASK_LED2執(zhí)行完成之后,任務(wù)鏈表從頭掃到尾,掃到TASK_LED1中有事件,然后又進(jìn)入TASK_LED1中,這樣一直循環(huán)下去。 同過上面的分析不難想到第3步的實(shí)驗(yàn)結(jié)論是正確的,但對(duì)于第4步不好理解。其實(shí)第4步中的TASK_LED2雖然為TASK_LED1設(shè)置了事件但是自己的事件號(hào)沒清除,又因?yàn)門ASK_LED2的優(yōu)先級(jí)高于TASK_LED1,故先掃描到TASK_LED2,于是進(jìn)入TASK_LED2中,TASK_LED2執(zhí)行完成之后任務(wù)鏈表從頭掃描,先掃描到TASK_LED2中有事件又進(jìn)入TASK_LED2中,這樣一直在TASK_LED2中。 實(shí)驗(yàn)結(jié)論:猜測(cè)是正確的。 三:揭秘TI操作系統(tǒng): 3.1:調(diào)度,非搶占,不需重入 調(diào)度是內(nèi)核的主要職責(zé)之一,就是要決定該輪到哪個(gè)任務(wù)運(yùn)行了。多數(shù)實(shí)時(shí)內(nèi)核是基于優(yōu)先級(jí)調(diào)度法的。每個(gè)任務(wù)根據(jù)其重要程度的不同被賦予一定的優(yōu)先級(jí)?;趦?yōu)先級(jí)的調(diào)度法指,CPU 總是讓處在就緒態(tài)的優(yōu)先級(jí)最高的任務(wù)先運(yùn)行。然而,究竟何時(shí)讓高優(yōu)先級(jí)任務(wù)掌握CPU 的使用權(quán),有兩種不同的情況,這要看用的是什么類型的內(nèi)核,是非搶占型的還是可搶占型內(nèi)核。 3.1.1 非搶占型內(nèi)核 (Non-Preemptive Kernel) 非搶占型內(nèi)核也叫做不可剝奪型內(nèi)核,不可剝奪型內(nèi)核要求每個(gè)任務(wù)自我放棄CPU 的所有權(quán)。不可剝奪型調(diào)度法也稱作合作型多任務(wù),各個(gè)任務(wù)彼此合作共享一個(gè)CPU。異步事件還是由中斷服務(wù)來處理。中斷服務(wù)可以使一個(gè)高優(yōu)先級(jí)的任務(wù)由掛起狀態(tài)變?yōu)榫途w狀態(tài)。但中斷服務(wù)以后控制權(quán)還是回到原來被中斷了的那個(gè)任務(wù),直到該任務(wù)主動(dòng)放棄CPU 的使用權(quán)時(shí),那個(gè)高優(yōu)先級(jí)的任務(wù)才能獲得CPU的使用權(quán)。 不可剝奪型內(nèi)核的一個(gè)優(yōu)點(diǎn)是響應(yīng)中斷快。在任務(wù)級(jí),不可剝奪型內(nèi)核允許使用不可重入函數(shù)。每個(gè)任務(wù)都可以調(diào)用不可重入性函數(shù),而不必?fù)?dān)心其它任務(wù)可能正在使用該函數(shù)從而造成數(shù)據(jù)的破壞。因?yàn)槊總€(gè)任務(wù)要運(yùn)行到完成時(shí)才釋放CPU 的控制權(quán)。 使用不可剝奪型內(nèi)核時(shí),任務(wù)級(jí)響應(yīng)時(shí)間比前后臺(tái)系統(tǒng)快得多。此時(shí)的任務(wù)級(jí)響應(yīng)時(shí)間取決于最長的任務(wù)執(zhí)行時(shí)間。不可剝奪型內(nèi)核的另一個(gè)優(yōu)點(diǎn)是,幾乎不需要使用信號(hào)量保護(hù)共享數(shù)據(jù)。運(yùn)行著的任務(wù)占有CPU,而不必?fù)?dān)心被別的任務(wù)搶占。但這也不是絕對(duì)的,在某種情況下,信號(hào)量還是用得著的。處理共享I/O 設(shè)備時(shí)仍需要使用互斥型信號(hào)量。例如,在打印機(jī)的使用上,仍需要滿足互斥條件。 圖2 示意不可剝奪型內(nèi)核的運(yùn)行情況,任務(wù)在運(yùn)行過程之中,[2(1)]中斷來了,如果此時(shí)中斷是開著的,CPU 由中斷向量[2(2)]進(jìn)入中斷服務(wù)子程序,中斷服務(wù)子程序做事件處理[2(3)],使一個(gè)有更高級(jí)的任務(wù)進(jìn)入就緒態(tài)。中斷服務(wù)完成以后,中斷返回指令[2(4)], 使CPU 回到原來被中斷的任務(wù),接著執(zhí)行該任務(wù)的代碼[2(5)]直到該任務(wù)完成,調(diào)用一個(gè)內(nèi)核服務(wù)函數(shù)以釋放CPU 控制權(quán),由內(nèi)核將控制權(quán)交給那個(gè)優(yōu)先級(jí)更高的、并已進(jìn)入就緒態(tài)的任務(wù)[2(6)],這個(gè)優(yōu)先級(jí)更高的任務(wù)才開始處理中斷服務(wù)程序標(biāo)識(shí)的事件[2(7)]。 (原文件名:圖片2.jpg) 不可剝奪型內(nèi)核的最大缺陷在于其響應(yīng)時(shí)間。高優(yōu)先級(jí)的任務(wù)已經(jīng)進(jìn)入就緒態(tài),但還不能運(yùn)行,要等,也許要等很長時(shí)間,直到當(dāng)前運(yùn)行著的任務(wù)釋放CPU。 3.1.2 可剝奪型內(nèi)核 當(dāng)系統(tǒng)響應(yīng)時(shí)間很重要時(shí),要使用可剝奪型內(nèi)核。最高優(yōu)先級(jí)的任務(wù)一旦就緒,總能得到CPU 的控制權(quán)。當(dāng)一個(gè)運(yùn)行著的任務(wù)使一個(gè)比它優(yōu)先級(jí)高的任務(wù)進(jìn)入了就緒態(tài),當(dāng)前任務(wù)的CPU 使用權(quán)就被剝奪了,或者說被掛起了,那個(gè)高優(yōu)先級(jí)的任務(wù)立刻得到了CPU 的控制權(quán)。如果是中斷服務(wù)子程序使一個(gè)高優(yōu)先級(jí)的任務(wù)進(jìn)入就緒態(tài),中斷完成時(shí),中斷了的任務(wù)被掛起,優(yōu)先級(jí)高的那個(gè)任務(wù)開始運(yùn)行。如圖3 所示。 (原文件名:圖片3.jpg) 使用可剝奪型內(nèi)核,最高優(yōu)先級(jí)的任務(wù)什么時(shí)候可以執(zhí)行,可以得到CPU 的控制權(quán)是可知的。 3.1.3 LTOS的調(diào)度分析 LTOS屬于非搶占型操作系統(tǒng),所以不必?fù)?dān)心函數(shù)重入的問題,也不必?fù)?dān)心臨界區(qū)的保護(hù)問題,對(duì)于沒有任何操作系統(tǒng)使用經(jīng)驗(yàn)的人來說,學(xué)習(xí)并且分析LTOS操作系統(tǒng),將使你在最短的時(shí)間里對(duì)操作系統(tǒng)有一個(gè)整體的理解。LTOS中調(diào)度函數(shù) osal_start_system( void ) 函數(shù)分析 void osal_start_system( void ) { UINT16 events; UINT16 retEvents; halIntState_t intState; // Forever Loop while(1) { /* This replaces MT_SerialPoll() and osal_check_timer() */ TaskActive = osalNextActiveTask(); TaskActive是一個(gè)全局變量,總是記錄著此刻處于就緒態(tài)的任務(wù),在LTOS中,所謂就緒態(tài)就是有事件等待處理的任務(wù)。osalNextActiveTask()該函數(shù)是用來尋找此刻有事件并且處于最高優(yōu)先級(jí)的任務(wù),該函數(shù)返回一個(gè)指針,指針指向最高優(yōu)先級(jí)有事件任務(wù)。 if ( TaskActive ) 如果TaskActive非空,即某個(gè)任務(wù)有事件,則進(jìn)入處理函數(shù)中 { HAL_ENTER_CRITICAL_SECTION(intState); 宏,保存此時(shí)的EA寄存器值,然后關(guān)閉中斷 events = TaskActive->events; 取得TaskActive中的事件。 // Clear the Events for this task TaskActive->events = 0; 清除TaskActive中的事件,為下一次的調(diào)度作準(zhǔn)備。 HAL_EXIT_CRITICAL_SECTION(intState); 宏,還原中斷值 if ( events != 0 ) 此處,不少讀者可能認(rèn)為該判斷可以不要。但是TI程序員寫程序非常的謹(jǐn)慎,為了避免任何一個(gè)不可預(yù)知的錯(cuò)誤,他們總是很小心。 { // Call the task to process the event(s) if ( TaskActive->pfnEventProcessor ) 如果TaskActive中的處理函數(shù)非空。TaskActive->pfnEventProcessor是一個(gè)指向任務(wù)的指針,即指向在osal_add_Task()中加入的APPLICATION中設(shè)計(jì)的FUNCTION()函數(shù),不能理解函數(shù)指針的讀者可以想閱讀一些關(guān)于指針的教程。 { retEvents= (TaskActive->pfnEventProcessor)( TaskActive->taskID, events ); 運(yùn)行函數(shù)(TaskActive->pfnEventProcessor)( TaskActive->taskID, events ),即運(yùn)行函數(shù)FUNCTION() // Add back unprocessed events to the current task HAL_ENTER_CRITICAL_SECTION(intState); 宏,保存此時(shí)的EA寄存器值,然后關(guān)閉中斷 TaskActive->events |= retEvents; 將函數(shù)的返回值重新賦值給TaskActive->events,這就是為什么任務(wù)函數(shù)執(zhí)行完之后,一定要將事件清空的原因。不清空高優(yōu)先級(jí)的任務(wù)中事件,高優(yōu)先級(jí)任務(wù)將一直運(yùn)行,低優(yōu)先級(jí)任務(wù)得不到運(yùn)行。 HAL_EXIT_CRITICAL_SECTION(intState); 宏,還原中斷值 } } } // Complete pass through all task events with no activity? } } 該函數(shù)就是整個(gè)程序的總調(diào)度,他總是在不停地從高到低掃描每一個(gè)任務(wù),當(dāng)任務(wù)中有事件時(shí),就進(jìn)入到該任務(wù)中去執(zhí)行事件。進(jìn)入該調(diào)度函數(shù)后,程序就這樣無限運(yùn)行下去,不會(huì)退出該調(diào)度函數(shù)了,除非中斷。知道了程序是如何被調(diào)度的,讀者可能還是一頭霧水,不用著急,下一步我們來介紹任務(wù)的加載以及任務(wù)中的事件是如何被加入進(jìn)去的。 3.2:任務(wù)加載 void osal_add_Task(pTaskInitFn pfnInit, pTaskEventHandlerFn pfnEventProcessor, UINT8 taskPriority) { OsalTadkREC_t * TaskNew; OsalTadkREC_t *TaskSech; OsalTadkREC_t **TaskPTR; TaskNew = osal_mem_alloc(sizeof( OsalTadkREC_t)); 申請(qǐng)一塊空間給新的任務(wù) if(TaskNew) { TaskNew->pfnInit = pfnInit; TaskNew->pfnEventProcessor = pfnEventProcessor; TaskNew->taskID = Task_id++; TaskNew->events = 0; TaskNew->taskPriority = taskPriority; TaskNew->next = (OsalTadkREC_t *)NULL; 新任務(wù)中賦初值 TaskPTR = &TaskHead; TaskSech = TaskHead; 以下為將各個(gè)任務(wù)一一加入任務(wù)鏈表的操作方法,實(shí)現(xiàn)過程中使用了雙重指針,掌握起來會(huì)有些困難,不過只要在好好揣摩之,也不難理解。 While(TaskSech) { if(TaskNew->taskPriority > TaskSech->taskPriority) { 如果新任務(wù)的優(yōu)先級(jí)高于現(xiàn)在的任務(wù) TaskNew->next = TaskSech; *TaskPTR = TaskNew; 直接讓TaskNew放在TaskPTR指向的地址,TaskPTR先前指向的地址可能為前一個(gè)優(yōu)先級(jí)高的任務(wù),這里經(jīng)過比較優(yōu)先級(jí)后直接將TaskNew放入TaskPTR指向的地址,也就是讓TaskNew與比他優(yōu)先級(jí)低的任務(wù)交換了位置,讓比新任務(wù)優(yōu)先級(jí)的任務(wù)往后靠,而新任務(wù)放在了以前這個(gè)任務(wù)的地方。筆者在此是這樣理解,如有錯(cuò)誤,請(qǐng)大家指正。 return; } TaskPTR = &TaskSech->next; TaskSech = TaskSech->next; 如果優(yōu)先級(jí)不高于以前的任務(wù),則繼續(xù)往后找 } *TaskPTR = TaskNew; 連表頭沒有任務(wù)時(shí),直接讓新任務(wù)放入連表頭。新任務(wù)的優(yōu)先級(jí)不高于以往任何一個(gè)任務(wù)時(shí),則新任務(wù)放于鏈表尾。第一次見識(shí)鏈表還能這樣子建立,這種寫法相當(dāng)精妙,讓人不得不感嘆老外做事之用心。 } return; } 3.3:事件設(shè)置函數(shù) 事件被設(shè)置只有一個(gè)函數(shù),就是osal_set_event(),但是設(shè)置的方式有三種: 1直接調(diào)用該函數(shù)為某個(gè)任務(wù)設(shè)置事件 2 使用延時(shí)設(shè)置事件函數(shù) 3 使用消息,消息會(huì)被LTOS默認(rèn)為是一個(gè)系統(tǒng)事件(system_event) 下面先分析一下osal_set_event函數(shù) byte osal_set_event( byte task_id, UINT16 event_flag ) { OsalTadkREC_t *srchTask; halIntState_t intState; srchTask = osalFindTask( task_id ); 在任務(wù)鏈表中尋找任務(wù)號(hào)為task_id的節(jié)點(diǎn),并讓srchTash指向該地址 task_id是需要增加事件的任務(wù)號(hào),event_flag為需要設(shè)定的事件號(hào),事件號(hào)一般設(shè)置成掩碼方式,比如0x0001,0x0020,0x0004等等,一個(gè)十六位的變量可以同時(shí)表示16的事件的置位與否,換句話來說,一個(gè)任務(wù)中事件數(shù)目不要超過15,讀者可能會(huì)疑惑了,剛剛明明說是16怎么一下又變成了15,其實(shí)是這樣的,0x8000這個(gè)掩碼已經(jīng)被默認(rèn)為系統(tǒng)事件了,所以供我們使用的還有15個(gè)掩碼。對(duì)于一些小型的工程,每個(gè)任務(wù)15個(gè)事件號(hào)足以應(yīng)對(duì)。但是筆者在實(shí)際應(yīng)用中發(fā)現(xiàn)有些任務(wù)確確實(shí)實(shí)需要多余15個(gè)的事件來完成整個(gè)任務(wù),這個(gè)時(shí)候該怎么辦呢?這也是有辦法的,將所有要處理的事件分門別類,一類為系統(tǒng)事件,一類為其余事件。系統(tǒng)事件的觸發(fā)是通過消息來完成的,任務(wù)自己給任務(wù)自己發(fā)送消息同樣是可以的,這樣系統(tǒng)事件就可以再利用消息中的變量來判斷更多的事件標(biāo)志從而達(dá)到更多事件的觸發(fā)了。但是筆者在移植該操作系統(tǒng)時(shí),考慮到“弱功能”單片機(jī)的資源有限,并沒有將消息隊(duì)列移植出來。這就需要讀者在做項(xiàng)目規(guī)劃的時(shí)候就將各個(gè)任務(wù)分配好,考慮項(xiàng)目是劃分為3個(gè)任務(wù)還是4個(gè)任務(wù)或者更多。每個(gè)任務(wù)所做的事情不得超過十五個(gè)事件就可以了,或者使用全局變量定義標(biāo)志位,同樣可以達(dá)到擴(kuò)展事件的目的。 if ( srchTask ) { 如果找到了合適的節(jié)點(diǎn) // Hold off interrupts HAL_ENTER_CRITICAL_SECTION(intState); 宏,保存此時(shí)的EA寄存器值,然后關(guān)閉中斷 // Stuff the event bit(s) srchTask->events |= event_flag; 將事件掩碼賦給節(jié)點(diǎn)中的events元素,請(qǐng)注意:上節(jié)中所講解的osalNextActiveTask()函數(shù)為什么能搜索到有事件的任務(wù)呢,就是因?yàn)檫@里給任務(wù)加了事件。 // Release interrupts HAL_EXIT_CRITICAL_SECTION(intState); 宏,還原中斷值 } else return ( INVALID_TASK ); return ( ZSUCCESS ); } 3.4:事件設(shè)置方式 在前面介紹了事件設(shè)置函數(shù),下面介紹設(shè)置事件的另外兩種方式。 3.4.1 延時(shí)為某個(gè)任務(wù)設(shè)置事件 byte osal_start_timerEx( byte taskID, UINT16 event_id, UINT16 timeout_value ) { 在該函數(shù)中taskID是預(yù)設(shè)置事件的任務(wù)號(hào),evnet_id為預(yù)設(shè)置的事件,timeout_value為延時(shí)多少個(gè)時(shí)間刻度再設(shè)置事件,實(shí)際延時(shí)的時(shí)間為timeout_value*(1/系統(tǒng)頻率)。比如系統(tǒng)頻率為100,即10ms周期。那么設(shè)置timeout_value為100,實(shí)際時(shí)間也就是1s。 halIntState_t intState; osalTimerRec_t *newTimer; HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts. 宏,保存此時(shí)的EA寄存器值,然后關(guān)閉中斷 // Add timer newTimer = osalAddTimer( taskID, event_id, timeout_value ); 在此又要引入一個(gè)鏈表了------時(shí)間鏈表,時(shí)間鏈表的各個(gè)節(jié)點(diǎn)有三個(gè)主要元素:記錄時(shí)間的變量,記錄任務(wù)號(hào)的變量和記錄事件的變量。osalAddTimer函數(shù)就是以某個(gè)任務(wù)的任務(wù)號(hào)作為主要信息來增加節(jié)點(diǎn),osalAddTimer函數(shù)先判斷這個(gè)任務(wù)在之前有無添加過時(shí)間節(jié)點(diǎn)并還沒有被利用,如果有則直接修改該時(shí)間節(jié)點(diǎn)將最新延時(shí)時(shí)間放入,如果這個(gè)任務(wù)之前沒有添加時(shí)間節(jié)點(diǎn)或時(shí)間節(jié)點(diǎn)已經(jīng)被利用并刪除,則重新申請(qǐng)空間并添加一個(gè)節(jié)點(diǎn)。添加完后返回該節(jié)點(diǎn)的地址。 if ( newTimer ) { 如果添加成功 // Does the timer need to be started? if ( timerActive == FALSE ) { timerActive為一個(gè)全局變量,用來判斷定時(shí)器是否開啟了,這里如果沒有開啟,則馬上開啟定時(shí)器,使得定時(shí)器開始運(yùn)作。 osal_timer_activate( TRUE ); 定時(shí)器運(yùn)作函數(shù),這個(gè)函數(shù)最終會(huì)被一個(gè)跟硬件定時(shí)器打交道的函數(shù)代替 } } HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts. 宏,還原中斷值 return ( (newTimer != NULL) ? ZSUCCESS : NO_TIMER_AVAIL ); } 3.4.2 消息為某個(gè)任務(wù)添加事件,添加的事件為系統(tǒng)事件 byte osal_msg_send( byte destination_task, byte *msg_ptr ) { destination_task為需要接收消息的任務(wù),msg_prt為指向消息的指針。在使用消息發(fā)送函數(shù)之前,要先使用osal_msg_allocate(len))來動(dòng)態(tài)申請(qǐng)一個(gè)空間存放該消息,用完該消息后同樣得調(diào)用osal_msg_deallocate((uint8 *) pMsg)函數(shù)來釋放動(dòng)態(tài)申請(qǐng)的空間。 if ( msg_ptr == NULL ) return ( INVALID_MSG_POINTER ); 消息為空的話直接返回錯(cuò)誤指示信息 if ( osalFindTask( destination_task ) == NULL ) { osal_msg_deallocate( msg_ptr ); return ( INVALID_TASK ); } 如果找不到需要接收消息的任務(wù),說明該函數(shù)絕對(duì)被錯(cuò)誤調(diào)用了,另外直接釋放動(dòng)態(tài)申請(qǐng)的空間 // Check the message header if ( OSAL_MSG_NEXT( msg_ptr ) != NULL || OSAL_MSG_ID( msg_ptr ) != TASK_NO_TASK ) { osal_msg_deallocate( msg_ptr ); return ( INVALID_MSG_POINTER ); } 如果msg_ptr的指針指向地址不正確,直接釋放動(dòng)態(tài)申請(qǐng)的空間。 OSAL_MSG_ID( msg_ptr ) = destination_task; // queue message osal_msg_enqueue( &osal_qHead, msg_ptr ); 將信號(hào)放入消息隊(duì)列中 // Signal the task that a message is waiting osal_set_event( destination_task, SYS_EVENT_MSG ); 設(shè)置為系統(tǒng)事件 return ( ZSUCCESS ); } 事實(shí)上考慮到小型單片機(jī)code與xdata,RAM都比較小這個(gè)事實(shí),我并沒有將消息以及消息隊(duì)列這個(gè)功能移植過來。其實(shí)利用延時(shí)設(shè)置事件、直接設(shè)置事件以及15個(gè)事件號(hào)已經(jīng)足以完成許多中小心的程序工程。再大點(diǎn)的工程可能小單片機(jī)也吃不消了,那個(gè)時(shí)候需要考慮的不是增加操作系統(tǒng)了 ,而是要考慮跟換單片機(jī)。 3.5:TICK,時(shí)間單位,硬件定時(shí)器 在介紹完任務(wù)和事件的加入之后,我們開始闡述一個(gè)隱蔽于LTOS操作系統(tǒng)身后但又十分重要的概念-----系統(tǒng)時(shí)鐘。 LTOS需要用戶提供周期性信號(hào)源,用于實(shí)現(xiàn)事件的定時(shí)判斷事件延時(shí)時(shí)間到并將事件置位。節(jié)拍率應(yīng)在每秒10 次到1000 次之間,或者說10 到1000Hz。時(shí)鐘節(jié)拍率越高,系統(tǒng)的額外負(fù)荷就越重。時(shí)鐘節(jié)拍的實(shí)際頻率取決于用戶應(yīng)用程序的精度。TI在他們的zigbee協(xié)議棧TI-MAC1.2.1就是使用了1000HZ的時(shí)鐘頻率。在此提出一點(diǎn)異議請(qǐng)讀者注意,筆者主要講解的是將LTOS移植到“弱功能”單片機(jī)上使用,所以并不提倡使用太高精度的時(shí)鐘頻率,這樣會(huì)給小型單片機(jī)單來太多的額外負(fù)擔(dān),建議使用100HZ的系統(tǒng)時(shí)鐘。時(shí)鐘節(jié)拍源可以是專門的硬件定時(shí)器,也可以是一些外部信號(hào)源。 對(duì)于osalTimerUpdate( UINT8 updateTime ) 函數(shù)分析 static void osalTimerUpdate( uint16 updateTime ) { halIntState_t intState; osalTimerRec_t *srchTimer; osalTimerRec_t *prevTimer; osalTimerRec_t *saveTimer; HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts. 宏,保存此時(shí)的EA寄存器值,然后關(guān)閉中斷 // Update the system time osal_systemClock += updateTime; osal_systemClock 是一個(gè)全局變量,用于知道系統(tǒng)從啟動(dòng)到現(xiàn)在一直運(yùn)行了多少個(gè)系統(tǒng)時(shí)鐘,updataTime是時(shí)間走動(dòng)刻度,一般取1。該函數(shù)在系統(tǒng)定時(shí)器的中斷函數(shù)中調(diào)用。比如,系統(tǒng)的頻率設(shè)置為100,那么也就是定時(shí)器的時(shí)間設(shè)置成10MS,每10MS進(jìn)入一次定時(shí)器中斷,然后執(zhí)行該osalTimerUpdate()函數(shù)。 // Look for open timer slot if ( timerHead != NULL ) { 對(duì)時(shí)間鏈表頭的分析。頭不為空就進(jìn)入處理函數(shù)。那么時(shí)間鏈表頭怎么樣才不為空呢,也就是osalAddTimer()函數(shù)至少被調(diào)用了一次,換句話說,也就是只少有一個(gè)任務(wù)利用osal_start_timerEx()函數(shù)為其余任務(wù)或自己增加延時(shí)任務(wù),時(shí)間鏈表中的節(jié)點(diǎn)的結(jié)構(gòu)體中的timeout成員會(huì)被加入時(shí)間值來代表某個(gè)任務(wù)多久之后觸發(fā)特定事件。這里聽著可能有點(diǎn)繞口,不過慢慢往后看,就能明白。 // Add it to the end of the timer list srchTimer = timerHead; prevTimer = (void *)NULL; // Look for open timer slot while ( srchTimer ) { // Decrease the correct amount of time if (srchTimer->timeout <= updateTime) 如果時(shí)間鏈表中的某個(gè)節(jié)點(diǎn)結(jié)構(gòu)體中timeout 成員變量值已經(jīng)小于等于系統(tǒng)時(shí)間走動(dòng)刻度,說明該節(jié)點(diǎn)的時(shí)間已到,也可以理解為到時(shí)候設(shè)置事件了。 srchTimer->timeout = 0; 直接將該小于等于系統(tǒng)時(shí)間走動(dòng)刻度的變量清零。 else srchTimer->timeout = srchTimer->timeout - updateTime; 如果timeout 成員變量值還比較大,說明他的時(shí)間還沒有到,就必須減去系統(tǒng)時(shí)間走動(dòng)刻度讓他一步步減小,如同時(shí)光一點(diǎn)點(diǎn)流逝。 // When timeout, execute the task if ( srchTimer->timeout == 0 ) { 時(shí)間已到的鏈表節(jié)點(diǎn) osal_set_event( srchTimer->task_id, srchTimer->event_flag ); 給指定任務(wù)號(hào)的任務(wù)設(shè)置事件。 // Take out of list if ( prevTimer == NULL ) timerHead = srchTimer->next; 將該節(jié)點(diǎn)從鏈表中剔除 else prevTimer->next = srchTimer->next; // Next saveTimer = srchTimer->next; // Free memory osal_mem_free( srchTimer ); 由于時(shí)間鏈表中的節(jié)點(diǎn)空間是動(dòng)態(tài)申請(qǐng)的,所以剔除完鏈表中的節(jié)點(diǎn)后,要將申請(qǐng)的空間釋放。 srchTimer = saveTimer; } else { // Get next prevTimer = srchTimer; srchTimer = srchTimer->next; 向后繼續(xù)尋找 } } } HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts. 宏,還原中斷值 } 任務(wù)的調(diào)度、事件的加入、系統(tǒng)時(shí)鐘中的時(shí)間刻度的走動(dòng),這些智慧碰撞于一起最終使一個(gè)系統(tǒng)動(dòng)態(tài)地運(yùn)作了起來。 3.6:內(nèi)存管理與分配 在前面講解到的時(shí)間鏈表的建立、任務(wù)鏈表的建立都離不開內(nèi)存的動(dòng)態(tài)分配與釋放,下面分析關(guān)于內(nèi)存管理的幾個(gè)函數(shù)。 在TI的zigbee程序TI-MAC1.2.1中,在XDATA定義了一個(gè)1024字節(jié)的數(shù)組,后續(xù)全部的動(dòng)態(tài)空間申請(qǐng)與釋放都是在這個(gè)數(shù)組中實(shí)現(xiàn)。 void osal_mem_init( void ) { 初始化函數(shù),主要完成了內(nèi)存分配數(shù)組的初始化 osalMemHdr_t *tmp; // Setup a NULL block at the end of the heap for fast comparisons with zero. tmp = (osalMemHdr_t *)theHeap + (MAXMEMHEAP / HDRSZ) - 1; 找到分配數(shù)組的尾部 *tmp = 0; 賦零,便于在使用該動(dòng)態(tài)內(nèi)存時(shí)判斷是否到了尾部,也就是說判斷是否還有空間能夠分配 // Setup a small-block bucket. tmp = (osalMemHdr_t *)theHeap; 找到分配數(shù)組的頭 *tmp = SMALLBLKHEAP; LTOS中將動(dòng)態(tài)分配的空間劃分為了兩塊,一個(gè)為小空間分配塊ff1,專門用來負(fù)責(zé)分配給小于16個(gè)字節(jié)的空間分配。另一塊為大空間分配塊ff2,用于分配給大于16個(gè)字節(jié)的空間分配。將SMALLBLKHEAP這個(gè)常數(shù)賦值給頭,SMALLBLKHEAP這個(gè)常數(shù)在TI-MAC1.2.1中設(shè)置為232,表示小空間分配塊有232個(gè)字節(jié)長度,大空間分配塊有(1024-232-2)個(gè)長度,為什么是1024-232-2而不是1024-232,讀者讀完下面的分析就能明白。如下圖4 (原文件名:圖片4.JPG) 被分配的空間總是比實(shí)際要分配的空間多HDRSZ個(gè)字節(jié),在8位單片機(jī)中,HDRSZ為2個(gè)字節(jié),前一個(gè)字節(jié)用來表示這個(gè)分配空間被使用了沒有,使用了就將該字節(jié)的最高位置一,沒使用就清零。后一個(gè)字節(jié)表示該空間的長度。所以由于232個(gè)字節(jié)的長度的分配空間占用了兩個(gè)字節(jié)的頭,此時(shí)就必須1024-232-2。 // Setup the wilderness. tmp = (osalMemHdr_t *)theHeap + (SMALLBLKHEAP / HDRSZ); *tmp = ((MAXMEMHEAP / HDRSZ) * HDRSZ) - SMALLBLKHEAP - HDRSZ; #if ( OSALMEM_GUARD ) ready = OSALMEM_READY; #endif // Setup a NULL block that is never freed so that the small-block bucket // is never coalesced with the wilderness. ff1 = tmp; 讓ff1先指向大空間分配塊,然后在該處給ff2分配一個(gè)0字節(jié)長度的空間,最后讓ff1指向小空間分配塊。這樣就巧妙地將小空間分配塊與大空間分配塊分開了。值得注意的是,當(dāng)小空間分配塊ff1的空間被耗盡后也可以繼續(xù)在大空間分配塊ff2中分配。 ff2 = osal_mem_alloc( 0 ); ff1 = (osalMemHdr_t *)theHeap; } 對(duì)于分配內(nèi)存函數(shù)的分析: void *osal_mem_alloc(UINT16 size ) { osalMemHdr_t *prev; osalMemHdr_t *hdr; halIntState_t intState; UINT16 tmp; byte xdata coal = 0; #if ( OSALMEM_GUARD ) // Try to protect against premature use by HAL / OSAL. if ( ready != OSALMEM_READY ) { osal_mem_init(); 調(diào)用初始化函數(shù),這里可能造成編譯器的遞歸調(diào)用警告 } #endif size += HDRSZ; 預(yù)分配的長度加上標(biāo)志頭的長度,如前面所說,標(biāo)志頭的長度為兩個(gè)字節(jié),第一個(gè)字節(jié)表示該空間被使用沒,第二個(gè)字節(jié)表示申請(qǐng)的長度 // Calculate required bytes to add to 'size' to align to halDataAlign_t. if ( sizeof( halDataAlign_t ) == 2 ) { size += (size & 0x01); } else if ( sizeof( halDataAlign_t ) != 1 ) { const byte mod = size % sizeof( halDataAlign_t ); if ( mod != 0 ) { size += (sizeof( halDataAlign_t ) - mod); } } 以上是對(duì)于size長度的一些計(jì)算,主要是針對(duì)不同位數(shù)的處理器,使size作一個(gè)靠齊處理。 HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts. 宏,保存此時(shí)的EA寄存器值,然后關(guān)閉中斷 // Smaller allocations are first attempted in the small-block bucket. if ( size <= OSALMEM_SMALL_BLKSZ ) { 如果要分配的空間比較小,小于OSALMEM_SMALL_BLKSZ(16),就直接在小長度分配空間分配 hdr = ff1; } else { 否則,說明要分配的空間比較大,在大長度分配空間分配 hdr = ff2; } tmp = *hdr; 將此處的空間頭的值取出來賦給tmp,也就是上面介紹的頭兩個(gè)字節(jié) do { if ( tmp & OSALMEM_IN_USE ) 判斷tmp的最高位是否被置一了,也就是判斷這塊空間是否被用了,如果被用了則繼續(xù)往后尋找。 { tmp ^= OSALMEM_IN_USE; coal = 0; 當(dāng)空間被動(dòng)態(tài)分配和釋放多次以后,就會(huì)形成一種每個(gè)動(dòng)態(tài)分配內(nèi)存機(jī)制都會(huì)碰到的問題----內(nèi)存碎片,當(dāng)程序要分配一個(gè)連續(xù)的大空間時(shí),就需要將以前被分散的小空間聯(lián)合起來,coal這個(gè)變量就是用在將幾塊連續(xù)打散的空間合并的判斷變量,判斷coal即可知道內(nèi)存快是否連續(xù)。如下圖5中,紅色的塊表示未使用的塊,黃色的塊表示已經(jīng)使用的塊,如果要在這個(gè)空間中分配大小為3的內(nèi)存,就必須跨過這些黃色的塊,使用最后一個(gè)連續(xù)的紅塊。而前面的紅塊成為了內(nèi)存碎片。 (原文件名:圖片5.JPG) } else { if ( coal != 0 ) { *prev += *hdr; 這里的一段程序主要是用來判斷如何分配一塊合適的內(nèi)存給應(yīng)用程序,當(dāng)內(nèi)存區(qū)沒有內(nèi)存碎片時(shí),這時(shí)申請(qǐng)內(nèi)存就直接到內(nèi)存分配塊分配,這個(gè)時(shí)候不用操心內(nèi)存碎片以及內(nèi)存合并的事,直截了當(dāng)分配就是。一旦需要分配大的內(nèi)存并且存在內(nèi)存碎片時(shí),就必須操心更多的事了。判斷以下幾種情況: 1是否有一塊沒使用的空間大于欲分配的空間,有則直接分配。 2若沒有,則合并內(nèi)存塊,直到合并到連續(xù)的塊并且空間足夠大 3 若沒有連續(xù)可加起來滿足條件的塊,則((void *)NULL). if ( *prev >= size ) { hdr = prev; tmp = *hdr; break; } } else { if ( tmp >= size ) { break; } coal = 1; prev = hdr; } } hdr = (osalMemHdr_t *)((byte *)hdr + tmp); tmp = *hdr; if ( tmp == 0 ) { hdr = ((void *)NULL); break; } } while ( 1 ); 直到按照以上條件分配完成,退出循環(huán) if ( hdr != ((void *)NULL)) { 下面的程序主要將被分配的空間與剛才分配的實(shí)際空間作一個(gè)差值,比較這個(gè)差值的大小。如果這個(gè)值很大,說明這塊空間還有很多能夠利用的余地,于是先將這個(gè)空間重新分割開以便接下來的繼續(xù)分配使用,然后在已經(jīng)分配的空間上打上已用的標(biāo)記;如果這個(gè)值不大,說明空間利用的差不多了,還算合理了,就不分割,直接打上已用的標(biāo)記。 tmp -= size; // Determine whether the threshold for splitting is met. if ( tmp >= OSALMEM_MIN_BLKSZ ) { // Split the block before allocating it. osalMemHdr_t *next = (osalMemHdr_t *)((byte *)hdr + size); *next = tmp; *hdr = (size | OSALMEM_IN_USE); 分割在此 } else { *hdr |= OSALMEM_IN_USE; } hdr++; } HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts. return (void *)hdr; } void osal_mem_free( void *ptr ) { osalMemHdr_t *currHdr; halIntState_t intState; #if ( OSALMEM_GUARD ) // Try to protect against premature use by HAL / OSAL. if ( ready != OSALMEM_READY ) { osal_mem_init(); } #endif HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts. currHdr = (osalMemHdr_t *)ptr – 1; *currHdr &= ~OSALMEM_IN_USE; 釋放空間的函數(shù)相對(duì)于申請(qǐng)空間的函數(shù)要簡單許多,直接將欲釋放空間的頭的被使用標(biāo)志清除了即可 if ( ff1 > currHdr ) { 這里可能會(huì)讓不少讀者疑惑:ff1為數(shù)組的頭,怎么也不可能指向比currHdr還靠后的的地址,怎么可能大于currHdr呢。其實(shí)這是有可能的,上面已經(jīng)提到,當(dāng)ff1的空間被分配完時(shí),是可以通過osal_mem_kick( void )函數(shù),讓ff1指向ff2的地址繼續(xù)分配的,這種情況下,ff1被移動(dòng)到了后面去了,但是currHdr可能是ff1在數(shù)組頭時(shí)分配的一塊空間,這時(shí)必須讓ff1前移來表示著ff1處繼續(xù)有可用空間了。 ff1 = currHdr; } HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts. } 五 內(nèi)核移植: 筆者將LTOS移植出來了,放入STC12C60S2中運(yùn)行起來,LTOS工程中建立了六個(gè)任務(wù),每個(gè)任務(wù)負(fù)責(zé)點(diǎn)亮一個(gè)由P1口控制的小燈,產(chǎn)生跑馬燈效果。移植工程見萬能配置文件夾。下面介紹一下移植工程中需要修改的地方。 5.1:與芯片相關(guān)數(shù)據(jù)結(jié)構(gòu) Type.h 中 // Data typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; // Unsigned numbers typedef unsigned char UINT8; typedef unsigned char byte; typedef unsigned short UINT16; typedef unsigned short INT16U; typedef unsigned long UINT32; typedef unsigned long INT32U; typedef unsigned char halDataAlign_t; // Signed numbers typedef signed char INT8; typedef signed short INT16; typedef signed long INT32; 5.2與芯片相關(guān)的中斷開關(guān): Type.h 中 #define HAL_ENABLE_INTERRUPTS() st( EA = 1; ) #define HAL_DISABLE_INTERRUPTS() st( EA = 0; ) #define HAL_INTERRUPTS_ARE_ENABLED() (EA) typedef unsigned char halIntState_t; #define HAL_ENTER_CRITICAL_SECTION(x) st( x = EA; HAL_DISABLE_INTERRUPTS(); ) #define HAL_EXIT_CRITICAL_SECTION(x) st( EA = x; ) #define HAL_CRITICAL_STATEMENT(x) st( halIntState_t s; HAL_ENTER_CRITICAL_SECTION(s); x; HAL_EXIT_CRITICAL_SECTION(s); ) 5.3:芯片定時(shí)器實(shí)現(xiàn)內(nèi)核TICK 定時(shí)器的更改,本例子中利用STC12C60S2的PCA作為系統(tǒng)定時(shí)器 Timer6.c中 初始化程序: void OSAL_TIMER_TICKINIT(void) { CMOD = 0X80; CCON = 0X00; CL = 0X00; CH = 0X00; IP |= 0x80; IPH |= 0x80; CCAP0L = channel0_10ms_l; CCAP0H = channel0_10ms_h; CCAPM0 = 0X49; } 中斷函數(shù): void timer_interrupt(void ) PCA_Routine { halIntState_t intState; OSAL_TIMER_TICKSTOP(); HAL_ENTER_CRITICAL_SECTION( intState ); CCF0 = 0; osal_update_timers( ); CCAP0L += channel0_10ms_l; CCAP0H += channel0_10ms_h; HAL_EXIT_CRITICAL_SECTION(intState); OSAL_TIMER_TICKSTART(); } 5.4:內(nèi)存中可分配單元的定義 本例子中分配STC12C60S2單片機(jī)的XDATA中768個(gè)字節(jié)的空間供系統(tǒng)調(diào)用,另外一些可用于其余的全局變量或作為緩沖 Osal_memory.c中 #define MAXMEMHEAP 768 #if defined( EXTERNAL_RAM ) static byte *theHeap = (byte *)EXT_RAM_BEG; #else static halDataAlign_t xdata _theHeap[ MAXMEMHEAP / sizeof( halDataAlign_t ) ]; static byte *theHeap = (byte *)_theHeap; #endif 結(jié)束語:LTOS講解以及移植過程全部完成了,由于本人接觸操作系統(tǒng)的經(jīng)驗(yàn)并不是特別豐富,所以以上講解中難免不出現(xiàn)錯(cuò)誤或有出入的地方,望各位經(jīng)驗(yàn)豐富的研究人員和學(xué)者加以指正。 最后,談一點(diǎn)點(diǎn)我個(gè)人對(duì)于技術(shù)研發(fā)這個(gè)職位的看法,做為技術(shù)人員,大家都覺得工資高,工作穩(wěn)定,還能學(xué)到很多 的東西。是大部份走出校門或性格內(nèi)向,或希望過平靜生活的人的必然選擇。其實(shí),你們有沒有問過自己,這條路到底走對(duì)了嗎? 一個(gè)剛畢業(yè)的大學(xué)生,從事銷售和從事技術(shù)兩種不同的工作,可能工資的差距會(huì)達(dá)到數(shù)倍之遠(yuǎn)。對(duì)于初出校門的人來說,不無一種極端的誘惑力。剛畢業(yè)的年青人,當(dāng)然會(huì)果斷的選擇技術(shù)之路。 兩年后,我們?cè)倏纯?,由于?jīng)驗(yàn)的積累,做業(yè)務(wù)的積累了部份客戶資源,做技術(shù)的積累了好的經(jīng)驗(yàn),在各自的領(lǐng)域內(nèi)都大展開了手腳,收入也基本接近了。 再以后呢,技術(shù)之路越來越難走,畢竟做技術(shù)需要的大量的時(shí)間和精力,否則就跟不上現(xiàn)在時(shí)代的技術(shù)更新了,做業(yè)務(wù)的呢,客戶群越來越大,經(jīng)驗(yàn)越來越豐富,誰的收入會(huì)更高? 兩種不同的職業(yè),它們有著各自不同的特點(diǎn),技術(shù)行業(yè)是個(gè)撐不死,飽不了的地方,而銷售行業(yè)則是沒有盡頭的發(fā)展之路。 過了三十歲,大家會(huì)選擇什么呢,結(jié)婚、生子,人生的一條老路, 到了三十歲,你還有自信面對(duì)繁重的工作嗎?你有剛出社會(huì)的人的活力嗎?你能和他們比工作時(shí)間,玩命地在老板面前表現(xiàn)嗎?你能丟下妻兒出差一、兩個(gè)月嗎? 比之于我們的生活和人際關(guān)系及工作,那些從事售前和市場(chǎng)開發(fā)的朋友,卻有比我們多的多的工作之外的時(shí)間,甚至他們工作的時(shí)間有的時(shí)候是和生活的時(shí)間是可以兼顧的,他們可以通過市場(chǎng)開發(fā),認(rèn)識(shí)各個(gè)行業(yè)的人士,可以認(rèn)識(shí)各種各樣的朋友,他們比我們坦率說更有發(fā)財(cái)和發(fā)展的機(jī)會(huì),只要他們跟我們一樣勤奮。(有一種勤奮的普通人,如果給換個(gè)地方,他馬上會(huì)成為一個(gè)勤奮且出眾的人。) 有人會(huì)說,我有了技術(shù)! 技術(shù)經(jīng)驗(yàn)是什么?一些老的,過去了的東西,他代表著你所留戀的過去,你所放不下的那一部份,你會(huì)以經(jīng)驗(yàn)來判別事物,選擇工作方法。在新老技術(shù)交替的時(shí)間內(nèi),經(jīng)驗(yàn)可以起到承前啟后的作用,讓你威風(fēng)八面??墒牵氵€會(huì)用到多少十年以前的芯片呢? 大家所掌握的技術(shù)終會(huì)過時(shí),腦子僵化的時(shí)候總會(huì)到來。那時(shí),你何去何從? 當(dāng)然,我并不是說技術(shù)就一無是處了,許多做技術(shù)的朋友都是貧苦出生,也有許多的朋友是不得以慢慢走上這條道路,也許有的朋友完全出于興趣愛好,出于興趣愛好的我先撇開不談,因?yàn)閻酆眠@個(gè)東西可以使你無欲無求而只求與它。我們都需要靠自己的雙手去創(chuàng)造未來,我只是想說不要一輩子只靠技術(shù)活著,不要一輩子只會(huì)一點(diǎn)技術(shù)。其實(shí)做技術(shù)的人往往思維慎密,做事謹(jǐn)慎,并且由于以前有很相當(dāng)長時(shí)間的技術(shù)積累,以至于很容易跟討論技術(shù)的客戶溝通起來,所以說做技術(shù)的人很應(yīng)該多拿出一點(diǎn)時(shí)間和精力去培養(yǎng)別的才能(比如口才,銷售,管理)。我只是奉勸那些學(xué)習(xí)技術(shù)的朋友,千萬不要拿科舉考試樣的心態(tài)去學(xué)習(xí)技術(shù),對(duì)技術(shù)的學(xué)習(xí)幾近的癡迷,想掌握所有所有的技術(shù),以讓自己成為技術(shù)領(lǐng)域的權(quán)威和專家,但是我們的國家沒有那么多的研究院去容下我們這么多的高手,等到年紀(jì)來了就開始感嘆懷才不遇了。 技術(shù)僅僅是一個(gè)工具,是你在人生一個(gè)階段生存的工具,你可以一輩子喜歡他,但最好不要一輩子靠它生存。擺弄電子是我的愛好,也是一個(gè)工具,我跟大家一樣每天像毒癮犯了似的偷偷跑到Ourdev論壇里瞄瞄有無新的帖子,也潛水很久,今天終于按捺不住寫出一些東東,僅屬于個(gè)人愚見,歡迎大家拍磚,。 |
|