您的位置:首页 > 其它

关于嵌入式系统的启动

2009-12-29 13:03 423 查看
关于嵌入式系统的启动
(SHARE FOR ALL)


嵌入式
Linux
启动分为两个部分,系统引导与
Linux
启动。系统引导将完成
Linux
装入内存前,初始化
CPU
和相关
IO
设备,并将
Linux
调入内存的工作。系统引导主要由
BootLoader
实现。在
BootLoader

Linux
内核调入内存之后,将权力交给
LinuxKernel
,进入
Linux
的启动部分。以下详细分析启动的过程与使用的文件。

一、系统引导与
BootLoader

BootLoader
因嵌入式系统的不同与
PC
机有很大不同,这里将以
Hyper250(Inter Xscale GDPXA250)
的启动为例来分析。由于没有
BIOS
驱动主板,
EnbeddedOS
必须由
bootloader
驱动所有的硬件,并完成硬件的初始化工作。


所有的初始化文件在
hyper250/Bootloader
目录下。




首先分析开机运行的分件:

hyper250/Bootloader/X-Hyper250R1.1-Boot/src/start_xscale.S


文件包含两个库文件:

hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/config.h

hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/start_xscale.h


文件
config.h
主要完成系统各硬件的宏定义与设定,
xscale.h
主要完成对系统芯片的及系统操作的设定。


以下分析
config.h
文件:

(1)
存储总线设备的宏定义:定义
Flash
的大小、字长等信息,定义
SRAM
的基址、大小和块大小。

(2)
动态内存设定:定义
DRAM
的大小、基址。

(3)
软件包信息:包名称、版本号。

(4)
设定
BOOT LOADER
的位置:在
DRAM

SRAM
的最大值、
DRAM
装入位置、栈的基址。

(5)
设定
kernel
的位置:在
DRAM

SRAM
的基址、
KERNEL
的最大值、
KERNEL
中块的数量。

(6)
设定文件系统的位置:根目录在
DRAM

SRAM
的基址、文件系统的最大值、文件系统中块的数量。

(7)
设定
LOADER
程序:
LOADER
程序的静态内存基址、
LOADER
程序的最大值、块的数量。

(8)
网络设定




以下分析
start_xcalse.h
文件:

(1)
定义内存基址
(A0000000)

(2)
定义中断基址
(40D00000)
和中断保护栈的偏移量

(3)
定义时钟管理基址
(41300000)
和寄存器偏移及其初始值

(4)
定义
GPIO
接口寄存器基址
(40E00000)
及各寄存器的偏移

(5)
定义
GPIO
接口各寄存器的初始值

(6)
定义内存控制寄存器基址
(48000000)
和各寄存器的偏移

(7)
定义内存控制寄存器的初始值

(8)
定义电源管理寄存器的参数

(9)
定义
FFUART
寄存器的基址
(40100000)
和各寄存器的偏移

(10)
定义
FFUART
各寄存器的初始值




以下分析
start_xcalse.S
文件:

(1)
设定中断基址
(40D00000),
完成中断保护栈的初始化

(2)
初始化
GPIO
接口

(3)
初始化内存
SDRAM

(4)

Bootloader

Flash
拷贝到
SDRAM


(5)
装入
Linux
内核镜像
,
将内核从
Flash(000C 0000)
装入
SDRAM(A0008000)

.

(6)
设定保护栈

(7)
调用
main.c
的主函数
c_main()




以上
start_xcalse.S
通过
APCS
的编程标准书写的汇编文件初始化了系统相关的硬件
,
并且完成了
BootLoader
的装入内存和
Linux
内核的装入
,
最后将权力转交给
main.c



以下将分析
main.c
文件
:

hyper250/Bootloader/X-Hyper250R1.1-Boot/src/main.c


以及两个库文件

hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/main.h

hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/scc.h

#2


二、
Linux
启动过程分析

1.Makefile
分析:


在分析
arch/arm/boot/compressed
目录下的文件的时候,对于
Makefile
的分析是很重要的,因为内核将在这个目录相产生。这里主要工作是对内核的压缩和解压工作。本目录在编译完成后将产生
vmlinux

head.o

misc.o

head-xscale.o

piggy.o
这几个文件。其中
vmlinux

没有

压缩过的内核。
head.o
是内核的头部文件,负责初始设置。
misc.o
将主要负责内核的解压工作,它在
head.o
之后。
head-xscale.o
文件主要针对
Xscale
的初始化,将在链接时与
head.o
合并。
piggy.o
是一个中间文件,其实是一个压缩的内核,只不过没有和初始化文件及解压文件链接而已。



2.Decompress
分析:



BootLoader
完成系统的引导以后并将
Linux
内核调入内存之后,调用
bootLinux()
,这个函数将跳转到
kernel
的起始位置。如果
kernel
没有压缩,就可以启动了。如果
kernel
压缩过,则要进行解压,在压缩过的
kernel
头部有解压程序。压缩过得
kernel
入口第一个文件源码位置在
arch/arm/boot/compressed/head.S
。它将调用函数
decompress_kernel()
,这个函数在文件
arch/arm/boot/compressed/misc.c
中,
decompress_kernel()
又调用
proc_decomp_setup(),arch_decomp_setup()
进行设置,然后使用在打印出信息
“Uncompressing Linux...”
后,调用
gunzip()
。将内核放于指定的位置。


启动首先运行的文件有:

arch/arm/boot/compressed/head.S

arch/arm/boot/compressed/head-xscale.S

arch/arm/boot/compressed/misc.c


这些文件主要用于解压内核和以及启动内核映象。一旦内核启动,则这些文件所占内存空间将被释放。而且,一旦系统通过
reset
重起,当
BootLoader
将压缩过的内核放入内存中,首先执行的必然是这些代码。




以下分析
head.S
文件:

(1)
对于各种
Arm CPU

DEBUG
输出设定,通过定义宏来统一操作。

(2)
设置
kernel
开始和结束地址,保存
architecture ID


(3)
如果在
ARM2
以上的
CPU
中,用的是普通用户模式,则升到超级用户模式,然后关中断。

(4)
分析
LC0
结构
delta offset
,判断是否需要重载内核地址
(r0
存入偏移量,判断
r0
是否为零
)



这里是否需要重载内核地址,我以为主要分析
arch/arm/boot/Makefile

arch/arm/boot/compressed/Makefile

arch/arm/boot/compressed/vmlinux.lds.in
三个文件,主要看
vmlinux.lds.in
链接文件的主要段的位置,
LOAD_ADDR(_load_addr)

0xA0008000
,而对于
TEXT_START(_text

_start)
的位置只设为
0

BSS_START(__bss_start)

ALIGN(4)
。对于这样的结果依赖于,对内核解压的运行方式,也就是说,内核解压前是在内存
(RAM)
中还是在
FLASH
上,因为这里,我们的
BOOTLOADER
将压缩内核
(zImage)
移到了
RAM

0xA0008000
位置,我们的压缩内核是在内存
(RAM)

0xA0008000
地址开始顺序排列,因此我们的
r0
获得的偏移量是载入地址
(0xA0008000)
。接下来的工作是要把内核镜像的相对地址转化为内存的物理地址,即重载内核地址。

(5)
需要重载内核地址,将
r0
的偏移量加到
BSS region

GOT table
中。

(6)
清空
bss
堆栈空间
r2

r3


(7)
建立
C
程序运行需要的缓存,并赋于
64K
的栈空间。

(8)
这时
r2
是缓存的结束地址,
r4

kernel
的最后执行地址,
r5

kernel
境象文件的开始地址。检查是否地址有冲突。



r5
等于
r2
,使
decompress
后的
kernel
地址就在
64K
的栈之后。

(9)
调用文件
misc.c
的函数
decompress_kernel()
,解压内核于缓存结束的地方
(r2
地址之后
)
。此时各寄存器值有如下变化:

r0
为解压后
kernel
的大小

r4

kernel
执行时的地址

r5
为解压后
kernel
的起始地址

r6

CPU
类型值
(processor ID)

r7
为系统类型值
(architecture ID)

(10)

reloc_start
代码拷贝之
kernel
之后
(r5+r0
之后
)
,首先清除缓存,而后执行
reloc_start


(11)reloc_start

r5
开始的
kernel
重载于
r4
地址处。

(12)
清除
cache
内容,关闭
cache
,将
r7

architecture ID
赋于
r1
,执行
r4
开始的
kernel
代码。




关于
head-xscale.S
文件,它定义了
xcale
处理器的
64k

cache
缓存的实现代码和关闭
MMU
及缓存的代码,这些代码将在链接过程中与
head.S
的合并。




关于
misc.c
文件,它引入了以下几个文件:

include/linux/kernel.h

include/asm-arm/arch-pxa/uncompress.h

include/asm-arm/proc-armv/uncompress.h

include/asm-arm/uaccess.h

lib/inflate.c


以下分析
misc.c
文件的
decompress_kernel()
函数:

(1)
首先传入参数:解压后内核地址,缓存开始地址,缓存结束地址,
arch id
。这些参数通过寄存器
r0(r5),r1,r2,r3(r7)
传入。

(2)
接着执行
proc_decomp_setup()
,它在
include/asm-arm/proc-armv/uncompress.h
文件中。主要刷新并起用
i cache
,锁住交换缓存,这是一段嵌入的
arm
汇编代码。

(3)
接着执行
arch_decomp_setup()
,它在
include/asm-arm/arch-pxa/uncompress.h
文件中,是一个空函数,用于扩展。

(4)
然后执行
makecrc()
,它在
lib/inflate.c
中,主要将产生
CRC-32 table
,进行循环冗余校验。

(5)
调用
gunzip()
解压
kernel
,它也在
lib/inflate.c
中。

(6)
返回
head.S
,解压后
kernel
的长度传给
r0
,解压后的内核地址预先在
r5
中定义了。


#3


3.kernel
进入文件分析:


随后系统将调入文件:
arch/arm/kernel/head_armv.S

arch/arm/kernel/head_armo.S
。对于
arm

kernel
而言,有两套
.S
文件:
_armv.S

_armo.S.
选择
_armv.S
还是
_armo.S
依赖于处理器。
ARM

version 1, version 2,
都只支持
26
位的地址空间。
version 3
开始支持
32
位的地址空间,同时还向后兼容
26
位的地址空间。
version 4
开始不再向后兼容
26
位的地址空间。这里由于
Hyper250
使用的是
version7
,故只涉及文件
head_armv.S



head_armv.S
是内核的入口点,在内核被解压到预定位置后,它将运行。
这里简要说明其主要工作:

(1)
首先,关中断并进入保护模式,这里将建立虚拟地址到物理地址的映射。
(
见第二章内存分析
)

(2)
调用
lookup_processor_type
,查询
CUP
和其
ID
是否在
.proc.info
表中,如果存在,则令
r10
指向此结构,在
CPU
的内核入口文件中。如果不是则提示
error

p
并挂起。关于
r10
指向的结构,他所属的内核入口文件,以
Hyper250
为例:
arch/arm/mm/proc-xcale.S



这里要要注意的是,此处操作的对象是由
vmlinux-armv.lds.in
链接文件定位的段
.proc.info
中,这个段定义在
proc-xcale.S
文件末尾,这里要注意,上面并没有使系统进入保护模式,所以在这里对
.proc.info
寻址的时候,为了得到相对地址,做了一个相对寻址的变换。这里好象只用了这个结构的前
3
位:处理器类型值
(value)
,处理器值掩码
(mask)

MMU
标志值
(mmuflags)
。这
3
个值在分别放在寄存器
r5(0x69052100)

r6(0xfffff7f0)

r8(0x00000c0e)
中,
r5

r6
只是用于和获得的处理器的
ID
相比较,而
r8
则有两个可能的值,分别表示
MMU
的状态:如果
MMU
开启,即
CACHE_WRITE_THROUGH
,则
r8=0x00000c0a
,否则
r8=0x00000c0e
。这里
r8
的值将会保持到初始页表时使用。

r10
此时指向段
.proc.info
的开始地址。

(3)
寄存器
r1
中的系统类型值
(unique architecture number)
,这个系统类型值的定义,并且由
bootloader
传入。在文件
arch/arm/tools/mach-types
中:

machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number

xhyper250R1 ARCH_PXA_XHYPER250R1 PXA_XHYPER250R1 200

(4)
调用
lookup_architecture_type
,将以
r1
的值检查
.arch.info
表,这是个
struct machine_desc
由文件
arch/arm/mach-pxa/xhyper250R1.c
中的
MACHINE_START()
创建。假如没有此结构则提示
error

a
并挂起。


这里要注意的是,段
.arch.info
的定位在
vmlinux-armv.lds.in
文件中紧接
.proc.info
,这个段定义在
include/asm-arm/mach/arch.h
文件中,使用了宏定义
MACHINE_START()
。文件首先定义了一个结构体
machine_desc
,段
.arch.info
主体部分使用了宏定义
MACHINE_START()
其中嵌入这个结构体。


通常来讲
MACHINE_START()
的实现应该在文件
arch/arm/kernel/arch.c
中,而这里
hyper250
的源码中,
MACHINE_START()
宏定义在
arch/arm/mach-pxa/xhyper250R1.c
中完成了定义,下面详细分析这个结构:

(A)MACHINE_START

MACHINE_START(_type,_name)
这宏开始处嵌入一个静态结构
machine_desc
,并且立即声明段
.arch.info


_type

MACH_TYPE(PXA_XHYPER250R1)
,用以赋值给
machine_desc
中的
nr
,这就是系统类型值
number(200)


_name
是描述系统类型的字符串,用以赋值给
machine_desc
中的
name

char*



以下几个宏定义均在包含在
machine_desc
的赋值中,也在段
.arch.info
中。

(B)MAINTAINER

MAINTAINER(n)
,这个
n
并没有赋值给
machine_desc
结构,
n

"Hybus Co,. ltd."
字符串,公司名字罢了。

(C)BOOT_MEM

BOOT_MEM(_pram,_pio,_vio)
,这里面很关键,又
3
个变量:

_pram
,传值给
phys_ram
:物理内存的开始地址,程序中赋值为:
0xa0000000


_pio
,传值给
phys_io
:物理
io
的开始地址,程序中赋值为:
0x40000000


_vio
,传值给
io_pg_offst

io
页表的偏移,程序中赋值为:
_vio=0xfc000000
,不过要进行转换:
((_vio)>>18)&0xfffc=0x3f00

(D)BOOT_PARAMS

BOOT_PARAMS(_params)
这个宏定义了启动参数页表的偏移:
param_offset
,程序中赋值为:
0xa0000100



(E)FIXUP(
接下来三个宏定义分别是三个函数指针:这些函数都在
machine_desc
结构中定义并且在
xhyper250R1.c
中实现。
)

FIXUP(fixup_xhyper250R1)
宏指向
fixup_xhyper250R1
函数,这个函数有
4
个参数:

fixup_xhyper250R1(struct machine_desc *desc, struct param_struct *params, char **cmdline, struct meminfo *mi)

struct machine_desc
:这个结构体前面已经提过了。

param_struct
:这个结构体定义在
include/asm/setup.h
中,这是一个向
kernel
传递参数的结构体。

char **cmdline
:好像用于定义输出窗口行数。

struct meminfo
:这个结构体定义在
include/asm/setup.h
中,这是一个对物理内存区间描述的结构体,它将整个地址空间分为
8
个区间,通常一个区必须是连续的地址并且是同一类型的设备,而用于特殊目的的地址将划分为一个独立的区。首先定义
nr_banks:
块号,然后是结构体
bank[NR_BANKS]

NR_BANKS

8
。结构体
bank[NR_BANKS]
中有:
start

size

node



下面分析这个函数
fixup_xhyper250R1
的工作,


首先,调用宏
SET_BANK
并赋值为
SET_BANK(0, 0xa0000000, 64*1024*1024)
,这个宏定义在
arch/arm/mach-pxa/generic.h
文件中。
SET_BANK
主要完成设置结构体
meminfo

bank[_nr]

start

size

node
。以上为例,则完成了
bank[0]
区间中的
start=0xa0000000

size=64*1024*1024=64M

node=(__start) - PHYS_OFFSET) >> 27=0


接着,使
mi

nr_banks=1
,好象设定了这个结构只有一个区。要注意的是
meminfo
将在
page_init()
中用于初始化页面。


(F)MAPIO

MAPIO(xhyper250R1_map_io)
宏指向
xhyper250R1_map_io
函数,这个函数没有参数,主要用于
io
地址从虚拟地址到物理地址的映射关系。


这个函数调用了
pxa_map_io()

iotable_init(xhyper250R1_io_desc)


pxa_map_io()
函数定义在
arch/arm/mach-pxa/generic.h
文件中,实现在
arch/arm/mach-pxa/generic.c
中,主要调用了
iotable_init()
函数来进行
io
地址的区间映象。
iotable_init(struct map_desc *)
函数中,参数
map_desc
结构体定义在文件
include/asm-arm/map.h
中,主要有:
virtual

physical

length
和一些标志位:
domain

read

write

cache

buffer
等。
iotable_init()
函数在文件
arch/arm/mm/mm-armv.c
中,循环调用
create_mapping()
来处理
map_desc
的映射关系。
create_mapping
函数主要工作就是将
io
的虚拟地址到物理地址的映射关系按照
PAGE_SIZE(4K)
的页来进行映射,同时还有段的映射关系。(关于内存的映射将在第
2
章中详细分析)


(G)INITIRQ

INITIRQ(xhyper250R1_init_irq)
指向了
xhyper250R1_init_irq
函数,这个函数将主要完成中断的初始化,这里主要调用了函数
pxa_init_irq()
,这个函数实现在
arch/arm/mach-pxa/irq.c
中。接着调用了
set_GPIO_IRQ_edge()
函数,这个函数也在
irq.c
中。(关于中断的分析将在以后进行)


我们以上通过分析宏
MACHINE_START
而分析了结构体
machine_desc
的一个实例的赋值,我们这里其实只用这个结构体很少一部分信息,主要有三个参数内存物理内存的开始地址、物理
io
的开始地址、
io
页表的偏移,分别存于寄存器
r5(phys_ram=0xa0000000)

r6(phys_io=0x40000000)

r7(io_pg_offst=0x3f00)
中,并返回。

(5)
初始化页表,映射了
4M

RAM
,以使内核运行。


这里值得注意的是:
r5
此时为物理内存开始地址
(0xa0000000)
,程序利用宏定义
pgtbl
,将
r4
成为页表首地址
0xC0004000
。然后清空内核目录
swapper_pg_dir
开始的
16K
空间。



(6)
设置
lr
为返回地址
__ret
,以使下面的程序得以跳转返回。

(7)
使
pc=[r10+12]
,也就是跳转到
_xscale_proc_init
结构中的
b __xscale_setup
位置,这个结构在
arch/arm/mm/proc-xcale.S
中。我们来看看这个结构:

__pxa250_proc_info: <--r10
指向这个地址

.long 0x69052100

.long 0xfffff7f0

#if CACHE_WRITE_THROUGH

.long 0x00000c0a

#else

.long 0x00000c0e <--
这个参数传入了
r8


#endif

b __xscale_setup <--[r10+12]

.long cpu_arch_name

.long cpu_elf_name

.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP

.long cpu_pxa250_info

.long xscale_processor_functions

.size __pxa250_proc_info, . - __pxa250_proc_info



__xscale_setup
相关的程序多是对协处理器
cp15
的操作,之中用到了宏
F_BIT|I_BIT|SVC_MODE




相关的宏定义在文件
include/asm-arm/proc-armv/ptrace.h
中。

#define SVC_MODE 0x13

#define T_BIT 0x20

#define F_BIT 0x40

#define I_BIT 0x80



(8)
通过
proc-xcale.S

__xscale_setup
设置
MMU
,并通过
__ret
返回
head_armv.S


(9)

__ret
返回处设置
lr
通过
__switch_data
返回到
__mmap_switched


(10)
打开
MMU
,将
pipeline
清空,以使所有的内存得以正确的访问。并返回到
__mmap_switched


(11)__mmap_switched
通过
__switch_data
获得数据,并设置了
stack pointer


(12)
清空
BSS
,并保存
CPU
类型值
(processor ID)
以及系统类型
(machine type)
等。

(13)
跳转到
start_kernel


转自:http://blog.csdn.net/gates84/archive/2007/01/15/1483979.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: