VxWorks及BSP启动流程与顺序
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
目录
1vxworks映像类型 (1)
1.1 可加载的映像类型(vxwoks) (1)
2vxworks映像启动顺序 (1)
2.1 可加载型vxworks映像启动顺序 (2)
2.2 基于ROM的vxworks映像启动顺序 (2)
2.3 基于ROM驻留型vxworks映像启动顺序 (3)
3BSP基础知识 (4)
3.1 BSP的定义 (4)
3.2 BSP的功能 (4)
3.3 BSP的组成 (4)
4BSP的启动 (5)
4.1 BSP的启动流程 (5)
4.2.1 romInit.s:romInit()函数 (6)
4.2.2 bootInit.c:romStart()函数 (14)
4.2.3 bootConfig.c分析 (24)
4.2.4 sysLib.s:sysInit()函数 (27)
4.2.5 usrconfig.c:usrInit()函数 (28)
4.2.6 usrconfig.c:usrRoot()函数 (29)
5总结 (32)
VxWorks及BSP启动流程与顺序
———李守轩
摘要:本文首先介绍vxworks映像的类型及各类型vxworks映像的启动顺序;
然后介绍BSP的启动流程与初始化顺序。
关键词:vxworks映像;BSP启动;代码分析
1vxworks映像类型
对于vxworks映像的启动情况,从根本上看,在初始化和装载vxworks映像的过程中,处理器所执行的步骤在逻辑上是一样的。
对于有些处理器可能需要增加一些额外的步骤,而另一些处理器可能会省略掉某些步骤。
当构造vxworks映像时,根据需要可以构造不同类型的映像,系统把这些映像划分成以下三种类型。
1.1可加载的映像类型(vxwoks)
可加载型映像的执行需要通过引导代码把它装载到目标机RAM中,然后才开始执行。
而引导代码分为两种:
(1)引导代码固化在ROM或FLASH中;
(2)引导代码是一个独立的vxworks应用;
引导代码通常也是一种vxworks映像,也被称为引导映像。
它的作用就是把包含应用的vxworks映像装入到RAM中。
引导映像可能在ROM/FLASH中执行,也可能在RAM中执行。
1.2基于ROM的映像类型(vxworks_Rom & vxwoks_RomCompress)
基于ROM的vxworks映像在执行前首先把自己从ROM/FLASH中装载到RAM中,这种类型的映像通常在启动阶段较慢,但在执行阶段比ROM驻留型要快。
1.3基于ROM驻留映像类型(vxwoks_RomResident)
ROM驻留型映像在启动时把数据段拷贝到目标机RAM中,这种类型的映像在启动阶段比较快,当RAM空间比较小的时候通常使用它。
在嵌入式应用中通常会使用该类型的映像,然而,它在目标机上执行的速度要比其他类型要慢,原因是CPU访问ROM比访问RAM要慢。
2vxworks映像启动顺序
在目标机加电启动时发生的顺序启动事件是一个典型vxworks映像需要执行的功能。
所有类型的vxworks映像在初始化阶段启动顺序是一样的,处理器通过“jump”跳转指令跳转到ROM或Flash中引导带代码入口处,这段引导代码所要执行的操作包括:
(1)关中断;
(2)初始化目标机内存;
(3)装载适当的vxworks映像段;
(4) 跳转到设置目标机为静止状态的代码处。
不同类型的vxworks 映像启动顺序略有不同,下面介绍vxworks 映像的启动顺序。
2.1 可加载型vxworks 映像启动顺序
ROM/Flash
RAM_HIGH_ADRS
RAM_LOW_ADRS
RAM LOCAL_MEM_LOCAL_ADRS FREE_RAM_ADRS
图2.1 可加载型vxworks 映像启动顺序
可加载型vxworks 映像引导的详细过程如下:
(1)数据段和代码段的装入。
系统加电后执行引导带代码,首先把引导代码的代码段和数据段从ROM 或Flash 里装入RAM 中。
此时需要考虑下列情况: ● 压缩型引导代码,这种类型的引导代码在拷贝时,需要对它进行解压缩; ● 非压缩型引导代码,这种类型的引导代码直接进行拷贝即可; ● 驻留型引导代码,这种类型的引导代码在拷贝时,仅仅拷贝它的数据段。
(2)vxworks 映像的装入。
引导代码执行后,把vxworks 映像装入到RAM 中,然后跳转到vxworks 映像装入点。
(3)系统初始化。
执行静态链接在vxworks 映像里的系统初始化代码,最终完成系统初始化操作。
2.2 基于ROM 的vxworks 映像启动顺序
ROM/Flash
RAM_LOW_ADRS
RAM LOCAL_MEM_LOCAL_ADRS
FREE_RAM_ADRS
图2.2 基于ROM 的VxWorks 映像启动顺序
基于ROM 的vxworks 映像启动详细过程如下:
(1)VxWorks 映像的装入。
系统加电后执行引导带代码,首先把VxWorks 映像的代码段和数据段从ROM 或Flash 里装入RAM 中。
此时需要考虑下列情况:
● 压缩型VxWorks 映像,这种类型的VxWorks 映像在拷贝时,需要对它进行解压操作;
● 非压缩型VxWorks 映像,这种类型的VxWorks 映像直接进行拷贝操作;
(2)控制权转移。
VxWorks 映像被装入后,系统控制权转移给RAM 中的vxworks 映像的初始化代码。
(3)系统初始化。
执行静态链接在vxworks 映像里的系统初始化代码,最终完成系统初始化操作。
2.3 基于ROM 驻留型vxworks 映像启动顺序
ROM/Flash
RAM_LOW_ADRS RAM LOCAL_MEM_LOCAL_ADRS
FREE_RAM_ADRS 图2.3 基于ROM 驻留型的VxWorks 映像启动顺序
基于ROM 驻留型的vxworks 映像启动详细过程如下:
(1)VxWorks 映像数据段的装入。
系统加电后执行引导带代码,首先把VxWorks 映像的数据段从ROM 或Flash 里装入RAM 中。
(2)控制权转移。
VxWorks 映像数据段被装入后,系统控制权转移给ROM 或Flash 中的vxworks 映像的初始化代码。
(3)系统初始化。
执行ROM 或Flash 中的静态链接在vxworks 映像里的系统初始化代码,最终完成系统初始化操作。
3 BSP 基础知识
BSP 是VxWorks 的一个重要组成部分,在目标机加电后,首先执行的代码就是BSP ,可以说VxWorks 的启动流程就是BSP 的启动流程。
3.1 BSP 的定义
BSP (board support package )即板级支持包,是介于底层硬件环境和VxWorks 之间的一个软件接口。
3.2 BSP 的功能
BSP 主要功能是系统加电后初始化目标机硬件、初始化操作系统及提供部分硬件的驱动程序。
具体如下。
(1)初始化。
● CPU 初始化。
初始化CPU 内部寄存器。
● 目标机初始化。
初始化控制芯片的寄存器,为整个软件系统提供底层硬件环境的支持。
● 系统资源初始化。
为操作系统及系统的正常运行做准备,进行资源初始化。
(2)使VxWorks 能够访问硬件驱动程序。
主要是指BSP 包含部分必要的设备驱动程序和相关设备的初始化操作。
(3)在VxWorks 系统中,集成了与硬件相关的软件和部分硬件无关的软件。
3.3 BSP 的组成
开发板上电后首先跑的就是BSP 的代码,BSP 主要由源文件、头文件、派生文件、Makefile 文件组成。
BSP 包含的具体文件请参考相关资料。
BSP 在Tornado 安装目录中的位置如图3.1中所示。
bspname (针对不同CPU 的班级支持包)Tornado host
Tornado 主机驻留
工具
share
共享XDR 代码
Target
Vxworks 系统、BSP h (VxWorks 头文件)idl lib (VxWorks 库文件)proj (tornado 工程文件)
src (VxWorks 部分源代码)
unsupported (工具、驱动程序)
config (配置和构造VxWorks 的文件)
all (通用配置文件)
图3.1 tornado 目录结构
4BSP的启动
下面首先给出BSP的启动流程,然后对启动过程执行的函数进行具体分析,这是本文的重点和难点,是建立在作者自己理解角度和深度基础之上的。
4.1 BSP的启动流程
BSP的启动流程如图3.2所示,从代码执行的角度描述了BSP的启动过程及启动过程中先后调用函数的功能。
rominit.s: romInit
初始化CPU,初始化
将ROM中的程序搬移到RAM中
bootConfig.c: usrInit
初始化Cache
库、VEC
、系统硬件、wind
内核、启动usrRoot
初始化内存、系统时钟、
I/O和文件系统、标准输入输出及出错、
异常处理、log ELF文件格式支持
bootConfig.c: autoboot
延时7秒,以默认参数启动
启动命令行用于配置VxWorks启动参数初始化CPU及l2cache、stack
初始化cache、VEC wind内核、启动usrRoot
用户可定义的系统根任务
加载VxWorks的ELF文件并转向它进行重启
图3.2 BSP启动流程图
4.2 BSP的启动过程分析
从BSP的启动流程图中可以看出BSP启动过程中先后执行的函数,上面对这些函数进行详细描述。
4.2.1romInit.s:romInit()函数
1.romInit()函数的功能
romInit()函数与包含在romInit.s文件中,且用汇编语言编写。
它是系统加电后首先执行的代码,也是所有从ROM/FLASH启动的VxWorks映像的入口点。
它执行目标机最小的初始化操作及调用romStart()函数,其它硬件初始化操作推迟到sysHwInit()函数中进行。
romInit()函数必须包含下列功能:(1)屏蔽处理器中断及处理器复位;(2)初始化内存系统;(3)初始化堆栈指针和其它寄存器,开始执行romStart()函数及传递启动类型。
2.冷启动与热启动
(1)冷启动。
所谓冷启动是指硬件环境通过加电启动。
在romInit()函数中需要保存系统启动类型,启动类型的宏定义为BOOT_COLD。
(2)热启动。
所谓热启动是通过调用reboot()、Ctrl+X或异常中断重新启动目标机系统。
实际上这些操作是把控制权传递给ROM中的监控函数——sysToMonitor(),这个函数包含在sysLib.c文件中,如执行sysToMonitor(2),则系统执行热启动。
3. 代码分析
(1)X86硬件相关知识
●IDT是中断描述表,是由门描述符组成的一个数组,每个门描述符对应一个中断/ 异常向量,其可以保存在内存中的任何位置,CPU通过访问IDTR寄存器获取IDT的位置。
IDTR寄存器的长度为48位,其中包括保存IDT的32位线性地址和16位的大小。
对于IDTR寄存器的操作包括两个指令:一个是LIDT,另一个是SIDT。
LIDT用来将指定IDT所在线性地址和其长度装入LDTR寄存器;而SIDT则是将IDTR寄存器的内容读出。
●从实模式切换到保护模式之前,必须将基址和限长的值用指令LGDT装入GDTR,一旦系统切换到保护模式,则表所在的物理地址就不再改变,同时立即启用全局描述表,IDT表的基址用指令IDTR。
●32位控制器CR0的0位为保护允许位PE(protected enable),用于启动保护模式,pe=1,保护模式启动;pe=0,实模式下运行。
●实模式进入保护模式的步骤:初始化段描述符—准备并加载GDTR—打开地址线A20—设置CR0寄存器,进入保护模式—跳转32位代码段。
●A20地址线的激活请查阅相关资料。
上述相关知识是作者在理解代码过程中了解的,当然与硬件相关的知识很多,详细请查阅相关资料。
(3)代码分析
从作者理解的角度给出了romInit()函数的流程图,如图4.1所示。
具体的代码分下如下:
包含C的4个头文件。
vxWorks.h为系统头文件
sysLib.h为系统提供给BSP的头文件
config.h是BSP的头文件
Asm.h 是系统头号文件
开始数据段。
以下内容出现在数据段里:
.globl copyright_wind_river
.long copyright_wind_river /* the first in .data */
.globl romwait
.globl _romInit
.globl _sdata 等等
申明全局变量_romInit 和_sdata 等
图4.1 romInit ()函数流程图
冷启动热启动
定义一个以0结尾的字符串”start of data ”。
这个串出现在数据段的第一个无名变量之后
.text
.align 16
.text 开始代码段,以下内容出现在代码段里。
.align 16使对齐编译器进行填充,使得下一条指令出现在能被16整除的地址上。
对齐可使CPU 取指令快一点。
进入rominit处:
Cli关中断
jmp cold 跳转到cold处。
这是段内相对跳转。
balign 16 在32-bit代码前加这样的前缀可以让它变为16-bit代码;在16-bit 代码前可以变为32-bit代码
进入romWarmHigh处:
Cli关中断
movl SP_ARG1(%esp),%ebx 把esp+ SP_ARG1偏移处的值赋予ebx寄存器jmp warm 跳转到warm处。
这是段内相对跳转.
balign 16 在32-bit代码前加这样的前缀可以让它变为16-bit代码;在16-bit 代码前可以变为32-bit代码
进入romWarm Low处:
cli 关中断
cld 清方向标志
movl $ RAM_LOW_ADRS,%esi /* 把RAM_LOW_ADRS 地址赋给esi movl $ ROM_TEXT_ADRS,%edi /*把RAM_LOW_ADRS 地址赋给edi movl $ ROM_SIZE,%ecx /*把RAM_size 地址赋给ecx
shrl $2,%ecx /* ecx 右移两位256字节变成64个双字
rep
movsl 循环执行esi->edi 真到ecx==0
movl SP_ARG1(%esp),%ebx 把esp+ SP_ARG1偏移处的值赋予ebx寄存器jmp warm /* jump to warm */
.ascii "Copyright 1984-2002 Wind River Systems, Inc."
.balign 16,0x90
cold: //cold处
.byte 0x67, 0x66 与blign16功能相同
lidt %cs:(romIdtr - romInit) 将中断和量表加载到cs:(romIdtr - romInit)处 .byte 0x67, 0x66 与blign16功能相同
lgdt %cs:(romGdtr - romInit) 将断描述表加载到cs:( romGdtr - romInit)处
mov %cr0,%eax /* move CR0 to EAX */
.byte 0x66 /* next inst has 32bit operand */
or $0x00000001,%eax /* set the PE bit */
mov %eax,%cr0 /* move EAX to CR0 */
//以上代码的作用是将控制寄存器cr0的PE位置1
jmp romInit1 /*跳转到rominit1处。
Rominit1:进入rominit1处
.byte 0x66 /* next inst has 32bit operand */
mov $0x0010,%eax /* set data segment 0x10 is 3rd one */
mov %ax,%ds /* set DS */
mov %ax,%es /* set ES */
mov %ax,%fs /* set FS */
mov %ax,%gs /* set GS */
mov %ax,%ss /* set SS */
.byte 0x66 /* next inst has 32bit operand */
mov $ ROM_STACK,%esp /* set lower mem stack pointer */
.byte 0x67, 0x66 /* next inst has 32bit operand */
ljmp $0x08, $ ROM_TEXT_ADRS + romInit2 - romInit
现在已进入保护模式。
然而各个段寄存器的值,以及它们的高速缓存寄存器中的值还是实模式下的。
把DS, ES, FS, GS, SS寄存器设为0x0010,即指向GDT 的第2项(从0开始),DPL=0。
它们都指向一个段。
把堆栈指针esp设为ROM_STACK,由于CS还是以前的值,意味着目前代码段的属性还是16-bit代码。
所以使用指令前缀以执行32-bit代码。
执行一个远程段间跳转修改CS。
CS的新值为0x08,即GDT的第1项,DPL=0。
修改CS时它的高速缓存寄存器也会自动更新。
romIDT(Interrupt Description Table),中断描述符表,空的。
romGdtr:
.word 0x0027
.long ROM_TEXT_ADRS + ROM_GDT //设置段描述表的大小及基地址balign 16,0x90
romGdt: 段描述表
/* 0(selector=0x0000): Null descriptor */
.word 0x0000
.word 0x0000
.byte 0x00
.byte 0x00
.byte 0x00
.byte 0x00
/* 1(selector=0x0008): Code descriptor */
.word 0xffff /* limit: xffff */段界限低16位
.word 0x0000 /* base : xxxx0000 */ 基地址低16位
.byte 0x00 /* base : xx00xxxx */基地址中间8位
.byte 0x9a //段属性
.byte 0xcf //段属性含段界限高4位
.byte 0x00 /* base : 基地址高8位
/* 2(selector=0x0010): Data descriptor */
.word 0xffff /* limit: xffff */
.word 0x0000 /* base : xxxx0000 */
.byte 0x00 /* base : xx00xxxx */
.byte 0x92 /* Data r/w, Present, DPL0 */
.byte 0xcf /* limit: fxxxx, Page Gra, 32bit */
.byte 0x00 /* base : 00xxxxxx */
/* 3(selector=0x0018): Code descriptor, for the nesting interrupt */
.word 0xffff /* limit: xffff */
.word 0x0000 /* base : xxxx0000 */
.byte 0x00 /* base : xx00xxxx */
.byte 0x9a /* Code e/r, Present, DPL0 */
.byte 0xcf /* limit: fxxxx, Page Gra, 32bit */
.byte 0x00 /* base : 00xxxxxx */
/* 4(selector=0x0020): Code descriptor, for the nesting interrupt */
.word 0xffff /* limit: xffff */
.word 0x0000 /* base : xxxx0000 */
.byte 0x00 /* base : xx00xxxx */
.byte 0x9a /* Code e/r, Present, DPL0 */
.byte 0xcf /* limit: fxxxx, Page Gra, 32bit */
.byte 0x00 /* base : 00xxxxxx */
.balign 16,0x90
romInit2://进入rominit2处
cli 关中断
movl $ ROM_STACK,%esp 设置esp成rom_stack 0x8000h
#if defined (TGT_CPU) && defined (SYMMETRIC_IO_MODE) movl $ MP_N_CPU, %eax
lock
incl (%eax)
#endif 定义TGT_CPU 和SYMMETRIC_IO_MODE
#ifdef INCLUDE_WINDML /* WindML + VesaBIOS initialization */ Movl $ VESA_BIOS_DATA_PREFIX,%ebx
/* move BIOS prefix addr to EBX */
movl $ VESA_BIOS_KEY_1,(%ebx) /* store "BIOS" */
addl $4, %ebx /* increment EBX */
movl $ VESA_BIOS_KEY_2,(%ebx) /* store "DATA" */
movl $ VESA_BIOS_DATA_SIZE,%ecx /* load ECX with nBytes to copy */
shrl $2,%ecx /* get nLongs to copy */
movl $0,%esi /* load ESI with source addr */
movl $ VESA_BIOS_DATA_ADDRESS,%edi /* load EDI with dest addr */ rep
movsl /* copy BIOS data to VRAM */
#endif /* INCLUDE_WINDML */
*/定义INCLUDE_WINDML 并初始化VesaBIOS
#ifndef INCLUDE_IACSFL
call FUNC(romA20on) /* enable A20 */
cmpl $0, %eax /* is A20 enabled? */
jne romInitHlt /* no: jump romInitHlt */
#endif
定义INCLUDE_IACSFL
warm://进入warm处
ARCH_REGS_INIT /* 初始化DR[0-7] 标志位寄存器*/
#if (CPU == PENTIUM) || (CPU == PENTIUM2) || (CPU == PENTIUM3) || \ (CPU == PENTIUM4)
xorl %eax, %eax /* zero EAX */
movl %eax, %cr4
#endif 如果是奔腾系列初将cr4清零
movl $romGdtr,%eax 加载前面的romGdt表
subl $ FUNC(romInit),%eax
addl $ ROM_TEXT_ADRS,%eax
pushl %eax
call FUNC(romLoadGdt)
movl $ STACK_ADRS, %esp /* initialise the stack pointer */
movl $ ROM_TEXT_ADRS, %esi /* get src addr(ROM_TEXT_ADRS) movl $ romInit, %edi /* get dst addr(romInit) */
cmpl %esi, %edi /* is src and dst same? */
je romInit4
如果rom_text_adrs和rominit处相同的话就跳转到rominit4处执行
movl $ FUNC(end), %ecx /* get "end" addr */
subl %edi, %ecx /* get nBytes to copy */
shrl $2, %ecx /* get nLongs to copy */
cld /* clear the direction bit */
rep /* repeat next inst ECX time */
movsl
如果rom_text_adrs和rominit处不相同的话将rom_text_adrs处拷贝至(end-rominit)处
romInit4:进入rominit4处
xorl %ebp, %ebp ebp清零
pushl $0
popfl //清标志寄存器
pushl %ebx ebx进栈
movl $ FUNC(romStart),%eax /* jump to romStart */
call *%eax //跳转到romStart处执下一步。
romInitHlt:
pushl %eax
call FUNC(romEaxShow) //调用romEaxShow显示eax寄存器的内容
hlt
jmp romInitHlt 死循环,当机
.balign 16,0x90
FUNC_LABEL(romA20on) //进入romA20on处:打开a20以扩大寻址范围空间 call FUNC(romWait) //调用romwait等待
movl $0xd1,%eax /* 向64h寄存器写入d1h*/
outb %al,$0x64
call FUNC(romWait)
movl $0xdf,%eax /* Enable A20 */
outb %al,$0x60 向60h寄存器写入dfh*/
call FUNC(romWait) 调用romwait等待
movl $0xff,%eax 向64h寄存器写入ffh*/
outb %al,$0x64
call FUNC(romWait) 调用romwait等待
movl $0x000000,%eax /* Check if it worked */
movl $0x100000,%edx
pushl (%eax)
pushl (%edx)
movl $0x0,(%eax)
movl $0x0,(%edx)
movl $0x01234567,(%eax)
cmpl $0x01234567,(%edx)
popl (%edx)
popl (%eax)
jne romA20on0
以上代码是检查A20地址线是否打开,就是检查000000h和100000是否相同,即当超过20位时看看会不会和第0位时相同,如果不相同说明打开了A20地址线。
否则没打开。
尝试别一种方法打开
/* another way to enable A20 */
movl $0x02,%eax
outb %al,$0x92 //向92H寄存器写入02
xorl %ecx,%ecx ecx清零
romA20on1://接着上面向92H寄存器写入02后
inb $0x92,%al
andb $0x02,%al
loopz romA20on1 //确保92H为02
movl $0x000000,%eax /* Check if it worked */
movl $0x100000,%edx
pushl (%eax)
pushl (%edx)
movl $0x0,(%eax)
movl $0x0,(%edx)
movl $0x01234567,(%eax)
cmpl $0x01234567,(%edx)
popl (%edx)
popl (%eax)
jne romA20on0
movl $ 0xdeaddead,%eax /* error, can't enable A20 */
ret
以上代码是检查A20地址线是否打开,就是检查000000h和100000是否相同,即当超过20位时看看会不会和第0位时相同,如果不相同说明打开了A20地址线。
否则没打开。
不再尝试无法打开A20
romA20on0:
xorl %eax,%eax
ret //成功打开A20同进EAX清零
.balign 16,0x90
FUNC_LABEL(romLoadGdt)
movl SP_ARG1(%esp),%eax
lgdt (%eax)
movw $0x0010,%ax /* a selector 0x10 is 3rd one */
movw %ax,%ds
movw %ax,%es
movw %ax,%fs
movw %ax,%gs
movw %ax,%ss
ret
把DS, ES, FS, GS, SS寄存器设为0x0010,即指向GDT的第2项(从0开始),DPL=0。
它们都指向一个段。
FUNC_LABEL(romWait) //等待状态
xorl %ecx,%ecx ecx清零
romWait0:
movl $0x64,%edx /* Check if it is ready to write */
inb %dx,%al
andb $2,%al
loopnz romWait0 //确保64h寄存器的值为02
ret
.balign 16,0x90 同上一次
FUNC_LABEL(romEaxShow) //显示eax寄存器的内容
/* show EAX register in your display device available */
ret
4.2.2bootInit.c:romStart()函数
1.romStart()函数的功能
romStart()函数包含在bootInit.c文件中,且用C语言编写。
它是系统最先执行的第一个C语言程序,为ROM映像执行必要的代码重定位、解压和RAM 初始化操作。
其具体操作如下:
(1)拷贝适当的ROM映像段到RAM中;
(2)清理没有被使用的那部分内存(冷启动时);
(3)执行解压操作(如果需要);
(4)调用预内核的通用初始化程序——usrInit()。
2.romStart()函数相关知识
(1)ROM的布局。
如图4.2所示。
+ROM_SIZE
binArrayEnd
binArrayStart
ROM_TEXT_ADRS
ROM_BASE_ADRS
图4.2 ROM布局
从上图中可以发现压缩型映像和非压缩型映像处于不同的ROM空间。
(2)RAM的布局。
如图4.3所示。
从图4.3中可以发现用户保留区处于RAM空间的顶端和底部,映像重新定位区位于保留堆栈区的上面。
sysPhyMemTop()
SYS_MEM_TOP
SYS_MEM_BOTTOM
LOCAL_MEM_LOCAL_ADRS
图4.3 RAM布局
3.代码分析
(1)代码综述
bootrom有三种类型:ROM_RESIDENT、UMCOMPRESS和COMPRESS。
第一种是一直运行在rom中的映象,只把data段拷贝到ram里面;第二种是非压缩方式的映象,data段和text段都要拷贝到ram里面,并在ram里面运行;第三种是压缩方式的映象,生成的时候编译器会把除掉romInit.s和bootInit.c之外的目标文件压缩并“汇编”成一个bootrom.Z.s,最后和romInit.o,bootInit.o,version.o 进行链接,生成bootrom映象。
所以它也是要全部拷贝到ram中,且必须要进行压缩的工作。
而这些工作基本上都是在bootInit.c中进行的。
bootInit.c里面主要就是romStart()这个函数,让我们来分析一下它。
它的入口参数是startType,是一个启动类型标志,如BOOT_CLEAR、BOOT_NORMAL 等,这在后面清内存时会用到。
函数一开始定义了一个函数指针变量absEntry,它最后指向的就是ursInit()或compressedEntry()函数。
接下来就是对三种bootrom 映象类型进行不同的操作,下面我们以arm为例来分别说明。
1.ROM_RESIDENT:它要拷贝的只是data段。
直接调用“copyLongs ((UINT *)(etext + 4), (UINT *) RESIDENT_DATA, ((UINT) edata - (UINT) RESIDENT_DATA) / sizeof (long));”来实现。
这时系统是运行在rom上的,链接器把所有的函数都定位在rom空间上,所以调用copyLongs时没有计算偏移,而拷贝的目标地址是RESIDENT_DATA,对于arm而言RESIDENT_DATA就是sdata,这是在romInit.s中定义的。
通过objdumparm这个工具可以看到sdata定位在RAM_HIGH_ADRS+0x4这个位置上。
而etext + 4则是rom上data段的起始地址。
这样,完成data段的拷贝。
然后如果startType为冷启动,那么清零SYS_MEM_BOTTOM到栈底(RESIDENT_DATA - STACK_SA VE)以及data段结束之后(edata到SYS_MEM_TOP)的内存空间。
然后将函数开始定义的指针absEntry指向usrInit(在rom中):absEntry = (FUNCPTR)usrInit;并带上startType 跳过去运行:(absEntry)(startType),完成。
2.UMCOMPRESS:一开始它将text段和data段都拷贝到ram中:((FUNCPTR)ROM_OFFSET(copyLongs))(ROM_TEXT_ADRS,(UINT)romInit, ROM_COPY_SIZE / sizeof (long))。
这时的ROM_TEXT_ADRS就是代码段在rom 上的开始位置,而romInit则是由链接器定位到了RAM_HIGH_ADRS的地址上,所以这时的确是按我们常规的思路拷贝的。
并且由于copyLongs函数是定位到ram空间的,所以要计算它在rom上的偏移ROM_OFFSET(copyLongs)。
然后象ROM_RESIDENT一样,它也要清零栈底(romInit - STACK_SA VE)以下和映象之上(SYS_MEM_TOP - (romInit + ROM_COPY_SIZE))的内存空间。
然后将函数开始定义的指针absEntry指向usrInit(在ram中):absEntry = (FUNCPTR)usrInit;并带上startType跳过去运行:(absEntry)(startType),完成。
3.COMPRESS:开始的时候把从ROM_TEXT_ADRS起始的长度为romInit 到binArrayStart的内容拷贝到romInit位置上。
注意,由于romInit被链接器定位到RAM_LOW_ADRS的位置上,这时相当于把romInit.o、bootInit.o和version.o 的内容拷贝到了RAM_LOW_ADRS上。
然后和UMCOMPRESS一样清零栈底(romInit - STACK_SA VE)以下的内存空间,不同之处是它接下来清除binArrayStart之上(SYS_MEM_TOP - binArrayStart)的内存空间:fillLongs ((UINT *)binArrayStart,((UINT)SYS_MEM_TOP - (UINT)binArrayStart) / sizeof (long), 0)。
然后调用解压程序inflate将在rom上的(binArrayEnd - binArrayStart)之间的内容解压到RAM_DST_ADRS(RAM_HIGH_ADRS)的位置上:binArrayStart(absUncompress) ((UCHAR *)ROM_OFFSET(binArrayStart),(UCHAR *)RAM_DST_ADRS, &binArrayEnd - binArrayStart)。
这样,解完压后函数compressedEntry()刚好就在RAM_DST_ADRS(RAM_HIGH_ADRS)的位置上,所以接下来将指针absEntry指向它:absEntry = (FUNCPTR)RAM_DST_ADRS。
最后带上startType跳过去运行:(absEntry)(startType),完成。
(2)代码分析
代码中针对不同类型的ROM程序有不同的copy类型,不同的CPU有不同的地址转换,RAM清零与最后的入口函数也有所不同。
具体的代码分析如下:
Bootinit.里面主要看romstart这个函数。
实现把rom中的内容拷贝到ram中去,同时把把ram其它未用的部分清零,对于压缩的rom在必要时还要进行解压。
这段代码由前面的rominit.s跳转到这来的。
ROM AND RAM MEMORY LAYOUT ,Example memory layout for a 1-megabyte board:
-------------- 0x00100000 = LOCAL_MEM_SIZE = sysMemTop()
| |
| RAM |
| 0 filled |
| |
|------------| = (romInit+ROM_COPY_SIZE) or binArrayStart
| ROM image |
|----------- | 0x00090000 = RAM_HIGH_ADRS
| STACK_SA VE |
|------------|
| | 0x00080000 = 0.5 Megabytes
| |
| |
| 0 filled |
| |
| | 0x00001000 = RAM_ADRS & RAM_LOW_ADRS
| |
| | exc vectors, bp anchor, exc msg, bootline
| |
| |
-------------- 0x00000000 = LOCAL_MEM_LOCAL_ADRS
--------------
| ROM |
| | 0xff8xxxxx = binArrayStart
| |
| | 0xff800008 = ROM_TEXT_ADRS
-------------- 0xff800000 = ROM_BASE_ADRS
在时入romstart之前先是定义了一些变量SYS_MEM_TOP, SYS_MEM_BOTTOM, BINARRAYEND_ROUNDOFF等等。
再接下来就是声明了一些系统函数,再接下来又是定义了romstart相关的定义如:
RESIDENT_DATA== RAM_DST_ADRS(mips)
//定义体系结构特殊信息==压缩映象最终重定位的地址
RESIDENT_DATA ==wrs_kernel_data_start
RAM_DST_ADRS==RAM_HIGH_ADRS
//压缩映象最终重定位的地址==拷贝引导映象的入口地址
ROM_TEXT_ADRS==((UINT)romInit)
ROM_BASE_ADRS==((UINT)romInit)//引导rom的入口地址
ROM_COPY_SIZE==(ROM_SIZE - (ROM_TEXT_ADRS - ROM_BASE_ADRS)) ROM_OFFSET(adr)==(((UINT)adr - (UINT)romInit) + ROM_TEXT_ADRS)
//rom->ram时涉及到计算偏移地址函数
接着又声明了三个函数
拷贝rom->ram函数:copyLongs()
往ram填零函数:fillLongs()
比较函数:checkLongs()
void romStart
(
FAST int startType /* 启动类形*/
它的入口参数是startType,是一个启动类型标志,如BOOT_CLEAR、BOOT _NORMAL等,这在后面清内存时会用到。
)
{
#if ((CPU_FAMILY==SPARC) || (CPU_FAMILY==MIPS) || (CPU_FAMILY==I80X86) || \
(CPU_FAMILY==PPC) || (CPU_FAMILY==ARM))
volatile /* to force absolute adressing */
注释:
volatile关键字的作用是使编译器不对被指定的变量做优化,这样可以确保该变量每次被取到新的值。
V olatile经常用来修饰全局的或者静态的或者在多任务环境下允许被改变的变量。
#endif /* (CPU_FAMILY==SPARC) */
FUNCPTR absEntry;
函数一开始定义了一个函数指针变量absEntry,它最后指向的就是
usrInit()或compressedEntry()函数
#if(CPU_FAMILY==ARM)&&(!defined(ROM_RESIDENT))
&& !defined(BOOTCODE_IN_RAM)
VOIDFUNCPTR ramfillLongs = fillLongs; /* force call to RAM */
#define fillLongs(a,b,c) ramfillLongs(a,b,c)
#endif /* (CPU_FAMILY==ARM) */
#if (CPU_FAMILY==MC680X0) && !defined(ROM_RESIDENT) && !defined(BOOTCODE_IN_RAM)
volatile VOIDFUNCPTR romcopyLongs = ©Longs; /* force call to ROM */
#define copyLongs romcopyLongs
#endif /* (CPU_FAMILY==MC680X0) */
/*
* Copy from ROM to RAM, minus the compressed image
* if compressed boot ROM which relies on binArray
* appearing last in DATA segment.
*/
#ifdef ROM_RESIDENT
/* If ROM resident code, then copy only data segment
* from ROM to RAM, initialize memory and jump
* to usrInit.
*/
#if (CPU_FAMILY == SPARC)
copyLongs ((UINT *)(etext + 8), (UINT *) RESIDENT_DATA,
#else
/*
对于ROM_RESIDENT,则只拷贝数据段到ram。
因为系统在ROM中运行,所有函数被定位到ROM空间。
所以copyLongs没有
计算偏移量。
RESIDENT_DATA 就是sdata,被LD定位到RAM_HIGH_ADRS。
又定义为wrs_kernel_data_start。
etext则是ROM上的data起始位置。
则直接拷贝ROM上的data数据到RAM上的RAM_HIGH_ADRS.
*/
copyLongs ((UINT *)etext, (UINT *) RESIDENT_DATA/*RAM_HIGH_ADRS*/,
#endif
((UINT) wrs_kernel_data_end - (UINT) RESIDENT_DATA) / sizeof (long));
#else /* ROM_RESIDENT */
#ifdef UNCOMPRESS
#if (CPU_FAMILY == MIPS)
/*
* copy text to uncached locations to avoid problems with
* copy back caches
*/
((FUNCPTR)ROM_OFFSET(copyLongs))(ROM_TEXT_ADRS,
(UINT)K0_TO_K1(romInit),
ROM_COPY_SIZE / sizeof (long));
#else /* CPU_FAMILY == MIPS */
/*
对于不是ROM_RESIDENT,并且没有被压缩,则拷贝代码段和数据段到ram.
因为系统在RAM中运行,所以copyLongs函数是定位到ram空间的,所以要计算ROM的偏移量。
ROM_TEXT_ADRS就是代码段在rom上的开始位置。
romInit则是由链接器定位到了RAM_HIGH_ADRS的地址上。
*/
((FUNCPTR)ROM_OFFSET(copyLongs)) (ROM_TEXT_ADRS, (UINT)romInit,
ROM_COPY_SIZE / sizeof (long));
#endif /* CPU_FAMILY == MIPS */
#else /* UNCOMPRESS */
#if (CPU_FAMILY == MIPS)
/*
* copy text to uncached locations to avoid problems with
* copy back caches
* copy the entire data segment because there is no way to ensure that
* binArray is the last thing in the data segment because of GP relative
* addressing
*/
((FUNCPTR)ROM_OFFSET(copyLongs)) (ROM_TEXT_ADRS, (UINT)K0_TO_K1(romInit),
((UINT)wrs_kernel_data_end - (UINT)romInit) / sizeof (long));
#else /* CPU_FAMILY == MIPS */
/*
对于不是ROM_RESIDENT,但是被压缩.要分开拷贝。
因为系统在RAM中运行,所以copyLongs函数是定位到ram空间的,所以要计算ROM的偏移量。
开始的时候把从ROM_TEXT_ADRS起始的长度为romInit到binArrayStart的内容拷贝到romInit位置上。
注意,由于romInit被链接器定位到RAM_LOW_ADRS的位置上。
(在make是可以看到定位信息)
这时相当于把romInit.o、bootInit.o和version.o的内容拷贝到了RAM_LOW_ADRS上。
*/
((FUNCPTR)ROM_OFFSET(copyLongs)) (ROM_TEXT_ADRS, (UINT)romInit,
((UINT)binArrayStart - (UINT)romInit)/ sizeof (long));
/*
BINARRAYEND_ROUNDOFF(binArrayEnd)
BINARRAYEND_ROUNDOFF(binArrayEnd)定义为被压缩的image尾。
(UINT)ROM_TEXT_ADRS + ((UINT)BINARRAYEND_ROUNDOFF - (UINT)romInit)
程序在编译时会检查romcopysize的大小,如果小于text+data+bss (ROM_BASE)就会报错。
我们不可能定义得这两者完成相等,一般都是romcopysize略大于text+data+bss(ROM_BASE),所以copy的不但包括bss,还有一些空段
binArryStart、binArryEnd是压缩型bootrom在ROM里的存放位置,压缩与非压缩bootrom在ROM里是存放在不同的区域的,压缩型copy的源地址是从binArryStart开始,目的地址定位到RAM_LOW_ADRS。
非压缩的从rominit()开始,目的地址定位到RAM_HIGH_ADRS处
*/
((FUNCPTR)ROM_OFFSET(copyLongs))
((UINT *)((UINT)ROM_TEXT_ADRS + ((UINT)BINARRAYEND_ROUNDOFF -
(UINT)romInit)), (UINT *)BINARRAYEND_ROUNDOFF,
((UINT)wrs_kernel_data_end - (UINT)binArrayEnd) / sizeof (long));
#if (CPU==XSCALE)
/* validate coherence, can not assume uncached area... */
((FUNCPTR)ROM_OFFSET(checkLongs))
(ROM_TEXT_ADRS, (UINT)romInit,
((UINT)binArrayStart - (UINT)romInit) / sizeof (long));
((FUNCPTR)ROM_OFFSET(checkLongs))
((UINT *)((UINT)ROM_TEXT_ADRS +
((UINT)BINARRAYEND_ROUNDOFF -
(UINT)romInit)), (UINT *)BINARRAYEND_ROUNDOFF,
((UINT)wrs_kernel_data_end - (UINT)binArrayEnd) / sizeof (long)); #endif
#endif /* CPU_FAMILY == MIPS */
#endif /* UNCOMPRESS */
#endif /* ROM_RESIDENT */
#if (CPU_FAMILY != MIPS) && (!defined (BOOTCODE_IN_RAM)) /* clear all memory if cold booting */
if (startType & BOOT_CLEAR)
{
#ifdef ROM_RESIDENT
/* Clear memory not loaded with text & data.
*
* We are careful about initializing all memory (except
* STACK_SA VE bytes) due to parity error generation (on
* some hardware) at a later stage. This is usually
* caused by read accesses without initialization.
*/
/*
是ROM_RESIDENT时,只有数据段被拷贝到RAM中
这条指令执行从数据段前面的堆栈区域之前到内存底部,全部清0
*/
fillLongs ((UINT *)SYS_MEM_BOTTOM,
((UINT) RESIDENT_DATA - STACK_SA VE - (UINT)SYS_MEM_BOTTOM) / sizeof(long), 0);
/* 从RAM数据段最后到内存顶部全部清0 */
fillLongs (((UINT *) wrs_kernel_data_end),
((UINT)SYS_MEM_TOP - ((UINT) wrs_kernel_data_end)) / sizeof(long), 0);
#else /* ROM_RESIDENT */
/*
如果不是ROM_RESIDENT时,数据段和代码段都拷贝到RAM中
则把从内存底部到栈底前(即romInit - STACK_SA VE之前)全部清0
*/
fillLongs ((UINT *)(SYS_MEM_BOTTOM),
((UINT)romInit - STACK_SA VE - (UINT)SYS_MEM_BOTTOM) /
sizeof(long), 0);
#if defined (UNCOMPRESS)
/*
不是ROM_DESIDENT并且没有压缩时
从拷贝过来的地址后面到内存顶部清0(即代码段和数据段之后的内存)
*/
fillLongs ((UINT *)((UINT)romInit + ROM_COPY_SIZE),
((UINT)SYS_MEM_TOP - ((UINT)romInit + ROM_COPY_SIZE))
/ sizeof(long), 0);
#else
/*
wrs_kernel_data_end数据段尾
压缩的话,就从RAM数据段后到内存顶端开始清0
*/
fillLongs ((UINT *)wrs_kernel_data_end,
((UINT)SYS_MEM_TOP - (UINT)wrs_kernel_data_end) / sizeof (long), 0);
#endif /* UNCOMPRESS */
#endif /* ROM_RESIDENT */
/*
* Ensure the boot line is null. This is necessary for those
* targets whose boot line is excluded from cleaning.
*/
*(BOOT_LINE_ADRS) = EOS;
将BOOT_LINE_ADRS中的内容清0,保证bootline的正确
}
#endif /* (CPU_FAMILY != MIPS) && (!defined (BOOTCODE_IN_RAM)) */ /* jump to VxWorks entry point (after uncompressing) */
#if defined (UNCOMPRESS) || defined (ROM_RESIDENT)
#if (CPU_FAMILY == I960)
absEntry = (FUNCPTR)sysInitAlt; /* reinit proc tbl */
#else
/*
如果是ROM_RESIDENT 或者没有被压缩
则把下一个要执行的usrInit()传递给入口指针
*/
absEntry = (FUNCPTR)usrInit; /* on to bootConfig */
#endif /* CPU_FAMILY == I960 */
#else
/* 压缩的。
当然不是ROM_RESIDENT */
{
#if (CPU_FAMILY == MIPS)
volatile FUNCPTR absUncompress = (FUNCPTR) UNCMP_RTN;
if ((absUncompress) ((UCHAR *)ROM_OFFSET(binArrayStart),
(UCHAR *)K0_TO_K1(RAM_DST_ADRS),
(int)((UINT)binArrayEnd - (UINT)binArrayStart)) != OK)
#elif (CPU_FAMILY == I80X86) || (CPU_FAMILY == ARM)
/*
解压缩,x86和ARM CPU则UNCMP_RTN inflate解压缩程序传递给absUncompress函数指针。
binArrayStart是ROM上的被压缩的TEXT段开始地址。
binArrayEnd - binArrayStart则是ROM上压缩的长度。
inflate压缩程序被LD定位到RAM中,所以要计算偏移量。
把ROM上binArrayStart开始的压缩image解压缩到RAM_DST_ADRS (RAM_HIGH_ADRS)上。
*/
volatile FUNCPTR absUncompress = (FUNCPTR) UNCMP_RTN;
if ((absUncompress) ((UCHAR *)ROM_OFFSET(binArrayStart),
(UCHAR *)RAM_DST_ADRS, binArrayEnd - binArrayStart) != OK)
#else
/*
解压缩,不是x86和ARM CPU则直接UNCMP_RTN inflate解压缩程序。
binArrayStart是ROM上的被压缩的TEXT段开始地址。
binArrayEnd - binArrayStart则是ROM上压缩的长度。
inflate压缩程序被LD定位到RAM中,所以要计算偏移量。
把ROM上binArrayStart开始的压缩image解压缩到RAM_DST_ADRS (RAM_HIGH_ADRS)上。
*/
if (UNCMP_RTN ((UCHAR *)ROM_OFFSET(binArrayStart),
(UCHAR *)RAM_DST_ADRS, binArrayEnd - binArrayStart) != OK)
#endif /* (CPU_FAMILY == MIPS) */
return; /* if we return then ROM's will halt */
/*
被压缩的程序解压缩后为RAM_DST_ADRS,即程序下一个入口地址为RAM_DST_ADRS
所以传递这个地址给程序函数指针。
*/
absEntry = (FUNCPTR)RAM_DST_ADRS; /* compressedEntry () */
}
#endif /* defined UNCOMPRESS || defined ROM_RESIDENT */
#if ((CPU_FAMILY == ARM) && ARM_THUMB)
absEntry = (FUNCPTR)((UINT32)absEntry | 1); /* force Thumb state */
#endif /* CPU_FAMILY == ARM */
/*
调用usrInit,并传递startType参数。
*/
(absEntry) (startType);
}
4.2.3bootConfig.c分析
本文件中包含几个非常重要的函数:usrInit( )、usrRoot( )、autoboot( )、bootCmdLoop( )、bootLoad( );从图3.2中可以看出这几个函数在BSP的启动过程中完成了必要的操作。
下面对这几个函数进行大致的说明。
rInit()函数
usrInit()函数所执行的操作包括:初始化cache库、VEC、系统硬件、wind 内核、启动usrRoot()函数。
代码具体描述如下:
该函数被romstart()中的boot代码调用。
调用cacheLibInit (USER_I_CACHE_MODE, USER_D_CACHE_MODE)初始化cache库.
调用bzero (edata, end - edata);对bss清零.
调用intVecBaseSet ((FUNCPTR *) VEC_BASE_ADRS)设置中断向量;
调用excVecInit ()设置异常向量;
调用sysHwInit ()初始化硬件;
调用usrKernelInit ()配置VXWORK内核;
调用cacheEnable (INSTRUCTION_CACHE);打开chche库
调用kernelInit ((FUNCPTR) usrRoot, ROOT_STACK_SIZE,启动usrRoot();
rRoot()函数(BOOTROM用的)
usrRoot()函数所执行的操作包括:初始化内存、系统时钟、I/O和文件系统、标准输入输出及出错、异常处理、log任务、ELF文件格式支持。
具体代码分析如下:
调用memInit (pMemPoolStart, memPoolSize);初始化内存;
调用sysClkConnect ((FUNCPTR) usrClock, 0); sysClkRateSet (60);
sysClkEnable();初始化系统时钟;
调用iosInit (NUM_DRIVERS, NUM_FILES, "/null");初始化文件系统;
调用ttyDrv();安装串行设备驱动程序;ttyDevCreate()创建串行设备.最后调用i/o控制Ioctl();完成串口设备的初始化;
调用wdbConfig(); 初始化WDB;
调用ioGlobalStdSet (STD_IN, consoleFd);
ioGlobalStdSet (STD_OUT, consoleFd);
ioGlobalStdSet (STD_ERR, consoleFd);//重定向标准输入输出到控制台.
调用pipeDrv (); 初始化管道设备.
调用excInit (); excHookAdd ((FUNCPTR) bootExcHandler); 初始化异常处理;
调用logInit (consoleFd, 5); 初始化登陆;
调用hashLibInit ();初始化hase库DOS文件系统;
调用bootAoutInit ();bootEcoffInit ();bootCoffInit ();bootElfInit ();根据配置选择之上的其一初始化;。