3. 加载第二部分内核代码--setup

1. bootsect对内存的规划

BIOS已经把bootsect也就是引导程序载入内存了,现在它的作用就是把第二批和第三批程序陆续加载到内存中。为了把第二批和第三批程序加载到内存中适当的位置。bootsect首先要做的工作就是规划内存。

  • 通常,我们用高级语言编写应用程序,这些程序是在操作系统的平台上运行的。我们只管写高级语言的代码、数据。至于代码、数据在运行的时候放在内存的什么地方,是否会相互覆盖,我们都不需care,OS和高级语言编译器替我们做了大量的看护工作,确保不会出错。而OS本身使用的是汇编语言,没有高级语言编译器替OS系统保障,只有靠OS的设计者把内存的安排想清楚,确保无论OS如何运行,都不会出现代码与代码、数据与数据、代码与数据之间相互覆盖的情况。为了更准确理解OS的运行机制,我们必须清楚OS的设计者是如何规划内存的。

在实模式下,寻址的最大范围是1MB。为了规划内存,bootsect首先设计了如下代码:
bootsect.s

.globl begtext, begdata, begbss, endtext, enddata, endbss

.text
begtext:
.data
begdata:
.bss
begbss:
.text

SETUPLEN = 4                ! nr of setup-sectors
BOOTSEG  = 0x07c0           ! original address of boot-sector
INITSEG  = 0x9000           ! we move boot here - out of the way
SETUPSEG = 0x9020           ! setup starts here
SYSSEG   = 0x1000           ! system loaded at 0x10000 (65536).
ENDSEG   = SYSSEG + SYSSIZE     ! where to stop loading

! ROOT_DEV: 0x000 - same type of floppy as boot.
!       0x301 - first partition on first drive etc
ROOT_DEV = 0x306

这些源码的作用是对后续操作所涉及的内存位置进行设置,包括:

  • 将要加载的setup程序的扇区数(SETUPLEN)以及被加载到的位置(SETUPSEG)
  • 启动扇区被BIOS加载的位置(BOOTSEG)及将要移动到的新位置(INTSEG)
  • 内核(Kernel)被加载的位置(SYSSEG)
  • 内核的末尾位置(ENDSEG)
  • 根文件系统设备号(ROOT_DEV)

设置这些位置就是为了确保将要载入内存的代码与已经载入内存的代码及数据各在其位,互不覆盖,并且各自有足够用的内存空间。

  • 操作系统的设计者要全面地、整体地考虑内存的规划。

2. 赋值bootsect

接下来,bootsect启动程序将它自身(全部512B内容)从内存0x07C00(BOOTSEG)处复制到内存0x90000(INITSEG)处。
执行这个操作的代码(bootsect.s)

entry _start
_start:
    mov ax,#BOOTSEG
    mov ds,ax
    mov ax,#INITSEG
    mov es,ax
    mov cx,#256
    sub si,si
    sub di,di
    rep
    movw
    jmpi    go,INITSEG
go: mov ax,cs
    mov ds,ax
    mov es,ax
! put stack at 0x9ff00.
    mov ss,ax
    mov sp,#0xFF00      ! arbitrary value >>512

! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.
  • ds(0x07C0)和si(0x0000)联合使用,构成了源地址0x07C00
  • es(0x9000)和di(0x0000)联合使用,构成了目的地址0x90000
  • mov cx,#256这一行循环控制量,提供了需要复制的“字”数(一个字2字节,256个字刚好是512字节,也就是第一个扇区的字节数)
jmpi    go,INITSEG
go: mov ax,cs

这两行代码写的很巧。复制bootsect完成后,在内存的0x07C00和0x90000位置有两段完全相同的代码。复制代码这件事本身也是要靠指令执行的。

  • 执行指令的过程就是CS和IP不断变化的过程。
  • 执行到jmpi go,INITSEG之前,代码的作用就是复制代码自身;
  • 执行了jmpi go,INITSEG之后,程序就转到了执行0x90000这边的代码了。Linus的设计意图是,想在跳转之后,在新的位置接着执行后面的mov ac, cs, 而不是死循环。
  • jmpi go,INITSEGgo: mov ax,cs配合,巧妙地实现了“到新位置后接着原来的执行序继续执行下去”的目的。
    bootsect复制到了新的地方,并且要在新的地方继续执行。因为代码的整体位置发生了变化,所以代码中的各段也会发生变化。前面已经改变了CS,现在对DS、ES、SS和SP进行调整。
go: mov ax,cs
    mov ds,ax
    mov es,ax
! put stack at 0x9ff00.
    mov ss,ax
    mov sp,#0xFF00      ! arbitrary value >>512

! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.

上述代码通过ax,用CS的值0x9000来把数据段寄存器(DS)、附加段寄存器(ES)、栈寄存器(SS)设置成与代码段寄存器(CS)相同的位置,并将栈顶指针SP指向偏移地址为0xFF00处。

  • SS与SP联合使用,就构成了栈数据在内存中的位置值。对这两个寄存器的设置为后面程序的栈操作(push、pop...)打下了基础。
  • 这里对SS、SP进行的设置是分水岭,它标志着从现在开始,程序可以执行一些更为复杂的数据运算类指令了。
  • 从代码开始出4个mov指令可以看出,系统给BIOS中断服务程序传参是通过几个通用寄存器实现的。这是汇编程序的常用方法。
  • 参数传递完毕后,执行int 0x13指令,产生0x13中断,通过中断向量表找到这个中断服务(0x90200)处,0x90200紧挨着bootsect的尾端,所以bootsect和setup是连在一起的。

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

第2批代码已经载入内存,现在要加载第3批代码。仍然适用BIOS提供的int 0x13中断。
bootsect程序执行第3批程序载入工作,将系统模块载入内存。

  • 这次载入底层技术上与setup没有差别。
  • 这次加载的扇区是240个,需要的时间较长。为了防止加载期间用户误认为是机器故障而执行不当操作,linus在此设计了一行屏幕显示信息“Loading system...”以提示用户此时正在加载OS。此时OS的main函数还未开始执行,这一行显示需要用汇编来实现。
    • 从体系角度看,显示器也是一个外设,所以还要用到其他BIOS中断
  • bootsect借助BIOS int 0x13中断,将system加载到内存。加载工作主要由bootsect调研read_it子程序完成。
    • 这个子程序将软盘第6个扇区开始的约240个扇区的system模块加载至内存的SYSSEG(0x10000)处往后的120KB空间中。
    • 由于是长时间操作软盘,所以需要对软盘设备进行更多监控,对读盘结果不断进行检测。

到此为止,第3批程序已经加载完毕,整个OS的代码已经全部加在至内存。bootsect的主体工作已经做完。还有一点小事就是,再次确定一下根设备号。

根文件系统设备(Root Device): Linux0.11使用Minix操作系统的文件系统管理方式,要求系统必须存在一个根文件系统,其他文件系统挂接其上,而不是同等地位 。

bootsect程序的人物已经完成,通过执行"jmpi 0, SETUPSEG"语句跳转至0x90200处,就是setup程序加载的位置。CS:IP指向setup程序的第一条指令,意味着由setup程序接着bootsect程序继续执行。

  • setup程序现在开始执行。它做的第一件事情就是利用BIOS提供的中断服务程序从设备上提取内核运行所需的机器系统数据,包括光标位置、显示页面等数据,并分别从中断向量0x41和0x46向量值所指的内存地址处获取硬盘参数表1、2,把他们存放在0x9000:0x0080和0x9000:0x0090处。
  • 这些机器系统数据被加载到内存0x90000 ~ 0x901FC位置。这些数据将在以后的main函数执行时发挥重要作用。
  • BIOS提取的机器系统数据将覆盖bootsect程序所在部分区域。这些数据由于是要留用的,所以在它们失去使用价值前不能被覆盖掉。
  • 在空间上OS对内存严格按需使用。
  • 在时间上,使用完毕的内存立即挪做它用。

到此为止,OS内核程序的加载工作已完成。接下来,系统通过已加载到内存中的代码,将实现从实模式到保护模式的转变,使Linux0.11真正成为“现代”操作系统。

1.3 开始向32位模式转变,为mian函数的调用做准备

  • 接下来,OS要使计算机在32位保护模式下工作。这期间要做吧大量的重建工作,并且持续工作到OS的main函数执行过程中。操作系统执行的操作包括:
    • 打开32位的寻址空间
    • 打开保护模式
    • 建立保护模式下的中断响应机制等与保护模式配套的相关工作
    • 建立内存的分页机制
    • 做好调用main函数的准备

《Linux内核设计的艺术第2版》学习笔记

你可能感兴趣的:(3. 加载第二部分内核代码--setup)