歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Freestanding C與交叉編譯器的生成原理分析

0 問題由來

以前也用過C51寫過簡單的裸機程序,但是並沒有認真的考慮過其與Linux環境下一般C語言程序的不同,只是想當然地認為C是跨平台的語言,並沒有考慮過C語言的標准問題。

今天在編譯GCC交叉編譯器時,遇到了種種問題,不得不重新考慮C語言的實現標准,否則很難清晰的了解交叉編譯器的編譯過程。

1 C編譯器的兩種實現要求

C語言標准的正式文檔中明確提出了C編譯器的兩種實現標准:

1.1 conforming freestanding implementation

所謂的Freestanding,C編譯器只需提供C語言語句的編譯,外加

<float.h>
<limits.h>
<stdarg.h>
<stdint.h>
<stdalign.h>
<stdnoreturn.h>

等基本的類型與數值范圍定義頭文件。

可以看出,在這種級別的編譯器下寫程序時,基本的printf都是不能夠直接使用的。
此種實現,主要用於裸機開發,比如OS開發,bootloader,以及C庫本身的開發。

1.2 conforming hosted implementation

這是一種更加全面的實現,除了包含Freestanding要求的功能外,必須包含完整的標准C庫實現。這也是大多數應用程序員能接觸到的環境。由於<stdio.h>中的輸入輸出函數的實現需要OS系統調用的支持,Hosted編譯器都是依賴於具體的操作系統而存在。

1.3 小結

可以看出,Hosted是比Freestanding更加全面的C實現要求,Hosted本身包含了Freestanding。例如Hosted的GCC實現,提供了-freestanding參數來作為Freestanding降級使用。

gcc -ffreestanding

C語言這種把語言本身和庫獨立對待的方式,使得它能夠適應更廣泛的開發環境。從單片機裸機開發到大型數據庫系統,C語言的身影無處不在。

2 C語言的兩種執行環境

與C語言的兩種實現等級相對應,C語言的程序的執行環境也有兩種,這裡直接貼出C語言標准原文。

5.1.2 Execution environments
Tw o execution environments are defined: freestanding and hosted. In both cases,
program startup occurs when a designated C function is called by the execution
environment. All objects with static storage duration shall be initialized (set to their
initial values) before program startup. The manner and timing of such initialization are
otherwise unspecified. Program termination returns control to the execution
environment.

在Hosted環境下,C程序入口點必須是命名為main的函數,而且其函數原型必須符合標准。

而在Freestanding環境下,C程序的入口點沒有指定的名稱,由程序員自行決定。

3 C語言交叉編譯器制作過程原理分析

3.1 為什麼需要單獨生成不同目標平台的編譯器

理論上一個編譯器可以通過命令行參數來編譯生成各種不同平台的可執行程序。例如:

mycompiler -platform=arm-linux 1.c  # 生成可以運行在ARM平台上的Linux程序
mycompiler -platform=mips-linux 1.c # 生成可以運行在MIPS平台的Linux程序
mycompiler -platform=i386-linux 1.c # 生成可以運行在Intel X86平台的Linux程序

然而實際上,這種做法的代價是十分巨大的,因為

  • 不同平台差異巨大,代碼轉換邏輯完全不同,在一個可執行程序中包含所有平台轉換代碼,導致編譯器程序本身體積巨大
  • 不同平台的C庫實現嚴重依賴於平台,在一個庫中包含所有平台的實現代碼,造成C庫體積巨大,管理混亂。
  • 匯編器、連接器也存在同樣的問題

所以在現實中,雖然GCC,GLIBC,binutils這樣的項目本身能夠支持很多的軟硬件平台,但是支持方式是通過配置來進行選擇性的編譯部分源代碼來生成針對具體平台二進制工具。

3.2 交叉編譯器生成步驟原理分析

理論上一個C交叉編譯器的生成非常簡單:
(0)編譯生成交叉匯編器和交叉鏈接器
之所以會存在這個步驟是因為,像GCC這樣的C編譯器本身只負責把C語言代碼編譯為匯編代碼。把匯編代碼轉換為二進制機器碼,以及把二進制對象文件鏈接為可執行文件的功能需要其他程序來實現。對Unix世界來說,提供匯編鏈接功能的就是著名的binutils包。

gcc編譯器在生成時,需要使用binutils來測試相關功能,以動態生成各種配置與代碼。所以,在編譯GCC之前,必須要有可用的binutils。

對於匯編程序員來說,binutils已經提供了一個完整的匯編開發環境,完全可以停留這裡享受匯編程序設計的美好。
然而,對於C程序員來說,這才剛剛開始。

(1)編譯生成Freestanding的交叉編譯器;
這個交叉編譯器完全可以用來編譯裸機C程序,編譯OS,編譯BootLoader,對於嵌入式底層開發者來說,這樣一個編譯器就足夠了,完全可以到此為止。

然而對於一個在OS環境下的應用開發者來說,就需要一個功能更完整的編譯器,以及完整的標准C庫,這就需要進入步驟(2)。

(2)用剛生成的交叉編譯器編譯生成標准C庫;
Freestanding編譯器可以用來編譯標准C庫,這個是很重要的。

此時雖然編譯器和C庫都有了,卻存在兩個缺陷:

  • 編譯器可執行文件本身並不知道對應的C庫的路徑,所以編譯程序時就需要額外的命令行參數來提供這些信息。
  • 編譯器不能提供Hosted類型C程序初始化環境,因為Hosted環境的建立需要標准C庫的支持,而Freestanding編譯器在生成時還沒有可用的標准C庫。這就是一個“雞生蛋蛋生雞”的問題了。

為了解決上述問題,需要額外的一個步驟:
(3)重新編譯生成Hosted的交叉編譯器。
這次編譯時,可以有(2)生成的標准C庫的支持了,所以可以產生出一個完整的Hosted交叉編譯器了。

Copyright © Linux教程網 All Rights Reserved