简单过程

  1. BIOS启动主动执行的固件,去认识第1个可启动设备
  2. 第一个可启动设备的第一个扇区的主引导块MBR,内含启动引导代码
  3. bootloader(启动引导程序),读取内核文件来执行软件
  4. 内核文件启动操作系统

    从加电到BIOS启动

    第一步,加电引导寄存器置位

    这个过程指,计算机加电之后,一个特殊电路会在CPU对应的针脚处产生一个逻辑电平,这个电平的值从针脚进入CPU,会引发寄存器(cs,eip)设置成特定值。

    第二步,引导BIOS启动

    这一过程指的是系统从物理地址0xfffffff0处加载一段程序到只读内存(ROM-> Read Only Memory),这个程序在80x86体系架构中一般称为BIOS

    相关知识学习

  • MS-DOS的很多系统调用依赖BIOS
  • Linux进入保护模式后不再依赖BIOS,BIOS只能以实模式运行。
    1. 实模式的寻址是20位总线寻址,支持的寻址空间为2^20,也就是1MB,保护模式目前在x86结构下,支持4GB寻址;
    2. 实际区别主要是EIP中的虚地址到实地址转化的区别:
    3. 实模式是seg(eip地址)*16+offset(4为偏移量);
    4. 保护模式实EIP16位地址代表页面位置,一个页在操作系统中都学习过是4KB1M*4K = 4G,我相信很多人就此理解了为啥页的大小要设计成4K

    BIOS引导加载操作系统镜像

    第一步,检查硬件

    一般可认为是开机加电自检,这个阶段会显示一些信息,包括BIOS版本这一类的信息

    第二步,初始化硬件

    主要是避免IRQ先与I/O冲突,本阶段最后会显示所有PCI(总线—内部硬件通信线路)设备信息

    第三步,搜索操作系统

    从软盘、网络、磁盘、CD-ROM的主引导扇区上搜索。找到后加载到扇区的内容到0x00007c00的位置(RAM中),跳转到这个地址,开始执行这段代码,这段程序叫做bootloader。
    1. 由于大小限制,linux的启动程序GRUBGRand Unified BootLoader)或者是LILOLInux LOader)被分为两部分。
    2. 第一部分就是加载到0x00007c00的这一段,他会把自己移动到0x00096a00的位置,建立实模式栈(0x00098000~0x000969ff
    3. 第一部分吧第二部分加载到0x00096c00开始的位置中。
    4. 以上的位置都是在RAM中。
    5. 第二部分搜索磁盘上的OS景象,,把对应的扇区拷贝到RAM中执行:
    6. 1、首先把内核景象的第一个512B的部分从0x00090000处装入RAM中;
    7. 2、把setup()函数代码段装入0x00090200位置(RAM);
    8. 3、加载其他内核部分从高(0x00100000)或低(0x00010000)两个位置任选其一加载到RAM中,分别称为大映像内核和小映像内核;
    9. 4、跳转到setup函数执行;

    Setup 函数引导内核

    这个过程主要是检查和初始化硬件、虽然BIOS完成了相似的大部分工作,但是因为不依赖与BIOS,所以,还是重新初始化了硬件方面的事情;重要的过程有:
  1. 移动低装载小映像内核的位置到0x00001000去,如果是高装载则不移动;
  2. 建立IDT(临时中断描述符表)和GDT(临时全局描述符表);
  3. 如果需要,重置浮点单元(FPU);
  4. 重新编写可编程终端控制器(PIC),屏蔽除IRQ2外的所有终端;
  5. 设置cr0寄存器到PE位,设置PG位为0,切换到保护模式,暂未启用分页;
  6. 跳转到startup_32()函数;

    内核建立阶段

    startup_32()函数

    主要做的事如下:

  7. 初始化段寄存器和一个临时堆栈,并清零eflags寄存器所有为;

  8. 用0填充_edata 和_end符号标识的内核未初始化数据区;
  9. 调用decompress_kernel函数解压内核映像;

【低装载的情况解压内容放在0x00100000位置开始的RAM中,高装载的放在这后面的一个临时缓冲区内,解压后的内核就被移动到0x00100000位置】

  1. 跳转到0x00100000位置开始执行,新的执行点事arch/i386/kernerlhead.s中的另一个startup_32函数。

    startup_32()函数

    这个函数就是init进程(也就是pid = 0 的 0号进程),主要做了以下工作

  2. 段寄存器初始化为最终值,内核的bss段填写为0;

  3. 初始化临时内核页表,初始化pg0,使得线性地址一律映射到统一的物理地址上;
  4. cr3寄存器保存了页全局目录,并设置cr0的pg位启用分页;
  5. 清零eflags,使用setup_idt函数用空的终端处理程序填充IDT;
  6. 从bios获取的数据(系统参数和传递给os的参数)放入页框1;
  7. 识别处理器、用GDT和IDT填充gdtr和idtr寄存器;
  8. 跳转到start_kernel函数

    内核完善阶段start_kernel函数

    这一阶段最终完善了内核的初始化的后续工作,启动了程序调度、内存管理等操作系统的功能,其中就涉及到了著名的函数sched_init函数,至此,系统完全启动成功