摘要: 首先介绍了嵌入式系统的概念, 及相关硬件平台和软件版本。然后, 主要介绍了嵌入式Linux 的引导程序U-Boot 的移植, 以及开源、免费操作系统Linux2.6.32.2 的移植。最后, 构建了基于NandFlash存储器的Yaffs2 文件系统,利用BusyBox 创建根文件系统。基于ARM 和嵌入式Linux 的嵌入式系统平台搭建基本完成,可以在此平台上添加更多驱动,以便更好地开发应用程序。
进入后PC 时代以来, 伴随着设计和制造技术的发展,集成电路从当初的晶体管集成发展到现在的IP 集成, 即SoC(Systemon Chip ) 设计技术。促使嵌入式系统渗透到了当今社会中的各个行业, 并且发挥越来越重要的作用。嵌入式系统一般可定义为以应用为中心、以计算机技术为基础、软硬件可裁剪、适用于应用系统且对功能、成本、体积、功耗有严格要求的专用计算机系统,它的主要特点是嵌入、应用。
随着各种嵌入式设备功能越来越强大, 在设备中使用嵌入式操作系统也成为必然。Linux 操作系统具有开放源代码、易于移植、资源丰富、免费等特点, 在嵌入式领域的地位越来越重要。嵌入式Linux 和PC 上的Linux是同一套内核代码, 只是裁剪的程度不一样, 所以, 很多在PC 上开发的软件, 经过交叉编译后可以直接在嵌入式设备上运行。本文主要涉及到Bootloader 移植和Linux-2.6.32.2 内核的移植、根文件系统移植、在S3C2440平台上构建完整的嵌入式开发平台三个方面。
1 交叉开发环境的建立
在进行嵌入式软件开发之前, 必须要在PC 上建立ARM 的交叉编译环境。交叉编译就是在PC 平台上生成可以在ARM 平台上运行的代码。其中主要包括ARM 的交叉编译器arm-elf-gcc 和交叉连接器arm-elf-ld 。本文采用的交叉编译器的版本是gcc -3.4.5 -glibc -2.3.6 。
交叉编译流程如图1 所示。
图1 嵌入式系统交叉编译流程
2 BootLoader 引导加载程序
BootLoader 是一段在系统上电时开始执行的程序,用以初始化硬件设备, 准备好软件环境, 设置好启动参数, 最后引导操作系统, 与PC 上的BIOS 程序相似。当前开放源码的Linux 引导程序主要有x86 架构的LILO、GRUB, 对于ARM 架构的主要有Vivi 和U-Boot 。本文使用U-Boot 作为引导程序。U-Boot(Universal Boot Loader) ,即通用的BootLoader , 遵循GPL 条款开放源代码。U-Boot相对于Vivi 功能更加强大, 也更方便后续程序的调试。
BootLoader 的启动一般分为两个阶段, 第一阶段的代码主要是用汇编语言编写, 主要的功能是完成硬件设备的初始化, 为加载第二阶段的代码准备RAM 空间, 设置好堆栈; 第二阶段主要用C 语言编写, 检测内存映射, 将内核映像和根文件系统从Nand Flash 读到RAM中, 为内核启动设置参数, 引导内核。
U-Boot 的源代码可以从ftp://ftp.denx.de/pub/u-boot/进行下载, 本文使用的U-Boot 版本是U-Boot2009.08 。
移植U-Boot 的关键步骤如下:
(1) 首先, 将include/configs 目录下的smdk2410.h 复制并改名为mini2440.h , 根据U-Boot 的说明可以知道,如果要使用开发板board/<board_name > , 则先执行“make <board_name > ”_config 命令进行配置, 然后执行“make all ” , 生成可执行文件。所以, 修改U-Boot 顶层的Makefile 文件, 添加下面一行mini2440_config : unconfig@ $ (MKCONFIG) $ (@:_config = ) arm arm920t mini2440frank s3c24x0 。这里有几个重要的参数,arm 指CPU 的架构,arm920t 指CPU 的类型,s3c24x0 指CPU 的型号。这样就可以使用make mini2440_config 这条命令进行配置。
(2)本文使用的U-Boot 是从Nand Flash 启动的, CPU可以直接访问Nand Flash 中前4 KB 代码, 利用这4 KB代码把U-Boot 中绝大部分代码拷贝到内存中[ 3]。其中下面的代码就是调用C 语言中的Nand Flash 的读写函数, 该函数主要把Nand Flash 中4 KB 以后的代码复制到RAM 中。在编写nand_read_ll 的函数时, 注意参考Nand Flash 的数据手册, 对大页和小页的Nand Flash , 其读写的命令和时序是不同的。
@copy U-Boot to RAM
ldr r0,=TEXT_BASE
mov r1,#0x0
mov r2,#0x60000
bl nand_read_ll
tst r0,#0x0
beq ok_nand_read
由于在后面加载Linux 内核和根文件系统时, 使用的是tftp 方式, 所以必须添加DM9000EP网卡的驱动。在mini2440.h 文件中, 其主要的配置如下:
#define CONFIG_DRIVER_DM9000 1
#define CONFIG_NET_MULTI 1
#define CONFIG_DM9000_NO_SROM 1
#define CONFIG_DM9000_BASE 0x20000300
#define DM9000_DATA (CONFIG_DM9000_BASE +4)
其中,CONFIG_DM9000_BASE 宏是最重要的, 因为它定义的是网卡的地址, 不同的网卡有不同的地址,DM9000EP 访问的基址为0x20000000, 之所以再偏移0x300 是由它的特性决定的。
(3) 要正确引导Linux 内核, 还需要配置下面几个重要的宏定义, 这几个宏定义不同, 意味着引导Linux 内核的方式也不同。
#define CONFIG_BOOTARGS"noinitrd root=/dev/mtdblock3
init=/linuxrc console=ttySAC0,115200 mem=64M"
其中,root =/dev/mtdblock3 是由Linux 中的Nand Flash 分区所决定的, 意味着Nand Flash 的第4 个分区为根文件系统。
#define CONFIG_BOOTCOMMAND"nand read 0x32000000 0x60000 0x560000;bootm 0x32000000"
这个宏定义是将Nand Flash 中0x60000 -0x560000( 和kernel 分区一致) 的内容读到内存0x32000000 中, 然后用bootm 命令来执行。
要正常地引导Linux 内核, 必须要具备如下几个条件:
(1)CPU 寄存器
R0=0 ;
R1= 机器类型ID ; 对于ARM 结构的CPU, 其机器类
型ID 在linux/arch/arm/tools/mach-types ;
R2=启动参数标记列表在RAM 中起始基地址。
(2)CPU 工作模式
必须禁止中断(IRQs 和FIQs ) ;
CPU 必须为SVC 模式。
(3)Cach 和MMU 的设置
MMU 必须关闭;
指令Cach 可以打开也可以关闭;
数据Cach 必须关闭。
3 Linux2.6.32.2 内核的移植
3.1 内核的获取
Linux 内核的更新很快, 可以从http://www.kernel.org/pub/linux/kernel/ 得到最新的Linux 内核版本, 本文使用的Linux 内核版本是Linux -2.6.32.2, 交叉编译工具使用符合EABI 标准的arm-linux-gcc-4.3.2 。
3.2 内核的移植
可以在内核的根目录下, 运行make menuconfig 命令, 对内核进行适当的裁剪, 以适应硬件平台。
对内核进行适当的裁剪, 以适应硬件平台。
(1) 修改Makefile 文件
欲设置Linux 的默认平台为ARM 平台, 需进入Linux-2.6.32 文件夹中, 修改此目录下的Makefile 文件。
export KBUILD_BUILDHOST := $(SUBARCH)
ARCH ?=arm // 使用的目标平台
CROSS_COMPILE ?=arm-linux- // 使用的交叉编译器,
这里使用系统默认的编译器
(2) 关于机器码
在启动内核时, 根据BootLoader 传入的机器码(MACH_TYPE) 来决定应启动哪种目标平台[ 6], 本开发平台的机器码为1999 。机器码存放在文件opt/kernel/linux-2.6.32.2/arch/arm/tools/mach-types 中。
mini2440 MACH_MINI2440 MINI2440 1999 // 机器码
如果机器码不匹配, 引导内核不成功, 则会出现如下的错误提示:
Uncompressing
Linux……………………………………………………………………………done, booting the kernel.
(3) 修改时钟源
将/kernel/linux -2.6.32.2/arch/arm/mach -s3c2440/ 目录下的mach-smdk2440.c 文件改名为mach-mini2440.c。
因为mini2440 和mach-smdk2440.c 极其相似, 以该文件为基础进行修改, 在mach -mini2440.c 文件中将staticvoid__init smdk2440_map_io ( void ) 函数中的晶振频率修改为mini2440 开发板上实际使用的12000000。
(4) 为内核打上yaffs2 补丁
①Yaffs2 文件系统是专门针对嵌入式设备, 特别是使用Nand Flash 作为存储器的嵌入式设备而创建的一种文件系统, 使用yaffs2 就可以支持大页的Nand Flash。
进入yaffs2 源代码目录执行如下命令:
#./patch -ker.sh c /opt/FriendlyARM/mini2440/linux -2.6.32.2
②配置内核以支持Yaffs2 文件系统
在Linux 内核源代码根目录运行make xconfig, 在“File Systems ” 选项中, 找到“Miscellaneous filesystems ” 菜单项, 找到“YAFFS2 file system support ” 并选中它, 这样就在内核中添加了yaffs2 文件系统的支持, 保存并退出。然后在命令行中, 执行make zImage 。
(5) 修改Nand Flash 分区信息
①在mach-mini2440.c 文件中添加Nand Flash 的分区信息, 下面的代码将Nand Flash 分成了4 个分区, 第1 分区也是BootLoader 所在的分区, 对应dev/mtdblock0 ;第2 个分区是U-Boot 的参数分区, 对应dev/mtdblock1 ;第3 个分区是内核分区, 对应dev/mtdblock2 ; 第4 个分区为根文件系统分区对应dev/mtdblock3 。分区结构图如表1 所示。
表1 128 MB Nand Flash 的分区结构图
其部分实现代码如下:
static struct mtd_partition mini2440_default_nand_part[] ={
[0] = {
.name="U-boot",
.offset= 0,
.size= 0x00040000,
}
其中name 是分区的名字,offset 是偏移的开始地址,size是分区的大小, 其余部分的分区与此类似。
②下面代码是添加Nand Flash 的设置表, 因为板子上只有一片Nand Flash, 因此也就只有一个设置表。
static struct s3c2410_nand_set mini2440_nand_sets[] = {
[0] = {
.name= "NAND",
.nr_chips= 1,
.nr_partitions=
ARRAY_SIZE(mini2440_default_nand_part),
.partitions= mini2440_default_nand_part,
}
}
③上面的设置完成后, 还需要将Nand Flash 设备注册到系统中。下面这段代码就是将Nand Flash 设备添加到开发板的设备列表结构。
static struct platform_device *mini2440_devices [] __initdata
= {
&s3c_device_nand,
}
④在mini2440_machine_init 函数中添加平台的数据信息。
static void __init mini2440_machine_init(void){
s3c_device_nand.dev.platform_data=&mini2440_nand_info;
}
现在可以进入kernel/linux-2.6.32.2/arch/arm/boot 目录,然后执行下面的命令, 就会在该目录下生成uImage.img格式的、U-Boot 可以引导的内核镜象。
Mkimage – n ‘linux-2.6.32.2 ’ –A arm – O linux–T kernel –C none – a 0x30008000 – e 0x30008000 –d zImage uImage.img
至此, 可以把生成的uImage.img 格式的镜像文件复制到tftp 目录下, 使用tftp 进行下载。
3.3 文件系统
所谓根文件系统, 就是创建各个目录, 例如在/bin 、/sbin/ 目录下存放各种可执行的程序, 在/etc 目录下存放配置文件, 在/lib 目录下存放库文件。
可以利用Busybox 工具创建根文件系统,Bosybox 是一个遵循GPL v2 协议的开源项目, 它在编写过程中对文件大小进行优化, 并考虑了系统资源有限( 例如内存)的情况, 使用Busybox 可以自动生成根文件系统所需的bin、sbin、usr 目录和linuxrc 文件, 可以使用make menuconfig对Busybox 的选项进行配置。
(1) 进入opt/kernel, 创建一个shell 脚本用于构建根文件系统的各个目录, 并且为其增加执行权限;(2)Linux 中的init 进程会根据etc/inittab 文件创建其他子进程, 下面代码是inittab 文件中的内容, 说明了系统启动后首先执行的脚本文件是rcS, 虚拟的终端是串口0, 当按下ctr+alt+del 时重启系统,inittab 文件的作用就是控制系统启动时和启动后一些程序的运行。
#etc/inittab
::sysinit:/etc/init.d/rcS
s3c2410_serial0::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a-r
(3) 创建etc/init.d/rcS 文件, 这是一个脚本文件, 可以在里面添加要自动执行的一些命令。
#! /bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
runlevel=S // 运行的级别
prevlevel=N
umask 022 // 文件夹的掩码
mount -a // 挂载/etc/fstab/ 文件指定的所有的文件系统
mdev-s
/bin/hostname -F /etc/sysconfig/HOSTNAME// 主机的名字
使用yaffs 源码提供的工具制作文件系统的映像文件。由于128 MB 的Nand Flash 是大页结构, 所以需要使用相应的大页制作工具; 使用命令mkyaffs2image rootfsrootfs.img 生成根文件系统映像文件。
本文通过对U-Boot 移植和Linux 内核移植的讨论,给出了移植U-Boot 和Linux 到大多数开发板的关键部分。由于移植的复杂性, 不可能包括全部步骤, 但通过本文的阐述可以了解移植的基本流程和关键点, 为移植不同版本到其他硬件平台提供了参考, 也为应用程序的开发搭建了一个比较完整的嵌入式平台。