当前位置: 首页 > 工具软件 > Linux-Insides > 使用案例 >

《linux-inside-zn》导读

云卓
2023-12-01

0. 前言

linux-insides是github中一个文章库,里面是一系列介绍linux内核及其内在机理的文章。作者是0xAX,外国大神。所以文章都是英文的。

很幸运的是,同样在github上有一个linux-insides-zh库,是针对linux-insides的中文翻译。该工程由MintCN发起,并由很多志愿者完成。在此表示感谢。

在阅读linux-insideslinux-insides-zh时,发现内容很详尽,几乎每一行代码都有相关的解释和引申。所以,就会由很多细节在里面。这就导致细节太多,而造成对整体的框架理解的凌乱。

所以,本文的作用就是按linux-insides-zhlinux-insides的思路整理出基本的思路,而省略详尽的细节。这样可以更快的理解linux内核及其工作原理。如果某些不明白之处可以再翻阅linux-insides-zhlinux-insides理解具体细节。

1. 启动流程

1.1 通电

对于一台PC而言,

  1. 当按下电源键的时候,主板就会发启动信号给电源,
  2. 电源接到启动信号时,就回输出适当电压给主板的各个部分,
  3. 并发送给主板电源妥备信号,
  4. 主板接收到该信号后,就会尝试启动CPU。
  5. CPU启动之后就会复位各个寄存器值。
    其中比较关键的就是下面寄存器。
IP 0xfff0
CS selector 0xf000
CS base 0xffff0000

1.2 第一条指令

CPU最开始时是运行在实模式下的。
在实模式下,结合上面CS寄存器的初始化值,可知,在CPU启动后,执行第一条指令的地址就是CS base + IP,也就是0xfffffff0,该地址指向4G-16Byte的地方,称为复位向量(Reset vector)。
那么此时问题就来了,此时,系统刚供电,内存控制器还没初始化呢,到哪里去寻址呢?
问题的答案是这样的,此时,对于CPU的指令的解码工作是由南桥而不是北桥完成的,CPU的地址会被传递到南桥,并由南桥的FHW(Fireware Hub 固件集线器)解码到BIOS ROM芯片1
所以,

  1. 根据cpu各个寄存器值得到复位向量的地址,复位向量指向BIOS ROM,复位向量包含一个jump指令,指向BIOS运行的入口点。

1.3 BIOS

  1. 进入BIOS后,BIOS首先会进行一些初始化工作和硬件自检POST(Power On Self Test)。
  2. 硬件自检OK,BIOS会调用#13中断,去按BIOS配置顺序寻找一个可引导设备。
    可能系统中有多个可引导设备,在BIOS会根据配置按顺序逐个尝试。在目前的系统中,一般情况下,可引导设备是硬盘。
    对于硬盘,BIOS会去尝试寻找引导(启动)扇区,以MBR分区方式的硬盘为例,启动扇区位于第一个扇区(512字节)的头446字节。
  3. 找到可引导设备后,将第一个包含有引导记录的引导扇区的内容加载到内存中,并且控制权也转移到此段代码。

1.4 引导程序

对于linux而言,有多个引导程序可选,如grub、syslinux等。就目前而言,linux使用最多的是GRUB 2,就以GRUB 2为例说明,GRUB2引导内核分为三个阶段:

  1. 阶段1:引导扇区中的引导程序是GRUB2的boot.img,因为一个扇区只有512字节,里面还有分区表等内容,所以,boot.img大概只有446字节。其主要作用是加载阶段2的代码到内存中。
  2. 阶段2:该段代码是GRUB2的core.img,里面包含有各种GRUB2支持的文件系统的驱动程序,这些驱动程序加载后,就能识别硬盘中的文件系统了。所以该阶段的主要作用就是加载文件系统驱动,并跳转到阶段3中。
  3. 阶段3:此时GRUB2已经可以读取文件系统中文件了,而该阶段的GRUB2的代码会转到/boot/grub2目录中。该阶段的主要功能是定位和加载Linux内核到内存中,并将控制权交给内核代码。
    引导完成之后,会调用grub_main函数。
    grub_main函数会初始化控制台,计算模块基地址,设置root设备,读取grub配置文件,加载模块。grub_main函数的最后将GRUB置于normal模式。
    在normal模式中,调用grub_normal_execute函数,以完成最后的准备工作。
    然后显示一个菜单列出所有的操作系统。当某一个操作系统被选择之后,会调用grub_menu_execute_entry函数,该函数会调用GRUB的boot命令,引导选中的的操作系统。

1.5 内核设置

  1. 内核的起始入口是arch/x86/boot/header.S文件的__start函数。
    _start前面还有一些代码,是远古linux自带的bootloader。
  2. _start函数最开始是设置剩余的setup header(之前的是由BIOS设置的)。
  3. 然后是start_of_setup标记,标记下的代码主要的工作是
    • 设置段寄存器
    • 设置堆栈
    • 设置bss
    • 跳转到main.c文件

1.6 内核启动第一步main()

  1. main函数首先调用copy_boot_param(void),主要作用内核设置信息hdr(就是上面BIOS和内核设置部分填充的setup header)。拷贝到boot_params结构中。

  2. 然后是控制台初始化console_init()

  3. 之后是堆栈初始化init_heap()

  4. 检查CPU类型validate_cpu(),法检查CPU级别以确定系统是否能够在当前的CPU上运行。

  5. 内存分布侦测detect_memory(),以得到系统当前内存的使用分布。

  6. 键盘初始化keyboard_init()

  7. 系统参数查询query_mca()

  8. 显示模式初始化set_video

  9. 进入保护模式前最后的准备工作go_to_protected_mode

  10. 切换到保护模式protected_mode_jump,函数的最后跳转到32位程序入口

    jmpl *%eax			/*32位入口点的地址:0x10000*/
    
  11. 而0x10000位置的代码就是arch/x86/boot/compressed/head_64.S文件中的startup_32。

    • 栈的建立
    • CPU的确认
    • 计算内核重定位地址
    • 进入长模式(64位模式)前的准备工作
  12. 跳转到startup_64函数。

    • 数据段的建立
    • 计算内核编译时的位置和它被加载的位置的差
    • 栈指针的设置和标志寄存器的重置
    • 清空.bss段
  13. 跳转到decompress_kernel函数,解压缩内核并放入到适当的内存地址中。

  14. 跳转到内存中的内核代码中。

2. 内核初始化


  1. ROM BIOS的启动问题 ↩︎

 类似资料: