bootloader相关

本章基于Intel公司的pxa270芯片分析U-Boot的工作机制,介绍了嵌入式Linux交叉开发工具ELDK(Embedded Linux Development Kit),说明配置安装U-Boot到特定主板、交叉编译调试Linux的方法,分析了U-Boot的构架。

目录

[隐藏]
  • 1 BootLoad启动方式
    • 1.1 网络启动方式
    • 1.2 2.磁盘启动方式
    • 1.3 3.Flash启动方式
  • 2 U-Boot介绍
    • 2.1 U-Boot功能介绍
    • 2.2 U-Boot安装
    • 2.3 U-Boot命令介绍
    • 2.4 U-Boot环境变量
    • 2.5 U-Boot脚本
    • 2.6 U-Boot单独应用程序
    • 2.7 位图支持
  • 3 2 ELDK工具介绍
    • 3.1 下载与编译
    • 3.2 建立开发系统
    • 3.3 编译安装Linux内核
    • 3.4 启动嵌入式Linux
    • 3.5 Flash文件系统
    • 3.6 根文件系统的设计与编译
    • 3.7 覆盖层文件系统
    • 3.8 调试
      • 3.8.1 (1)U-Boot的调试
      • 3.8.2 (2)Linux内核调试
      • 3.8.3 (3)远程调试应用程序

BootLoad启动方式

Bootloader的主要功能是引导操作系统启动,开放源代码的BootLoader种类很多,常用的BootLoader的功能说明如表1所示。

表1 常用BootLoader的功能说明

Bootloader名 是否终端监视 描    述 x86 ARM PowerPC
LILO Linux磁盘引导程序
GRUB GNU的LILO替代程序
Loadlin 从DOS引导Linux
ROLO 从ROM引导Linux而不需要BIOS
Etherboot 通过以太网卡启动Linux系统的固件
LinuxBIOS 完全替代BUIS的Linux引导程序
BLOB LART等硬件平台的引导程序
U-boot 通用引导程序
RedBoot 基于eCos的引导程序

网络启动方式

这种方式开发板不需要配置较大的存储介质,跟无盘工作站有点类似。但是使用这种启动方式之前,需要把Bootloader安装到板上的EPROM或者Flash中。Bootloader通过以太网接口远程下载Linux内核映像或者文件系统。第4章介绍的交叉开发环境就是以网络启动方式建立的。这种方式对于嵌入式系统开发来说非常重要。

使用这种方式也有前提条件,就是目标板有串口、以太网接口或者其他连接方式。串口一般可以作为控制台,同时可以用来下载内核影像和RAMDISK文件系统。串口通信传输速率过低,不适合用来挂接NFS文件系统。所以以太网接口成为通用的互连设备,一般的开发板都可以配置10M以太网接口。

对于PDA等手持设备来说,以太网的RJ-45接口显得大了些,而USB接口,特别是USB的迷你接口,尺寸非常小。对于开发的嵌入式系统,可以把USB接口虚拟成以太网接口来通讯。这种方式在开发主机和开发板两端都需要驱动程序。

另外,还要在服务器上配置启动相关网络服务。Bootloader下载文件一般都使用TFTP网络协议,还可以通过DHCP的方式动态配置IP地址。

DHCP/BOOTP服务为Bootloader分配IP地址,配置网络参数,然后才能够支持网络传输功能。如果Bootloader可以直接设置网络参数,就可以不使用DHCP。

TFTP服务为Bootloader客户端提供文件下载功能,把内核映像和其他文件放在/tftpboot目录下。这样Bootloader可以通过简单的TFTP协议远程下载内核映像到内存。如图6.1所示。

图6.1 网络启动示意图

大部分引导程序都能够支持网络启动方式。例如:BIOS的PXE(Preboot Execution Environment)功能就是网络启动方式;U-Boot也支持网络启动功能。

2.磁盘启动方式

传统的Linux系统运行在台式机或者服务器上,这些计算机一般都使用BIOS引导,并且使用磁盘作为存储介质。如果进入BIOS设置菜单,可以探测处理器、内存、硬盘等设备,可以设置BIOS从软盘、光盘或者某块硬盘启动。也就是说,BIOS并不直接引导操作系统。那么在硬盘的主引导区,还需要一个Bootloader。这个Bootloader可以从磁盘文件系统中把操作系统引导起来。

Linux传统上是通过LILO(LInux LOader)引导的,后来又出现了GNU的软件GRUB(GRand Unified Bootloader)。这2种Bootloader广泛应用在X86的Linux系统上。你的开发主机可能就使用了其中一种,熟悉它们有助于配置多种系统引导功能。

LILO软件工程是由Werner Almesberger创建,专门为引导Linux开发的。现在LILO的维护者是John Coffman,最新版本下载站点:http://lilo.go.dyndns.org。LILO有详细的文档,例如LILO套件中附带使用手册和参考手册。此外,还可以在LDP的"LILO mini-HOWTO"中找到LILO的使用指南。

GRUB是GNU计划的主要bootloader。GRUB最初是由Erich Boleyn为GNU Mach操作系统撰写的引导程序。后来有Gordon Matzigkeit和Okuji Yoshinori接替Erich的工作,继续维护和开发GRUB。GRUB的网站http://www.gnu.org/software/grub/上有对套件使用的说明文件,叫作《GRUB manual》。GRUB能够使用TFTP和BOOTP或者DHCP通过网络启动,这种功能对于系统开发过程很有用。

除了传统的Linux系统上的引导程序以外,还有其他一些引导程序,也可以支持磁盘引导启动。例如:LoadLin可以从DOS下启动Linux;还有ROLO、LinuxBIOS,U-Boot也支持这种功能。

3.Flash启动方式

大多数嵌入式系统上都使用Flash存储介质。Flash有很多类型,包括NOR Flash、NAND Flash和其他半导体盘。其中,NOR Flash(也就是线性Flash)使用最为普遍。

NOR Flash可以支持随机访问,所以代码是可以直接在Flash上执行的。Bootloader一般是存储在Flash芯片上的。另外,Linux内核映像和RAMDISK也可以存储在Flash上。通常需要把Flash分区使用,每个区的大小应该是Flash擦除块大小的整数倍。图6.2是Bootloader和内核映像以及文件系统的分区表。

图6.2 Flash存储示意图

Bootloader一般放在Flash的底端或者顶端,这要根据处理器的复位向量设置。要使Bootloader的入口位于处理器上电执行第一条指令的位置。

接下来分配参数区,这里可以作为Bootloader的参数保存区域。

再下来内核映像区。Bootloader引导Linux内核,就是要从这个地方把内核映像解压到RAM中去,然后跳转到内核映像入口执行。

然后是文件系统区。如果使用Ramdisk文件系统,则需要Bootloader把它解压到RAM中。如果使用JFFS2文件系统,将直接挂接为根文件系统。这两种文件系统将在第12章详细讲解。

最后还可以分出一些数据区,这要根据实际需要和Flash大小来考虑了。

这些分区是开发者定义的,Bootloader一般直接读写对应的偏移地址。到了Linux内核空间,可以配置成MTD设备来访问Flash分区。但是,有的Bootloader也支持分区的功能,例如:Redboot可以创建Flash分区表,并且内核MTD驱动可以解析出redboot的分区表。

除了NOR Flash,还有NAND Flash、Compact Flash、DiskOnChip等。这些Flash具有芯片价格低,存储容量大的特点。但是这些芯片一般通过专用控制器的I/O方式来访问,不能随机访问,因此引导方式跟NOR Flash也不同。在这些芯片上,需要配置专用的引导程序。通常,这种引导程序起始的一段代码就把整个引导程序复制到RAM中运行,从而实现自举启动,这跟从磁盘上启动有些相似。

U-Boot介绍

U-Boot功能介绍

PC机引导加载程序由BIOS和OS BootLoader(通常为GRUB)组成,嵌入式系统一般没有BIOS这样的固件程序,系统的引导加载完全由BootLoader完成。不同的系统,boot Loader是不同的。Boot Loader有许多开放源代码,但在标准Linux中没有,需要用户从网上下载。

BootLoader包括LILO、GRUB、Loadlin、BOLB、U-boot、RedBoot等多种,其中,GRUB是LILO的继任者,用于PC机,Loadlin用于从DOS装载Linux;BLOB来自LART(Luser Attitude Readjustment Tool)项目,用于引导加载基于StorngARM CPU的单个主板计算机;U-boot(Universal loader)是通用的开源引导程序,常用于基于ARM、PowerPC、MIPS构架的嵌入式Linux系统的引导,也用于NetBSD和VxWorks等多种操作系统的引导。

U-Boot支持的主要功能列出如下:

  • 系统引导功能。
  • 支持NFS挂载、RAMDISK 系统引导 压缩或非压缩形式的根文件系统。
  • 支持NFS挂载、从Flash中引导压缩或非压缩的Linux内核。
  • 具有强大的操作系统接口功能,可灵活设置、传递多个参数给操作系统,支持目标板环境参数的多种存储方式,如Flash、NVRAM、EEPROM。
  • 支持CRC32校验,可校验Flash中内核、RAMDISK镜像文件是否完好。
  • 支持串口、SDRAM、Flash、以太网、LCD、NVRAM、EEPROM、键盘、USB、PCMCIA、PCI、RTC等设备驱动。
  • 支持上电自检功能,如:SDRAM、Flash大小自动检测;SDRAM 故障检测;CPU型号检测等。

U-Boot有"启动加载"和"下载"两种操作模式,启动加载模式将操作系统加载到 RAM 中运行。下载模式是以某种通信方式从主机(Host)下载文件保存到目标机的 RAM 中,然后被 U-Boot 写到目标机上的FLASH中。这种模式下还提供一个简单的命令行接口。

U-Boot初始化目标板硬件,为嵌入式操作系统提供目标板硬件配置信息,完成嵌入式操作系统装载、引导和运行的固件程序。它能够将系统的软硬件紧密衔接在一起。

Intel基于XScale架构的pxa27X ARM系列处理器,最高主频可达624MHz,加入了Wireless MMX技术,广泛应用于PDA、智能手机、PMP等产品的开发中,硬件的详细说明请参考产品手册。

ARM嵌入式系统复位后通常都从地址 0x00000000 开始执行,Boot Loader 就从这里开始。

Boot Loader分为由汇编语言实现的部分(称为stage1)和用C语言实现部分(称为stage2)。stage1的功能是硬件设备初始化、准备 RAM、将C语言实现部分拷贝RAM、设置好堆栈,并跳转到C语言实现部分入口点。stage1中的初始化过程列出如下:

(1)设置GPIO控制器,以便能控制外围设备。

(2)屏蔽所有的中断。Boot Loader 的执行过程中不必响应任何中断。

(3)设置 CPU时钟频率。

(4)RAM 初始化。设置系统的内存控制器等。

(5)关闭 CPU 内部指令/数据 cache。

(6)准备stage2的ram空间,拷贝stage2到ram,设置堆栈。堆栈设置在stage2_end-4的地方,向下增长。

(7)跳转到stage2的入口点即main函数开始执行stage2的代码。

跳转方法是使用了trampoline程序来包装了main函数,trampoline.S程序如下:

.text
 
.globl _trampoline
_trampoline:
	bl	main
	/* if main ever returns we just call it again */
	b	_trampoline

 从这段代码可以看出,当从main函数中返回时,则再进入main函数。以防止程序异常中断。

 执行完stage1后系统内存布局如图1所示。

图1 执行完stage1后系统内存布局图

stage2的功能是初始化硬件、检测系统内存映射(memory map)、将 kernel 映像和根文件系统映像从 flash 上读到 RAM 中、设置启动参数、进入内核。进入stage2后,程序都是c语言实现的,stage2的第一个函数为main函数。

U-Boot安装

下载和编译U-Boot源代码,方法如下:

#下载

$ cd /opt/eldk/usr/src

$ wget ftp://ftp.denx.de/pub/u-boot/u-boot-1.3.2.tar.bz2

$ rm -f u-boot

$ bunzip2 < u-boot-1.3.2.tar.bz2 | tar xf -

$ ln -s u-boot-1.3.2 u-boot

$ cd u-boot

#编译

$ make canyonlands_config #生成配置文件

$ make all #编译

当目标板没有烧录U-Boot或其他固件时,用户可用带有BDM/JTAG接口的调试或烧录编程器(如:BDI2000)将U-Boot烧录到目标板的Flash中,如果目标板已有一个可运行的U-Boot,可不用烧录编程器,而直接通过U-Boot的命令将U-Boot映像文件下载到Flash中。

U-Boot映像文件是特殊格式的文件,它包括创建时间、操作系统、压缩类型、映像文件类型、映像文件名和CRC32校验和等信息。可由ELDK工具命令mkimage创建。

U-Boot命令介绍

在缺省配置下,U-Boot以交互模式运行,它在UART1端口的串行控制台上提供了简单的命令行操作,用户可用命令help查看可用的U-Boot命令,部分命令列出如下:

=> help

' - alias for 'help'

askenv - get environment variables from stdin

autoscr - run script from memory

base - print or set address offset

bdinfo - print Board Info structure

boot - boot default, i.e., run 'bootcmd'

bootd - boot default, i.e., run 'bootcmd'

bootelf - Boot from an ELF image in memory

bootm - boot application image from memory

bootp - boot image via network using BootP/TFTP protocol

bootstrap - program the I2C bootstrap EEPROM

bootvx - Boot vxWorks from an ELF image

cmp - memory compare

coninfo - print console devices and information

cp - memory copy

crc32 - checksum calculation

date - get/set/reset date & time

dhcp - invoke DHCP client to obtain IP/boot params

U-Boot环境变量

U-Boot环境是保存在永久存储中的一块内存,当U-Boot启动时拷贝到RAM中,存储配置系统的环境变量,并被CRC32校验和保护。一些环境变量对U-Boot来说有特殊含义,可用这些环境变量配置U-Boot的行为。表1说明了环境变量的功能。

表1 U-Boot环境变量的说明
环境变量 功能说明
bootdelay 定义复位等待执行bootcmd变量对应命令的时间(以秒为单位)。
baudrate 定义串口控制台的波特率。
netmask 定义以太网接口的掩码。
ethaddr 定义以太网接口的MAC地址。
bootfile 用TFTP装载的映像的名字。
bootargs 定义传递给Linux内核的启动参数。
bootcmd 定义自动启动时执行的几条命令。
serverip 定义tftp服务器端的IP地址。
ipaddr 定义本地的IP地址。
autoload 如果设置为no,命令rarpb、bootp或dhcp将仅从BOOTP/DHCP服务器执行配置查找,而不使用TFTP装载任何映像。
autostart 如果设置为yes,使用命令rarpb、bootp、dhcp、tftp、disk或docb装载的映像将被命令bootm自动启动。
loadaddr 命令tftp或loads等的缺省装载地址。

U-Boot的环境变量都有缺省值,用户可以修改用于改变U-Boot的行为,环境变量存储于EEPROMak Flash这样的永久存储区。环境变量用U-Boot命令setenv设置,一些设置样例列出如下:

=>setenv serverip 192.168.1.151
=>setenv ipaddr 192.168.1.152
=>setenv rootpath "/opt/ruiva/xscale/rootfs "
=>setenv bootargs "root=/dev/nfs rw nfsroot=\$(serverip):\$(rootpath) ip=\$(ipaddr) " console= ttyS0,115200 mem=64M     #通过bootm命令传递给内核的启动参数
=>setenv kernel_addr 30000000
=>setenv nfscmd "tftp \$(kernel_addr) uImage; bootm\$(kernel_addr) "  #将uImage下载到指定的地址
=>run nfscmd     #运行nfscmd脚本

U-Boot脚本

U-Boot允许存储命令序列在纯文本文件中,用命令mkimage将该文件转换成脚本映像,该映像可用U-Boot命令autoscr执行。

例如:用户需要在许多目标板上运行下面命令序列,可将该命令序列存储在一个文本文件setenv-commands中,其内容如下:

bash$ cat setenv-commands

setenv loadaddr 00200000
echo ===== U-Boot settings =====
setenv u-boot /tftpboot/TQM860L/u-boot.bin
setenv u-boot_addr 40000000
setenv load_u-boot 'tftp ${loadaddr} ${u-boot}'
setenv install_u-boot 'protect off ${u-boot_addr} +${filesize};era ${u-boot_addr} +${filesize};cp.setenv update_u-boot run load_u-boot install_u-boot
echo ===== Linux Kernel settings =====
setenv bootfile /tftpboot/TQM860L/uImage
setenv kernel_addr 40040000
setenv load_kernel 'tftp ${loadaddr} ${bootfile};'
setenv install_kernel 'era ${kernel_addr} +${filesize};cp.b ${loadaddr} ${kernel_addr} ${filesize}'
setenv update_kernel run load_kernel install_kernel
echo ===== Ramdisk settings =====
setenv ramdisk /tftpboot/TQM860L/uRamdisk
setenv ramdisk_addr 40100000
setenv load_ramdisk 'tftp ${loadaddr} ${ramdisk};'
setenv install_ramdisk 'era ${ramdisk_addr} +${filesize};cp.b ${loadaddr} ${ramdisk_addr} ${filesize}'
setenv update_ramdisk run load_ramdisk install_ramdisk
echo ===== Save new definitions =====
saveenv

转换成映像文件的方法列出如下:

bash$ mkimage -T script -C none -n 'Demo Script File' -d setenv-commands setenv.img

Image Name: Demo Script File

Created: Mon Jun 6 13:33:14 2005

Image Type: PowerPC Linux Script (uncompressed)

Data Size: 1147 Bytes = 1.12 kB = 0.00 MB

Load Address: 0x00000000

Entry Point: 0x00000000

Contents:

Image 0: 1139 Bytes = 1 kB = 0 MB

在目标板上,用户可以像其他映像文件一样使用tftp这样的命令进行装载,然后,使用命令autoscr执行该映像,方法如下:

=> tftp 100000 /tftpboot/TQM860L/setenv.img

U-Boot单独应用程序

U-Boot允许动态装载或运行 单个应用程序,该应用程序可以使用U-Boot资源,如:控制台I/O函数、内存分配或中断服务。编译U-Boot时,单个应用程序与U-Boot一起编译。

位图支持

通过添加CFG_CMD_BMP选项到CONFIG_COMMAND命令选项,可以在U-Boot中打开位图支持,这将添加命令bmp到U-Boot配置的命令列表中。命令bmp可以在屏幕上显示图像。图像传送与显示的命令列出如下:

=> tftp 100000 /tftpboot/LWMON/denk_startup.bmp
TFTP from server 192.168.3.1; our IP address is 192.168.3.74
Filename '/tftpboot/LWMON/denk_startup.bmp'.
Load address: 0x100000
Loading: #############################################################
done
Bytes transferred = 308278 (4b436 hex)
=> bmp info 100000
Image size : 640 x 480
Bits per pixel: 8
Compression : 0
=> bmp display 100000

许多用户期望在系统启动过程(约5~6秒)中看见图像,这种图像显示称为splash screen。U-Boot支持该功能。用户通过添加"#define CONFIG_SPLASH_SCREEN"到目标板的配置文件,就可打开splash screen支持,这意味着打开了位图支持。

目标板开机后,U-Boot将测试环境变量splashimage是否定义,它是否含有位图的地址,如果含有位图,目标板启动时将不显示启动信息,而是显示splash screen。安装splash screen位图的方法列出如下:

=> tftp 100000 /tftpboot/denx_startup.bmp
TFTP from server 192.168.3.1; our IP address is 192.168.3.74
Filename '/tftpboot/denx_startup.bmp'.
Load address: 0x100000
Loading: #############################################################
done
Bytes transferred = 308278 (4b436 hex)
=> cp.b 100000 41F80000 $filesize
Copy to Flash... done
=> setenv splashimage 41F80000
=> saveenv
Saving Environment to Flash...
Un-Protected 1 sectors
Erasing Flash...
. done
Erased 1 sectors
Writing to Flash... done
Protected 1 sectors
=> bmp info $splashimage
Image size : 640 x 480
Bits per pixel: 8
Compression : 0

为了实现U-Boot Splash Screen特征的支持,Linux内核加入了配置选项CONFIG_FB_PRE_INIT_FB,它允许跳过帧缓冲区(frame buffer)的某一部分,并再使用由U-Boot固件建立的帧缓冲区内容。这样,系统启动过程中可以显示一个启动图像,该图像就掩盖了系统启动过程中屏幕输出。

2 ELDK工具介绍

ELDK(Embedded Linux Deveelopment Kit)包括GNU交叉开发工具(如:编译器、binutils、gdb等)、大量的预编译目标工具、目标系统需要的库、常用工具(如:ls、rm)、常用服务器(如:DHCP、ftp)、U-Boot等。通过ELDK,目标板可以运行在控制台,并通过主机连接调试、装载映像文件等。

ELDK使用的主机操作系统通常为Fedora Core、Red Hat Linux、SuSE Linux、Debian、Ubuntu、Suse Linux、Mandrake、Slackware、Gentoo Linux和FreeBSD等类型Linux操作系统,支持的目标构架为PPC、ARM、MIPS等。

下载与编译

EKDK提供了源代码包和不同构架平台的二进制代码,可以方便地交叉编译所有源代码或单个软件包,提供了下载、在线调试、在线运行等多种工作方式。用户可以下载源代码光盘或用版本管理工具git进行下载,用git下载的方法列出如下:

$ cd /opt/eldk

$ git clone git://www.denx.de/git/eldk/build.git build

$ git clone git://www.denx.de/git/eldk/tarballs.git tarballs

$ git clone git://www.denx.de/git/eldk/SRPMS.git SRPMS

编译整个ELDK工具与软件包的方法列出如下:

bash$ TARGET_CPU_FAMILY_LIST="8xx 85xx" \   #CPU系列号
> /opt/eldk/build.sh -a ppc \               #CPU构架
> -n 2007-01-19 \                           #编译结果存放目录名
> -p /opt/eldk/build/ppc-2007-01-19 \       #编译前缀,编译目录存放路径
> -r /opt/eldk/build/ppc-2007-01-19/results \#输出存入目录
> -w /opt/eldk/build/ppc-2007-01-19/work \   #工作目录
> trg 47 47              #编译软件包的起始、结束编号,在文件tpckgs.lst与cpckgs.lst中

在ELDK工具编译成功或下载有ELDK工具可执行代码后,可单独编译不同平台的源文件或软件包,方法如下:

bash$ export CROSS_COMPILE=ppc_8xx-                 #编译器前缀,组成不同构架编译器
bash$ ${CROSS_COMPILE}gcc -o hello_world hello_world.c #编译源文件
bash$ ${CROSS_COMPILE}rpm -iv <package_name>.src.rpm   #解压缩并安装源代码 
bash$ ${CROSS_COMPILE}rpmbuild -ba <package_name>.spec #编译源代码包

建立开发系统

开发时,主机需要与目标板交互,如:调试程序,控制目标板,下载程序映像到目标板,目标板的调试信息需要反回到主机等。为了与目标板交互,主机必须建立开发环境,如:建立串行终端,配置TFTP服务器、DHCP服务器和NFS服务器。

(1)配置串行终端

开发者在开发时需要访问目标板的串行终端端口,通过在主机上的终端对目标板进行控制台操作。通常,开发者将目标板的串行终端端口绑定在主机的一个串行端口,实现控制台的功能,开发者还需要在主机上使用一个终端仿真程序,如:cu或kermit。

命令cu是软件包UUCP的一部分,UUCP可用作串行终端,还具有简单的文件传输功能,可用于映像文件的下载。

依赖于目标板的波特率,需要改写UUCP的配置文件,典型的配置列出如下:

•配置文件/etc/uucp/sys的内容列出如下:

#

# /dev/ttyS0 at 115200 bps:

#

system S0@115200

port serial0_115200

time any

配置文件/etc/uucp/port的内容列出如下:

#

# /dev/ttyS0 at 115200 bps:

#

port serial0_115200

type direct

device /dev/ttyS0

speed 115200

hardflow false

接着,用户可用下面命令连接到串行线:

$ cu S0@115200

$ ~. #断开连接

命令kermit代表了整个串行线和网络连接通信软件簇,文件~/.kermrc执行kermit的初始化,通过合适的初始化命令可用来定制kermit的行为,U-Boot推荐使用下面的设置:

$cat ~/.kermrc:

set line /dev/ttyS0 #串行端口

set speed 115200 #波特率

set carrier-watch off

set handshake none

set flow-control none

robust

set file type bin

set file name lit

set rec pack 1000

set send pack 1000

set window 5

命令kermit连接串行线的方法如下:

$ kermit –c

(2)配置TFTP服务器

使用 U-Boot装载Linux内核或应用程序的最快捷方式是通过Ethernet传递,U-Boot实现了TFTP协议,通过U-Boot的tftpboot命令下载文件到目标板中,主机应确信安装了TFTP后台程序/usr/sbin/in.tftpd。

(3)配置BOOTTP/DHCP服务器

BOOTP/DHCP服务器用于自动传递配置信息到目标机,目标机仅需要知道它自己的Ethernet硬件MAC地址,主机应安装DHCP包,配置DHCP服务器,一个配置样例列出如下:

subnet 192.168.0.0 netmask 255.255.0.0 {

option routers 192.168.1.1;

option subnet-mask 255.255.0.0;

option domain-name "local.net";

option domain-name-servers ns.local.net;

host trgt { hardware ethernet 00:30:BF:01:02:D0; #目标板MAC地址

fixed-address 192.168.100.6; #目标板固定IP地址

option root-path "/opt/eldk-4.2/ppc_4xx";

option host-name "canyonlands"; #目标板的主机名

next-server 192.168.1.1;

filename "/tftpboot/canyonlands/uImage ";

}

}

用上面的配置,DHCP服务器将回答来自目标板的请求,给出下面的信息:

  • 目标板位于子网192.168.0.0,子网掩码为255.255.0.0。
  • 目标板的主机名为Canyonlands,IP地址为192.168.100.0。
  • IP地址为192.168.1.1的主机给目标板提供启动映像(boot image),当目标板根文件系统通过NFS挂接在主机上,它还提供NFS服务器功能。
  • 主机提供文件/tftpboot/canyonlands/uImage作为目标板的启动映像。
  • 目标板可以挂接目录/opt/eldk-4.2/ppc_4xx在NFS服务作为根文件系统。

(3)配置NFS服务器

通过NFS,主机和目标板可以共享文件,NFS服务器对外暴露的目录可以挂接作目标板的根文件系统。主机应安装NFS服务器,并开启NFS服务。

NFS服务器的配置样例列出如下:

$cat /etc/exports

/opt/eldk-4.2/ppc_4xx 192.168.0.0/255.255.0.0(rw,no_root_squash,sync)

上述语句含义为暴露目录/opt/eldk-4.2/ppc_4xx,在子网192.168.0.0上的所有主机都可以读写操作该目录。

编译安装Linux内核

ELDK源代码包括了Linux内核,用户可以编译安装内核,方法如下:

bash$ cd /opt/eldk/usr/src/linux-2.6-denx

bash$ make mrproper

bash$ make ARCH=powerpc CROSS_COMPILE=ppc_4xx- canyonlands_defconfig #用户设备目标板的配置

bash$ make ARCH=powerpc CROSS_COMPILE=ppc_4xx- INSTALL_MOD_PATH=/opt/eldk-4.2/ppc_4xx modules_install

bash$ cp arch/powerpc/boot/uImage /tftpboot/uImage

启动嵌入式Linux

(1)启动内核

当Linux内核和扁平设备树块(Flattened Device Tree Blob,FDT blob)下载到目标板的系统内存(如:RAM、ROM、flash等)时,用户可用bootm命令启动系统。假设内核映像存放在地址0xFC000000处,FDT blob存放在地址0xFC1E0000处,则启动映像文件的命令列出如下:

=>bootm FC000000 – FC1E0000

(2)编译blob

Linux内核运行时,期望知道它运行的硬件信息,这些硬件信息按《开放固件规范》以设备树的形式描述,由于象U-Boot这样的BootLoader没有实现开放固件API(Open Firmware API),硬件信息将以扁平设备树的二进制形式传递给内核,这种二进制形式称为FDT blob或简称blob。在设备树信息文件(如:canyonlands.dts)由dtc编译器编译成二进制文件,ELDK包括了dtc编译器,编译方法列出如下:

dtc -b 0 -V 17 -p 0x1000 -I dts -O dtb -f arch/powerpc/boot/dts/canyonlands.dts > /tftpboot/canyon

编译完后,可使用命令tftp将blob传输到目标机的内存中,接着,系统启动bootm将blob中的硬件信息传递给内核。

U-Boot还提供命令fdt修改blob。

(3)传递内核参数

用户可以传递附加信息(如:根设备或网络配置)给内核。U-Boot通过环境变量bootargs实现该功能。该变量的内容作为启动参数(或命令行参数)自动传递给内核,这样,可以让同一个内核映像有更多的配置,例如:仅改变变量bootargs的内容,就可以让同一个内核映像与initrd内存盘( ramdisk)映像一起启动,或者与在NFS之上的根文件系统一起启动。

例如:内核映像地址在0x200000,作为根文件系统的initrd ramdisk映像地址在0x400000,FDT blob地址在0x600000,则启动命令列出如下:

=> setenv bootargs root=/dev/ram rw

=> bootm 200000 400000 600000

假设NFS服务器地址为192.168.1.1,输出目录/opt/eldk-4.2/ppc_4xx作为目标板的根文件系统,目标板的地址为192.168.100.6,目标板主机名为canyonlands,子网掩码为255.255.255.0。现由在NFS上的根文件系统启动同样内核,则启动命令列出如下:

=> setenv bootargs root=/dev/nfs rw nfsroot=192.168.1.1:/opt/eldk-4.2/ppc_4xx ip=192.168.100.6:192.168.1.1

=> bootm 200000 - 600000

内核启动后,可用下面命令查看启动的命令行:

$ cat /proc/cmdline

用户还可用U-Boot环境变量存放所有需要的配置参数,方法如下:

=> setenv ipaddr 192.168.100.6

=> setenv serverip 192.168.1.1

=> setenv netmask 255.255.0.0

=> setenv hostname canyonlands

=> setenv rootpath /opt/eldk-4.2/ppc_4xx

=> saveenv

用户可用这些变量构建传递到内核的启动参数,方法如下:

=> setenv nfsargs 'root=/dev/nfs rw nfsroot=${serverip}:${rootpath}'

下面可以定义bootargs环境变量,并一步步构建启动参数:

=> setenv ramargs setenv bootargs root=/dev/ram rw

=> setenv nfsargs 'setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${rootpath}'

=> setenv addip 'setenv bootargs ${bootargs} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::=> setenv ram_root 'run ramargs addip;bootm ${kernel_addr} ${ramdisk_addr} ${fdt_addr} '

=> setenv nfs_root 'run nfsargs addip;bootm ${kernel_addr} - ${fdt_addr} '

上面定义了ram_root和nfs_root两个变量,分别用于从ramdisk映像的根文件系统和NFS上的根文件系统启动。变量通过run命令执行。

(4)用Ramdisk映像单独操作

当应用程序开发完成时,用户希望独立运行嵌入式系统,而不依赖外部系统,如:NFS文件系统,目标板不再通过NFS挂接根文件系统,而是使用压缩的ramdisk映像,该ramdisk映像存储在flash中,在系统启动时装载到RAM中。

装载ramdisk映像到RAM,并将它写入到flash的方法如下:

=> setenv ramdisk_addr FC200000

=>

=> setenv ram_ws 100000

=>

=> prot off FC200000 FC3FFFFF

Un-Protected 16 sectors

=>

=> era FC200000 FC3FFFFF

................ done

Erased 16 sectors

=>

=> tftp 100000 /tftpboot/canyonlands/uRamdisk

=> imi 100000

## Checking Image at 00100000 ...

=>

=> cp.b ${ram_ws} ${ramdisk_addr} ${filesize}

Copy to Flash... done

=>

=> imi ${ramdisk_addr}

=>

=> saveenv

Saving Environment to Flash...

为了告诉Linux内核使用ramdisk映像作为根文件系统,用户必须修改传递给内核的命令行参数,ramdisk映像地址作为命令bootm的第二个参数,第一个参数为内核映像的内存地址,第三个参数是FDT blob的内存地址。

Flash文件系统

(1)MTD

所有可用的Flash文件系统都基于内存技术设备(Memory Technology Devices,MTD)层,因此,用户配置内核时必须打开flash文件系统支持,配置结果如下:

CONFIG_MTD=y

CONFIG_MTD_PARTITIONS=y

CONFIG_MTD_CHAR=y

CONFIG_MTD_BLOCK=y

CONFIG_MTD_CFI=y

CONFIG_MTD_GEN_PROBE=y

CONFIG_MTD_CFI_AMDSTD=y

CONFIG_MTD_ROM=y

CONFIG_MTD_canyonlands=y

目标板flash设备的分区设计由MTD映射例程(源代码在drivers/mtd/maps/)定义,canyonlands的分区设计列出如下:

-bash-3.2# cat /proc/mtd

dev: size erasesize name

mtd0: 001e0000 00020000 "kernel"

mtd1: 00020000 00020000 "dtb"

mtd2: 00200000 00020000 "root"

mtd3: 03b60000 00020000 "user"

mtd4: 00040000 00020000 "env"

mtd5: 00060000 00020000 "u-boot"

mtd6: 02000000 00004000 "NAND 32MiB 3,3V 8-bit"

mtd7: 00060000 00004000 "u-boot"

mtd8: 00008000 00004000 "env"

mtd9: 01f98000 00004000 "content"

用户还可以运行一些基本的测试,验证flash驱动程序例程和分区,方法如下:

-bash-3.2# xxd /dev/mtd3 | head -4

0000000: 5468 7520 4a61 6e20 2031 2030 313a 3034 Thu Jan 1 01:04

0000010: 3a35 3120 4345 5420 3139 3730 0aff ffff :51 CET 1970....

0000020: ffff ffff ffff ffff ffff ffff ffff ffff ................

0000030: ffff ffff ffff ffff ffff ffff ffff ffff ................

在MTD设备16进制转储中,用户可以鉴别一些字符串,验证U-Boot环境、一个Linux内核、ramdisk映像或一个空的分区。如:上面的转储显示表明该分区是空的,用户可以写数据到该分区,方法如下:

-bash-3.2# date > /dev/mtd3

-bash-3.2#

-bash-3.2# head -1 /dev/mtd3

Thu Jan 1 01:04:51 CET 1970

-bash-3.2#

-bash-3.2# date > /dev/mtd3

上述命令中,第一次写是正确的,第二次写将得到一个错误,因为flash内存必须先擦除,再进行写操作。

用户可使用Linux命令eraseall擦除整个MTD分区,再进行写操作,方法如下:

-bash-3.2# flash_eraseall /dev/mtd3

-bash-3.2#

-bash-3.2# date > /dev/mtd3

-bash-3.2#

-bash-3.2# head -1 /dev/mtd3

Thu Jan 1 01:04:51 CET 1970

(2)JFFS2

JFFS(Journalling Flash File System)是嵌入式设备flash上最合适的文件系统,JFFS2为嵌入式系统闪存的使用而设计,它是一个日志结构的文件系统,这意味着它对掉电、系统崩溃或其他意外关闭等系统异常具有有效保护措施,如:仅丢失正在写的数据,重启动后,不需要任何的文件系统检查。

配置内核时,需要打开JFFS配置选项,配置结果列出如下:

CONFIG_JFFS_FS=y

CONFIG_JFFS_FS_VERBOSE=0

对flash的直接读写或擦除操作一般使用MTD层的字符设备接口/dev/mtd*,文件系统操作(如:mount)必须使用块设备接口/dev/mtdblock*。JFFS2还提供了透明的压缩功能,让flash可以存储更多的内容。擦除、挂接并查看分区的方法列出如下:

# eraseall /dev/mtd2 //擦除分区

Erased 4096 Kibyte @ 0 -- 100% complete.

# mount -t jffs2 /dev/mtdblock2 /mnt //挂接文件系统

# mount //查看分区挂接情况

/dev/root on / type nfs (rw,v2,rsize=4096,wsize=4096,hard,udp,nolock,addr=10.0.0.2)

proc on /proc type proc (rw)

devpts on /dev/pts type devpts (rw)

/dev/mtdblock2 on /mnt type jffs (rw)

# df //查看磁盘使用情况

Filesystem 1k-blocks Used Available Use% Mounted on

/dev/root 2087212 1232060 855152 60% /

/dev/mtdblock2 3584 0 3584 0% /mnt

可以用工具mkfs.jffs2创建JFFS2文件系统映像,下面的样例给flash分区创建了3MB的JFFS2映像文件image.jffs2,并移入目录/tmp/flashtools/中的数据,然后,擦除分区/dev/mtd4,并将映像image.jffs2拷贝到分区mtd4中。最后,挂接分区mtd4。这些操作的命令列出如下:

# mkfs.jffs2 --pad=3145728 --eraseblock=262144 \

--root=/tmp/flashtools/ --output image.jffs2

# eraseall /dev/mtd4

Erased 3072 Kibyte @ 0 -- 100% complete.

# dd if=image.jffs2 of=/dev/mtd4 bs=256k

12+0 records in

12+0 records out

# mount -t jffs2 /dev/mtdblock4 /mnt

# df /mnt

Filesystem 1k-blocks Used Available Use% Mounted on

/dev/mtdblock4 3072 2488 584 81% /mnt

(3)CramFS

用户有时希望文件系统是仅读的,可压缩仅读文件系统(Compressed ROM Filesystem) CramFs实现了文件系统仅读与压缩的功能。用工具mkcramfs创建可压缩仅读文件系统映像的方法列出如下:

$ mkdir /tmp/test

$ cp ... /tmp/test

$ du -sk /tmp/test

64 /tmp/test

$ mkcramfs /tmp/test test.cramfs.img

Super block: 76 bytes

erase

eraseall

mkfs.jffs

lock

unlock

Directory data: 176 bytes

-54.96% (-4784 bytes) erase

-55.46% (-5010 bytes) eraseall

-51.94% (-8863 bytes) mkfs.jffs

-58.76% (-4383 bytes) lock

-59.68% (-4215 bytes) unlock

Everything: 24 kilobytes

$ ls -l test.cramfs.img

-rw-r--r-- 1 wd users 24576 Nov 10 23:44 test.cramfs.img

由于CramFs映像是压缩的,因此,输入的目录数据为64kB,而映像文件为24kB大小。

写CramFs映像到flash分区的方法列出如下:

# cp test.cramfs.img /dev/mtd3

# mount -t cramfs /dev/mtdblock3 /mnt

# mount

/dev/root on / type nfs (rw,v2,rsize=4096,wsize=4096,hard,udp,nolock,addr=10.0.0.2)

proc on /proc type proc (rw)

devpts on /dev/pts type devpts (rw)

/dev/mtdblock3 on /mnt type cramfs (rw)

# ls -l /mnt

total 54

-rwxr-xr-x 1 wd users 8704 Jan 9 16:32 erase

-rwxr-xr-x 1 wd users 9034 Jan 1 01:00 eraseall

-rwxr-xr-x 1 wd users 7459 Jan 1 01:00 lock

-rwxr-xr-x 1 wd users 17063 Jan 1 01:00 mkfs.jffs

根文件系统的设计与编译

嵌入式系统的根文件系统(Root File System)设计包括存放的内容、文件系统类型、在哪里存放文件系统和如何启动它。

根文件系统通常存放在目标板的闪存(flash memory)中或CF、SD、MMC卡等中,可以使用像ELDK这样的Linux发布版本创建根文件系统的内容。

下面使用来自ELDK的SELF(Simple Embedded Linux Framework)映像创建根文件系统。

在ELDK软件包中,各种平台构架的SELF映像文件在目录/opt/eldk/<architecture>/images/下,文件名为ramdisk_image.gz,是个压缩的ramdisk映像。

由SELF创建根文件系统tar包的方法列出如下:

//解压缩ramdisk映像

bash$ gzip -d -c -v /opt/eldk/ppc_8xx/images/ramdisk_image.gz >/tmp/ramdisk_image

/opt/eldk/ppc_8xx/images/ramdisk_image.gz: 61.4%

//下面的操作需要root权限

//挂接ramdisk映像

bash# mount -o loop /tmp/ramdisk_image /mnt/tmp

//创建tar包,为了避免需要root权限,这里不包括在tar包中的设备(device)文件

bash# cd /mnt/tmp

bash# tar -zc --exclude='dev/*' -f /tmp/rootfs.tar.gz *

//需要时,为CramFs创建一个单独的tar包,用来包括设备条目

bash# tar -zcf /tmp/devices.tar.gz dev/

bash# cd /tmp

//卸载ramdisk映像

bash# umount /mnt/tmp

(1)根文件系统在ramdisk上

ramdisk常用于存放嵌入式系统的根文件系统,但很少情况将ramdisk映像作为嵌入式系统的根文件系统解决方案。

根文件系统包括在ramdisk中的优点是:编译简单;基于RAM,速度快;Linux内核支持完美;容易使用,甚至可将带有Linux内核的ramdisk放在一个映像文件中;根文件系统是可写的;每次重启动郈的初始状态容易恢复。 缺点是:每次必须将整个文件系统装入RAM,造成启动慢,需要附加存储器存放可写的永久数据。

在ramdisk映像中,根文件系统常用ext2文件系统类型,创建在ramdisk上根文件系统的方法说明如下:

  • 通过解开tar包创建目标板根文件系统内容的目录树

$ mkdir rootfs

$ cd rootfs

$ tar zxf /tmp/rootfs.tar.gz

  • 使用工具genext2fs创建ramdisk映像,可用设备表文本文件定义在产生的文件系统映像中应创建的设备,这意味着需要root权限。设备表rootfs_devices.tab部分内容列出如表2所示。

表2 设备表文件rootfs_devices.tab部分内容

name type mode uid gid major minor start inc count
/dev d 755 0 0 - - - - -
/dev/console c 640 0 0 5 1 - - -
/dev/fb0 c 640 0 0 29 0 - - -

使用工具genext2fs 创建文件系统映像的方法如下:

$ ROOTFS_DIR=rootfs # 根文件系统内容目录

$ ROOTFS_SIZE=3700 # 文件系统映像的大小

$ ROOTFS_FREE=100 # 期望的空闲空间

$ ROOTFS_INODES=380 # 节点(inode)数

$ ROOTFS_DEVICES=rootfs_devices.tab # 设备描述表文件

$ ROOTFS_IMAGE=ramdisk.img # 产生的文件系统映像

$ genext2fs -U \

-d ${ROOTFS_DIR} \

-D ${ROOTFS_DEVICES} \

-b ${ROOTFS_SIZE} \

-r ${ROOTFS_FREE} \

-i ${ROOTFS_INODES} \

${ROOTFS_IMAGE}

  • 压缩文件系统映像,方法如下:

$ gzip -v9 ramdisk.img

rootfs.img: 55.6% -- replaced with ramdisk.img.gz

  • 创建U-Boot使用的根文件系统映像uRamdisk,方法如下:

$ mkimage -T ramdisk -C gzip -n 'Test Ramdisk Image' \

> -d ramdisk.img.gz uRamdisk

Image Name: Test Ramdisk Image

Created: Sun Jun 12 16:58:06 2005

Image Type: PowerPC Linux RAMDisk Image (gzip compressed)

Data Size: 1618547 Bytes = 1580.61 kB = 1.54 MB

Load Address: 0x00000000

Entry Point: 0x00000000

We now have a root file system image uRamdisk that can be used with U-Boot.

(2)根文件系统在JFFS2文件系统上

在嵌入式系统中,JFFS2作为根文件系统的优点是:文件系统使用压缩,可有效利用闪存空间;它是日志结构的文件系统,对无序的关机是强壮的;它是可写闪存文件系统。缺点是:挂接时间长;由于压缩/解压缩,造成读写操作时间长;垃圾回收线程可在任何时间启动,消耗CPU时间,阻塞对文件系统的访问。

尽管有上面一些不利因素,但基于JFFS2 的根文件系统容易编译,可有效利用可用的资源,并能可靠运行。

创建基于JFFS2的根文件系统的方法如下:

  • 通过解开tar包创建目标板根文件系统的内容,方法如下:

$ mkdir rootfs

$ cd rootfs

$ tar zxf /tmp/rootfs.tar.gz

  • 使用工具mkfs.jffs2创建一个JFFSW文件系统映像,方法如下:

$ ROOTFS_DIR=rootfs # 根文件系统内容目录

$ ROOTFS_EBSIZE=0x20000 # 闪存的擦除块大小

$ ROOTFS_ENDIAN=b # 目标板系统是大端字节序

$ ROOTFS_DEVICES=rootfs_devices.tab # 设备描述表文件

$ ROOTFS_IMAGE=jffs2.img # 产生的文件系统映像

$ mkfs.jffs2 -U \

-d ${ROOTFS_DIR} \

-D ${ROOTFS_DEVICES} \

-${ROOTFS_ENDIAN} \

-e ${ROOTFS_EBSIZE} \

-o ${ROOTFS_IMAGE}

mkfs.jffs2: skipping device_table entry '/dev': no parent directory!

注意:如果写JFFSW文件系统到NAND flash设备中时,不需要加"-n"或"选项,--no-cleanmarkers",因为不需要清除的标识。

启动内核时,终端显示缺省的分区映射表,列出如下:

0x00000000-0x00040000 : "u-boot"

0x00040000-0x00100000 : "kernel"

0x00100000-0x00200000 : "user"

0x00200000-0x00400000 : "initrd"

0x00400000-0x00600000 : "cramfs"

0x00600000-0x00800000 : "jffs"

0x00400000-0x00800000 : "big_fs"

使用U-Boot装载和存储JFFS2映像到最后一个分区,并建立Linux启动参数,将该映像作为根设备,方法如下:

  • 擦除flash

=> era 40400000 407FFFFF

................. done

Erased 35 sectors

  • 下载JFFS2映像

=> tftp 100000 /tftpboot/TQM860L/jffs2.img

Using FEC ETHERNET device

TFTP from server 192.168.3.1; our IP address is 192.168.3.80

Filename '/tftpboot/TQM860L/jffs2.img'.

Load address: 0x100000

Loading: #################################################################

done

Bytes transferred = 2033888 (1f08e0 hex)

  • 拷贝映像到flash

=> cp.b 100000 40400000 ${filesize}

Copy to Flash... done

  • 建立使用flash分区6作为根设备的启动参数

=> setenv mtd_args setenv bootargs root=/dev/mtdblock6 rw rootfstype=jffs2

=> printenv addip

addip=setenv bootargs ${bootargs} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}:${=> setenv flash_mtd 'run mtd_args addip;bootm ${kernel_addr}'

=> run flash_mtd

Using FEC ETHERNET device

TFTP from server 192.168.3.1; our IP address is 192.168.3.80

Filename '/tftpboot/TQM860L/uImage'.

Load address: 0x200000

Loading: #################################################################

done

Bytes transferred = 719233 (af981 hex)

## Booting image at 40040000 ...

(3)根文件系统在cramfs文件系统上

cramfs是压缩的仅读文件系统,作为根文件系统的优点是:文件系统是压缩的,可有效利用闪存空间;允许快速启动,因为仅装载使用的文件,且仅解压缩。缺点是:仅能替换整个映像文件而非单个文件;需要附加的存储空间给可写的永久数据;工具mkcramfs不支持设备表。

创建基于cramfs的根文件系统的方法列出如下:

  • 通过解开tar包创建目标板根文件系统的内容,方法如下:

$ mkdir rootfs

$ cd rootfs

$ tar zxf /tmp/rootfs.tar.gz

  • 创建需要的设备文件,这里通过解开仅含有设备文件条目的tar包来实现,方法列出如下:

# cd rootfs

  • # tar -zxf /tmp/devices.tar.gz
  • 许多工具需要一些存储地方,这里提供了一个小的可写文件系统。临时文件系统tmpfs是一个选择,为了创建tmpfs文件系统,需要将下面的行加入到脚本/etc/rc.sh中:

# 挂接tmpfs,是因为根文件系统仅读

/bin/mount -t tmpfs -o size=2M tmpfs /tmpfs

  • 一些工具需要对某些设备节点有写权限,如:改变属主和权限,可动态创建设备节点,如:/dev/log。这些设备文件需要放在一个可写的文件系统中,仅读的根文件系统用符号链接指向可读设备到新的位置(即在可读写文件系统中的位置),对应符号链接位置部分列出如下:

dev/ptyp0 € /tmpfs/dev/ptyp0 dev/ttyp0 € /tmpfs/dev/ttyp0

dev/ptyp1 € /tmpfs/dev/ptyp1 dev/ttyp1 € /tmpfs/dev/ttyp1

dev/ptyp2 € /tmpfs/dev/ptyp2 dev/ttyp2 € /tmpfs/dev/ttyp2

  • 为了放置相应的目录与设备文件在tmpfs文件系统,放置下面的代码在/etc/rc.sh脚本中:

mkdir -p /tmpfs/tmp /tmpfs/dev \

/tmpfs/var/lib/dhcp /tmpfs/var/lock /tmpfs/var/run

while read name minor

do

mknod /tmpfs/dev/ptyp$name c 2 $minor

mknod /tmpfs/dev/ttyp$name c 3 $minor

done <<__EOD__

0 0

1 1

2 2

……

e 14

f 15

__EOD__

chmod 0666 /tmpfs/dev/*

  • 使用工具mkcramfs创建cramfs文件系统映像,方法如下:

$ ROOTFS_DIR=rootfs # 带有根文件系统内容的目录

$ ROOTFS_ENDIAN="-r" # 目标板系统有相反的字符序列

$ ROOTFS_IMAGE=cramfs.img # 产生的文件系统映像

PATH=/opt/eldk/usr/bin:$PATH

mkcramfs ${ROOTFS_ENDIAN} ${DEVICES} ${ROOTFS_DIR} ${ROOTFS_IMAGE}

Swapping filesystem endian-ness

bin

dev

etc

...

-48.78% (-86348 bytes) in.ftpd

-46.02% (-16280 bytes) in.telnetd

-45.31% (-74444 bytes) xinetd

Everything: 1864 kilobytes

Super block: 76 bytes

CRC: c166be6d

warning: gids truncated to 8 bits. (This may be a security concern.)

  • 可以使用前面JFFS2文件系统的方法建立启动参数,只是将启动参数设置到"rootfstype=cramfs "。

(4)根文件系统在仅读的ext2文件系统上

在闪存上使用标准的ext2文件系统不合适,因为它不含有任何类型的wear leveling,在闪存上的可写文件系统需要有wear leveling保护,需要对无序异常系统关闭有强壮的保护。使用仅读的ext2文件系统将可不需要这些保护。

挂接ext2文件系统时可挂接成仅读的,在某些情况下仅读的ext2文件系统很有用,如:根文件系统在闪存卡上。

仅读ext2文件系统优点是速度快,占用较少的RAM。缺省是由于没有压缩而占用较多的闪存。

与cramfs文件系统作为根文件系统一样,仅读ext2文件系统作为根文件系统,还需要使用tmpfs这样的可写文件系统,存放可写的文件数据。

仅读ext2文件系统作为根文件系统的操作步骤与cramfs文件系统一样,创建方法列出如下:

  • 通过解开tar包创建目标板根文件系统的内容,方法如下:

$ mkdir rootfs

$ cd rootfs

$ tar zxf /tmp/rootfs.tar.gz

  • 像cramfs根文件系统一样,这里使用tmpfs作为可写的文件系统,并添加下面的行在/etc/rc.sh脚本中:

# 因为根文件系统是仅读,所以挂接tmpfs文件系统作为可写的文件系统

/bin/mount -t tmpfs -o size=2M tmpfs /tmpfs

  • 创建设备文件的符号链接放在可写的文件系统中,符号链接从仅读文件系统到可写文件系统,符号链接的目录如下:

dev/ptyp0 € /tmpfs/dev/ptyp0 dev/ttyp0 € /tmpfs/dev/ttyp0

dev/ptyp1 € /tmpfs/dev/ptyp1 dev/ttyp1 € /tmpfs/dev/ttyp1

  • 放置相应目录和设备文件在tmpfs文件系统中,将下面的代码放在/etc/rc.sh脚本中:

mkdir -p /tmpfs/tmp /tmpfs/dev \

/tmpfs/var/lib/dhcp /tmpfs/var/lock /tmpfs/var/run

while read name minor

do

mknod /tmpfs/dev/ptyp$name c 2 $minor

mknod /tmpfs/dev/ttyp$name c 3 $minor

done <<__EOD__

0 0

1 1

2 2

3 3

4 4

5 5

6 6

7 7

8 8

9 9

a 10

b 11

c 12

d 13

e 14

f 15

__EOD__

chmod 0666 /tmpfs/dev/*

  • 使用genext2fs工具创建ext2文件系统映像,方法如下:

$ ROOTFS_DIR=rootfs # 根文件系统内容的目录

$ ROOTFS_SIZE=3700 # 文件系统映像的大小

$ ROOTFS_FREE=100 # 期望的空闲空间

$ ROOTFS_INODES=380 # 节点号

$ ROOTFS_DEVICES=rootfs_devices.tab # 设备描述文件

$ ROOTFS_IMAGE=ext2.img # 产生的文件系统映像

$ genext2fs -U \

-d ${ROOTFS_DIR} \

-D ${ROOTFS_DEVICES} \

-b ${ROOTFS_SIZE} \

-r ${ROOTFS_FREE} \

-i ${ROOTFS_INODES} \

${ROOTFS_IMAGE}

  • 可以使用前面JFFS2文件系统的方法建立启动参数,只是将启动参数设置到"rootfstype=ext2 "。

(5)根文件系统在闪存卡上

可在闪存卡(如:CF、SD、MMC等)上使用仅读ext2文件系统 作为根文件系统,用于启动系统。方法如下:

  • 将用工具genext2fs制作的ext2文件系统映像文件ext2.img拷贝到CF卡上,下面列出使用U-Boot拷贝映像文件到CF卡上的方法:

=> tftp 100000 /tftpboot/TQM860L/ext2.img

Using FEC ETHERNET device

TFTP from server 192.168.3.1; our IP address is 192.168.3.80

Filename '/tftpboot/TQM860L/ext2.img'.

Load address: 0x100000

Loading: #################################################################

done

Bytes transferred = 3788800 (39d000 hex)

=> ide part

Partition Map for IDE device 0 -- Partition Type: DOS

Partition Start Sector Num Sectors Type

1 32 500704 6

=> ide write 100000 20 1ce8 #装载地址,第一个分区的起始扇区,扇区数

IDE write: device 0 block # 32, count 7400 ... 7400 blocks written: OK

命令ide write用16进制参数,每个扇区数为512字节,0x20为第一个分区的起始扇区,扇区数计算方法为:3788800/512 = 7400 = 0x1CE8 。

  • 设置启动参数,将CF的分区作为仅读根设备,方法如下:

=> setenv cf_args setenv bootargs root=/dev/hda1 ro

=> setenv flash_cf 'run cf_args addip;bootm ${kernel_addr} - ${fdt_addr}'

=> setenv bootcmd run flash_cf

(6)根文件系统在FAT文件系统的一个仅读文件中

在用户嵌入式设备上,用户可使用CF、MMC或SD卡等通用存储介质存储Linux内核和根文件系统。用户可能期望Internet发布软件更新:客户可以从用户的网站下载更新文件,或者将映像文件通过Email发送给客户,客户可将文件拷贝到CF卡上,该CF卡仅有一个FAT或VFAT文件系统,不能直接用作Linux根文件系统,这需要进行一些处理,下面是其中的一种解决方法。

用户的发布软件由两个文件组成,第一个文件是Linux内核带有一个很小的ramdisk映像(U-Boot使用多个文件映像),U-boot能从FAT或VFAT文件系统装载和启动这些映像文件。第二个文件是根文件系统。为了方便和速度,这里使用ext2文件系统作为根文件系统。当Linux启动时,启动刚开始时,它使用附加的ramdisk作为根文件系统。在ramdisk中的程序会以仅读的方式挂接FAT或VFAT文件系统。接着,可使用loop设备将根文件系统与块设备联系起来。最后使用pivot_root切换根文件系统到CF卡上的映像。详细步骤说明如下:

  • 用前面制作仅读ext2根文件系统的方法制作ext2.img映像文件。
  • 用前面制作ramdisk映像的方法制作初始ramdisk,需要加入空目录bin、dev、etc、lib、loopfs、mnt、proc和sysroot,加入文件bin/nash(脚本解释器)、linuxrc脚本和从sbin到bin的符号链接,列出如下:

drwxr-xr-x 2 wd users 4096 Apr 13 01:11 bin

-rwxr-xr-x 1 wd users 469512 Apr 11 22:47 bin/nash

drwxr-xr-x 2 wd users 4096 Apr 12 00:04 dev

drwxr-xr-x 2 wd users 4096 Apr 12 00:04 etc

drwxr-xr-x 2 wd users 4096 Apr 12 00:04 lib

-rwxr-xr-x 1 wd users 511 Apr 13 01:28 linuxrc

drwxr-xr-x 2 wd users 4096 Apr 12 00:04 loopfs

drwxr-xr-x 2 wd users 4096 Apr 12 00:09 mnt

drwxr-xr-x 2 wd users 4096 Apr 12 00:04 proc

lrwxrwxrwx 1 wd users 3 Jun 12 18:54 sbin -> bin

drwxr-xr-x 2 wd users 4096 Apr 12 00:04 sysroot

  • 还需要用于创建初始ramdisk的小规模设备表,列出如表3。

表3 创建初始ramdisk的小规模设备表

name type mode uid gid major minor start inc count
/dev d 755 0 0 - - - - -
/dev/console c 640 0 0 5 1 - - -
/dev/hda b 640 0 0 3 0 - - -
/dev/hda b 640 0 0 3 1 1 1 8
/dev/loop b 640 0 0 7 0 0 1 4
/dev/null c 640 0 0 1 3 - - -
/dev/ram b 640 0 0 1 0 0 1 2
/dev/ram b 640 0 0 1 1 - - -
/dev/tty c 640 0 0 4 0 0 1 4
/dev/tty c 640 0 0 5 0 - - -
/dev/ttyS c 640 0 0 4 64 0 1 4
/dev/zero c 640 0 0 1 5 - - -
  • 创建初始ramdisk,方法如下:

$ INITRD_DIR=initrd

$ INITRD_SIZE=490

$ INITRD_FREE=0

$ INITRD_INODES=54

$ INITRD_DEVICES=initrd_devices.tab

$ INITRD_IMAGE=initrd.img

$ genext2fs -U \

-d ${INITRD_DIR} \

-D ${INITRD_DEVICES} \

-b ${INITRD_SIZE} \

-r ${INITRD_FREE} \

-i ${INITRD_INODES} \

${INITRD_IMAGE}

$ gzip -v9 ${INITRD_IMAGE}

结果产生了一个233KB的压缩的ramdisk映像。

  • 假定用户已编译了Linux内核映像,现可以用mkimage编译一个U-Boot多文件映像,将Linux内核与初始的ramdisk组合起来,方法如下:

$ LINUX_KERNEL=linuxppc_2_4_devel/arch/ppc/boot/images/vmlinux.gz

$ mkimage -A ppc -O Linux -T multi -C gzip \

> -n 'Linux with Pivot Root Helper' \

> -d ${LINUX_KERNEL}:${INITRD_IMAGE}.gz linux.img

Image Name: Linux with Pivot Root Helper

Created: Mon Jun 13 01:48:11 2005

Image Type: PowerPC Linux Multi-File Image (gzip compressed)

Data Size: 1020665 Bytes = 996.74 kB = 0.97 MB

Load Address: 0x00000000

Entry Point: 0x00000000

Contents:

Image 0: 782219 Bytes = 763 kB = 0 MB

Image 1: 238433 Bytes = 232 kB = 0 MB

新创建的linux.img是必须拷贝到CF卡中的第二个映像文件。

  • linuxrc脚本用于完成根文件系统的设置,其内容列出如下:

#!/bin/nash #用nash脚本解释器解析此文件

echo Mounting /proc filesystem

mount -t proc /proc /proc #挂接/proc文件系统,用于查询一些需要信息

echo Creating block devices

mkdevices /dev #为列在/proc/partitions中的所有分区创建块设备条目

echo Creating root device

mkrootdev /dev/root #为新根文件系统创建块设备

echo 0x0100 > /proc/sys/kernel/real-root-dev

echo Mounting flash card

mount -o noatime -t vfat /dev/hda1 /mnt #挂接CF卡,假定它仅有一个分区在/dev/hda1上

echo losetup for filesystem image

#在CF卡上的根文件系统映像名为rootfs.img,使用用loop设备挂接

losetup /dev/loop0 /mnt/rootfs.img

echo Mounting root filesystem image

mount -o defaults --ro -t ext2 /dev/loop0 /sysroot

echo Running pivot_root

pivot_root /sysroot /sysroot/initrd #将/sysroot作为新的根文件系统

上面的脚本有点小缺陷:由于CF卡挂接在ramdisk中一个目录下,以便能够从根文件系统映像访问。这意味着用户不能卸载(umount)CF卡,阻止用户释放初始ramdisk的空间,将永久失去450kb左右的RAM(用于ramdisk)。

  • 首先,在目标板上拷贝2个映像文件到CF卡,方法如下:

bash-2.05b# fdisk -l /dev/hda

Disk /dev/hda: 256 MB, 256376832 bytes

16 heads, 32 sectors/track, 978 cylinders

Units = cylinders of 512 * 512 = 262144 bytes

Device Boot Start End Blocks Id System

/dev/hda1 * 1 978 250352 6 FAT16

bash-2.05b# mkfs.vfat /dev/hda1

mkfs.vfat 2.8 (28 Feb 2001)

bash-2.05b# mount -t vfat /dev/hda1 /mnt

bash-2.05b# cp -v linux.img rootfs.img /mnt/

`linux.img' -> `/mnt/linux.img'

`rootfs.img' -> `/mnt/rootfs.img'

bash-2.05b# ls -l /mnt

total 4700

-rwxr--r-- 1 root root 1020729 Jun 14 05:36 linux.img

-rwxr--r-- 1 root root 3788800 Jun 14 05:36 rootfs.img

bash-2.05b# umount /mnt

  • 现准备用U-Boot从CF卡装载uMulti文件(将Linux内核和初始ramdisk组合在一起),并启动它,方法如下:

=> setenv fat_args setenv bootargs rw

=> setenv fat_boot 'run fat_args addip;fatload ide 0:1 200000 linux.img;bootm'

=> setenv bootcmd run fat_boot

覆盖层文件系统

覆盖层文件系统(Overlay File System)mini_fo是一个虚拟内核文件系统,作为附加层插入在VFS(虚拟文件系统)与本地文件系统之间,能让仅读文件系统成为可写的。它通过重定向修改操作到可写的存储目录来实现的,并保留初始数据在仅读的基本目录中。在读操作时,操作系统整合修改的和初始的数据,让读操作读出最新的数据。这些对用户都是透明的,用户访问数据就像在可读写文件系统中一样进行操作。

在嵌入式系统中,mini_fo覆盖根文件系统,它被挂接在常规根文件系统的顶部,允许用户透明地进行修改操作,并将修改定向到不同的目录位置。这样,仅读的根文件系统变成可读写操作的文件系统。

嵌入式系统的根文件系统通常是仅读的,如:cramfs或仅读的ext2。但有时需要修改根文件系统,如:应用较小的软件更新而不必烧录整个根文件系统映像到flash,开发时需要对根文件系统进行频繁修改。这时,可通过挂接在根文件系统上面的mini_fo,将修改操作定向到可写的文件系统,如:在闪存上的JFFS2文件系统,甚至于在开发时,还可定向到外部的硬盘。

当根文件系统需要非永久性修改时,常使用ramdisk,不好的方面是:ramdisk使用大量的RAM,并且在启动时将ramdisk映像拷贝到RAM花费时间较多。作为替换,可使用覆盖层文件系统覆盖在仅读文件系统上,用tmpfs文件系统作为可写的文件系统,仅将修改的文件存放在RAM中,这样,可节省启动时间和RAM。

使用覆盖层文件系统mini_fo可以很容易复位到出厂时的缺省配置,恢复缺省配置仅需要删除修改操作所存储的目录内容。

通常在根文件系统上覆盖mini_fo文件系统,用于两种 场景:启动单个应用程序和启动整个系统,分别说明如下:

(1)在改变根目录的覆盖文件系统中启动单个应用程序

假定"/"是仅读的根文件系统,/dev/mtdblock5含有一个小的JFFS2闪存分区,用于存放应用程序"/usr/bin/autopilot"的修改。

# mount -t jffs2 /dev/mtdblock5 /tmp/sto //挂接用于存放数据的文件系统

# insmod mini_fo.o //插入文件系统mini_fo的内核模块

//基目录为"/",存储目录为"/tmp/sto/",挂接点为"/mnt/mini_fo/"

# mount -t mini_fo -o base=/,sto=/tmp/sto/ / /mnt/mini_fo/

# cd /mnt/mini_fo/

# chroot . /usr/bin/autopilot //将当前目录作为应用程序autopilot的根目录

(2)在改变根目录的覆盖文件系统中启动整个系统

在根文件系统已经挂接之后,在init启动之前,需要挂接mini_fo文件系统,这样做的最好办法是使用脚本,将mini_fo文件系统挂接在根文件系统之上,接着在改变了根目录的覆盖文件系统中启动init。假定脚本名为overlay_init,存储在/sbin中。脚本的内容列出如下:

#!/bin/bash

#

# mount mini_fo overlay file system and execute init

#

# make sure these exist in the read-only file system

STORAGE=/tmp/sto

MOUNT_POINT=/mnt/mini_fo/

# mount tmpfs as storage file system with a maximum size of 32MB

mount -t tmpfs -o rw,size=32M none $STORAGE

/sbin/modprobe mini_fo

mount -t mini_fo -o base=/,sto=$STORAGE / $MOUNT_POINT

exec /usr/sbin/chroot $MOUNT_POINT /sbin/init

echo "exec chroot failed, bad!"

exec /bin/sh

exit 1

在boot loader中,将"init"内核参数设置为"init=/sbin/overlay_init"。

如果需要覆盖文件系统访问最初的非覆盖的文件系统的根目录,可使用命令pivot_root。

调试

嵌入式系统同样使用GNU调试器gdb进行调试,gdb的图形前端常用ddd,嵌入式系统还用BDM/JTAG调试器工具(如:BDI2000)协助调试。

(1)U-Boot的调试

为了能在U-Boot运行时能更新它自己,U-Boot将它自己重定位到RAM。因此,U-Boot调试有重定位之前和重定位之后两种情况。下面分别进行说明

  • 在重定位之前的U-Boot调试

在重定位之前,可以使用ELF的地址,这时,使用BDI2000进行调试较简单,方法如下:

bash[0]$ ${CROSS_COMPILE}gdb u-boot

GNU gdb 5.1.1

(gdb) target remote bdi:2001

Remote debugging using bdi:2001

0xfffffffc in ' ' ()

(gdb) b cpu_init_f #设置断点,cpu_init_f是start.c调用第1个函数

Breakpoint 1 at 0xfffd3310: file cpu_init.c, line 136.

(gdb) c

Continuing.

Breakpoint 1, cpu_init_f () at cpu_init.c:136

136 asm volatile(" bl 0f" ::: "lr");

(gdb) s

137 asm volatile("0: mflr 3" ::: "r3");

(gdb)

138 asm volatile(" addi 4, 0, 14" ::: "r4");

(gdb)

  • 在重定位后的U-Boot调试

在重定位后的U-Boot调试需要知道U-Boot将它自己重定痊到RAM的地址,该地址通常为<MAXMEM> - CFG_MONITOR_LEN。例如:对于16MB RAM和CFG_MONITOR_LEN为192KB来说,重定位地址为0x1000000-0x30000=0xFD0000。这样,gdb不使用旧的符号表,而重装载带有新偏移的符号。修改符号表偏移及调试方法的简单例子列出如下:

(gdb) symbol-file

Discard symbol table from `/home/dzu/denx/cvs-trees/u-boot/u-boot' ' (y or n) y

No symbol file now.

(gdb) add-symbol-file u-boot 0xfd0000

add symbol table from file "u-boot" at

.text_addr = 0xfd0000

(y or n) y

Reading symbols from u-boot...done.

(gdb) b board_init_r #在新定位的RAM环境中运行的第一个例程

Breakpoint 2 at 0xfd99ac: file board.c, line 533.

(gdb) c

Continuing.

Breakpoint 2, board_init_r (id=0xfbb1f0, dest_addr=16495088) at board.c:533

533 {

(gdb)

(2)Linux内核调试

动态装载的设备驱动程序(内核模块)

  • 首先在Linux内核的根目录启动gdb,使用vmlinux内核映像作为文件进行调试方法如下:

bash$ cd <linux-root>

bash$ ${CROSS_COMPILE}gdb vmlinux

  • 粘附到目标板,并开始执行,方法如下:

(gdb) target remote bdi:2001

Remote debugging using bdi:2001

0x00000100 in ' ' ()

(gdb) c

Continuing.

现目标板应该正常启动Linux,接着,用户需要在目标板上装载内核模块,方法如下:

bash# insmod -m ex_sw.o

Sections: Size Address Align

.this 00000060 cf030000 2**2

.text 000002f4 cf030060 2**2

.rodata 00000134 cf030354 2**2

.data 00000000 cf030488 2**0

.sdata 0000000c cf030488 2**2

.kstrtab 00000085 cf030494 2**0

.bss 00000000 cf030519 2**0

.sbss 00000008 cf03051c 2**2

...

选项"-m"打印出各种代码重定位后的地址和数据段(如:.text,.data等)。

  • gdb需要这些地址以便知道所有符号定位的位置。下面中断gdb,装载模块的符号表,方法如下:

(gdb) ^C

Program received signal SIGSTOP, Stopped (signal).

...

(gdb) add-symbol-file <path-to-module-dir>/ex_sw.o 0xcf030060\

-s .rodata 0xcf030354\

-s .data 0xcf030488\

-s .sdata 0xcf030488\

-s .bss 0xcf030519\

-s .sbss 0xcf03051c

add symbol table from file "<path-to-module-dir>/ex_sw.o" at

.text_addr = 0xcf030060

.rodata_addr = 0xcf030354

.data_addr = 0xcf030488

.sdata_addr = 0xcf030488

.bss_addr = 0xcf030519

.sbss_addr = 0xcf03051c

(y or n) y

Reading symbols from <path-to-module-dir>/ex_sw.o...done.

  • 现用户可以列出模块的代码,设置断点或检查变量,方法如下:

(gdb) l fun

61 static RT_TASK *thread;

62

63 static int cpu_used[NR_RT_CPUS];

64

65 static void fun(int t)

66 {

67 unsigned int loops = LOOPS;

68 while(loops--) {

69 cpu_used[hard_cpu_id()]++;

70 rt_leds_set_mask(1,t);

(gdb)

(gdb) b ex_sw.c:69

Breakpoint 1 at 0xcf03007c: file ex_sw.c, line 69.

(gdb) c

Continuing.

Breakpoint 1, fun (t=1) at ex_sw.c:69

69 cpu_used[hard_cpu_id()]++;

(gdb) p ntasks

$1 = 16

(gdb) p stack_size

$2 = 3000

(3)远程调试应用程序

gdb的gdbserver允许应用使用"target remote"命令远程连接应用程序。在目标机器上,用户需要拷贝一份需要调试的程序,gdbserver并不需要程序的符号表,在主机上的gdb进行所有的符号处理。

在目标机上通过网络启动调试会话的方法如下:

$cd <directory-shared-with-host>

$gdbserver 192.168.1.1:12345 hello-copy

process hello-copy created;pid = 353

在主机上,用户调试程序,方法如下:

bash$ ${CROSS_COMPILE}gdb hello

...

(gdb) set solib-absolute-prefix /opt/eldk/$CROSS_COMPILE

(gdb) dir /opt/eldk/$CROSS_COMPILE

Source directories searched:

/opt/eldk/$CROSS_COMPILE:$cdir:$cwd

(gdb) target remote 192.168.1.99:12345

Remote debugging using 192.168.1.99:12345

0x30012748 in ' ' ()

...

(gdb) l

1 #include <stdio.h>

2

3 int main(int argc, char* argv[])

4 {

5 printf ("Hello world\n");

6 return 0;

7 }

(gdb) break 5

Breakpoint 1 at 0x10000498: file hello.c, line 5.

(gdb) continue

Continuing.

Breakpoint 1, main (argc=1, argv=0x7ffffbe4) at hello.c:5

5 printf ("Hello world\n");

(gdb) p argc

$1 = 1

(gdb) continue

Continuing.

Program exited normally.

如果目标程序连接了共享库,用户必须告诉gdb目标库定位的位置,这通过使用gdb命令"set solib-absolute-prefix"实现。如果省略了该命令,那么,gdb装载库的主机版本,程序调试因此而变得错乱。

,main函数的调用层次图,main函数分析如下:


main函数的调用层次图

int main(void)
{
	u32 blockSize = 0x01000000;
	int numRead = 0;
	char commandline[128];
	int i;
	int retval = 0;
	s32 waitTimeout;
	// 初始化串口波特率为11500 
	SerialInit(11500);
      //初始化定时器
	TimerInit();
 
	//得到内存的大小并作出块的标识
	get_memory_map();
       ……
      //把kernel和ramdisk 从flash装载到RAM 
	load("kernel");
 
	//在自动启动之前等待10秒
	for(i = 0; i < 10; i++) {
      //看1秒内有否按download键
		retval = download_key_pressed(1); 
		if(retval > 0)
			break;
	}
	//没有键按下,启动内核
	if(retval == 0) {
             //跳转到内核第一条指令去执行,不会再从函数返回
		boot_linux('\0' );
	}
   	//进入下载函数
      Download();
}

get_memory_map函数的功能是检测CPU给ram预留的地址空间中,哪些指到ram上,因为ram一般比预留的地址空间小。memory_area_struct结构描述了 RAM 地址空间中的一段连续地址空间使用情况,其成员used = 1则表明地址空间指到了 RAM 上。used = 0,则表明没有指到ram上。这个结构如下:

typedef struct memory_area_struct {
	u32 start; //开始地址
	u32 size; //内存块的字节数
	int used;
} memory_area_t;

  get_memory_map函数实现如下:

#define TEST_BLOCK_SIZE (1024 * 1024)
memory_area_t memory_map[NUM_MEM_AREAS];
void get_memory_map(void)
{
	u32 addr;
	int i;
 
	//初始化
	for(i = 0; i < NUM_MEM_AREAS; i++)
		memory_map[i].used = 0;
 
	//清0
	for(addr = MEMORY_START; addr < MEMORY_END; addr += TEST_BLOCK_SIZE)
		* (u32 *)addr = 0;
 
	//扫描内存块
	i = 0;
	for(addr = MEMORY_START; addr < MEMORY_END; addr += TEST_BLOCK_SIZE) {
	        //测试内存, 测试给出的地址处是否有可用的ram	
     if(testram(addr) == 0) {
			//ram可用
			if(* (u32 *)addr != 0) { //已有内容
				if(memory_map[i].used)
					i++;
 
				continue;
			}
 
			/* not an alias, write the current address */
			* (u32 *)addr = addr;
 
			//一个新页,即所在地址有ram
			if(memory_map[i].used == 0) {
				memory_map[i].start = addr;
				memory_map[i].len = TEST_BLOCK_SIZE;
				memory_map[i].used = 1;
			} else {
				memory_map[i].len += TEST_BLOCK_SIZE;
			}
		} else {
			//没有可用的ram
			if(memory_map[i].used == 1)
				i++;
		}//end_if_testram
	}//end_for
}

  load函数完成加载内核映像和根文件系统映像的拷贝操作。嵌入式 Linux 的内核一般都不操过 1MB,对应zImage文件,将内核映像拷贝到从KERNEL_START 开始的约1MB的内存中。

  根文件系统映像被拷贝到INITRD_START地址开始的内存中。如果用 Ramdisk 作为根文件系统映像,其解压后的大小一般是1MB。

  load函数列出如下:

void load(char *commandline)
{
	u32 *src = 0;
	u32 *dst = 0;
	int numWords;
      //比较commandline中是否含有相关的字符串
   //从flash装载kernel
	if(StrNCmp(commandline, "kernel", 6) == 0) {
		src = (u32 *)KERNEL_RAM_BASE;
		dst = (u32 *)KERNEL_START;
		numWords = KERNEL_LEN / 4;
 
	}
       //从flash中装载ramdisk 
       else if(StrNCmp(commandline, "ramdisk", 7) == 0) {
		src = (u32 *)RAMDISK_RAM_BASE;
		dst = (u32 *)INITRD_START;
		numWords = INITRD_LEN / 4;
 
	} 
      //完成从flash到ram的拷贝
	MemCpy(src, dst, numWords);
 
}

boot_linux函数设置内核的启动参数,并跳转到内核开始的地址KERNEL_RAM_BASE去执行内核。内核以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中:

//链表用ATAG_NONE节点结束
#define ATAG_NONE	0x00000000
struct tag_header {
	u32 size;//是字数为单位
	u32 tag;
};
……
struct tag {
	struct tag_header hdr;
	union {
		struct tag_core	core;
		struct tag_mem32	mem;
		struct tag_videotext  videotext;
		struct tag_ramdisk	ramdisk;
		struct tag_initrd	initrd;
		struct tag_serialnr	serialnr;
		struct tag_revision	revision;
		struct tag_videolfb	videolfb;
		struct tag_cmdline	cmdline;
 
		/*
		 * Acorn specific
		 */
		struct tag_acorn	acorn;
 
		/*
		 * DC21285 specific
		 */
		struct tag_memclk	memclk;
	} u;
};

在tag结构中,Boot Loade设置的常见启动参数有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。

boot_linux函数分析如下:

void boot_linux(char *commandline )
{
	void (*theKernel)(int zero, int arch) =
               (void (*)(int, int))KERNEL_RAM_BASE;
 
	setup_start_tag();
	setup_memory_tags();
	setup_commandline_tag(commandline);
  	setup_initrd_tag();
	setup_ramdisk_tag();
	setup_end_tag();
   //并跳转到内核开始的地址KERNEL_RAM_BASE去执行内核第一条指令
	theKernel(0, ARCH_NUMBER);
}
 
static void setup_start_tag(void)
{   // BOOT_PARAMS表示内核启动参数在内存中的起始地址
	params = (struct tag *)BOOT_PARAMS;
 
	params->hdr.tag = ATAG_CORE;
	params->hdr.size = sizeof(struct tag_core);
 
	params->u.core.flags = 0;
	params->u.core.pagesize = 0;
	params->u.core.rootdev = 0;
      //以当前标记的指针为参数,计算下一个标记的起始地址
	params = tag_next(params);
}
static void setup_memory_tags(void)
{
	int i;
      //每个有效的内存段对应一个ATAG_MEM参数标记
	for(i = 0; i < NUM_MEM_AREAS; i++) {
		if(memory_map[i].used) {
			params->hdr.tag = ATAG_MEM;
			params->hdr.size = sizeof(struct tag_mem32);
 
			params->u.mem.start = memory_map[i].start;
			params->u.mem.size = memory_map[i].len;
 
			params = tag_next(params);
		}
	}
}

Linux 内核在启动时可以以命令行参数的形式来接收信息,利用这一点我们可以向内核提供那些内核不能自己检测的硬件参数信息。比如,命令行参数字符串"console=ttyS0,115200n8"通知内核以 ttyS0 作为控制台,串口采用 "115200bps、无奇偶校验、8位数据位"的设置。下面是设置调用内核命令行参数字符串的示例代码:

static void setup_commandline_tag(char *commandline)
{
	char *p;
 
	/* eat leading white space */
	for(p = commandline; *p == ' '; p++);
 
	/* skip non-existent command lines so the kernel will still
         * use its default command line.
	 */
	if(*p == '\0')
		return;
 
	params->hdr.tag = ATAG_CMDLINE;
	params->hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2;
 
	strcpy(params->u.cmdline.cmdline, p);
 
	params = tag_next(params);
}

下面是设置 ATAG_INITRD 的例子,它告诉内核initrd 映象(压缩格式) 在 RAM 中的位置以及它的大小。

static void setup_initrd_tag(void)
{
	/* an ATAG_INITRD node tells the kernel where the compressed
         * ramdisk can be found. ATAG_RDIMG is a better name, actually.
	 */
	params->hdr.tag = ATAG_INITRD;
	params->hdr.size = sizeof(struct tag_initrd);
	params->u.initrd.start = RAMDISK_RAM_BASE;
	params->u.initrd.size = INITRD_LEN;
 
	params = tag_next(params);
}

下面是设置 ATAG_RAMDISK 的示例代码,它告诉内核解压后 Ramdisk 的大小(单位是KB):

static void setup_ramdisk_tag(void)
{
	/* an ATAG_RAMDISK node tells the kernel how large the
         * decompressed ramdisk will become.
	 */
	params->hdr.tag = ATAG_RAMDISK;
	params->hdr.size = sizeof(struct tag_ramdisk);
 
	params->u.ramdisk.start = 0;
	params->u.ramdisk.size = RAMDISK_SIZE;
	params->u.ramdisk.flags = 1;	/* automatically load ramdisk */
 
	params = tag_next(params);
}

setup_end_tag函数设置 ATAG_NONE 标记,结束整个启动参数列表:

static void setup_end_tag(void)
{
	params->hdr.tag = ATAG_NONE;
	params->hdr.size = 0;
}
http://www.shangshuwu.cn/index.php/%E5%B5%8C%E5%85%A5%E5%BC%8FLinux%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B

你可能感兴趣的:(linux,image,Flash,嵌入式,存储,linux内核)