Twentyone xuzhouhe@hotmail.com
4510B BOOTLOADER (uCLinux)的实现与分析
前段时间,写了一个简单直接的 4510B bootloader 用来装载 uclinux 内核。在这篇文档
里,我将结合源代码介绍一下 4510 Bootloader 的实现和分析。源代码很简单很直接,希
望对初学的朋友有点帮助。也许在分析中有很多错误,希望大家能够指正,共同进步。
Bootloader 的实现基于三星 4510B,假设开发板上有 2M 的 Flash 作为 Boot rom,SDRAM
的大小是 16M。开发环境是 ADS1.2。
一.相关介绍
1.Bootloader 的基本任务
Bootloader 一般是被烧录或者下载到 bootrom 的 0x0 地址处,作为上电后执行的第一部分
指令,bootloader 需要完成两个任务:(1). memory remap, (2). 把 kernel 装载到 SDRAM 里
合适的位置上去。在完成这两个任务后,bootloader 就“功成身退”了。
下面的讨论都只围绕这两个任务进行。其他跟这两个不相关的东东不在介绍范围之内,
相关的东东我都会顺带介绍。
2. 4510B 开发板上的存储介质
一块 4510B 开发板上,一般有三种存储介质:FLASH,SDRAM 和 4510 片内的 SRAM。
FLASH 一般是只读的(只在运行的时候),而且一般都作为 bootrom 使用,因为 FLASH
里存储的
内容
财务内部控制制度的内容财务内部控制制度的内容人员招聘与配置的内容项目成本控制的内容消防安全演练内容
在掉电的时候也不会丢失,所以 flash 很适合作为 bootrom,用来保存
bootloader。SDRAM,大家应该都很熟悉的,是可读写的存储介质,速度比 FLASH 快的
多,在系统运行的时候,SDRAM 是主要的存储介质。但 SDRAM 里的数据在掉电后即消
失,无法用来保存数据。所以每次启动的时候都需要 bootloader 将内核装重新装载到
SDRAM 里去。在 4510 的片内还集成了 8k 的 SRAM,SRAM 也是可读写的,一般作为系
统的 cache 使用。
3.一般程序结构
简单来说,一般的可执行程序都包括代码段、数据段和 BSS 段。也可以简单的看作由两
部分组成:RO 段和 RW 段。RO 段一般包括代码段和一些常量,在运行的时候是只读
的。而 RW 段包括一些全局变量和静态变量,在运行的时候是可以改变的(读写)。如
果有部分全局变量被初始化为零,则 RW 段里还包括了 ZI 段。
RO: Read Only
RW: Read Write
ZI: Zero Init
1
Twentyone xuzhouhe@hotmail.com
因为 RO 段是只读的,在运行的时候不可以改变,所以,在运行的时候,RO 段可以驻留
在 Flash 里(当然也可以在 SDRAM 或者 SRAM 里了)。而 RW 段是可以读写的,所
以,在运行的时候必须被装载到 SDRAM 或者 SRAM 里。
在用 ADS 编译的时候,是需要设置 RO BASE 和 RW BASE 的,用过 ADS 的应该都清楚
这点。通过 RO BASE 和 RW BASE 的设置,告诉链接器(linker)该程序的起始运行地
址(RO BASE)和 RW 段的地址 (RW BASE)。如果一个程序只有 RO 段,没有 RW 段,
那么这个程序可以完全在 Flash 里运行,不需要用到 SDRAM 或者 SRAM。如果包括 RW
段和 RO 段,那么该程序的 RW 段必须在被访问以前被拷贝到 SDRAM 或者 SRAM 里
去,以保证程序可以正确运行。下面这个图说明了一个程序执行前(load view)和执行
时(execute view)的状态。从图中可以看到,整个程序在执行前始放在 ROM 里的,在
执行的时候,RW 段被拷贝到了 RAM 里的合适位置去。
在ADS里,有一些预先定义了的变量可以用(linker defined symbol)。在下面的实现里,
用到了几个预定义的变量:
Image$$RO$$Base 该变量指定了RO段的 BASE
Image$$RO$$Limit 该变量指定了RO段的 Limit
Image$$RW$$Base 该变量指定了RW段的 BASE
Image$$RW$$Limit 该变量指定了RW段的 Limit
Image$$ZI$$Base 该变量指定了ZI段的 BASE
Image$$ZI$$Limit 该变量指定了ZI段的 Limit
注:具体可以参考 ADS Linker Guide
Image$$RO$$Limit 减 Image$$RO$$Base 等于 RO 段的大小
Image$$RW$$Limit 减 Image$$RW$$Base 等于 RW 段的大小
Image$$ZI$$Limit 减 Image$$ZI$$Base 等于 ZI 段的大小
(Image$$RO$$Limit 减 Image$$RO$$Base)
+ (Image$$RW$$Limit 减 Image$$RW$$Base)
= 等于整个程序的大小
注:ZI 段始包括在 RW 段里面的。
2
Twentyone xuzhouhe@hotmail.com
二.实现与分析
在这部分里,我将介绍 bootloader 的实现与分析。这部分主要围绕 bootloader 需要完成的
两个基本任务展开。分析将结合源代码进行。按照运行的步骤和代码实现的功能,源代
码被分成了几个 part,方便网友阅读。主要的代码包括 init.s 和 loadkernel.c 和 kernel.c 三
个文件。
Bootlaoder:RO BASE = 0x0 RW BASE = 0xA00000
1.初始化
Part 1:进入 SVC32 工作模式,并且禁止所有的中断。
Part 2:设置 SYSCFG 寄存器。(具体参考 4510 的数据
手册
华为质量管理手册 下载焊接手册下载团建手册下载团建手册下载ld手册下载
)
Part 3:导入一些链接器预定义的关键变量,在程序的后面将要使用到。
2.Memory Remap 的实现:
Part 4 – 6 实现了 memory remap。
Part 4:初始化 SDRAM
板子刚上电后,只有 Flash 是可用的(在这里我们不考虑片内 SRAM)。SDRAM 没有被
初始化,是不可用的。此时的 memory map 请参照图 1,flash 被 map 到 0 - 2M 的地方。
所以,现在需要做的第一件事情是初始化 SDRAM。Part 4 的代码完成 SDRAM 的初始
化。在这部分代码执行完成后,SDRAM 被 map 到了 2 - 18M 的位置上。此时的
memory map 请参照图 2,0 - 2M 是 flash,2 - 18 M 是 SDRAM。
Part 5:
在这部分代码执行前,bootloader 一直是在 flash 里面执行的。为了实现 memory remap,
并且保证 remap 后 bootloader 可以继续执行,part 5 的代码把 bootloader 自己全部拷贝到
SDRAM 里面去。这样,在 SDRAM 里就有了一个 bootloader 的拷贝。但是此时
bootloader 还是在 flash 里面执行。此时的 memory map 请参照图 3,0 - 2M 还是 flash,2
- 18 M 还是 SDRAM,但此时 SDRAM 里面多了一个 bootloader 的拷贝。
Part 6:
这部分代码实现了 memory remap。在这部分代码执行完成后,memory map 变成了图 4 的
样子,SDRAM 在 0-16M 的位置,而 flash 被 map 到了 16-18M 的位置。由于在 SDRAM
有一份 bootloader 的拷贝,所以在 remap 后 bootloader 可以继续执行。但在 remap 以后
bootloader 现在转到 SDRAM 里面来执行了。通过图 3 和图 4 的比较,我们很容易分析出
为什么在 remap 前后 bootloader 还能正常的执行。仔细琢磨琢磨!
代码 part 4 - 6 实现了 memory remap 的过程,SDRAM 被 map 到地址 0X0 开始的地方,
完成了 bootlaoder 的第一个任务。至此,在 SDRAM 里有一个 bootlaoder 的拷贝,所以
flash 我们已经不用在关心它了。下面的部分将介绍如果完成第二个任务:内核的装载。
3
Twentyone xuzhouhe@hotmail.com
Flash @ 0-2M
Bootloader
Flash @ 0-2M
Bootloader
SDRAM @ 2-18M
Flash @ 0-2M
Bootloader
SDRAM @ 2-18M
Bootloader
Flash @ 16-18M
Bootloader
SDRAM @ 0-16M
Bootloader
图 1 图 2 图 3 图 4
Memory map 的变化过程
3.Kernel 的装载:
Part 7 & 8 的代码完成内核装载前的一些处理工作。C 语言函数 loadkernel()完成 kenrel
的装载。下面让我们看看这个过程是如何实现的。
Part 7:
在此之前的执行都没有涉及到 RW 段,因为我们还没有用到 RW 数据。为了让后面的程
序能够顺利的执行下去,我们必须把 RW 段拷贝到 RW BASE 指定的位置上去,并且把
RW 段里的 ZI 段初始化为零。Part 7 部分的代码完成这些操作。
Part 8:
这部分代码设置堆栈指针 = 0x800000 ,然后跳转到 C 函数 loadkernel 里去。至此,init.s
执行完毕,内核的装载将由 loadkernel.c 里的函数 loadkernel 完成。
Loadkernel():
编译好 uclinux 后,会生成两个二进制内核镜像文件: image.ram 和 image.rom。
image.rom 是由一个 bootloader 加上 image.ram 生成的,所以可以自启动。而 image.ram 是
一个纯粹的内核镜像文件,其运行的起始地址是 0x8000,所以不能直接运行,需要由
bootloader 将其装载到 SDRAM 的 0x8000 处,才能启动。
现在的问
题
快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题
是:bootloader 需要把 image.ram 装载到 ox8000 处,但在 bootloader 的实现里
如何确定 image.ram 的位置呢?有一个很巧妙的办法可以解决这个问题。如果我们将
image.ram 转换为一个 char 数组,那我们就可以直接通过这个数组名引用 image.ram,而
不需要知道链接器把 image.ram 到底放到什么地方去了。
在源代码里,有一个 kernel.c 文件,这个 c 文件里只包括了一个很大数组 char kernel[],
这个数组是由 bin2c 软件生成的,(bin2c 软件我在源代码里一起给出了)。
4
Twentyone xuzhouhe@hotmail.com
好了,现在看看 loadkernel.c 里的 loadkernel 函数,该函数通过对数组 kernel[]的引用,将
内核装载到 0x8000 处,然后将控制权交给内核。这时候,bootloader 的任务就全部完成
了。顺利的话,内核就应该会自己启动了。在 loadkernel()函数里面,我们定义了一个
函数指针 fp,并且让它指向地址 0x8000。这样的话,调用函数 fp()就相当于让 pc 指
针指向地址 0x8000,把控制权交给了内核。
OK,到此结束,bootlaoder 的两个基本任务都完成了,kernel 也启动起来了。:)
三.ADS 和源代码相关
源代码包括下面一些文件:
init.s snds.s kernel.c loadkernel.c typdef.h
要用 ADS 编译,只需要建立一个新的项目,然后把这几个文件导入就可以了。但有几个
地方需要设置一下,以保证可以正确运行。
1.在 ARM Linker - > Output 里,设置 RO BASE = 0X0, 设置 RW BASE = 0xA00000
2.在 ARM Linker - > Layout 里:
这样就保证了链接的时候,在地址 0x0 处是我们期望执行的第一条指令。
5
Twentyone xuzhouhe@hotmail.com
后记:
在这篇文档里,对一个最简单直接的 bootloader 的实现作了一个简单的分析,希望对大家
有点点帮助,如果有什么不对的地方希望大家指正。另外,为了简单起见,内核没有被
压缩,也不支持网络下载功能。如果想实现一个更紧凑的 bootlaoder,可以先将
image.ram 压缩后在转化成一个 char 数组。在把 kernel 装载到 0x8000 前先解压缩。如果
要支持网络下载,可以添加一个简单的 ip
协议
离婚协议模板下载合伙人协议 下载渠道分销协议免费下载敬业协议下载授课协议下载
栈。
Twentyone 2004-5-3
6