




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
1、.51單片機多任務操作系統(tǒng)的原理與實現(xiàn) 51單片機多任務操作系統(tǒng)的原理與實現(xiàn)- 一個超輕量級的操作系統(tǒng)前言想了很久,要不要寫這篇文章?最后覺得對操作系統(tǒng)感興趣的人還是很多,寫吧.我不一定能造出玉,但我可以拋出磚.包括我在內(nèi)的很多人都對51使用操作系統(tǒng)呈悲觀態(tài)度,因為51的片上資源太少.但對于很多要求不高的系統(tǒng)來說,使用操作系統(tǒng)可以使代碼變得更直觀,易于維護,所以在51上仍有操作系統(tǒng)的生存機會. 流行的uCos,Tiny51等,其實都不適合在2051這樣的片子上用,占資源較多,唯有自已動手,以不變應萬變,才能讓51也有操作系統(tǒng)可用.這篇貼子的目的,是教會大家如何現(xiàn)場寫一個OS,而不是給大家提供一
2、個OS版本.提供的所有代碼,也都是示例代碼,所以不要因為它沒什么功能就說LAJI之類的話.如果把功能寫全了,一來估計你也不想看了,二來也失去靈活性沒有價值了. 下面的貼一個示例出來,可以清楚的看到,OS本身只有不到10行源代碼,編譯后的目標代碼60字節(jié),任務切換消耗為20個機器周期.相比之下,KEIL內(nèi)嵌的TINY51目標代碼為800字節(jié),切換消耗100700周期.唯一不足之處是,每個任務要占用掉十幾字節(jié)的堆棧,所以任務數(shù)不能太多,用在128B內(nèi)存的51里有點難度,但對于52來說問題不大.這套代碼在36M主頻的STC12C4052上實測,切換任務僅需2uS. #include #define
3、MAX_TASKS 2 /任務槽個數(shù).必須和實際任務數(shù)一至 #define MAX_TASK_DEP 12 /最大棧深.最低不得少于2個,保守值為12. unsigned char idata task_stackMAX_TASKSMAX_TASK_DEP;/任務堆棧. unsigned char task_id; /當前活動任務號 /任務切換函數(shù)(任務調(diào)度器) void task_switch() task_sptask_id = SP; if(+task_id = MAX_TASKS) task_id = 0; SP = task_sptask_id; /任務裝入函數(shù).將指定的函數(shù)(參數(shù)1
4、)裝入指定(參數(shù)2)的任務槽中.如果該槽中原來就有任務,則原任務丟失,但系統(tǒng)本身不會發(fā)生錯誤. void task_load(unsigned int fn, unsigned char tid) task_sptid = task_stacktid + 1; task_stacktid0 = (unsigned int)fn & 0xff; task_stacktid1 = (unsigned int)fn 8; /從指定的任務開始運行任務調(diào)度.調(diào)用該宏后,將永不返回. #define os_start(tid) task_id = tid,SP = task_sptid;return; /
5、*=以下為測試代碼=*/ void task1() static unsigned char i; while(1) i+; task_switch();/編譯后在這里打上斷點 void task2() static unsigned char j; while(1) j+=2; task_switch();/編譯后在這里打上斷點 void main() /這里裝載了兩個任務,因此在定義MAX_TASKS時也必須定義為2 task_load(task1, 0);/將task1函數(shù)裝入0號槽 task_load(task2, 1);/將task2函數(shù)裝入1號槽 os_start(0); 這樣一個
6、簡單的多任務系統(tǒng)雖然不能稱得上真正的操作系統(tǒng),但只要你了解了它的原理,就能輕易地將它擴展得非常強大,想知道要如何做嗎?一.什么是操作系統(tǒng)?人腦比較容易接受類比這種表達方式,我就用公交系統(tǒng)來類比操作系統(tǒng)吧. 當我們要解決一個問題的時候,是用某種處理手段去完成它,這就是我們常說的方法,計算機里叫程序(有時候也可以叫它算法). 以出行為例,當我們要從A地走到B地的時候,可以走著去,也可以飛著去,可以走直線,也可以繞彎路,只要能從A地到B地,都叫作方法.這種從A地到B的需求,相當于計算機里的任務,而實現(xiàn)從A地到B地的方法,叫作任務處理流程 很顯然,這些走法中,并不是每種都合理,有些傻子都會采用的,有些
7、是傻子都不采會用的.用計算機的話來說就是,有的任務處理流程好,有的任務處理流程好,有的處理流程差. 可以歸納出這么幾種真正算得上方法的方法: 有些走法比較快速,適合于趕時間的人;有些走法比較省事,適合于懶人;有些走法比較便宜,適合于窮人. 用計算機的話說就是,有些省CPU,有些流程簡單,有些對系統(tǒng)資源要求低. 現(xiàn)在我們可以看到一個問題: 如果全世界所有的資源給你一個人用(單任務獨占全部資源),那最適合你需求的方法就是好方法.但事實上要外出的人很多,例如10個人(10個任務),卻只有1輛車(1套資源),這叫作資源爭用. 如果每個人都要使用最適合他需求的方法,那司機就只好給他們一人跑一趟了,而在任
8、一時刻里,車上只有一個乘客.這叫作順序執(zhí)行,我們可以看到這種方法對系統(tǒng)資源的浪費是嚴重的. 如果我們沒有法力將1臺車變成10臺車來送這10個人,就只好制定一些機制和約定,讓1臺車看起來像10臺車,來解決這個問題的辦法想必大家都知道,那就是制定公交線路. 最簡單的辦法是將所有旅客需要走的起點與終點串成一條線,車在這條線上開,乘客則自已決定上下車.這就是最簡單的公交線路.它很差勁,但起碼解決客人們對車爭用.對應到計算機里,就是把所有任務的代碼混在一起執(zhí)行. 這樣做既不優(yōu)異雅,也沒效率,于是司機想了個辦法,把這些客戶叫到一起商量,將所有客人出行的起點與終點羅列出來,統(tǒng)計這些線路的使用頻度,然后制定出
9、公交線路:有些路線可以合并起來成為一條線路,而那些不能合并的路線,則另行開辟行車車次,這叫作任務定義.另外,對于人多路線,車次排多點,時間上也優(yōu)先安排,這叫作任務優(yōu)先級. 經(jīng)過這樣的安排后,雖然仍只有一輛車,但運載能力卻大多了.這套車次/路線的按排,就是一套公交系統(tǒng).哈,知道什么叫操作系統(tǒng)了吧?它也就是這么樣的一種約定. 操作系統(tǒng): 我們先回過頭歸納一下: 汽車 系統(tǒng)資源.主要指的是CPU,當然還有其它,比如內(nèi)存,定時器,中斷源等.客戶出行 任務正在走的路線 進程 一個一個的運送旅客 順序執(zhí)行 同時運送所有旅客 多任務并行按不同的使用頻度制定路線并優(yōu)先跑較繁忙的路線 任務優(yōu)先級 計算機內(nèi)有各種
10、資源,單從硬件上說,就有CPU,內(nèi)存,定時器,中斷源,I/O端口等.而且還會派生出來很多軟件資源,例如消息池. 操作系統(tǒng)的存在,就是為了讓這些資源能被合理地分配. 最后我們來總結(jié)一下,所謂操作系統(tǒng),以我們目前權(quán)宜的理解就是:為解決計算機資源爭用而制定出的一種約定.二.51上的操作系統(tǒng)對于一個操作系統(tǒng)來說,最重要的莫過于并行多任務.在這里要澄清一下,不要拿當年的DOS來說事,時代不同了.況且當年IBM和小比爾著急將PC搬上市,所以才抄襲PLM(好象是叫這個名吧?記不太清)搞了個今天看來很粗制濫造的DOS出來.看看當時真正的操作系統(tǒng)-UNIX,它還在紙上時就已經(jīng)是多任務的了. 對于我們PC來說,要
11、實現(xiàn)多任務并不是什么問題,但換到MCU卻很頭痛: 1.系統(tǒng)資源少 在PC上,CPU主頻以G為單位,內(nèi)存以GB為單位,而MCU的主頻通常只有十幾M,內(nèi)存則是Byts.在這么少的資源上同時運行多個任務,就意味著操作系統(tǒng)必須盡可能的少占用硬件資源. 2.任務實時性要求高 PC并不需要太關(guān)心實時性,因為PC上幾乎所有的實時任務都被專門的硬件所接管,例如所有的聲卡網(wǎng)卡顯示上都內(nèi)置有DSP以及大量的緩存.CPU只需坐在那里指手劃腳告訴這些板卡如何應付實時信息就行了. 而MCU不同,實時信息是靠CPU來處理的,緩存也非常有限,甚至沒有緩存.一旦信息到達,CPU必須在極短的時間內(nèi)響應,否則信息就會丟失. 就拿
12、串口通信來舉例,在標準的PC架構(gòu)里,巨大的內(nèi)存允許將信息保存足夠長的時間.而對于MCU來說內(nèi)存有限,例如51僅有128字節(jié)內(nèi)存,還要扣除掉寄存器組占用掉的832個字節(jié),所以通常都僅用幾個字節(jié)來緩沖.當然,你可以將數(shù)據(jù)的接收與處理的過程合并,但對于一個操作系統(tǒng)來說,不推薦這么做. 假定以115200bps通信速率向MCU傳數(shù)據(jù),則每個字節(jié)的傳送時間約為9uS,假定緩存為8字節(jié),則串口處理任務必須在70uS內(nèi)響應. 這兩個問題都指向了同一種解決思路:操作系統(tǒng)必須輕量輕量再輕量,最好是不占資源(那當然是做夢啦). 可用于MCU的操作系統(tǒng)很多,但適合51(這里的51專指無擴展內(nèi)存的51)幾乎沒有.前陣
13、子見過一個圈圈操作系統(tǒng),那是我所見過的操作系統(tǒng)里最輕量的,但仍有改進的余地. 很多人認為,51根本不適合使用操作系統(tǒng).其實我對這種說法并不完全接受,否則也沒有這篇文章了. 我的看法是,51不適合采用通用操作系統(tǒng).所謂通用操作系統(tǒng)就是,不論你是什么樣的應用需求,也不管你用什么芯片,只要你是51,通通用同一個操作系統(tǒng). 這種想法對于PC來說沒問題,對于嵌入式來說也不錯,對AVR來說還湊合,而對于51這種貧窮型的MCU來說,不行. 怎樣行?量體裁衣,現(xiàn)場根據(jù)需求構(gòu)建一個操作系統(tǒng)出來! 看到這里,估計很多人要翻白眼了,大體上兩種: 1.操作系統(tǒng)那么復雜,說造就造,當自已是神了? 2.操作系統(tǒng)那么復雜,
14、現(xiàn)場造一個會不會出BUG? 哈哈,看清楚了?問題出在復雜上面,如果操作系統(tǒng)不復雜,問題不就解決了? 事實上,很多人對操作系統(tǒng)的理解是片面的,操作系統(tǒng)不一定要做得很復雜很全面,就算僅個多任務并行管理能力,你也可以稱它操作系統(tǒng). 只要你對多任務并行的原理有所了解,就不難現(xiàn)場寫一個出來,而一旦你做到了這一點,為各任務間安排通信約定,使之發(fā)展成一個為你的應用系統(tǒng)量身定做的操作系統(tǒng)也就不難了. 為了加深對操作系統(tǒng)的理解,可以看一看這份PPT,讓你充分了解一個并行多任務是如何一步步從順序流程演變過來的.里面還提到了很多人都在用的狀態(tài)機,你會發(fā)現(xiàn)操作系統(tǒng)跟狀態(tài)機從原理上其實是多么相似.會用狀態(tài)機寫程序,都能
15、寫出操作系統(tǒng).三.我的第一個操作系統(tǒng)直接進入主題,先貼一個操作系統(tǒng)的示范出來.大家可以看到,原來操作系統(tǒng)可以做得么簡單. 當然,這里要申明一下,這玩意兒其實算不上真正的操作系統(tǒng),它除了并行多任務并行外根本沒有別的功能.但凡事都從簡單開始,搞懂了它,就能根據(jù)應用需求,將它擴展成一個真正的操作系統(tǒng). 好了,代碼來了. 將下面的代碼直接放到KEIL里編譯,在每個task?()函數(shù)的task_switch();那里打上斷點,就可以看到它們的確是同時在執(zhí)行的.#include #define MAX_TASKS 2 /任務槽個數(shù).必須和實際任務數(shù)一至 #define MAX_TASK_DEP 12 /最
16、大棧深.最低不得少于2個,保守值為12.unsigned char idata task_stackMAX_TASKSMAX_TASK_DEP;/任務堆棧. unsigned char task_id; /當前活動任務號 /任務切換函數(shù)(任務調(diào)度器) void task_switch() task_sptask_id = SP; if(+task_id = MAX_TASKS) task_id = 0; SP = task_sptask_id; /任務裝入函數(shù).將指定的函數(shù)(參數(shù)1)裝入指定(參數(shù)2)的任務槽中.如果該槽中原來就有任務,則原任務丟失,但系統(tǒng)本身不會發(fā)生錯誤. void task
17、_load(unsigned int fn, unsigned char tid) task_sptid = task_stacktid + 1; task_stacktid0 = (unsigned int)fn & 0xff; task_stacktid1 = (unsigned int)fn 8; /從指定的任務開始運行任務調(diào)度.調(diào)用該宏后,將永不返回. #define os_start(tid) task_id = tid,SP = task_sptid;return; /*=以下為測試代碼=*/ void task1() static unsigned char i; while(1
18、) i+; task_switch();/編譯后在這里打上斷點 void task2() static unsigned char j; while(1) j+=2; task_switch();/編譯后在這里打上斷點 void main() /這里裝載了兩個任務,因此在定義MAX_TASKS時也必須定義為2 task_load(task1, 0);/將task1函數(shù)裝入0號槽 task_load(task2, 1);/將task2函數(shù)裝入1號槽 os_start(0); 限于篇幅我已經(jīng)將代碼作了簡化,并刪掉了大部分注釋,大家可以直接下載源碼包,里面完整的注解,并帶KEIL工程文件,斷點也打好
19、了,直接按ctrl+f5就行了. 現(xiàn)在來看看這個多任務系統(tǒng)的原理: 這個多任務系統(tǒng)準確來說,叫作協(xié)同式多任務. 所謂協(xié)同式,指的是當一個任務持續(xù)運行而不釋放資源時,其它任務是沒有任何機會和方式獲得運行機會,除非該任務主動釋放CPU. 在本例里,釋放CPU是靠task_switch()來完成的.task_switch()函數(shù)是一個很特殊的函數(shù),我們可以稱它為任務切換器. 要清楚任務是如何切換的,首先要回顧一下堆棧的相關(guān)知識. 有個很簡單的問題,因為它太簡單了,所以相信大家都沒留意過: 我們知道,不論是CALL還是JMP,都是將當前的程序流打斷,請問CALL和JMP的區(qū)別是什么? 你會說:CALL
20、可以RET,JMP不行.沒錯,但原因是啥呢?為啥CALL過去的就可以用RET跳回來,JMP過去的就不能用RET來跳回呢? 很顯然,CALL通過某種方法保存了打斷前的某些信息,而在返回斷點前執(zhí)行的RET指令,就是用于取回這些信息. 不用多說,大家都知道,某些信息就是PC指針,而某種方法就是壓棧. 很幸運,在51里,堆棧及堆棧指針都是可被任意修改的,只要你不怕死.那么假如在執(zhí)行RET前將堆棧修改一下會如何?往下看: 當程序執(zhí)行CALL后,在子程序里將堆棧剛才壓入的斷點地址清除掉,并將一個函數(shù)的地址壓入,那么執(zhí)行完RET后,程序就跳到這個函數(shù)去了. 事實上,只要我們在RET前將堆棧改掉,就能將程序跳
21、到任務地方去,而不限于CALL里壓入的地址. 重點來了. 首先我們得為每個任務單獨開一塊內(nèi)存,這塊內(nèi)存專用于作為對應的任務的堆棧,想將CPU交給哪個任務,只需將棧指針指向誰內(nèi)存塊就行了. 接下來我們構(gòu)造一個這樣的函數(shù): 當任務調(diào)用該函數(shù)時,將當前的堆棧指針保存一個變量里,并換上另一個任務的堆棧指針.這就是任務調(diào)度器了.OK了,現(xiàn)在我們只要正確的填充好這幾個堆棧的原始內(nèi)容,再調(diào)用這個函數(shù),這個任務調(diào)度就能運行起來了. 那么這幾個堆棧里的原始內(nèi)容是哪里來的呢?這就是任務裝載函數(shù)要干的事了. 在啟動任務調(diào)度前將各個任務函數(shù)的入口地址放在上面所說的任務專用的內(nèi)存塊里就行了!對了,順便說一下,這個任務專
22、用的內(nèi)存塊叫作私棧,私棧的意思就是說,每個任務的堆棧都是私有的,每個任務都有一個自已的堆棧. 話都說到這份上了,相信大家也明白要怎么做了: 1.分配若干個內(nèi)存塊,每個內(nèi)存塊為若干字節(jié): 這里所說的若干個內(nèi)存塊就是私棧,要想同時運行幾少個任務就得分配多少塊.而每個子內(nèi)存塊若干字節(jié)就是棧深.記住,每調(diào)一層子程序需要2字節(jié).如果不考慮中斷,4層調(diào)用深度,也就是8字節(jié)棧深應該差不多了. unsigned char idata task_stackMAX_TASKSMAX_TASK_DEP 當然,還有件事不能忘,就是堆指針的保存處.不然光有堆棧怎么知道應該從哪個地址取數(shù)據(jù)啊 unsigned char
23、idata task_spMAX_TASKS 上面兩項用于裝任務信息的區(qū)域,我們給它個概念叫任務槽.有些人叫它任務堆,我覺得還是槽比較直觀 對了,還有任務號.不然怎么知道當前運行的是哪個任務呢? unsigned char task_id 當前運行存放在1號槽的任務時,這個值就是1,運行2號槽的任務時,這個值就是2. 2.構(gòu)造任務調(diào)度函函數(shù): void task_switch() task_sptask_id = SP;/保存當前任務的棧指針 if(+task_id = MAX_TASKS)/任務號切換到下一個任務 task_id = 0; SP = task_sptask_id;/將系統(tǒng)的棧
24、指針指向下個任務的私棧. 3.裝載任務: 將各任務的函數(shù)地址的低字節(jié)和高字節(jié)分別入在 task_stack任務號0和task_stack任務號1中: 為了便于使用,寫一個函數(shù): task_load(函數(shù)名, 任務號) void task_load(unsigned int fn, unsigned char tid) task_sptid = task_stacktid + 1; task_stacktid0 = (unsigned int)fn & 0xff; task_stacktid1 = (unsigned int)fn 8; 4.啟動任務調(diào)度器: 將棧指針指向任意一個任務的私棧,執(zhí)行
25、RET指令.注意,這可很有學問的哦,沒玩過堆棧的人腦子有點轉(zhuǎn)不彎:這一RET,RET到哪去了?嘿嘿,別忘了在RET前已經(jīng)將堆棧指針指向一個函數(shù)的入口了.你別把RET看成RET,你把它看成是另一種類型的JMP就好理解了. SP = task_sp任務號; return; 做完這4件事后,任務并行執(zhí)行就開始了.你可以象寫普通函數(shù)一個寫任務函數(shù),只需(目前可以這么說)注意在適當?shù)臅r候(例如以前調(diào)延時的地方)調(diào)用一下task_switch(),以讓出CPU控制權(quán)給別的任務就行了. 最后說下效率問題. 這個多任務系統(tǒng)的開銷是每次切換消耗20個機器周期(CALL和RET都算在內(nèi)了),貴嗎?不算貴,對于很多
26、用狀態(tài)機方式實現(xiàn)的多任務系統(tǒng)來說,其實效率還沒這么高- case switch和if()可不像你想像中那么便宜. 關(guān)于內(nèi)存的消耗我要說的是,當然不能否認這種多任務機制的確很占內(nèi)存.但建議大家不要老盯著編譯器下面的那行字DATA = XXXbyte.那個值沒意義,堆棧沒算進去.關(guān)于比較省內(nèi)存多任務機制,我將來會說到. 概括來說,這個多任務系統(tǒng)適用于實時性要求較高而內(nèi)存需求不大的應用場合,我在運行于36M主頻的STC12C4052上實測了一把,切換一個任務不到3微秒. 下回我們講講用KEIL寫多任務函數(shù)時要注意的事項. 下下回我們講講如何增強這個多任務系統(tǒng),跑步進入操作系統(tǒng)時代.四.用KEIL寫多
27、任務系統(tǒng)的技巧與注意事項C51編譯器很多,KEIL是其中比較流行的一種.我列出的所有例子都必須在KEIL中使用.為何?不是因為KEIL好所以用它(當然它的確很棒),而是因為這里面用到了KEIL的一些特性,如果換到其它編譯器下,通過編譯的倒不是問題,但運行起來可能是堆棧錯位,上下文丟失等各種要命的錯誤,因為每種編譯器的特性并不相同.所以在這里先說清楚這一點. 但是,我開頭已經(jīng)說了,這套帖子的主要目的是闡述原理,只要你能把這幾個例子消化掉,那么也能夠自已動手寫出適合其它編譯器的OS. 好了,說說KEIL的特性吧,先看下面的函數(shù): sbit sigl = P17; void func1() regi
28、ster char data i; i = 5; dosigl = !sigl; while(-i); 你會說,這個函數(shù)沒什么特別的嘛!呵呵,別著急,你將它編譯了,然后展開匯編代碼再看看: 193: void func1() 194: register char data i; 195: i = 5; C:0x00C3 7F05 MOV R7,#0x05 196: do 197: sigl = !sigl; C:0x00C5 B297 CPL sigl(0x90.7) 198: while(-i); C:0x00C7 DFFC DJNZ R7,C:00C5 199: C:0x00C9 22 R
29、ET 看清楚了沒?這個函數(shù)里用到了R7,卻沒有對R7進行保護! 有人會跳起來了:這有什么值得奇怪的,因為上層函數(shù)里沒用到R7啊.呵呵,你說的沒錯,但只說對了一半:事實上,KEIL編譯器里作了約定,在調(diào)子函數(shù)前會盡可能釋放掉所有寄存器.通常性況下,除了中斷函數(shù)外,其它函數(shù)里都可以任意修改所有寄存器而無需先壓棧保護(其實并不是這樣,但現(xiàn)在暫時這樣認為,飯要一口一口吃嘛,我很快會說到的). 這個特性有什么用呢?有!當我們調(diào)用任務切換函數(shù)時,要保護的對象里可以把所有的寄存器排除掉了,就是說,只需要保護堆棧即可! 現(xiàn)在我們回過頭來看看之前例子里的任務切換函數(shù): void task_switch() ta
30、sk_sptask_id = SP;/保存當前任務的棧指針 if(+task_id = MAX_TASKS)/任務號切換到下一個任務 task_id = 0; SP = task_sptask_id;/將系統(tǒng)的棧指針指向下個任務的私棧. 看到?jīng)],一個寄存器也沒保護,展開匯編看看,的確沒保護寄存器. 好了,現(xiàn)在要給大家潑冷水了,看下面兩個函數(shù): void func1() register char data i; i = 5; do sigl = !sigl; while(-i); void func2() register char data i; i = 5; do func1(); whi
31、le(-i); 父函數(shù)fun2()里調(diào)用func1(),展開匯編代碼看看: 193: void func1() 194: register char data i; 195: i = 5; C:0x00C3 7F05 MOV R7,#0x05 196: do 197: sigl = !sigl; C:0x00C5 B297 CPL sigl(0x90.7) 198: while(-i); C:0x00C7 DFFC DJNZ R7,C:00C5 199: C:0x00C9 22 RET 200: void func2() 201: register char data i; 202: i =
32、5; C:0x00CA 7E05 MOV R6,#0x05 203: do 204: func1(); C:0x00CC 11C3 ACALL func1(C:00C3) 205: while(-i); C:0x00CE DEFC DJNZ R6,C:00CC 206: C:0x00D0 22 RET 看清楚沒?函數(shù)func2()里的變量使用了寄存器R6,而在func1和func2里都沒保護. 聽到這里,你可能又要跳一跳了:func1()里并沒有用到R6,干嘛要保護?沒錯,但編譯器是怎么知道func1()沒用到R6的呢?是從調(diào)用關(guān)系里推測出來的. 一點都沒錯,KEIL會根據(jù)函數(shù)間的直接調(diào)用關(guān)系
33、為各函數(shù)分配寄存器,既不用保護,又不會沖突,KEIL好棒哦!等一下,先別高興,換到多任務的環(huán)境里再試試: void func1() register char data i; i = 5; do sigl = !sigl; while(-i); void func2() register char data i; i = 5; do sigl = !sigl; while(-i); 展開匯編代碼看看: 193: void func1() 194: register char data i; 195: i = 5; C:0x00C3 7F05 MOV R7,#0x05 196: do 197:
34、sigl = !sigl; C:0x00C5 B297 CPL sigl(0x90.7) 198: while(-i); C:0x00C7 DFFC DJNZ R7,C:00C5 199: C:0x00C9 22 RET 200: void func2() 201: register char data i; 202: i = 5; C:0x00CA 7F05 MOV R7,#0x05 203: do 204: sigl = !sigl; C:0x00CC B297 CPL sigl(0x90.7) 205: while(-i); C:0x00CE DFFC DJNZ R7,C:00CC 20
35、6: C:0x00D0 22 RET 看到了吧?哈哈,這回神仙也算不出來了.因為兩個函數(shù)沒有了直接調(diào)用的關(guān)系,所以編譯器認為它們之間不會產(chǎn)生沖突,結(jié)果分配了一對互相沖突的寄存器,當任務從func1()切換到func2()時,func1()中的寄存器內(nèi)容就給破壞掉了.大家可以試著去編譯一下下面的程序: sbit sigl = P17; void func1() register char data i; i = 5; do sigl = !sigl; task_switch(); while (-i); void func2() register char data i; i = 5; do s
36、igl = !sigl; task_switch(); while(-i); 我們這里只是示例,所以仍可以通過手工分配不同的寄存器避免寄存器沖突,但在真實的應用中,由于任務間的切換是非常隨機的,我們無法預知某個時刻哪個寄存器不會沖突,所以分配不同寄存器的方法不可取.那么,要怎么辦呢? 這樣就行了: sbit sigl = P17; void func1() static char data i; while(1) i = 5; do sigl = !sigl; task_switch(); while(-i); void func2() static char data i; while(1)
37、 i = 5; do sigl = !sigl; task_switch(); while(-i); 將兩個函數(shù)中的變量通通改成靜態(tài)就行了.還可以這么做: sbit sigl = P17; void func1() register char data i; while(1) i = 5; do sigl = !sigl; while(-i); task_switch(); void func2() register char data i; while(1) i = 5; do sigl = !sigl; while(-i); task_switch(); 即,在變量的作用域內(nèi)不切換任務,等
38、變量用完了,再切換任務.此時雖然兩個任務仍然會互相破壞對方的寄存器內(nèi)容,但對方已經(jīng)不關(guān)心寄存器里的內(nèi)容了. 以上所說的,就是變量覆蓋的問題.現(xiàn)在我們系統(tǒng)地說說關(guān)于變量覆蓋. 變量分兩種,一種是全局變量,一種是局部變量(在這里,寄存器變量算到局部變量里). 對于全局變量,每個變量都會分配到單獨的地址. 而對于局部變量,KEIL會做一個覆蓋優(yōu)化,即沒有直接調(diào)用關(guān)系的函數(shù)的變量共用空間.由于不是同時使用,所以不會沖突,這對內(nèi)存小的51來說,是好事. 但現(xiàn)在我們進入多任務的世界了,這就意味著兩個沒有直接調(diào)用關(guān)系的函數(shù)其實是并列執(zhí)行的,空間不能共用了.怎么辦呢?一種笨辦法是關(guān)掉覆蓋優(yōu)化功能.呵呵,的確很
39、笨. 比較簡單易行一個解決辦法是,不關(guān)閉覆蓋優(yōu)化,但將那些在作用域內(nèi)需要跨越任務(換句話說就是在變量用完前會調(diào)用task_switch()函數(shù)的)變量通通改成靜態(tài)(static)即可.這里要對初學者提一下,靜態(tài)你可以理解為全局,因為它的地址空間一直保留,但它又不是全局,它只能在定義它的那個花括號對里訪問. 靜態(tài)變量有個副作用,就是即使函數(shù)退出了,仍會占著內(nèi)存.所以寫任務函數(shù)的時候,盡量在變量作用域結(jié)束后才切換任務,除非這個變量的作用域很長(時間上長),會影響到其它任務的實時性.只有在這種情況下才考慮在變量作用域內(nèi)跨越任務,并將變量申明為靜態(tài). 事實上,只要編程思路比較清析,很少有變量需要跨越任務的.就是說,靜態(tài)變量并不多. 說完了覆蓋我們再說說重入.所謂重入,就是一個函數(shù)在同一時刻有兩個不同的進程復本.對初學者來說可能不好理解,我舉個例子吧: 有一個函數(shù)在主程序會被調(diào)用,在中斷里也會被調(diào)用,假如正當在主程序里調(diào)用時,中斷發(fā)生了,會發(fā)生什么情況? void func1() static char data i; i = 5; do sigl = !sigl; while(-i); 假定func1()正執(zhí)行到i=3時,中斷發(fā)生,一旦中斷調(diào)用到func1()時,i的值就被破壞了,當中斷結(jié)束后,i = 0. 以上說的是在傳統(tǒng)的單任
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 自建樓房買賣合同
- 房產(chǎn)抵押反擔保合同
- 企業(yè)信息化管理系統(tǒng)建設與維護合同
- 體育賽事活動策劃與執(zhí)行合同
- 養(yǎng)豬場生產(chǎn)經(jīng)營合同
- 重慶護理職業(yè)學院《化工儀表自動化》2023-2024學年第二學期期末試卷
- Unit 2 Topic 1 Section C 教學設計 2024-2025學年仁愛科普版八年級英語上冊
- 沈陽科技學院《漆畫創(chuàng)作》2023-2024學年第二學期期末試卷
- 《人的正確的思想從哪里來》教學設計
- 哈爾濱學院《文化創(chuàng)意理論與實踐》2023-2024學年第二學期期末試卷
- 《會計信息系統(tǒng)應用》-課件 王新惠 模塊二 供應鏈管理系統(tǒng)
- 美容院會員積分營銷方案
- 水利水電工程金屬結(jié)構(gòu)制作與安裝安全技術(shù)規(guī)程
- DL5000-火力發(fā)電廠設計技術(shù)規(guī)程
- 第一節(jié)-原核生物與真核生物DNA的復制課件
- 2024年4月自考03708中國近現(xiàn)代史綱要試題
- 深部熱療與免疫治療聯(lián)合治療腫瘤
- 2024年貴銀金融租賃公司招聘筆試參考題庫附帶答案詳解
- 眼電生理在視網(wǎng)膜疾病診斷中的應用
- 《汽車電氣設備構(gòu)造與維修》 (第4版) 課件 第四章 發(fā)動機電器
- 部編版語文六年級下冊第五單元大單元教學設計核心素養(yǎng)目標
評論
0/150
提交評論