我是靠谱客的博主 高挑曲奇,最近开发中收集的这篇文章主要介绍从烧录程序到设备加载运行,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前言

    在最近的一个项目中,突然想起以前学习嵌入式系统启动流程,所以借此机会复习一下从烧录程序到设备加载代码运行的过程,加深印象。

一.程序的结构

一个程序一般分为3段:text段,data段,bss段

text段:就是放程序代码的,编译时确定,只读,

data段:存放在编译阶段(而非运行时)就能确定的数据,可读可写

就是通常所说的静态存储区,赋了初值的全局变量和静态变量存放在这个区域,常量也存放在这个区域

bss段:定义而没有赋初值的全局变量和静态变量,放在这个区域

我们编译完成后生成烧录文件,一般单片机的HEX文件,还有些其他类型的文件,内容基本上都是上述三个段。接下来用烧录工具把生成的文件烧录到机器中的ROM或者FLASH。

二.设备启动

    以一般的单片机或者嵌入式Linux产品为例,CPU芯片中一般还有两个片内的ROM和RAM,ROM中存着一段芯片厂家出厂就写好的指令。机器启动时,CPU会运行ROM中的指令,把Flash中bootloader的第一部分,加载到CPU的RAM中,这个阶段做的事是:

1.硬件设备初始化:CPU的工作模式、关看门狗、设置时钟、关MMU、关CACHE等 (代码在cpu/arm920t/start.S中reset:)
2.为加载Bootloader的第二阶段代码准备RAM空间:初始化内存芯片SDRAM,使得外接 的SDRAM可用 (代码在board/smdk2410/lowlevel_Init.S中的lowlevel.Init中,是在start.S中调用的)
3.复制Bootloader的第二阶段代码在RAM空间中:这里将整个U-boot的代码(包括第一、 第二阶段)都复制到SDRAM中 (代码在cpu/arm920t/start.S中实现)
4.设置好堆栈:堆栈的灵活性很大,只要让sp寄存器指向一段没有使用的内存即可 (代码在cpu/arm920t/start.S中实现)
5.清除BSS段之后,跳转到第二阶段的C代码入口点:ldrpc,_start_armboot _start_armboot:.wordstart_armboot (代码在cpu/arm920t/start.S中实现,被调用的函数start_armboot在lib_arm/board.c中)

第二阶段:

6.初始化本阶段要使用的硬件设备:如设置系统时钟、初始化串口。注意board_init函数 还保存了机器类型的ID (如代码在board/smdk2410/smdk2410.c中的board_init、在cpu/arm920t/s3c24x0/srial.c中的serial.init)

7.检测系统内存映射:确定板上使用了多少内存、它们的地址空间是什么,检测到的参数 在向内核传递参数的时候用到 (代码在board/smdk2410/smdk2410.c中的dram_init)
8.将内核映像和根文件系统映像从Flash上读到RAM空间中。内核的复制和启动,这里 是通过命令bootm、bootp、nboot来完成的,这些命令实际上是调用相应的函数,先将映像从各种媒介中读出,存放在指定的位置 (u-boot中的命令都是通过U_BOOT_CMD宏来定义的, U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help"))
9. 为内核{设置}启动参数:U-Boot也是通过【标记列表】向内核传递参数。一般而言只设置内存标记-取值函数setup_memory_tags和命令行标记-取值setup_commandline_tag就可以了。这一步很简单,仅仅是配置对应的两个宏就可以了(这两个函数在lib_arm/armlinux.c中定义)
10.调用内核。对于ARM架构的CPU,都是通过lib_arm/armlinux.c中的do_bootm_linux函 数来启动内核。在这个函数中,先设置标记列表,最后通过theKernel(0,bd->bi_arch_number,bd->bi_boot_params);调用内核。 theKernel指向内核存放的地址(对于ARM架构的CPU,通常是0x30008000),bd->bi_arch_number就是前面board_init函数设置的机器类型ID,而bd_bi_boot_params就是标记列表的开始地址(lib_arm/armlinux.c)

注意:
1、上面的有些步奏可能有些不是必需的、可以调换顺序,比如在S3C2410/S3C2440的开发板中使用的U-Boot中,就是将CPU的速度和时钟频率的设置放到第二阶段;
2、Flash上的内核映像有可能是经过压缩的,在读到RAM之后,还要进行解压,当然,对于有自解压的功能的内核,不需要Bootloadr来解压
3、将根文件系统映像复制到RAM中也不是必须的,这取决于是什么类型的根文件系统,以及内核访问它的方法
4、甚至,将第二阶段的代码复制到RAM空间也不是必需的,对于NORFlash等存储设备,完全可以在上面直接执行代码,只不过相比在RAM中执行效率大为降低


对于一般的单片机,可能没有那么复杂,简单的来说,这两个阶段就是,初始化外部的RAM,初始化其他的硬件设备,把FLash中的text, data,bss三段加载到RAM中,加载的过程如下:

(1)为全局变量分配地址空间---如果全局变量已赋初值,则将初始值从ROM中拷贝到RAM中,如果没有赋初值,则这个全局变量所对应的地址下的初值为0或者是不确定的。当然,如果已经指定了变量的地址空间,则直接定位到对应的地址就行,那么这里分配地址及定位地址的任务由“连接器”完成。

(2)设置堆栈段的长度及地址---用C语言开发的单片机程序里面,普遍都没有涉及到堆栈段长度的设置,但这不意味着不用设置。堆栈段主要是用来在中断处理时起“保存现场”及“现场还原”的作用,其重要性不言而喻。而这么重要的内容,也包含在了编译器预设的内容里面,确实省事,可并不一定省心。
(3)分配数据段data,常量段const,代码段code的起始地址——代码段与常量段的地址可以不管,它们都是固定在ROM里面的,无论它们怎么排列,都不会对程序产生影响。但是数据段的地址就必须得关心。数据段的数据时要从ROM拷贝到RAM中去的,而在RAM中,既有数据段data,也有堆栈段stack,还有通用的工作寄存器组。通常,工作寄存器组的地址是固定的,这就要求在绝对定址数据段时,不能使数据段覆盖所有的工作寄存器组的地址。必须引起严重关注。
注:这里所说的“第一行代码处”,并不一定是你自己写的程序代码,绝大部分都是编译器代劳的,或者是编译器自带的demo程序文件。因为,你自己写的程序(C语言程序)里面,并不包含这些内容。高级一点的单片机,这些内容,都是在startup的文件里面。
4、普通的flashMCU是在上电时或复位时,PC指针里面的存放的是“0000”,表示CPU从ROM的0000地址开始执行指令,在该地址处放一条跳转指令,使程序跳转到_main函数中,然后根据不同的指令,一条一条的执行,当中断发生时(中断数量也很有限,2~5个中断),按照系统分配的中断向量表地址,在中断向量里面,放置一条跳转到中断服务程序的指令,如此如此,整个程序就跑起来了。决定CPU这样做,是这种ROM结构所造成的。
注:特别的,如下

1--I/O口寄存器:也是可以被改变的量,它被安排在一个特别的RAM地址,为系统所访问,而不能将其他变量定义在这些位置。

2--中断向量表:中断向量表是被固定在MCU内部的ROM地址中,不同的地址对应不同的中断。每次中断产生时,直接调用对应的中断服务子程序,将程序的入口地址放在中断向量表中。

总结有如下几段:

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放

4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放

5、程序代码区—存放函数体的二进制代码。

如果哪位有缘人看到觉得有帮助,就随手端个赞呗~

最后

以上就是高挑曲奇为你收集整理的从烧录程序到设备加载运行的全部内容,希望文章能够帮你解决从烧录程序到设备加载运行所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部