小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

將TIzigbee開源協(xié)議棧中的OS操作系統(tǒng)移植出來,放在STC12C60S2中使用

 昵稱7503466 2012-06-01
本人從事zigbee的研發(fā)工作已接有多年,在這幾年的技術(shù)之路上收獲了很多,也失去了很多。幾年之后,離開了zigbee研發(fā)崗位,決定寫點什么作為紀(jì)念,另外也希望給后來的學(xué)習(xí)zigbee的同盟們留下一點“磚塊”。
        不復(fù)雜的小系統(tǒng)一般設(shè)計成如圖1所示的樣子,這種系統(tǒng)一般稱作為前后臺系統(tǒng)或超循環(huán)系統(tǒng)。整個應(yīng)用程序(整個工程)是一個無限循環(huán),對于應(yīng)用中的具體操作既是在這個無限循環(huán)中不停地調(diào)用各個相應(yīng)的函數(shù)來實現(xiàn),這部分可以叫做后臺行為。后臺也叫做任務(wù)級。前臺也叫中斷級。要求實時響應(yīng)的操作一般由中斷服務(wù)來保證。如果前后臺程序需要修改,原先整體的循環(huán)會被打亂的凌亂不堪,循環(huán)的時序也會受到影響,從而也就不能保證修改后的程序繼續(xù)能夠正常無誤工作。這樣就給中大型程序的升級,應(yīng)用的增加,工程的管理帶來的不可避免的麻煩,最壞的情況就是一個程序的升級相當(dāng)于一個工程的重新編寫。
       
(原文件名:圖片1.JPG)
                                 圖 1
        此時就需要引進(jìn)操作系統(tǒng)了,有了操作系統(tǒng)后,整個工程可以被劃分成許多小的模塊(任務(wù))互相協(xié)調(diào)合作共同完成整個項目,程序修改或者升級時只需要修改對應(yīng)的任務(wù)即可完成,不必改動整個工程,這是筆者認(rèn)為在使用操作系統(tǒng)時最顯而易見的好處。另外,使用操作系統(tǒng)使得程序的各個功能模塊化,利用各種調(diào)度方法(不同操作系統(tǒng)可能不同)實現(xiàn)整個工程,從而也使得大型的程序、雜亂無章的循環(huán)變的盡然有序,程序運行更加安全可靠?,F(xiàn)在在嵌入式領(lǐng)域被廣泛關(guān)注和認(rèn)同的操作系統(tǒng)有uCOS,linux,windowsCE,UCLINUX,等等,據(jù)筆者了解(才疏學(xué)淺),應(yīng)用于8位單片機(jī)并得到了實踐證明的暫時聽說了UCOS以及RTX51就是KEIL公司針對51開發(fā)的一個小型RTOS。RTOS只提供了庫接口函數(shù),對于學(xué)者和開發(fā)者并不開源,所以使用者和探討者的人數(shù)相對于UCOS并不多,UCOS是一款開源的實時操作系統(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ā)項目穩(wěn)定可靠,便于理解和學(xué)習(xí),使得操作系統(tǒng)初學(xué)者可以很容易的對操作系統(tǒng)整體有一個全面的了解,所以筆者決定將該操作系統(tǒng)移植出來,放入STC12C60S2單片機(jī)中使用。記錄下該文檔拋磚引玉,使更多人能更快地理解該操作系統(tǒng)并得到的該小型操作系統(tǒng)更好的發(fā)展和應(yīng)用。

一:對TI操作系統(tǒng)初步分析
1.1任務(wù)、事件、消息
        剛拿到TI-MAC1.2.1時被該程序搞蒙了,TI-MAC1.2.1的程序竟然是基于他們自己編寫的OS操作系統(tǒng)運行的。說到LTOS操作系統(tǒng),不得不說說他的任務(wù)、事件和消息機(jī)制。據(jù)筆者理解,任務(wù)就是程序編寫員將預(yù)實現(xiàn)的功能分成不同的模塊,這些模塊之間分工明確并且相互合作,共同完成程序員預(yù)完成的某個項目的整個功能;事件是這些任務(wù)中要處理的某個小功能的口令,比如老師說張三你站起來或坐下,張三聽到站起來就站起來,聽到坐下就坐下,同樣道理,某個任務(wù)得到處理器后,先判斷自己的事件是什么,如果是URAT_Writer則任務(wù)知道是串口寫;而如果是LED_STOP,則任務(wù)知道是小燈停;消息是任務(wù)之間相互通信的方式,任務(wù)之間的數(shù)據(jù)傳輸一是通過消息來實現(xiàn),二是通過延時設(shè)置任務(wù)來完成。任務(wù)內(nèi)部消息就是一個系統(tǒng)事件。
        在進(jìn)入LTOS系統(tǒng)前,先利用osal_init_system()等初始化程序?qū)⒉僮飨到y(tǒng)初始化,主要功能就是內(nèi)存分配函數(shù)的初始化、定時器的初始化以及為任務(wù)的加入。任務(wù)初始化時將任務(wù)按預(yù)先設(shè)定分配了不同的優(yōu)先級,LTOS系統(tǒng)按照賦值的優(yōu)先級順序從高到底不停的掃描這些任務(wù),查看他們是否被設(shè)置了事件,如果該任務(wù)被設(shè)置了事件,則操作系統(tǒng)將馬上進(jìn)入這個該任務(wù)對應(yīng)的pFnEventProcessor(處理任務(wù)函數(shù))中執(zhí)行該任務(wù)中的事件。
        初始化和任務(wù)加入完成之后就開始進(jìn)入任務(wù)調(diào)度函數(shù)osalNextActiveTask( void )。進(jìn)入任務(wù)調(diào)度函數(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)先級中有無事件
                  return srchTask;
              }
              srchTask = srchTask->next;
          }
          return NULL;
        }
        進(jìn)入該函數(shù)后,讓srchTask指向任務(wù)列表的頭(tasksHead),然后利用if(srch->events)查看改任務(wù)中是否有事件,如果沒有事件則srchTask指向任務(wù)鏈表的下一個元素,繼續(xù)以上的工作,一旦查到某個任務(wù)有事件就返回任務(wù)的任務(wù)ID。然后利用retEvents = (activeTask->pfnEventProcessor)( activeTask->taskID, events )函數(shù)進(jìn)入改任務(wù)中執(zhí)行事先編寫的函數(shù)。值得注意的是任務(wù)在執(zhí)行完成之后一定要記得將任務(wù)的事件清空,不然返回的retEvents會跟activeTask->events相或(activeTask->events |= retEvents),假如該任務(wù)的優(yōu)先級最高,這樣每當(dāng)程序進(jìn)入下一次的調(diào)度時總會進(jìn)入該任務(wù)中(因為該任務(wù)的事件不曾清空),這樣其余的任務(wù)即使有置位的事件也不會被執(zhí)行。具體的分析將在下一章中利用實驗詳細(xì)講解。
1.2加入自己的任務(wù)
        上一節(jié)中講到了操作系統(tǒng)的基本運行方式,運行中涉及的任務(wù)的初始化和運行,下面主要介紹如何加入自己的任務(wù)。
        在TI-MAC1.2.1中全部的任務(wù)加入是利用osalAddTasks( void )在初始化時完成的,該函數(shù)屬于應(yīng)用層和OS之間的接口函數(shù),而單個的任務(wù)加入就在osalAddTasks( void )函數(shù)中利用osalTaskAdd (Task_Init,Task_ProcessEvent, OSAL_TASK_PRIORITY)加入的,其中Task-Init(uint8 task_id)是任務(wù)的初始化函數(shù),該函數(shù)中系統(tǒng)為任務(wù)分配特定的唯一的ID號;Task_ProcessEvent(uint8 task_id, uint16 events)是任務(wù)的執(zhí)行函數(shù),該函數(shù)中程序表達(dá)的就是要實現(xiàn)的功能;最后一個參數(shù)OSAL_TASK_PRIORITY是任務(wù)的優(yōu)先級別。
        下面以一個小的實驗例子來說明自己的任務(wù)的加入:
        該實驗實現(xiàn)一個簡單的功能——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)的運行方式
        OS操作系統(tǒng)不是一個完整的操作系統(tǒng),任務(wù)與任務(wù)之間也不能搶占,只是簡單地利用定時器管理和任務(wù)事件設(shè)置來周而復(fù)始地調(diào)用任務(wù),與其他的操作系統(tǒng)一樣(ucos,linux)它同樣需要定時器來確定一個系統(tǒng)時鐘tick,在cc2430中是占用一個硬件定時器來定時。每次一個任務(wù)執(zhí)行完后系統(tǒng)都會從高優(yōu)先級到低優(yōu)先級掃描任務(wù)是否被設(shè)置了事件,當(dāng)有任務(wù)被設(shè)置事件時,就馬上進(jìn)入該任務(wù)中。OS操作系統(tǒng)的思想是:保證高優(yōu)先級的任務(wù)有事件時最先得到處理器。在任務(wù)的優(yōu)先級賦值時,MAC Task一般賦最高的優(yōu)先級,這樣是為了使得無線電的發(fā)射和接受提高到最重要的地位。
2.1一些主要的函數(shù)
        OS操作系統(tǒng)的運行依靠許多重要的函數(shù),下面介紹一些主要函數(shù)以及其中的參數(shù)。
        1  byte osal_set_event(byte task_id,uint16 event_flag)
        說明:該函數(shù)與任務(wù)的運行至關(guān)重要,它是為任務(wù)設(shè)置事件的函數(shù),該函數(shù)被利用為任務(wù)設(shè)置事件標(biāo)示符。
         參數(shù):task_id:欲設(shè)置事件的這個任務(wù)ID,一旦寫入,將為該任務(wù)ID的任務(wù)設(shè)置事件。
               event_falg:事件標(biāo)示,該事件標(biāo)示占2個字節(jié),每個位指定一個事件,只能有一個系統(tǒng)事件,其余的事件位在接受任務(wù)中自行定義。
        2 osal_start_timer(uint16 event_id,uint16 timeout_value)
         說明:該函數(shù)啟動一個計時器,timeout_value單位時間后為這個函數(shù)現(xiàn)在所處的任務(wù)設(shè)置event_id事件標(biāo)示。
         參數(shù):event_id:同上。
               Timeout_value:設(shè)置的時間毫秒數(shù),當(dāng)時間到是設(shè)置事件。
        這個函數(shù)用的不多,為了精確地給出為哪個任務(wù)ID的任務(wù)設(shè)置事件,這個函數(shù)升級為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 )
          說明:分配一個消息緩沖器,供任務(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)示符必須給出一個有效的系統(tǒng)任務(wù)ID,當(dāng)消息發(fā)送成功后會給目的任務(wù)設(shè)置一個事件,該事件為系統(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ù)用于一個任務(wù)去接收消息緩沖器中的消息,接收完成之后最好利用osal_msg_deallocate()取消消息緩沖器。
           參數(shù):task_id:接收者的任務(wù)標(biā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)。
        理解了以上幾個函數(shù)之后,基本上就可以實現(xiàn)一些小的任務(wù)的加入,任務(wù)的執(zhí)行和消息的發(fā)送與接收了。
2.2小實驗驗證系統(tǒng)的運行方式
        猜測:OS系統(tǒng)按照任務(wù)的優(yōu)先級從高到底不停的掃描這些任務(wù),查看他們是否被設(shè)置了事件,如果該任務(wù)被設(shè)置了事件,則操作系統(tǒng)將馬上進(jìn)入這個任務(wù)的pFnEventProcessor(處理任務(wù)函數(shù))中執(zhí)行程序員預(yù)先編制好的程序。高優(yōu)先級的任務(wù)處理完成后必須取消該任務(wù)的事件,否則處理器一直進(jìn)入該高優(yōu)先級的任務(wù)中,不能正常執(zhí)行低優(yōu)先級的任務(wù)。
實驗?zāi)康模候炞C以上猜測是否正確
        實驗器材:zigbee實驗板一套  TI-MAC程序(或者使用移植出來的LTOS以及STC12C60S2實驗板)
實驗步驟:
       1:設(shè)置兩個任務(wù),TASK_LED1和TASK_LED2,TASK_LED1的優(yōu)先級低,TASK_LED2的優(yōu)先級高。
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的語句)。運行結(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互相為對方設(shè)置開燈關(guān)燈事件,并且TASK_LED2執(zhí)行完成后清除事件標(biāo)志,而TASK_LED1運行完成后不清除(去掉I的語句)。運行結(jié)果:兩小燈交替閃爍。
        4:TASK_LED1,TASK_LED2互相為對方設(shè)置開燈關(guān)燈事件,并且TASK_LED1執(zhí)行完成后清除事件標(biāo)志,而TASK_LED2運行完成后不清除(去掉II的語句)。運行結(jié)果:LED1和LED2各閃爍一下,不再閃爍,處理器一直進(jìn)入TASK_LED2中。
實驗分析:在初始化時,通過osal_start_TimerEx(1, LED_START_EVENT,100)為任務(wù)TASK_LED1設(shè)置了LED_START_EVENT事件標(biāo)示,于是程序掃描TASK_LED1時知道該任務(wù)中設(shè)置了事件,就進(jìn)入任務(wù)TASK_LED1中,TASK_LED1為TASK_LED2設(shè)置了事件且運行完成后自己的事件標(biāo)志清零了,當(dāng)任務(wù)鏈表從頭掃到尾時,掃到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步的實驗結(jié)論是正確的,但對于第4步不好理解。其實第4步中的TASK_LED2雖然為TASK_LED1設(shè)置了事件但是自己的事件號沒清除,又因為TASK_LED2的優(yōu)先級高于TASK_LED1,故先掃描到TASK_LED2,于是進(jìn)入TASK_LED2中,TASK_LED2執(zhí)行完成之后任務(wù)鏈表從頭掃描,先掃描到TASK_LED2中有事件又進(jìn)入TASK_LED2中,這樣一直在TASK_LED2中。
實驗結(jié)論:猜測是正確的。


三:揭秘TI操作系統(tǒng):
3.1:調(diào)度,非搶占,不需重入
        調(diào)度是內(nèi)核的主要職責(zé)之一,就是要決定該輪到哪個任務(wù)運行了。多數(shù)實時內(nèi)核是基于優(yōu)先級調(diào)度法的。每個任務(wù)根據(jù)其重要程度的不同被賦予一定的優(yōu)先級?;趦?yōu)先級的調(diào)度法指,CPU 總是讓處在就緒態(tài)的優(yōu)先級最高的任務(wù)先運行。然而,究竟何時讓高優(yōu)先級任務(wù)掌握CPU 的使用權(quán),有兩種不同的情況,這要看用的是什么類型的內(nèi)核,是非搶占型的還是可搶占型內(nèi)核。
3.1.1 非搶占型內(nèi)核 (Non-Preemptive Kernel)
非搶占型內(nèi)核也叫做不可剝奪型內(nèi)核,不可剝奪型內(nèi)核要求每個任務(wù)自我放棄CPU 的所有權(quán)。不可剝奪型調(diào)度法也稱作合作型多任務(wù),各個任務(wù)彼此合作共享一個CPU。異步事件還是由中斷服務(wù)來處理。中斷服務(wù)可以使一個高優(yōu)先級的任務(wù)由掛起狀態(tài)變?yōu)榫途w狀態(tài)。但中斷服務(wù)以后控制權(quán)還是回到原來被中斷了的那個任務(wù),直到該任務(wù)主動放棄CPU 的使用權(quán)時,那個高優(yōu)先級的任務(wù)才能獲得CPU的使用權(quán)。
不可剝奪型內(nèi)核的一個優(yōu)點是響應(yīng)中斷快。在任務(wù)級,不可剝奪型內(nèi)核允許使用不可重入函數(shù)。每個任務(wù)都可以調(diào)用不可重入性函數(shù),而不必?fù)?dān)心其它任務(wù)可能正在使用該函數(shù)從而造成數(shù)據(jù)的破壞。因為每個任務(wù)要運行到完成時才釋放CPU 的控制權(quán)。
        使用不可剝奪型內(nèi)核時,任務(wù)級響應(yīng)時間比前后臺系統(tǒng)快得多。此時的任務(wù)級響應(yīng)時間取決于最長的任務(wù)執(zhí)行時間。不可剝奪型內(nèi)核的另一個優(yōu)點是,幾乎不需要使用信號量保護(hù)共享數(shù)據(jù)。運行著的任務(wù)占有CPU,而不必?fù)?dān)心被別的任務(wù)搶占。但這也不是絕對的,在某種情況下,信號量還是用得著的。處理共享I/O 設(shè)備時仍需要使用互斥型信號量。例如,在打印機(jī)的使用上,仍需要滿足互斥條件。
圖2 示意不可剝奪型內(nèi)核的運行情況,任務(wù)在運行過程之中,[2(1)]中斷來了,如果此時中斷是開著的,CPU 由中斷向量[2(2)]進(jìn)入中斷服務(wù)子程序,中斷服務(wù)子程序做事件處理[2(3)],使一個有更高級的任務(wù)進(jìn)入就緒態(tài)。中斷服務(wù)完成以后,中斷返回指令[2(4)], 使CPU 回到原來被中斷的任務(wù),接著執(zhí)行該任務(wù)的代碼[2(5)]直到該任務(wù)完成,調(diào)用一個內(nèi)核服務(wù)函數(shù)以釋放CPU 控制權(quán),由內(nèi)核將控制權(quán)交給那個優(yōu)先級更高的、并已進(jìn)入就緒態(tài)的任務(wù)[2(6)],這個優(yōu)先級更高的任務(wù)才開始處理中斷服務(wù)程序標(biāo)識的事件[2(7)]。

(原文件名:圖片2.jpg)


        不可剝奪型內(nèi)核的最大缺陷在于其響應(yīng)時間。高優(yōu)先級的任務(wù)已經(jīng)進(jìn)入就緒態(tài),但還不能運行,要等,也許要等很長時間,直到當(dāng)前運行著的任務(wù)釋放CPU。

3.1.2 可剝奪型內(nèi)核
當(dāng)系統(tǒng)響應(yīng)時間很重要時,要使用可剝奪型內(nèi)核。最高優(yōu)先級的任務(wù)一旦就緒,總能得到CPU 的控制權(quán)。當(dāng)一個運行著的任務(wù)使一個比它優(yōu)先級高的任務(wù)進(jìn)入了就緒態(tài),當(dāng)前任務(wù)的CPU 使用權(quán)就被剝奪了,或者說被掛起了,那個高優(yōu)先級的任務(wù)立刻得到了CPU 的控制權(quán)。如果是中斷服務(wù)子程序使一個高優(yōu)先級的任務(wù)進(jìn)入就緒態(tài),中斷完成時,中斷了的任務(wù)被掛起,優(yōu)先級高的那個任務(wù)開始運行。如圖3 所示。


(原文件名:圖片3.jpg)

使用可剝奪型內(nèi)核,最高優(yōu)先級的任務(wù)什么時候可以執(zhí)行,可以得到CPU 的控制權(quán)是可知的。
        3.1.3 LTOS的調(diào)度分析
        LTOS屬于非搶占型操作系統(tǒng),所以不必?fù)?dān)心函數(shù)重入的問題,也不必?fù)?dān)心臨界區(qū)的保護(hù)問題,對于沒有任何操作系統(tǒng)使用經(jīng)驗的人來說,學(xué)習(xí)并且分析LTOS操作系統(tǒng),將使你在最短的時間里對操作系統(tǒng)有一個整體的理解。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是一個全局變量,總是記錄著此刻處于就緒態(tài)的任務(wù),在LTOS中,所謂就緒態(tài)就是有事件等待處理的任務(wù)。osalNextActiveTask()該函數(shù)是用來尋找此刻有事件并且處于最高優(yōu)先級的任務(wù),該函數(shù)返回一個指針,指針指向最高優(yōu)先級有事件任務(wù)。
       
            if ( TaskActive )
如果TaskActive非空,即某個任務(wù)有事件,則進(jìn)入處理函數(shù)中
            {
              HAL_ENTER_CRITICAL_SECTION(intState);
宏,保存此時的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)慎,為了避免任何一個不可預(yù)知的錯誤,他們總是很小心。
              {
                // Call the task to process the event(s)
                if ( TaskActive->pfnEventProcessor )
如果TaskActive中的處理函數(shù)非空。TaskActive->pfnEventProcessor是一個指向任務(wù)的指針,即指向在osal_add_Task()中加入的APPLICATION中設(shè)計的FUNCTION()函數(shù),不能理解函數(shù)指針的讀者可以想閱讀一些關(guān)于指針的教程。
                {
                  retEvents= (TaskActive->pfnEventProcessor)( TaskActive->taskID, events );
運行函數(shù)(TaskActive->pfnEventProcessor)( TaskActive->taskID, events ),即運行函數(shù)FUNCTION()
                  // Add back unprocessed events to the current task
                  HAL_ENTER_CRITICAL_SECTION(intState);
宏,保存此時的EA寄存器值,然后關(guān)閉中斷
                  TaskActive->events |= retEvents;
        將函數(shù)的返回值重新賦值給TaskActive->events,這就是為什么任務(wù)函數(shù)執(zhí)行完之后,一定要將事件清空的原因。不清空高優(yōu)先級的任務(wù)中事件,高優(yōu)先級任務(wù)將一直運行,低優(yōu)先級任務(wù)得不到運行。
                  HAL_EXIT_CRITICAL_SECTION(intState);
宏,還原中斷值
                }
              }
            }
            // Complete pass through all task events with no activity?
       
          }
        }
        該函數(shù)就是整個程序的總調(diào)度,他總是在不停地從高到低掃描每一個任務(wù),當(dāng)任務(wù)中有事件時,就進(jìn)入到該任務(wù)中去執(zhí)行事件。進(jìn)入該調(diào)度函數(shù)后,程序就這樣無限運行下去,不會退出該調(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));
        申請一塊空間給新的任務(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;
                以下為將各個任務(wù)一一加入任務(wù)鏈表的操作方法,實現(xiàn)過程中使用了雙重指針,掌握起來會有些困難,不過只要在好好揣摩之,也不難理解。
                While(TaskSech)
                {
                        if(TaskNew->taskPriority > TaskSech->taskPriority)
                        {
                如果新任務(wù)的優(yōu)先級高于現(xiàn)在的任務(wù)
                                TaskNew->next = TaskSech;
                                *TaskPTR = TaskNew;
直接讓TaskNew放在TaskPTR指向的地址,TaskPTR先前指向的地址可能為前一個優(yōu)先級高的任務(wù),這里經(jīng)過比較優(yōu)先級后直接將TaskNew放入TaskPTR指向的地址,也就是讓TaskNew與比他優(yōu)先級低的任務(wù)交換了位置,讓比新任務(wù)優(yōu)先級的任務(wù)往后靠,而新任務(wù)放在了以前這個任務(wù)的地方。筆者在此是這樣理解,如有錯誤,請大家指正。
                                return;
                        }
                       
                        TaskPTR = &TaskSech->next;
                        TaskSech = TaskSech->next;
如果優(yōu)先級不高于以前的任務(wù),則繼續(xù)往后找       
                }
                        *TaskPTR = TaskNew;
                連表頭沒有任務(wù)時,直接讓新任務(wù)放入連表頭。新任務(wù)的優(yōu)先級不高于以往任何一個任務(wù)時,則新任務(wù)放于鏈表尾。第一次見識鏈表還能這樣子建立,這種寫法相當(dāng)精妙,讓人不得不感嘆老外做事之用心。
        }
        return;
       
}
       
3.3:事件設(shè)置函數(shù)
事件被設(shè)置只有一個函數(shù),就是osal_set_event(),但是設(shè)置的方式有三種:
1直接調(diào)用該函數(shù)為某個任務(wù)設(shè)置事件
2 使用延時設(shè)置事件函數(shù)
3 使用消息,消息會被LTOS默認(rèn)為是一個系統(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ù)號為task_id的節(jié)點,并讓srchTash指向該地址
task_id是需要增加事件的任務(wù)號,event_flag為需要設(shè)定的事件號,事件號一般設(shè)置成掩碼方式,比如0x0001,0x0020,0x0004等等,一個十六位的變量可以同時表示16的事件的置位與否,換句話來說,一個任務(wù)中事件數(shù)目不要超過15,讀者可能會疑惑了,剛剛明明說是16怎么一下又變成了15,其實是這樣的,0x8000這個掩碼已經(jīng)被默認(rèn)為系統(tǒng)事件了,所以供我們使用的還有15個掩碼。對于一些小型的工程,每個任務(wù)15個事件號足以應(yīng)對。但是筆者在實際應(yīng)用中發(fā)現(xiàn)有些任務(wù)確確實實需要多余15個的事件來完成整個任務(wù),這個時候該怎么辦呢?這也是有辦法的,將所有要處理的事件分門別類,一類為系統(tǒng)事件,一類為其余事件。系統(tǒng)事件的觸發(fā)是通過消息來完成的,任務(wù)自己給任務(wù)自己發(fā)送消息同樣是可以的,這樣系統(tǒng)事件就可以再利用消息中的變量來判斷更多的事件標(biāo)志從而達(dá)到更多事件的觸發(fā)了。但是筆者在移植該操作系統(tǒng)時,考慮到“弱功能”單片機(jī)的資源有限,并沒有將消息隊列移植出來。這就需要讀者在做項目規(guī)劃的時候就將各個任務(wù)分配好,考慮項目是劃分為3個任務(wù)還是4個任務(wù)或者更多。每個任務(wù)所做的事情不得超過十五個事件就可以了,或者使用全局變量定義標(biāo)志位,同樣可以達(dá)到擴(kuò)展事件的目的。
  if ( srchTask )
          {
如果找到了合適的節(jié)點
    // Hold off interrupts
        HAL_ENTER_CRITICAL_SECTION(intState);
宏,保存此時的EA寄存器值,然后關(guān)閉中斷
       
    // Stuff the event bit(s)
        srchTask->events |= event_flag;
將事件掩碼賦給節(jié)點中的events元素,請注意:上節(jié)中所講解的osalNextActiveTask()函數(shù)為什么能搜索到有事件的任務(wù)呢,就是因為這里給任務(wù)加了事件。
    // Release interrupts
        HAL_EXIT_CRITICAL_SECTION(intState);
宏,還原中斷值
       
     }
   else
    return ( INVALID_TASK );

  return ( ZSUCCESS );
        }
       
       
3.4:事件設(shè)置方式
在前面介紹了事件設(shè)置函數(shù),下面介紹設(shè)置事件的另外兩種方式。
3.4.1 延時為某個任務(wù)設(shè)置事件
        byte osal_start_timerEx( byte taskID, UINT16 event_id, UINT16 timeout_value )
        {
        在該函數(shù)中taskID是預(yù)設(shè)置事件的任務(wù)號,evnet_id為預(yù)設(shè)置的事件,timeout_value為延時多少個時間刻度再設(shè)置事件,實際延時的時間為timeout_value*(1/系統(tǒng)頻率)。比如系統(tǒng)頻率為100,即10ms周期。那么設(shè)置timeout_value為100,實際時間也就是1s。
          halIntState_t  intState;
          osalTimerRec_t  *newTimer;
          HAL_ENTER_CRITICAL_SECTION( intState );  // Hold off interrupts.
宏,保存此時的EA寄存器值,然后關(guān)閉中斷
          // Add timer
          newTimer = osalAddTimer( taskID, event_id, timeout_value );
        在此又要引入一個鏈表了------時間鏈表,時間鏈表的各個節(jié)點有三個主要元素:記錄時間的變量,記錄任務(wù)號的變量和記錄事件的變量。osalAddTimer函數(shù)就是以某個任務(wù)的任務(wù)號作為主要信息來增加節(jié)點,osalAddTimer函數(shù)先判斷這個任務(wù)在之前有無添加過時間節(jié)點并還沒有被利用,如果有則直接修改該時間節(jié)點將最新延時時間放入,如果這個任務(wù)之前沒有添加時間節(jié)點或時間節(jié)點已經(jīng)被利用并刪除,則重新申請空間并添加一個節(jié)點。添加完后返回該節(jié)點的地址。
          if ( newTimer )
          {
        如果添加成功
            // Does the timer need to be started?
            if ( timerActive == FALSE )
            {
        timerActive為一個全局變量,用來判斷定時器是否開啟了,這里如果沒有開啟,則馬上開啟定時器,使得定時器開始運作。
              osal_timer_activate( TRUE );
        定時器運作函數(shù),這個函數(shù)最終會被一個跟硬件定時器打交道的函數(shù)代替
            }
          }
       
          HAL_EXIT_CRITICAL_SECTION( intState );   // Re-enable interrupts.
宏,還原中斷值
       
          return ( (newTimer != NULL) ? ZSUCCESS : NO_TIMER_AVAIL );
        }
3.4.2 消息為某個任務(wù)添加事件,添加的事件為系統(tǒng)事件
byte osal_msg_send( byte destination_task, byte *msg_ptr )
        {
        destination_task為需要接收消息的任務(wù),msg_prt為指向消息的指針。在使用消息發(fā)送函數(shù)之前,要先使用osal_msg_allocate(len))來動態(tài)申請一個空間存放該消息,用完該消息后同樣得調(diào)用osal_msg_deallocate((uint8 *) pMsg)函數(shù)來釋放動態(tài)申請的空間。
          if ( msg_ptr == NULL )
            return ( INVALID_MSG_POINTER );
        消息為空的話直接返回錯誤指示信息
       
          if ( osalFindTask( destination_task ) == NULL )
          {
            osal_msg_deallocate( msg_ptr );
            return ( INVALID_TASK );
          }
        如果找不到需要接收消息的任務(wù),說明該函數(shù)絕對被錯誤調(diào)用了,另外直接釋放動態(tài)申請的空間
          // 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的指針指向地址不正確,直接釋放動態(tài)申請的空間。
          OSAL_MSG_ID( msg_ptr ) = destination_task;
       
          // queue message
          osal_msg_enqueue( &osal_qHead, msg_ptr );
        將信號放入消息隊列中
          // Signal the task that a message is waiting
          osal_set_event( destination_task, SYS_EVENT_MSG );
        設(shè)置為系統(tǒng)事件
          return ( ZSUCCESS );
        }
        事實上考慮到小型單片機(jī)code與xdata,RAM都比較小這個事實,我并沒有將消息以及消息隊列這個功能移植過來。其實利用延時設(shè)置事件、直接設(shè)置事件以及15個事件號已經(jīng)足以完成許多中小心的程序工程。再大點的工程可能小單片機(jī)也吃不消了,那個時候需要考慮的不是增加操作系統(tǒng)了 ,而是要考慮跟換單片機(jī)。
       
3.5:TICK,時間單位,硬件定時器
在介紹完任務(wù)和事件的加入之后,我們開始闡述一個隱蔽于LTOS操作系統(tǒng)身后但又十分重要的概念-----系統(tǒng)時鐘。
        LTOS需要用戶提供周期性信號源,用于實現(xiàn)事件的定時判斷事件延時時間到并將事件置位。節(jié)拍率應(yīng)在每秒10 次到1000 次之間,或者說10 到1000Hz。時鐘節(jié)拍率越高,系統(tǒng)的額外負(fù)荷就越重。時鐘節(jié)拍的實際頻率取決于用戶應(yīng)用程序的精度。TI在他們的zigbee協(xié)議棧TI-MAC1.2.1就是使用了1000HZ的時鐘頻率。在此提出一點異議請讀者注意,筆者主要講解的是將LTOS移植到“弱功能”單片機(jī)上使用,所以并不提倡使用太高精度的時鐘頻率,這樣會給小型單片機(jī)單來太多的額外負(fù)擔(dān),建議使用100HZ的系統(tǒng)時鐘。時鐘節(jié)拍源可以是專門的硬件定時器,也可以是一些外部信號源。

對于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.
宏,保存此時的EA寄存器值,然后關(guān)閉中斷
  // Update the system time
  osal_systemClock += updateTime;
osal_systemClock 是一個全局變量,用于知道系統(tǒng)從啟動到現(xiàn)在一直運行了多少個系統(tǒng)時鐘,updataTime是時間走動刻度,一般取1。該函數(shù)在系統(tǒng)定時器的中斷函數(shù)中調(diào)用。比如,系統(tǒng)的頻率設(shè)置為100,那么也就是定時器的時間設(shè)置成10MS,每10MS進(jìn)入一次定時器中斷,然后執(zhí)行該osalTimerUpdate()函數(shù)。

  // Look for open timer slot
  if ( timerHead != NULL )
  {
對時間鏈表頭的分析。頭不為空就進(jìn)入處理函數(shù)。那么時間鏈表頭怎么樣才不為空呢,也就是osalAddTimer()函數(shù)至少被調(diào)用了一次,換句話說,也就是只少有一個任務(wù)利用osal_start_timerEx()函數(shù)為其余任務(wù)或自己增加延時任務(wù),時間鏈表中的節(jié)點的結(jié)構(gòu)體中的timeout成員會被加入時間值來代表某個任務(wù)多久之后觸發(fā)特定事件。這里聽著可能有點繞口,不過慢慢往后看,就能明白。
    // 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)
如果時間鏈表中的某個節(jié)點結(jié)構(gòu)體中timeout 成員變量值已經(jīng)小于等于系統(tǒng)時間走動刻度,說明該節(jié)點的時間已到,也可以理解為到時候設(shè)置事件了。
        srchTimer->timeout = 0;
直接將該小于等于系統(tǒng)時間走動刻度的變量清零。
      else
        srchTimer->timeout = srchTimer->timeout - updateTime;
如果timeout 成員變量值還比較大,說明他的時間還沒有到,就必須減去系統(tǒng)時間走動刻度讓他一步步減小,如同時光一點點流逝。

      // When timeout, execute the task
      if ( srchTimer->timeout == 0 )
      {
時間已到的鏈表節(jié)點
        osal_set_event( srchTimer->task_id, srchTimer->event_flag );
給指定任務(wù)號的任務(wù)設(shè)置事件。
        // Take out of list
        if ( prevTimer == NULL )
          timerHead = srchTimer->next;
將該節(jié)點從鏈表中剔除
        else
          prevTimer->next = srchTimer->next;

        // Next
        saveTimer = srchTimer->next;

        // Free memory
        osal_mem_free( srchTimer );
由于時間鏈表中的節(jié)點空間是動態(tài)申請的,所以剔除完鏈表中的節(jié)點后,要將申請的空間釋放。
        srchTimer = saveTimer;
      }
      else
      {
        // Get next
        prevTimer = srchTimer;
        srchTimer = srchTimer->next;
向后繼續(xù)尋找
      }
        }
          }
       
          HAL_EXIT_CRITICAL_SECTION( intState );   // Re-enable interrupts.
        宏,還原中斷值
       
        }   
        任務(wù)的調(diào)度、事件的加入、系統(tǒng)時鐘中的時間刻度的走動,這些智慧碰撞于一起最終使一個系統(tǒng)動態(tài)地運作了起來。
3.6:內(nèi)存管理與分配
    在前面講解到的時間鏈表的建立、任務(wù)鏈表的建立都離不開內(nèi)存的動態(tài)分配與釋放,下面分析關(guān)于內(nèi)存管理的幾個函數(shù)。
在TI的zigbee程序TI-MAC1.2.1中,在XDATA定義了一個1024字節(jié)的數(shù)組,后續(xù)全部的動態(tài)空間申請與釋放都是在這個數(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;
賦零,便于在使用該動態(tài)內(nèi)存時判斷是否到了尾部,也就是說判斷是否還有空間能夠分配
  // Setup a small-block bucket.
  tmp = (osalMemHdr_t *)theHeap;
找到分配數(shù)組的頭
  *tmp = SMALLBLKHEAP;
LTOS中將動態(tài)分配的空間劃分為了兩塊,一個為小空間分配塊ff1,專門用來負(fù)責(zé)分配給小于16個字節(jié)的空間分配。另一塊為大空間分配塊ff2,用于分配給大于16個字節(jié)的空間分配。將SMALLBLKHEAP這個常數(shù)賦值給頭,SMALLBLKHEAP這個常數(shù)在TI-MAC1.2.1中設(shè)置為232,表示小空間分配塊有232個字節(jié)長度,大空間分配塊有(1024-232-2)個長度,為什么是1024-232-2而不是1024-232,讀者讀完下面的分析就能明白。如下圖4

(原文件名:圖片4.JPG)

被分配的空間總是比實際要分配的空間多HDRSZ個字節(jié),在8位單片機(jī)中,HDRSZ為2個字節(jié),前一個字節(jié)用來表示這個分配空間被使用了沒有,使用了就將該字節(jié)的最高位置一,沒使用就清零。后一個字節(jié)表示該空間的長度。所以由于232個字節(jié)的長度的分配空間占用了兩個字節(jié)的頭,此時就必須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分配一個0字節(jié)長度的空間,最后讓ff1指向小空間分配塊。這樣就巧妙地將小空間分配塊與大空間分配塊分開了。值得注意的是,當(dāng)小空間分配塊ff1的空間被耗盡后也可以繼續(xù)在大空間分配塊ff2中分配。
  ff2 = osal_mem_alloc( 0 );
  ff1 = (osalMemHdr_t *)theHeap;

}

對于分配內(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)志頭的長度為兩個字節(jié),第一個字節(jié)表示該空間被使用沒,第二個字節(jié)表示申請的長度
  // 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);
    }
  }
以上是對于size長度的一些計算,主要是針對不同位數(shù)的處理器,使size作一個靠齊處理。
  HAL_ENTER_CRITICAL_SECTION( intState );  // Hold off interrupts.
宏,保存此時的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,也就是上面介紹的頭兩個字節(jié)
  do
  {
    if ( tmp & OSALMEM_IN_USE )
    判斷tmp的最高位是否被置一了,也就是判斷這塊空間是否被用了,如果被用了則繼續(xù)往后尋找。
        {
      tmp ^= OSALMEM_IN_USE;
      coal = 0;
當(dāng)空間被動態(tài)分配和釋放多次以后,就會形成一種每個動態(tài)分配內(nèi)存機(jī)制都會碰到的問題----內(nèi)存碎片,當(dāng)程序要分配一個連續(xù)的大空間時,就需要將以前被分散的小空間聯(lián)合起來,coal這個變量就是用在將幾塊連續(xù)打散的空間合并的判斷變量,判斷coal即可知道內(nèi)存快是否連續(xù)。如下圖5中,紅色的塊表示未使用的塊,黃色的塊表示已經(jīng)使用的塊,如果要在這個空間中分配大小為3的內(nèi)存,就必須跨過這些黃色的塊,使用最后一個連續(xù)的紅塊。而前面的紅塊成為了內(nèi)存碎片。


(原文件名:圖片5.JPG)

}
    else
    {
      if ( coal != 0 )
      {

        *prev += *hdr;
這里的一段程序主要是用來判斷如何分配一塊合適的內(nèi)存給應(yīng)用程序,當(dāng)內(nèi)存區(qū)沒有內(nèi)存碎片時,這時申請內(nèi)存就直接到內(nèi)存分配塊分配,這個時候不用操心內(nèi)存碎片以及內(nèi)存合并的事,直截了當(dāng)分配就是。一旦需要分配大的內(nèi)存并且存在內(nèi)存碎片時,就必須操心更多的事了。判斷以下幾種情況:
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))
  {
下面的程序主要將被分配的空間與剛才分配的實際空間作一個差值,比較這個差值的大小。如果這個值很大,說明這塊空間還有很多能夠利用的余地,于是先將這個空間重新分割開以便接下來的繼續(xù)分配使用,然后在已經(jīng)分配的空間上打上已用的標(biāo)記;如果這個值不大,說明空間利用的差不多了,還算合理了,就不分割,直接打上已用的標(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ù)相對于申請空間的函數(shù)要簡單許多,直接將欲釋放空間的頭的被使用標(biāo)志清除了即可
  if ( ff1 > currHdr )
  {
這里可能會讓不少讀者疑惑:ff1為數(shù)組的頭,怎么也不可能指向比currHdr還靠后的的地址,怎么可能大于currHdr呢。其實這是有可能的,上面已經(jīng)提到,當(dāng)ff1的空間被分配完時,是可以通過osal_mem_kick( void )函數(shù),讓ff1指向ff2的地址繼續(xù)分配的,這種情況下,ff1被移動到了后面去了,但是currHdr可能是ff1在數(shù)組頭時分配的一塊空間,這時必須讓ff1前移來表示著ff1處繼續(xù)有可用空間了。

    ff1 = currHdr;
  }
  HAL_EXIT_CRITICAL_SECTION( intState );  // Re-enable interrupts.
}

五 內(nèi)核移植:
        筆者將LTOS移植出來了,放入STC12C60S2中運行起來,LTOS工程中建立了六個任務(wù),每個任務(wù)負(fù)責(zé)點亮一個由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:芯片定時器實現(xiàn)內(nèi)核TICK
        定時器的更改,本例子中利用STC12C60S2的PCA作為系統(tǒng)定時器
        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個字節(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)驗并不是特別豐富,所以以上講解中難免不出現(xiàn)錯誤或有出入的地方,望各位經(jīng)驗豐富的研究人員和學(xué)者加以指正。                                    
          最后,談一點點我個人對于技術(shù)研發(fā)這個職位的看法,做為技術(shù)人員,大家都覺得工資高,工作穩(wěn)定,還能學(xué)到很多        的東西。是大部份走出校門或性格內(nèi)向,或希望過平靜生活的人的必然選擇。其實,你們有沒有問過自己,這條路到底走對了嗎? 一個剛畢業(yè)的大學(xué)生,從事銷售和從事技術(shù)兩種不同的工作,可能工資的差距會達(dá)到數(shù)倍之遠(yuǎn)。對于初出校門的人來說,不無一種極端的誘惑力。剛畢業(yè)的年青人,當(dāng)然會果斷的選擇技術(shù)之路。 兩年后,我們再看看,由于經(jīng)驗的積累,做業(yè)務(wù)的積累了部份客戶資源,做技術(shù)的積累了好的經(jīng)驗,在各自的領(lǐng)域內(nèi)都大展開了手腳,收入也基本接近了。 再以后呢,技術(shù)之路越來越難走,畢竟做技術(shù)需要的大量的時間和精力,否則就跟不上現(xiàn)在時代的技術(shù)更新了,做業(yè)務(wù)的呢,客戶群越來越大,經(jīng)驗越來越豐富,誰的收入會更高? 兩種不同的職業(yè),它們有著各自不同的特點,技術(shù)行業(yè)是個撐不死,飽不了的地方,而銷售行業(yè)則是沒有盡頭的發(fā)展之路。 過了三十歲,大家會選擇什么呢,結(jié)婚、生子,人生的一條老路, 到了三十歲,你還有自信面對繁重的工作嗎?你有剛出社會的人的活力嗎?你能和他們比工作時間,玩命地在老板面前表現(xiàn)嗎?你能丟下妻兒出差一、兩個月嗎?
        比之于我們的生活和人際關(guān)系及工作,那些從事售前和市場開發(fā)的朋友,卻有比我們多的多的工作之外的時間,甚至他們工作的時間有的時候是和生活的時間是可以兼顧的,他們可以通過市場開發(fā),認(rèn)識各個行業(yè)的人士,可以認(rèn)識各種各樣的朋友,他們比我們坦率說更有發(fā)財和發(fā)展的機(jī)會,只要他們跟我們一樣勤奮。(有一種勤奮的普通人,如果給換個地方,他馬上會成為一個勤奮且出眾的人。)      有人會說,我有了技術(shù)!     技術(shù)經(jīng)驗是什么?一些老的,過去了的東西,他代表著你所留戀的過去,你所放不下的那一部份,你會以經(jīng)驗來判別事物,選擇工作方法。在新老技術(shù)交替的時間內(nèi),經(jīng)驗可以起到承前啟后的作用,讓你威風(fēng)八面??墒?,你還會用到多少十年以前的芯片呢? 大家所掌握的技術(shù)終會過時,腦子僵化的時候總會到來。那時,你何去何從?
           當(dāng)然,我并不是說技術(shù)就一無是處了,許多做技術(shù)的朋友都是貧苦出生,也有許多的朋友是不得以慢慢走上這條道路,也許有的朋友完全出于興趣愛好,出于興趣愛好的我先撇開不談,因為愛好這個東西可以使你無欲無求而只求與它。我們都需要靠自己的雙手去創(chuàng)造未來,我只是想說不要一輩子只靠技術(shù)活著,不要一輩子只會一點技術(shù)。其實做技術(shù)的人往往思維慎密,做事謹(jǐn)慎,并且由于以前有很相當(dāng)長時間的技術(shù)積累,以至于很容易跟討論技術(shù)的客戶溝通起來,所以說做技術(shù)的人很應(yīng)該多拿出一點時間和精力去培養(yǎng)別的才能(比如口才,銷售,管理)。我只是奉勸那些學(xué)習(xí)技術(shù)的朋友,千萬不要拿科舉考試樣的心態(tài)去學(xué)習(xí)技術(shù),對技術(shù)的學(xué)習(xí)幾近的癡迷,想掌握所有所有的技術(shù),以讓自己成為技術(shù)領(lǐng)域的權(quán)威和專家,但是我們的國家沒有那么多的研究院去容下我們這么多的高手,等到年紀(jì)來了就開始感嘆懷才不遇了。       技術(shù)僅僅是一個工具,是你在人生一個階段生存的工具,你可以一輩子喜歡他,但最好不要一輩子靠它生存。擺弄電子是我的愛好,也是一個工具,我跟大家一樣每天像毒癮犯了似的偷偷跑到Ourdev論壇里瞄瞄有無新的帖子,也潛水很久,今天終于按捺不住寫出一些東東,僅屬于個人愚見,歡迎大家拍磚,。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多