• <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++博客 :: 首頁 :: 聯系 :: 聚合  :: 管理
              95 Posts :: 8 Stories :: 3 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(4)

            我參與的團隊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            2013年9月11日 #

            從一個任務轉變到另一個任務的實際過程叫作設備場景切換。因為設備場景是處理器專用的,實現設備場景切換的實現也是這樣。那意味著它總是要用匯編來寫。與其向你展示我在ADEOS 中使用的80x86 專用的匯編代碼,不如我用一種類C 的偽代碼來展示設備場景切換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()實際上是被調度程序凋用,而調度程序又在那此終止中斷的tb系統調用中被調用,因此它不一定在這里終止中斷。此外,由于調用調度程序的操作系統調用是用高級語言寫的,所以大部分運行任務的寄存器已經被保存到它自己當地的棧中了。這減少了例程saveContext()和restoreContext()需要做的工作。它們只需要關心指令指針,棧指針以及標志位的保存。例程 contextSwitch()的實際行為是很難僅僅通過看前面的代碼來理解的。大部分的軟件開發者以連續的方式思考問題,認為每一行代碼會緊接著上一條代碼破執行。然而,這個代碼實際為并行地執行了兩次。當一個任務(新任務)轉變到運行狀態,另一個(舊任務)必須同時返回到就緒狀態。想一下新任務當它在restoreContext()代碼中被恢復的時候就會明白。無論新任務以前做什么,它在saveContext 代碼里總是醒著的——因為這就是它的指令存放的地方。新任務如何知道它是否是第一次(也就是,在準備休眠的過程)或者是第二次(醒來的過程)從saveContext()中出來的呢?它確實需要知道這個差別,因此我不得不用一種有點隱蔽的方法來實現saveContext()。例程saveContext()不是保存了準確的目前的指令指針。實際上是保存了一些指令前面的地址。那樣,當保存的設備場景恢復的時候,程序從saveContext 中另一個下同的點繼續。這也使得saveContext 可能返回不同的值:當任務要休眠的時候為非零,當任務喚起的時候為零。例程contextSwitch()利用這個返回的值來決定是否調用restoreContext()。如果不進行這個檢測,那么與這個新任務相關的代碼永遠不會執行。

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

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

            2013年9月5日 #

            作為 ADEOS 的開發者(或是其他操作系統的開發者),你需要知道如何創建和使用任務。就像別的抽象數據結構,Task 類有自己的成員函數。ADEOS的的任務接口比別的大多數操作系統要簡單一些,因為它只是創建一個新的Task 對象。一旦創建,ADEOS 任務繼續在系統中存在,直到相關的函數返回。當然,這也許永遠不會發生(意即ADEOS 任務也許永遠不會結束),但是,如果一旦發生了,那么該任務就會被操作系統刪除掉。Task 的構造函數如下所示。調用者通過構造函數的參數分配一個函數,一個權限值,和一個可選擇的新任務的堆棧大小。第一個參數,fUnCtion,是一個指向C/C++語言或匯編語言的函數指針,該函數是要在新任務的上下文環境中運行的。該函數不需要任何輸人參數,也不返回任何結果。第二個參數P,是一個單字節的整數(從1 到255),代表了任務的權限級別,這個權限級別是與別的任務相對而言的,在tb任務調度器選擇新的任務運行的時候會用到(p 的值越大,表示權限越高)。
            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() */
            注意這個例程的功能塊被兩個函數 enterCS()和exitCS()的調用包圍。在這些調用之間的代碼塊叫作tb臨界區(critical section)。臨界區是一個程序必須完整執行的一部分。也就是說,組成這一個部分的指令必須沒有中斷地按照順序執行。因為中斷可能隨時發生,保證不受到中斷的唯一辦法就是在執行關鍵區期間禁止中斷。因此在關鍵區的開始調用enterCS 以保存中斷的允許狀態以及禁止進一步的中斷。在關鍵區尾部調用exitCS 以恢復前面保存的中斷調用。我們會看到在下面每一個例程中都應用了同樣的技巧。
            在前面代碼中,有幾個在構造函數里調用的其他例程,但是在這里我沒有空間列出。它們是contextInit()和os.readyList.insert()例程。例程contextInit()為任務建立了初始的設備場景。這個例程必定是處理器專用的,因此是用匯編語言寫的。
            contextInit()有四個參數。第一個是一個指向待初始比的設備場景數據結構指針。第二個是一個指向啟動函數的指針。這是一個特殊的ADEOS 函數,叫作run(),它被用來啟動一個任務,并且如果以后相關的函數退出了,它被用來做其后的清理工作。第三個參數是一個指向新任務對象的指針。這個參數被傳遞給run(),因此相關的任務就能夠被啟動。第四個和最后一個參數是指向新任務棧的指針。
            另一個函數調用是 os.readyList.insert()。這個函數把新任務加入到操作系統內部的就緒任務列表中。readyList 是一個TaskList 類型的對象。這個類是那些具有insert()和remove()兩個方法的任務(按照優先級排序)的鏈表。感興趣的讀者如果想知道這些函數是如何實現的就應該下載和研究其ADEOS 的源代碼。你將在下面的討論中了解到更多有關就緒列表的問題。
            posted @ 2013-09-05 16:58 tbwshc 閱讀(833) | 評論 (0)編輯 收藏

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

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

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

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

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

            2013年8月19日 #

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

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

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

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

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

            #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 閱讀(265) | 評論 (0)編輯 收藏

            2013年7月23日 #

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

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

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

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

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

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

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

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

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

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

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

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

            inline 函數提供了一個很好的例子,它說明了有時執行的速度和代碼的太小是如何反向關聯的。重復的加入內聯代碼會增加你的程序的大小,增加的大小和函數調用的次數成正比。而且,很明顯,如果函數越大,程序大小增加得越明顯。優化后的程序運行的更快了,但是現在需要更多的ROM。

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

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

            enum NodeType {NodeA, NodeB, NodeC}
            switch(getNodeType())
            {
            case NodeA:
            ...
            case NodeB:
            ...
            case NodeC:
            ...
            }
            為了提高速度,我們要用下面的代碼替換這個switch 語句。這段代碼的第一部分是準備工作:一個函數指針數組的創建。第二部分是用更有效的一行語句替換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++編譯器產生的機器代碼比一個一般水平的程序員編寫的機器代碼要好的多,但是對于一個給定的函數,一個好的程序員仍然可能做得比一般水平的編譯器要好。比如,在我職業生涯的早期,我用C 實現了一個數字濾波器,把它作為TI TMS320C30 數字信號處理器的輸出目標。當時我們有的tb編譯器也許是不知道,也許是不能利用一個特殊的指令,該指令準確地執行了我需要的那個數學操作。我用功能相同的內聯匯編指令手工地替換了一段C 語言的循環,這樣我就能夠把整個計算時間降低了十分之一以上。

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

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

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

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

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


             

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

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

            2013年7月10日 #

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

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

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

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

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

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

                那么可以得到如下的小結:

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

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

               對于下述的一個結構體,其對齊方式為:

            struct Node1{

                double m1;
                char m2;
                int m3;
            };

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

             

            typedef struct{

                char a;

                int b;

                char c;

            }Node2;

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

                char a;

                char b;

                int c;

            }Node3;

               明顯地:sizeof(Node3) = 8

               對于結構體A中包含結構體B的情況,將結構體A中的結構體成員B中的最寬的數據類型作為該結構體成員B的數據寬度,同時結構體成員B必須滿足上述對齊的規定。

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

               例如:

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

             

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

             

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

             

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

             

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

             

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

            (x666f)

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

            2013年6月25日 #

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

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

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

            2. 讓types容易被正確使用,不容易被誤用。盡量領你的types行為與內置types一致。

            3. 設計class猶如設計type

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

            對象的初始化和對象的復制該有什么樣的差別?對應于不同的函數調用

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

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

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

            你的心type需要什么樣的轉換

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

            什么樣的標準函數應該駁回

            誰該取用新type的成員

            什么是新type的未聲明接口

            你的新type有多么一般化

            你真的需要一個新type嗎

             

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

            1.tbw效率高,沒有任何構造函數或析構函數被調用,因為沒有任何新對象被創建。

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

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

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

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

            1. 可以實現出“不準訪問”、“只讀訪問”、“讀寫訪問”、“惟寫訪問”

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

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

            四、寧以non-member、non-friend替換member函數

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

            五、若所有參數皆需要類型轉換,請為此采用non-member函數

            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函數

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

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

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

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

            看起來我們還是最好以一個小的,容易實現并且高度可移植的聯人式程序來開始,這樣的tb程序也不太會有編程錯誤。歸根到底,我這本書繼續選用“Hello,World!”。這個例子的原因是,實現這個程序實在太簡單了。這起碼在讀者的程序第一次就運行不起來的時候,會去掉一個可能的原因,即:錯誤不是因為代碼里的缺陷:相反,問題出在開發工具或者創建可執行程序的過程里面。

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

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

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

            亚洲香蕉网久久综合影视 | 亚洲AV日韩精品久久久久久| 亚洲精品无码久久不卡| 国产偷久久久精品专区 | 亚洲精品乱码久久久久久不卡| 久久精品免费全国观看国产| 少妇精品久久久一区二区三区| 2021久久精品国产99国产精品| 久久精品二区| 91久久精一区二区三区大全| 久久精品国产福利国产琪琪| 久久精品国产第一区二区三区| 久久国产V一级毛多内射| 无码伊人66久久大杳蕉网站谷歌| 久久综合狠狠色综合伊人| 99久久做夜夜爱天天做精品| 久久婷婷久久一区二区三区| 热99RE久久精品这里都是精品免费| 国产成人久久激情91| 国产99久久久国产精品小说| 九九久久精品无码专区| 国产精品久久一区二区三区| 欧美日韩精品久久免费| 久久夜色精品国产| 国产高清美女一级a毛片久久w| 国产成人久久AV免费| 思思久久99热只有频精品66| 久久综合狠狠综合久久97色| 久久久久四虎国产精品| 精品久久久久久久无码| 久久香蕉超碰97国产精品| 久久国语露脸国产精品电影| 伊人久久大香线蕉AV一区二区| 国产精品日韩深夜福利久久| 伊人久久大香线蕉精品| 久久精品免费一区二区三区| WWW婷婷AV久久久影片| 97久久久精品综合88久久| 国产人久久人人人人爽| 国内精品伊人久久久久AV影院| 久久久久高潮毛片免费全部播放|