现在的位置: 首页 > 综合 > 正文

嵌入式Linux实验手册——基于ARM9 S3C2410实验平台.doc

2013年06月12日 ⁄ 综合 ⁄ 共 31932字 ⁄ 字号 评论关闭

 

嵌入式Linux实验手册

基于ARM9 S3C2410 实验平台

 

1. 实验概述... 1

2. 实验环境配置... 1

2.1 开发主机配置... 1

2.2 实验板介绍... 1

2.3 实验板配置准备... 4

3. Linux预备知识... 5

3.1 Linux常用命令... 5

3.2 Makefile的使用常识... 8

3.3 嵌入式Linux系统引导过程... 12

3.4 U-boot启动过程... 13

3.5 Linux内核启动过程... 22

4. 实验内容... 31

4.1 建立交叉开发环境... 31

4.2 编译调试应用程序... 34

4.3 移植u-boot 36

4.4 编译Linux内核... 39

4.5 移植Linux内核... 47

4.6 调试Linux内核... 50

4.7 制作Linux文件系统... 52

4.8 部署Linux系统... 55


1. 实验概述

对于嵌入式系统,目标板一般只有很小的存储空间,处理器频率也很低。而且没有可以预装的Linux系统,直接在这样的硬件上建立Linux系统非常困难。嵌入式Linux交叉开发环境可以很好地解决这个难题。

所谓交叉开发,就是在开发主机上编辑编译源程序,在目标板上运行可执行程序。通常通过以太网接口传输Linux内核影像到目标板内存,让目标板的Linux挂接NFS的文件系统。这样的交叉开发环境可以非常方便地进行嵌入式Linux开发调试以及集成。

本实验以S3C2410 ARM920T处理器的实验板为例,建立嵌入式Linux交叉开发环境,完成嵌入式Linux开发的全过程。

通过本实验,可以掌握嵌入式Linux基本开发流程,熟悉u-boot、Linux内核、应用程序以及Linux文件系统的配置开发。从而能够在具体的工程项目中应用嵌入式Linux系统。

2. 实验环境配置

2.1 开发主机配置

建议开发主机硬件配置高一些,这样编译的速度快一些,尤其是编译Linux内核。

推荐使用X86 PC配置:

主频:>1GHz

内存:>256MB

安装Redhat Linux 9操作系统作为开发环境。可以在PC上安装Windows和Linux双操作系统,也可以在Windows安装vmware。

安装过程中,建议完全安装所有软件包,这样可以使用Redhat Linux提供的一些Linux工具和服务,方便开发。如果磁盘存储空间有限,安装过程可以附加选择一些软件包,或者Linux启动后再安装这些rpm包。

这些软件包包括:tftp, tftp-server等。

在开发时所需其他服务,请参考手册试验部分4.1内容:建立交叉开发环境。

2.2 实验板介绍

实验板的硬件特点:

  • SAMSUNG ARM9 S3C2410处理器,主频可达203MHz。
  • 64MB SDRAM,有2片K4S561632构成,工作在32位模式
  • 2MB NOR FLASH,型号为SST39VF1601,工作在16位模式
  • 64MB NAND FLASH,型号为K9F1208,可以兼容16MB、32MB和128MB
  • 通过跳线可以设置系统从NOR FLASH或者NAND FLASH启动。
  • 10M以太网接口,采用CS8900Q3芯片,带传输和连接状态指示灯
  • LCD和触摸屏接口。
  • 二个USB HOST接口,遵守USB1.1标准。
  • 一个USB Device接口,遵守USB1.1标准
  • Audio音频接口:音频模块由S3C2410的IIS音频总线和UDA1341音频编解码器组成,板上带一个麦克。
  • 2路RS232接口,波特率可达115200bps。
  • RTC接3V锂电池供电
  • SD卡接口,兼容SD Memory Card 1.0和SDIO Card Protocol 1.0
  • I2C接口的EEPROM:可通过CPU的I2C接口实现对EEROM中的数据读写,数据掉电不丢失。
  • 提供国际标准20针ICE JTAG接口,提供配套的Flash编程下载线。
  • 16个按键
  • 蜂鸣器,4个LED
  • 开关电源

2.3 实验板配置准备

  • 实验板与开发主机之间连接串口线,以太网线和JTAG接口线。

表2.3.1 连线接口说明

连接线

实验板

开发主机

串口线

UART1

串口

以太网线

10M网口

网口

JTAG电缆线

JTAG接口

并口

注意:切勿带电拔插JTAG电缆和并口线,否则很容易损坏芯片。

  • 设置启动方式,从NOR FLASH启动。

核心板上的跳线JP1:连接短路线插,设置为NAND FLASH启动; 断开短路插,设置为NOR FLASH启动。

注意:断开短路插时,要把短路插子插在一个脚上,不要取下,防止丢失。

  • 烧写FS2410BIOS到FLASH

使用sjf2410.exe程序把FS2410_BIOS_I.bin烧写到FLASH中:

1) 复制“Flash烧写工具”目录到主机上,双击“安装驱动.exe”,安装GIVEIO驱动。

2) 连接JTAG接口和并口线。20针扁平电缆连接JTAG接口和JTAG小板的JP3,并口连接主机和JTAG小板。确认核心板上的JP1调线断开。

3) 进入“Flash烧写工具”目录,双击执行sjf2410_bios.bat,显示烧写提示信息

4) 根据烧写提示信息,选择3:SST39VF160 Prog,再输入Input target offset: 0,开始烧写通过并口烧写到NOR FLASH

5)烧写完成后,复位即可启动BIOS

具体参考光盘提供的FS2410用户手册和烧写文档:

2.3节 FS2410的BIOS功能说明

2.6节 用SJF2410工具将BIOS烧写到NAND FLASH

2.7节 用SJF2410工具将BIOS烧写到NOR FLASH

FLASH烧写说明文档_sjf2410_v4.pdf

  • 烧写u-boot到FLASH

通过BIOS烧写u-boot.bin到NOR FLASH。

目标板上电,BIOS启动,在DNW显示启动信息并提示命令选项。

通过串口下载影像文件,选择1: Uart download file,输入1,显示等待串口接收传输文件。

打开DNW的Serial Port菜单,选择发送文件:Tx file

在对话框中浏览选择u-boot.bin的文件,确认后,开始传输,下载到目标板的SDRAM中缓存。

传输完成,提示是否立刻执行,输入n,不立刻执行

然后,回到显示命令选项,选择5: Write NOR FLASH with download file,SDRAM中内容就自动烧写到NOR FLASH的0地址了。

烧写完成后,复位实验板,串口终端应该显示出现u-boot的启动信息。

3. Linux预备知识

3.1 Linux常用命令

       本课程要求学员对Linux基本操作命令有一定了解和掌握。下面列出的一些常用命令作为参考。最好针对每一个都能亲自练习、掌握。

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

ls                  以默认方式显示当前目录文件列表
ls –a              显示所有文件包括隐藏文件
ls –l              显示文件属性,包括大小,日期,符号连接,是否可读写及是否可执行
----------------------------------------------------------------------
cd dir            切换到当前目录下的dir目录
cd ..              切换到到上一级目录
cd ~              切换到用户目录,比如是root用户,则切换到/root下
----------------------------------------------------------------------
rm file           删除某一个文件
rm -rf dir              删除当前目录下叫dir的整个目录
----------------------------------------------------------------------
cp source target                          将文件source 复制为 target
cp –av soure_dir target_dir          将整个目录复制,两目录完全一样
cp –fr source_dir target_dir         将整个目录复制,并且是以非链接方式复制,当source目录带有符号链接时,两个目录不相同
----------------------------------------------------------------------
mv source target                         将文件或者目录source更名为target
----------------------------------------------------------------------
diff dir1 dir2                              比较目录1与目录2的文件列表是否相同,但不比较文件的实际内容,不同则列出
diff file1 file2                            比较文件1与文件2的内容是否相同,如果是文本格式的文件,则将不相同的内容显示,如果是二进制代码则只表示两个文件是不同的
----------------------------------------------------------------------
echo message                              显示一串字符
cat file                                       显示文件的内容,和DOS的type相同
cat file | more                             显示文件的内容并传输到more程序实现分页显示,使用命令less file可实现相同的功能
more             分页命令,一般通过管道将内容传给它,如ls | more
----------------------------------------------------------------------
eject              umout掉CDROM并将光碟弹出,但cdrom不能处于busy的状态,否则无效
----------------------------------------------------------------------
du                               计算当前目录的容量
du -sm /root                 计算/root目录的容量并以M为单位
find -name /path file     在/path目录下查找看是否有文件file
grep -ir “chars”            在当前目录的所有文件查找字串chars,并忽略大小写,-i为大小写,-r为下一级目录
----------------------------------------------------------------------
vi file                          编辑文件file
vi原基本使用及命令:
vi分为编辑状态和命令状态。输入命令要先按ESC,退出编辑状态, 然后输入命令。

常用命令有:

:x(退出)

:x!(退出不保存)

:w(保存文件)

:w!(不询问方式写入文件)

:r file(读文件file)

:%s/oldchars/newchars/g(将所有字串oldchars换成newchars)

i进入编辑插入状态

ESC退出编辑状态

----------------------------------------------------------------------
man ls           读取关于ls命令的帮助
----------------------------------------------------------------------
reboot           重新启动计算机
halt                      关闭计算机
init 0             关闭所有应用程序和服务,进入纯净的操作环境
init 1             重新启动应用及服务
init 6             重新启动计算机
----------------------------------------------------------------------
tar xfzv file.tgz     将文件file.tgz解压
tar -zcvf file.tgz <source>    将文件或目录<source>压缩为file.tgz
gzip directory.tar          将覆盖原文件生成压缩的 directory.tar.gz
gunzip directory.tar.gz   覆盖原文件解压生成不压缩的 directory.tar。
----------------------------------------------------------------------
dmesg           显示kernle启动及驱动装载信息
uname -a        显示操作系统的类型
----------------------------------------------------------------------
strings file     显示file文件中的ASCII字符内容
---------------------------------------------------------------------
rpm -ihv program.rpm 安装程序program并显示安装进程
----------------------------------------------------------------------
su root                         切换到超级用户
chmod a+x file             将file文件设置为可执行,脚本类文件一定要这样设置一个,否则得用bash file才能执行
chmod 666 file             将文件file设置为可读写
chown user /dir            将/dir目录设置为user所有
----------------------------------------------------------------------
mknod /dev/hda1 b 3 1 创建块设备hda1,主设备号为3,从设备号为1,即master硬盘的的第一个分区
mknod /dev/tty1 c 4 1   创建字符设备tty1,主设备号为4,众设备号为1,即第一个tty终端
----------------------------------------------------------------------
touch /tmp/running       在/tmp下创建一个临时文件running,重新启动后消失
----------------------------------------------------------------------
fdisk /dev/hda                            就像执行了dos的fdisk一样
mount -t ext2 /dev/hda1 /mnt       把/dev/hda1装载到 /mnt目录
df                                             显示文件系统装载的相关信息
mount -t nfs 192.168.1.1:/sharedir /mnt 将nfs服务的共享目录sharedir加载到/mnt/nfs目录
umount /mnt         将/mnt目录卸载,/mnt目录必须处于空闲状态
sync                    刷新缓冲区,使内容与磁盘同步,
mkfs /dev/hda1      格式化/dev/hda1为ext2格式
----------------------------------------------------------------------
lilo                              运行lilo程序,程序自动查找/etc/lilo.conf并按该配置生效
lilo -C /root/lilo.conf    lilo程序按/root/lilo.conf配置生效
----------------------------------------------------------------------
dd if=/dev/fd0 of=floppy.fd         将软盘的内容复制成一个镜像
dd if=/dev/zero of=root.ram bs=1024,count=1024  生成一个大小为1M的块设备,可以把它当作硬盘的一个分区来用
----------------------------------------------------------------------
gcc hello.c -o hello                     将hello.c编译成名为hello的二进制执行文件
ldd program                               显示程序所使用了哪些库
----------------------------------------------------------------------
ps                        显示当前系统进程信息
ps –ef                   显示系统所有进程信息
kill -9 500            将进程编号为500的程序杀死
killall -9 netscape 将所有名字为netscape的程序杀死,kill不是万能的,对僵死的程序则无效。
top                显示系统进程的活动情况,按占CPU资源百分比来分
free               显示系统内存及swap使用情况
time program        在program程序结束后,将计算出program运行所使用的时间
----------------------------------------------------------------------
chroot .         将根目录切换至当前目录,调试新系统时使用
----------------------------------------------------------------------
ifconfig eth0 192.168.1.1 netmask 255.255.255.0      设置网卡1的地址192.168.1.1,掩码为255.255.255.0,不写netmask参数则默认为255.255.255.0
ifconfig eth0:0 192.168.1.2 捆绑网卡1的第二个地址为192.168.1.2
----------------------------------------------------------------------
telnet 192.168.1.1         登陆IP为192.168.1.1的telnet服务器
ftp 192.168.1.1            登陆到ftp服务器

3.1.2 GUN工具链常识

一般在完全安装的Redhat Linux上都会自动按照Linux GUN Toolchains。在/usr/bin目录下容易找到gcc,ld等工具。

为了对GUN工具链有一个初步印象,这里安排一个课余作业,在预习时完成。

课余作业:

通过编译一个简单的hello world对Linux编译开发环境有一个直观印象,学习使用编译工具。

1) 在主机上, 写好简单的“hello world” 例子程序。

# include <stdio.h>

main(int argc, char **argv)

{

int i;

for ( i=0; i<10; I++) {

printf("hello i=%d\n", i);

  }

return 0;

}

2) 编译可执行程序.

$ gcc -o hello hello.c

3)使用如下命令运行程序:

# ./hello

可以看到控制台有”hello world”输出。

3.2 Makefile的使用常识

下面将介绍怎样编写、编译一个复杂点的应用程序。

多个代码文件的复杂应用可以通过Makefiel来进行管理。实际上内核就是一个由多个文件组成的程序,也是通过多级的Makefile进行管理,最终通过编译、链接,生成一个内核映象。在理解后面内核编译过程之前需要先了解Makefile的使用常识。

make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。

Makefile里有什么?

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。

5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#”。

还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。

Makefile的文件名

默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如:make -f Make.Linux或make --file Make.AIX。

引用其它的Makefile

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:

include <filename>

filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和<filename>可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:

include foo.make *.mk $(bar)

等价于:

include foo.make a.mk b.mk c.mk e.mk f.mk

make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

1、如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
2、如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:

-include <filename>

其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。

环境变量 MAKEFILES

如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。

但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。

make的工作方式

GNU的make工作时的执行步骤入下:(想来其它的make也是类似)

    1、读入所有的Makefile。
    2、读入被include的其它Makefile。
    3、初始化文件中的变量。
    4、推导隐晦规则,并分析所有规则。
    5、为所有的目标文件创建依赖关系链。
    6、根据依赖关系,决定哪些目标要重新生成。
    7、执行生成命令。

1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。

l  书写规则

规则包含两个部分,一个是依赖关系,一个是生成目标的方法。

在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。

书写规则举例:

foo.o : foo.c defs.h       # foo模块
cc -c -g foo.c

看到这个例子,各位应该不是很陌生了,前面也已说过,foo.o是我们的目标,foo.c和defs.h是目标所依赖的源文件,而只有一个命令“cc -c -g foo.c”(以Tab键开头)。这个规则告诉我们两件事:

1、文件的依赖关系,foo.o依赖于foo.c和defs.h的文件,如果foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那么依赖关系发生。
2、如果生成(或更新)foo.o文件。也就是那个cc命令,其说明了,如何生成foo.o这个文件。(当然foo.c文件include了defs.h文件)

规则的语法

targets : prerequisites
command
...

command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以[Tab键]开头,如果和prerequisites在一行,那么可以用分号做为分隔。

prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。

如果命令太长,你可以使用反斜框(‘\’)作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事,文件的依赖关系和如何成成目标文件。

一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。

在规则中使用通配符

如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三各通配符:“*”,“?”和“[...]”。这是和Unix的B-Shell是相同的。

波浪号(“~”)字符在文件名中也有比较特殊的用途。如果是“~/test”,这就表示当前用户的$HOME目录下的test目录。而“~hchen/test”则表示用户hchen的宿主目录下的test目录。(这些都是Unix下的小知识了,make也支持)而在Windows或是MS-DOS下,用户没有宿主目录,那么波浪号所指的目录则根据环境变量“HOME”而定。

通配符代替了你一系列的文件,如“*.c”表示所以后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:“*”,那么可以用转义字符“\”,如“\*”来表示真实的“*”字符,而不是任意长度的字符串。

还是先来看几个例子吧:

    clean:
         rm -f *.o

上面这个例子我不不多说了,这是操作系统Shell所支持的通配符。这是在命令中的通配符。

   objects = *.o

上面这个例子,表示了,通符同样可以用在变量中。并不是说[*.o]会展开,不!objects的值就是“*.o”。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的文件名的集合,那么,你可以这样:

objects := $(wildcard *.o)

这种用法由关键字“wildcard”指出,wildcard是Makefile的关键字。

课余作业:

随便找一个Linux下的应用程序,提供源代码,tar.gz压缩格式的软件包。用上节介绍的命令解压。读该软件根目录下的Makefile文件。

3.3 嵌入式Linux系统引导过程

嵌入式Linux内核通常需要目标板上的固件引导。这些引导程序就是bootloader,在目标板上电的时候,完成板级初始化和Linux内核引导的任务。U-boot是一种常用的bootloader,本实验就是通过u-boot来引导的。

一般来说,嵌入式Linux系统启动过程都经过3个阶段,如下图所示:

嵌入式Linux系统启动过程

板子上电以后,首先执行bootloader,bootloader负责把Linux内核影像加载或者解压到RAM中。如果有ramdisk的话,也在这个阶段解压到RAM中。然后bootloader把控制权交给Linux内核。

Linux内核开始执行,初始化内存和硬件设备,挂接根文件系统,然后执行/sbin/init。启动第一个用户进程init,开始Linux用户空间的初始化过程。通常Linux内核影像也包含一段Linux bootloader的代码,完成zImage自解压功能。但是这不能完全替代bootloader固件。

Init进程根据inittab,执行系统初始化脚本,启动网络服务和X-windows等,并且管理用户登录。

这样Linux系统就完全启动起来了。

3.4 U-boot启动过程

S3C2410EP实验板上电后,执行u-boot的第一条指令,顺序执行下列函数或者子程序,本手册中以u-boot-1.1.2为例:

_start:   --> reset:

              --> cpu_init_crit --> memsetup

              --> relocate:

              --> stack_setup:

              --> start_armboot( )  --> init_sequence[]

--> …

--> main_loop()

       这样u-boot就可以执行go或者bootm命令,引导Linux内核启动了。详细分析一下启动过程代码。

cpu/arm920t/start.S

_start:     b       reset

       ldr   pc, _undefined_instruction

       ldr   pc, _software_interrupt

       ldr   pc, _prefetch_abort

       ldr   pc, _data_abort

       ldr   pc, _not_used

       ldr   pc, _irq

       ldr   pc, _fiq

 /* the actual reset code  */

reset:

       /* set the cpu to SVC32 mode       */

       mrs  r0,cpsr

       bic   r0,r0,#0x1f

       orr   r0,r0,#0xd3

       msr  cpsr,r0

/* turn off the watchdog */

 

       /*

        * we do sys-critical inits only at reboot,

        * not when booting from ram!

        */

#ifdef CONFIG_INIT_CRITICAL

       bl    cpu_init_crit

#endif

 

relocate:                                    /* relocate U-Boot to RAM   */

       adr   r0, _start                      /* r0 <- current position of code */

       ldr   r1, _TEXT_BASE         /* test if we run from flash or RAM */

       cmp     r0, r1           /* don't reloc during debug  */

       beq     stack_setup

 

       ldr   r2, _armboot_start

       ldr   r3, _bss_start

       sub  r2, r3, r2               /* r2 <- size of armboot   */

       add  r2, r0, r2               /* r2 <- source end address */

 

copy_loop:

       ldmia      r0!, {r3-r10}         /* copy from source address [r0] */

       stmia       r1!, {r3-r10}         /* copy to   target address [r1] */

       cmp r0, r2                           /* until source end addreee [r2] */

       ble   copy_loop

 

       /* Set up the stack                                         */

stack_setup:

       ldr   r0, _TEXT_BASE                       /* upper 128 KiB: relocated uboot */

       sub  r0, r0, #CFG_MALLOC_LEN      /* malloc area    */

       sub  r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo   */

#ifdef CONFIG_USE_IRQ

       sub  r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

       sub  sp, r0, #12             /* leave 3 words for abort-stack */

clear_bss:

       ldr   r0, _bss_start         /* find start of bss segment */

       ldr   r1, _bss_end          /* stop here   */

       mov       r2, #0x00000000           /* clear */

 

clbss_l:str       r2, [r0]           /* clear loop...  */

       add  r0, r0, #4

       cmp r0, r1

       bne  clbss_l

 

       ldr   pc, _start_armboot

 

_start_armboot:      .word start_armboot

 

cpu_init_crit:

       bl    memsetup

 

board/smdk2410/memsetup.S

.globl memsetup

memsetup:

       /* memory control configuration */

       /* make r0 relative the current location so that it */

       /* reads SMRDATA out of FLASH rather than memory ! */

       ldr     r0, =SMRDATA

       ldr   r1, _TEXT_BASE

       sub  r0, r0, r1

       ldr   r1, =BWSCON      /* Bus Width Status Controller */

       add     r2, r0, #13*4

0:

       ldr     r3, [r0], #4

       str     r3, [r1], #4

       cmp     r2, r0

       bne     0b

 

       /* everything is fine now */

       mov pc, lr

 

lib_arm/board.c

void start_armboot (void)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong size;

       init_fnc_t **init_fnc_ptr;

       char *s;

       /* Pointer is writable since we allocated a register for it */

       gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));

       /* compiler optimization barrier needed for GCC >= 3.4 */

       __asm__ __volatile__("": : :"memory");

 

       memset ((void*)gd, 0, sizeof (gd_t));

       gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

       memset (gd->bd, 0, sizeof (bd_t));

 

       monitor_flash_len = _bss_start - _armboot_start;

 

       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                     hang ();

              }

       }

 

       /* configure available FLASH banks */

       size = flash_init ();

       display_flash_config (size);

 

       /* armboot_start is defined in the board-specific linker script */

       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

 

       /* initialize environment */

       env_relocate ();

 

       /* IP Address */

       gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

 

       /* MAC Address */

       devices_init ();       /* get the devices list going. */

       jumptable_init ();

       console_init_r ();    /* fully init console as a device */

       /* enable exceptions */

       enable_interrupts ();

       /* Initialize from environment */

       if ((s = getenv ("loadaddr")) != NULL) {

              load_addr = simple_strtoul (s, NULL, 16);

       }

       /* main_loop() can return to retry autoboot, if so just run it again. */

       for (;;) {

              main_loop ();

       }

       /* NOTREACHED - no way out of command loop except booting */

}

 

init_fnc_t *init_sequence[] = {

       cpu_init,               /* basic cpu dependent setup */   --cpu/arm920t/cpu.c

       board_init,           /* basic board dependent setup */  --board/smdk2410/smdk2410.c

       interrupt_init,      /* set up exceptions */        --cpu/arm920t/s3c24x0/interrupt.c

       env_init,               /* initialize environment */         --common/cmd_flash.c

       init_baudrate,      /* initialze baudrate settings */     --lib_arm/board.c

       serial_init,            /* serial communications setup */        --cpu/arm920t/s3c24x0/serial.c

       console_init_f,      /* stage 1 init of console */               --common/console.c

       display_banner,    /* say that we are here */            --lib_arm/board.c

       dram_init,            /*configure available RAM banks */ --board/smdk2410/smdk2410.c

       display_dram_config,                                            --lib_arm/board.c

       NULL,

};

main_loop() { }  -- common/main.c

 

go 命令引导Linux内核的代码

l  common/cmd_boot.c

int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

       ulong      addr, rc;

       int     rcode = 0;

       if (argc < 2) {

              printf ("Usage:\n%s\n", cmdtp->usage);

              return 1;

       }

       addr = simple_strtoul(argv[1], NULL, 16);

       printf ("## Starting application at 0x%08lX ...\n", addr);

       /*

        * pass address parameter as argv[0] (aka command name),

        * and all remaining args

        */

       rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);

       if (rc != 0) rcode = 1;

 

       printf ("## Application terminated, rc = 0x%lX\n", rc);

       return rcode;

}

U_BOOT_CMD(

       go, CFG_MAXARGS, 1,      do_go,

       "go      - start application at address 'addr'\n",

       "addr [arg ...]\n    - start application at address 'addr'\n"

       "      passing 'arg' as arguments\n"

);

 

bootm命令引导Linux内核的部分代码

l  common/cmd_bootm.c

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

       ulong      iflag;

       ulong      addr;

       ulong      data, len, checksum;

       ulong  *len_ptr;

       uint  unc_len = 0x400000;

       int   i, verify;

       char *name, *s;

       int   (*appl)(int, char *[]);

       image_header_t *hdr = &header;

 

       s = getenv ("verify");

       verify = (s && (*s == 'n')) ? 0 : 1;

       if (argc < 2) {

              addr = load_addr;

       } else {

              addr = simple_strtoul(argv[1], NULL, 16);

       }

       SHOW_BOOT_PROGRESS (1);

       printf ("## Booting image at %08lx ...\n", addr);

       /* Copy header so we can blank CRC field for re-calculation */

       memmove (&header, (char *)addr, sizeof(image_header_t));

       if (ntohl(hdr->ih_magic) != IH_MAGIC)

       {

              puts ("Bad Magic Number\n");

              SHOW_BOOT_PROGRESS (-1);

              return 1;

       }

       SHOW_BOOT_PROGRESS (2);

       data = (ulong)&header;

       len  = sizeof(image_header_t);

 

       checksum = ntohl(hdr->ih_hcrc);

       hdr->ih_hcrc = 0;

 

       if (crc32 (0, (char *)data, len) != checksum) {

              puts ("Bad Header Checksum\n");

              SHOW_BOOT_PROGRESS (-2);

              return 1;

       }

       SHOW_BOOT_PROGRESS (3);

 

       /* for multi-file images we need the data part, too */

       print_image_hdr ((image_header_t *)addr);

 

       data = addr + sizeof(image_header_t);

       len  = ntohl(hdr->ih_size);

       if (verify) {

              puts ("   Verifying Checksum ... ");

              if (crc32 (0, (char *)data, len) != ntohl(hdr->ih_dcrc)) {

                     printf ("Bad Data CRC\n");

                     SHOW_BOOT_PROGRESS (-3);

                     return 1;

              }

              puts ("OK\n");

       }

       SHOW_BOOT_PROGRESS (4);

 

       len_ptr = (ulong *)data;

……

       switch (hdr->ih_os) {

       default:                 /* handled by (original) Linux case */

       case IH_OS_LINUX:

           do_bootm_linux  (cmdtp, flag, argc, argv,

                          addr, len_ptr, verify);

           break;

       ……

}

U_BOOT_CMD(

      bootm,    CFG_MAXARGS, 1,    do_bootm,

      "bootm   - boot application image from memory\n",

      "[addr [arg ...]]\n    - boot application image stored in memory\n"

      "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"

      "\t'arg' can be the address of an initrd image\n"

);

 

l  lib_arm/armlinux.c

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

                   ulong addr, ulong *len_ptr, int verify)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong len = 0, checksum;

       ulong initrd_start, initrd_end;

       ulong data;

       void (*theKernel)(int zero, int arch, uint params);

       image_header_t *hdr = &header;

       bd_t *bd = gd->bd;

 

#ifdef CONFIG_CMDLINE_TAG

       char *commandline = getenv ("bootargs");

#endif

       theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

       /*

        * Check if there is an initrd image

        */

       if (argc >= 3) {

              SHOW_BOOT_PROGRESS (9);

              addr = simple_strtoul (argv[2], NULL, 16);

              printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

              /* Copy header so we can blank CRC field for re-calculation */

              memcpy (&header, (char *) addr, sizeof (image_header_t));

              if (ntohl (hdr->ih_magic) != IH_MAGIC) {

                     printf ("Bad Magic Number\n");

                     SHOW_BOOT_PROGRESS (-10);

                     do_reset (cmdtp, flag, argc, argv);

              }

              data = (ulong) & header;

              len = sizeof (image_header_t);

              checksum = ntohl (hdr->ih_hcrc);

              hdr->ih_hcrc = 0;

              if (crc32 (0, (char *) data, len) != checksum) {

                     printf ("Bad Header Checksum\n");

                     SHOW_BOOT_PROGRESS (-11);

                     do_reset (cmdtp, flag, argc, argv);

              }

              SHOW_BOOT_PROGRESS (10);

              print_image_hdr (hdr);

              data = addr + sizeof (image_header_t);

              len = ntohl (hdr->ih_size);

              if (verify) {

                     ulong csum = 0;

                     printf ("   Verifying Checksum ... ");

                     csum = crc32 (0, (char *) data, len);

                     if (csum != ntohl (hdr->ih_dcrc)) {

                            printf ("Bad Data CRC\n");

                            SHOW_BOOT_PROGRESS (-12);

                            do_reset (cmdtp, flag, argc, argv);

                     }

                     printf ("OK\n");

              }

              SHOW_BOOT_PROGRESS (11);

              if ((hdr->ih_os != IH_OS_LINUX) ||

                  (hdr->ih_arch != IH_CPU_ARM) ||

                  (hdr->ih_type != IH_TYPE_RAMDISK)) {

                     printf ("No Linux ARM Ramdisk Image\n");

                     SHOW_BOOT_PROGRESS (-13);

                     do_reset (cmdtp, flag, argc, argv);

              }

              /*

               * Now check if we have a multifile image

               */

       } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {

              ulong tail = ntohl (len_ptr[0]) % 4;

              int i;

              SHOW_BOOT_PROGRESS (13);

              /* skip kernel length and terminator */

              data = (ulong) (&len_ptr[2]);

              /* skip any additional image length fields */

              for (i = 1; len_ptr[i]; ++i)

                     data += 4;

              /* add kernel length, and align */

              data += ntohl (len_ptr[0]);

              if (tail) {

                     data += 4 - tail;

              }

              len = ntohl (len_ptr[1]);

       } else {

              /*

               * no initrd image

               */

              SHOW_BOOT_PROGRESS (14);

              len = data = 0;

       }

       if (data) {

              initrd_start = data;

              initrd_end = initrd_start + len;

       } else {

              initrd_start = 0;

              initrd_end = 0;

       }

       SHOW_BOOT_PROGRESS (15);

       debug ("## Transferring control to Linux (at address %08lx) ...\n",

              (ulong) theKernel);

 

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \

    defined (CONFIG_CMDLINE_TAG) || \

    defined (CONFIG_INITRD_TAG) || \

    defined (CONFIG_SERIAL_TAG) || \

    defined (CONFIG_REVISION_TAG) || \

    defined (CONFIG_LCD) || \

    defined (CONFIG_VFD)

       setup_start_tag (bd);

#ifdef CONFIG_SERIAL_TAG

       setup_serial_tag (&params);

#endif

#ifdef CONFIG_REVISION_TAG

       setup_revision_tag (&params);

#endif

#ifdef CONFIG_SETUP_MEMORY_TAGS

       setup_memory_tags (bd);

#endif

#ifdef CONFIG_CMDLINE_TAG

       setup_commandline_tag (bd, commandline);

#endif

#ifdef CONFIG_INITRD_TAG

       if (initrd_start && initrd_end)

              setup_initrd_tag (bd, initrd_start, initrd_end);

#endif

       setup_end_tag (bd);

#endif

 

       /* we assume that the kernel is in place */

       printf ("\nStarting kernel ...\n\n");

       cleanup_before_linux ();

 

       theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

}

3.5 Linux内核启动过程

Linux内核影像接管控制权后,执行一系列解压和初始化的操作,然后调用start_kernel函数,启动整个Linux系统。zImage启动过程函数调用顺序如下(本手册以2.4内核启动过程为例):

start: --> decompress_kernel()

       --> call_kernel()

              --> stext: --> start_kernel()

                            --> setup_arch()

                            --> parse_options(command_line)

              --> trap_init()

--> init_IRQ()

--> sched_init()

--> softirq_init()

--> time_init()

--> console_init()

--> …

--> calibrate_delay()

--> …

--> rest_init() --> kernel_thread(init, …)

                     --> do_basic_setup()

                     --> prepare_namespace()

                     --> execve("/sbin/init",argv_init,envp_init)

 

zImage从结构上可分为Linux bootloader和Linux内核两部分。具体代码分析如下:

zImage的Linux bootloader部分

l  arch/arm/boot/compressed/head.S

start:

              .type       start,#function

              .rept 8

              mov r0, r0

              .endr

 

              b     1f

              .word      0x016f2818           @ Magic numbers to help the loader

              .word      start               @ absolute load/run zImage address

              .word      _edata                   @ zImage end address

1:           mov r7, r1                    @ save architecture ID

              mov r8, #0                    @ save r0

              /*

               * Booting from Angel - need to enter SVC mode and disable

               * FIQs/IRQs (numeric definitions from angel arm.h source).

               * We only do this if we were in user mode on entry.

               */

              mrs  r2, cpsr          @ get current mode

              tst    r2, #3                    @ not user?

              bne  not_angel

              mov r0, #0x17              @ angel_SWIreason_EnterSVC

              swi  0x123456              @ angel_SWI_ARM

not_angel:

              mrs  r2, cpsr          @ turn off interrupts to

              orr   r2, r2, #0xc0          @ prevent angel from running

              msr  cpsr_c, r2

              /*

               * Note that some cache flushing and other stuff may

               * be needed here - is there an Angel SWI call for this?

               */

 

              /*

               * some architecture specific code can be inserted

               * by the linker here, but it should preserve r7 and r8.

               */

 

              .text

              adr   r0, LC0

              ldmia      r0, {r1, r2, r3, r4, r5, r6, ip, sp}

              subs r0, r0, r1        @ calculate the delta offset

 

              teq   r0, #0                    @ if delta is zero, we're

              beq  not_relocated         @ running at the address we

                                          @ were linked at.

 

              /*

               * We're running at a different address.  We need to fix

               * up various pointers:

               *   r5 - zImage base address

               *   r6 - GOT start

               *   ip - GOT end

               */

              add  r5, r5, r0

              add  r6, r6, r0

              add  ip, ip, r0

 

              /*

               * If we're running fully PIC === CONFIG_ZBOOT_ROM = n,

               * we need to fix up pointers into the BSS region.

               *   r2 - BSS start

               *   r3 - BSS end

               *   sp - stack pointer

               */

              add  r2, r2, r0

              add  r3, r3, r0

              add  sp, sp, r0

 

              /*

               * Relocate all entries in the GOT table.

               */

1:           ldr   r1, [r6, #0]

              add  r1, r1, r0

              str    r1, [r6], #4

              cmp r6, ip

              blo   1b

not_relocated: mov r0, #0

1:           str    r0, [r2], #4            @ clear bss

              str    r0, [r2], #4

              str    r0, [r2], #4

              str    r0, [r2], #4

              cmp r2, r3

              blo   1b

 

              /*

               * The C runtime environment should now be setup

               * sufficiently.  Turn the cache on, set up some

               * pointers, and start decompressing.

               */

              bl    cache_on

 

              mov r1, sp                    @ malloc space above stack

              add  r2, sp, #0x10000    @ 64k max

 

/*

 * Check to see if we will overwrite ourselves.

 *   r4 = final kernel address

 *   r5 = start of this image

 *   r2 = end of malloc space (and therefore this image)

 * We basically want:

 *   r4 >= r2 -> OK

 *   r4 + image length <= r5 -> OK

 */

              cmp r4, r2

              bhs  wont_overwrite

              add  r0, r4, #4096*1024 @ 4MB largest kernel size

              cmp r0, r5

              bls   wont_overwrite

 

              mov r5, r2                    @ decompress after malloc space

              mov r0, r5

              mov r3, r7

              bl    decompress_kernel

 

              add  r0, r0, #127

              bic   r0, r0, #127           @ align the kernel length

/*

 * r0     = decompressed kernel length

 * r1-r3  = unused

 * r4     = kernel execution address

 * r5     = decompressed kernel start

 * r6     = processor ID

 * r7     = architecture ID

 * r8-r14 = unused

 */

              add  r1, r5, r0        @ end of decompressed kernel

              adr   r2, reloc_start

              ldr   r3, LC1

              add  r3, r2, r3

1:           ldmia      r2!, {r8 - r13}              @ copy relocation code

              stmia       r1!, {r8 - r13}

              ldmia      r2!, {r8 - r13}

              stmia       r1!, {r8 - r13}

              cmp r2, r3

              blo   1b

 

              bl    cache_clean_flush

              add  pc, r5, r0        @ call relocation code

 

/*

 * We're not in danger of overwriting ourselves.

 * Do this the simple way.

 * r4     = kernel execution address

 * r7     = architecture ID

 */

wont_overwrite:     mov r0, r4

              mov r3, r7

              bl    decompress_kernel

              b     call_kernel

 

call_kernel:     bl    cache_clean_flush

              bl    cache_off

              mov r0, #0

              mov r1, r7                    @ restore architecture number

              mov pc, r4                    @ call kernel

 

l  arch/arm/boot/compressed/misc.c

ulg decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p,

                int arch_id)

{

       output_data            = (uch *)output_start;    /* Points to kernel start */

       free_mem_ptr        = free_mem_ptr_p;

       free_mem_ptr_end = free_mem_ptr_end_p;

       __machine_arch_type    = arch_id;

 

       proc_decomp_setup();

       arch_decomp_setup();

 

       makecrc();

       puts("Uncompressing Linux...");

       gunzip();

       puts(" done, booting the kernel.\n");

      

       return output_ptr;

}

 

Linux 内核部分代码

l  arch/arm/kernel/head-armv.S

              .section ".text.init",#alloc,#execinstr

              .type       stext, #function

ENTRY(stext)

              mov r12, r0

__entry:

              mov r0, #F_BIT | I_BIT | MODE_SVC       @ make sure svc mode

              msr  cpsr_c, r0                     @ and all irqs disabled

              bl    __lookup_processor_type

              teq   r10, #0                         @ invalid processor?

              moveq     r0, #'p'                   @ yes, error 'p'

              beq  __error

              bl    __lookup_architecture_type

              teq   r7, #0                           @ invalid architecture?

              moveq     r0, #'a'                   @ yes, error 'a'

              beq  __error

      

抱歉!评论已关闭.