[ZLG-ARM] 嵌入式系统Boot Loader技术内幕

[复制链接]
1909|1
 楼主| reeper 发表于 2009-4-2 16:38 | 显示全部楼层 |阅读模式
&nbsp;Boot&nbsp;Loader&nbsp;的主要任务与典型结构框架<br /><br />在继续本节的讨论之前,首先我们做一个假定,那就是:假定内核映像与根文件系统映像都被加载到&nbsp;RAM&nbsp;中运行。之所以提出这样一个假设前提是因为,在嵌入式系统中内核映像与根文件系统映像也可以直接在&nbsp;ROM&nbsp;或&nbsp;Flash&nbsp;这样的固态存储设备中直接运行。但这种做法无疑是以运行速度的牺牲为代价的。<br /><br />从操作系统的角度看,Boot&nbsp;Loader&nbsp;的总目标就是正确地调用内核来执行。<br /><br />另外,由于&nbsp;Boot&nbsp;Loader&nbsp;的实现依赖于&nbsp;CPU&nbsp;的体系结构,因此大多数&nbsp;Boot&nbsp;Loader&nbsp;都分为&nbsp;stage1&nbsp;和&nbsp;stage2&nbsp;两大部分。依赖于&nbsp;CPU&nbsp;体系结构的代码,比如设备初始化代码等,通常都放在&nbsp;stage1&nbsp;中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而&nbsp;stage2&nbsp;则通常用C语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。<br /><br />Boot&nbsp;Loader&nbsp;的&nbsp;stage1&nbsp;通常包括以下步骤(以执行的先后顺序):<br /><br />硬件设备初始化。<br /><br /><br />为加载&nbsp;Boot&nbsp;Loader&nbsp;的&nbsp;stage2&nbsp;准备&nbsp;RAM&nbsp;空间。<br /><br /><br />拷贝&nbsp;Boot&nbsp;Loader&nbsp;的&nbsp;stage2&nbsp;到&nbsp;RAM&nbsp;空间中。<br /><br /><br />设置好堆栈。<br /><br /><br />跳转到&nbsp;stage2&nbsp;的&nbsp;C&nbsp;入口点。<br /><br /><br />Boot&nbsp;Loader&nbsp;的&nbsp;stage2&nbsp;通常包括以下步骤(以执行的先后顺序):<br /><br />初始化本阶段要使用到的硬件设备。<br /><br /><br />检测系统内存映射(memory&nbsp;map)。<br /><br /><br />将&nbsp;kernel&nbsp;映像和根文件系统映像从&nbsp;flash&nbsp;上读到&nbsp;RAM&nbsp;空间中。<br /><br /><br />为内核设置启动参数。<br /><br /><br />调用内核。<br />(一)Boot&nbsp;Loader&nbsp;的&nbsp;stage1<br />基本的硬件初始化<br /><br />这是&nbsp;Boot&nbsp;Loader&nbsp;一开始就执行的操作,其目的是为&nbsp;stage2&nbsp;的执行以及随后的&nbsp;kernel&nbsp;的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后顺序):<br /><br />1.屏蔽所有的中断。为中断提供服务通常是&nbsp;OS&nbsp;设备驱动程序的责任,因此在&nbsp;Boot&nbsp;Loader&nbsp;的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写&nbsp;CPU&nbsp;的中断屏蔽寄存器或状态寄存器(比如&nbsp;ARM&nbsp;的&nbsp;CPSR&nbsp;寄存器)来完成。<br /><br />2.设置&nbsp;CPU&nbsp;的速度和时钟频率。<br /><br />3.RAM&nbsp;初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。<br /><br />4.初始化&nbsp;LED。典型地,通过&nbsp;GPIO&nbsp;来驱动&nbsp;LED,其目的是表明系统的状态是&nbsp;OK&nbsp;还是&nbsp;Error。如果板子上没有&nbsp;LED,那么也可以通过初始化&nbsp;UART&nbsp;向串口打印&nbsp;Boot&nbsp;Loader&nbsp;的&nbsp;**&nbsp;字符信息来完成这一点。<br /><br />5.关闭&nbsp;CPU&nbsp;内部指令/数据&nbsp;cache。<br /><br />(二)&nbsp;为加载&nbsp;stage2&nbsp;准备&nbsp;RAM&nbsp;空间<br /><br />为了获得更快的执行速度,通常把&nbsp;stage2&nbsp;加载到&nbsp;RAM&nbsp;空间中来执行,因此必须为加载&nbsp;Boot&nbsp;Loader&nbsp;的&nbsp;stage2&nbsp;准备好一段可用的&nbsp;RAM&nbsp;空间范围。<br /><br />由于&nbsp;stage2&nbsp;通常是&nbsp;C&nbsp;语言执行代码,因此在考虑空间大小时,除了&nbsp;stage2&nbsp;可执行映象的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是&nbsp;memory&nbsp;page&nbsp;大小(通常是&nbsp;4KB)的倍数。一般而言,1M&nbsp;的&nbsp;RAM&nbsp;空间已经足够了。具体的地址范围可以任意安排,比如&nbsp;blob&nbsp;就将它的&nbsp;stage2&nbsp;可执行映像安排到从系统&nbsp;RAM&nbsp;起始地址&nbsp;0xc0200000&nbsp;开始的&nbsp;1M&nbsp;空间内执行。但是,将&nbsp;stage2&nbsp;安排到整个&nbsp;RAM&nbsp;空间的最顶&nbsp;1MB(也即(RamEnd-1MB)&nbsp;-&nbsp;RamEnd)是一种值得推荐的方法。<br /><br />为了后面的叙述方便,这里把所安排的&nbsp;RAM&nbsp;空间范围的大小记为:stage2_size(字节),把起始地址和终止地址分别记为:stage2_start&nbsp;和&nbsp;stage2_end(这两个地址均以&nbsp;4&nbsp;字节边界对齐)。因此:<br /><br />stage2_end=stage2_start+stage2_size<br />&nbsp;<br /><br />另外,还必须确保所安排的地址范围的的确确是可读写的&nbsp;RAM&nbsp;空间,因此,必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于&nbsp;blob&nbsp;的方法,也即:以&nbsp;memory&nbsp;page&nbsp;为被测试单位,测试每个&nbsp;memory&nbsp;page&nbsp;开始的两个字是否是可读写的。为了后面叙述的方便,我们记这个检测算法为:test_mempage,其具体步骤如下:<br /><br />1.&nbsp;先保存&nbsp;memory&nbsp;page&nbsp;一开始两个字的内容。<br /><br />2.&nbsp;向这两个字中写入任意的数字。比如:向第一个字写入&nbsp;0x55,第&nbsp;2&nbsp;个字写入&nbsp;0xaa。<br /><br />3.&nbsp;然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是&nbsp;0x55&nbsp;和&nbsp;0xaa。如果不是,则说明这个&nbsp;memory&nbsp;page&nbsp;所占据的地址范围不是一段有效的&nbsp;RAM&nbsp;空间。<br /><br />4.&nbsp;再向这两个字中写入任意的数字。比如:向第一个字写入&nbsp;0xaa,第&nbsp;2&nbsp;个字中写入&nbsp;0x55。<br /><br />5.&nbsp;然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是&nbsp;0xaa&nbsp;和&nbsp;0x55。如果不是,则说明这个&nbsp;memory&nbsp;page&nbsp;所占据的地址范围不是一段有效的&nbsp;RAM&nbsp;空间。<br /><br />6.&nbsp;恢复这两个字的原始内容。测试完毕。<br /><br />为了得到一段干净的&nbsp;RAM&nbsp;空间范围,我们也可以将所安排的&nbsp;RAM&nbsp;空间范围进行清零操作。<br /><br />(三)&nbsp;拷贝&nbsp;stage2&nbsp;到&nbsp;RAM&nbsp;中<br /><br />拷贝时要确定两点:(1)&nbsp;stage2&nbsp;的可执行映象在固态存储设备的存放起始地址和终止地址;(2)&nbsp;RAM&nbsp;空间的起始地址。<br /><br />(四)&nbsp;设置堆栈指针&nbsp;sp<br /><br />堆栈指针的设置是为了执行&nbsp;C&nbsp;语言代码作好准备。通常我们可以把&nbsp;sp&nbsp;的值设置为(stage2_end-4),也即在&nbsp;3.1.2&nbsp;节所安排的那个&nbsp;1MB&nbsp;的&nbsp;RAM&nbsp;空间的最顶端(堆栈向下生长)。<br /><br />此外,在设置堆栈指针&nbsp;sp&nbsp;之前,也可以关闭&nbsp;led&nbsp;灯,以提示用户我们准备跳转到&nbsp;stage2。<br /><br />(五)跳转到&nbsp;stage2&nbsp;的&nbsp;C&nbsp;入口点<br /><br /><br />在上述一切都就绪后,就可以跳转到&nbsp;Boot&nbsp;Loader&nbsp;的&nbsp;stage2&nbsp;去执行了。比如,在&nbsp;ARM&nbsp;系统中,这可以通过修改&nbsp;PC&nbsp;寄存器为合适的地址来实现。<br /><br />(六)&nbsp;Boot&nbsp;Loader&nbsp;的&nbsp;stage2<br />正如前面所说,stage2&nbsp;的代码通常用&nbsp;C&nbsp;语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通&nbsp;C&nbsp;语言应用程序不同的是,在编译和链接&nbsp;boot&nbsp;loader&nbsp;这样的程序时,我们不能使用&nbsp;glibc&nbsp;库中的任何支持函数。其原因是显而易见的。这就给我们带来一个问题,那就是从那里跳转进&nbsp;main()&nbsp;函数呢?直接把&nbsp;main()&nbsp;函数的起始地址作为整个&nbsp;stage2&nbsp;执行映像的入口点或许是最直接的想法。但是这样做有两个缺点:1)无法通过main()&nbsp;函数传递函数参数;2)无法处理&nbsp;main()&nbsp;函数返回的情况。一种更为巧妙的方法是利用&nbsp;trampoline(弹簧床)的概念。也即,用汇编语言写一段trampoline&nbsp;小程序,并将这段&nbsp;trampoline&nbsp;小程序来作为&nbsp;stage2&nbsp;可执行映象的执行入口点。然后我们可以在&nbsp;trampoline&nbsp;汇编小程序中用&nbsp;CPU&nbsp;跳转指令跳入&nbsp;main()&nbsp;函数中去执行;而当&nbsp;main()&nbsp;函数返回时,CPU&nbsp;执行路径显然再次回到我们的&nbsp;trampoline&nbsp;程序。简而言之,这种方法的思想就是:用这段&nbsp;trampoline&nbsp;小程序来作为&nbsp;main()&nbsp;函数的外部包裹(external&nbsp;wrapper)。<br /><br />下面给出一个简单的&nbsp;trampoline&nbsp;程序示例(来自blob):<br /><br />.text<br /><br />.globl&nbsp;_trampoline<br />_trampoline:<br />&nbsp;bl&nbsp;main<br />&nbsp;/*&nbsp;if&nbsp;main&nbsp;ever&nbsp;returns&nbsp;we&nbsp;just&nbsp;call&nbsp;it&nbsp;again&nbsp;*/<br />&nbsp;b&nbsp;_trampoline<br />&nbsp;<br /><br /><br />可以看出,当&nbsp;main()&nbsp;函数返回后,我们又用一条跳转指令重新执行&nbsp;trampoline&nbsp;程序――当然也就重新执行&nbsp;main()&nbsp;函数,这也就是&nbsp;trampoline(弹簧床)一词的意思所在。<br /><br />(七)初始化本阶段要使用到的硬件设备<br /><br />这通常包括:(1)初始化至少一个串口,以便和终端用户进行&nbsp;I/O&nbsp;输出信息;(2)初始化计时器等。<br /><br />在初始化这些设备之前,也可以重新把&nbsp;LED&nbsp;灯点亮,以表明我们已经进入&nbsp;main()&nbsp;函数执行。<br /><br />设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。<br /><br />(八)&nbsp;检测系统的内存映射(memory&nbsp;map)<br /><br />所谓内存映射就是指在整个&nbsp;4GB&nbsp;物理地址空间中有哪些地址范围被分配用来寻址系统的&nbsp;RAM&nbsp;单元。比如,在&nbsp;SA-1100&nbsp;CPU&nbsp;中,从&nbsp;0xC000,0000&nbsp;开始的&nbsp;512M&nbsp;地址空间被用作系统的&nbsp;RAM&nbsp;地址空间,而在&nbsp;Samsung&nbsp;S3C44B0X&nbsp;CPU&nbsp;中,从&nbsp;0x0c00,0000&nbsp;到&nbsp;0x1000,0000&nbsp;之间的&nbsp;64M&nbsp;地址空间被用作系统的&nbsp;RAM&nbsp;地址空间。虽然&nbsp;CPU&nbsp;通常预留出一大段足够的地址空间给系统&nbsp;RAM,但是在搭建具体的嵌入式系统时却不一定会实现&nbsp;CPU&nbsp;预留的全部&nbsp;RAM&nbsp;地址空间。也就是说,具体的嵌入式系统往往只把&nbsp;CPU&nbsp;预留的全部&nbsp;RAM&nbsp;地址空间中的一部分映射到&nbsp;RAM&nbsp;单元上,而让剩下的那部分预留&nbsp;RAM&nbsp;地址空间处于未使用状态。由于上述这个事实,因此&nbsp;Boot&nbsp;Loader&nbsp;的&nbsp;stage2&nbsp;必须在它想干点什么&nbsp;(比如,将存储在&nbsp;flash&nbsp;上的内核映像读到&nbsp;RAM&nbsp;空间中)&nbsp;之前检测整个系统的内存映射情况,也即它必须知道&nbsp;CPU&nbsp;预留的全部&nbsp;RAM&nbsp;地址空间中的哪些被真正映射到&nbsp;RAM&nbsp;地址单元,哪些是处于&nbsp;&quot;unused&quot;&nbsp;状态的。<br /><br />(1)&nbsp;内存映射的描述<br /><br />可以用如下数据结构来描述&nbsp;RAM&nbsp;地址空间中的一段连续(continuous)的地址范围:<br /><br />typedef&nbsp;struct&nbsp;memory_area_struct&nbsp;{<br />&nbsp;u32&nbsp;start;&nbsp;/*&nbsp;the&nbsp;base&nbsp;address&nbsp;of&nbsp;the&nbsp;memory&nbsp;region&nbsp;*/<br />&nbsp;u32&nbsp;size;&nbsp;/*&nbsp;the&nbsp;byte&nbsp;number&nbsp;of&nbsp;the&nbsp;memory&nbsp;region&nbsp;*/<br />&nbsp;int&nbsp;used;<br />}&nbsp;memory_area_t;<br />&nbsp;<br /><br /><br />这段&nbsp;RAM&nbsp;地址空间中的连续地址范围可以处于两种状态之一:(1)used=1,则说明这段连续的地址范围已被实现,也即真正地被映射到&nbsp;RAM&nbsp;单元上。(2)used=0,则说明这段连续的地址范围并未被系统所实现,而是处于未使用状态。<br /><br />基于上述&nbsp;memory_area_t&nbsp;数据结构,整个&nbsp;CPU&nbsp;预留的&nbsp;RAM&nbsp;地址空间可以用一个&nbsp;memory_area_t&nbsp;类型的数组来表示,如下所示:<br /><br />memory_area_t&nbsp;memory_map[NUM_MEM_AREAS]&nbsp;=&nbsp;{<br />&nbsp;[0&nbsp;...&nbsp;(NUM_MEM_AREAS&nbsp;-&nbsp;1)]&nbsp;=&nbsp;{<br />&nbsp;&nbsp;.start&nbsp;=&nbsp;0,<br />&nbsp;&nbsp;.size&nbsp;=&nbsp;0,<br />&nbsp;&nbsp;.used&nbsp;=&nbsp;0<br />&nbsp;},<br />};<br />&nbsp;<br /><br /><br />(2)&nbsp;内存映射的检测<br /><br />下面我们给出一个可用来检测整个&nbsp;RAM&nbsp;地址空间内存映射情况的简单而有效的算法:<br /><br />/*&nbsp;数组初始化&nbsp;*/<br />for(i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt&nbsp;NUM_MEM_AREAS;&nbsp;i++)<br />&nbsp;memory_map.used&nbsp;=&nbsp;0;<br /><br />/*&nbsp;first&nbsp;write&nbsp;a&nbsp;0&nbsp;to&nbsp;all&nbsp;memory&nbsp;locations&nbsp;*/<br />for(addr&nbsp;=&nbsp;MEM_START;&nbsp;addr&nbsp;&lt&nbsp;MEM_END;&nbsp;addr&nbsp;+=&nbsp;PAGE_SIZE)<br />&nbsp;*&nbsp;(u32&nbsp;*)addr&nbsp;=&nbsp;0;<br /><br />for(i&nbsp;=&nbsp;0,&nbsp;addr&nbsp;=&nbsp;MEM_START;&nbsp;addr&nbsp;&lt&nbsp;MEM_END;&nbsp;addr&nbsp;+=&nbsp;PAGE_SIZE)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;检测从基地址&nbsp;MEM_START+i*PAGE_SIZE&nbsp;开始,大小为<br />*&nbsp;PAGE_SIZE&nbsp;的地址空间是否是有效的RAM地址空间。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;调用3.1.2节中的算法test_mempage();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(&nbsp;current&nbsp;memory&nbsp;page&nbsp;isnot&nbsp;a&nbsp;valid&nbsp;ram&nbsp;page)&nbsp;{<br />&nbsp;&nbsp;/*&nbsp;no&nbsp;RAM&nbsp;here&nbsp;*/<br />&nbsp;&nbsp;if(memory_map.used&nbsp;)<br />&nbsp;&nbsp;&nbsp;i++;<br />&nbsp;&nbsp;continue;<br />&nbsp;}<br />&nbsp;<br />&nbsp;/*<br />&nbsp;&nbsp;*&nbsp;当前页已经是一个被映射到&nbsp;RAM&nbsp;的有效地址范围<br />&nbsp;&nbsp;*&nbsp;但是还要看看当前页是否只是&nbsp;4GB&nbsp;地址空间中某个地址页的别名?<br />&nbsp;&nbsp;*/<br />&nbsp;if(*&nbsp;(u32&nbsp;*)addr&nbsp;!=&nbsp;0)&nbsp;{&nbsp;/*&nbsp;alias?&nbsp;*/<br />&nbsp;&nbsp;/*&nbsp;这个内存页是&nbsp;4GB&nbsp;地址空间中某个地址页的别名&nbsp;*/<br />&nbsp;&nbsp;if&nbsp;(&nbsp;memory_map.used&nbsp;)<br />&nbsp;&nbsp;&nbsp;i++;<br />&nbsp;&nbsp;continue;<br />&nbsp;}<br />&nbsp;<br />&nbsp;/*<br />&nbsp;&nbsp;*&nbsp;当前页已经是一个被映射到&nbsp;RAM&nbsp;的有效地址范围<br />&nbsp;&nbsp;*&nbsp;而且它也不是&nbsp;4GB&nbsp;地址空间中某个地址页的别名。<br />&nbsp;&nbsp;*/<br />&nbsp;if&nbsp;(memory_map.used&nbsp;==&nbsp;0)&nbsp;{<br />&nbsp;&nbsp;memory_map.start&nbsp;=&nbsp;addr;<br />&nbsp;&nbsp;memory_map.size&nbsp;=&nbsp;PAGE_SIZE;<br />&nbsp;&nbsp;memory_map.used&nbsp;=&nbsp;1;<br />&nbsp;}&nbsp;else&nbsp;{<br />&nbsp;&nbsp;memory_map.size&nbsp;+=&nbsp;PAGE_SIZE;<br />&nbsp;}<br />}&nbsp;/*&nbsp;end&nbsp;of&nbsp;for&nbsp;(…)&nbsp;*/<br />&nbsp;<br /><br /><br />在用上述算法检测完系统的内存映射情况后,Boot&nbsp;Loader&nbsp;也可以将内存映射的详细信息打印到串口。<br /><br />(九)加载内核映像和根文件系统映像<br /><br />(1)&nbsp;规划内存占用的布局<br /><br />这里包括两个方面:(1)内核映像所占用的内存范围;(2)根文件系统所占用的内存范围。在规划内存占用的布局时,主要考虑基地址和映像的大小两个方面。<br /><br />对于内核映像,一般将其拷贝到从(MEM_START+0x8000)&nbsp;这个基地址开始的大约1MB大小的内存范围内(嵌入式&nbsp;Linux&nbsp;的内核一般都不操过&nbsp;1MB)。为什么要把从&nbsp;MEM_START&nbsp;到&nbsp;MEM_START+0x8000&nbsp;这段&nbsp;32KB&nbsp;大小的内存空出来呢?这是因为&nbsp;Linux&nbsp;内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。<br /><br />而对于根文件系统映像,则一般将其拷贝到&nbsp;MEM_START+0x0010,0000&nbsp;开始的地方。如果用&nbsp;Ramdisk&nbsp;作为根文件系统映像,则其解压后的大小一般是1MB。<br /><br />(2)从&nbsp;Flash&nbsp;上拷贝<br /><br />由于像&nbsp;ARM&nbsp;这样的嵌入式&nbsp;CPU&nbsp;通常都是在统一的内存地址空间中寻址&nbsp;Flash&nbsp;等固态存储设备的,因此从&nbsp;Flash&nbsp;上读取数据与从&nbsp;RAM&nbsp;单元中读取数据并没有什么不同。用一个简单的循环就可以完成从&nbsp;Flash&nbsp;设备上拷贝映像的工作:<br /><br />while(count)&nbsp;{<br />&nbsp;*dest++&nbsp;=&nbsp;*src++;&nbsp;/*&nbsp;they&nbsp;are&nbsp;all&nbsp;aligned&nbsp;with&nbsp;word&nbsp;boundary&nbsp;*/<br />&nbsp;count&nbsp;-=&nbsp;4;&nbsp;/*&nbsp;byte&nbsp;number&nbsp;*/<br />};<br />&nbsp;<br /><br /><br />(十)&nbsp;设置内核的启动参数<br /><br />应该说,在将内核映像和根文件系统映像拷贝到&nbsp;RAM&nbsp;空间中后,就可以准备启动&nbsp;Linux&nbsp;内核了。但是在调用内核之前,应该作一步准备工作,即:设置&nbsp;Linux&nbsp;内核的启动参数。<br /><br />Linux&nbsp;2.4.x&nbsp;以后的内核都期望以标记列表(tagged&nbsp;list)的形式来传递启动参数。启动参数标记列表以标记&nbsp;ATAG_CORE&nbsp;开始,以标记&nbsp;ATAG_NONE&nbsp;结束。每个标记由标识被传递参数的&nbsp;tag_header&nbsp;结构以及随后的参数值数据结构来组成。数据结构&nbsp;tag&nbsp;和&nbsp;tag_header&nbsp;定义在&nbsp;Linux&nbsp;内核源码的include/asm/setup.h&nbsp;头文件中:<br /><br />/*&nbsp;The&nbsp;list&nbsp;ends&nbsp;with&nbsp;an&nbsp;ATAG_NONE&nbsp;node.&nbsp;*/<br />#define&nbsp;ATAG_NONE&nbsp;0x00000000<br /><br />struct&nbsp;tag_header&nbsp;{<br />&nbsp;u32&nbsp;size;&nbsp;/*&nbsp;注意,这里size是字数为单位的&nbsp;*/<br />&nbsp;u32&nbsp;tag;<br />};<br />……<br />struct&nbsp;tag&nbsp;{<br />&nbsp;struct&nbsp;tag_header&nbsp;hdr;<br />&nbsp;union&nbsp;{<br />&nbsp;&nbsp;struct&nbsp;tag_core&nbsp;&nbsp;core;<br />&nbsp;&nbsp;struct&nbsp;tag_mem32&nbsp;mem;<br />&nbsp;&nbsp;struct&nbsp;tag_videotext&nbsp;videotext;<br />&nbsp;&nbsp;struct&nbsp;tag_ramdisk&nbsp;ramdisk;<br />&nbsp;&nbsp;struct&nbsp;tag_initrd&nbsp;initrd;<br />&nbsp;&nbsp;struct&nbsp;tag_serialnr&nbsp;serialnr;<br />&nbsp;&nbsp;struct&nbsp;tag_revision&nbsp;revision;<br />&nbsp;&nbsp;struct&nbsp;tag_videolfb&nbsp;videolfb;<br />&nbsp;&nbsp;struct&nbsp;tag_cmdline&nbsp;cmdline;<br /><br />&nbsp;&nbsp;/*<br />&nbsp;&nbsp;&nbsp;*&nbsp;Acorn&nbsp;specific<br />&nbsp;&nbsp;&nbsp;*/<br />&nbsp;&nbsp;struct&nbsp;tag_acorn&nbsp;acorn;<br /><br />&nbsp;&nbsp;/*<br />&nbsp;&nbsp;&nbsp;*&nbsp;DC21285&nbsp;specific<br />&nbsp;&nbsp;&nbsp;*/<br />&nbsp;&nbsp;struct&nbsp;tag_memclk&nbsp;memclk;<br />&nbsp;}&nbsp;u;<br />};<br />&nbsp;<br /><br /><br />在嵌入式&nbsp;Linux&nbsp;系统中,通常需要由&nbsp;Boot&nbsp;Loader&nbsp;设置的常见启动参数有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。<br /><br />比如,设置&nbsp;ATAG_CORE&nbsp;的代码如下:<br /><br />params&nbsp;=&nbsp;(struct&nbsp;tag&nbsp;*)BOOT_PARAMS;<br /><br />&nbsp;params-&gthdr.tag&nbsp;=&nbsp;ATAG_CORE;<br />&nbsp;params-&gthdr.size&nbsp;=&nbsp;tag_size(tag_core);<br /><br />&nbsp;params-&gtu.core.flags&nbsp;=&nbsp;0;<br />&nbsp;params-&gtu.core.pagesize&nbsp;=&nbsp;0;<br />&nbsp;params-&gtu.core.rootdev&nbsp;=&nbsp;0;<br /><br />&nbsp;params&nbsp;=&nbsp;tag_next(params);<br />&nbsp;<br /><br /><br />其中,BOOT_PARAMS&nbsp;表示内核启动参数在内存中的起始基地址,指针&nbsp;params&nbsp;是一个&nbsp;struct&nbsp;tag&nbsp;类型的指针。宏&nbsp;tag_next()&nbsp;将以指向当前标记的指针为参数,计算紧临当前标记的下一个标记的起始地址。注意,内核的根文件系统所在的设备ID就是在这里设置的。<br /><br />下面是设置内存映射情况的示例代码:<br /><br />for(i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt&nbsp;NUM_MEM_AREAS;&nbsp;i++)&nbsp;{<br />&nbsp;&nbsp;if(memory_map.used)&nbsp;{<br />&nbsp;&nbsp;&nbsp;params-&gthdr.tag&nbsp;=&nbsp;ATAG_MEM;<br />&nbsp;&nbsp;&nbsp;params-&gthdr.size&nbsp;=&nbsp;tag_size(tag_mem32);<br /><br />&nbsp;&nbsp;&nbsp;params-&gtu.mem.start&nbsp;=&nbsp;memory_map.start;<br />&nbsp;&nbsp;&nbsp;params-&gtu.mem.size&nbsp;=&nbsp;memory_map.size;<br />&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;params&nbsp;=&nbsp;tag_next(params);<br />&nbsp;&nbsp;}<br />}<br />&nbsp;<br /><br /><br />可以看出,在&nbsp;memory_map[]数组中,每一个有效的内存段都对应一个&nbsp;ATAG_MEM&nbsp;参数标记。<br /><br />Linux&nbsp;内核在启动时可以以命令行参数的形式来接收信息,利用这一点我们可以向内核提供那些内核不能自己检测的硬件参数信息,或者重载(override)内核自己检测到的信息。比如,我们用这样一个命令行参数字符串&quot;console=ttyS0,115200n8&quot;来通知内核以&nbsp;ttyS0&nbsp;作为控制台,且串口采用&nbsp;&quot;115200bps、无奇偶校验、8位数据位&quot;这样的设置。下面是一段设置调用内核命令行参数字符串的示例代码:<br /><br />char&nbsp;*p;<br /><br />&nbsp;/*&nbsp;eat&nbsp;leading&nbsp;white&nbsp;space&nbsp;*/<br />&nbsp;for(p&nbsp;=&nbsp;commandline;&nbsp;*p&nbsp;==&nbsp;''&nbsp;'';&nbsp;p++)<br />&nbsp;&nbsp;;<br /><br />&nbsp;/*&nbsp;skip&nbsp;non-existent&nbsp;command&nbsp;lines&nbsp;so&nbsp;the&nbsp;kernel&nbsp;will&nbsp;still<br />&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;use&nbsp;its&nbsp;default&nbsp;command&nbsp;line.<br />&nbsp;&nbsp;*/<br />&nbsp;if(*p&nbsp;==&nbsp;''\0'')<br />&nbsp;&nbsp;return;<br /><br />&nbsp;params-&gthdr.tag&nbsp;=&nbsp;ATAG_CMDLINE;<br />&nbsp;params-&gthdr.size&nbsp;=&nbsp;(sizeof(struct&nbsp;tag_header)&nbsp;+&nbsp;strlen(p)&nbsp;+&nbsp;1&nbsp;+&nbsp;4)&nbsp;&gt&gt&nbsp;2;<br /><br />&nbsp;strcpy(params-&gtu.cmdline.cmdline,&nbsp;p);<br /><br />&nbsp;params&nbsp;=&nbsp;tag_next(params);<br />&nbsp;<br /><br /><br />请注意在上述代码中,设置&nbsp;tag_header&nbsp;的大小时,必须包括字符串的终止符''\0'',此外还要将字节数向上圆整4个字节,因为&nbsp;tag_header&nbsp;结构中的size&nbsp;成员表示的是字数。<br /><br />下面是设置&nbsp;ATAG_INITRD&nbsp;的示例代码,它告诉内核在&nbsp;RAM&nbsp;中的什么地方可以找到&nbsp;initrd&nbsp;映象(压缩格式)以及它的大小:<br /><br />params-&gthdr.tag&nbsp;=&nbsp;ATAG_INITRD2;<br />&nbsp;params-&gthdr.size&nbsp;=&nbsp;tag_size(tag_initrd);<br />&nbsp;<br />&nbsp;params-&gtu.initrd.start&nbsp;=&nbsp;RAMDISK_RAM_BASE;<br />&nbsp;params-&gtu.initrd.size&nbsp;=&nbsp;INITRD_LEN;<br />&nbsp;<br />&nbsp;params&nbsp;=&nbsp;tag_next(params);<br />&nbsp;<br /><br /><br />下面是设置&nbsp;ATAG_RAMDISK&nbsp;的示例代码,它告诉内核解压后的&nbsp;Ramdisk&nbsp;有多大(单位是KB):<br /><br />params-&gthdr.tag&nbsp;=&nbsp;ATAG_RAMDISK;<br />params-&gthdr.size&nbsp;=&nbsp;tag_size(tag_ramdisk);<br />&nbsp;<br />params-&gtu.ramdisk.start&nbsp;=&nbsp;0;<br />params-&gtu.ramdisk.size&nbsp;=&nbsp;RAMDISK_SIZE;&nbsp;/*&nbsp;请注意,单位是KB&nbsp;*/<br />params-&gtu.ramdisk.flags&nbsp;=&nbsp;1;&nbsp;/*&nbsp;automatically&nbsp;load&nbsp;ramdisk&nbsp;*/<br />&nbsp;<br />params&nbsp;=&nbsp;tag_next(params);<br />&nbsp;<br /><br /><br />最后,设置&nbsp;ATAG_NONE&nbsp;标记,结束整个启动参数列表:<br /><br />static&nbsp;void&nbsp;setup_end_tag(void)<br />{<br />&nbsp;params-&gthdr.tag&nbsp;=&nbsp;ATAG_NONE;<br />&nbsp;params-&gthdr.size&nbsp;=&nbsp;0;<br />}<br />&nbsp;<br /><br /><br />(十一)调用内核<br /><br />Boot&nbsp;Loader&nbsp;调用&nbsp;Linux&nbsp;内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到&nbsp;MEM_START+0x8000&nbsp;地址处。在跳转时,下列条件要满足:<br /><br />1.&nbsp;CPU&nbsp;寄存器的设置:<br /><br />R0=0;<br /><br /><br />R1=机器类型&nbsp;ID;关于&nbsp;Machine&nbsp;Type&nbsp;Number,可以参见linux/arch/arm/tools/mach-types。<br /><br /><br />R2=启动参数标记列表在&nbsp;RAM&nbsp;中起始基地址;<br /><br /><br />2.&nbsp;CPU&nbsp;模式:<br /><br />必须禁止中断(IRQs和FIQs);<br /><br /><br />CPU&nbsp;必须&nbsp;SVC&nbsp;模式;<br /><br /><br />3.&nbsp;Cache&nbsp;和&nbsp;MMU&nbsp;的设置:<br /><br />MMU&nbsp;必须关闭;<br /><br /><br />指令&nbsp;Cache&nbsp;可以打开也可以关闭;<br /><br /><br />数据&nbsp;Cache&nbsp;必须关闭;<br />如果用&nbsp;C&nbsp;语言,可以像下列示例代码这样来调用内核:<br /><br />void&nbsp;(*theKernel)(int&nbsp;zero,&nbsp;int&nbsp;arch,&nbsp;u32&nbsp;params_addr)&nbsp;=&nbsp;(void&nbsp;(*)(int,&nbsp;int,&nbsp;u32))KERNEL_RAM_BASE;<br />……<br />theKernel(0,&nbsp;ARCH_NUMBER,&nbsp;(u32)&nbsp;kernel_params_start);<br />&nbsp;<br /><br /><br />注意,theKernel()函数调用应该永远不返回的。如果这个调用返回,则说明出错。<br /><br />
msleep 发表于 2009-4-2 17:07 | 显示全部楼层

学习,要是有u-boot的方法就好了

  
您需要登录后才可以回帖 登录 | 注册

本版积分规则

139

主题

185

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部