BootLoader工作原理
BootLoader指系统启动后,在操作系统内核运行之前运行的一段小程序。通过BootLoader,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。通常,BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 BootLoader 几乎是不可能的。尽管如此,我们仍然可以对BootLoader归纳出一些通用的概念来,以指导用户特定的BootLoader设计与实现。
BootLoader 的实现依赖于CPU的体系结构,因此大多数 BootLoader 都分为stage1 和stage2 两大部分。依赖于CPU体系结构的代码,比如设备初始化代码等,通常都放在 stage1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而stage2 则通常用C 语言来实现,这样可以实现更复杂的功能,而且代码会具有更好的可读性和可移植性。
BootLoader 的 stage1 通常包括以下步骤:
·硬件设备初始化;
·为加载Boot Loader的stage2准备 RAM 空间;
·拷贝Boot Loader的stage2 到RAM空间中;
·设置好堆栈;
·跳转到 stage2 的 C 入口点。
Boot Loader的stage2通常包括以下步骤:
·初始化本阶段要使用到的硬件设备;
·检测系统内存映射(memory map);
·将kernel 映像和根文件系统映像从flash上读到 RAM 空间中;
·为内核设置启动参数;
·调用内核。
Bootloader
BootLoader的概念
BootLoader是系统加电启运行的第一段软件代码.回忆一下PC的体系结构我们可以知道,PC机中的引导加载程序由BIOS(其本质就是一段固件程序)和位于硬盘MBR中的引导程序一起组成。BIOS在完成硬件检测和资源分配后,将硬盘MBR中的引导程序读到系统的RAM中,然后将控制权交给引导程序。引导程序的主要运行任务就是将内核映象从硬盘上读到RAM中 然后跳转到内核的入口点去运行,也即开始启动操作系统。
而在嵌入式系统中,通常并没有像BIOS那样的固件程序(有的嵌入式系统也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成.比如在一个基于 ARM7TDMI core的嵌入式系统中,系统在上电或复位时都从地址 0x00000000开始执行.而在这个地址处安排的通常就是系统的BootLoader程序。
简单地说BootLoader就是在操作系统内核或用户应用程序运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图(有的CPU没有内存映射功能如 S3C44B0),从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核或用户应用程序准备好正确的环境。对于一个嵌入式系统来说,可能有的包括操作系统,有的小型系统也可以只包括应用程序,但是在这之前都需要BootLoader为它准备一个正确的环境。通常,BootLoader是依赖于硬件而实现的,特别是在嵌入式领域,为嵌入式系统建立一个通用的BootLoader是很困难的。当然,我们可以归纳出一些通用的概念来,以便我们了解特定BootLoader的设计与实现。
u BootLoader的移植和修改
每种不同的CPU体系结构都有不同的BootLoader。除了依赖于CPU的体系结构外,BootLoader实际上也依赖于具体的嵌入式板级设备的配置,比如板卡的硬件地址分配,RAM芯片的类型,其他外设的类型等。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种CPU而构建的,如果他们的硬件资源和配置不一致的话,要想让运行在一块板子上的BootLoader程序也能运行在另一块板子上,也还是需要作一些必要的修改。
u BootLoader的安装
系统加电或复位后,所有的CPU通常都从CPU制造商预先安排的地址上取指令。比如,S3C44B0在复位的都从地址0x00000000取它的第一条指令。而嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM或FLASH等)被安排这个起始地址上,因此在系统加电后,CPU将首先执行BootLoader程序。也就是说对于基于S3C44B0的这套系统,我们的BootLoader是从0地址开始存放的,而这块起始地址需要采用可引导的固态存储设备如FLASH。
u 用来控制BootLoader的设备或机制
串口通讯是最简单也是最廉价的一种双机通讯设备,所以往往在BootLoader中主机和目标机之间都通过串口建立连接,BootLoader程序在执行时通常会通过串口来进行 I/O,比如:输出打印信息到串口,从串口读取用户控制字符等。当然如果认为出口通讯速度不够,也可以采用网络或者USB通讯,那么相应的在BootLoader中就需要编写各自的驱动
u BootLoader的启动过程
多阶段的BootLoader能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的 BootLoader大多都是2阶段的启动过程,也即启动过程可以分为 stase1和stase 2两部分,具体功能将在下一节介绍。
u Boot Loader的操作模式
大多数BootLoader都包含两种不同的操作模式。“启动加载”模式和“下载”模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,BootLoader的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。
启动加载(Boot loading)模式:这种模式也称为“自主”(Autonomous)模式,也即BootLoader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。这种模式是BootLoader的正常工作模式。因此在嵌入式产品发布的时候,BootLoader显然必须工作在这种模式下.
下载(Down loading)模式:在这种模式下 目标机上的BootLoader将通过串口连接或网络连接等通信手段从主机下载文件,比如:下载应用程序、数据文件、内核映像等.从主机下载的文件通常首先被BootLoader保存到目标机的RAM中然后再被BootLoader写到目标机上的固态存储设备中。BootLoader的这种模式通常在系统更新时使用。工作于这种模式下的BootLoader通常都会向它的终端用户提供一个简单的命令行接口。
在教学系统中提供的BootLoader中没有实现自主模式,可以通过修改代码来实现该功能。
u BootLoader与主机之间进行文件传输所用的通信设备及协议
最常见的情况就是,目标机上的BootLoader通过串口与主机之间进行文件传输,传输可以简单的采用直接数据收发,当然在串口上也可以采用xmode/ymode/zmode协议以及在以太网上采用TPTP协议。
此外,在论及这个话题时,主机方所用的软件也要考虑。比如,在通过以太网连接和TFTP协议来下载文件时,主机方必须有一个软件用来提供TFTP服务。
Cirrus Logic 的 clps7111~Ep9312 系列 ARM core 的 CPU 内置 128 字节的 boot 程序。这个 boot 程序为把操作系统下载到裸机提供了极大的方便。这样再焊接电路板之前不用把操作系统预先写入 Flash ,而且日后升级操作系统也非常方便。 这个 boot 程序的功能是:
1. 设置串行口的参数为: 9600 , 8N1 , No FlowControl 。
2. 然后送出一个 < 字符
3. 开始接收 2K 字节程序( Bootloader )
4. 送出一个 > 字符
5. 跳转去执行这 2K 的程序。
烧写操作系统的过程是 :
1. 连接 ARM target 的产性口和 PC 的串行口
ARM PC
RX ------------------- TX
TX ------------------- RX
GND ---------------- GND
2. 从 BOOT 程序引导 ARM target
3. 在 Windows NT4.0 的 console 中 , 设置串行口的参数 9600 8N1
C:>mode COM2: baud=9600 data=8 parity=n stop=1
4. 在 console 中把 bootloader 送到串行口。 /b 表示以二进制方式
C:>copy /b bootldr.bin COM1:
5. 在 console 中 , 根据 bootloader 的设置来调整串行口的参数 115200 8N1
C:>mode COM2: baud=115200 data=8 parity=n stop=1
6. 在 console 中把 vxworks image 送到串行口。 /b 表示以二进制方式
C:>copy /b vxworks COM1:
7. Power off ARM target ,设置其从 Flash 启动。
8. reboot ,进入 VxWorks
这 2K 字节的程序就是我们说的 ARM Bootloader ,它的任务一般是:
1. 必要的硬件初始化
2. 从串行口接收 VxWorks 的二进制文件,并写入 Flash
3. 在这过程中,显示一些提示信息。
像 Bootloader 这样底层的程序一般认为是要用纯汇编来写的。但是用汇编写的程序可读性肯定没有用C写的程序好。汇编程序不宜维护,没办法向其它类型的 CPU 去移植。这些方面 C 的程序是没有问题的! _
那么Bootloader能否用纯C语言去写呢?不可能。因为有些操作特殊寄存器的指令也是特殊指令,用C是实现不了的。有些功能用C也是不能实现的。用C不能作的有:
1. 操作 CP15 寄存器的指令
2. 中断使能
3. 堆栈地址的设定
所以,只要知道这几条汇编指令可以了,不必学习所有的汇编指令。是不是上手很快呀。下面来看看我们在Bootloader 中所用到的汇编部分:
asm ("_my_start:
mov r14, #0x70
mcr p15, 0, r14, c1, c0, 0 /* MMU disabled, 32 Bit mode, Little endian */
mrs r14, cpsr
bic r14, r14, #0x 1f /* Mask */
orr r14, r14, #0xc0 + 0x13 /* Diasble IRQ and FIQ, SVC32 Mode */
msr cpsr, r14
ldr r13, =0xc0020000 /* Setup Stack */
");
简单吧,比看几十K的汇编程序感觉好得多吧。也许你会问:硬件的初始化怎么办?那是要操作寄存器的。我说:看看这段 C 的代码:
*((DWORD*)(dwHardwareBase + HW1_SYSCON1)) = SYSCON1_VALUE;
明白了吧, ARM 中把寄存器映射在内存中了,就跟读写内存没有区别。现在编写程序的问题已经全部解决了,但是否就没有问题了呢?不是。你的程序应该写成什么样呢?怎么编译生成二进制文件呢?让我们先写一个程序试一下吧:
#define DWORD unsigned int
int main(void)
{
register DWORD dwHardwareBase;
asm ("_my_start:
mov r14, #0x70
mcr p15, 0, r14, c1, c0, 0 /* MMU disabled, 32 Bit mode, Little endian */
mrs r14, cpsr
bic r14, r14, #0x 1f /* Mask */
orr r14, r14, #0xc0 + 0x13 /* Diasble IRQ and FIQ, SVC32 Mode */
msr cpsr, r14
ldr r13, =0xc0020000 /* Setup Stack */
");
dwHardwareBase = (DWORD)0x80000000;
return 0;
}
编译一下:
C:>ccarm -c -O2 -mcpu=arm710 -mlittle-endian -nostdlib -fvolatile -nostdinc –static sam1.c
C:>ldarm -o sam1.out -Ttext 10000000 sam1.o
ldarm: warning: cannot find entry symbol _start; defaulting to 10000000
sam1.o(.text+0xc):fake: undefined reference to `__gccmain'
sam1.o(.text+0xc):fake: relocation truncated to fit: ARM_26 __gccmain
我们发现应该把 main 定义成 _start
#define DWORD unsigned int
void start(void) //gcc 需要把它定义成 _start , vxworks 的 egcs 要把它定义成 start 。
{
register DWORD dwHardwareBase;
asm ("_my_start:
mov r14, #0x70
mcr p15, 0, r14, c1, c0, 0 /* MMU disabled, 32 Bit mode, Little endian */
mrs r14, cpsr
bic r14, r14, #0x 1f /* Mask */
orr r14, r14, #0xc0 + 0x13 /* Diasble IRQ and FIQ, SVC32 Mode */
msr cpsr, r14
ldr r13, =0xc0020000 /* Setup Stack */
");
dwHardwareBase = (DWORD)0x80000000;
}
编译一下:
C:>ccarm -c -O2 -mcpu=arm710 -mlittle-endian -nostdlib -fvolatile -nostdinc –static
sam1.c
C:>ldarm -o sam1.out -Ttext 10000000 sam1.o
C:>objdumparm -d sam1.out > sam1.asm
嵌入式系统由硬件和软件两部分组成,软件部分主要包括Bootloader、内核和文件系统。Bootloader是硬件系统加电所运行的第l段软件代码,但在嵌入式系统中一般没有像PC中的BIOS那样的固件,因此整个系统的加载过程全部是由Bootloader来完成的。系统在上电l或复位时通常都从地址Ox00000000处开始执行,而在这个地址处安排的通常就是系统的Bootloader。Bootloader的主要任务包括:初始化最基本的硬件;将Bootloader本身拷贝到RAM中运行;将内核拷贝到RAM中并调用内核等。
通常在嵌入式系统中,首先通过JTAG接口将Bootloader烧写到目标板的Flash中,然后在Bootloader中,将内核映像文件和文件系统映像文件通过串口和网络下载并烧写到Flash中。若需对内核或文件系统升级,则按照上述方法重新烧写新的映像文件,直接覆盖原来的映像文件。
上述方法中,一方面必须将目标板和主机通过串口线和网线相连接,另一方面通过串口或网络下载映像文件,速度很慢。本实验通过扩充Bootloader功能,实现了通过CF存储卡对内核或文件系统映像文件的自动升级,对需要经常为内核或文件系统升级的嵌入式系统来说,克服了传统升级方法的局限,简化了升级方法,提高了升级速度。
1 基本原理
本实验对传统Bootloader的功能进行了扩充,加入了升级系统的功能。例如,用户需要对目标板上的内核或文件系统进行升级,只需要将新的映像文件命名为指定的名称并拷贝到CF存储卡中。然后,CF存储卡插入目标板的CF存储卡插槽,重新启动目标板即可完成升级过程。重启时,系统首先运行Bootloader,Bootloader将检测CF存储卡中是否有内核或文件系统的映像文件。若有,则读取映像文件并烧写到目标板的F1ash中,实现升级;若无,则直接启动目标板中的系统,如图1所示。
实验使用的开发板基于Intel XScale处理器PXA255。PXA255具有16位的CF存储卡控制器,用于连接CF存储卡。开发板上有32 MB的Flash和64 MB的SDRAM,且Flash的起始地址映射到Ox00000000,SDRAM的起始地址映射到OxA0000000。
实验板上的InteI Strata Flash,容量为32 MB,分为Bootloader、reserved、kernel和root filesystem四个区。其中,Bootloader分区用于烧写Bootloader,其起始地址为Ox00000000,当系统加电启动或复位时,CPU便跳转到这个位置开始执行指令;reserved分区为保留分区,主要用于传递内核启动参数以及其他系统设置;kernel分区和root filesystem分区分别用于烧写内核和文件系统。各分区的起始地址及大小如图2所示。
2 实现
本文所讨论的实现方法,主要是扩充Bootloader的功能,增加对CF存储卡的支持,使系统启动时,Bootloader能对CF存储卡进行文件读取。首先,要将CF存储卡格式化成特定的文件系统格式(本实验主要支持FAT32、FATl6和EXT2三种文件系统)。然后,将待升级的映像文件(内核映像文件、文件系统映像文件或Bootloader本身的映像文件)通过主机拷贝到CF存储卡。因此,Bootloader可以榆测到需要升级的映像文件并对目标板上的相应部分进行更新。
2.1 Bootloader框架及工作流程
本实验所编写的Bootloader仅实现了最基本的硬件初始化功能、系统引导功能和系统升级功能,静态编译的二进制文件大小为38 KB。Bootloader用汇编语言和C语言实现,汇编语言仅作了屏蔽所有中断、初始化相关GPIO(General Purpose IO)、初始化SDRAM、拷贝Bootloader和内核到SDRAM等简单工作,便跳转到C程序,在C程序中实现了后续的初始化工作及系统升级。详细流程如图3所示。
2.2 对CF存储卡的支持及数据读取过程
由于是从CF存储卡上读取新的映像文件并实现系统更新,故在Bootloader中必须首先支持CF卡。CF卡本身提供了两个探测引脚(即Card Detect Pins),用于判断CF卡是否存在。这两个引脚成为CDl和CD2,在CF卡内部被硬件设计为直接与地相连。当CF卡插入时,CDl和CD2应全为低电平,因此,在Bootloader中通过检测CDl和CD2的电平高低,可以判断CF卡是否存在。
CF卡主要由3部分组成:控制器、存储器阵列和缓冲区。其中,内置的智能存储器可以使外围电路设计大大简化,且完全符合内存卡的PCMCIA(Personal Computer Memory Card Intemational Association)和AIA (AdvanccdTechnology Attachment)接口规范。因此,对CF卡的访问有基于PCMCIA规范的Memory Map模式、I/O方式以及基于ATA规范的True IDE方式。这里所实现的Bootloader中,CF卡工作在Truc IDE模式下,将CF卡的0E(Output Enable)引脚设置为低电平(反之,若为高电平,则CF卡将工作在PCMCIA规范的Memory Map模式或I/O模式下)。
对CF卡的True IDE工作模式设置完成后,通过向CF卡的寄存器写入必要的信息实现对CF卡的控制及读写。CF卡主要包含以下寄存器:
◆数据寄存器(R/W),用于对扇区的读/写操作,主机通过该寄存器向CF卡控制器写入或从CF卡控制寄存器读出扇区缓冲区的数据;
◆错误寄存器(R),控制寄存器在诊断方式或操作方式下的错误原因;
◆扇区数寄存器(R/W)。记录读、写命令的扇区数目;
◆扇区号寄存器(R/W),记录读、写和校验命令指定的起始扇区号;
◆柱面号寄存器(R/W),记录读、写、校验和寻址命令指定的柱面号;
◆驱动器/寄存器(R/W),记录读、写、校验和寻道命令指定的驱动器号、磁头号和寻址方式;
◆状态寄存器(R),反映CF卡驱动器执行命令后的状态,读浚寄存器要清除中断请求信号;
◆命令寄存器(W),命令寄存器接收主机发送的CF卡工作的控制命令。
从CF卡读取数据的过程如图4所示。
2.3 文件系统支持
要对CF卡进行文件存取,必须将CF卡格式化成某种文件系统。本实验所编写的Bootloader主要支持3种文件系统:FATl6、FAT32和EXT2。当需要对嵌入式系统的内核映像(映像文件名为zlmage)或根文件系统映像(映像文件名为tootfs.img)进行升级时,将待更新的映像文件按照指定的文件名拷贝到CF存储卡中。系统启动时,Bootloader首先检测CF存储卡的文件系统类型,然后按照相应的文件系统格式查询CF卡中的所有文件。若发现待更新的映像文件,则调用CF卡底层操作(详见2.2节),将映像文件读出到SDRAM中,再从SDRAM烧写到嵌入式开发板的Flash中,实现升级。有关文件系统的实现细节,详见参考文献。
3 结论
通过CF存储卡对嵌入式系统的自动升级,一方面可以简化升级过程,无需通过串口或网络将目标板与主机相连,将文件下载升级,而只需插入CF卡,启动系统便可以完成升级过程;另一方面,升级速度也大大提高,因为系统对CF卡的存取速度远远高于串口或网络。但是,要通过CF卡实现系统升级,嵌入式板必须具有CF卡接口,因此,它并不适合所有的嵌入式系统。
三星s3c2410支持Nand Flash启动,但这并不意味着程序能够在Nand Flash中运行,这点与Nor Flash中不同。原因在于,Nand Flash不支持按字节寻址,它的最小寻址单位是页(512+16bytes),其中可用容量为512bytes,后16bytes一般用于存放校验信息。所以,Nand Flash启动仅仅是在系统上电后,cpu拷贝Nand Flash的前4K内容到stepping stone中运行,后者是cpu中一个高速缓存。这么以来有两个问题:其一,bootloader代码如果大于4K,就必须做程序代码段,数据段的搬运工作,后面具体谈;其二,代码搬运后,要重定向pc,使其指向SDRAM所对应的内存。
典型的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的中断响应函数等等。
第二阶段首先做的是设置时钟。s3c2410手册中明确指出,复位后,cpu使用外部时钟源,而非MPLL。通过设置MPLL,从而初始化HCLK,FCLK和PCLK,后者给cpu和外设提供稳定的时钟。接着可以对中断控制器和串口进行初始化,这样就可以通过串口向PC终端输出一些交互信息了。
接着打开MMU,指令缓存和数据缓存。这里可以设置协处理器CP15的Register 13(ProcID)为0,并建立从虚地址到物理地址的直接映射关系。
以上工作完成后,将存放于Nand Flash中的kernel和启动参数搬运到内存中的特定区域,重新设置好时钟(与内核中的保持一致), 关闭MMU,指令缓存和数据缓存,跳转到内核的起始地址就可以运行了。
至此,bootloader就完成了它的历史使命,如果人品好的话,kernel就会乖乖的运行了。当然,这里假定的是手头上已经有一份移植并裁剪好的内核。
1.概述
linux运行在保护模式下,但是当机器启动复位的时候却处于实模式下。所以写bootloader做的工作也是在实模式之下的。
linux的内核有多种格式,老式的zImage和新型的bzImage。它们之间最大的差别是对于内核体积大小的限制。由于zImage内核需要放在实模式1MB的内存之内,所以其体积受到了限制。目前采用的内核格式大多为bzImage,这种格式没有1MB内存限制。本文以下部分主要以bzImage为例进行分析。
2.bzImage格式内核的结构
bzImage内核从前向后分为3个部分,前512字节被称为bootsect,这就是软盘引导linux时用到的bootloader,如果不从软盘引导,这部分就没有用,其中存储了一些编译时生成的内核启动选项的默认值。从512个字节开始的512*n个字节称为setup部分,这是linux内核的实模式部分,这部分在实模式下运行,主要功能是为保护模式的linux内核启动准备环境。这个部分最后会切换进入保护模式,跳转到保护模式的内核执行。最后的部分就是保护模式的内核,也就是真正意义上的linux内核。其中n的大小可以从bootsect后半部得到,详细地址可以参阅linux boot protocol。
3.引导过程概述
第一步,打开冰箱门;第二步把大象放到冰箱里……不要笑,过程就是这么简单。首先需要把linux内核的setup部分拷贝到9020H:0开始的地址,然后把保护模式内核拷贝到1MB开始的地址,然后根据Linux Boot Protocol 2.03的内容设定参数区的内容,基地址就是9000H:0,最后使用一条ljmp $0x9020,$0跳转到setup段,剩下的事情就是linux自己的了^_^,果然简单吧!
4.THE LINUX/I386 BOOT PROTOCOL
这个就是我们引导linux所使用的协议,它的位置在:Documetation/i386/boot.txt中。里面详细的写了引导linux所需要知道的一切知识,对于其它体系结构的CPU,也一定存在着类似的东东,仿照本文的方法就可以了。
5.细节一:基本引导参数
当然我们不指定任何参数linux内核也可以启动,但是这样有可能启动进入一个我们不支持的framebuffer模式,导致没有任何屏幕显示;也可能mount了错误的根分区失败,导致No Init Found的kernel panic。所以我们必须要指定一些东西。
如果你像我一样是一个懒人,那么可以直接把bootsect拷到9000H:0的位置,使用软盘引导时它会把自己复制到这个地方的,这里面有些默认的设置,详情请见boot.txt。
首先是root的位置,这里bootsect_pos指向的是9000H:0的地址。
bootsect_pos[0x1fc] = root_minor;
bootsect_pos[0x1fd] = root_major;
其中root_minor和root_major分别是root的主设备号和次设备号。
当前显示模式:
bootsect_pos[0x1fa] = 0xff;
bootsect_pos[0x1fb] = 0xff;
这两个数值相当于引导参数vga=0xHHH的值,两个0xff代表文本模式。
bootsect_pos[0x210] = 0xff;
这是在设定你的bootloader的类型,其实只要不是0就行,因为0代表的loader太旧无法引导新的内核,setup发现这个后就会停下来。按照规范你应该写成0xff,这表示未知的boot loader,如果你的bootloader已经得到了一个官方分配的type id,那就写上自己的数值。
6.细节二:如何加载内核
如果你现在的环境是一无所有,那么必须使用bios中断或者ATA指令去读硬盘了,不过如果你手中如果有基本的DOS系统,那么就可以使用DOS的程序了。为了能够操作整个4GB的地址空间,我使用了WATCOM C写了个小程序读内核,不过你可以仿照bootsect里面的做法,在实模式中读一部分,然后进入到保护模式拷贝到1MB以上,然后再从实模式读一部分……需要注意1:9000H:0也是DOS占用的地址空间,所以读完内核后就不要返回DOS了,否则会有问题;
注意2:一定保证是纯DOS,不要加载HIMEM或者EMM386这样的东西,它们会使上面的引导过程失败。loadlin倒是可以来者通吃几乎所有的DOS,不过它的作者也是这方面的大牛,对DOS下的内存管理非常的熟悉。我们现在研究这些古老的东西很难找资料了,况且我们是在写bootloader,不是DOS killer^_^。
7.引导时的高级功能
1)initrd
initrd是启动时的一个小虚拟盘,一般用它来实现模块化的内核。引导initrd的方法主要有两个要点:
第一,把initrd读入内存,我们可以仿照大多数boot loader的方法把它放在内存的最高端;
第二,设定initrd的起始位置和长度
bootsect_pos[0x218]开始的4个字节放的是起始物理地址,bootsect_pos[0x21c]开始的4个字节放的是initrd的长度。
2)command_line支持
用command_line你可以给内核传一些参数,自己定制内核的行为。我是这样做的,首先把command_line放在9900H:0的地址里,然后把9900H:0的物理地址存放在bootsect_pos[0x228]开始的4个字节里面。注意一定是物理地址,所以你应该放99000H这个数,然后内核就会识别你的command_line了。
8.结束语
写本文的目的主要是为了用最少的语言和最短的时间说明bootloader的原理,真正的权威资料还是要看linux内核源码和boot.txt文件。我曾经写过一个例子loaderx,使用WATCOM C和TASM,WATCOM C是一个可以在DOS下生成能访问4GB物理地址程序的C编译器,里面也有详细的注释和文档说明。可以从下面的地址下载:[url=http://www-900.ibm.com/developerworks/cn/linux/embed/l-bootloader/loaderx.tar.gz]loaderx.tar.gz[/url]
参考资料
THE LINUX/I386 BOOT PROTOCOL 2.03
bryan_sun 回复于:2005-02-25 13:32:41 写得不错啊
天外闲云 回复于:2005-02-26 01:02:19 晕,这个东西不同的设备不一样的说。而且现在免费的bootloader有很多,重写一个没必要,改写就行了。