一個搶先式“裸奔\"系統的設計
5 程序運行流程
程序初始化流程圖如圖1所示。本文引用地址:http://www.j9360.com/article/201809/388713.htm
首先,main()在完成硬件初始化Sys_init()后,調用I2c_svr()總線通信服務程序。
I2c_svr()服務程序里,首先完成類似通用RTOS的任務現場保護的過程。再通過切換堆棧指針,完成了新任務堆棧的初始化過程。然后進入I2C總線通信模塊主循環(類似創建任務的操作),再通過調用idelay(),將CPU的控制權交還給main()。奧妙就在于idelay()首先保存通信程序的寄存器現場(ACC和PSW),然后轉換到main()的堆棧空間,并恢復剛才被I2c_svr()保存的寄存器現場(ACC和PSW)。所以;i2c_svr()里的idelay()函數返回后將不執行其下面的i2write(),而是執行main()里的while(1)。
i2write()又如何能得到執行呢?它是通過定時中斷服務程序timer1()再次獲得CPU控制權的。如果在main()的執行中發生timer1()中斷,因為timer1()里也進行與idelay()類似的任務切換操作,這時候將切換到I2c_svr()的堆棧和寄存器(現場)。此時timer1()中斷返回時,不會返回到main()里,而是執行i2write()。
另外,函數i2write()內部執行中也會調用idelay(),在I2c_svr()中的每次調用idelay()都會將CPU控制權交給main()的切換。main()和I2c_svr()的切換關系如圖2所示。
當然,timer1()并不總是引起任務的切換,通過判斷bi2csvr標志可以避免(在不需要數據傳輸時)不必要的任務切換。另外,timer1()也可能進行從I2c_svr()到main()的切換。所以即使I2c_svr()里很長時間沒有調用idelay(),也不會阻塞main()的執行。
切換現場一般基于80C51的RTOS,通常要保存所有的CPU寄存器(包括8個工作寄存器、ACC、PSW、B、DPTR等),而這里與它們不同,因為在筆者的通信服務模塊I2c_svr()中使用了另外的寄存器組,且未使用B和DPTR,因此不需要保存8個工作寄存器及B和DPTR,僅保存和恢復PSW和ACC這兩個寄存器就可以了,大大提高了切換效率。
本系統里僅有兩個“任務”,即main()和I2c_svr(),也沒有固定優先級,處于“等待”狀態任務的優先級總比當前運行中的任務高,所以相當于同優先級時間片輪轉調度方式。但相對于RTOS,這里還缺少操作系統必須管理的與任務相關的狀態和數據結構,所以筆者還將其稱做“裸奔”系統。
6 現場保護的補充說明
任務切換中的寄存器現場保護代碼如下:
上面是Keil C51對定時中斷服務函數timer1()編譯生成的LST文件。編譯器在中斷服務里自動生成壓棧和出棧寄存器的指令,所以在寫idelay()函數的寄存器現場切換的時候,必須完全遵守這個寄存器壓棧和出棧順序規則才能正常工作。
結語
通過學習和借鑒RTOS的CPU時間搶先調度和分配方法,可以將本系統中總線時序里許多很短的延時都交給主程序使用,最大程度利用CPU時間,實現主程序和通信服務程序的并行執行,從而讓主程序和通信服務程序均達到系統要求的實時性能。
本文為時間緊張的系統設計提供了一個新的解決思路。應該有助于初學操作系統的讀者理解操作系統任務切換的工作機理。
評論