《The Art of Linux Kernel》- Ch1

版本:Linux 0.11

硬件:IA-32 CPU、16MB 内存(2MB作为虚拟内存)、BIOS中设置软驱作为启动

(软盘相当于一种可移动的外部存储设备,由于读取速度慢、容量小等原因,目前已经被其他更高效、容量更大的外部存储设备所替代)

通电 -> main

通电->BIOS启动->内存中加载中断向量表、中断服务程序->32位模式

1、BIOS启动

找到BIOS

  • CPU硬件逻辑实现:CS=0xF000 IF=0xFFF0
  • BIOS的地址位置:0xFFFF0,固化在主机版的ROM中
开电时,BIOS的状态

BIOS加载中断向量表、中断服务程序

  • 启动BIOS以后,开始监测显卡、内存等等,最重要开始加载中断向量表、中断服务程序
image-20230922144618617
  • 每个中断向量都指向对应的中断服务程序(实模式的中断机制)
  • 256个中断向量,每个4字节,2字节IP的值,2字节是CS的值

2、加载操作系统内核程序

分批从软盘的不同扇区加载所有的操作系统程序到内存,为保护模式准备

加载第一批内核代码:bootsect

  • 硬件设计+BIOS,出现一个int 0x19中断,对应找到对应的中断服务程序,这个服务程序的作用是将第一扇区的程序加载
image-20230922150911622
  • 第一扇区(boot sector)内对应的程序就是引导程序:bootsect(512B)
image-20230922151502051
  • 至此,从启动电脑终于接触到了linux操作系统自己的代码(一段汇编),加载到了内存中

讨论:对于不同操作系统,如何同样方式加载?

BIOS是写在主机板ROM上的,不管操作系统是啥,对于不同的操作系统类型,采用同样方式加载:

  1. BIOS接到启动os的指定,固定从启动扇区把程序加载到0x07C00
  2. 操作系统的第一段程序固定放在软盘0盘面0磁道1扇区

第二部分内核代码:setup

boosect的任务是继续加载第二、三批的内核代码,采取以下步骤:

(1)规划内存(key in os)
  • 确保不同的程序、数据不会重叠,有足够的空间安身立命
image-20230922153024041
(2)复制bootsect到新的位置
  • 现在CPU的CS指向的就是BOOTSEG

  • bootsect把自身从BOOTSEG复制到INITSEG(pool)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    start:
    mov ax,#BOOTSEG
    mov ds,ax
    mov ax,#INITSEG
    mov es,ax
    mov cx,#256 (256字就是512B)
    sub si,si
    sub di,di
    rep
    movw

  • bootsect直接可以跳转到INITSEG执行了(相当于原来的BOOTSEG位置是约定的位置,不是自己规划的位置),这个时候开始完全摆脱BIOS,根据自己的安排执行

image-20230922161104507
1
2
jmpi go,INITSEG   !这行之前bootsect已经复制过去到了INITSEG,程序跳转到INITSEG执行了
go: mov ax, cs !在INITSEG新的位置继续执行bootsect代码
image-20230922161722859
  • 代码整体位置变了,所以代码各个段(寄存器)也会发生变化
    • SS(Stack segment):指向栈段
    • SP(Stack pointer):栈顶指针
(3)加载setup程序
  • int 0x13中断,指向磁盘服务程序
  • 和加载bootect时候的0x19中断不同,0x19对应的服务程序是BIOS执行的,但现在是bootsect执行0x13指向的程序
  • 0x19对应的服务程序写死,将第一扇区的程序加载到0x07C00,而0x13的程序可以指定扇区的代码加载到指定位置,开始要传参
image-20230922162147780
  • 注意此时已经加载5个扇区,且setup.s会加载到INITSEG的位置

    image-20230922162942831

加载第三部分内核代码:system模块

  • 仍然用0x13的中断

    image-20230922163259030
  • 这次加载240个扇区,由bootsect的read_it完成,加载到SYSSEG之后

    image-20230922163439978

Bootsect收尾工作:检查根设备号

  • 检查是否设置了根文件系统设备,若没有设置根据不同扇区数设置
  • 所以linux启动要系统内核镜像、根文件系统
  • 可以理解到这里,bootsect程序执行完了,接下来是setup的执行

setup执行

  • jump 0,SETUPSEG,跳转到setup程序的位置开始执行
  • 利用BIOS的中断服务程序,提取内核运行需要的机器系统数据:光标位置、显示页面等等,加载到内存对应的位置
image-20230922164428132
  • 加载的位置覆盖了bootsect的程序位置(只留出一个字节空间)
  • 至此,内核代码加载完全结束,通过已经加载到内存的内核代码,系统将从实模式转变为保护模式

3、开启32位保护模式

废除原本的中断机制

  • 关闭中断:关闭直到建立main适合的新的中断服务建立
    • 将CPU的标识寄存器EFLAGS的IF置为0(中断不被允许)
    • setup.s中的cli和sti分别是关、开中断
  • 移动内核代码到0x00000,覆盖了原来BIOS的中断向量表、数据的区域
    • 16位的实模式中断不再适合即将开始的32位linux中断,因此可以回收这块区域了

描述符表的准备

  • 全局描述符表:GDT

    • 唯一存段寄存器内容(段描述符)的数组,配合程序实现段寻址
    • 所有进程的全局目录,存储每一个任务的局部描述符表(LDT)地址、任务状态段(TSS)地址
    • 功能:各段的寻址、保护和恢复现场
    • GDTR:GDT基地址寄存器,存GDT的基地址,通过LGDTR命令来加载GDT到GDTR
  • 中断描述符表:IDT

    • 所有中断服务程序的入口地址,类似实模式的中断向量表
    • IDTR:IDT基地址寄存器
  • 比较16位中断和32位中断

    • 16位的中断向量表固定写死在0x00000的位置,32位利用IDT可以任意放位置,用IDTR来寻址
    • 在中断重启之前,IDT是一张空的表

打开32位寻址

  • 打开A20地址线,注意物理内存寻址和内存寻址的变化

    image-20230923102442817
  • 内存寻址:直接从5个f变成8个f,32位,4GB的寻址空间

  • 物理寻址:16MB(pool)

重新编程8259A

  • 8259A是一个可编程的中断控制器
  • 保护模式下,Intel保留了0x00-0x1F作为不可屏蔽中断、异常中断,所以要重新分布中断号
image-20230923103757762
  • CR0寄存器置为1,CR0寄存器保存的是PE(protected mode enable,保护模式使能,为0则实模式)
  • 正式开启保护模式,对比开启前后的寻址变化(pool,P26)

4、 head.s的执行

p27

review

实模式和保护模式

  • 实模式,即程序中用到的地址都是真实的物理地址,“段基址:段内偏移地址”产生的逻辑地址就是物理地址,即程序员可见的地址完全是真实的内存地址
  • 保护模式:随着CPU的位数和寄存器的位数增加,有更安全内存地址定位方式。见详细解释

CS和IP

  • CS是代码段寄存器,定义存放代码的存储器的起始地址
  • IP为指令指针寄存器,他们一起合作指向了CPU当前要读取的指令地址

段寄存器

  • pool

《The Art of Linux Kernel》- Ch1
https://al-377.github.io/2023/09/22/The-Art-of-Linux-Kernel-Ch1/
作者
Aidan Lew
发布于
2023年9月22日
许可协议