1 引言
由於一般嵌入式開發系統存儲容量有限,在裁減和定製Linux,運用於嵌入式系統前,通常需要在PC機上建立一個用於目標機的交叉編譯環境,也就是將各種二進制工具程序集成爲工具鏈,其中包括如GNU的鏈接器(ld)、GNU的彙編器(as)、ar(產生修改和解開一個存檔文件)、C編譯器(gcc)以及C鏈接庫(glibc)。本文以在Linux系統上針對目標機arm爲例,介紹了跨平臺開發工具鏈的建立過程。
2 基本概念
2.1 什麼是交叉編譯?
簡單地說,交叉編譯就是在一個平臺上生成在另一個平臺上執行的代碼。這裏的平臺包括體系結構(Architecture)和操作系統(OS)。同一個體系結構可以運行不同的操作系統,同樣,同一個操作系統也可以在不同的體系結構上運行。舉例來說,x86 Linux平臺是Intel x86 體系結構和Linux for x86操作系統的統稱。
2.2 爲什麼要用交叉編譯?
原因有兩個。一是目標平臺所需要的bootloader以及OS核心還沒有建立時,需要作交叉編譯。二是目標機設備不具備一定的處理器能力和存儲空間,即單獨在目標板上無法完成程序開發,所以只好求助宿主機。這樣可以在宿主機上對即將在目標機上運行的應用程序進行編譯,生成可以在目標機上運行的代碼格式,然後移植到目標板上,也就是目前嵌入式程序開發的Host/Target模式。
2.3 對於i386的理解
如果單純說i386、i686,就是指平時所說的CPU類型。從Linux內核設計上講,i386是架構,i486/586/686這些CPU的架構都是i386,所以很多linux方面的設計都是基於i386。簡單地說,i386跟ppc,alpha,arm等放在一起時就是指架構,跟i586,i686放在一起指處理器型號,一個是橫向的,一個是縱向的。
3 建立過程
3.1 選定軟件版本
要想選用適當的版本,以保證建立的工具鏈可用,就必須找到適合主機和目標板的組合。這些可以自己測試,也可以從網上尋找已經測試過的版本組合,即binutils、gcc、glibc的版本組合。我用的宿主機爲redhat-9.0,目標機arm,選擇的版本如下:
--------------------------------------------------------------------------------
binutils-2.11.2.tar.gz 包含有ld、ar、as等一些產生或者處理二進制文件的工具。
gcc-core-2.95.3.tar.gz 包含GCC的主體部分。
gcc-g++2.95.3.tar.gz 可以使GCC編譯C++程序。
glibc-2.2.4.tar.gz libc是很多用戶層應用都要用到的庫,即C鏈接庫。
glibc-linuxthreads-2.2.4.tar.gz libc用於支持Posix線程單獨發佈的壓縮包。
linux-2.4.21.tar.gz+rmk1 Linux的內核及其支持ARM的補丁包。
--------------------------------------------------------------------------------
你可以嘗試選定更新的版本,編譯無法通過時,依次使用較舊的版本。即時發現新版本組合能夠編譯成功,仍然需要測試建立的工具鏈是否可以使用。
你可以從FTP網站ftp://ftp.gnu.org/gnu/或者任何其他的鏡像網站下載GNU工具鏈的各個組件:binutils包位於binutils目錄,gcc包位於gcc目錄,而glibc包與glibc-linuxthreads包放在glibc目錄。下面給出上面選用的各個版本的下載路徑。
--------------------------------------------------------------------------------
binutils-2.11.2.tar.gz
ftp://ftp.gnu.org/gnu/binutils/binutils-2.11.2.tar.gz
gcc-core-2.95.3.tar.gz
ftp://ftp.gnu.org/gnu/gcc/gcc-2.95.3/gcc-core-2.95.3.tar.gz
gcc-g++2.95.3.tar.gz
ftp://ftp.gnu.org/gnu/gcc/gcc-2.95.3/gcc-g++-2.95.3.tar.gz
glibc-2.2.4.tar.gz
ftp://ftp.gnu.org/gnu/glibc/glibc-2.2.4.tar.gz
glibc-linuxthreads-2.2.4.tar.gz
ftp://ftp.gnu.org/gnu/glibc/glibc-linuxthreads-2.2.4.tar.gz
linux-2.4.21.tar.gz+rmk1
ftp://ftp.kernle.org/pub/linux/kernel/v2.4/linux-2.4.21.tar.gz
ftp://ftp.arm.linux.org.uk/pub/linux/arm/kernel/v2.4/patch-2.4.21-rmk1.gz
--------------------------------------------------------------------------------
3.2 建立工作目錄
我的用戶名爲lqm,所以所有的工作都在/home/lqm下面建立完成。
************************************************************
$cd /home/lqm 進入工作目錄
$pwd 查看當前目錄
/home/lqm
$mkdir embedded-system 創建工具鏈文件夾
$ls 查看/home/lqm建立的所有文件
embedded-system
************************************************************
現在已經建立了頂層文件夾embedded-system,下面在此文件夾下建立如下幾個目錄:
--------------------------------------------------------------------------------
setup-dir 存放下載的壓縮包
src-dir 存放binutils、gcc、glibc解壓之後的源文件
kernel 存放內核文件,對內核的配置和編譯工作也在此完成
build-dir 編譯src-dir下面的源文件。這是GNU推薦的源文件目錄與編譯目錄分離的做法。
tool-chain 交叉編譯工具鏈的安裝位置
program 存放編寫程序
doc 說明文檔和腳本文件
--------------------------------------------------------------------------------
下面建立目錄,並且下載源文件。
************************************************************
$pwd
/home/lqm/
$cd embedded-system
$mkdir setup-dir src-dir kernel build-dir tool-chain program doc
$ls
build-dir doc kernel program setup-dir src-dir tool-chain
$cd setup-dir
$wget ftp://ftp.gnu.org/gnu/binutils/binutils-2.11.2.tar.gz 下載源文件
$wget ftp://ftp.gnu.org/gnu/gcc/gcc-2.95.3/gcc-core-2.95.3.tar.gz
$wget ftp://ftp.gnu.org/gnu/gcc/gcc-2.95.3/gcc-g++-2.95.3.tar.gz
$wget ftp://ftp.gnu.org/gnu/glibc/glibc-2.2.4.tar.gz
$wget ftp://ftp.gnu.org/gnu/glibc/glibc-linuxthreads-2.2.4.tar.gz
$wget ftp://ftp.kernel.org/pub/linux/kernel/v2.4/linux-2.4.21.tar.gz
$wget ftp://ftp.arm.linux.org.uk/pub/linux/arm/kernel/v2.4/ patch-2.4.21-rmk1.gz
$ls
binutils-2.11.2.tar.gz gcc-g++-2.95.3.tar.gz glibc-linuxthreads-2.2.4.tar.gz
patch-2.4.21-rmk1.gz gcc-core-2.95.3.tar.gz glibc-2.2.4.tar.gz linux-2.4.21.tar.gz
$cd ../build-dir
$mkdir build-binutils build-gcc build-glibc 建立編譯目錄
************************************************************
3.3 輸出環境變量
在建立與使用某些工具程序時,可能會用到這些目錄的路徑,如果設計一個簡短的命令腳本,設定適當的環境變量,則可以簡化操作過程。下面就建立命令腳本hjbl:
************************************************************
$pwd
/home/lqm/embedded-system/build-dir
$cd ../doc
$mkdir scripts
$cd scripts
$emacs hjbl 用文本編輯器emacs編譯環境變量腳本
--------------------------------------------------------------------------------
在隨後打開的emacs編輯窗口中輸入下面內容(如果在命令行界面下,則必須要用到vi文本編輯器,emacs則不可以):
export PRJROOT=/home/lqm/embedded-system
export TARGET=arm-linux
export PREFIX=$PRJROOT/tool-chain
export TARGET_PREFIX=$PREFIX/$TARGET
export PATH=$PREFIX/bin:$PATH
保存後關閉emacs窗口,如果要在目前的窗口中執行此腳本,即讓環境變量生效,還需要執行下面的語句:
--------------------------------------------------------------------------------
$. hjbl(注意:在點和hjbl之間有一個空格)
$cd $PRJROOT 驗證環境變量是否生效
$ls
build-dir doc kernel program setup-dir src-dir tool-chain
************************************************************
該環境變量的作用時間僅僅在Terminal當前窗口,如果將窗口關閉,開啓一個新的窗口,則環境變量實效,需要重新執行下面的命令:
$. /home/lqm/embedded-system/doc/scripts/hjbl
說明:
TARGET變量用來定義目標板的類型,以後會根據此目標板的類型來建立工具鏈。參看表1。目標板的定義與主機的類型是沒有關係的,但是如果更改TARGET的值,GNU工具鏈必須重新建立一次。
PREFIX變量提供了指針,指向目標板工具程序將被安裝的目錄。
TARGET_PREFIX變量指向與目標板相關的頭文件和鏈接庫將被安裝的目錄。
PATH變量指向二進制文件(可執行文件)將被安裝的目錄。
表1 TARGET變量值
實際的目標板 |
TARGET變量值 |
PowerPC |
powerpc-linux |
ARM |
arm-linux |
MIPS(big endian) |
mips-linux |
MIPS(little endian) |
mipsel-linux |
SuperH 4 |
sh4-linux |
3.4 內核頭文件的配置
內核頭文件的配置是建立工具鏈的第一步。它與後面將要執行的其他步驟有着類似性,大多需要執行下面幾步操作:
1、 解壓縮包
2、 爲跨平臺開發設定包的配置
3、 建立包
4、 安裝包
************************************************************
$pwd
/home/lqm/embedded-system/
$cd kernel
$tar xvzf ../setup-dir/ linux-2.4.21.tar.gz 解壓縮
$gunzip ../setup-dir/ patch-2.4.21-rmk1.gz
$cd linux-2.4.21
$patch –p1 < ../../setup-dir/patch-2.4.21-rmk1 給Linux內核打補丁
$make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig 配置
$make dep
--------------------------------------------------------------------------------
變量ARCH和CROSS_COMPILE的值與目標板的架構類型有關。如果使用PPC目標板,則ARCH=ppc CROSS_COMPILE=ppc-linux-。如果使用i386目標板,則ARCH=i386 CROSS_COMPILE=i386-linux-。
make menuconfig是以文本菜單方式配置。
make xconfig是以圖形界面方式配置。
make config是純文本方式界面配置。
一般選擇make menuconfig,注意在選項System Types中選擇正確的硬件類型。配置完退出並保存,檢查一下的內核目錄中的 kernel/linux-2.4.21/include/linux/version.h 和autoconf.h 文件是不是生成了,這是編譯glibc是要用到。version.h 和 autoconf.h 文件的存在,說明你生成了正確的頭文件。
然後,建立工具鏈需要的include目錄,並將內核頭文件複製過去。
--------------------------------------------------------------------------------
$cd include
$ln -s asm-arm asm #可以查看一下,經過編譯可以自動生成。如果已經生成連接,則不必寫
$cd asm
$ln -s arch-epxa arch #同上說明
$ln -s proc-armv proc #同上說明
#這些是針對makefile文件作出的修改
$mkdir –p $TARGET_PREFIX/include
$cp –r $PRJROOT/kernel/linux-2.4.21/include/linux $TARGET_PREFIX/include
$cp –r $PRJROOT/kernrl/linux-2.4.21/include/asm-arm $TARGET_PREFIX/include/asm
************************************************************
注意:
1、不必再每次重新設定內核配置之後重建工具鏈,除非你變更了處理器或系統的類型。工具鏈只需要一組可供目標板使用的有效頭文件即可,這些頭文件在前面的程序中早就已經提供了。
2、asm-linux文件夾放到目標文件夾$TARGET_PREFIX/include/時要更改名稱爲asm,因爲配置文件的include包含都是<asm/*.h>方式。這也是交叉編譯的不同之處。否則就會出現類似下面的錯誤提示:
--------------------------------------------------------------------------------
.........
done
_udivsi3
_divsi3
_umodsi3
_modsi3
_dwmd_lnx
libgcc1.s:438:asm/unistd.h:No such file or directory
make [1] *** [libgcc1-asm.a] error 1
--------------------------------------------------------------------------------
3.5 binutils(二進制工具程序)的設置
binutils包中的工具常用來操作二進制目標文件。該包中最重要的兩個工具就是GNU彙編器as和鏈接器ld。
************************************************************
$cd $PRJROOT/src-dir
$tar xvzf ../setup-dir/binutils-2.11.2.tar.gz
$cd $PRJROOT/build-dir/build-binutils
$../../src-dir/binutils-2.11.2/configure --target=$TARGET --prefix=$PREFIX
$make
$make install
$ls $PREFIX/bin 驗證安裝的結果是否正確
arm-linux-addr2line arm-linux-ld arm-linux-readelf
arm-linux-ar arm-linux-nm arm-linux-size
arm-linux-as arm-linux-objcopy arm-linux-strings
arm-linux-c++filt arm-linux-objdump arm-linux-strip
arm-linux-gasp arm-linux-ranlib
************************************************************
注意:每個工具的文件名的前綴都是前面爲TARGET變量設定的值。如果目標板是i386-linux,那麼這些工具的文件名前綴就會是i386-linux-。這樣就可以根據目標板類型找到正確的工具程序。
3.6 初始編譯器的建立
開始只能建立支持C語言的引導編譯器,因爲缺少C鏈接庫(glibc)的支持。等到glibc編譯好之後,可以重新編譯gcc並提供完整的C++支持。
************************************************************
$cd $PRJROOT/setup-dir
$mv gcc-core-2.95.3.tar.gz gcc-2.95.3.tar.gz #重命名
$cd $PRJROOT/src-dir
$tar xvzf ../setup-dir/gcc-2.95.3.tar.gz
$cd $PRJROOT/build-dir/build-gcc
$../../src-dir/gcc-2.95.3/configure --target=$TARGET --prefix=$PREFIX --without-headers
--enable-languages=c
--------------------------------------------------------------------------------
因爲是交叉編譯器,還不需要目標板的系統頭文件,所以需要使用 --without-headers這個選項。--enable-language=c用來告訴配置腳本,需要產生的編譯器支持何種語言,現在只能支持C語言。--disable-threads是因爲threads需要glibc的支持。
準備好了Makefile文件,進行編譯之前,需要修改src-dir/gcc-2.95.3/gcc/config/arm/t-linux文件,在TARGET_LIBGCC2_CFLAGS中添加兩個定義:-Dinhibit_libc -D__gthr_posix_h,否則會報錯。
--------------------------------------------------------------------------------
$make
$make install
************************************************************
3.7 建立C庫(glibc)
這一步是最爲繁瑣的過程。目標板必須靠它來執行或者是開發大部分的應用程序。glibc套件常被稱爲C鏈接庫,但是glibc實際產生很多鏈接庫,其中之一是C鏈接庫libc。因爲嵌入式系統的限制,標準GNU C鏈接庫顯得太大,不適合應用在目標板上。所以需要尋找C鏈接庫的替代品,比如uClibc。在這裏,現以標準GNU C爲例建立工具鏈。
************************************************************
$cd $PRJROOT/src-dir
$tar xvzf ../setup-dir/glibc-2.2.4.tar.gz
$tar xvzf ../setup-dir/glibc-linuxthreads-2.2.4.tar.gz --directory=glibc-2.2.4
$cd $PRJROOT/build-dir/build-glibc
$CC=arm-linux-gcc ../../src-dir/glibc-2.2.4/configure --host=$TARGET --prefix=”/usr”
--enable-add-ons --with-headers=$TARGET_PREFIX/include
$make
$make install_root=$TARGET_PREFIX prefix=”” install
--------------------------------------------------------------------------------
在這裏設定了install_root變量,指向鏈接庫組件目前所要安裝的目錄。這樣可以讓鏈接庫及其頭文件安裝到通過TARGET_PREFIX指定的與目標板有關的目錄,而不是建立系統本身的/usr目錄。因爲之前使用--prefix選項來設定prefix變量的值,而且prefix的值會被附加到install_root的值之後,成爲鏈接庫組件的安裝目錄,所以需要重新設定prefix的值。這樣所有的glibc組件將會安裝到$TARGET_PREFIX指定的目錄下。
--------------------------------------------------------------------------------
$cd $TARGET_PREFIX/lib
$cp ./libc.so ./libc.so.orig
--------------------------------------------------------------------------------
編輯文件libc.so,更改如下:
/* GNU ld script
Use the shared library,but some functions are only in
the static library,so try that secondarily.*/
GROUP(libc.so.6 libc_nonshared.a)
--------------------------------------------------------------------------------
************************************************************
3.8 完整編譯器的設置
現在可以爲目標板安裝支持C和C++的完整編譯器了。這個步驟相對於前面來建立過程要簡單一些。
************************************************************
$cd $PRJROOT/build-dir/build-gcc
$../../src-dir/gcc-2.95.3/configure --target=$TARGET --prefix=$PREFIX
--enable-languages=c,c++
$make all
$make install
************************************************************
3.9 完成工具鏈的設置
************************************************************
$cd $TARGET_PREFIX/bin
$file as ar gcc ld nm ranlib strip 查看文件是否爲二進制文件
$arm-linux-gcc -print-search-dirs 查看缺省的搜尋路徑
$mv as ar gcc ld nm ranlib strip $PREFIX/lib/gcc-lib/arm-linux/2.95.3 轉移文件
$for file in as ar gcc ld nm ranlib strip
>do
>ln -s $PREFIX/lib/gcc-lib/arm-linux/2.95.3/$file
>done
************************************************************
3.10 使用工具鏈
下面編寫一個簡單的C程序,使用建立的工具鏈。、
************************************************************
$cd $PRJROOT/program
$emacs hello.c
--------------------------------------------------------------------------------
在文本編輯器emacs中編寫:
#include <stdio.h>
int main()
{
int i;
for(i=1;i<9;i++)
printf(“Hello World %d times!/n”,i);
}
保存退出
--------------------------------------------------------------------------------
$gcc -g hello.c -o hello
$gdb
(gdb)file hello
(gdb)l
#include <stdio.h>
int main()
{
int i;
for(i=1;i<9;i++)
printf(“Hello World %d times!/n”,i);
}
(gdb)r
(gdb)q
$arm-linux-gcc -g hello.c -o hello-linux
$file hello-linux
hello-linux:ELF 32-bit LSB executable,ARM,version 1(ARM),for GNU/Linux 2.0.0,dynamically linked(uses shared libs),not stripped
************************************************************
上面的輸出說明你編譯了一個能在 arm 體系結構下運行的 hello-linux,證明你的編譯工具做成功了。
4 總結
通過上面的操作,已經能夠建立全功能的跨平臺開發工具鏈,在以後的嵌入式開發中將會經常用到。
說明:-----之間爲說明文字
***之間爲源程序