新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 牛人業(yè)話 > 51單片機(jī)多任務(wù)操作系統(tǒng)的原理與實(shí)現(xiàn)

51單片機(jī)多任務(wù)操作系統(tǒng)的原理與實(shí)現(xiàn)

作者: 時(shí)間:2017-01-06 來(lái)源:網(wǎng)絡(luò) 收藏

  前言

本文引用地址:http://2s4d.com/article/201701/342566.htm

  想了很久,要不要寫這篇文章?最后覺(jué)得對(duì)感興趣的人還是很多,寫吧.我不一定能造出玉,但我可以拋出磚.

  包括我在內(nèi)的很多人都對(duì)使用呈悲觀態(tài)度,因?yàn)?a class="contentlabel" href="http://2s4d.com/news/listbylabel/label/51">51的片上資源太少.但對(duì)于很多要求不高的系統(tǒng)來(lái)說(shuō),使用可以使代碼變得更直觀,易于維護(hù),所以在上仍有操作系統(tǒng)的生存機(jī)會(huì).

  流行的uCos,Tiny51等,其實(shí)都不適合在2051這樣的片子上用,占資源較多,唯有自已動(dòng)手,以不變應(yīng)萬(wàn)變,才能讓51也有操作系統(tǒng)可用.這篇貼子的目的,是教會(huì)大家如何現(xiàn)場(chǎng)寫一個(gè)OS,而不是給大家提供一個(gè)OS版本.提供的所有代碼,也都是示例代碼,所以不要因?yàn)樗鼪](méi)什么功能就說(shuō)LAJI之類的話.如果把功能寫全了,一來(lái)估計(jì)你也不想看了,二來(lái)也失去靈活性沒(méi)有價(jià)值了.

  下面的貼一個(gè)示例出來(lái),可以清楚的看到,OS本身只有不到10行源代碼,編譯后的目標(biāo)代碼60字節(jié),任務(wù)切換消耗為20個(gè)機(jī)器周期.相比之下,KEIL內(nèi)嵌的TINY51目標(biāo)代碼為800字節(jié),切換消耗100~700周期.唯一不足之處是,每個(gè)任務(wù)要占用掉十幾字節(jié)的堆棧,所以任務(wù)數(shù)不能太多,用在128B內(nèi)存的51里有點(diǎn)難度,但對(duì)于52來(lái)說(shuō)問(wèn)題不大.這套代碼在36M主頻的STC12C4052上實(shí)測(cè),切換任務(wù)僅需2uS.

  #include

  #define MAX_TASKS 2 //任務(wù)槽個(gè)數(shù).必須和實(shí)際任務(wù)數(shù)一至

  #define MAX_TASK_DEP 12 //最大棧深.最低不得少于2個(gè),保守值為12.

  unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP]; //任務(wù)堆棧.

  unsigned char task_id; //當(dāng)前活動(dòng)任務(wù)號(hào)

  //任務(wù)切換函數(shù)(任務(wù)調(diào)度器)

  void task_switch(){

  task_sp[task_id] = SP;

  if(++task_id == MAX_TASKS)

  task_id = 0;

  SP = task_sp[task_id];

  }

  //任務(wù)裝入函數(shù).將指定的函數(shù)(參數(shù)1)裝入指定(參數(shù)2)的任務(wù)槽中.如果該槽中原來(lái)就有任務(wù),則原任務(wù)丟失,但系統(tǒng)本身不會(huì)發(fā)生錯(cuò)誤.

  void task_load(unsigned int fn, unsigned char tid)

  {

  task_sp[tid] = task_stack[tid] + 1;

  task_stack[tid][0] = (unsigned int)fn & 0xff;

  task_stack[tid][1] = (unsigned int)fn >> 8;

  }

  //從指定的任務(wù)開(kāi)始運(yùn)行任務(wù)調(diào)度.調(diào)用該宏后,將永不返回.

  #define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}

  /*======================以下為測(cè)試代碼======================*/

  void task1()

  {

  static unsigned char i;

  while(1){

  i++;

  task_switch(); //編譯后在這里打上斷點(diǎn)

  }

  }

  void task2()

  {

  static unsigned char j;

  while(1){

  j+=2;

  task_switch(); //編譯后在這里打上斷點(diǎn)

  }

  }

  void main()

  {

  //這里裝載了兩個(gè)任務(wù),因此在定義MAX_TASKS時(shí)也必須定義為2

  task_load(task1, 0); //將task1函數(shù)裝入0號(hào)槽

  task_load(task2, 1); //將task2函數(shù)裝入1號(hào)槽

  os_start(0);

  }

  這樣一個(gè)簡(jiǎn)單的多任務(wù)系統(tǒng)雖然不能稱得上真正的操作系統(tǒng),但只要你了解了它的原理,就能輕易地將它擴(kuò)展得非常強(qiáng)大,想知道要如何做嗎?

  一.什么是操作系統(tǒng)?

  人腦比較容易接受"類比"這種表達(dá)方式,我就用"公交系統(tǒng)"來(lái)類比"操作系統(tǒng)"吧.

  當(dāng)我們要解決一個(gè)問(wèn)題的時(shí)候,是用某種處理手段去完成它,這就是我們常說(shuō)的"方法",計(jì)算機(jī)里叫"程序"(有時(shí)候也可以叫它"算法").

  以出行為例,當(dāng)我們要從A地走到B地的時(shí)候,可以走著去,也可以飛著去,可以走直線,也可以繞彎路,只要能從A地到B地,都叫作方法.這種從A地到B的需求,相當(dāng)于計(jì)算機(jī)里的"任務(wù)",而實(shí)現(xiàn)從A地到B地的方法,叫作"任務(wù)處理流程"

  很顯然,這些走法中,并不是每種都合理,有些傻子都會(huì)采用的,有些是傻子都不采會(huì)用的.用計(jì)算機(jī)的話來(lái)說(shuō)就是,有的任務(wù)處理流程好,有的任務(wù)處理流程好,有的處理流程差.

  可以歸納出這么幾種真正算得上方法的方法:

  有些走法比較快速,適合于趕時(shí)間的人;有些走法比較省事,適合于懶人;有些走法比較便宜,適合于窮人.

  用計(jì)算機(jī)的話說(shuō)就是,有些省CPU,有些流程簡(jiǎn)單,有些對(duì)系統(tǒng)資源要求低.

  現(xiàn)在我們可以看到一個(gè)問(wèn)題:

  如果全世界所有的資源給你一個(gè)人用(單任務(wù)獨(dú)占全部資源),那最適合你需求的方法就是好方法.但事實(shí)上要外出的人很多,例如10個(gè)人(10個(gè)任務(wù)),卻只有1輛車(1套資源),這叫作"資源爭(zhēng)用".

  如果每個(gè)人都要使用最適合他需求的方法,那司機(jī)就只好給他們一人跑一趟了,而在任一時(shí)刻里,車上只有一個(gè)乘客.這叫作"順序執(zhí)行",我們可以看到這種方法對(duì)系統(tǒng)資源的浪費(fèi)是嚴(yán)重的.

  如果我們沒(méi)有法力將1臺(tái)車變成10臺(tái)車來(lái)送這10個(gè)人,就只好制定一些機(jī)制和約定,讓1臺(tái)車看起來(lái)像10臺(tái)車,來(lái)解決這個(gè)問(wèn)題的辦法想必大家都知道,那就是制定公交線路.

  最簡(jiǎn)單的辦法是將所有旅客需要走的起點(diǎn)與終點(diǎn)串成一條線,車在這條線上開(kāi),乘客則自已決定上下車.這就是最簡(jiǎn)單的公交線路.它很差勁,但起碼解決客人們對(duì)車爭(zhēng)用.對(duì)應(yīng)到計(jì)算機(jī)里,就是把所有任務(wù)的代碼混在一起執(zhí)行.

  這樣做既不優(yōu)異雅,也沒(méi)效率,于是司機(jī)想了個(gè)辦法,把這些客戶叫到一起商量,將所有客人出行的起點(diǎn)與終點(diǎn)羅列出來(lái),統(tǒng)計(jì)這些線路的使用頻度,然后制定出公交線路:有些路線可以合并起來(lái)成為一條線路,而那些不能合并的路線,則另行開(kāi)辟行車車次,這叫作"任務(wù)定義".另外,對(duì)于人多路線,車次排多點(diǎn),時(shí)間上也優(yōu)先安排,這叫作"任務(wù)優(yōu)先級(jí)".

  經(jīng)過(guò)這樣的安排后,雖然仍只有一輛車,但運(yùn)載能力卻大多了.這套車次/路線的按排,就是一套"公交系統(tǒng)".哈,知道什么叫操作系統(tǒng)了吧?它也就是這么樣的一種約定.

  操作系統(tǒng):

  我們先回過(guò)頭歸納一下:

  汽車 系統(tǒng)資源.主要指的是CPU,當(dāng)然還有其它,比如內(nèi)存,定時(shí)器,中斷源等.

  客戶出行 任務(wù)

  正在走的路線 進(jìn)程

  一個(gè)一個(gè)的運(yùn)送旅客 順序執(zhí)行

  同時(shí)運(yùn)送所有旅客 多任務(wù)并行

  按不同的使用頻度制定路線并優(yōu)先跑較繁忙的路線 任務(wù)優(yōu)先級(jí)

  計(jì)算機(jī)內(nèi)有各種資源,單從硬件上說(shuō),就有CPU,內(nèi)存,定時(shí)器,中斷源,I/O端口等.而且還會(huì)派生出來(lái)很多軟件資源,例如消息池.

  操作系統(tǒng)的存在,就是為了讓這些資源能被合理地分配.

  最后我們來(lái)總結(jié)一下,所謂操作系統(tǒng),以我們目前權(quán)宜的理解就是:為"解決計(jì)算機(jī)資源爭(zhēng)用而制定出的一種約定".

  二.51上的操作系統(tǒng)

  對(duì)于一個(gè)操作系統(tǒng)來(lái)說(shuō),最重要的莫過(guò)于并行多任務(wù).在這里要澄清一下,不要拿當(dāng)年的DOS來(lái)說(shuō)事,時(shí)代不同了.況且當(dāng)年IBM和小比爾著急將PC搬上市,所以才抄襲PLM(好象是叫這個(gè)名吧?記不太清)搞了個(gè)今天看來(lái)很"粗制濫造"的DOS出來(lái).看看當(dāng)時(shí)真正的操作系統(tǒng)---UNIX,它還在紙上時(shí)就已經(jīng)是多任務(wù)的了.

  對(duì)于我們PC來(lái)說(shuō),要實(shí)現(xiàn)多任務(wù)并不是什么問(wèn)題,但換到MCU卻很頭痛:

  1.系統(tǒng)資源少

  在PC上,CPU主頻以G為單位,內(nèi)存以GB為單位,而MCU的主頻通常只有十幾M,內(nèi)存則是Byts.在這么少的資源上同時(shí)運(yùn)行多個(gè)任務(wù),就意味著操作系統(tǒng)必須盡可能的少占用硬件資源.

  2.任務(wù)實(shí)時(shí)性要求高

  PC并不需要太關(guān)心實(shí)時(shí)性,因?yàn)镻C上幾乎所有的實(shí)時(shí)任務(wù)都被專門的硬件所接管,例如所有的聲卡網(wǎng)卡顯示上都內(nèi)置有DSP以及大量的緩存.CPU只需坐在那里指手劃腳告訴這些板卡如何應(yīng)付實(shí)時(shí)信息就行了.

  而MCU不同,實(shí)時(shí)信息是靠CPU來(lái)處理的,緩存也非常有限,甚至沒(méi)有緩存.一旦信息到達(dá),CPU必須在極短的時(shí)間內(nèi)響應(yīng),否則信息就會(huì)丟失.

  就拿串口通信來(lái)舉例,在標(biāo)準(zhǔn)的PC架構(gòu)里,巨大的內(nèi)存允許將信息保存足夠長(zhǎng)的時(shí)間.而對(duì)于MCU來(lái)說(shuō)內(nèi)存有限,例如51僅有128字節(jié)內(nèi)存,還要扣除掉寄存器組占用掉的8~32個(gè)字節(jié),所以通常都僅用幾個(gè)字節(jié)來(lái)緩沖.當(dāng)然,你可以將數(shù)據(jù)的接收與處理的過(guò)程合并,但對(duì)于一個(gè)操作系統(tǒng)來(lái)說(shuō),不推薦這么做.

  假定以115200bps通信速率向MCU傳數(shù)據(jù),則每個(gè)字節(jié)的傳送時(shí)間約為9uS,假定緩存為8字節(jié),則串口處理任務(wù)必須在70uS內(nèi)響應(yīng).

  這兩個(gè)問(wèn)題都指向了同一種解決思路:操作系統(tǒng)必須輕量輕量再輕量,最好是不占資源(那當(dāng)然是做夢(mèng)啦).

  可用于MCU的操作系統(tǒng)很多,但適合51(這里的51專指無(wú)擴(kuò)展內(nèi)存的51)幾乎沒(méi)有.前陣子見(jiàn)過(guò)一個(gè)"圈圈操作系統(tǒng)",那是我所見(jiàn)過(guò)的操作系統(tǒng)里最輕量的,但仍有改進(jìn)的余地.

  很多人認(rèn)為,51根本不適合使用操作系統(tǒng).其實(shí)我對(duì)這種說(shuō)法并不完全接受,否則也沒(méi)有這篇文章了.

  我的看法是,51不適合采用"通用操作系統(tǒng)".所謂通用操作系統(tǒng)就是,不論你是什么樣的應(yīng)用需求,也不管你用什么芯片,只要你是51,通通用同一個(gè)操作系統(tǒng).

  這種想法對(duì)于PC來(lái)說(shuō)沒(méi)問(wèn)題,對(duì)于嵌入式來(lái)說(shuō)也不錯(cuò),對(duì)AVR來(lái)說(shuō)還湊合,而對(duì)于51這種"貧窮型"的MCU來(lái)說(shuō),不行.

  怎樣行?量體裁衣,現(xiàn)場(chǎng)根據(jù)需求構(gòu)建一個(gè)操作系統(tǒng)出來(lái)!

  看到這里,估計(jì)很多人要翻白眼了,大體上兩種:

  1.操作系統(tǒng)那么復(fù)雜,說(shuō)造就造,當(dāng)自已是神了?

  2.操作系統(tǒng)那么復(fù)雜,現(xiàn)場(chǎng)造一個(gè)會(huì)不會(huì)出BUG?

  哈哈,看清楚了?問(wèn)題出在"復(fù)雜"上面,如果操作系統(tǒng)不復(fù)雜,問(wèn)題不就解決了?

  事實(shí)上,很多人對(duì)操作系統(tǒng)的理解是片面的,操作系統(tǒng)不一定要做得很復(fù)雜很全面,就算僅個(gè)多任務(wù)并行管理能力,你也可以稱它操作系統(tǒng).

  只要你對(duì)多任務(wù)并行的原理有所了解,就不難現(xiàn)場(chǎng)寫一個(gè)出來(lái),而一旦你做到了這一點(diǎn),為各任務(wù)間安排通信約定,使之發(fā)展成一個(gè)為你的應(yīng)用系統(tǒng)量身定做的操作系統(tǒng)也就不難了.

  為了加深對(duì)操作系統(tǒng)的理解,可以看一看<<演變>>這份PPT,讓你充分了解一個(gè)并行多任務(wù)是如何一步步從順序流程演變過(guò)來(lái)的.里面還提到了很多人都在用的"狀態(tài)機(jī)",你會(huì)發(fā)現(xiàn)操作系統(tǒng)跟狀態(tài)機(jī)從原理上其實(shí)是多么相似.會(huì)用狀態(tài)機(jī)寫程序,都能寫出操作系統(tǒng).

  三.我的第一個(gè)操作系統(tǒng)

  直接進(jìn)入主題,先貼一個(gè)操作系統(tǒng)的示范出來(lái).大家可以看到,原來(lái)操作系統(tǒng)可以做得么簡(jiǎn)單.

  當(dāng)然,這里要申明一下,這玩意兒其實(shí)算不上真正的操作系統(tǒng),它除了并行多任務(wù)并行外根本沒(méi)有別的功能.但凡事都從簡(jiǎn)單開(kāi)始,搞懂了它,就能根據(jù)應(yīng)用需求,將它擴(kuò)展成一個(gè)真正的操作系統(tǒng).

  好了,代碼來(lái)了.

  將下面的代碼直接放到KEIL里編譯,在每個(gè)task?()函數(shù)的"task_switch();"那里打上斷點(diǎn),就可以看到它們的確是"同時(shí)"在執(zhí)行的.

  #include

  #define MAX_TASKS 2 //任務(wù)槽個(gè)數(shù).必須和實(shí)際任務(wù)數(shù)一至

  #define MAX_TASK_DEP 12 //最大棧深.最低不得少于2個(gè),保守值為12.

  unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];//任務(wù)堆棧.

  unsigned char task_id; //當(dāng)前活動(dòng)任務(wù)號(hào)


上一頁(yè) 1 2 3 下一頁(yè)

關(guān)鍵詞: 51 操作系統(tǒng)

評(píng)論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉