一步一步建立基於ARM+Linux的cross toolchain

聲明:本文在參考網上資料的基礎上,以樑元恩的《如何爲嵌入式開發建立交叉編譯環境》爲藍本修改而成。對於自己在建立過程中遇到的問題進行了詳細的說明,本文隨自己學習的深入會進行相應的更新。

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的理解

如果單純說i386i686,就是指平時所說的CPU類型。從Linux內核設計上講,i386是架構,i486/586/686這些CPU的架構都是i386,所以很多linux方面的設計都是基於i386。簡單地說,i386ppc,alpha,arm等放在一起時就是指架構,跟i586i686放在一起指處理器型號,一個是橫向的,一個是縱向的。

3 建立過程

3.1 選定軟件版本

要想選用適當的版本,以保證建立的工具鏈可用,就必須找到適合主機和目標板的組合。這些可以自己測試,也可以從網上尋找已經測試過的版本組合,即binutilsgccglibc的版本組合。我用的宿主機爲redhat-9.0,目標機arm,選擇的版本如下:

--------------------------------------------------------------------------------

binutils-2.11.2.tar.gz  包含有ldaras等一些產生或者處理二進制文件的工具。

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  存放binutilsgccglibc解壓之後的源文件

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

--------------------------------------------------------------------------------

變量ARCHCROSS_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、不必再每次重新設定內核配置之後重建工具鏈,除非你變更了處理器或系統的類型。工具鏈只需要一組可供目標板使用的有效頭文件即可,這些頭文件在前面的程序中早就已經提供了。

2asm-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  完整編譯器的設置

    現在可以爲目標板安裝支持CC++的完整編譯器了。這個步驟相對於前面來建立過程要簡單一些。

************************************************************

$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 總結

通過上面的操作,已經能夠建立全功能的跨平臺開發工具鏈,在以後的嵌入式開發中將會經常用到。

 

 

 

說明:-----之間爲說明文字

      ***之間爲源程序

 

 

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