ucos在s3c2410上运行过程整体剖析-从加电到执行main函数
扫描二维码
随时随地手机看文章
先说明一下在加电之前的这个软硬件情况,这个三星公司根据ARM920T软核生产的这个s3c2410集成了64M的sdram和64M的nandflash存储器。Vivi和UCOS都存储在这个nandflash中,因为nandflash断电后不会丢失信息。这个VIVI是三星公公司为ARM系列芯片书写的bootloader,用于开发阶段,做系统的引导程序。
VIVI存放在flash 0x00000000地址开始的地方,UCOS存放在flash 0x03f30000地址开始的地方。ARM920T开机从flash启动,启动时把flash前4K (即vivi的前4K)COPY到SDRAM(这种启动方式是利用Nandflash启动,COPY前4K到sdram中是硬件自动实现的),vivi的前4K 代码中有用于COPY剩余VIVI的代码。执行完这些代码之后,VIVI就控制了FLASH的读取,串口的控制以及用户shell接口,当然它还有其他一些功能。当用户执行bootucos命令时,VIVI会把ucos相关代码从flash 0x03f30000 COPY到SDRAM0x30008000的地方。当然也可以设置VIVI自动引导ucos执行。当代码copy完毕后,vivi会把PC值改成0x30008000去执行。
我们先说一下为什么我们非要说具体的那个地址那,咱们前面说了,编译好的程序有一个load地址,一个真正运行的地址,0x30008000这个地址就是咱们说的程序的装载地址,这个地址是我们用编译器指定的地址,也就是通过在ads工程里后缀名是scf的文件配置的。在这个文件里我们配置了程序的装载地址和程序运行的地址,我们为什么要指定这两个地址那?我们整个工程的程序是最后链接时一次性固定的绝对地址,也就是说最终链接出来的程序地址和真正运行的地址是一致的。只不过我们一般不会把这些代码直接放到相应的部位去罢了,其中一个原因就是,我们为了在不加电时保存程序会把程序放到非易失的存储设备里去,而我们运行时会把程序copy到运行速度比较快的sdram中去。也就是说,本来这些静态链接的程序的执行地址都是固定的了,我们要在这些程序运行之前要把这些程序放对位置。我们必须知道我们的程序装载到什么地址和真正在什么地址运行。这样我们才能知道那些装载地址和运行地址不一样的程序段应该怎么搬运。至于搬运的工作,你可以自己手工实现,也可以用ADS提供的库函数实现。
跳转到这个0x30008000去执行这个地址处的指令,我们这个工程编译出来后谁是第一条指令那?我们平时写的程序都是从main()函数开始执行,但我们这个嵌入式的开发可不是哦,在分析完启动代码后你就知道了,在执行的所谓的main()函数之前要做很多工作的。
arm映像文件的入口点有两种类型:一种是映像文件运行时的入口点,称为初始入口点(initial entry point),另一种是普通入口点(entry point).
初始入口点是映像文件运行时的入口点,每个映像文件只有一个唯一的初始入口点,它保存在ELF头文件中。假如映像文件是被操作系统加载的,操作系统是通过跳转到该初始入口点处来加载该映像文件。
普通的入口点是在汇编中用ENTRY伪操作定义。他通常用于标志该段代码是通过异常中断处理程序进入的。这样连接器删除无用的段时不会将该段代码删除。一个映像文件中可以定义多个普通入口点。
应该注重的是,初始入口点可以使普通入口点,但也可以不是普通入口点.
初始入口点必须满足下面两个条件:
1.初始入口点必须位于映像文件的运行时域内。
1.1饱含初始入口点的运行时域不能被覆盖,他的加载地址和运行地址必须是相同的。
可以使用连接选项-entry address来指定映像文件的初始入口点。这时,address指定了映像文件的初始入口点的地址值。对于地址0x0处为rom的嵌入式应用系统,可以使用-entry 0x0来指定映像文件的初始入口点。这样当系统复位后,自动跳转到该入口开始执行。假如映像文件是被一个加载器加载的,该映像文件该映像文件必须包含一个初始化入口点。这种映像文件通常还包含了其他普通入口点,这些普通入口点一般为异常中断处理程序的入口地址。
当用户没有指定-entry address时,连接器根据下面的规则决定映像文件的初始入口点。
假如输入的目标文件中只有一个普通入口点,该普通入口点被连接器当成映像文件的初始入口点。
假如输入的目标文件中没有一个普通入口点,或者其中的普通入口点多于一个,则连接器生成的映像文。
我们编译好的可执行文件时去除了头格式的映像文件,我们讲的本来就是操作系统,所以这个程序不是通过初始入口点执行的第一条指令,应该是通过普通入口点来执行的,通常是中断向量表。也就是程序中用伪指令entry指定的指令段的第一条指令。我们用ADS1.2打开ucos的学习资料的工程中的第十个实验(ucos系统移植实验)。在startup文件夹中有一个startup.s 的汇编程序,这个就是ucos的启动代码了。由ENTRY伪指令指定的第一条指令是b ColdReset,所以第一条指令就是它了。
咱先不管这个第一条指令的问题,我的目的是把我学习的UCOS讲述给你听,但这需要一定的讲述规范,希望我说的你能听懂,愿意看下去,我想这样做:
先从整体描述一下整个过程,然后在分阶段概括这一阶段整个硬件和软件系统干了什么?为什么会有这些顺序?为什么要这么干?在这个过程中可能思维随即发散到任何有关系的知识点。最后我将逐一分析源代码,在分析源代码时遇到的问题,都将解决,当然包括那些精华和美。还可能阐述一下我的理解和方法,以及我对学习的一些认识。我是想按照一定的规范去写这个东东,但是我又不想完全按照一种思路去写,毕竟我是随意书写的。我的整体思路就是针对硬件和软件在整个时间流里都干了什么?为什么要这么干为主要线路。在这个线路中涉及到的所有疑问和知识点都将一一展开阐述。我尽量做到自然,而不是强加给你一些生硬的概念,因为人不喜欢被。被学习,被干活,被记忆。
理解UCOS最好的方式是阅读其源代码,一本很好的参考书是嵌入式实时操作系统ucos-ii,邵贝贝译
声明:在写这个文档时,我还有很多地方没有真正弄明白,所以有些地方可能我也说不清楚,但我会把我的疑问写出来,我什么时候想明白了,我会把它写出来,如果你知道请你告诉我,我会很高兴的。
在说明一下现在的情况:现在ucos的所有代码(包括启动的bootloader)都被vivi copy到0x30008000的内存地址开始的地方了,然后PC值改为0x30008000,取出这个地址放的arm指令就开始执行这条指令了。前面已经分析完整个工程编译出来的可执行程序的第一条指令了。
好了,下面开始说整个班子以及UCOS的整体启动过程,只是大概的说明流程,至于会为什么这样的问题等到具体详解的时候在具体解释。
硬件初始化,主要是让硬件平台处于一个可知的状态,重要的一点就是初始化C语言运行环境。
UCOS初始化
UCOS运行并执行应用程序
哎 ,这样看的话,整个过程还真挺简单的,哈。
下面具体讲解硬件初始化阶段,这个就真的比较麻烦了,但没关系,咱们慢慢说。
从具体代码上看,它主要干了这些活:
关闭看门狗,(一个用于开发阶段的硬件,到代码讲解时具体说明)
屏蔽中断掩码寄存器(现在整个硬件平台的控制权都在UCOS,在初始化的时候,我们不希望被打扰,具体原因我们以后说)
初始化各个模式堆栈空间(堆栈空间很重要哦)
COPY中断向量表(关于为什么要copy,我们在后面说)
初始化c库环境
然后跳转到主应用程序(即我们平时说的main()函数)
下面这些代码是用汇编写的代码,其中分号后面的是注释。
下面就以具体代码为例,详细讲解启动代码。
每个代码块做一个说明,对于特别重要的代码,我在代码后面做详细注释。注释写在//后面,如果此处有很重要知识点的话,单独起一段进行说明。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Copyright (c) 2004-2007 threewater@up-tech.com, All rights reserved.
;;;
;;; Startup Code for
;;; S3C2410 : Startup.s
;;;; by threewater 2005.2.22
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GET 2410addr.s //引入2410addr.s文件里的内容,作用像是c语言里的#include一样。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Some ARM920 CPSR bit discriptions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Pre-defined constants
USERMODE EQU 0x10
FIQMODE EQU 0x11
IRQMODE EQU 0x12
SVCMODE EQU 0x13
ABORTMODE EQU 0x17
UNDEFMODE EQU 0x1b
MODEMASK EQU 0x1f
NOINT EQU 0xc0
I_Bit * 0x80