KEY:x86体系 CPU 系统引导 启动过程 Linux LILO bootstrapping
X86体系计算机系统的自举(bootstrapping)过程起始于对CPU的RESET引脚的触发。这个操作会把CPU的一些寄存器置为默认值,比如代码段寄存器CS(code segment)被置为0xf000、指令指针寄存器(EIP)被置为0x0000fff0等,其它寄存器的初始值如下表(下表未列的其它寄存器值均为未定义):
32位还是16位?
细心注意开机时有部分寄存器的值是32位的。其实在386以后的32位处理器在实模式下产生的地址也是32位的,只不过16位程序只使用固定的16位兼容的一个地址空间罢了(KEMIN:这个结论有待进一步的验证)。举例如开机第一指令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.
从上面这段话可知应该是需要的,那么在开机的一刻这样的操作环境建立起来没有?请想想上BIOS的初始化工作一步,并看以下的实模式下的内存布局,非ROM内存类型(比如实模式中断向量表)的数据都是16位程序的操作环境数据。
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.
接上一阶段,这一阶段一开始已经有一段引导程序代码驻留在始于0x00007c00 处,大小为一个扇区512字节,并且处理器下一条指令的地址是 0x00007c00。由于引导的功能需求多种多样(例如有不同的引导介质或多操作系统共存),会有多种不同的引导程序。引导过程细节不尽相同,比如不同的引导介质调用不同的驱动(调用软盘驱动或IDE磁盘驱动),而引导过程本身也有分两个(比如LILO)甚至三个(比如GRUB)前后相继“接力”的16 位程序完成。不管引导的策略如何,引导程序主要任务 还是把操作系统从引导介质加载入内存。下面是利用LILO从硬盘加Linux的一个过程(假设LILO 分两步完成引导,前一步由16位程序LA完成,后一步由16位程序LB完成):
图LILO运行后的计算机内存布局
KEMIN:这个过程远没详尽LILO的引导过程。过程中出现很多神秘的信息,比如描述表、参数行数据、软盘启动扇区、setup()函数所在扇区,这里没有讲LILO是怎么知道这些数据的位置的。
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),接着就是内核镜像。当我们编译一个新内核的时候,引导代码只是简单地放在内核镜像的前面,这样只需要把内核镜像从磁盘的第一个扇区开始整个拷进去就可以制作出一张可引导的磁盘了。