Presentation is loading. Please wait.

Presentation is loading. Please wait.

(第2版).

Similar presentations


Presentation on theme: "(第2版)."— Presentation transcript:

1 (第2版)

2 主讲内容 第8章 嵌入式系统Boot Loader技术 第9章 嵌入式Linux操作系统移植 第10章 嵌入式Linux设备驱动程序开发

3 Boot Loader的基本概念 8.1 BootLoader(vivi)的代码分析 8.2

4 8.1 Boot Loader的基本概念 Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。

5 8.1.1 Boot Loader的相关设备和机制 通常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 Boot Loader 几乎是不可能的。尽管如此,我们仍然可以对 Boot Loader 归纳出一些通用的概念来,以指导用户特定的 Boot Loader 设计与实现。 1. Boot Loader 所支持的 CPU 和嵌入式板 每种不同的 CPU 体系结构都有不同的 Boot Loader。有些 Boot Loader 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS(Million Instructions Per Second)体系结构。除了依赖于 CPU 的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。 2. Boot Loader 的安装媒介(Installation Medium) 系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x 取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在系统加电后,CPU 将首先执行 Boot Loader 程序。

6 3. 用来控制 Boot Loader 的设备或机制 主机和目标机之间一般通过串口建立连接,Boot Loader 软件在执行时通常会通过串口来进行 I/O,比如:输出打印信息到串口,从串口读取用户控制字符等。 4. Boot Loader 的启动过程是单阶段(Single Stage)还是多阶段(Multi-Stage) 通常多阶段的 Boot Loader 能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程,也即启动过程可以分为 stage 1 和 stage 2 两部分。而至于在 stage 1 和 stage 2 具体完成哪些任务将在下面讨论。 5. Boot Loader 的操作模式 (Operation Mode) 大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。 6. BootLoader 与主机之间进行文件传输所用的通信设备及协议 最常见的情况就是,目标机上的 Boot Loader 通过串口与主机之间进行文件传输,传输协议通常是 xmodem/ymodem/zmodem 协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助 TFTP 协议来下载文件是个更好的选择。

7 8.1.2 Boot Loader的启动过程 在专用的嵌入式板子运行 GNU/Linux 系统已经变得越来越流行。一个嵌入式 Linux 系统从软件的角度看通常可以分为四个层次: 1. 引导加载程序。包括固化在固件(firmware)中的 boot 代码(可选),和 Boot Loader 两大部分。 2. Linux 内核。特定于嵌入式板子的定制内核以及内核的启动参数。 3. 文件系统。包括根文件系统和建立于 Flash 内存设备之上文件系统。通常用 ram disk 来作为 root fs。 4. 用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有:MicroWindows 和 MiniGUI 等。

8 8.1.3 Boot Loader的操作模式 大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式, 启动加载(Boot loading)模式:这种模式也称为"自主"(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。 下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。

9 8.1.4 Boot Loader的总体设计 嵌入式系统中内核映像与根文件系统映像也可以直接在 ROM 或 Flash 这样的固态存储设备中直接运行。但这种做法无疑是以运行速度的牺牲为代价的。 从操作系统的角度看,Boot Loader 的总目标就是正确地调用内核来执行。 另外,由于 Boot Loader 的实现依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为 stage1 和 stage2 两大部分。依赖于 CPU 体系结构的代码,比如设备初始化代码等,通常都放在 stage1 中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而 stage2 则通常用C语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。

10 Boot Loader 的 stage1 通常包括以下步骤(以执行的先后顺序):
硬件设备初始化。 为加载 Boot Loader 的 stage2 准备 RAM 空间。 拷贝 Boot Loader 的 stage2 到 RAM 空间中。 设置好堆栈。 跳转到 stage2 的 C 入口点。 Boot Loader 的 stage2 通常包括以下步骤(以执行的先后顺序): 初始化本阶段要使用到的硬件设备。 检测系统内存映射(memory map)。 将 kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中。 为内核设置启动参数。 调用内核。

11 Stage1: 1、 基本的硬件初始化 这是 Boot Loader 一开始就执行的操作,其目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后顺序): (1) 屏蔽所有的中断。 (2)设置 CPU 的速度和时钟频率。 (3) RAM 初始化。 (4) 初始化 LED。 (5) 关闭 CPU 内部指令/数据 cache。

12 2.为加载 stage2 准备 RAM 空间 先保存 memory page 一开始两个字的内容。 向这两个字中写入任意的数字。比如:向第一个字写入 0x55,第 2 个字写入 0xaa。 然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是 0x55 和 0xaa。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。 再向这两个字中写入任意的数字。比如:向第一个字写入 0xaa,第 2 个字中写入 0x55。 然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是 0xaa 和 0x55。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。 恢复这两个字的原始内容。测试完毕。

13 3、 拷贝 stage2 到 RAM 中 拷贝时要确定两点: 一、 stage2 的可执行映象在固态存储设备的存放起始地址和终止地址; 二、RAM 空间的起始地址。 4、 设置堆栈指针 sp 堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4) 此外,在设置堆栈指针 sp 之前,也可以关闭 led 灯,以提示用户我们准备跳转到 stage2。 经过上述这些执行步骤后,系统的物理内存布局应该如下图8-2所示。

14 5、跳转到 stage2 的 C 入口点 在上述一切都就绪后,就可以跳转到 Boot Loader 的 stage2 去执行了。比如,在 ARM 系统中,这可以通过修改 PC 寄存器为合适的地址来实现。

15 Stage2: 1、初始化本阶段要使用到的硬件设备 这通常包括: (1)初始化至少一个串口,以便和终端用户进行 I/O 输出信息; (2)初始化计时器等。 在初始化这些设备之前,也可以重新把 LED 灯点亮,以表明我们已经进入 main() 函数执行。 设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。

16 2、 检测系统的内存映射(memory map) (1) 内存映射的描述 可以用如下数据结构来描述 RAM 地址空间中的一段连续(continuous)的地址范围: typedef struct memory_area_struct { u32 start; /* the base address of the memory region */ u32 size; /* the byte number of the memory region */ int used; } memory_area_t;

17 (2) 内存映射的检测 for(i = 0; i < NUM_MEM_AREAS; i++)
memory_map[i].used = 0; for(addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE) * (u32 *)addr = 0; for(i = 0, addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE) { if ( current memory page isnot a valid ram page) { if(memory_map[i].used ) i++; continue; } if(* (u32 *)addr != 0) { if ( memory_map[i].used ) if (memory_map[i].used == 0) { memory_map[i].start = addr; memory_map[i].size = PAGE_SIZE; memory_map[i].used = 1; } else { memory_map[i].size += PAGE_SIZE;

18 3、 加载内核映像和根文件系统映像 (1) 规划内存占用的布局 这里包括两个方面: 内核映像所占用的内存范围; 根文件系统所占用的内存范围。 (2)从 Flash 上拷贝 由于像 ARM 这样的嵌入式 CPU 通常都是在统一的内存地址空间中寻址 Flash 等固态存储设备的,因此从 Flash 上读取数据与从 RAM 单元中读取数据并没有什么不同。用一个简单的循环就可以完成从 Flash 设备上拷贝映像的工作: while(count) { *dest++ = *src++; count -= 4; };

19 4、设置内核的启动参数 Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中:

20 5、 调用内核 Boot Loader 调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START+0x8000 地址处。在跳转时,下列条件要满足: (1) CPU 寄存器的设置: R0=0; R1=机器类型 ID;关于 Machine Type Number,可以参见 linux/arch/arm/tools/mach-types。 R2=启动参数标记列表在 RAM 中起始基地址; (2) CPU 模式: 必须禁止中断(IRQs和FIQs); CPU 必须 SVC 模式; (3) Cache 和 MMU 的设置: MMU 必须关闭; 指令 Cache 可以打开也可以关闭; 数据 Cache 必须关闭;

21 8.2 BootLoader(vivi)的代码分析 Bootloader有很多种,如本文将要阅读的vivi,除此之外还有uboot,redboot,lilo等等。Vivi 是韩国mizi公司专门为三星s3c2440芯片设计的Bootloader。 vivi也可以分为2个阶段,阶段1的代码在arch/s3c2440/head.S中,阶段2的代码从init/main.c的main函数开始。

22 阶段1 1. 关WATCH DOG (disable watch dog timer) 上电后,WATCH DOG默认是开着的 2
阶段1 1.关WATCH DOG (disable watch dog timer) 上电后,WATCH DOG默认是开着的 2.禁止所有中断 (disable all interrupts) vivi中不会用到中断,中断是系统的事,bootloader可不能去干这事的(不过这段代码实在多余,上电后中断默认是关闭的) 3.初始化系统时钟启动MPLL,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz,“CPU bus mode”改为“Asynchronous bus mode”。 4.初始化内存控制寄存器(memsetup):S3c2440共有15个寄存器,在此开始初始化13个寄存器。 5.检查是否从掉电模式唤醒(Check if this is a wake-up from sleep)若是,则调用WakeupStart函数进行处理。 6.点亮所有LED (All LED on)点一下灯,通知外面的同志,告诉他们有情况发生。

23 7.初始化UART0 (set GPIO for UART & InitUART) (1)设置GPIO,选择UART0使用的引脚 (2)初始化UART0,设置工作方式(使用FIFO)、波特率 N1、无流控等。这可是使用串口与s3c2440通信的条件啊,在终端也要如此设置。 8.跳到内存测试函数 9.如果定义了以Nand flash方式启动,则此时要将vivi所有代码(包括阶段1和阶段2)从Nand flash复制到SDRAM中 (1)设置nand flash控制寄存器 (2)设置堆栈指针 (3)设置即将调用的函数nand_read_ll的参数 (4)调用nand_read_ll进行复制 10.跳到bootloader的阶段2运行,亦即调用init/main.c中的main函数(get read to call C functions) (1)重新设置堆栈 (2)设置main函数的参数 (3)调用main函数

24 阶段2 从init/main.c中的main函数开始,终于步入C语言的世界了。Main函数总共有5步 1、Step 1:reset_handler() 2、Step 2:board_init() board_init调用2个函数用于初始化定时器和设置各GPIO引脚功能 3、Step 3:建立页表和启动MMU mem_map_init函数用于建立页表 mmu_init()函数用于启动MMU 4、Step 4:heap_init() 第4步调用了heap_init(void)函数,并返回值。该值是函数heap_init()调用的mmalloc_init()函数的返回值。其实,这步就是申请一块内存区域。

25 5、Step 5:mtd_dev_init() 在linux系统中,我们通常会用到不同的存储设备,特别是FLASH设备。为了在使用新的存储设备时,我们能更简便地提供它的驱动程序,在上层应用和硬件驱动的中间,抽象出MTD设备层。驱动层不必关心存储的数据格式如何,比如是FAT32、ETX2还是FFS2或其它。它仅仅提供一些简单的接口,比如读写、擦除及查询。如何组织数据,则是上层应用的事情。MTD层将驱动层提供的函数封装起来,向上层提供统一的接口。这样,上层即可专注于文件系统的实现,而不必关心存储设备的具体操作。


Download ppt "(第2版)."

Similar presentations


Ads by Google