三星s
典型的bootloader设计,将代码分成两个阶段,这主要是为了在代码的可移植性和效率上做一个折中。因为bootloader是高度硬件相关的,不同体系结构的cpu初始化等操作截然不同。代码的第一阶段主要任务的是初始化硬件设备,为bootloader第二阶段准备好RAM空间并初始化堆栈。这部分内容一般由汇编实现。第二阶段的主要工作是设置系统时钟,设置Translation Table并初始化MMU,设置内核启动参数并将内核映像由Nand Flash拷贝到相应的SDRAM中,最后跳转到内核的入口点。
值得一提的是,为什么bootloader的初始部分要用汇编?一种解释是有些操作必须用汇编实现,如协处理器寄存器的操作。更重要的问题在于,c程序需要一个具体的运行环境,如代码段,初始化的数据段,BSS段,栈,堆等。尤其是栈,它承担着C函数调用参数传递,局部变量的存储等工作(虽然C89中并没有迹象显示栈对于C程序是必须的)。再者,启动时仅有Nand Flash的前4K内容在stepping stone中运行,这如何能保证C程序的完整性呢?因此,通常的做法是将第一阶段的汇编代码在单独的模块实现,并链接到程序的开始处。然后有它将完整的bootloader程序映像文件从Nand Flash中搬运至SDRAM中,并设置好上面谈到的各个段。这么以来,第二阶段的代码就会在SDRAM中运行。
第一阶段汇编代码的入口处,一般首先放置的是cpu异常的跳转代码,如IRQ,FIQ,SWI,Undef等。中断源将中断请求送至cpu的中断控制器,通过中断控制器仲裁,决定被响应与否或响应的顺序。例如IRQ异常,cpu会跳转到IRQ异常跳转指令处,该指令修改pc地址使其指向IRQ异常处理例程。在处理历程中,程序通过判断中断源的偏移量,确定该IRQ异常的具体类型,计算出这种IRQ异常的中断响应函数,这个过程是通过查阅IRQ中断向量表来实现的。IRQ中断向量表中定义了具体IRQ中断响应函数的地址,这些地址可以在第二阶段根据需要而设置,例如Timer的中断响应函数等等。
第二阶段首先做的是设置时钟。s
接着打开MMU,指令缓存和数据缓存。这里可以设置协处理器CP15的Register 13(ProcID)为0,并建立从虚地址到物理地址的直接映射关系。
以上工作完成后,将存放于Nand Flash中的kernel和启动参数搬运到内存中的特定区域,重新设置好时钟(与内核中的保持一致), 关闭MMU,指令缓存和数据缓存,跳转到内核的起始地址就可以运行了。
至此,bootloader就完成了它的历史使命,如果人品好的话,kernel就会乖乖的运行了。当然,这里假定的是手头上已经有一份移植并裁剪好的内核。