什么是交叉編譯?交叉編譯是一個行為,是在一個平臺上生成另一個平臺上的可執行代碼。
- 本地編譯:本地編譯可以理解為,在當前編譯平臺下,編譯出來的程序只能放到當前平臺下運行。平時我們常見的軟件開發,都是屬于本地編譯:比如,我們在 x86 平臺上,編寫程序并編譯成可執行程序。這種方式下,我們使用 x86 平臺上的工具,開發針對 x86 平臺本身的可執行程序,這個編譯過程稱為本地編譯。
- 交叉編譯:交叉編譯可以理解為,在當前編譯平臺下,編譯出來的程序能運行在體系結構不同的另一種目標平臺上,但是編譯平臺本身卻不能運行該程序:比如,我們在 x86 平臺上,編寫程序并編譯成能運行在 ARM 平臺的程序,編譯得到的程序在 x86 平臺上是不能運行的,必須放到 ARM 平臺上才能運行。
- 宿主機(host) :編輯和編譯程序的平臺,一般是基于X86的PC機,通常也被稱為主機。
- 目標機(target):用戶開發的系統,通常都是非X86平臺。host編譯得到的可執行代碼在target上運行。
- prefix:交叉編譯器的安裝位置。
- 比如:我們在windows上面編寫51和32的代碼,并編譯成可執行代碼,如xx.hex,是在C51上面運行不是在windows上面運行,交叉編譯的發生在keil(集成環境上面)。同樣:我們在ubuntu(linux)上面編寫樹莓派的代碼,并編譯成可執行代碼,如xx.out,是在樹莓派上面運行不是在ubuntu(linux)上面運行。
為什么要交叉編譯?
-
有時是因為目的平臺上不允許或不能夠安裝我們所需要的編譯器,而我們又需要這個編譯器的某些特征
-
有時是因為目的平臺上的資源貧乏,無法運行我們所需要編譯器
-
有時又是因為目的平臺還沒有建立,連操作系統都沒有,根本談不上運行什么編譯器。
-
在項目的起始階段,目的平臺尚未建立,因此需要做交叉編譯,以生成我們所需要的bootloader(啟動引導代碼)以及操作系統核心
-
其次,當目的平臺能啟動之后,由于目的平臺上資源的限制,當我們編譯大型程序時,依然可能需要用到交叉編譯。
-
Speed: 目標平臺的運行速度往往比主機慢得多,許多專用的嵌入式硬件被設計為低成本和低功耗,沒有太高的性能
-
Capability: 整個編譯過程是非常消耗資源的,嵌入式系統往往沒有足夠的內存或磁盤空間
-
Availability: 即使目標平臺資源很充足,可以本地編譯,但是第一個在目標平臺上運行的本地編譯器總需要通過交叉編譯獲得
-
Flexibility: 一個完整的Linux編譯環境需要很多支持包,交叉編譯使我們不需要花時間將各種支持包移植到目標板上
交叉編譯工具鏈(交叉編譯器)是什么?一般由編譯器、連接器、解釋器和調試器組成,就是為了編譯、鏈接、處理和調試跨平臺體系結構的程序代碼。
要進行交叉編譯,我們需要在主機平臺上安裝對應的交叉編譯工具鏈(cross compilation tool chain),然后用這個交叉編譯工具鏈編譯我們的源代碼,最終生成可在目標平臺上運行的代碼。常見的交叉編譯例子如下:
- 在Windows PC上,利用ADS(ARM開發環境),使用armcc編譯器,則可編譯出針對ARM CPU的可執行代碼
- 在Linux PC上,利用arm-linux-gcc編譯器,可編譯出針對Linux ARM平臺的可執行代碼
- 在Windows PC上,利用cygwin環境,運行arm-elf-gcc編譯器,可編譯出針對ARM CPU的可執行代碼
- 樹莓派有flash和運行內存(RAM隨機存儲器),但是樹莓派有時因為目標平臺沒有建立,連操作系統都沒有,根本談不上編譯器。因此需要做交叉編譯,以生成平臺至少所需要的:bootloader(啟動引導代碼) 以及 操作系統核心
交叉編譯工具鏈的安裝:
不同的平臺用的交叉比編譯器不同,就像我們在用keil編譯51的代碼時要選擇對應的芯片,編譯32的代碼時也要選擇對應的芯片,這其實都是在間接地選擇了編譯器。如果目標機是樹莓派,那么我們將會用到樹莓派的交叉編譯工具鏈。
-
首先下載樹莓派的交叉編譯工具鏈,下載地址點這里,點擊download下載即可。
-
交叉編譯工具鏈的安裝有兩種方法:①臨時有效 ②永久有效
-
臨時有效:將路徑加入PATH環境變量(將命令加入終端,使命令使用更加方便,不用敲很長的路徑),
echo $PATH
顯示當下的環境變量,然后使用指令:export PATH=
設置新的環境變量
-
永久有效:
vi .bashrc
打開工作目錄下的.bashrc (隱藏文件),用來配置命令終端的,每次啟動終端都會執行這個腳本。將上面的配置命令添加在這個文件的最后一行即可。然后source /home/CLC/.bashrc
使用這個指令是設置立即生效
-
下面記錄在ubuntu上面安裝樹莓派的交叉編譯工具,首先在上方的那個網址下載交叉編譯工具到與虛擬機共享的文件夾,然后將壓縮包拷貝到虛擬機使用指令
cp /mnt/hgfs/Sharefromwindows/tools-master.zip .
-
然后使用指令進行解壓:
unzip tools-master.zip,
解壓完成后會出現tool-master
這個文件夾,進入這個文件夾,可以看到arm-bcm2708
,所需要的交叉編譯工具就是在這里,進入這個文件夾gcc-linaro-arm-linux-gnueabihf-raspbian-x64
因為我的虛擬機(宿主機)是64位的所以選擇這個文件夾,如果是32位的用gcc-linaro-arm-linux-gnueabihf-raspbian
這個文件夾夾里面的。進入自己對應的文件夾,然后進入bin目錄下,可以看到很多綠色的可執行文件
-
我們要用的交叉編譯工具就是淺藍色的那個軟連接(相當于windows下面的快捷方式)
arm-linux-gnueabihf-gcc
,可以使用指令:./arm-linux-gnueabihf-gcc -v
查看交叉編譯1器的版本相關信息,和查看gcc版本的指令類似:gcc -v
-
在ubuntu里面編譯一個可執行代碼,可以通過
file testone
來查看文件的屬性,如下圖所示:x86-64 表示只能運行在64位的X86的機子上,這樣在可執行程序就不能在樹莓派上面跑。
-
如果想要在樹莓派上面運行,就要使用指令編譯:
arm-linux-gnueabihf-gcc testone.c -o testone
這樣生成的testone就可以在ARM平臺上面運行。
-
可以使用指令:
scp testone pi@192.168.43.136:/home/pi
testone是要移動到樹莓派的可執行文件,192.168.43.136是樹莓派的ip地址,冒號后面是將文件放到樹莓派的哪一個位置。可以看到綠色的可執行文件testone然后可執行。
-
如果將一個只能在x86運行的代碼,移動到樹莓派上面,則會報錯:
-bash: ./testone: cannot execute binary file: Exec format error
表示不能解析這個二進制文件。同樣我們可以將項目二代碼的服務端移到樹莓派上面運行,客戶端在虛擬機上面運行。
客戶端:
服務端:
帶WiringPi庫的代碼如何在上位機進行交叉編譯?
-
需要先在宿主機上面(這里是ubuntu)安裝wiringPi庫
-
wiringPi庫百度網盤:鏈接:https://pan.baidu.com/s/1cPIt-xZLye1DAQjq2yKzeg提取碼:35vt
-
進入解壓后的WiringPi文件夾,然后打開INSTALL這個文件夾,查看如何下載,這里顯示直接執行build文件即可(編譯wiringPi庫生成動態庫,編譯時需要鏈接,但是它是使用的gcc編譯器,編譯出來庫是只能運行在x86平臺上的),然后將會下載到
/usr/local/lib
這個目錄下,然后可以使用指令:arm-linux-gnueabihf-gcc demo.c -I /home/fhn/wringPi/WiringPi/wiringPi -lwiringPi
但是會發現報錯,是因為鏈接wiringPi庫的時候是鏈的/usr/local/lib
這個目錄下的libwiringPi.so
-
但是
file libwiringPi.so
去查看這個文件的屬性,發現這個文件的屬性是在x86上面運行的,然而我們需要在ARM上面運行,所以是不兼容的,會報錯:/home/fhn/arm-tool/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/../lib/gcc/arm-linux-gnueabihf/4.8.3/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lwiringPi collect2: error: ld returned 1 exit status
-
正常情況下我們先要交叉編譯wiringPi庫,編譯出適合樹莓派的庫,這時候交叉編譯可執行程序的時候,鏈接庫的格式也是正確的。那么我們可以將樹莓派的wiringPi庫拿到虛擬機,
libwiringPi.so
就這個動態庫,但是這是個軟連接(相當于快捷方式)
-
使用指令:
ls -l |grep libwiringPi.so
可以看出這個文件是指向libwiringPi.so.2.50這個動態庫的。所以我們需要將這個動態庫發送到ubuntu里面,使用指令:scp libwiringPi.so.2.50 fhn@192.168.43.112:/home/fhn/wringPi/WiringPi
,就算將軟連接發送到ubuntu也不會指向這個動態庫,還需要我們自己創建動態庫。如果傳輸過程中出現錯誤:ssh: connect to host 192.168.43.112 port 22: Connection refused lost connection
表示ubuntu沒有安裝ssh,在ubuntu里面下載ssh即可:sudo apt-get install ssh
-
使用指令:
ln -s libwiringPi.so.2.50 libwiringPi.so
創建軟連接,下面有詳細的講解軟硬鏈接,創建軟連接后使用指令:arm-linux-gnueabihf-gcc hc.c -I /home/fhn/wringPi/WiringPi/wiringPi -L . -lwiringPi -o hc
進行編譯,其中:-I /home/fhn/wringPi/WiringPi/wiringPi
是鏈接的wiringPi庫的頭文件,-L
是指定動態庫在當前目錄下去查找庫文件。 -
學習指令:
grep gcc * -nir
在所有文件里面查找gcc字眼,n表示顯示行號、i表示不區分大小寫、n表示遞歸查找、*表示在所有文件里面去查找。
硬盤:
在介紹硬鏈接和軟鏈接之前,首先介紹下硬盤相關的知識,主要是了解下 inode 。
-
硬盤設備是由大量的扇區組成的。以 MBR 分區為例。每個扇區的容量為 512 字節。其中第一個扇區最重要。它里面保存著主引導記錄與分區表信息。就第一個扇區來講,主引導記錄需要占用 446 字節,分區表為 64 字節,結束符占用 2 字節。其中分區表每記錄一個分區信息就需要 16 字節,這樣一來,最多就只有4個分區信息可以寫到第一扇區中,這4個分區就是4個主分區。
-
第一個扇區最多只能創建出4個分區 ?
為了解決分區個數不夠的問題,可以將第一個扇區的分區表中16個字節(原本要寫入主分區信息)的空間(稱之為擴展分區)拿出來指向另一個分區。 -
也就是說,擴展分區并不是一個真正的分區,而像是有一個占用 16
字節的分區表空間的指針,一個指向另外一個分區的指針。這樣一來,用戶一般會選擇使用3個主分區+1個擴展分區的方法,然后在擴展分區中創建無數個邏輯分區,從而來滿足多分區(大于4個)的需求。 -
Linux 系統中有一個名為 superblock 的 “硬盤地圖”。 Linux 并不是把文件內容直接寫入到 superblock 中,而是在里面記錄著整個文件系統的信息。Linux 把每個文件的權限與屬性記錄在 inode("索引節點:index node ") 中,而且每個文件占用一個獨立的 inode 表格,該表格的默認大小為 128 字節。里面記錄著如下信息 :文件的訪問權限(read、write、execute)、該文件的所有者與所屬組(owner、group)、該文件的大小(size)、該文件的創建或內容修改時間(ctime)、該文件的最后一次訪問時間(atime)、該文件的修改時間(mtime)、文件的特殊權限(SUID、SGID、SBIT)、該文件的真實數據地址(point)
-
在 Linux 系統中 ,inode 號才是文件的唯一標識而非文件名。文件名只是為了方便人們的記憶和適用。
-
如上述命令 “ls -li” 結果中的第一列就是文件的 inode 號。系統是通過 inode 號尋找正確的文件數據塊。
-
文件的實際內容則保存在 block 中(大小可以是 1KB、2KB 或 4KB),一個 inode 的默認大小為 128KB (在 Ext3 文件系統中),記錄一個 block 則消耗 4B 。當文件的 inode 被寫滿后,Linux 系統會自動分配出一個 Block 塊,專門用于像 innode 那樣記錄其他 block 塊的信息,這樣能把各個 block 塊的內容串到一起,就能夠讓用戶讀到完整的文件內容了。
-
對于存儲文件內容的的 Block 塊,有以下兩種常見情況,以 4KB 的 block 大小為例說明情況 : 文件很小(1KB) , 但依然會占用一個 block ,因此會潛在占用 3kb。 文件很大(5kb) , 那么會占用兩個 block。
總結:
- superBlock : 存儲整個文件系統的信息。
- inode : 存儲文件的權限與屬性。
- data block : 真正存儲文件內容。
軟硬連接:
在 Windows 系統中,快捷方式是指向原始文件的一個鏈接文件。可以讓用戶從不同的位置來訪問原始的文件;原文件一旦被刪除或剪切到其他地方后,會導致鏈接文件失效。但是在 Linux 系統中,"快捷方式"就不太一樣 ,在 Linux 系統存在硬鏈接和軟鏈接兩種文件。
- 硬鏈接(hard link) : 可以將它理解為一個 “指向原始文件 inode(儲存原始文件信息) 的指針”,系統不為它分配獨立的 inode 和 文件。所以,硬鏈接文件與原始文件其實是同一個文件,只是名字不同。我們每添加一個硬鏈接,該文件的 innode 連接數就會增加 1 ; 而且只有當該文件的 inode 連接數為 0 時,才算徹底將它刪除。因此即便刪除原始文件,依然可以通過硬鏈接文件來訪問。需要注意的是,我們不能跨分區對文件進行鏈接。可以使用指令:
ln fileName newFileName
為原文件fileName 創建硬鏈接newFileName - 軟鏈接(symbolic link) : 等同于 Windows 系統下的快捷方式。僅僅包括所含鏈接文件的路徑名字。因此能鏈接目錄,也能跨文件系統鏈接。但是,當刪除原始文件后,鏈接文件也將失效。
- 總結:硬鏈接是指向原始文件 inode的指針,而軟連接則是僅僅包括所含鏈接文件的路徑名字
ln - 新建鏈接:ln 用于創建軟或硬鏈接。
- 軟鏈接示例 :
ln -s helloWorld sHelloWorld
- 硬鏈接示例 :
ln helloWorld hardHelloWorld
問題:
- 硬鏈接占據空間嗎 ? 比如我有一個 1G 的文件,現在我給這個文件建了一個硬鏈接。那么會占據 2G 空間嗎?
不會,之前我們說了硬鏈接是一個指針或者說是文件的引用,只占一點點空間,軟連接不占用磁盤空間。
軟硬連接參考博文、交叉編譯參考博文、軟硬連接精彩博文、交叉編譯工具鏈下載的另一種方法