Linux系统引导过程(BIOS和Bootloader部分)

BIOS(0xfffffff0)

X86体系计算机系统的自举(bootstrapping)过程起始于对CPU的RESET引脚的触发。这个操作会把CPU的一些寄存 器置为默认值,比如代码段寄存器CS(code segment)被置为0xf000、指令指针寄存器(EIP)被置为0x0000fff0等,其它寄存器的初始值如下表(下表未列的其它寄存器值均为未 定义):

  1. EFLAGS =00000002H  
  2. EIP =0000FFF0H  
  3. CS selector =F000H BASE = FFFF0000H Limit = FFFFH  
  4. DS selector =0000H BASE = 00000000H Limit = FFFFH  
  5. ES selector =0000H 同上  
  6. SS selector =0000H 同上  
  7. FS selector =0000H 同上  
  8. GS selector =0000H 同上  
  9. IDTR: base =0 limit =03FFH  

EFLAGS =00000002H EIP =0000FFF0H CS selector =F000H BASE = FFFF0000H Limit = FFFFH DS selector =0000H BASE = 00000000H Limit = FFFFH ES selector =0000H 同上 SS selector =0000H 同上 FS selector =0000H 同上 GS selector =0000H 同上 IDTR: base =0 limit =03FFH

32位还是16位?

细心注意有部分寄存器的值是32位的。其实在386以后的32位处理器在实模式下产生的地址也是32位的,只不过16位程序只使用固定 的16位兼容的一个地址空间罢了。举例如开机第一指令8086上应该是0xffff0,但是在80368却是0xfffffff0。这个地址在保护模式是 通过EIP加上保存在由段选择子指向的段描述符里的基址得到的,但是在实模式下段描述符尚没有初始化,那么基地址从哪来的呢?INTEL解释说为了提高访 问的速度,每个段寄存器都有隐藏部分用作缓存,看下面:

The CS register has two parts: the visible segment selector part and the hidden base address part. In real-address mode, the base address is normally formed by shifting the 16-bit segment selector value 4 bits to the left to produce a 20-bit base address. However, during a hardware reset, the segment
selector in the CS register is loaded with F000H and the base address is loaded with FFFF0000H. The starting address is thus formed by adding the base address to the value in the EIP register (that is, FFFF0000 + FFF0H = FFFFFFF0H).

Every segment register has a “visible” part and a “hidden” part. (The hidden part is sometimes referred to as a “descriptor cache” or a “shadow register.”) When a segment selector is loaded into the visible part of a segment register, the processor also loads the hidden part of the segment register with the base address, segment limit, and access control information from the segment descriptor pointed to by the segment selector. The information cached in the segment register (visible and hidden) allows the processor to translate addresses without taking extra bus cycles to read the base address and limit from the segment descriptor. In systems in which multiple processors have access to the same descriptor tables, it is the responsibility of software to reload the segment registers when the descriptor tables are modified. If this is not done, an old segment descriptor cached in a segment register might be used after its memory-resident version has been modified.

因此电脑一开启,工作在实模式下的16位BIOS程序便开始工作,包括系统自检(POST)和硬件初始化工作;BIOS还提供了基本的 低级的设备驱动程序,以便进行输入输出,比如磁盘驱动、显示驱动。一些早期的基于实模式的OS还依赖BIOS提供的驱动程序,比如DOS,比如 WINDOWS3.1。由于 Linux内核是工作在保护模式下的,所以内核重新实现了所有设备驱动,用以替换实模式BIOS驱动。但在内核正式接管计算机之前,引导加载程序还是利 BIOS的磁盘驱动把内核映像和其它数据从磁盘读出。

在这一阶段BIOS的大概工作为四部分:

1. 第一个部分是对系统硬件进行故障检测,也叫做加电自检(Power On Self Test,简称POST),功能是检查计算机系统是否良好;通常完整的POST自检将包括对CPU,640K基本内存,1M以上的扩展内存,ROM,主 板, CMOS存储器,串并口,显示卡,软硬盘子系统及键盘进行测试,一旦在自检中发现问题,系统将给出提示信息或鸣笛警告。自检中如发现有错误,将按两种情况 处理:对于严重故障(致命性故障)则停机,此时由于各种初始化操作还没完成,不能给出任何提示或信号;对于非严重故障则给出提示或声音报警信号,等待用户 处理。 Recent 80 x 86, AMD64, and Itanium computers make use of the Advanced Configuration and Power Interface(ACPI ) standard. The bootstrap code in an ACPI-compliant BIOS builds several tables that describe the hardware devices present in the system. These tables have a vendor-independent format and can be read by the operating system kernel to learn how to handle the devices.

2. 第二个部分是初始化,包括创建中断向量、设置寄存器、对一些外部设备进行初始化和检测等,其中很重要的一部分是读取BIOS配置,对系统硬件进行状态配置 和检查。 This phase is crucial in modern PCI-based architectures, because it guarantees that all hardware devices operate without conflicts on the IRQ lines and I/O ports. At the end of this phase, a table of installed PCI devices is displayed.

3. 最后一个部分是查找引导程序,用来引导DOS或其他操作系统。查找顺序取决于BIOS设置depending on the BIOS setting, the procedure may try to access (in a predefined, customizable order) the first sector (boot sector) of every floppy disk, hard disk, and CD-ROM in the system.

4. As soon as a valid device is found, it copies the contents of its first sector into RAM, starting from physical address 0x00007c00, and then jumps into that address and executes the code just loaded.

KEMIN:把引导自举划分为几个阶段,并明确各个阶段的[逻辑任务 ]对把握引导过程很有裨益。这些[逻辑任务 ]除硬件状态检测,最主要是逻辑环境的构建。因为这些阶段前后有明显的[逻辑层级 ]关系,后一阶段的操作环境由前一阶段来构建和初始化。

16位程序的操作环境?

KEMIN:其实Bootloaders和BIOS一样,都是16位程序(所有DOS程序不管没有界面都是16位程序)。与32位程序运行需要一个操作环境类似,16位程序需不要操作环境?

Boot loaders are programs that reside on the boot device of a computer. A boot loader is called by BIOS after enough system initialization has occurred to support the memory, interrupts, and I/O required to load the kernel.

从上面这段话可知应该是需要的,那么在开机的一刻这样的操作环境建立起来没有?

Physical memory layout of the PC

线性地址范围
实模式地址范围 内存类型 用途
0- 3FF 0000:0000-0000:03FF RAM real-mode interrupt vector table (IVT)
400- 4FF 0040:0000-0040:00FF BIOS data area (BDA)
500- 9FBFF 0050:0000-9000:FBFF free conventional memory (below 1 meg)
9FC00- 9FFFF 9000:FC00-9000:FFFF extended BIOS data area (EBDA)
A0000- BFFFF A000:0000-B000:FFFF video RAM VGA framebuffers
C0000- C7FFF C000:0000-C000:7FFF ROM video BIOS (32K is typical size)
C8000- EFFFF C800:0000-E000:FFFF NOTHING
F0000- FFFFF F000:0000-F000:FFFF ROM motherboard BIOS (64K is typical size)
100000- FEBFFFFF
RAM free extended memory (1 meg and above)
FEC00000- FFFFFFFF
various motherboard BIOS, PnP NVRAM, ACPI, etc.

Boot Loader(0x00007c00)

接上一阶段,这一阶段一开始已经有一段引导程序代码驻留在始于0x00007c00 处,大小为一个扇区512字节,并且处理器下一条指令的地址是 0x00007c00。由于引导的功能需求多种多样(例如有不同的引导介质或多操作系统共存),会有多种不同的引导程序,引导过程细节不尽相同,比如不同 的引导介质调用不同的驱动(调用软盘驱动或IDE磁盘驱动),而引导过程本身也有分两个(比如LILO)甚至三个(比如GRUB)前后相继“接力”的16 位程序完成。不管引导的策略如何,引导程序主要任务 还是把操作系统从引导介质加载入内存。下面是利用LILO从硬盘加Linux的一个过程(假设LILO 分两步完成引导,前一步由16位程序LA完成,后一步由16位程序LB完成):

BIOS将硬盘主引导扇区(其中有LA代码)读入至内存中的0x00007c00处,控制权转给该地址程序LA; LA把自身移动至0x9A000处,并建立堆栈(从0x9B000处向0x9A200增长); LA将LB读入至内存的0x9B000处,把控制权交给LB(KEMIN:LB在什么地方?目前所知它可以在LA或BIOS能访问到的任何地方); LB将把[描述符表 ](descriptor table)读入0x9D200处,并把保存[默认命令行 ](default command line)数据的扇区读入0x9D600处; 接着LB检查用户输入,不管用户输入了一个新的内核镜像还是使用缺省内核镜像信息,[选项扇区 ](options sector)的数据都会被LB读入0x9D600处,而最终生成的[参数行 ](parameter line)会保存在0x9D800处(如果这个参数行中有lock选项,那新参数行会被保存作为默认的参数行); 如果用户配置了初始化RAMDISK镜像的话,这个镜像文件将被读入到物理内存(或16MB)末尾以下(KEMIN:也就是以内)的空间里,并且文件的起始地址必须[低于]下一[内存页 ] 的边界,以便于启动后系统把它所占的内存回收到[空闲内存池]。这里有一个16MB的限制,这是因为BIOS程序不支持对16MB(24位内存空间)以上 内存的访问。(KEMIN:实模式下怎么又会是24位的呢?貌似在开机与正式进入保护模式之前的代码都使用CPU所有指令,改变CPU的操作模式。) (KEMIN:为什么需要初始化RAMDISK:initrd?) 下一步,内核镜像的软盘启动扇区(floppy boot sector)被读入0x90000处;操作系统的初始化代码扇区(setup()函数)则被读入0x90200处。内核镜像余下部分被读入 0x00010000处(称为低地址,专为存放用"make zImage"编译的小内核)或0x00100000处(称为高地址,专为存放用"make bzImage"编译的大内核);在读入的过程中,存放map文件的扇区被读入0x9D000处。 如果读入的image是Linux的内核,控制权将交给处于0x90200的Setup.S。如果读入的是另外的操作系统,过程要稍微麻烦一 点:chain loader被读入到内存的0x90200处。该系统用于启动的扇区被读入到0x90400。chain loader将把它所包含的分区表移到0x00600处,把引导扇区读入到0x07c00。做完这一切,它把控制权交给引导扇区。

图LILO运行后的计算机内存分布情况

KEMIN:这个过程远没详尽LILO的引导过程。过程中出现很多神秘的信息,这里没有讲LILO是怎么知道从什么地方找到这些数据的,比如描述表、参数行数据、软盘启动扇区、setup()函数所在扇区。

LILO怎么找到内核的?

When Lilo boots the system, it uses BIOS calls to load the Linux kernel off the disk (IDE drive, floppy or whatever). Therefore, the kernel must live in some place that can be accessed by the bios.

At boot time, Lilo is not able to read filesystem data, and any pathname you put in /etc/lilo.conf is resolved at installation time (when you invoke /sbin/lilo). Installation time is when the program builds the tables that list which sectors are used by the files used to load the operating system. As a consequence, all of these files must live in a partition that can be accessed by the BIOS (the files are usually located in the /boot directory, this means that only the root partition of your Linux system needs to be accessed via the BIOS).

Another consequence of being BIOS-based is that you must reinstall the loader (i.e., you must reinvoke /sbin/lilo) any time you modify the Lilo setup. Whenever you recompile your kernel and overwrite your old image you must reinstall Lilo.

由此可见,LILO是事先生成启动所需的所有数据,并生成一个元数据文件(map file);并且可看出来,lilo与操作系统的亲缘性强度胜于与BIOS的强度。当然,也别忘记操作系统本身就与硬件是相关的。

LINUX引导磁盘制作

Linux内核经裁减(通过重新配置内核组成的模块并编译)后可被装进一张1.44M的软盘。这张可引导的磁盘布局很简单,引导扇区 (也就是第一个扇区)存放[引导程序](源码是/arch/i386/boot/bootsect.S),接着就是内核镜像。当我们编译一个新内核的时 候,引导代码只是简单地放在内核镜像的前面,这样只需要把内核镜像从磁盘的第一个扇区开始整个拷进去就可以制作出一张可引导的磁盘了。

你可能感兴趣的:(Linux系统引导过程(BIOS和Bootloader部分))