• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            tbwshc

            tbw

              C++博客 :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
              95 Posts :: 8 Stories :: 3 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(4)

            我參與的團隊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            2013年9月11日 #

            從一個任務(wù)轉(zhuǎn)變到另一個任務(wù)的實際過程叫作設(shè)備場景切換。因為設(shè)備場景是處理器專用的,實現(xiàn)設(shè)備場景切換的實現(xiàn)也是這樣。那意味著它總是要用匯編來寫。與其向你展示我在ADEOS 中使用的80x86 專用的匯編代碼,不如我用一種類C 的偽代碼來展示設(shè)備場景切換tb例程。
            void
            contextSwitch(PContext pOldContext, PContext pNewContext)
            {
            if(saveContext(pOldContext))
            {
            //
            // Restore new context only on a nonzero exit from saveContext().
            //
            restoreContext(pNewContext);
            // This line is never executed!
            }
            // Instead, the restored task continues to execute at this point.
            }

            例程 contextSwitch()實際上是被調(diào)度程序凋用,而調(diào)度程序又在那此終止中斷的tb系統(tǒng)調(diào)用中被調(diào)用,因此它不一定在這里終止中斷。此外,由于調(diào)用調(diào)度程序的操作系統(tǒng)調(diào)用是用高級語言寫的,所以大部分運行任務(wù)的寄存器已經(jīng)被保存到它自己當?shù)氐臈V辛恕_@減少了例程saveContext()和restoreContext()需要做的工作。它們只需要關(guān)心指令指針,棧指針以及標志位的保存。例程 contextSwitch()的實際行為是很難僅僅通過看前面的代碼來理解的。大部分的軟件開發(fā)者以連續(xù)的方式思考問題,認為每一行代碼會緊接著上一條代碼破執(zhí)行。然而,這個代碼實際為并行地執(zhí)行了兩次。當一個任務(wù)(新任務(wù))轉(zhuǎn)變到運行狀態(tài),另一個(舊任務(wù))必須同時返回到就緒狀態(tài)。想一下新任務(wù)當它在restoreContext()代碼中被恢復(fù)的時候就會明白。無論新任務(wù)以前做什么,它在saveContext 代碼里總是醒著的——因為這就是它的指令存放的地方。新任務(wù)如何知道它是否是第一次(也就是,在準備休眠的過程)或者是第二次(醒來的過程)從saveContext()中出來的呢?它確實需要知道這個差別,因此我不得不用一種有點隱蔽的方法來實現(xiàn)saveContext()。例程saveContext()不是保存了準確的目前的指令指針。實際上是保存了一些指令前面的地址。那樣,當保存的設(shè)備場景恢復(fù)的時候,程序從saveContext 中另一個下同的點繼續(xù)。這也使得saveContext 可能返回不同的值:當任務(wù)要休眠的時候為非零,當任務(wù)喚起的時候為零。例程contextSwitch()利用這個返回的值來決定是否調(diào)用restoreContext()。如果不進行這個檢測,那么與這個新任務(wù)相關(guān)的代碼永遠不會執(zhí)行。

            我知道這可能是一個復(fù)雜的事件序列,因此我在圖8-3 中說明了整個的過程。

            posted @ 2013-09-11 16:34 tbwshc 閱讀(347) | 評論 (0)編輯 收藏

            2013年9月5日 #

            作為 ADEOS 的開發(fā)者(或是其他操作系統(tǒng)的開發(fā)者),你需要知道如何創(chuàng)建和使用任務(wù)。就像別的抽象數(shù)據(jù)結(jié)構(gòu),Task 類有自己的成員函數(shù)。ADEOS的的任務(wù)接口比別的大多數(shù)操作系統(tǒng)要簡單一些,因為它只是創(chuàng)建一個新的Task 對象。一旦創(chuàng)建,ADEOS 任務(wù)繼續(xù)在系統(tǒng)中存在,直到相關(guān)的函數(shù)返回。當然,這也許永遠不會發(fā)生(意即ADEOS 任務(wù)也許永遠不會結(jié)束),但是,如果一旦發(fā)生了,那么該任務(wù)就會被操作系統(tǒng)刪除掉。Task 的構(gòu)造函數(shù)如下所示。調(diào)用者通過構(gòu)造函數(shù)的參數(shù)分配一個函數(shù),一個權(quán)限值,和一個可選擇的新任務(wù)的堆棧大小。第一個參數(shù),fUnCtion,是一個指向C/C++語言或匯編語言的函數(shù)指針,該函數(shù)是要在新任務(wù)的上下文環(huán)境中運行的。該函數(shù)不需要任何輸人參數(shù),也不返回任何結(jié)果。第二個參數(shù)P,是一個單字節(jié)的整數(shù)(從1 到255),代表了任務(wù)的權(quán)限級別,這個權(quán)限級別是與別的任務(wù)相對而言的,在tb任務(wù)調(diào)度器選擇新的任務(wù)運行的時候會用到(p 的值越大,表示權(quán)限越高)。
            TaskId Task::nextId = 0
            /**************************************************************
            *
            * Method : Task()
            *
            * Description : Create a new task and initialize its state.
            *
            * Notes :
            *
            * Returns :
            *
            **************************************************************/
            Task:Task(void (*function)(), Priority p, int stackSize)
            {
            stackSize /= sizeof(int); //Convert bytes to words.
            enterCS(); //Critical Section Begin
            //
            // Initialize the task-specific data.
            //
            if = Task::nextId++;
            state = Ready;
            priority = p;
            entryPoint = function;
            pStack = new int[stackSize];
            pNext = NULL;
            //
            // Initialize the processor context.
            //
            contextInit(&context, run, this, pStack + stackSize);
            //
            // Insert the task into the ready list.
            //
            os.readyList.insert(this);
            os.schedule(); // Scheduling Point
            exitCS(); // Critical Section End
            } /* Task() */
            注意這個例程的功能塊被兩個函數(shù) enterCS()和exitCS()的調(diào)用包圍。在這些調(diào)用之間的代碼塊叫作tb臨界區(qū)(critical section)。臨界區(qū)是一個程序必須完整執(zhí)行的一部分。也就是說,組成這一個部分的指令必須沒有中斷地按照順序執(zhí)行。因為中斷可能隨時發(fā)生,保證不受到中斷的唯一辦法就是在執(zhí)行關(guān)鍵區(qū)期間禁止中斷。因此在關(guān)鍵區(qū)的開始調(diào)用enterCS 以保存中斷的允許狀態(tài)以及禁止進一步的中斷。在關(guān)鍵區(qū)尾部調(diào)用exitCS 以恢復(fù)前面保存的中斷調(diào)用。我們會看到在下面每一個例程中都應(yīng)用了同樣的技巧。
            在前面代碼中,有幾個在構(gòu)造函數(shù)里調(diào)用的其他例程,但是在這里我沒有空間列出。它們是contextInit()和os.readyList.insert()例程。例程contextInit()為任務(wù)建立了初始的設(shè)備場景。這個例程必定是處理器專用的,因此是用匯編語言寫的。
            contextInit()有四個參數(shù)。第一個是一個指向待初始比的設(shè)備場景數(shù)據(jù)結(jié)構(gòu)指針。第二個是一個指向啟動函數(shù)的指針。這是一個特殊的ADEOS 函數(shù),叫作run(),它被用來啟動一個任務(wù),并且如果以后相關(guān)的函數(shù)退出了,它被用來做其后的清理工作。第三個參數(shù)是一個指向新任務(wù)對象的指針。這個參數(shù)被傳遞給run(),因此相關(guān)的任務(wù)就能夠被啟動。第四個和最后一個參數(shù)是指向新任務(wù)棧的指針。
            另一個函數(shù)調(diào)用是 os.readyList.insert()。這個函數(shù)把新任務(wù)加入到操作系統(tǒng)內(nèi)部的就緒任務(wù)列表中。readyList 是一個TaskList 類型的對象。這個類是那些具有insert()和remove()兩個方法的任務(wù)(按照優(yōu)先級排序)的鏈表。感興趣的讀者如果想知道這些函數(shù)是如何實現(xiàn)的就應(yīng)該下載和研究其ADEOS 的源代碼。你將在下面的討論中了解到更多有關(guān)就緒列表的問題。
            posted @ 2013-09-05 16:58 tbwshc 閱讀(835) | 評論 (0)編輯 收藏

            在早期的計算機中,沒有操作系統(tǒng)一說,應(yīng)用程序開發(fā)人員都要對處理器(CPU)和硬件進行徹頭徹尾的控制。實際上,第一個操作系統(tǒng)的誕生,就是為了提供一個虛擬的硬件平臺,以方便程序員開發(fā)。為了實現(xiàn)這個目標,操作系統(tǒng)只需要提供一些較為松散的函數(shù)、例程——就好像現(xiàn)在的軟件庫一樣——以便于對硬件設(shè)備進行重置、讀取狀態(tài)、寫入指令之類的操作?,F(xiàn)代的操作系統(tǒng)則在單處理器上加入了多任務(wù)機制,每個任務(wù)都是一個軟件模塊,可以是相互獨立的。嵌入式的軟件經(jīng)常是可以劃分成小的互相獨立的模塊。例如,第五章“接觸硬件”講到的打印tb共享設(shè)備就包含三個不同的軟件任務(wù):
            ?? 任務(wù) 1:從計算機的串行口A 接收數(shù)據(jù)
            ?? 任務(wù) 2:從計算機的串行口B 接收數(shù)據(jù)
            ?? 任務(wù) 3:格式化數(shù)據(jù)并輸送到計算機的并行口(打印機就連接在并行口)
            這些任務(wù)的劃分提供了一個很關(guān)鍵的軟件抽象概念,這使得嵌入式操作系統(tǒng)的設(shè)計和實現(xiàn)更加容易,源程序也更易于理解和維護。通過把大的程序進行模塊化劃分,程序員可以集中精力克服系統(tǒng)開發(fā)過程中的關(guān)鍵問題。

            坦言之,一個操作系統(tǒng)并不是嵌入式或其它計算機系統(tǒng)的必需的組件,它所能做的,也是像時用程序要實現(xiàn)的功能一樣。本書中的所有例子都說明了這一點。應(yīng)用程序執(zhí)行起來,都是從main 開始,然后進入系統(tǒng)調(diào)用、運行、結(jié)束。這與系統(tǒng)中只有一個任務(wù)是一樣的。對于應(yīng)用程序來說,僅僅是實現(xiàn)使LED 進行閃爍,這就是操作系統(tǒng)的主要功用(屏蔽了很多復(fù)雜的操作)。

            如果你以前沒作過對操作系統(tǒng)的研究,那么,在這里得提醒一下,操作系統(tǒng)是非常復(fù)雜的。tb操作系統(tǒng)的廠商肯定是想使你相信,他們是唯一能生產(chǎn)出功能強大又易用的操作系統(tǒng)的科學(xué)家。但是,我也要告訴你:這并不是根困難的。實際上嵌入式操作系統(tǒng)要比桌面操作系統(tǒng)更容易編寫,所需的模塊和功能更為小巧、更易于實現(xiàn)。一旦明確了要實現(xiàn)了功能,并有一定的實現(xiàn)技能,你將會發(fā)現(xiàn),開發(fā)一個操作系統(tǒng)并不比開發(fā)嵌入式軟件艱難多少。

            嵌入式操作系統(tǒng)很小,因為它可以缺少很多桌面操作系統(tǒng)的功能。例如,嵌入式操什系統(tǒng)很少有硬盤或圖形界面,因此,嵌入式操作系統(tǒng)可以下需要文件系統(tǒng)和圖形用戶接口。而且,一般來說,是單用戶系統(tǒng),所以多用戶操作系統(tǒng)的安全特性也可以省去了。上面所說的各種性能,都可以作為嵌入式操作系統(tǒng)的一部分,但不是必須的。

            posted @ 2013-09-05 16:46 tbwshc 閱讀(225) | 評論 (0)編輯 收藏

            2013年8月19日 #

            在這一章里,我試圖把到目前為止所有我們討論過的單元合在一起,使之成為一個完整的嵌入式的應(yīng)用程序。在這里我沒有把很多新的素材加入到討論中,因此本章主要是描述其中給出的代碼。我的目的是描述這個應(yīng)用程序的結(jié)構(gòu)和它的源代碼,通過這種方式使你對它不再感到神奇。完成這一章以后,你應(yīng)該對于示例程序有一個完整的理解,并且有能力開發(fā)自己的嵌入式應(yīng)用程序。應(yīng)用程序的概述

            我們將要討論的這個應(yīng)用程序不比其他大部分tb編程書籍中找到的“Hello,World”例子更復(fù)雜。它是對于嵌入式軟件開發(fā)的一個實證,因此這個例子是出現(xiàn)在書的結(jié)尾而不是開始。我們不得不逐漸地建立我們的道路通向大部分書籍甚至是高級語言編譯器認為是理所當然的計算平臺。

            一旦你能寫“Hello, World”程序,你的嵌入式平臺就開始著上去很像任何其他編程環(huán)境。但是,嵌入式軟件開發(fā)過程中最困難的部分——使自己熟悉硬件,為它建立一個軟件開發(fā)的過程,連接到具體的硬件設(shè)備——還在后面呢。最后,你能夠把你的力量集中于算法和用戶界面,這是由你要開發(fā)的產(chǎn)品來確定的。很多情況下,這些程序的高級方面可以在其他的計算機平臺上開發(fā),和我們一直在討論的低級的嵌入式軟件開發(fā)同時進行,并且只要把高級部分導(dǎo)入嵌入式系統(tǒng)一次,兩者就都完成了。

            圖 9-1 包含了一個“Hello, World!”應(yīng)用程序的高級的示意圖。這個應(yīng)用程序包括三個設(shè)備驅(qū)動程序,ADEOS 操作系統(tǒng)和兩個ADEOS 任務(wù)。第一個任務(wù)以每秒10Hz 的速度切換Arcom 板上的紅色指示燈。第二個每隔10 秒鐘向主機或是連接到位子串口上的啞終端發(fā)送字符串“Hello,WOrld!”。這兩個任務(wù)之外,圖中還有三個設(shè)備的驅(qū)動程序。這些驅(qū)動程序分別控制著Arcom 板子的指示燈、時鐘以及串行端口。雖然通常把設(shè)備驅(qū)動畫在操作系統(tǒng)的下面,但是我把它們?nèi)齻€和操作系統(tǒng)放在同一個級別,是為了著重說明它們事實上依賴于ADEOS 比ADEOS 依賴于它們更多。實際上,ADEOS 嵌入式操作系統(tǒng)甚至不知道(或者說下關(guān)心)這些設(shè)備驅(qū)動是否存在于系統(tǒng)之中。這是嵌入式操作系統(tǒng)中設(shè)備驅(qū)動程序和其他硬件專用軟件的共性。

            程序 main()的實現(xiàn)如下所示。這段代碼簡單地創(chuàng)造廠兩個任務(wù),tb啟動了操作系統(tǒng)的日程表。在這樣一個高的級別上,代碼的含義是不言而喻的。事實上、我們已經(jīng)在上一章中討論了類似的代碼。

            #include "adeos.h"
            void flashRed(void);
            void helloWorld(void);
            /*
            * Create the two tasks.
            */
            Task taskA(flashRed, 150, 512);
            Task taskB(helloWorld, 200, 512);
            /****************************************************
            *
            * Function : main()
            *
            * Description : This function is responsible for starting the ADEOS scheduler
            only.
            *
            * Notes :
            *
            * Returns : This function will never return!
            *
            ****************************************************/
            void
            main(void)
            {
            os.start();
            // This point will never be reached.
            } /* main() */

            posted @ 2013-08-19 11:49 tbwshc 閱讀(268) | 評論 (0)編輯 收藏

            2013年7月23日 #

            正如我早先說的那樣,當問題歸結(jié)于減小代碼的大小的時候,你最好讓編譯器為你做這件事。然而,如果處理后的程序代碼對于你可得的只讀存貯器仍然太大了,還有幾種技術(shù)你可以用來進一步減少體程序的大小。在本節(jié)中,自動的和人工的代碼優(yōu)化我們都要討論。
            當然,墨菲法則指出,第一次你啟用編譯器的優(yōu)化特性后,你先前的工作程序會突然失效,也許自動優(yōu)化最臭名昭著的是“死碼刪除”。這種優(yōu)化會刪除那些編譯器相信是多余的或者是不相關(guān)的代碼,比如,把零和一個變量相加不需要任何的計算時間。但是你可能還是希望如果程序代碼執(zhí)行了tb編譯器不了解的函數(shù),編譯器能夠產(chǎn)生那些“不相關(guān)”的指示。
            比如,下面這段給出的代碼,大部分優(yōu)化編譯器會去除第一條語句,因為*pControl 在重寫(第三行)之前沒有使用過:
            *pControl = DISABLE;
            *pData = 'a';
            *pCotrol = ENABLE;
            但是如果 pControl 和pData 實際上是指向內(nèi)存映像設(shè)備寄存器的指針怎么辦?這種情況下,外設(shè)在這個字節(jié)的數(shù)據(jù)寫入之前將接收不到DISABLE 的命令。這可能會潛在地毀壞處理器和這個外設(shè)之間的所有未來的交互作用。為了使你避免這種問題,你必須用關(guān)鍵字“volatile”聲明所有指向內(nèi)存映像設(shè)備寄存器的指針和線程之間(或者是一個線程和一個中斷服務(wù)程序之間)共享的全局變量。你只要漏掉了它們中的一個,墨菲法則就會在你的工程的最后幾天里回來,攪得你心神不寧。我保證。
            ——————————————————————————————————
            警告:千萬不要誤以為程序優(yōu)化后的行為會和未優(yōu)化時的一樣。你必須在每一次新的優(yōu)化后完全重新測試你的軟件,以確保它的行為沒有發(fā)生改變。
            ——————————————————————————————————
            更糟糕的是,或者退一步說,調(diào)試一個優(yōu)化過的程序是富有挑戰(zhàn)性的。啟用了編譯器的優(yōu)化后,在源代碼中的一行和實現(xiàn)這行代碼的那組處理器指令之間的關(guān)聯(lián)關(guān)系變得更加微弱了。那些特定的指令可能被移動或者拆分開來,或者兩個類似的代碼可能現(xiàn)在共用一個共同的實現(xiàn)。實際上,tb高級語言程序的有些行可能完全從程序中去除了(正如在前面例子里那樣)。結(jié)果,你可能無法在程序特定的一行上設(shè)置一個斷點或者無法研究一個感興趣變量的值。

            一旦你使用了自動優(yōu)化,這里有一些關(guān)于用手工的辦法進一步減少代碼大小的技巧。

            避免使用標準庫例程
            為了減少你的程序的大小,你所能做的最好的一件事情就是避免使用大的標準庫例程。很多最大的庫例程代價昂貴,只是因為它們設(shè)法處理所有可能的情況。你自己有可能用更少的代碼實現(xiàn)一個子功能。比如,標準C 的庫中的spintf例程是出了名的大。這個龐大代碼中有相當一部分是位于它所依賴的浮點數(shù)處理例程。但是如果你不需要格式化顯示浮點數(shù)值(%f 或者%d),那么你可以寫你自己的sprintf 的整數(shù)專用版本,并且可以節(jié)省幾千字節(jié)的代碼空間。實際上,一些標準C 的庫(這讓我想起Cygnus 的newlib)里恰好包含了這樣一個函數(shù),叫作sprintf。

            本地字長
            每一個處理器都有一個本地字長,并且ANSI C 和C++標準規(guī)定數(shù)據(jù)類型int必須總是對應(yīng)到那個字長。處理更小或者更大的數(shù)據(jù)類型有時需要使用附加的機器語言指令。在你的程序中通過盡可能的一致使用int 類型,你也許能夠從你的程序中削減寶貴的幾百字節(jié)。

            goto 語句
            就像對待全局變量一樣,好的軟件工程實踐規(guī)定反對使用這項技術(shù)。但是危急的時候,goto 語句可以用來去除復(fù)雜的控制結(jié)構(gòu)或者共享一塊經(jīng)常重復(fù)的代碼。
            除了這些技術(shù)以外,在前一部分介紹的幾種方法可能也會有幫助,特別是查詢表、手工編寫匯編、寄存器變最以及全局變量。在這些技術(shù)之中,利用手工編寫匯編通常可以得到代碼最大幅度的減少量。

            posted @ 2013-07-23 17:21 tbwshc 閱讀(589) | 評論 (0)編輯 收藏

            事情應(yīng)該盡可能簡化,而不只是簡單一點點,一愛因斯坦

            雖然使軟件正確的工作好像應(yīng)該是一個工程合乎邏輯的最后一個步驟,但是在嵌入式的系統(tǒng)的開發(fā)中,情況并不總是這樣的。出于對低價系列產(chǎn)品的需要,硬件的設(shè)計者需要提供剛好足夠的存儲器和完成工作的處理能力。當然,在工程的軟件開發(fā)階段,使程序正確的工作是很重要的。為此,通常需要一個或者更多的開發(fā)電路板,有的有附加的存貯器,有的有更快的處理器,有的兩者都有。這些電路板就是用來使軟件正確工作的。而工程的最后階段則變成了對代碼進行優(yōu)化。最后一步的目標是使得工作程序在一個廉價的硬件平臺上運行。

            提高代碼的效率
            所有現(xiàn)代的 C 和C++編譯器都提供了一定程度上的代碼優(yōu)化。然而,大部分由編譯器執(zhí)行的優(yōu)化技術(shù)僅涉及執(zhí)行速度和代碼大小的一個平衡。你的程序能夠變得更快或者更小,但是不可能又變快又變小。事實上,在其中一個方面的提高就會對另一方面產(chǎn)生負面的影響。哪一方面的提高對于程序更加的重要是由程序員來決定。知道這一點后,無論什么時候遇到速度與大小的矛盾,編譯器的優(yōu)化階段就會作出合適的選擇。

            因為你不可能讓編譯器為你同時做兩種類型的優(yōu)化,我建議你讓它盡其所能的減少程序的大小。執(zhí)行的速度通常只對于某些有時間限制或者是頻繁執(zhí)行的代碼段是重要的。而且你可以通過手工的辦法做很多事以提高這些代碼段的效率。然而,手工改變代碼大小是一件很難的事情,而且編譯器處于一個更有利的位置,使得它可以在你所有的軟件模塊之間進行這種改變。

            直到你的程序工作起來,你可能已經(jīng)知道或者是非常的清楚,哪一個子程序或者模塊對于整體代碼效率是最關(guān)鍵的。中斷服務(wù)例程、高優(yōu)先級的任務(wù)、有實時限制的計算、計算密集型或者頻繁調(diào)用的函數(shù)都是候選對象。有一個叫作profiler 的工具,它包括在一些軟件開發(fā)工具組中,這個工具可以用來把你的視線集中到那些程序花費大部分時間(或者很多時間)的例程上去。
            一旦你確定了需要更高代碼效率的例程,可以運用下面的一種或者多種技術(shù)來減少它們的執(zhí)行時間。

            inline 函數(shù)
            在 c++中,關(guān)鍵字inline 可以被加入到任何函數(shù)的聲明。這個關(guān)鍵字請求編譯器用函數(shù)內(nèi)部的代碼替換所有對于指出的函數(shù)的調(diào)用。這樣做刪去了和實際函數(shù)調(diào)用相關(guān)的時間開銷,這種做法在inline 函數(shù)頻繁調(diào)用并且只包含幾行代碼的時候是最有效的。

            inline 函數(shù)提供了一個很好的例子,它說明了有時執(zhí)行的速度和代碼的太小是如何反向關(guān)聯(lián)的。重復(fù)的加入內(nèi)聯(lián)代碼會增加你的程序的大小,增加的大小和函數(shù)調(diào)用的次數(shù)成正比。而且,很明顯,如果函數(shù)越大,程序大小增加得越明顯。優(yōu)化后的程序運行的更快了,但是現(xiàn)在需要更多的ROM。

            查詢表
            switch 語句是一個普通的編程技術(shù),使用時需要注意。每一個由tb機器語言實現(xiàn)的測試和跳轉(zhuǎn)僅僅是為了決定下一步要做什么工作,就把寶貴的處理器時間耗盡了。為了提高速度,設(shè)法把具體的情況按照它們發(fā)生的相對頻率排序。換句話說,把最可能發(fā)生的情況放在第一,最不可能的情況放在最后。這樣會減少平均的執(zhí)行時間,但是在最差情況下根本沒有改善。

            如果每一個情況下都有許多的工作要做,那么也許把整個switch 語句用一個指向函數(shù)指針的表替換含更加有效。比如,下面的程序段是一個待改善的候選對象:

            enum NodeType {NodeA, NodeB, NodeC}
            switch(getNodeType())
            {
            case NodeA:
            ...
            case NodeB:
            ...
            case NodeC:
            ...
            }
            為了提高速度,我們要用下面的代碼替換這個switch 語句。這段代碼的第一部分是準備工作:一個函數(shù)指針數(shù)組的創(chuàng)建。第二部分是用更有效的一行語句替換switch 語句。

            int processNodeA(void);
            int processNodeB(void);
            int processNodeC(void);
            /*
            * Establishment of a table of pointers to functions.
            */
            int (* nodeFunctions[])() = { processNodeA, processNodeB, processNodeC };
            ...
            /*
            * The entire switch statement is replaced by the next line.
            */
            status = nodeFunctions[getNodeType()]();

            手工編寫匯編
            一些軟件模塊最好是用匯編語言來寫。這使得程序員有機會把程序盡可能變得有效率。盡管大部分的C/C++編譯器產(chǎn)生的機器代碼比一個一般水平的程序員編寫的機器代碼要好的多,但是對于一個給定的函數(shù),一個好的程序員仍然可能做得比一般水平的編譯器要好。比如,在我職業(yè)生涯的早期,我用C 實現(xiàn)了一個數(shù)字濾波器,把它作為TI TMS320C30 數(shù)字信號處理器的輸出目標。當時我們有的tb編譯器也許是不知道,也許是不能利用一個特殊的指令,該指令準確地執(zhí)行了我需要的那個數(shù)學(xué)操作。我用功能相同的內(nèi)聯(lián)匯編指令手工地替換了一段C 語言的循環(huán),這樣我就能夠把整個計算時間降低了十分之一以上。

            寄存器變量
            在聲明局部變量的時候可以使用 register 關(guān)鍵字。這就使得編譯器把變量放入一個多用選的寄存器,而不是堆棧里。合適地使用這種方琺,它會為編譯器提供關(guān)于最經(jīng)常訪問變量的提示,會稍微提高函數(shù)的執(zhí)行速度。函數(shù)調(diào)用得越是頻繁,這樣的改變就越是可能提高代碼的速度。

            全局變量
            使用全局變量比向函數(shù)傳遞參數(shù)更加有效率。這樣做去除了函數(shù)調(diào)用前參數(shù)入棧和函數(shù)完成后參數(shù)出棧的需要。實際上,任何子程序最有效率的實現(xiàn)是根本沒有參數(shù)。然而,決定使用全局變量對程序也可能有一些負作用。軟件工程人士通常不鼓勵使用全局變量,努力促進模塊化和重入目標,這些也是重要的考慮。

            輪詢
            中斷服務(wù)例程經(jīng)常用來提高程序的效率。然而,也有少數(shù)例子由于過度和中斷關(guān)聯(lián)而造成實際上效率低下。在這些情況中,中斷間的平均時間和中斷的等待時間具有相同量級。這種情況下,利用輪詢與硬件設(shè)備通信可能會更好。當然,這也會使軟件的模塊更少。

            定點運算
            除非你的目標平臺包含一個浮點運算的協(xié)處理器,否則你會費很大的勁去操縱你程序中的浮點數(shù)據(jù)。編譯器提供的浮點庫包含了一組模仿浮點運算協(xié)處理器指令組的子程序。很多這種函數(shù)要花費比它們的整數(shù)運算函數(shù)更長的執(zhí)行時間,并且也可能是不可重入的。

            如果你只是利用浮點數(shù)進行少量的運算,那么可能只利用定點運算來實現(xiàn)它更好。雖然只是明白如何做到這一點就夠困難的了,但是理論上用定點運算實現(xiàn)任何浮點計算都是可能的。(那就是所謂的浮點軟件庫。)你最大的有利條件是,你可能不必只是為了實現(xiàn)一個或者兩個計算而實現(xiàn)整個IEEE 754 標準。如果真的需要那種類型的完整功能,別離開編譯器的浮點庫,去尋找其他加速你程序的方法吧。


             

            posted @ 2013-07-23 17:19 tbwshc 閱讀(300) | 評論 (0)編輯 收藏

            你可能想知道為什么C++語言的創(chuàng)造者加入了如此多的昂貴的——就執(zhí)行時間和代碼大小來說——特性。你并不是少數(shù),全世界的人都在對同樣的一件事情困惑——特別是用C++做嵌入式編程的用戶們。很多這些昂貴的特性是最近添加的,它們既不是絕對的必要也不是原來C++規(guī)范的一部分。這些特性一個接著一個的被添加到正在進行著的“標準化”進程中來。在1996 年,一群日本的芯片廠商聯(lián)臺起來定義了一個C++語言和庫的子集,它更加適合嵌入式軟件開發(fā)。他們把他們新的工業(yè)標準叫作嵌入式C++。令人驚奇的是,在它的初期,它就在C++用戶群中產(chǎn)生了很大的影響。
            作為一個C++標準草案的合適子集,嵌入式C++省略了很多不限制下層語言可表達性的任何可以省略的東西。這些被省略的特性不僅包括像多重繼承性、虛擬基類、運行時類型識別和異常處理等昂貴的特性,而且還包括了一些最新的添加特性,比如:模板、命名tb空問、新的類型轉(zhuǎn)換等。所剩下的是一個C++的簡單版本,它仍然是面向?qū)ο蟮牟⑶沂荂 的一個超集,但是它具有明顯更少的運行開銷和更小的運行庫。
            很多商業(yè)的C++編譯器已經(jīng)專門地支持嵌入式C++標準。個別其他的編譯器允許手工的禁用具體的語言特性,這樣就使你能夠模仿嵌入式C++或者創(chuàng)建你的很個性化的C++語言。
            posted @ 2013-07-23 17:11 tbwshc 閱讀(361) | 評論 (0)編輯 收藏

            2013年7月10日 #

            最近在調(diào)試中遇到點內(nèi)存對齊的問題,別人問我是怎么回事,我趕緊偷偷查了一下,記錄下來。

            不論是C、C++對于內(nèi)存對齊的問題在原理上是一致的,對齊的原因和表現(xiàn),簡單總結(jié)一下,以便朋友們共享。

            一、內(nèi)存對齊的原因
            大部分的參考資料都是如是說的:
            1、平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
            2、性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問。

               也有的朋友說,內(nèi)存對齊出于對讀取的效率和數(shù)據(jù)的安全的考慮,我覺得也有一定的道理。

            二、對齊規(guī)則
                每個特定平臺上的編譯器都有自己的默認“對齊系數(shù)”(也叫對齊模數(shù))。比如32位windows平臺下,VC默認是按照8bytes對齊的(VC->Project->settings->c/c++->Code Generation中的truct member alignment 值默認是8),程序員可以通過預(yù)編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數(shù),其中的n就是你要指定的“對齊系數(shù)”。

                在嵌入式環(huán)境下,對齊往往與數(shù)據(jù)類型有關(guān),特別是C編譯器對缺省的結(jié)構(gòu)成員自然對屆條件為“N字節(jié)對 齊”,N即該成員數(shù)據(jù)類型的長度。如int型成員的自然對界條件為4字節(jié)對齊,而double類型的結(jié)構(gòu)成員的自然對界條件為8字節(jié)對齊。若該成員的起始 偏移不位于該成員的“默認自然對界條件”上,則在前一個節(jié)面后面添加適當個數(shù)的空字節(jié)。C編譯器缺省的結(jié)構(gòu)整體的自然對界條件為:該結(jié)構(gòu)所有成員中要求的 最大自然對界條件。若結(jié)構(gòu)體各成員長度之和不為“結(jié)構(gòu)整體自然對界條件的整數(shù)倍,則在最后一個成員后填充空字節(jié)。

                那么可以得到如下的小結(jié):

            類型 對齊方式(變量存放的起始地址相對于結(jié)構(gòu)的起始地址的偏移量)
            Char    偏移量必須為sizeof(char)即1的倍數(shù)
            Short   偏移量必須為sizeof(short)即2的倍數(shù)
            int     偏移量必須為sizeof(int)即4的倍數(shù)
            float   偏移量必須為sizeof(float)即4的倍數(shù)
            double  偏移量必須為sizeof(double)即8的倍數(shù)

               各成員變量在存放的時候根據(jù)在結(jié)構(gòu)中出現(xiàn)的順序依次申請空間,同時按照上面的對齊方式調(diào)整位置,空缺的字節(jié)編譯器會自動填充。同時為了確保結(jié)構(gòu)的大小為結(jié) 構(gòu)的字節(jié)邊界數(shù)(即該結(jié)構(gòu)中占用最大空間的類型所占用的字節(jié)數(shù))的倍數(shù),所以在為最后一個成員變量申請空間后,還會根據(jù)需要自動填充空缺的字節(jié),也就是 說:結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要編譯器會在最末一個成員之后加上填充字節(jié)。對于char數(shù)組,字節(jié)寬度仍然認為為1。

               對于下述的一個結(jié)構(gòu)體,其對齊方式為:

            struct Node1{

                double m1;
                char m2;
                int m3;
            };

              對于第一個變量m1,sizeof(double)=8個字節(jié);接下來為第二個成員m2分配空間,這時下一個可以分配的地址對于結(jié)構(gòu)的起始地址的偏移量為8,是sizeof(char)的倍數(shù),所以把m2存放在偏移量為8的地方滿足對齊方式,該成員變量占用 sizeof(char)=1個字節(jié);接下來為第三個成員m3分配空間,這時下一個可以分配的地址對于結(jié)構(gòu)的起始地址的偏移量為9,不是sizeof (int)=4的倍數(shù),為了滿足對齊方式對偏移量的約束問題,自動填充3個字節(jié)(這三個字節(jié)沒有放什么東西),這時下一個可以分配的地址對于結(jié)構(gòu)的起始地址的偏移量為12,剛好是sizeof(int), 由于8+4+4 = 16恰好是結(jié)構(gòu)體中最大空間類型double(8)的倍數(shù),所以sizeof(Node1) =16.

             

            typedef struct{

                char a;

                int b;

                char c;

            }Node2;

                成員a占一個字節(jié),所以a放在了第1位的位置;由于第二個變量b占4個字節(jié),為保證起始位置是4(sizeof(b))的倍數(shù),所以需要在a后面填充3個 字節(jié),也就是b放在了從第5位到第8位的位置,然后就是c放在了9的位置,此時4+4+1=9。接下來考慮字節(jié)邊界數(shù),9并不是最大空間類型int(4) 的倍數(shù),應(yīng)該取大于9且是4的的最小整數(shù)12,所以sizeof(Node2) = 12.
            typedef struct{

                char a;

                char b;

                int c;

            }Node3;

               明顯地:sizeof(Node3) = 8

               對于結(jié)構(gòu)體A中包含結(jié)構(gòu)體B的情況,將結(jié)構(gòu)體A中的結(jié)構(gòu)體成員B中的最寬的數(shù)據(jù)類型作為該結(jié)構(gòu)體成員B的數(shù)據(jù)寬度,同時結(jié)構(gòu)體成員B必須滿足上述對齊的規(guī)定。

               要注意在VC中有一個對齊系數(shù)的概念,若設(shè)置了對齊系數(shù),那么上述描述的對齊方式,則不適合。

               例如:

            1字節(jié)對齊(#pragma pack(1))
            輸出結(jié)果:sizeof(struct test_t) = 8 [兩個編譯器輸出一致]
            分析過程:
            成員數(shù)據(jù)對齊
            #pragma pack(1)
            struct test_t {
                int a;
                char b;
                short c;
                char d;
            };
            #pragma pack()
            成員總大小=8;

             

            2字節(jié)對齊(#pragma pack(2))
            輸出結(jié)果:sizeof(struct test_t) = 10 [兩個編譯器輸出一致]
            分析過程:
            成員數(shù)據(jù)對齊
            #pragma pack(2)
            struct test_t {
                int a;
                char b;
                short c;
                char d;
            };
            #pragma pack()
            成員總大小=9;

             

            4字節(jié)對齊(#pragma pack(4))
            輸出結(jié)果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
            分析過程:
            1) 成員數(shù)據(jù)對齊
            #pragma pack(4)
            struct test_t { //按幾對齊, 偏移量為后邊第一個取模為零的。
            int a;
            char b;
            short c;
            char d;
            };
            #pragma pack()
            成員總大小=9;

             

            8字節(jié)對齊(#pragma pack(8))
            輸出結(jié)果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
            分析過程:
            成員數(shù)據(jù)對齊
            #pragma pack(8)
            struct test_t {
            int a;
            char b;
            short c;
            char d;
            };
            #pragma pack()
            成員總大小=9;

             

            16字節(jié)對齊(#pragma pack(16))
            輸出結(jié)果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
            分析過程:
            1) 成員數(shù)據(jù)對齊
            #pragma pack(16)
            struct test_t {
            int a;
            char b;
            short c;
            char d;
            };
            #pragma pack()
            成員總大小=9;

             

            至于8字節(jié)對齊和16字節(jié)對齊,我覺得這兩個例子取得不好,沒有太大的參考意義。

            (x666f)

            posted @ 2013-07-10 17:09 tbwshc 閱讀(255) | 評論 (0)編輯 收藏

            2013年6月25日 #

            任何接口設(shè)計的一個準則:讓接口容易被正確使用,不容易被誤用。

            理想上:如何客戶企圖使用某個接口缺沒有獲得他所預(yù)期的行為,這個代碼不該通過編譯;如果代碼通過了編譯,他的行為就該是客戶所想要的。

            1. 導(dǎo)入外覆類型(wrapper types)

            2. 讓types容易被正確使用,不容易被誤用。盡量領(lǐng)你的types行為與內(nèi)置types一致。

            3. 設(shè)計class猶如設(shè)計type

            新type的對象應(yīng)該如何被創(chuàng)建和銷毀?(自己設(shè)計operatornew,operatornew[],operator delete和operator delete[])

            對象的初始化和對象的復(fù)制該有什么樣的差別?對應(yīng)于不同的函數(shù)調(diào)用

            新type的對象如果被passed by value ,意味著什么

            什么是新type的“合法值”?(??)

            你的新type需要配合某個繼承圖系嗎?

            你的心type需要什么樣的轉(zhuǎn)換

            什么樣的操作符合函數(shù)對此新type而言是合理的

            什么樣的標準函數(shù)應(yīng)該駁回

            誰該取用新type的成員

            什么是新type的未聲明接口

            你的新type有多么一般化

            你真的需要一個新type嗎

             

            一、寧以pass-by-reference-to-const 替換 pass-by-value

            1.tbw效率高,沒有任何構(gòu)造函數(shù)或析構(gòu)函數(shù)被調(diào)用,因為沒有任何新對象被創(chuàng)建。

            2. by refrenece方式傳遞參數(shù)還可以避免對象slicing 問題

            二、必須返回對象時,別妄想返回其reference

            所有用上static對象的設(shè)計,會造成多線程安全性的懷疑。

            三、將成員變量聲明為private:

            1. 可以實現(xiàn)出“不準訪問”、“只讀訪問”、“讀寫訪問”、“惟寫訪問”

            2. 封裝:它使我們能夠改變事物而只影響有限客戶。

            將成員變量隱藏在函數(shù)接口的背后,可以為“所有可能的實現(xiàn)”提供彈性。如:tb變量被讀或被寫時通知其他對象、可以驗證class的約束條件以及函數(shù)的前提和事后轉(zhuǎn)帖;以及在多線程環(huán)境執(zhí)行同步控制。。。。等。

            四、寧以non-member、non-friend替換member函數(shù)

            1. namespace WebBrowserStuff{ 
            2.         class WebBrowser{...}; 
            3.         void clearBrowser(WebBrowser& wb); 

            五、若所有參數(shù)皆需要類型轉(zhuǎn)換,請為此采用non-member函數(shù)

            1. class Rational { 
            2. ... 
            3.  
            4. const Rational operator*(const Rational& lhs,const Rational& rhs) 
            5. return Rational( lhs.numerator()* rhs.numerator()
            6. ,lhs.denominator()*rhs.denominator() ); 
            7.  
            8. Rational oneFourth(1,4); 
            9. Rational result; 
            10. result = oneFourth * 2; 
            11. result = 2*oneFourth; 

             六、考慮寫出一個不拋異常的swap函數(shù)

            posted @ 2013-06-25 17:17 tbwshc 閱讀(548) | 評論 (0)編輯 收藏

            好像所有講述編程的書都用同一個例子來開始,就是在用戶的屏幕上顯示出“Hello,World!”??偸鞘褂眠@個例子可能有一點叫人厭煩,可是它確實可以幫助讀者迅速地接觸到在編程環(huán)境中書寫簡單程序時的簡便方法和可能的困難。就這個意義來說,“Hello,World!”可以作為檢驗編程語言和計算機平臺的一個基準。

            不幸的是,如果按照這個標準來說,嵌入式系統(tǒng)可能是程序員工作中碰到的最難的計算機平臺了。甚至在某些嵌入式系統(tǒng)中,根本無法實現(xiàn)“Hello,World!”程序。即使在那些可以實現(xiàn)這個程序的嵌入式系統(tǒng)里面,文本字符串的輸出也更像是目標的一部分而不是開始的一部分。

            你看,“Hello,World!”示例隱含的假設(shè),就是有一個可以打印字符串的輸出設(shè)備。通常使用的是用戶顯示器上的一個窗口來完成這個功能。但是大多數(shù)的嵌入式系統(tǒng)并沒有一個顯示器或者類似的輸出設(shè)備。即使是對那些有顯示器的系統(tǒng),通常也需要用一小段嵌入式程序,通過調(diào)用顯示驅(qū)動程序來實現(xiàn)這個功能。這對一個嵌入式編程者來說絕對是一個相當具有挑戰(zhàn)性的開端。

            看起來我們還是最好以一個小的,容易實現(xiàn)并且高度可移植的聯(lián)人式程序來開始,這樣的tb程序也不太會有編程錯誤。歸根到底,我這本書繼續(xù)選用“Hello,World!”。這個例子的原因是,實現(xiàn)這個程序?qū)嵲谔唵瘟恕_@起碼在讀者的程序第一次就運行不起來的時候,會去掉一個可能的原因,即:錯誤不是因為代碼里的缺陷:相反,問題出在開發(fā)工具或者創(chuàng)建可執(zhí)行程序的過程里面。

            嵌人式程序員在很大程度上必須要依靠自己的力量來工作。在開始一個新項目的時候,除了他所熟悉的編程語言的語法,他必須首先假定什么東西都沒有運轉(zhuǎn)起來,甚至連標準庫都沒有,就是類似printf()和scanf()的那些程序員常常依賴的輔助函數(shù)。實際上,庫例程常常作為編程語言的基本語法出現(xiàn)??墒沁@部分標準很難支持所有可能的計算平臺,并且常常被嵌入式系統(tǒng)編譯器的制造商們所忽略。

            所以在這一章里你實際上將找不到一個真正的”Hello,World!”程序,相反,我們假定在第一個例子中只可以使用最基本的C 語言語法。隨著本書的進一步深人,我們會逐步向我們的指令系統(tǒng)里添加C++的語法、標準庫例程和一個等效的字符輸出設(shè)備。然后,在第九章“綜合所學(xué)的知識”里面。我們才最終實現(xiàn)一個“Hello,World!”程序。到那時候你將順利地走上成為一個嵌入式系統(tǒng)編程專家的道路。

            posted @ 2013-06-25 17:12 tbwshc 閱讀(304) | 評論 (0)編輯 收藏

            精品999久久久久久中文字幕| 久久丝袜精品中文字幕| 亚洲午夜久久久久妓女影院 | 2021国产精品久久精品| 亚洲精品无码久久久久去q | 国产V亚洲V天堂无码久久久| 亚洲国产精品久久66| 亚洲精品无码久久毛片| 国内精品久久久久影院一蜜桃| 国产巨作麻豆欧美亚洲综合久久 | 99精品久久久久久久婷婷| 久久国产精品久久| 狠狠综合久久综合88亚洲| 狠狠色丁香久久综合五月| 亚洲性久久久影院| 一本大道久久a久久精品综合| 一本色综合网久久| 久久久久亚洲AV综合波多野结衣 | 久久婷婷五月综合色奶水99啪| 精品一久久香蕉国产线看播放| 伊人久久精品无码av一区| 久久伊人影视| 情人伊人久久综合亚洲| 性高湖久久久久久久久| 久久国产V一级毛多内射| 久久免费视频观看| 久久天天躁狠狠躁夜夜96流白浆| 久久有码中文字幕| 久久99精品久久久久久秒播| 久久精品成人免费网站| 国产亚洲色婷婷久久99精品| 中文字幕人妻色偷偷久久| 波多野结衣久久一区二区| 亚洲欧洲精品成人久久曰影片 | 日本加勒比久久精品| 久久久久国色AV免费观看| AAA级久久久精品无码区| 天天久久狠狠色综合| 国内精品久久久久久久涩爱 | 久久综合一区二区无码| 欧美日韩中文字幕久久久不卡|