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

时间:2022-01-22 19:30:21
 

嵌入式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

              bl    __create_page_tables

              adr   lr, __ret                 @ return address

              add  pc, r10, #12                  @ initialise processor

                                                 @ (return control reg)

 

              .type       __switch_data, %object

__switch_data:       .long       __mmap_switched

              .long       SYMBOL_NAME(__bss_start)

              .long       SYMBOL_NAME(_end)

              .long       SYMBOL_NAME(processor_id)

              .long       SYMBOL_NAME(__machine_arch_type)

              .long       SYMBOL_NAME(cr_alignment)

              .long       SYMBOL_NAME(init_task_union)+8192

 

              .type       __ret, %function

__ret:             ldr   lr, __switch_data

              mcr  p15, 0, r0, c1, c0

              mov r0, r0

              mov r0, r0

              mov r0, r0

              mov pc, lr

 

              /*

               * This code follows on after the page

               * table switch and jump above.

               *

               * r0  = processor control register

               * r1  = machine ID

               * r9  = processor ID

               */

              .align      5

__mmap_switched:

              adr   r3, __switch_data + 4

              ldmia      r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat

                                                 @ sp = stack pointer

 

              mov fp, #0                           @ Clear BSS (and zero fp)

1:           cmp r4, r5

              strcc fp, [r4],#4

              bcc  1b

 

              str    r9, [r6]                  @ Save processor ID

              str    r1, [r7]                  @ Save machine type

#ifdef CONFIG_ALIGNMENT_TRAP

              orr   r0, r0, #2               @ ...........A.

#endif

              bic   r2, r0, #2               @ Clear 'A' bit

              stmia       r8, {r0, r2}                   @ Save control register values

              b     SYMBOL_NAME(start_kernel)

 

l  init/main.c

asmlinkage void __init start_kernel(void)

{

       char * command_line;

       unsigned long mempages;

       extern char saved_command_line[];

/*

 * Interrupts are still disabled. Do necessary setups, then

 * enable them

 */

       lock_kernel();

       printk(linux_banner);

       setup_arch(&command_line);

       printk("Kernel command line: %s\n", saved_command_line);

       parse_options(command_line);

       trap_init();

       init_IRQ();

       sched_init();

       softirq_init();

       time_init();

 

       /*

        * HACK ALERT! This is early. We're enabling the console before

        * we've done PCI setups etc, and console_init() must be aware of

        * this. But we do want output early, in case something goes wrong.

        */

       console_init();

……

    sti();

    calibrate_delay();

……

    printk("POSIX conformance testing by UNIFIX\n");

    /*

     *    We count on the initial thread going ok

     *    Like idlers init is an unlocked kernel thread, which will

     *    make syscalls (and thus be locked).

     */

    smp_init();

    rest_init();

}

 

/*

 * We need to finalize in a non-__init function or else race conditions

 * between the root thread and the init thread may cause start_kernel to

 * be reaped by free_initmem before the root thread has proceeded to

 * cpu_idle.

 */

 

static void rest_init(void)

{

       kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);

       unlock_kernel();

       current->need_resched = 1;

      cpu_idle();

}

static int init(void * unused)

{

       lock_kernel();

       do_basic_setup();

       prepare_namespace();

       /*

        * Ok, we have completed the initial bootup, and

        * we're essentially up and running. Get rid of the

        * initmem segments and start the user-mode stuff..

        */

       free_initmem();

       unlock_kernel();

       if (open("/dev/console", O_RDWR, 0) < 0)

              printk("Warning: unable to open an initial console.\n");

       (void) dup(0);

       (void) dup(0);

       /*

        * We try each of these until one succeeds.

        *

        * The Bourne shell can be used instead of init if we are

        * trying to recover a really broken machine.

        */

       if (execute_command)

              execve(execute_command,argv_init,envp_init);

       execve("/sbin/init",argv_init,envp_init);

       execve("/etc/init",argv_init,envp_init);

       execve("/bin/init",argv_init,envp_init);

       execve("/bin/sh",argv_init,envp_init);

       panic("No init found.  Try passing init= option to kernel.");

}

 


4. 实验内容

4.1 建立交叉开发环境

l  实验目的:

在开发主机Redhat Linux 9上安装交叉开发工具和相关文件目录,配置TFTP和NFS服务,目标板通过u-boot启动Linux,挂接NFS根文件系统。学习建立交叉开发环境。

l  安装Red Hat Linux

可以在已经安装了Windows的主机上,安装Linux,实现双操作系统引导。也可以只安装Linux操作系统。一般完全安装,可以包含所有需要的程序。PC机要有串口、并口、以太网接口、光驱,并且Linux驱动能够支持。

l  工具链的安装配置

挂接光驱:

$ mount /mnt/cdrom

              解压安装软件包到相应目录下:

                     $ cd /

              解压安装工具链,工具链安装在/usr/local/arm/3.3/目录下

$ tar -jxvf /mnt/cdrom/s3c2410_linux/tools/arm_linux_3.3_tool_chain.tar.bz2

              解压目标板文件系统,目标板文件系统安装在/opt/target/rootfs目录下

                     $ mkdir /opt/target

                     $ cd /opt/target

                     $ tar -zxvf /mnt/cdrom/s3c2410_linux/rootfs/rootfs.tar.gz

              复制已有的影像文件到/tftpboot目录下,以便网络下载

                     $ cp /mnt/cdrom/s3c2410_linux/image/*  /tftpboot/

              工作区在~/S3C2410目录下,作为编译源码的目录

              最后在/etc/profile文件中修改PATH环境变量,添加工具链的路径。在export之前,添加如下一行:

PATH=$PATH:/ usr/local/arm/3.3/bin

              然后使这项配置生效:

$ source /etc/profile

             

l  配置minicom串口终端

在主机启动minicom作为目标板的串口终端。minicom需要配置如下参数:

COM1:      /dev/ttyS0

波特率:    115200

帧格式:    8N1  (8位数据,没有校验位,1位停止位)

硬件流控:   No

1) 以root身份登录,运行:

minicom –s

屏幕上出现Minicom配置菜单。

2) 选择 Serial port setup 菜单项,设置目标板的选项。

A - Serial Device : /dev/ttyS0

B - Lockfile Location : /var/lock

C - Callin Program :

D - Callout Program :

E - Bps/Par/Bits : 115200 8N1

F - Hardware Flow Control : No

G - Software Flow Control : No

Change which setting?

按照目标板设置指令部分给出的参数设置Minicom的串口参数

注意: 上面的串口设备号是典型值,也可以根据所连接的端口更改。比如连接到串口1,设置为ttyS0。

3) 敲回车回到主配置菜单

Filenames and paths

File transfer protocols

Serial port setup

Modem and dialing

Screen and keyboard

Save setup as dfl

Save setup as..

Exit

Exit from Minicom.

4) 敲回车返回主菜单。

注意:敲ESC退出主菜单;敲CTRL+A,再敲Z进入主菜单,这时可以使用命令快捷键。

5) 选择 Save as dfl 菜单项,保存为缺省设置。

6) 选择Exit from Minicom 退出。

7) 启动minicom

        $ minicom

l  配置TFTP服务

TFTP是目标板下载影像所需要的网络服务。

操作过程 以root身份登录,完成下面的操作步骤:

执行以下步骤:

编辑tftp配置文件:/etc/xinetd.d/tftp

修改disable一行,把yes修改成no

disable = no

使用下面的命令使xinetd重新读取配文件:

$ /etc/init.d/xinetd restart

把内核影像文件复制到/tftpboot目录下

$ cp zImage /tftpboot

        注意:需要去掉主机防火墙,tftp服务才能正常传输文件

l  配置NFS服务

以root的身份完成下面的步骤。

1) 在/etc/exports中添加下面一行:

/opt/target/rootfs  *(rw,no_root_squash,no_all_squash)

/opt/target/rootfs是具体的输出目录。

同时可以有输出多个目录,方便开发调试。

2) 确认NFS守护进程正在运行。使用下面命令启动或者重启NFS服务:

$ /etc/rc.d/init.d/nfs restart

配置NFS服务在每次系统引导时启动,使用下面命令:

$ /sbin/chkconfig nfs on

NFS 配置完成,每次修改配置文件中输出的目录,可以重新起动NFS服务。

l  配置交叉开发环境

在开发主机端启动minicom,注意所连接的串口设备号设置端口,设置波特率为115200。U-boot启动正常,应该提示自动启动,敲回车进入命令行提示符“UBOOT>”。这时可以使用help命令查看命令解释,从而配置目标板参数。

确认板子配置,检查串口线连接实验板Serial port0,以太网线连接100M以太网接口。同时确认主机端串口,以太网接口。确认无误后,板子再上电,这时串口控制台应该有u-boot的启动信息。 如果还没有把u-boot烧到FLASH中,请先关掉电源,再按照2.3节的说明把u-boot烧到FLASH里,已经烧写u-boot的实验板就不再需要重新烧写了。

配置主机以太网接口的IP地址,可以通过图形化的配置界面,也可以通过命令行方式:

$ ifconfig eth0 192.168.1.1

设置主机和目标板的IP地址,才能通过网络加载Linux内核。启动Linux内核,挂接NFS文件系统

           UBOOT> tftp 30008000 zImage

           UBOOT> go 30008000

正常情况下,Linux内核会启动起来,并且挂接NFS文件系统。可以在串口终端进入Shell执行命令

 

 


4.2 编译调试应用程序

l  实验目的

通过交叉开发环境,学习交叉编译调试应用程序

l  交叉编译应用程序

编辑下面的c程序,使用arm-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) 编译可执行程序.

$ arm-linux-gcc -o hello hello.c

3) 把可执行程序复制到NFS输出的目录下面.

$ cp hello /opt/target/rootfs

4) 目标板加载内核并且引导以后,目标系统的NFS挂接目录变成它的根目录,于是可以使用如下命令运行程序:

# ./hello

hello world

l  交叉调试应用程序

下面用hello.c程序为例,说明调试过程。

编译程序的时候要添加调试信息,如下命令行所示:

$ arm-linux-gcc  -g  –o  hello  hello.c

注意: 当目标板上使用一个精简的二进制文件时,主机上必须是一个带调试信息的便于符号调试的二进制文件。

使用GDB和ddd调试过程:

1) 在目标板上,启动gdbserver,调试hello程序

# gdbserver <host>:2345 hello

2345是网络端口号,服务器在这个端口上等待客户端的连接,这个值可以是任何目标板上可用的端口号。hello是调试程序名,还可以添加程序运行的参数。<host>是主机名称或者IP地址。

控制台输出下面类似的显示:

Process hello created; pid = 38

2) 在主机上,改变目录到hello程序目录下,执行下面命令:

$ arm-linux-gdb  hello

3) 在ddd下窗口的GDB命令提示下,执行下面命令:

(gdb)target remote <target>:2345

<target>是目标板名称或者IP地址,端口是2345。这个命令结果是在目标板端显示:

Remote debugging using 192.168.1.1:2345

现在就可以使用GDB的命令调试了。

4) 双击main或者输入命令b main,在main函数设置断点。

单击cont 或者输入c 命令执行程序

l  练习编译调试较复杂的C程序

找一个较复杂的C程序,练习编写Makefile,练习交叉编译调试。

 

课后练习:

       1)工具链编译

l  原理理解

参考讲义Development Environment上所述的编译工具链过程,理解工具链编译原理。在光盘目录:~/mnt/cdrom/s3c2410_linux/tools/中找到工具链的代码文件,

arm_linux_3.3_tool_chain_src.tar.bz2,解压缩,进入src目录,读取readme.txt和build.sh脚本。运行build.sh脚本,将在/usr/local/arm/3.3/目录下找到3.3 arm-linux工具链。


4.3 移植u-boot

l  实验目的:

分析u-boot源代码,练习基于u-boot-1.1.2源代码移植u-boot,添加支持实验板。

l  编译u-boot-1.1.2

u-boot-1.1.2.tar.bz2源码包在光盘的s3c2410_linux/resource目录下。U-boot-1.1.2已经支持smdk2410板,smdk2410是韩国三星的S3C2410开发板。因为处理器相同,S3C2410EP板完全可以基于smdk2410板的代码移植。

解压后,先配置编译smdk2410板的u-boot。

$ cd  ~/workspace

$ tar -jxvf u-boot-1.1.2.tar.bz2

$cd u-boot-1.1.2

在Makefile中修改交叉编译工具链前缀CROSS_COMPILE:

$ vi Makefile

CROSS_COMPILE = arm-linux-

配置编译smdk2410板:

$ make smdk2410_config

$ make

 

根据编译过程中的错误进行后面内容的修改。需要下载原来版本的u-boot.1.1.2和生成的最后的patch文件。

armv-linux-gcc板本的缘故,编译过程可能出现一些奇怪的错误。对于不是必要的程序,可以不去编译这些程序。例如:如果编译examples中的hello_world出错,不必作过多分析,直接修改examples/Makefile,注释掉目标,不再编译。

$ vi examples/Makefile

#SREC   = hello_world.srec

#BIN    = hello_world.bin hello_world

编译过程会出现下面一个错误,先看一下报错信息:

……

armv4l-unknown-linux-gcc -g  -Os   -fno-strict-aliasing  -fno-common

-ffixed-r8 -mshort-load-bytes -msoft-float -D__KERNEL__

-DTEXT_BASE=0x33F80000 -I/home/user/s3c2410/u-boot-1.1.2/include

-fno-builtin -ffreestanding -nostdinc -isystem

/opt/host/armv4l/lib/gcc-lib/armv4l-unknown-linux/2.95.2/include -pipe

-DCONFIG_ARM -D__ARM__ -mapcs-32 -march=armv4 -Wall -Wstrict-prototypes

-c -o cfi_flash.o cfi_flash.c

In file included from cfi_flash.c:48:

/home/user/s3c2410/u-boot-1.1.2/include/asm/processor.h:52: parse error before `1'

/home/user/s3c2410/u-boot-1.1.2/include/asm/processor.h:52: warning: no semicolon at end of struct or union

/home/user/s3c2410/u-boot-1.1.2/include/asm/processor.h:54: parse error before `}'

/home/user/s3c2410/u-boot-1.1.2/include/asm/processor.h:58: field `insn' has incomplete type

make[1]: *** [cfi_flash.o] Error 1

make[1]: Leaving directory `/home/user/s3c2410/u-boot-1.1.2/drivers'

make: *** [drivers/libdrivers.a] Error 2

 

问题出在include/asm/processor.h文件中union debug_insn {}的定义编译有问题。从C语言语法上看没有错,高版本的编译器也可以编译通过。所以可能是因为2.95.3版本的gcc存在的问题。不过这里union debug_insn并没有什么用处,把相关的东西都注释掉好了。

$ vi include/asm/processor.h

/*

union debug_insn {

        u32     arm;

        u16     thumb;

};

struct debug_entry {

        u32                     address;

        union debug_insn        insn;

};

struct debug_info {

        int                     nsaved;

        struct debug_entry      bp[2];

};

*/

……

                                  /* debugging      */

//        struct debug_info               debug;

 

l  移植u-boot-1.1.2支持实验板

1) 修改Makefile,添加fs2410板的配置项

$ vi Makefile

fs2410_config   :       unconfig

        @./mkconfig $(@:_config=) arm arm920t fs2410 NULL s3c24x0

 

2) 在board目录下添加板子专用目录,并复制修改smdk2410的有关文件

$ mkdir ./board/fs2410

$ cp ./board/smdk2410/* ./board/fs2410/

$ cd board/fs2410

$ mv smdk2410.c fs2410.c

$ vi Makefile

OBJS    := fs2410.o flash.o

 

3) 添加板子配置文件

$ cp ./include/configs/smdk2410.h ./include/configs/fs2410.h

$ vi include/configs/fs2410.h

#define CONFIG_FS2410   1     /* on a uCdragon FS2410 Board */

#define CONFIG_CMDLINE_TAG  1       /* enable passing of ATAGs */

#define CONFIG_SETUP_MEMORY_TAGS     1

#define CONFIG_INITRD_TAG               1

                     #define CONFIG_SHOW_BOOT_PROGRESS   1

$ vi cpu/arm920t/s3c24x0/interrupts.c

#elif defined(CONFIG_SMDK2410) || defined(CONFIG_FS2410)

 

4) 配置板子编译选项

$ make distclean

$ make fs2410_config

5) 编译

$ make

编译通过以后,可以得到u-boot u-boot.bin u-boot.srec影像文件。

 

选作项目:网络接口和FLASH的支持

 

实验板有一个CS8900芯片的10M以太网接口。请把以太网相关的文件和配置添加进去,能够通过tftp下载文件到RAM。SMDK2410的cs8900驱动可以很好的支持FS2410板上的以太网接口

实验板有一片2MB的SST型FLASH,考虑实现对flash的擦写操作。只有能够擦写FLASH,才能够保存环境变量。

board/fs2410/flash.c

 

l  烧写u-boot到FLASH

使用JFLASH烧写u-boot.bin到FLASH,参考第2.3节的配置说明。注意:一定不要带电拔插JTAG电缆。

              通过FS2410BIOS烧写

              复位目标板,能够出现u-boot启动信息,不要求以太网接口和FLASH驱动完全移植好。

       其它烧写FLASH方式(不做要求):

1)通过BDI2000烧写FLASH,通过以太网口下载,速度快;

              2)通过U-boot 自己的FLASH擦写命令,这需要把FLASH的擦写函数移植好。

 

课后练习:

 

       启动板子,进入u-boot,实验下面附件中的u-boot功能:

附件,U-Boot Commands

Following chapters describes the most used commands in alphabetic order.

1.1 base - print or set address offset

Abbreviation: ba

=> help base

base

- print address offset for memory commands

base off

- set address offset for memory commands to 'off'

=>

With this command you can set the base address of the memory region you like to access. When set, you need only to work with the offsets to this address. Example:

=> ba

Base Address: 0x00000000

=> md 3000c000 20

3000c000: 27051956 65d6b450 3cbc2652 003738ef '..Ve..P<.&R.78.

3000c010: 00000000 00000000 43450692 05070401 ........CE......

=> ba 3000c000

Base Address: 0x3000c000

=> md 0 20

ffc00000: 27051956 65d6b450 3cbc2652 003738ef '..Ve..P<.&R.78.

ffc00010: 00000000 00000000 43450692 05070401 ........CE......

ffc00020: 6b65726e 656c2061 6e642069 6e697472 kernel and initr

1.2 bdinfo - print Board Info structure

Shows the Board Information Structure which is used to pass information from the U-Boot to the Operating System.

Abbreviation: bdi

=>bdi

memstart = 0x00000000

……

ethaddr = 00:60:C2:08:00:00

IP addr = 192.168.3.31

baudrate = 9600 bps

1.3 bootd - boot default, i.e run ‘bootcmd’

Abbreviation: bootd

This command is an alias to the command ‘run bootcmd’. If invoked, the commands in the environment variable bootcmd will be executed. This variable contains the necessary commands to boot the default way. Example:

=> printenv bootcmd

bootcmd=bdinfo; bootm 100000

1.4 bootm - boot application image from memory

This command is used to execute the u-boot image residing in the Memory.

Abbreviation: bootm

=> help bootm

bootm [addr [arg ...]]

- boot application image stored in memory

passing arguments 'arg ...'; when booting a Linux kernel,

'arg' can be the address of an initrd image

=>

Information about the type of operating system, compression, load and entry point are retrieved from the image header. If

loading a Linux kernel and a RAMdisk the address of the RAMdisk image is the second argument.

Example:

=> bootm 400000

## Booting image at 00400000 ...

Image Name: Linux- mip405

……

1.5 bootp - boot image via network using BOOTP/TFTP protocol

Used to load a image via network using the BOOTP/TFTP protocol.

Abbreviation: bootp

=> help bootp

bootp [loadAddress] [bootfilename]

=>

To use this command, you need to set-up a bootp server.

1.6 cmp - memory compare

The cmp command compares the first memory area with the second one. The command stops on the first non matching

address or if the count parameter is reached.

Abbreviation: cmp

=> help cmp

cmp [.b, .w, .l] addr1 addr2 count

- compare memory

=>

Example:

=> cmp 3000c000 3000d000 400

word at 0xffc00320 (0x13f0efff) != word at 0x00400320 (0x55ff55ff)

Total of 200 words were the same

=>

1.7 coninfo - print console devices and information

Abbreviation: conin

=>conin

List of available devices:

vga 80000002 S.O

serial 80000003 SIO stdin stdout stderr

=>

This command shows information of the console devices. The characters ‘SIO’ shows the device capabilities:

S = System device, I = Input and O = Output. The current assignment is shown by ‘stdin’, ‘stdout’ and ‘stderr’.

1.8 cp - memory copy

The copy command copies data from one memory address to another. It distinguish itself if the destination lies in the

flash area, so this command can be used to write to the flash.

Abbreviation: cp

=> help cp

cp [.b, .w, .l] source target count

- copy memory

Example:

Copy 0x400 Longs from 0xFFC00000 to 0x400000.

=> cp ffc00000 400000 400

=>

Writing to the flash:

=> cp.b 400000 ffc00000 10000

Copy to Flash... done

=>

Please note, that the count value specifies the amount of data in the modifier size. This means, count is the number of

bytes to copy if the modifier is ‘.b’, the number of words (2Bytes) if the modifier is ‘.w’ and the number of longs (4Bytes) if

no modifier or the modifier is ‘.l’.

Note: Please make sure to use the correct modifier (.b, .w or .l). If you are using the wrong

modifier, you may overwrite a memory area which is already used.

The flash area must be erased and unprotected, prior to write.

 

1.9 crc32 - checksum calculation

The crc32 command is used to calculate the crc32 checksum over a certain memory area.

Abbreviation: crc

=> help crc32

crc32 address count [addr]

- compute CRC32 checksum [save at addr]

Example:

=> crc ffc00000 400

CRC32 for ffc00000 ... ffc003ff ==> 056ec8f3

If issued with 3 parameters, the crc32 checksum is written to the specified location.

=> crc ffc00000 400 400000

CRC32 for ffc00000 ... ffc003ff ==> 056ec8f3

=> md 400000 1

00400000: 056ec8f3 .n..

=>

1.10 date - get/set/reset date & time

The date command gets, sets or resets date and time of the RTC:

Abbreviation: dat

=> help dat

date [MMDDhhmm[[CC]YY][.ss]]

date reset

- without arguments: print date & time

- with numeric argument: set the system date & time

- with 'reset' argument: reset the RTC

=>

The command

=> date 062913302001

sets the RTC to the Jun 29 2001 - 13:30:00

Caution: If the battery has been replaced or the RTC is stuck of another reason, you must first reset the RTC with the command date reset.

1.11 dhcp - invoke DHCP client to obtain IP/boot params

Used to get the IP and boot parameter using a DHCP server.

Abbreviation: dhcp

=> help dhcp

dhcp

 


4.4 编译Linux内核

l  实验目的:

配置编译支持目标板的Linux-2.6.11内核,熟悉Linux内核的配置选项和编译过程。

l  配置编译过程

解压源码包

$ cd ~/ workspace

$ tar -jxvf  /mnt/cdrom/FS2410_V60_02/Linux/Source/linux-2.6.11_fs2410.tar.bz2

$ cd linux-2.6.11_fs2410

              检查修改Makefile,确认ARCH和CROSS_COMPILER定义:

                     ARCH = arm

                     CROSS_COMPILER = arm-inux-

              使用保存的配置文件编译:

              $ cp .config_nfs  .config

              $ make oldconfig

              $ make menuconfig

              $ make zImage

 

l  Linux内核配置选项

$ make menuconfig弹出内核配置菜单,熟悉选项的内容和含义,才能有效的裁减内核,配置相关功能。

主要配置菜单项图样说明:

图4.4.1 主配置菜单

 

图4.4.2 使能内核模块动态加载

 

 

图4.4.3 平台系统支持选项

图4.4.4 RAMDISK设备配置选项

 

图4.4.5 内核命令行参数设置

 

图4.4.6 MTD设备配置选项

 

 

图4.4.7 MTD设备FLASH芯片配置选项

 

图4.4.8 MTD设备地址映射配置选项

图4.4.9 字符设备虚拟终端配置

图4.4.10  S3C2410串口终端配置选项

 

图4.4.11 文件系统配置选项

图4.4.12 LCD framebuffer配置选项

图4.4.13  NFS配置选项

图4.4.14  USB配置选项

图4.4.15 为USB存储盘配置SCSI

图4.4.17  Linux内核调试选项

熟悉内核配置选项的功能,编译Linux内核最小配置,加载执行。然后根据需要添加一些模块,再编译。

4.5 移植Linux内核

l  移植概述

在本试验中所用的Linux 2.6.11内核已经加入了对S3C2410芯片的支持,不再需要打补丁文件。但是对外围设备的支持还需要根据板子所用的不同芯片作相应的移植。例如NAND Flash分区,需要针对硬件配置,在arch/arm/mach-s3c2410/devs.c或者smdk2410.c中添加FLASH的分区信息s3c_nand_info,

然后在s3c_device_nand中增加.dev={.platform_data= &s3c_nand_info},在arch/arm/mach-s3c2410/mach-smdk2410.c中的__initdata部分增加&s3c_device_nand,使内核在启动时初始化NAND FLASH信息。

其他的例如,选择支持所用的各类文件系统(DEVFS、TMPFS、CRAMFS、YAFFS、EXT2、NFS)以及网络设备和协议,还需要加载网络芯片CS8900以及USB支持等。

 Linux 支持多种文件系统,可以使用CRAMFS格式的根文件系统,将FLASH中的USER区使用支持可读写的YAFFS文件系统格式等。YAFFS文件系统格式的支持需要将驱动加入到内核代码树下fs/yaffs/,修改内核配置文件,就可以在内核编译中加载对该文件系统的支持。使用mkyaffs工具将NAND FLASH分区格式化为YAFFS分区,将mkyaffsimage生成的应用程序镜像烧写进YAFFS分区,在启动时通过写入fstab自动加载YAFFS分区即可。

系统中采用CS8900A的10M网络芯片,它使用S3C2410的nGCS3和IRQ_EINT9,相应修改linux/arch/arm/mach-s3c2410/irq.c,并在mach-smdk2410.c的smdk2410_iodesc[]中增加{SMDK2410_ETH_IO,S3C2410_CS2, SZ_1M, MT_DEVICE},内核源码中加入芯片的驱动程序drivers/net/arm/cs8900.h和cs8900.c,并且配置网络设备驱动的Makefile和Kconfig文件,加入CS8900A的配置选项,这样可以在内核编译时加载网络设备的驱动。

这样,就可以支持一个较为完整的内核。

l  实验目的:

分析Linux内核源码组织结构,基于Linux-2.6.11内核移植,添加对实验板的支持。

l  准备Linux-2.6.11 simetec bast 内核源码

解压linux-2.6.11内核源码,并打补丁

$ cd ~/workspace

$ tar -jxvf  /mnt/cdrom/s3c2410_linux/resource/linux-2.6.11.tar.bz2

$ cd linux-2.6.11

$ bzcat /mnt/cdrom/s3c2410_linux/resource/patch-2.6.11-bk10.bz2 | patch -p1

$ bzcat mnt/cdrom/s3c2410_linux/resource/patch-2.6.11-bk10-bast.bz2 | patch -p1

$ cp arch/arm/configs/smdk2410_defconfig .config

$ make menuconfig

确认内核编译配置了FSS3C2410 SMDK2410板子支持,退出,

$ make zImage

检验内核源码是否能够编译通过。说明内核源码没有编译上的问题。

 

l  移植修改代码支持fs2410

在本教程中已经将所作的修改工作制作成一个patch补丁文件,所以只需要打补丁,

$ cat ../ patch-2.6.11-bk10-bast-fs2410 | patch –p1

主要涉及的目录和文件:

arch/arm/kernel/head.S 添加平台号识别代码

arch/arm/mach-s3c2410/mach-smdk.c 主要是添加了网络驱动支持

include/asm-arm/arch-s3c2410/smdk2410.h 主要是添加用到的宏定义

driver/net/arm/Kconfg 添加网卡驱动配置支持

driver/net/arm/Makefile 添加网卡驱动编译支持

arch/arm/configs/smdk2410_def 默认的内核配置文件

具体内容参考patch-2.6.11-bk10-bast-fs2410,将围绕patch文件作详细讲解。

配置编译内核,如果出错还得根据相应的错误作一些修改。

修改基本完成后,把内核选项配置成最小,然后编译。

$ make menuconfig

$ make zImage

编译过程出现错误,常见的问题是:

找不到头文件或者宏定义,这在编译*.o文件的时候就会发现,需要查找相关的头文件;或者是因为宏定义的函数出错。

函数没有定义,一般是在连接vmlinux的时候出现,说明这个函数没有实现;

一些语法错误,一般是编辑过程的失误导致;还有函数参数不一致的问题,可以修改;有些串口初始化函数在串口驱动程序中,没有包含进来,可以先注释掉。

最后,生成的内核可以通过u-boot加载zImage启动,看能够执行到什么程度。

 

l  内核调试手段预习

在移植内核的过程中需要一些调试手段,一般会有以下几种:

1)      Printk/Printascii:

通过串口打印信息可以知道代码执行情况。在没有硬件调试器时,这种方法是最好方法了。

2)      Kgdb

内核GDB调试手段可以通过定义一个串口作为通讯手段,通过主机arm-linux-gdb连接,可以单步执行代码。

这种方法可以获得内核代码执行的更多信息。

3)      Light LED

通过点亮LED的方式不失为一种简单有效的调试手段。

4)       硬件调试器BDI2000

具体细节参考讲义中调试部分。


 4.6 调试Linux内核

l  实验目的:

继续移植2.6.11内核,添加串口和网络驱动程序,掌握获取log_buf信息方法和Linux内核调试技术。

l  移植内核步骤

参考讲义和补丁文件,移植实验板的Linux-2.6.11内核,支持fs2410板子。

首先移植基本的Linux内核,确认内核支持串口,然后添加以太网口的支持。

主要涉及的文件:

drivers/net/arm/cs8900.c

drivers/net/arm/cs8900.h

drivers/net/Kconfig

drivers/net/Makefile

 

可以手工把patch-2.6.11-bk10-bast-fs2410内容添加到内核中,要理解每一步改写的意义。

在Linux内核启动过程中添加打印信息,辅助理解和调试Linux内核启动过程

 

l  printk和log_buf[]

分析kernel/printk.c代码,可以知道printk打印信息是缓存在log_buf[]数组中的,Syslog系统调用把系统信息记录在缓冲区里面。

log_buf[]长度为16KB,是全局的静态字符串数组。从System.map中可以找到这个全局变量的逻辑地址。直接使用grep命令搜索,或者打开System.map文件查找。

$ grep  log_buf  ./System.map

c01458f8 b log_buf

log_buf的地址为:0xc01458f8

如果使用支持MMU的仿真器,可以直接把log_buf地址开始的16KB内存dump到开发主机上。比如BDI2000就有这样的功能。

如果printk不能正常打印时,可以通过讲义上所说的改动printk,用printascii将log buffer中内容打印到串口。从而获得调试信息。如果没有支持仿真器,也可以借助u-boot的调试功能。 引导Linux启动后,不要让板子断电,通过reset键复位,这样可以是内存的内容保持不变。然后进入u-boot,使用md命令显示内存中的内容。Log_buf的地址需要把Linux内核虚拟基地址0xc0000000替换为物理地址的基地址,偏移地址不变。

Linux内核虚拟基地址:  0xc0000000

物理地址的基地址:    0x30000000

显示内存信息:

 

 

 

l  分析Linux内核启动信息

Linux启动过程中,通过printk打印初始化和启动的信息。通过分析这些信息,可以了解到各阶段的状况和已经初始化的设备。当然也可以辅助调试Linux系统。

根据3.5节中Linux内核启动源代码分析,对照分析Linux完整的启动信息,掌握Linux启动过程。

l  试验

按照patch中打上cs8900a网卡的支持后,编译内核选中网络支持:

 

配置内核挂载NFS启动,如果能够正确挂载主机上的NFS Rootfs说明网络工作正常。

启动Linux之后,在控制台下:

#ifconfig –a

看看是否有Eth0网络接口出现,如果有进行下面试验:

#ifconfig eth0 192.168.1.2 up

#ifconfig

检查是否正确配置网址。再通过ping命令测试是否工作正常。


4.7 制作Linux文件系统

l  实验目的

熟悉Linux文件系统目录结构,创建自己的文件系统,通过NFS方式集成测试,用文件系统生成ramdisk、jffs2和yaffs文件系统映象文件。

l  下载buxybox源码

              从http://busybox.net/downloads/下载最新的busybox-1.1.0.tar.bz2,或从光盘目录

~workspace/FS2410_V60/Linux/busybox/ 中找到busybox-1.1.0.tar.bz2,用命令“tar jxvf” 把 busybox-1.1.0.tar.gz解压,将生成busybox-1.1.0的目录。

l  配置busybox

进入busybox-1.1.0目录,运行make menucofig,进入配置界面,并做出如下配置(其它配置遵循busybox的默认配置即可,或者按照需要进行选项修改):

(1)进入“General Configuration ---> ”菜单,添加“Support for devfs”选项,然后回到主菜单。

      [*] Show verbose applet usage messages

      [ ] Support --install [-s] to install applet links at runtime     

      [ ] Enable locale support (system needs locale for this to work)  

      [*] Support for devfs                

      [*] Use the devpts filesystem for Unix98 PTYs 

[ ] Clean up all memory before exiting (usually not needed) 

(2)进入“Build Options  --->”菜单,添加交叉编译工具配置

         [ ] Build BusyBox as a static binary (no shared libs)

         [ ] Build with Large File Support (for accessing files > 2 GB) 

         [*] Do you want to build BusyBox with a Cross Compiler?       

         (/usr/i386-linux-uclibc/bin/i386-uclibc- ) Cross Compiler prefix 

         ()  Any extra CFLAGS options for the compiler? 

选中“Cross Compiler prefix”,回车,然后输入交叉编译工具所在目录及前缀(提示:按下Ctrl键,再按Back Space可删除字符),例如:

/usr/local/arm/3.3/bin/arm-linux-

(3) 进入“Linux Module Utilities  --->”,如下配置

         [*] insmod           

                [*]   Module version checking       

              [ ]   Add module symbols to kernel symbol table (NEW)      

              [ ]   In kernel memory optimization (uClinux only) (NEW)          

              [ ]   Enable load map (-m) option (NEW)    

         [*] rmmod    

         [*] lsmod        

              [ ]   lsmod pretty output for 2.6.x Linux kernels  (NEW)    

         [*] modprobe           

             [*]   Multiple options parsing (NEW)     

         ---   Options common to multiple modutils      

         [*]   Support tainted module checking with new kernels (NEW)      

         [*]   Support version 2.2.x to 2.4.x Linux kernels (NEW)          

         [*]   Support version 2.6.x Linux kernels (NEW)

      (4)进入“Networking Utilities  --->”,去掉“Route”选项

(5)进入“Linux System Utilities  --->”,添加“Support mounting NFS file systems”

选项

      (6)退出配置界面,并保存配置。

 

l  编译busybox

运行make命令,编译busybox

 

l  安装busybox

运行make install,将在busybox目录下生成_install的目录,里面就是busybox生成的工具。(选用:或者运行make PREFIX=<directory name> install命令,将把busybox安装到PREFIX制定的目录。)

 

l  创建目录结构

进入_istall目录,建立目录:

#mkdir dev etc lib mnt proc var tmp

l  建立初始化启动所需文件

可以按照下面手动创建,或者从光盘提供的资料中拷贝相关文件,所在目录:

~workspace/FS2410_V60/Linux/busybox/

1)etc目录,建立inittab文件,文件内容:

              #this is run first except when booting in single-user mode.

::sysinit:/etc/init.d/rcS

            # /bin/sh invocations on selected ttys

# Start an "askfirst" shell on the console (whatever that may be)

            ttyS0::askfirst:-/bin/sh

            # Stuff to do when restarting the init process

            ::restart:/sbin/init

            # Stuff to do before rebooting

            ::ctrlaltdel:/sbin/reboot

2)  进入etc目录,建立init.d目录,进入init.d目录,建立rcS文件,文件内容如下:

            #!/bin/sh

            # This is the first script called by init process

            /bin/mount -a

            /sbin/syslogd

            exec /usr/etc/rc.local

   保存退出, 运行:

#chmod  777  rcS ,改变文件权限。

 

3)  etc目录,建立fatab文件和profile文件,文件内容分别如下所示:

Fstab文件内容:

          none            /proc           proc    defaults        0 0

          one            /dev/pts        devpts  mode=0622       0 0

          mpfs           /dev/shm        tmpfs   defaults        0 0

          one            /proc/bus/usb   usbdevfs defaults       0 0

            profile文件内容:

          # /etc/profile

          PATH=/bin:/sbin:/usr/bin:/usr/sbin

          LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH

          export PATH LD_LIBRARY_PATH

      其中profile用于设置shell的环境变量,shell启动时会读取/etc/profile文件,来设置环境变量。

 

4)进入usr目录,建立etc目录。

5)进入etc目录,建立rc.local文件,文件内容如下:

            #!/bin/sh

            #add user specified script

            cd /dev

            ln -s /dev/fb/0 fb0

            ln -s vc/0 tty0

            ln -s vc/1 tty1

            ln -s vc/2 tty2

            if [ -e /dev/ttySA0 ]; then

                 echo "/dev/ttySAx exist."

            else

                 mknod ttySA0 c 204 5

                 mknod ttySA1 c 204 6

                 mknod ttySA2 c 204 7

            fi

保存退出, 运行:

#chmod  777  rc.local ,改变文件运行权限

 

l  创建其他设备节点

如果内核配置了devfs,就不需要创建这些节点了。(选用:如果用光盘中提供的MAKEDEV脚本,可以很快的创建所有节点。切换到dev目录直接运行./MAKEDEV教本)

# cd dev/

# mknod –m 660 mtd0 c 90 0

# mknod –m 660 mtd1 c 90 2

# mknod –m 660 mtd2 c 90 4

# mknod –m 660 mtdblock0 b 31 0

# mknod –m 660 mtdblock1 b 31 1

# mknod –m 660 mtdblock2 b 31 2

 

l  添加应用程序

如果有自己的应用程序可以放到usr/bin目录下。

              例如:

              Cp ./helloworld _install/usr/bin/

l  添加busybox所需要的动态运行库

进入bin目录,运行如下命令检查busybox所需要的动态共享库

/usr/local/arm/3.3/bin/arm-linux-readelf –a busybox | grep NEEDED,

该命令输出“0x00000001 (NEEDED) Shared library: [libc.so.6]”,表明busybox正常执行时需要libc.so.6(它是个符号链接,指向libc-2.2.3.so文件)动态共享库。

从/usr/local/arm/3.3/arm-linux/lib目录下拷贝ld-2.2.3.so和libc-2.2.3.so文件到lib目录下,进入lib目录,建立符号链接:

ln  –s  ld-2.2.3.so  ld-linux.so.2

ln  –s  libc-2.2.3.so  libc.so.6

其他用到的库还有:

libnss_dns.so.2,libnss_files.so.2,libresolv.so.2 等。

改变库文件访问权限: chmod  766  *

 

l  NFS测试

在主机端修改/etc/exports,添加一行,输出定制的文件系统目录。然后配置Linux内核命令行参数,挂接定制的文件系统。

或者把/opt/target/rootfs目录暂时改名,将定制的文件系统复制过来。

# cd /opt/target/

# mv rootfs rootfs_default

# cp –a  ~/workspace/myfs ./

# mv myfs rootfs

重起一下NFS服务,启动target,挂接NFS文件系统;直到测试文件系统通过。

l  可能的问题

如果编译busybox时,选用动态共享库,需要把动态共享库和动态链接程序(ld-2.2.3.so)拷贝/lib目录下。内核加载init(连接到busybox)时需要用到,在shell没有启动,无法读取环境变量的情况下,init进程可能无法启动,所以,可以在内核启动命令行参数中添加“LD_LIBRARY_PATH=/lib”,从而找到所需要的动态库:

CMDLINE ““console=ttySAC0,115200, LD_LIBRARY_PATH=/lib…….”

l  制作ramdisk教本

#!/bin/sh

dd if=/dev/zero of=./ramdisk bs=6144 count=1024

/sbin/mke2fs -q -F ./ramdisk

mount -t ext2 ./ramdisk /mnt/ramdisk -o loop

cp -av ./root/* /mnt/ramdisk/

umount /mnt/ramdisk

dd if=./ramdisk bs=6144 count=1024 | gzip -v9>./ramdisk.gz

l  制作root.yaffs文件系统映象

光盘提供的mkyaffsimage程序(~workspace/FS2410_V60/Linux/busybox/)可以制作yaffs格式的文件系统。

./mkyaffsimage rootfs rootfs.yaffs

 

l  最后

已建立好根文件系统,以后实验可以使用这个根文件系统制作ramdisk、jffs2.mig、yaffs.img映象文件,这三种映象文件的文件系统格式分别为ext2、jffs2、yaffs,并可以作为根文件系统进行挂载。


4.8 部署Linux系统

l  实验目的

把Linux内核影像和文件系统影像烧写到存储设备中,使实验板Linux系统能够独立启动并运行。

 

l  烧写FS2410 BIOS到板子的NAND FLASH(参考fs2410使用手册中的操作过程)

2.6节,《用sjf2410工具将bios烧写到nand flash》

l  重启,用FS2410 BIOS引导板子

 

l  配置Linux 内核 MTD NAND FLASH和YAFFS

MTD内核配置编译

NAND Flash Device Drivers  --->目录下

在arch/arm/mach-s3c2410/mach-smdk.c中定义了分区表,把整个NAND FLASH芯片的地址空间分为几个分区,可以分别擦写使用。

l  配置Linux 内核YAFFS和JFFS2文件系统支持

 

l  配置内核命令行

配置Linux内核命令行参数:

对于YAFFS文件系统,在/dev/mtd/2分区上

root=/dev/mtdblock2 rw console=ttySAC0,115200

              编译出zImage,准备烧写到flash.

l  通过BIOS工具将内核和根文件系统写到FLASH设备中

参考fs2410手册的第三章 《烧写和启动Linux》,

3.1节《烧写Linux内核》,将上面编译好的zImage烧写到NAND Flash的第一块。

3.2节《烧写根文件系统》,将上节制作好的root.yaffs(参考上节中的yaffs文件系统制作)烧写到NAND Flash的第二块。

3.3节 《启动linux》。确保正确挂载文件系统,启动控制台。