进程管理(2):内核启动之前的引导流程

1. 说明

切确地说,Linux内核启动之前的过程并不属于Linux进程管理的部分,但是了解这部分内容对于从全局上学习操作系统也是很有帮助的,所以我把这部分内容也放在了Linux进程管理的分类里面,作为整个故事的起源,可以更好地帮助我梳理整个Linux进程启动到消亡的过程。

本篇笔记涉及到grub,BIOS等内容本身也是十分复杂和庞大的软件,但是不属于我准备研究的重点,所以对这些内容只是作个宽泛地说明,了解个大概就好,后续如果有机会再专门研究看看吧。

注:文中的启动流程基于Linux v5.12版本和x86体系结构的处理器。

2. 平台引导流程

从按下电源键开始,到Linux start_kernel,这么长的时间内其实大部分都是运行在BIOS和grub引导程序中的,真正涉及到Linux内核的部分很少,而前两者并不仅仅针对Linux系统,而是所有系统都可通用的。所以与其说是Linux引导流程,我觉得叫平台引导流程更贴切些。

2.1 总体流程

  • 按下开关键
  • 上电后RIP寄存器内容被设置为0xFFFF0,指向1MB内存的最后16bytes(此时系统进入实模式,最大寻址为1MB)
  • BIOS启动运行
    • 启动POST自检程序
    • 读取CMOS Flash
    • 查找操作系统的引导盘 读取MBR内的boot.S文件,并跳转
    • GRUB引导程序启动
    • stage1:boot.S(MBR) -> diskboot.S -> startup.S ->
      grub_main() in main.c
    • grub_main() -> … -> grub_show_menu -> show_menu
      -> grub_menu_execute_entry -> grub_parser_execute / grub_command_execute -> grub_linux_boot -> code32_start
  • 进入Linux代码
    • Linux/arch/x86/boot/header.S -> arch/x86/boot/main.c ->
      arch/x86/boot/pm.c -> arch/x86/boot/pmjump.S
    • arch/x86/boot/compressed/head_32.S -> extract_kernel()
    • arch/x86/kernel/head_32.S: initial_code -> i386_start_kernel ->
      start_kernel()
    • init/main.c: start_kernel() -> 内核启动

2.2 RIP寄存器

RIP寄存器是Intel x86架构下提供的指令寄存器,用于存放指令地址和程序计数。全称为instruction pointer register。

这里RIP被设置为0xFFFF0,是由硬件线路固定的,表示CPU下一步从该地址获取指令运行,而这个地址对应的是BIOS ROM的起始地址,从这里开始,平台跳转到BIOS开始执行。

2.3 BIOS阶段

BIOS一般是一个固定在硬件主板上的ROM,由BIOS开发商开发的FW被烧录到该器件内。此时BIOS ROM上的内容会被映射到内存(0xFFFF0的地址)中,也就是说对CPU而言仍然是按照内存寻址的方式去获取指令。

这部分运行流程如下:

  • 启动自检程序
  • 寻找显示设备,这部分内容在地址0xC000中 寻找其他设备的ROMs,比如IDE/ATA 磁盘在0xC8000地址处。
  • 点亮启动屏幕
  • 初始化内存
  • 初始化外设,比如COM和LPT端口等
  • 检测并配置即插即用设备
  • 在屏幕上显示系统基本配置信息
  • 根据CMOS的引导顺序,搜索引导设备,这里主要是硬盘和硬盘分区
  • 在CHS=001的位置上寻找MBR
  • 将MBR的内容拷贝到0x7C00内存位置(该地址在boot.S文件中规定)
  • 跳转到0x7C00,grub引导程序开始执行

流程图如下所示:
在这里插入图片描述

注:上述BIOS运行流程根据开发商和平台的不同,可能略有不同。

CMOS Flash

CMOS Flash 芯片存放着一些在BIOS运行阶段用到的数据信息,这些数据是可改的,比如BIOS Setup下的配置等。该芯片的内容本身是易失的,一旦掉电,数据将恢复默认值,BIOS的运行也将恢复到出厂时默认的流程。

通常硬件主板会有一个纽扣电池用于给CMOS供电,保证数据稳定,这也就是为什么有的时候再重启系统不能解决问题的时候,会有人建议扣掉纽扣电池,完全放电再试一下。

2.4 GRUB阶段

grub其实只是bootloader的一种,Linux系统通常使用LILO或者GRUB,RedHat从7.2版本开始就是用GRUB作为默认的启动引导程序。不同的发行版Linux有可能会使用不同的引导程序。

grub引导程序是独立于Linux Kernel的,所以这部分代码也不在Linux kernel的源代码内。但是会被包含在发行版的系统(.iso)内,所以当我们光盘或者USB安装完整操作系统时,是自带了grub程序的。

grub有几个重要的文件,stage1,stage2。stage1很小,通常是1024字节。stage1通常位于主引导扇区里面,对于硬盘就是MBR了,stage1的主要功能就是装载第二引导程序(stage2)。这第二引导装载程序实际上是引出更高级的功能, 以允许用户装载入一个特定的操作系统。在GRUB中,这步是让用户显示一个菜单或是输入命令。由于stage2很大,所以它一般位于文件系统之中(通常是boot所在的根分区)。

这部分运行流程如下:
stage1:

  • BIOS拷贝MBR(stage1) boot.S到0x7C00,并跳转到给地址;
  • 拷贝diskboot.S镜像到buffer GRUB_BOOT_MACHINE_BUFFER_SEG (0X7000),并跳转到该地址;
  • 拷贝buffer到kernel_address GRUB_BOOT_MACHINE_KERNEL_ADDR(0x8000),并跳转到该地址;
  • 拷贝startup.S镜像到地址0x8200,并跳转到该地址;
  • 此时进入startup.S,调用EXT_C(grub_main),进入stage2

stage2:

  • main.c
    • grub_machine_init()
    • grub_load_modules() -> grub_dl_load_core() -> grub_dl_call_init() -> mod -> init(mod)
    • grub_register_core_commands()
      grub_load_config() -> grub_parser_execute
    • grub_load_normal_mode() -> grub_dl_load(“normal”) -> grub_command_execute(“normal” ) -> cmd -> func(cmd, argc, argv) -> grub_cmd_normal()
    • grub_enter_normal_mode() -> grub_normal_execute(config) -> read_config_file(config) / grub_show_menu()
  • commands/boot.c
    • GRUB_MOD_INIT(boot) -> grub_register_command(“boot”, …) -> grub_register_command_prio() -> grub_command_list -> grub_cmd_boot()
    • GRUB_MOD_FINI(boot)
    • GRUB_MOD_INIT(linux) -> grub_cmd_linux()
    • grub_cmd_linux() -> grub_loader_set(grub_linux_boot, …) -> grub_loader_boot_func -> grub_loader_loaded=1 -> loaded = 1
    • GRUB_MOD_FINI(boot)
  • menu.c
    • show_menu() -> run_menu() -> e = grub_menu_execute_entry -> grub_scripte_execute_sourcecode / grub_cmd_execute(“boot”, …)
    • grub_scripte_execute_sourcecode(entry->sourcecode) -> parsed_script = grub_script_parse(line) -> grub_script_execute(parsed_script) -> grub_script_execute_cmd(script->cmd) -> cmd -> exec(cmd)
    • grub_cmd_execute(“boot”, …) -> grub_cmd_boot() -> grub_loader_boot() -> grub_loader_boot_func() -> grub_linux_boot() -> state.eip = params -> code32_start -> grub_relocator32_boot()-> grub_relocator_prepare_relocs() -> grub_cpu_relocator_jumper() -> restart = rels0; -> asm volatile(“cli”) -> ((void () (void)) relst) () --> code32_start --> 0x100000
    • 进入Linux Kernel 下的 header.S

其中stage1的流程图如下所示:
在这里插入图片描述

2.5 Linux引导阶段

grub stage2最后会根据平台的不同跳转到Linux系统的引导文件中,比如x86架构下文件在linux/arch/x86/boot目录下。

  1. header.S:
code32_start:        .long        0x100000        # 0x100000 = default for big kernel
call        main    # Jump to C code (should not return)
  1. main.c: main(void)
copy_boot_params();go_to_protected_mode()
  1. pm.c : go_to_protected_mode(void)
realmode_switch_hook()protected_mode_jump(boot_params.hdr.code32_start,  (u32)&boot_params + (ds() << 4));
  1. pmjump.S : protected_mode_jump
.byte        0x66, 0xea                # ljmpl opcode
jmpl        *%eax                        # Jump to the 32-bit entrypoint
  1. compressed/head_32.S:
call        extract_kernel                /* returns kernel location in %eax */
xorl        %ebx, %ebx
jmp        *%eax
  1. compressed/misc.c: extract_kernel()
__decompress(...)
  1. x86/kernel/head_32.S:
call *(initial_code)
SYM_DATA(initial_code,                .long i386_start_kernel)
  1. x86/kernel/head32.c:
asmlinkage __visible void __init i386_start_kernel(void)
start_kernel();

至此,引导程序结束,Linux内核开始运行,start_kernel()函数是各架构通用的入口函数。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翔底

您的鼓励将是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值