ARM学习报告3.docx
- 文档编号:6709739
- 上传时间:2023-01-09
- 格式:DOCX
- 页数:41
- 大小:1.10MB
ARM学习报告3.docx
《ARM学习报告3.docx》由会员分享,可在线阅读,更多相关《ARM学习报告3.docx(41页珍藏版)》请在冰豆网上搜索。
ARM学习报告3
ARM学习报告003杜云海duyunhai@
ARM学习报告003
——HaydenLuoBIOS总体结构及部分源代码分析
文章版权说明:
文章版权为杜云海()所有,不得随意修改发布本文内容,转载请注明作者及网站()
-1-
ARM学习报告003杜云海duyunhai@基于HaydenLuoBios的BootLoader源代码级分析第二部分——
第二篇:
BIOS总体结构及部分源代码分析
ARM学习报告0032004-5-25
这段时间事情真的是很多,老板要文章,这已经要人命了,还要翻译成英文投到国际会议,还有本科生毕业设计…,于是报告003就这么被推迟了。
不过,鲁迅先生说时间是海绵,的确有道理,终于还是基本上看完了Bootloader,并移植了uClinux,还在上面实现了一个有关安全的身份认证程序,呵呵,不然要投稿的那篇文章乱哈拉就不好了!
!
ARM学习是一个很令人兴奋的过程,可以在其中体会到在51单片机世界里早已经淡薄的挑战和愉悦。
在以前做过的51片子的项目中,一直都没有好好深入到某些机理中,实在遗憾。
现在把自己在ARM学习过程中的一点一滴写下来,既是为了自我总结,也可以和ARM同行交流讨论。
ARM学习报告001和002在电子产品世界论坛上得到了大家的好评,也使我有了写好报告003的动力,这篇文章基于以上两个学习报告,如果你是初学者或还没有看过以上两篇报告,那么推荐下载看看,也许对理解本篇文章中的一些论述很有帮助J(推销嫌疑)lARM学习报告001:
lARM学习报告002:
写在文章前
在报告002中我们阐述了GNU工具集开发ARM程序的过程和机理,重点讲了elf文件格式和section的来历和作用,并和ARM的映象文件组织结构做了类比,主要起基础性的作用,因为不了解这些东西,看linux下开发的ARM程序只能囫囵吞枣,很难深入。
不过,报告002只是重点介绍了head.S、Makefile和bios.ld三个文件,对输入段,输出段的机理进行详细讲述,没有对整个BIOS做系统描述,可能无法给人以全面印象,本篇将结合整个bios来重温并补充那些“机理”!
在报告003中并没有对HaydenLuoBios进行详细的描述,没有这个必要,也没有时间,我觉得只要入门了,很多程序都很容易自己看懂。
关键是入门难。
所以在本篇报告中我用的例子程序bios-dyh是我根据HaydenLuoBios缩减而来的,由于HaydenLuoBios的结构的特殊性,虽然是缩减,却丝毫没有改动bios的大结构,只是功能上减弱一些,绝对不会影响到大家日后继续研究HaydenLuoBios或bios-lt。
特别声明
还有一点要阐述的,也是论坛上有弟兄问的,就是HaydenLuoBios哪里可以下?
现在用google搜,好像就只有liutao大侠的bios-lt74,当然bios-lt74也是从HaydenLuoBios继承而来的,liutao大侠对其进行了大量的补充和增强,并将其发扬光大。
如果大家要想真正使用功能强大的bios,可以推荐使用bios-lt74。
本文之所以没有用bios-lt做例子,是因为相对于原始的HaydenLuoBios,bios-lt74的数据结构和功能都变得复杂了,不利于初学者入门,而我这个bios-dyh是为了本篇报告学习而特意制作的,比bios-lt74的功能差一些,不过,麻雀虽小,学习正好!
开始我们的BIOS旅程吧!
-2-
ARM学习报告003杜云海duyunhai@
一BIOS的整体结构
1.1BIOS的“生成三阶段”整体图
在开始讲bios整体结构之前,不如来看看一张图,图1是根据bios-dyh画的,完整的HaydenLuoBios还包括了setup、fdisk两个文件夹(也就是setup和fdisk功能模块),缺少的两个模块是比较独立的,不影响整个系统结构。
图1bios-dyh的总体结构图
-3-
ARM学习报告003杜云海duyunhai@
上图只是一个感性认识,从图1可以看出一种层次感,就是图中标注的①②③阶段,还有“子文件夹和根目录”层次。
再配合bios-dyh根目录下的Makefile文件,那就可以建立一个比较系统的bios生成过程了。
1.2Makefile文件
/bios-dyh/Makefile
在这里不讨论Makefile的语法了,如果你还看不懂这个文件的话,建议应该先补一补Linux编程知识,否则将举步维艰。
看Makefile当然从all:
开始,
all:
make-Csysinit
make-Cbiosapi
make-Ctftp
make-Cuart_rec
makebios.bin
make-Cimgtools/param
make-Cimgtools/img
总的Makefile可以分为三个部分,对应图1的三个阶段:
l前面四个make对应图1的①部分,也就是进入sysinit、biosapi、tftp、uart_rec各
个子文件夹进行相对较独立的编译连接,在子文件夹里生成“独立”的功能模块的可执行文件,并用bin2c将子文件夹里可执行文件生成只包含一个数组的同名C文件,生成的C文件在根目录下。
l第四个makebios.bin对应图1的②部分,根据根目录下的各个C程序,编译生成
相应的.o文件,然后根据bios.ld连接生成bios,并用objcopy取出其中需要的section,生成bios.bin
l最后两步是创建重要的“系统参数表”,对应图1的③部分,并用combine工具将
这个表和bios.bin以纯二进制形式合并在一起,最后在imgtools/img文件夹中生成bios.img。
bios.img——一个的可烧入ROM中的Bootloader就这样出现的!
!
以上只是一个大体的框架分析,以期建立一个大致的BIOS的概念——“三阶段生成过程”,从而使BIOS有章可循,不至于在脑子中零乱而无法入手。
当然,这样一个这样的系统分析肯定讲不清楚很多细节问题,对于熟悉这个bootloader的人来说,可能一看就明白;但是对于刚刚准备学习bootloader的人,一定会存在很多不解的地方。
没有关系,下面我们来具体分析。
分析源代码,特别是感觉是比较大的源程序,一个“分析顺序”很重要,否则写的人也乱,看的人更乱。
本报告的分析顺序有两种:
l“BIOS生成三阶段”顺序:
就如图1的三部分
l“BIOS执行流程”顺序:
以程序的执行流程为主线
第一种分析顺序的主要作用还是建立“机理性”的概念,讲究linux下开发的BIOS程序到底是如何一步一步生成的。
这种分析就象在天上来分析地上迷宫的结构——面
-4-
ARM学习报告003杜云海duyunhai@
而第二种分析顺序则跟着程序走,更接近于源代码分析,象亲自进入迷宫走一遭,作用和第一种是不同的——点
先来第一种吧,先建立一个“面”的概念,再来“由面到点”更符合学习逻辑。
二生成BIOS的“三把斧”
首先分析BIOS生成三阶段里的第一阶段,在这个阶段主要是6个大字——“独立编译连接”。
在这个阶段里,各个功能模块进行了“独立编译连接”,注意是“独立编译连接”,也就是说,在各个子文件夹里,这三个功能模块都已经编译连接并生成了相应真正的可执行文件——sysinit、biosapi、tftp、urat_rec。
也就是说,BIOS还没有生成,sysinit、biosapi、tftp、urat_rec已经生成了,并且是可执行的。
这带来一个问题,这么早生成可执行文件,那最后和BIOS整合在一起并使用它呢?
2.1第一把斧:
BIOS功能模块——sysinit、biosapi、tftp、urat_rec
让我们来看看相应的Makefile和.ld连接脚本文件,体会一下“独立编译连接”过程。
以sysinit为例,其他两个类同。
/sysinit/Makefile
BIN2C=../tools/bin2c
CFLAGS+=-I..
AFLAGS+=-I..
OBJ=head.osysinit.o
all:
../sysinit.c
../sysinit.c:
sysinit.bin
$(BIN2C)-c-ssysinit_datasysinit.bin../sysinit.c
sysinit.bin:
sysinit
$(OBJCOPY)-Obinary--only-section=.init\
--only-section=.text\
--only-section=.rodata\
--only-section=.data\
--only-section=.bsssysinit\
sysinit.bin
sysinit:
$(OBJ)
$(LD)-p-X-Tsysinit.ld$(OBJ)\
-osysinit
clean:
$(RM)-rf*.osysinitsysinit.bin../sysinit.c
%.o:
%.c
$(CC)$(CFLAGS)-c-o$@$<
-5-
ARM学习报告003杜云海duyunhai@
%.o:
%.S
$(CC)$(AFLAGS)-c-o$@$<
从/sysinit/Makefile可以看出,源程序sysinit.s和head.S已经经过了编译连接,生成了可执行文件sysinit(红色标识处)。
可执行文件sysinit和sysinit.o有什么不同呢?
为什么我老是强调“可执行文件”这五个字呢?
肯定是有不同!
!
一说到这个东西,马上又是GNU/linux的编译连接机理。
还是让我们暂时先停下分析BIOS,来补一补在ARM学习报告002中没有讲完的GNU/linux机理问题吧!
!
磨刀不误砍柴功阿!
有关机理问题的补充我写在附录1中,不放在这了,省得打乱了BIOS的分析J
object文件虽然已经编译,但是里面的变量和地址都没有重定位,库函数也没有连接,所以这种文件虽然已经是相应CPU的二进制代码指令格式,却不能正常运行,只能等到连接重定位了,才算是真正的可执行程序。
那么接着上面分析,sysinit文件夹中的独立编译连接后,sysinit已经是可执行文件,连接的地址是0x03fe0000,这个下面会分析到,也就是说,只要能把sysinit拷到0x03fe0000处,并将PC寄存器设为0x03fe0000,那么sysinit就可以正确运行起来,只要内存分配没有冲突,这个sysinit和其他BIOS程序没有什么直接关系。
这就是所谓的“独立编译连接”。
先看看Makefile的其他部分:
/sysinit/Makefile中其他部分的介绍如下:
sysinit.bin:
sysinit
$(OBJCOPY)-Obinary--only-section=.init\
--only-section=.text\
--only-section=.rodata\
--only-section=.data\
--only-section=.bsssysinit\
sysinit.bin
上面这段Makefile命令是使用arm-elf-objcopy工具将sysinit这个可执行文件(elf格式)中的几个需要的段(.init.text.rodata.data.bss)抽取出来,组成一个ARM可执行文件,sysinit.bin,这个bin文件是二进制的,已经不是elf格式,仅仅包含了elf文件中抽取出来的几个段,完全以二进制形式“拼”在一起,去除了elf文件中系统使用的段(例如.systab.syntab等)。
强调是“ARM可执行文件”,也就是说sysinit.bin甚至可以烧入flash中运行,只要.text对应的地址正确(也就是相当于ro_base)。
相关机理在报告001、002和附录1中已经讲述。
../sysinit.c:
sysinit.bin
$(BIN2C)-c-ssysinit_datasysinit.bin../sysinit.c
上面这两行命令是使用bin2c工具将刚刚生成的sysinit.bin可执行二进制文件转变成只包含一个数组sysinit_data[]的C程序,这个C程序生成到上一层目录,也就是根目录下,打开这个C程序看看这个唯一的数组到底是什么内容?
原来数组是个unsignedcharsysinit_data[],里面包含的数据就是sysinit.bin的二进制字符,大家可以用ultraedit打开sysinit.bin核对看看,这里就不贴图了。
转变为数组的目的就是想利用数组名来定位sysinit.bin文件,因为根目录下的这个sysinit.c还要被编译连接成.o文件(这个sysinit.o和sysinit子文件夹里的完全不同了),在根目录下生成的sysinit.o的symboltable中只包含了一个变量,就是sysinit_data,数组首地址,那么BIOS在第二次连接时就可以在其他程序定位到这个sysinit_data变量,从而得到数组
-6-
ARM学习报告003杜云海duyunhai@中的内容——sysinit.bin,达到直接调用sysinit.bin的目的。
好好琢磨一下,等会儿程序分析时还会讨论到这个问题。
这种调用也是bios中比较巧妙的技术之一。
正是由于这样的定位方式,导致这些功能模块可以单独编译连接,而不必考虑最后生成bios时的连接定位。
如果你不能理解透,那么可能是GNU/linux机理还是没有吃透J
再来看看sysinit的ld连接文件
/sysinit/sysinit.ld
OUTPUT_ARCH(arm)
ENTRY(stext)
SECTIONS
{
.text0x03fe0000:
{/*Realtextsegment*/
_text=.;/*Textandread-onlydata*/
*(.text)
*(.rodata)
.=ALIGN(4);
_etext=.;/*Endoftextsection*/
}
.data:
{
__data_start=.;
*(.data)
.=ALIGN(4);
_edata=.;
}
.bss:
{
__bss_start=.;/*BSS*/
*(.bss)
.=ALIGN(4);
_end=.;
}
}
这个连接脚本文件的格式在002中已经讲述,不过这里还必须说明一点,连接具有局域性。
就是在各个子文件夹中连接,只和连接需要的几个.o文件有关,例如sysinit只和head.o、sysinit.o有关,且这两个.o文件都是从子文件夹的.c或.S源程序生成的,和上层根目录下的文件没有什么直接关系。
从这个连接脚本可以看出三个重要信息:
l该功能模块是单独编译连接,入口地址为stext,注意,这个stext是在/sysinit/head.S
中,不是根目录下的那个head.S。
根据连接结果stext=0x03fe0000,也就是下面提
到的代码段起始地址。
l从.text0x03fe0000看出这个可执行文件的代码段起始地址(ro_base)是0x03fe0000,
也就是说,只有把sysinit.bin送到0x03fe0000开始处的内存中,它才可以正确执行,而事实上,在后面我们要说的系统初始化时,sysinit.bin的确“整个”被送到了
0x03fe0000,这个地址是在哪里?
呵呵,是在S3C4510B的SRAM中,等会儿再说。
-7-
ARM学习报告003杜云海duyunhai@
l.data.bss紧随.text其后
形象来说,假设整个BIOS是一个操作系统,那么sysinit、biosapi、tftp、urat_rec就象可以运行在操作系统上的独立的应用软件一样,具有比较独立的功能,这个软件模块事先已经做好了,需要的时候只需将其装入,在windows中我们是通过双击,在这个BIOS中我们调用这个“软件模块”用的方法比较特殊,属于“Bootloader系统”的特色用法——先用bin2c将可执行bin文件转化为数组,然后调用数组名而得到其可执行代码。
所以我们在看BIOS代码的时候,甚至可以很孤立地看这三个功能模块,因为他们和BIOS其他部分交叉比较少!
只是调用的时候比较特殊一些!
正是基于以上原因,所以bios-dyh虽然比HaydenLuoBios少了几个功能模块,但是,对BIOS没有造成很大影响,不过是“操作系统上少装了几个软件而已”!
!
当然,由于是嵌入式系统,所以,功能模块在内存分布上还是要满足整个BIOS的“统筹规划”。
而不是随心所欲,这是和通用计算机编程不同的地方!
biosapi和tftp功能模块的机理和sysinit类似,这里就不赘述了,大家打开其子文件夹看看就清楚了。
这里只讲一讲他们的内存分布:
其中biosapi的连接地址为.text0x00001000,.data.bss紧跟其后;tftp的连接地址为.text0x00300000,.data.bss紧跟其后,关于具体的模块调用,到后面程序分析时再细说。
lsysinit:
系统初始化代码0x03fe0000
lbiosapi:
系统底层调用,主要是获得一些有关的参数0x00001000
ltftp:
当然是tftp服务器模块0x00300000
lurat_rec:
串口接收模块,我自己添的0x00400000
这四个模块只要用“bin2c+数组”的方法将其二进制代码(.bin)调到相应的连接地址处,该模块就可以正确运行了!
!
2.2第二把斧:
bios.bin的生成
第一阶段中,各个独立功能模块已经编译连接完成,并用bin2c在根目录下生成只包含一个数组的c程序,等待BIOS调用。
这时,第二部分可以启动了——makebios.bin,开始进入真正的bios!
也就是图1的第二部分。
makebios.bin到底要做多少事呢?
来看看Makefile吧
/Makefile
OBJ=head.obios.ogunzip.outils.oconsole.obioscall.o\
sysinit.obiosapi.otftp.ouart_rec.o
……
bios.bin:
bios
$(OBJCOPY)-Obinary\
--only-section=.init\
--only-section=.text\
--only-section=.rodata\
--only-section=.data\
--only-section=.bssbiosbios.bin
-8-
ARM学习报告003杜云海duyunhai@
bios:
$(OBJ)
$(LD)-p-X-Tbios.ld$(OBJ)\
-obios
从Makefile可以看出,bios.bin是由bios中的相应段section组成的,用arm-elf-objcopy工具,这个已经在上面说过了。
那么bios才是最实质的一个文件,一个经过连接的可执行文件,从上面Makefile可以看出bios是由几个.o文件连接而成(OBJ=head.obios.ogunzip.outils.oconsole.obioscall.osysinit.obiosapi.otftp.o,uart_rec.o,有顺序的阿),附录1中已经讲述了.o文件怎么连接生成可执行文件的机理。
那么.o文件怎么得到的呢?
当然是通过arm-elf-gcc编译各个c程序得到的。
从图1的第二阶段可以看出,包括第一阶段生成的三个c程序(只包含一个数组)在内,所有的c程序都是“平等”的:
lhead.S:
这个是入口汇编程序,跳入bios_main
lbios.c:
这个c程序当然包括了bios主函数bios_main(),也就是bios的主体函数,
这个函数中调用了其他模块函数
lgunzip.c:
解压缩程序,有些功能模块都经过压缩,例如biosapi、tftp等,要先将
其解压缩到相应地址处,才可以正确执行
lutils.c:
系统通用一些工具函数
lbioscall.c:
主要是调用biosapi的一些封装好的函数
lconsole.c:
有关串口的一些函数,例如初始化,读写串口等等
lborad.h:
开发板的寄存器地址以及其他一些宏定义
lconfig.h:
系统配置头文件,主要是配置系统中ROM或DRAM的起始地址,各个
独立功能的调用地址,系统参数表的地址等等
l还有其他一些相关头文件
这些c程序结合.h文件被gcc编译后,形成了相应的.o文件,这时他们之间还是孤立的,等待连接,连接的机制已经在附录1中说过,用rel<.section>和symboltable来将各个.o文件联系起来,并根据.ld连接脚本,合并相应的段,从而形成了可执行文件bios(注,这个bios不是笼统的bootloader,而是在根目录下生成的bios文件)。
大致步骤分以下两步:
第一步,所有的c源程序都被arm-elf-gcc编译生成.o文件,从图1的②可以看出,这时大家之间并没有什么联系,都是一个个独立的.o文件。
结合报告002和附录1的机理分析可知,每个.o文件中除了包含.text.rodata.data.bss段外,还包含了.rel
大家之间仿佛独立,其实里面千丝万缕的联系,只是这些关系要有ld来“牵”起来。
第二步,GNUld根据.ld文件(连接脚本)合并各个输入.o文件的各个相同的段(见报告002),主要合并.text.rodata.data.bss这四个段,相当于RO、RW、ZI。
合并后,开始形成一个新的symboltable,这个symboltable包含了所有输入.o文件的symbol的值,并根据合并后的新地址更新了各个symbol值,用这些新的symbol值来更新.rel
从连接脚本看出:
/Makefile
SECTIONS
{
.text0x01000000:
{/*Realtextsegment
_text=.;/*Textandread-onlydata*/*/
-9-
ARM学习报告003杜云海duyunhai@
*(.t
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- ARM 学习 报告