嵌入式系統運行的程序是如何編譯出來的?

​汽車上使用的ECU中運行的程序,是軟件工程師基於C/C++語言編寫出來,然後通過編譯器編譯得到可執行文件,最後將可執行文件刷寫入ECU中實現的,今天我們介紹下編譯過程。

通常我們使用GCC編譯器來實現編譯,對於GCC編譯器,我們就不展開介紹了,感興趣的同學可以到網上找些材料,自行充電。

【公衆號後臺回覆“高質量編程指南”獲取pdf文檔,100頁幹活,你不容錯過!】

0 四個階段

雖然我們稱GCC是C語言的編譯器,但使用GCC由C語言源代碼文件生成可執行文件的過程不僅僅是編譯的過程,而是要經歷四個相互關聯的步驟∶

預處理(也稱預編譯,Preprocessing)
編譯(Compilation)
彙編(Assembly)
鏈接(Linking)
GCC首先調用cpp進行預處理,在預處理過程中,對源代碼文件中包含的預編譯語句進行分析。然後調用編譯器進行編譯,這個階段根據輸入文件生成以.s爲後綴的彙編文件。彙編過程是針對彙編語言的步驟,將.S爲後綴的彙編語言源代碼文件生成以.o爲後綴的目標文件。當所有的目標文件都生成之後,GCC會完成最後的鏈接過程,最終生成可執行文件。

在這裏插入圖片描述

下面我們詳細看看每個階段。

1 預處理階段

讀取c源程序,對其中的僞指令(以#開頭的指令)和特殊符號進行處理。那麼,什麼事僞指令呢?僞指令主要包括以下四個方面:(1)宏定義指令,如#define Demu 520,#undef等。對於前一個僞指令,預編譯所要做的是將程序中的所有Demu用520替換,但作爲字符串常量的Demu則不被替換。對於後者,則將取消對某個宏的定義,使以後該串的出現不再被替換。(2)條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif等。這些條件編譯指令的引入使得程序員可以通過定義不同的宏來決定編譯程序對哪些代碼進行處理。預編譯程序將根據有關的文件,將那些不必要的代碼過濾掉。(3)頭文件包含指令,如#include “Filename"或者#include 等。在頭文件中一般用僞指令#define定義了大量的宏(最常見的是字符常量),同時包含有各種外部符號的聲明。採用頭文件的目的主要是爲了使某些定義可以供多個不同的C源程序使用。因爲在需要用到這些定義的C源程序中,只需加上一條#include語句即可,而不必再在此文件中將這些定義重複一遍。預編譯程序將把頭文件中的定義統統都加入到它所產生的輸出文件中,以供編譯程序對之進行處理。包含到c源程序中的頭文件可以是系統提供的。在程序中#include它們要使用尖括號(<>)。另外開發人員也可以定義自己的頭文件,這些文件一般與c源程序放在同一目錄下,此時在#include中要用雙引號(”")。所以,你知道爲什麼include的頭文件,尖括號和雙引號都有了吧,當然這是規範用法,大家敲代碼的時候最好也按照這個規範來。(4)特殊符號,預編譯程序可以識別一些特殊的符號。例如在源程序中出現的LINE標識將被解釋爲當前行號(十進制數),FILE則被解釋爲當前被編譯的C源程序的名稱。預編譯程序對於在源程序中出現的這些串將用合適的值進行替換。
在這裏插入圖片描述

預編譯程序所完成的基本上是對源程序的“替代”工作。經過此種替代,生成一個沒有宏定義、沒有條件編譯指令、沒有特殊符號的輸出文件。這個文件的含義同沒有經過預處理的源文件是相同的,但內容有所不同。下一步,此輸出文件將作爲編譯程序的輸出而被翻譯成爲機器指令。

2 編譯階段

這個階段,編譯器將預處理後的輸出文件進行編譯處理和優化處理。

編譯程序所要做的工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中間代碼表示或彙編代碼。

2.1 詞法分析

詞法分析的任務是:輸入源程序,對構成源程序的字符串進行掃描和分解,識別出一個個的單詞(亦稱單詞符號),如關鍵字(if,else,for,while)、標識符、常數、運算符和界符(標點符號、左右括號)。

單詞符號是語言的基本組成成分,是人們理解和編寫程序的基本要素。識別和理解這些要素無疑也是翻譯的基礎。如同將英文翻譯成中文的情形一樣,如果你對英語單詞不理解,那就談不上進行正確的翻譯。在詞法分析階段的工作中所依循的是語言的詞法規則(也稱構詞規則)。

2.2 語法分析

語法分析的任務是:在詞法分析的基礎上,根據語言的語法規則,把單詞符號串分解成各類語法單位(語法範疇),如“短語”、“句子”、“程序段”和“程序”等。通過語法分析,確定整個輸入串是否構成語法上正確的“程序”。語法分析所依循的是語言的語法規則。語法規則通常用上下文無關文法描述。詞法分析是一種線性分析,而語法分析是一種層次結構分析。

例如:

Z = X + 0.618 * Y;

代表一個“賦值語句”,而其中的X + 0.618 * Y 代表一個“算術表達式”。因而,語法分析的任務就是識別X + 0.618 * Y爲算術表達式,同時,識別整個符號串屬於賦值語句的範疇。

2.3 優化處理

優化處理是編譯系統中一項比較深奧的技術。它涉及到的問題不僅同編譯技術本身有關,而且同機器的硬件環境也有很大的關係。優化一方面是對中間代碼的優化,不依賴於具體的計算機。另一種優化則主要針對目標代碼的生成而進行的。

對於前一種優化,主要的工作是刪除公共表達式、循環優化(代碼外提、強度削弱、變換循環控制條件、已知量的合併等)、複寫傳播,以及無用賦值的刪除等。

後一種類型的優化同機器的硬件結構密切相關,最主要的是考慮是如何充分利用機器的各個硬件寄存器存放有關變量的值,以減少對於內存的訪問次數。另外,如何根據機器硬件執行指令的特點對指令進行一些調整使目標代碼比較短,執行的效率比較高,這一點非常重要。

2.4 中間代碼生成

對語法分析所識別出的各類語法範疇,分析其含義,然後進行初步翻譯,產生中間代碼。這一階段通常包含兩個方面的工作。

首先,對每種語法範疇進行語義i安插,例如,變量是否定義、類型是否正確等等。如果語義正確,則進行另一方面工作,即進行中間代碼的解釋。這一階段所依循的是語言的語義規則。通常使用屬性文法描述語義規則。

“翻譯”僅僅在這裏纔開始涉及到。所謂“中間代碼”是一種含義明確、便於處理的記號系統,它通常獨立於具體的硬件。這種記號系統或者與現代計算機的指令形式比較接近,或者能夠比較容易地把它變換成現代計算機的機器指令。例如,許多編譯程序採用了“四元式”作爲中間代碼。這種四元式的形式是:

算符/左操作數/右操作數/結果

它的意義是:對“左右操作數”進行某種運算(由“算符”指明),把運算所得的值作爲“結果”保留下來。在採用四元式作爲中間代碼的情形下,中間代碼產生的任務就是按語言的語法規則把各類範疇翻譯成四元式序列。

例如,下面的賦值語句:

Z = (X + 0.618) * Y / W;

可被翻譯爲如下的四元式序列:
在這裏插入圖片描述
其中,T1和T2是編譯期間引進的臨時工作變量;第一個四元式意味着把X的值加上0.618存放在T1中;第二個四元式值將T1的值和Y的值相乘存於T2中;第三個四元式指將T2的值除以Y的值留結果於Z中。

一般而言,中間代碼是一種獨立於具體硬件的記號系統。常用的中間代碼,除了四元式之外,還有三元式、間接三元式、逆波蘭記號和樹形表示等等。

這樣,經過以上分析和優化後,彙編代碼經過彙編程序的彙編轉換成相應的機器指令,纔可能被機器執行。
在這裏插入圖片描述

3 彙編階段

彙編過程實際上指把彙編語言代碼翻譯成目標機器指令的過程。對於被翻譯系統處理的每一個C語言源程序,都將最終經過這一處理而得到相應的目標文件。目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。目標文件由段組成。通常一個目標文件中至少有兩個段:代碼段:該段中所包含的主要是程序的指令。該段一般是可讀和可執行的,但一般卻不可寫。數據段:主要存放程序中要用到的各種全局變量或靜態的數據。一般數據段都是可讀,可寫,可執行的。嵌入式系統中主要有三種類型的目標文件:

(1)可重定位文件(relocatable)

其中包含有適合於其它目標文件鏈接來創建一個可執行的或者共享的目標文件的代碼和數據。

(2)共享的目標文件(shared)

這種文件存放了適合於在兩種上下文裏鏈接的代碼和數據。第一種是鏈接程序可把它與其它可重定位文件及共享的目標文件一起處理來創建另一個目標文件;第二種是動態鏈接程序將它與另一個可執行文件及其它的共享目標文件結合到一起,創建一個進程映象。

(3)可執行文件(executable)

它包含了一個可以被操作系統創建一個進程來執行之的文件。

彙編程序生成的實際上是第一種類型的目標文件。對於後兩種還需要其他的一些處理方能得到,這個就是鏈接程序的工作了。

4 鏈接階段

由彙編程序生成的目標文件並不能立即就被執行,其中可能還有許多沒有解決的問題。

例如,某個源文件中的函數可能引用了另一個源文件中定義的某個符號(如變量或者函數調用),在程序中可能調用了某個庫文件中的函數等。所有的這些問題,都需要經鏈接程序的處理方能得以解決。

鏈接程序的主要任務是將有關的目標文件彼此相連接,即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標文件成爲一個能夠被操作系統裝入執行的統一整體。

根據開發人員指定的同庫函數的鏈接方式的不同,鏈接處理分爲兩種:

4.1 靜態鏈接

在這種鏈接方式下,函數的代碼將從其所在的靜態鏈接庫中被拷貝到最終的可執行程序中。這樣該程序在被執行時這些代碼將被裝入到該進程的虛擬地址空間中。靜態鏈接庫實際上是一個目標文件的集合,其中的每個文件含有庫中的一個或者一組相關函數的代碼。

4.2 動態鏈接

在此種方式下,函數的代碼被放到稱作是動態鏈接庫或共享對象的某個目標文件中。鏈接程序此時所做的只是在最終的可執行程序中記錄下共享對象的名字以及其它少量的登記信息。在此可執行文件被執行時,動態鏈接庫的全部內容將被映射到運行時相應進程的虛地址空間。動態鏈接程序將根據可執行程序中記錄的信息找到相應的函數代碼。

對於可執行文件中的函數調用,可分別採用動態鏈接或靜態鏈接的方法。使用動態鏈接能夠使最終的可執行文件比較短小,並且當共享對象被多個進程使用時能節約一些內存,因爲在內存中只需要保存一份此共享對象的代碼。但並不是使用動態鏈接就一定比使用靜態鏈接要優越。在某些情況下動態鏈接可能帶來一些性能上損害。

在這裏插入圖片描述

更多文章和幹活,請關注公衆號【汽車控制與人工智能】。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章