一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

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

 昵稱7503466 2012-06-01
本人從事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è)人愚見,歡迎大家拍磚,。

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

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    丝袜视频日本成人午夜视频| 日本加勒比系列在线播放| 国产成人精品在线播放| 久久青青草原中文字幕| 黄色片国产一区二区三区| 免费一级欧美大片免费看| 免费观看潮喷到高潮大叫 | 久久少妇诱惑免费视频| 中文字幕有码视频熟女| 欧美午夜一区二区福利视频| 亚洲精品福利视频在线观看| 国产精品久久久久久久久久久痴汉| 国产又猛又黄又粗又爽无遮挡| 中文字幕亚洲精品人妻| 国产毛片av一区二区三区小说| 国产精品九九九一区二区| 久久精品少妇内射毛片| 午夜福利直播在线视频| 精品视频一区二区不卡| 亚洲中文字幕三区四区| 国产av一二三区在线观看| 91插插插外国一区二区| 日本熟女中文字幕一区| 国产成人高清精品尤物| 视频一区二区黄色线观看| 国产一区二区三中文字幕| 99一级特黄色性生活片| 日本人妻丰满熟妇久久| 日韩特级黄片免费在线观看| 国产精品熟女在线视频| 国产美女精品午夜福利视频| 久久人人爽人人爽大片av| 国产欧美一区二区色综合| 精品国产av一区二区三区不卡蜜 | 深夜视频成人在线观看| 99热九九热这里只有精品| 国产又色又爽又黄又免费| 欧美日韩乱码一区二区三区| 欧洲日本亚洲一区二区| 精品人妻少妇二区三区| 91精品国产综合久久不卡|