第5章嵌入式系统开发环境的建立 本章主要学习嵌入式系统开发环境的建立方法,通过本章的学习,可以掌握和了解以下知识点。 · 在宿主机端建立开发环境。 · 配置minicom终端。 · 嵌入式Linux系统内核的编译。 · 嵌入式开发板内核及系统文件的烧写方法。 视频讲解 5.1建立宿主机开发环境 5.1.1交叉编译 绝大多数的软件开发都是以本地(Native)编译方式进行的,即在普通计算机上开发编译、在本机上运行的方式。但是,这种方式通常不适合于嵌入式系统的软件开发,因为对于嵌入式系统来说,本机(即板上系统)没有足够的资源来运行开发工具和调试工具。因此,嵌入式系统软件的开发通常采用交叉编译的方式。交叉编译,就是指在一个平台上生成可以在另一个平台上执行的代码。 编译最主要的工作是将程序转化成运行该程序的CPU所能识别的机器代码。由于不同的硬件体系结构之间存在差异,它们的指令系统也不尽相同,因此,不同的CPU需要用与其相应的编译器来编译代码。交叉编译就是在特殊的编译环境下,把程序代码编译成不同的CPU所对应的机器代码。 通常把嵌入式系统的开发放置到普通计算机端,在一台普通计算机(称为宿主机)上建立交叉编译环境,而把嵌入式开发板(称为目标板)作为开发的测试平台,对编写的代码进行检验和调试。其连接方式如图5.1所示。 图5.1建立交叉编译的连接方式 开发时使用宿主机上的交叉编译、汇编及链接工具形成可执行的二进制代码,这种可执行代码并不能在宿主机上执行,而只能在目标板上执行。然后再把可执行文件下载到目标机上运行。 5.1.2建立交叉编译开发环境 对于嵌入式Linux的开发和应用,在进行开发前首要的工作就是要搭建一个完整的交叉编译开发环境。即在宿主机上安装与目标板相应的编译器及库函数,以便能在宿主机上应用开发工具编译在目标板上运行的Linux引导程序、内核、文件系统和应用程序。 1. 下载和安装armlinuxgcc编译工具链 1) 下载armlinuxgcc 下载或复制armlinuxgcc到当前目录下。例如,本书使用的armlinuxgcc版本为armlinuxgcc4.5.1.tar.gz。 2) 解压 armlinuxgcc 应用下列解压命令。 #tar -zxvf arm-linux-gcc-4.5.1.tar.gz 解压后,将armlinux文件目录及其所有文件复制到/usr/local/下。 #cp -rvarm-linux /usr/local/ 现在交叉编译工具都在/usr/local/armlinux/bin目录下了。 这时,可以用ls命令查看安装在/usr/local/armlinux/bin目录下的嵌入式系统开发交叉编译器,显示情况如下。 \[root@localhostroot\]# cd /usr/local/arm-linux/bin \[root@localhost bin\]# ls arm-linux-addr2linearm-linux-cpparm-linux-gcovarm-linux-ranlib arm-linux-ararm-linux-g++arm-linux-ldarm-linux-readelf arm-linux-asarm-linux-gccarm-linux-nmarm-linux-size arm-linux-c++arm-linux-gcc-4.4.3arm-linux-objcopyarm-linux-strings arm-linux-c++filtarm-linux-gccbugarm-linux-objdumparm-linux-strip 2. 在系统配置文件profile中设置环境变量 为了可以在所有目录下直接使用armlinuxgcc这个工具,需要修改profile文件。 1) 方法一 在profile文件中加入搜索路径pathmunge /usr/local/armlinux/bin。 命令如下所示。 \[root@localhost root\]#gedit/etc/profile 这时在终端窗口显示如下(其中,pathmunge /usr/local/armlinux/bin为新加入)。 … # Path manipulation if \[ 'id -u' = 0 \]; then pathmunge /sbin pathmunge /usr/sbin pathmunge /usr/local/sbin pathmunge /usr/local/arm-linux/bin fi unset pathmunge 关于if语句的使用参见教材第4章的“Linux shell编程”。 2) 方法二 修改环境变量,把交叉编译器的路径加入PATH中。 在profile文件的最后加入搜索路径。 exportPATH=$PATH: /usr/local/arm-linux/bin exportPATH 3. 立即使新的环境变量生效,不用重启计算机 1) 运行source命令,使设置生效 # source/etc/profile 2) 检查是否将路径加入PATH中 # echo$PATH 显示的内容中有/usr/local/armlinux/bin,说明已经将交叉编译器的路径加入PATH。至此,交叉编译环境安装完成。 3) 测试是否安装成功 # arm-linux-gcc -v 如果该命令能显示交叉编译工具的版本情况,如图5.2所示,则表明安装成功。 图5.2测试交叉编译工具是否安装成功 4. 编译Hello World程序,测试交叉工具链 编写下面的Hello World程序,保存为hello.c: #include <stdio.h> int main() { printf("Hello World!\n"); return 0; } 执行下面的命令。 # arm-linux-gcc -o hello hello.c 若源程序有错误,则会有提示; 若没有任何提示,表示通过了编译,就可以下载到ARM开发板上运行了。 接着可以输入file hello命令,查看生成的hello文件的类型。要注意的是,生成的可执行文件只能在ARM体系下运行,不能在PC上运行。 如果是建立S3C2410系统的主机开发环境,在运行完毕开发商提供的安装脚本程序/install.sh后,将在根目录生成一个/linuette目录,该目录为S3C2410系统的工作目录,与安装PXA270系统所生成的/pxa270_linux目录类似。 另外,生成一个 /opt/host/armv4l 目录(目录名armv4l最后的是字母l,不是数字1),主编译器为armv4lunknownlinuxgcc。 要建立主编译器的搜索路径,则修改/root/.bash_profile文件,将文件中的PATH变量值设为PATH=$PATH: $HOME/bin: /opt/host/armv4l/bin。存盘后,执行source /root/.bash_profile,这时设置的搜索路径生效。 视频讲解 5.2配置超级终端minicom Linux系统的minicom很像Windows系统的超级终端,它是一个串口通信工具。可以利用minicom作为在宿主机端与目标板进行通信的终端。下面介绍minicom的配置方法。 (1) 在宿主机Linux终端中输入minicoms或minicom,然后再按Ctrl+A+O快捷键。弹出超级终端minicom的设置菜单,如图5.3所示。 (2) 选择Serial port setup项,首先选择串口,如果使用第1个串口,则设置串口号为ttyS0; 如果使用第2个串口,则将串口号设为ttyS1。 (3) 将串口配置为: 波特率115200,8位数据位,1位停止位,没有数据流控制。如图5.4所示,按A键进行端口号配置,按E键进行串口配置。 (4) 选择Save setup as dfl项,将设置保存为默认值(这里,dfl代表default),如图5.5所示。 (5) 选择Exit退回到minicom界面。 正确连接串口线,PC端使用在MINICOM中被配置的串口(ttyS0或ttyS1),目标板使用第1个串口(电路板上标示为SERIAL PORT 0)或第2个串口(电路板上标示为SERIAL PORT 1)。 图5.3超级终端minicom配置窗口 图5.4设置串口为ttyS0,波特率为115200 图5.5保存为df1 视频讲解 5.3编译嵌入式Linux系统内核 完成了主机的开发环境搭建等前期准备工作之后,接下来,就可以编译嵌入式Linux的内核了。本节主要介绍嵌入式Linux内核的编译过程。 编译嵌入式Linux内核都是通过make的不同命令来实现的,它的执行配置文件是Makefile。Linux内核中不同的目录结构里都有相应的Makefile,而不同的Makefile又通过彼此之间的依赖关系构成统一的整体,共同完成建立依存关系、建立内核等功能。 编译内核需要3个步骤,分别是内核配置、建立依存关系、建立内核。下面分别讲述这3个步骤。 5.3.1内核裁剪配置 1. 确定处理器类型 编译内核的第一步是根据目标板微处理器类型来确定微处理器架构,不同的微处理器架构在编译内核时会有不同的处理器选项。例如,ARM就有其专用的选项,如Multimedia capabilities port drivers等。因此,在此之前,必须在ARM系统文件的根目录中的Makefile下为ARCH设定目标板微处理器的类型值。例如: ARCH∶= arm 或输入命令进行设置。 \[root@localhostlinux\]# exportARCH=arm 2. 确定内核配置方法 内核支持 4种不同的配置方法,这4种方法只是与用户交互的界面不同,其实现的功能是一样的。每种方法都会读入和修改内核源码根目录下的一个默认的.config配置文件(该文件是一个隐藏文件)。这4种方式简介如下。 (1) make config: 基于文本的最为传统的配置界面,不推荐使用。 (2) make menuconfig: 基于文本菜单的配置界面,字符终端下推荐使用。 (3) make xconfig: 基于图形窗口模式的配置界面,Xwindow下推荐使用。 (4) make oldconfig: 自动读入.config配置文件,并且只要求用户设定前次没有设定过的选项。 在这4种模式中,make menuconfig的使用最为广泛。 【例51】以make menuconfig为例进行S5PV210系统的内裁剪核配置。 (1) 首先下载或复制适合开发板微处理器型号的Linux内核源码,进入内核源码的系统根目录。 (2) 运行make menuconfig命令,弹出内核裁剪配置窗口,如图5.6所示。 \[root@localhostlinux\]# make menuconfig 图5.6make menuconfig内核裁剪配置界面 从图5.6可以看出,Linux 内核允许用户对其各类功能逐项配置,共有19类配置选项,如表5.1所示。 表5.1内核裁剪配置选项表 序号选 项 名 称说明 1Code maturity level options代码成熟度选项。当内核中包含有不成熟的代码或驱动程序进行调试时,一般选择该项 2General setup通用选项。例如,进程间通信方式等 3Loadable module support系统模块选项。提供系统模块支持 4Block layer系统调度方式选项 5System Type系统类型选项,提供微处理器型号及特性配置 6Bus Support提供总线接口支持选项 7Kernel Features内核特性选项 8Boot options内核启动选项 9Floating point emulation提供和浮点运算相关选项 10Userspace binary formats用户空间使用的二进制文件格式选项 11Power management options电源管理选项 12Networking网络协议相关选项 13Device drivers提供所有设备驱动程序相关的配置选项 14File System提供文件系统的配置选项 15Profiling support提供和程序性能分析相关的选项 16Kernel hacking与内核调试相关的选项 17Security options有关安全的配置选项 18Cryptographic options加密算法配置选项 19Library routines提供CRC校验的库选项 在嵌入式Linux的内核源码安装目录中通常有以下几个配置文件: .config、autoconf.h、config.h。其中,.config文件是make menuconfig默认的配置文件,位于源码安装目录的根目录中,autoconf.h和config.h是以宏的形式表示的内核的配置。当用户使用make menuconfig 做了一定的更改之后,系统会自动在autoconf.h和config.h中做出相应的更改。后两个文件位于源码安装目录的/include/linux/下。 在menuconfig的配置界面中是纯键盘的操作,用户可使用上下键和Tab键移动光标以进入相关子项,图5.7所示为进入了System Type→子项的界面,该子项是一个重要的选项,主要用来选择处理器的类型。这里,带有→的选项表示当前项还有下一级菜单子项。 图5.7System Type →子项的界面 每个选项前都有一对括号,可以通过按空格键或Y键表示包含该选项; 按N表示不包含该选项。 选项前面的括号有3种形式,即中括号、尖括号或圆括号。 (1) \[ \]表示该选项有两种选择。 · \[*\]表示选择该项编译进内核。 · \[ \]表示不编译该选项。 (2) < >表示该选项有3种选择。 · <*>将该项选进内核。 · <M>将该项编译成模块,但不编译进内核。 · < <表示不编译该选项。 可以用空格键选择相应的选项。 (3) ()表示该项可以输入数值。 一般情况下,使用嵌入式系统设备厂商提供的默认配置文件都能正常运行,所以用户初次使用时可以不用对其进行额外的配置,在以后需要使用其他功能时再另行添加,这样可以大大减少出错的概率,有利于错误定位。在完成配置之后,就可以保存退出,如图5.8所示。 图 5.8保存内核设置 5.3.2内核编译 在嵌入式系统的项目设计过程中,经常需要修改及重新编译内核,下面介绍编译内核文件的方法。 1. 内核配置系统的基本结构 Linux内核的配置系统由3个文件组成,对它们分别简介如下。 (1) Makefile: 分布在Linux内核源代码根目录及各层目录中,定义Linux内核的编译规则。 (2) 内核配置菜单Kconfig: 它是配置界面的源文件,给用户提供配置选择的功能。 (3) 配置文件.config: 编译内核所依据的配置项,决定将哪些驱动编译进内核。该文件通常由menuconfig生成。 2. 内核配置菜单Kconfig Kconfig文件是menuconfig的关键文件。Kconfig用来配置内核,它就是各种配置界面的源文件,内核的配置工具读取各个Kconfig文件,生成配置界面供开发人员配置内核,最后生成.config配置文件。 Kconfig文件的一般格式如下。 menuconfig "菜单入口名称" tristate"菜单选项" 3. 编译内核的步骤 静态加载设备驱动程序需要以下几个步骤。 (1) 创建.config配置文件。 (2) 编写编译驱动程序的Makefile文件。 (3) 修改上层目录中的Kconfig和Makefile文件。 (4) 运行内核配置界面menuconfig,生成编译内核的配置文件.config。 (5) 运行内核源代码根目录下的Makefile或build文件,编译内核。 下面通过一个简单示例,说明把设备驱动程序编译到内核中的方法。 【例52】设有一个设备驱动程序drv_led.c,将其编译到内核文件中。 把一个设备驱动程序编译到内核中的具体步骤如下。 (1) 建立工作目录。 为了防止破坏原生系统以及方便后期管理,在Linux系统内核源程序中,建立一个工作目录menu_test。 /Linux-kernel/drivers/char/menu_test (2) 创建 Kconfig文件。 Kconfig文件是menuconfig的关键文件。Kconfig用来配置内核,它是配置界面的源文件,内核的配置工具读取各个Kconfig文件,生成配置界面供开发人员配置内核,最后生成配置文件.config。 下面是为了测试而创建的Kconfig文件,文件存放路径为/Linuxkernel/drivers/char/menu_test/Kconfig。 Kconfig文件的代码如下。 menuconfig menu_test tristate " menu_test driver" if menu_test config LED tristate "LED driver" config MOTO tristate "MOTO driver" endif # menu_test 其中: menuconfig menu_test定义了要传给Makefile的参数menu_test,在生成的.config文件中会多一项CONFIG_ menu_test; tristate " menu_test driver"语句用来在执行make menuconfig命令时,为配置界面增加一个<> menu_test driver选项。其中的tristate定义该选项为三态的,即可以有< > <*>和<M> 3种状态。若用bool定义,则该选项只有两种状态两个选择,即\[ \]和\[*\]。 if menu_test和endif代码段只有在menu_test被定义时才执行。也就是说,必须是 <M> menu_test driver --> 或 <*> menu_test driver --> 时才会在配置界面中显示代码段中定义的内容。该代码段运行后的结果如图5.9所示。 图5.9定义内核配置选项 (3) 创建Makefile。 Makefile是编译内核的重要文件。下面在/Linuxkernel/drivers/char/menu_test/目录下,创建Makefile文件,其代码如下。 obj-$(CONFIG_LED)+= drv_led.o obj-$(CONFIG_MOTO)+= drv_moto.o Makefile中的每一项都对应Kconfig代码中的一个条目,例如: obj-$(CONFIG_LED)+= drv_led.o 在Kconfig中对应的条目为: config LED tristate "LED driver" (4) 修改上层目录的Kconfig。 工作目录下的Kconfig创建好之后,需要将它添加到原有内核的Kconfig中去。为此需修改上一层目录/Linuxkernel/drivers/char/下的Kconfig文件,将下面语句添加到该文件中。 source "drivers/char/menu_test/Kconfig" 一般是添加到文件的最后面,但需要注意的是,要在endmenu之前。Kconfig文件中#开头的语句为注释语句,可以在Kconfig文件中添加一些自己的注释语句。 (5) 修改上层目录的Makefile。 还需要把新的驱动程序添加到原有的Makefile中,这样在编译系统的时候才能编译新添加的驱动。修改上一层目录/Linuxkernel/drivers/char/下的Makefile文件,将下面内容添加到该文件中。 obj-$(CONFIG_menu_test)+= menu_test/ 其中的CONFIG_menu_test对应的是/Linuxkernel/drivers/char/menu_test/Kconfig中的语句: menuconfig menu_test tristate " menu_test driver" 至此,已经将设备驱动程序drv_led.o添加进menuconfig中了。 (6) 运行menuconfig。 执行命令: make menuconfig 在弹出的配置窗口中,选择Device Drivers →项,再在新进入的选项窗口中选择Character devices项,可以看到新建的< > menu_test driver(NEW)选项,如图5.10所示。 图5.10选择所定义的内核配置选项标题 (7) 做好相应的配置之后,保存配置,在Linux内核根目录下便会生成.config文件。在如图5.11所示的提示框中,选择<Yes>项,则选择的设备驱动程序项目编译到.config内核配置文件中。 图5.11确认把设备驱动程序保存到编译内核的配置文件中 (8) 在内核源代码的根目录,运行Makefile或build文件,则把.config系统内核配置文件中所有项目均编译并打包压缩到zImage文件中。 5.4文件系统的制作 文件系统是嵌入式Linux系统必备的一个组成部分,是系统文件和应用文件存储的地方。通常使用的PC上的文件系统包括很多功能,容量达几百兆字节之多,在嵌入式系统中要使用这样的文件系统是不可能的。因此,嵌入式系统中的文件系统是一个Linux文件系统的简化版。文件系统中仅包含必需的目录和文件,完成需要的功能即可。 下面对文件系统中需要包含的目录和文件进行简要的说明。 1. 文件目录 文件系统要求建立的目录有/bin、/sbin、/etc、/dev、/lib、/mnt、/proc和/usr。 · /bin: 目录下需要包含常用的用户命令,如sh等。 · /sbin: 目录要包含所有系统命令,如reboot等。 · /etc: 目录下是系统配置文件。 · /boot: 目录下是内核映像。 · /dev: 目录包含系统所有的特殊设备文件。 · /lib: 目录包含系统所有的库文件。 · /mnt: 目录只用于挂接,可以是空目录。 · /proc: 目录是/proc文件系统的主目录,包含了系统的启动信息。 · /usr: 目录包含用户选取的命令。 2. 文件目录应该包含的文件和子目录 1) 目录/bin 目录/bin中至少应包含命令文件date、sh、login、mount、umount、cp、ls、ftp和ping。这些命令文件的主要作用如下。 · date: 查取系统时间值。 · sh: 是bash的符号链接。 · login: 登录进程启动后,若有用户输入,此程序就提供password提示符。 · mount: 挂接根文件系统时使用的命令,有些Linux开发商将此文件安排在/sbin下。 · umount: 卸载文件系统时使用的命令。 · cp: 文件复制命令。 · ls: 列出目录下的文件需使用的命令。 · ftp: 根据文件传输协议实现的命令,可以用于FTP登录。 · ping: 基本的网络测试命令,运行在网络层。 2) 目录/sbin 目录/sbin至少应包含命令文件mingetty、reboot、halt、sulogin、update、init、fsck、telinit和mkfs。这些命令的主要作用如下。 · reboot: 系统重新启动的命令。 · halt: 系统关机命令,它与reboot共享运行的脚本。 · init: 它是最早运行的进程,从Start_kernel()函数中启动。此命令可以实现Linux运行级别切换。 3) 目录/etc 目录/etc至少应包含配置文件HOSTNAME、bashrc、fstab、group、inittab、nsswitch、pam.d、passwd、pwdb.conf、rc.d、securetty、shadow、shells以及lilo.conf。这些配置文件的主要作用如下。 · HOSTNAME: 用于保存Linux系统的主机名。 · fstab: 用于保存文件系统列表。 · group: 用于保存Linux系统的用户组。 · inittab: 用于决定运行级别的脚本。 · passwd: 保存了所有用户的加密信息。 · shadow: 密码屏蔽文件。 · shells: 支持的所有Shell版本。 4) 目录/dev 目录/dev至少应包含设备文件console、hda1、hda2、hda3、kmem、mem、null、tty1和ttyS0。这些特殊设备文件的作用如下。 · console: 表示控制台设备。 · hda1: 表示第1个IDE盘的第1个分区。 · hda2: 表示第1个IDE盘的第2个分区。 · hda3: 表示第1个IDE盘的第3个分区。 · kmem: 描述内核内存的使用信息。 · mem: 描述内存的使用信息。 · null: 表示Linux系统中的空设备,可用于删除文件。 · tty1: 第1个虚拟字符终端。 · ttyS0: 第1个串行口终端。 5) 目录/lib 目录/lib至少应包含库文件libc.so.6、ldlinux.so.2、libcom_err.so.2、 libcrypt.so.2、libpam.so.0、libpam_misc.so.2、libuuid.so.2、libnss_files.so.2、libtermcap.so.2和security。这些库文件的作用如下。 · libc.so.6: Linux系统中所有命令的基本库文件。 · ldlinux.so.2: 基本库文件libc.so.6的装载程序库。 · libcom_err.so.2: 对应命令出错处理的程序库。 · libcrypt.so.2: 对应加密处理的程序库。 · libpam.so.0: 对应可拆卸身份验证模块的程序库。 · libpam_misc.so.2: 对应可拆卸身份验证模块解密用的程序库。 · libuuid.so.2: 对应于身份识别信息程序库。 · libnss_files.so.2: 对应名字服务切换的程序库。 · libtermcap.so.2: 用于描述终端和脚本的程序库。 · security: 此目录用来提供保证安全性所需的配置,与libpam.so.0配合使用。 6) 目录/mnt和/proc 目录/mnt和/proc可以为空。 3. 制作文件系统的镜像文件 Linux支持多种文件系统,同样,嵌入式Linux也支持多种文件系统。虽然嵌入式系统由于资源受限,它的文件系统和Linux的文件系统有较大的区别(前者往往是只读文件系统),但是,它们的总体架构是一样的,都是采用目录树的结构。下面介绍在嵌入式Linux中常见的文件系统cramfs及jffs2文件系统的镜像文件的制作。 【例53】制作cramfs文件系统。 cramfs文件系统是一种经压缩的、极为简单的只读文件系统,因此非常适合嵌入式系统。要注意的是,不同的文件系统都有相应的制作工具,但是其主要的原理和制作方法是类似的。 制作cramfs文件系统需要用到的工具是mkcramfs,下面就来介绍使用mkcramfs制作文件系统映像的方法。 假设用户已经在目录/fs/root/下建立了一个文件系统,/fs/root目录下的文件如下所示。 \[root@localhostfs\]# lsroot bin dev etc home lib linuxrc proc Qtopia ramdisk sbin tmp usr var 接下来就可以使用mkcramfs工具了,命令格式如下。 mkcramfs系统文件目录名生成的镜像文件名 设当前目录为/fs,现将系统文件子目录root生成镜像文件camare_rootfs.cramfs: \[root@localhost fs\]# ./mkcramfsroot camare_rootfs.cramfs 则在当前目录/fs下生成了系统文件的镜像文件camare_rootfs.cramfs,mkcramfs在制作文件镜像的时候对该文件进行了压缩。 【例54】制作jffs2文件系统。 jffs2是一种可读/写的文件系统。制作它的工具叫作mkfs.jffs2,可以用下面的命令来生成一个jffs2的文件系统。 #./mkfs.jff2-r\[系统文件目录\]-o\[系统文件名.jffs2\]-e\[ 烧写到flash的地址\]-p=\[文件长度\] 设在/fs/root 目录中有文件系统的源文件,烧写命令如下。 \[root@localhostfs\]# lsroot binetcmntrootsbinusrdevlibproctmpvar \[root@localhostfs\]# ./mkfs.jffs2 -r root -o xscale_fs.jffs2 -e 0x40000 -p=0x01000000 这样,就会在/fs目录下生成一个文件名为xscale_fs.jffs2的文件系统镜像文件。 5.5嵌入式系统开发板的烧写方法 嵌入式linux系统可以安装在开发板上,在嵌入式开发板上安装Linux的过程称为“烧写系统”或“刷机”。下面介绍在嵌入式系统开发板上烧写linux系统的方法。 5.5.1引导加载程序Bootloader 引导加载程序从开发板通电到应用程序开始工作,经历了一个非常复杂的加载过程,下面简单介绍这个加载过程。 1. 基本概念 一个嵌入式Linux系统从软件的角度看通常分为4个层次: 引导加载程序Bootloader、Linux内核、文件系统和用户应用程序。 简单地说,Bootloader就是在操作系统内核运行之前运行的一段程序,它类似于PC机中的 BIOS 程序。通过这段程序,可以完成硬件设备的初始化,并建立内存空间的映射图的功能,从而将系统的软硬件环境带到一个合适的状态,为最终调用系统内核做好准备。 通常,Bootloader严重地依赖于硬件设备。在嵌入式系统中,不同体系结构使用的Bootloader是不同的。除了体系结构,Bootloader还依赖于具体的嵌入式板级设备的配置。也就是说,对于两块不同的嵌入式开发板而言,即使它们基于相同的CPU构建,运行在其中一块电路板上的Bootloader,未必能够在另一块电路开发板上运行。因此,在嵌入式世界里建立一个通用的Bootloader几乎是不可能的。尽管如此,仍然可以对Bootloader归纳出一些通用的概念来指导用户完成特定的Bootloader设计与实现。 1) Bootloader所支持的 CPU 和嵌入式开发板 每种不同的CPU体系结构都有不同的Bootloader。有些Bootloader也支持多种体系结构的CPU,如后面要介绍的UBoot就同时支持ARM体系结构和MIPS体系结构。除了依赖于CPU的体系结构外,Bootloader实际上也依赖于具体的嵌入式板级设备的配置。 2) Bootloader的安装媒介 系统加电或复位后,所有的CPU通常都从某个由CPU制造商预先安排的地址上取指令。基于CPU构建的嵌入式系统通常都有某种类型的固态存储设备(如ROM、EEPROM或FLASH)被映射到这个预先安排的地址上。因此,在系统加电后,CPU将首先执行Bootloader程序。 3) Bootloader的启动过程 Bootloader的启动过程分为单阶段和多阶段两种。通常多阶段的Bootloader能提供更为复杂的功能,以及更好的可移植性。 4) Bootloader的操作模式 大多数Bootloader都包含两种不同的操作模式: 启动加载模式和下载模式,这种区别仅对于开发人员才有意义。从最终用户的角度看,Bootloader的作用就是用来加载操作系统,而并感觉不到所谓的启动加载模式与下载工作模式的区别。 (1) 启动加载模式: 这种模式也称为“自主”模式。也就是Bootloader从目标板上的某个固态存储设备(如Flash)上将操作系统加载到RAM中运行,整个过程没有用户的介入。这种模式是嵌入式产品发布时的通用模式。 (2) 下载模式: 在这种模式下,目标机上的Bootloader将通过串口连接或网络连接等通信手段从宿主机下载文件。例如,下载内核映像文件和文件系统映像文件等。从主机下载的文件通常首先被Bootloader保存到目标板的RAM中,然后再被Bootloader写到目标板上的Flash之类固态存储设备中。Bootloader的这种模式通常在系统更新时使用。工作在这种模式下的Bootloader通常都会向它的终端用户提供一个简单的命令行接口,如vivi、Blob等。 5) Bootloader与主机之间进行文件传输所用的通信设备及协议 最常见的情况是,目标机上的Bootloader通过串口与主机之间进行文件传输,传输协议通常是xmodem、ymodem、zmodem协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助TFTP协议来下载文件是个更好的选择。 2. Bootloader启动流程 Bootloader的启动流程一般分为两个阶段: stage1和stage2,下面分别对这两个阶段进行讲解。 1) Bootloader的stage1 在stage1中Bootloader主要完成以下工作。 (1) 基本的硬件初始化,包括屏蔽所有的中断、设置CPU的速度和时钟频率、RAM初始化、初始化LED、关闭CPU内部指令和数据cache等。 (2) 为加载stage2准备RAM空间,通常为了获得更快的执行速度,把stage2加载到RAM空间中来执行,因此必须为加载Bootloader的stage2准备好一段可用的RAM空间范围。 (3) 复制stage2到RAM中,在这里要确定以下两点。 · stage2的可执行映像在固态存储设备的存放起始地址和终止地址。 · RAM空间的起始地址。 (4) 设置堆栈指针sp,这是为执行stage2的C语言代码做好准备。 2) Bootloader的stage2 stage2的代码通常用C语言来实现,以实现更复杂的功能和取得更好的代码可读性与可移植性。但是与普通C语言应用程序不同的是,在编译和链接Bootloader这样的程序时,不能使用glibc库中的任何支持函数。 在stage2中Bootloader主要完成以下工作。 (1) 初始化本阶段要使用到的硬件设备,包括初始化串口、初始化计时器等。在初始化这些设备之前,可以输出一些打印信息。 (2) 检测系统的内存映射,内存映射是指在整个内存物理地址空间中指出哪些地址范围被分配用来寻址系统的RAM单元。 (3) 加载内核映像和根文件系统映像,这里包括规划内存占用的布局和从Flash上复制数据。 (4) 设置内核的启动参数。 3. Bootloader的烧写 要在嵌入式系统开发板中完成Linux操作系统的烧写,首先要把Bootloader烧写到Flash中。这时,要用到JTAG。 用并口线通过JTAG小板将宿主机(打印输出口)与开发板连接,接线方法如图5.12所示。 图5.12应用JTAG烧写Bootloader的接线图 由于Bootloader严重地依赖于硬件设备,不同微处理器的Bootloader是不相同的。表5.2列出了几种典型的微处理器开发板所使用的Bootloader。 表5.2几种典型的微处理器开发板所使用的Bootloader 微处理器型号使用的Bootloader ARMS3C2410vivi XSCALEPXA270blob ARM Cortex A8UBoot 视频讲解 5.5.2ARM Cortex A8内核开发板的烧写 本节主要介绍基于ARM Cortex A8微处理器开发板S5PV210的Bootloader、内核、文件系统的烧写方法。其系统引导程序Bootloader为UBoot。 1. UBoot简介 UBoot(全称Universal Boot Loader)是德国DENX小组开发的Bootloader项目,它支持数百种嵌入式开发板和各种微处理器。UBoot有以下两种操作模式。 1) 启动加载(Boot loading)模式 启动加载模式是Bootloader的正常工作模式,在嵌入式产品正式发布时,Bootloader必须工作在这种模式下,Bootloader将嵌入式操作系统从Flash中加载到SDRAM中运行,整个过程是自动的。 2) 下载(Downloading)模式 下载模式是Bootloader通过某些通信手段,将内核映像或根文件系统映像等从PC机中下载到目标板的Flash中。用户可以利用Bootloader提供的一些命令接口来完成自己想要的操作。 UBoot的常用命令见表5.3。 表5.3UBoot常用命令 命令说明 bootfile默认的下载文件名 bootcmd自动启动时执行命令 serveripTFTP服务器端的IP地址 ipaddr本地的IP地址 setenv设置环境变量 saveenv保存环境变量到nand Flash中 nand scrub擦除指令,擦除整个nand Flash的数据 loadlinuxramdisk从网络TFTP服务器下载内核及文件系统到内存中 2. 主要烧写步骤 对于基于Cortex A8微处理器的S5PV210开发板,嵌入式Linux系统的Bootloader、内核、文件系统将烧写到Flash中。烧写S5PV210开发板的步骤如下。 (1) 编译和启动系统引导程序UBoot。 (2) 通过SD卡把uboot镜像文件写到Flash中。 (3) 通过Uboot烧写嵌入式Linux文件系统。 3. 在宿主机上编译UBoot (1) 将开发板供应商提供的uboots5pv210.tar.gz文件复制到Linux主机的工作目录下,用解压命令将其解压到uboots5pv210目录。在这里,把uboots5pv210目录设置为UBoot的根目录,其命令如下。 # tar zxvf u-boot-s5pv210.tar.gz # cd u-boot-s5pv210 (2) 清理UBoot。对于已经运行过UBoot的机器,需要先清理uboot,其命令如下。 # make clean (3) 检查UBoot的交叉编译路径是否正确。打开Makefile文件, 检查交叉编译路径是否配置正确。其配置交叉编译路径的代码为: ifeq ($(ARCH), arm) CROSS_COMPILE = arm-none-linux-gnueabi- 如图5.13所示。 (4) 编译UBoot,生成镜像文件。在UBoot的根目录(即uboots5pv210目录),执行编译命令: # make 编译完成之后,用ls列出文件内容,可以看到已经生成了系统引导程序的镜像文件uboot.bin,如图5.14所示。 图5.13检查UBoot的交叉编译路径 图5.14查看系统生成的镜像文件uboot.bin 4. 制作启动SD卡 在uboots5pv210\sd_fusing目录下,有一个名为sd_fusing.sh的脚本程序,该脚本程序可以对SD卡进行分区、格式化为vfat格式、写入UBoot镜像文件等操作。 事先准备好一张空白的SD卡,在终端窗口进入uboots5pv210\sd_fusing目录,执行制作启动SD卡的命令: #./sd_fusing.sh /dev/sdb 其执行结果如图5.15所示。 图5.15制作启动SD卡 然后在SD卡的根目录建立sdfuse目录,把镜像文件uboot.bin复制到SD卡的sdfuse目录下。这样就制作好了一张可以启动的SD卡。 5. 通过SD卡把UBoot镜像文件烧写到Flash中 1) 从SD卡启动开发板 将串口线将宿主机的串口与开发板UART端口连接,将串口的波特率设置为115200。然后再将开发板的启动模式设置为从SD卡启动模式,并把制作好的启动SD卡插到MMC卡槽。 打开S5PV210开发板电源,当超级终端窗口显示Hit any key to stop autoboot: 0时,快速按空格键,屏幕显示SMDKV210#的提示符,进入UBoot命令行,如图5.16所示。 图5.16从SD卡启动,进入UBoot命令行 2) 清空开发板NAND Flash数据 在超级终端窗口中的SMDKV210#提示符输入后面,输入nandscrub命令,将NAND Flash数据清空。 当出现Really scrub this NAND flash? <y/N>提示时,按y键后,按Enter键确认,则完成清空NAND Flash数据的工作,如图5.17所示。 图5.17清空NAND Flash数据 3) 把镜像文件uboot.bin烧写到NAND Flash中 接上述步骤,继续执行sdfuse flash bootloader uboot.bin命令,把uboot.bin烧写到NAND Flash的bootloader分区中。 命令执行结果如图5.18所示。 图5.18把镜像文件uboot.bin烧写到NAND Flash中 至此,完成UBoot的烧写。关闭开发板电源,从MMC卡槽中取出SD卡,将开发板的启动模式设置为从NAND Flash启动模式。重新将开发板电源打开,可以看到,开发板已经能正常启动。 6. 通过UBoot烧写嵌入式Linux文件系统 在完成UBoot烧写之后,使用串口线将宿主机与开发板的串口连接起来,再使用网线将宿主机与开发板的网口连接起来。 (1) 把要烧写的系统文件全部复制到tftpd32.exe文件所在的同一目录下。这些文件包括 zImageramdisk(内核映像文件)、ramdisk.gz(文件系统映像文件)。 (2) 打开超级终端后,开启开发板电源,UBoot启动过程中,按任意键,进入UBoot命令行模式。 (3) 假设宿主机的IP地址设置为192.168.0.100,运行UBoot的setenvserverip 192.168.0.100命令,在开发板上将宿主机的IP地址192.168.0.100设置为TFTP服务器的IP地址; 然后运行saveenv命令将设置保存到环境变量中。命令执行结果如图5.19所示。 图5.19在开发板设置TFTP服务器地址 (4) 运行UBOOT命令sdfuse erase kernel; sdfuse erase system擦除kernel区与system文件系统区数据,执行结果如图5.20所示。 图5.20擦除开发板内核区及文件系统区数据 (5) 运行UBOOT命令run loadlinuxramdisk,通过连接宿主机TFTP服务,将Linux内核与ramdisk文件系统下载到SDRAM中,命令执行情况如图5.21所示。 图5.21将Linux内核与ramdisk文件系统下载到SDRAM中 下载完成后,启动Linux文件系统,进入Linux命令行。至此,全部烧写工作结束。 本 章 小 结 本章详细讲解了在主机上建立嵌入式Linux开发环境,在主机端通过minicom终端窗口操作开发板的文件系统。详细讲解了建立嵌入式Linux的数据共享服务,包括NFS服务的配置,应用串口协议传输数据,在VMware虚拟机中设置Windows——Linux的数据共享。在本章还详细叙述了如何烧写开发板,如何移植嵌入式Linux内核以及如何制作文件系统。这些都是操作性很强的内容,而且在嵌入式的开发中也是必不可少的一部分,希望读者熟练掌握。 习题 1. 仿照例52,在Linux 内核系统中建立一个名为kernel_test的项目,完成内核配置后,查看.config文件中的配置结果。 2. 自己动手,完成对嵌入式开发板的系统烧写实验。