⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 阿冰blog 嵌入式系统 boot loader 技术内幕.htm

📁 arm体系结构和编程,一份很好的ARM汇编编程资料
💻 HTM
📖 第 1 页 / 共 5 页
字号:
          <TD><PRE><CODE>
<FONT face=Courier size=2>.text

.globl _trampoline
_trampoline:
	bl	main
	/* if main ever returns we just call it again */
	b	_trampoline
</FONT></CODE></PRE></TD></TR></TBODY></TABLE>
      <P>可以看出,当 main() 函数返回后,我们又用一条跳转指令重新执行 trampoline 程序――当然也就重新执行 main() 
      函数,这也就是 trampoline(弹簧床)一词的意思所在。 </P>
      <P><B xmlns:dw="http://www.ibm.com/developerworks/" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">3.2.1初始化本阶段要使用到的硬件设备</B> 
      </P>
      <P>这通常包括:(1)初始化至少一个串口,以便和终端用户进行 I/O 输出信息;(2)初始化计时器等。 </P>
      <P>在初始化这些设备之前,也可以重新把 LED 灯点亮,以表明我们已经进入 main() 函数执行。 </P>
      <P>设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。 </P>
      <P><B xmlns:dw="http://www.ibm.com/developerworks/" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">3.2.2 
      检测系统的内存映射(memory map)</B> </P>
      <P>所谓内存映射就是指在整个 4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如,在 SA-1100 CPU 中,从 
      0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间,而在 Samsung S3C44B0X CPU 中,从 
      0x0c00,0000 到 0x1000,0000 之间的 64M 地址空间被用作系统的 RAM 地址空间。虽然 CPU 
      通常预留出一大段足够的地址空间给系统 RAM,但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 
      地址空间。也就是说,具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上,而让剩下的那部分预留 RAM 
      地址空间处于未使用状态。 <B xmlns:dw="http://www.ibm.com/developerworks/" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">由于上述这个事实,因此 Boot 
      Loader 的 stage2 必须在它想干点什么 (比如,将存储在 flash 上的内核映像读到 RAM 空间中) 
      之前检测整个系统的内存映射情况,也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元,哪些是处于 
      "unused" 状态的。</B> </P>
      <P><B xmlns:dw="http://www.ibm.com/developerworks/" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">(1) 内存映射的描述</B> </P>
      <P>可以用如下数据结构来描述 RAM 地址空间中的一段连续(continuous)的地址范围:</P>
      <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc 
        border=1><TBODY>
        <TR>
          <TD><PRE><CODE>
<FONT face=Courier size=2>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;
</FONT></CODE></PRE></TD></TR></TBODY></TABLE>
      <P>这段 RAM 地址空间中的连续地址范围可以处于两种状态之一:(1)used=1,则说明这段连续的地址范围已被实现,也即真正地被映射到 RAM 
      单元上。(2)used=0,则说明这段连续的地址范围并未被系统所实现,而是处于未使用状态。 </P>
      <P>基于上述 memory_area_t 数据结构,整个 CPU 预留的 RAM 地址空间可以用一个 memory_area_t 
      类型的数组来表示,如下所示: </P>
      <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc 
        border=1><TBODY>
        <TR>
          <TD><PRE><CODE>
<FONT face=Courier size=2>memory_area_t memory_map[NUM_MEM_AREAS] = {
	[0 ... (NUM_MEM_AREAS - 1)] = {
		.start = 0,
		.size = 0,
		.used = 0
	},
};
</FONT></CODE></PRE></TD></TR></TBODY></TABLE>
      <P>(2) 内存映射的检测 </P>
      <P>下面我们给出一个可用来检测整个 RAM 地址空间内存映射情况的简单而有效的算法: </P>
      <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc 
        border=1><TBODY>
        <TR>
          <TD><PRE><CODE>
<FONT face=Courier size=2>/* 数组初始化 */
for(i = 0; i &lt; NUM_MEM_AREAS; i++)
	memory_map[i].used = 0;

/* first write a 0 to all memory locations */
for(addr = MEM_START; addr &lt; MEM_END; addr += PAGE_SIZE)
	* (u32 *)addr = 0;

for(i = 0, addr = MEM_START; addr &lt; MEM_END; addr += PAGE_SIZE) {
     /*
      * 检测从基地址 MEM_START+i*PAGE_SIZE 开始,大小为
* PAGE_SIZE 的地址空间是否是有效的RAM地址空间。
      */
     调用3.1.2节中的算法test_mempage();
     if ( current memory page isnot a valid ram page) {
		/* no RAM here */
		if(memory_map[i].used )
			i++;
		continue;
	}
	
	/*
	 * 当前页已经是一个被映射到 RAM 的有效地址范围
	 * 但是还要看看当前页是否只是 4GB 地址空间中某个地址页的别名?
	 */
	if(* (u32 *)addr != 0) { /* alias? */
		/* 这个内存页是 4GB 地址空间中某个地址页的别名 */
		if ( memory_map[i].used )
			i++;
		continue;
	}
	
	/*
	 * 当前页已经是一个被映射到 RAM 的有效地址范围
	 * 而且它也不是 4GB 地址空间中某个地址页的别名。
	 */
	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;
	}
} /* end of for (…) */
</FONT></CODE></PRE></TD></TR></TBODY></TABLE>
      <P>在用上述算法检测完系统的内存映射情况后,Boot Loader 也可以将内存映射的详细信息打印到串口。 </P>
      <P><B xmlns:dw="http://www.ibm.com/developerworks/" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">3.2.3 
      加载内核映像和根文件系统映像</B> </P>
      <P><B xmlns:dw="http://www.ibm.com/developerworks/" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">(1) 规划内存占用的布局</B> 
      </P>
      <P>这里包括两个方面:(1)内核映像所占用的内存范围;(2)根文件系统所占用的内存范围。在规划内存占用的布局时,主要考虑基地址和映像的大小两个方面。 
      </P>
      <P>对于内核映像,一般将其拷贝到从(MEM_START+0x8000) 这个基地址开始的大约1MB大小的内存范围内(嵌入式 Linux 
      的内核一般都不操过 1MB)。为什么要把从 MEM_START 到 MEM_START+0x8000 这段 32KB 大小的内存空出来呢?这是因为 
      Linux 内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。 </P>
      <P>而对于根文件系统映像,则一般将其拷贝到 MEM_START+0x0010,0000 开始的地方。如果用 Ramdisk 
      作为根文件系统映像,则其解压后的大小一般是1MB。 </P>
      <P><B xmlns:dw="http://www.ibm.com/developerworks/" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">(2)从 Flash 上拷贝</B> 
      </P>
      <P>由于像 ARM 这样的嵌入式 CPU 通常都是在统一的内存地址空间中寻址 Flash 等固态存储设备的,因此从 Flash 上读取数据与从 
      RAM 单元中读取数据并没有什么不同。用一个简单的循环就可以完成从 Flash 设备上拷贝映像的工作: </P>
      <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc 
        border=1><TBODY>
        <TR>
          <TD><PRE><CODE><FONT face=Courier size=2> 
while(count) {
	*dest++ = *src++; /* they are all aligned with word boundary */
	count -= 4; /* byte number */
};
</FONT></CODE></PRE></TD></TR></TBODY></TABLE>
      <P><B xmlns:dw="http://www.ibm.com/developerworks/" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">3.2.4 设置内核的启动参数</B> 
      </P>
      <P>应该说,在将内核映像和根文件系统映像拷贝到 RAM 空间中后,就可以准备启动 Linux 
      内核了。但是在调用内核之前,应该作一步准备工作,即:设置 Linux 内核的启动参数。 </P>
      <P>Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 
      开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 
      tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中: </P>
      <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc 
        border=1><TBODY>
        <TR>
          <TD><PRE><CODE>
<FONT face=Courier size=2>/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE	0x00000000

struct tag_header {
	u32 size; /* 注意,这里size是字数为单位的 */
	u32 tag;
};
……
struct tag {
	struct tag_header hdr;
	union {
		struct tag_core		core;
		struct tag_mem32	mem;
		struct tag_videotext	videotext;
		struct tag_ramdisk	ramdisk;
		struct tag_initrd	initrd;
		struct tag_serialnr	serialnr;
		struct tag_revision	revision;
		struct tag_videolfb	videolfb;
		struct tag_cmdline	cmdline;

		/*
		 * Acorn specific
		 */
		struct tag_acorn	acorn;

		/*
		 * DC21285 specific
		 */
		struct tag_memclk	memclk;
	} u;
};
</FONT></CODE></PRE></TD></TR></TBODY></TABLE>
      <P>在嵌入式 Linux 系统中,通常需要由 Boot Loader 
      设置的常见启动参数有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。 </P>
      <P>比如,设置 ATAG_CORE 的代码如下: </P>
      <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc 
        border=1><TBODY>
        <TR>
          <TD><PRE><CODE>
<FONT face=Courier size=2>params = (struct tag *)BOOT_PARAMS;

	params-&gt;hdr.tag = ATAG_CORE;
	params-&gt;hdr.size = tag_size(tag_core);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -