linux0.11/bootsect/load_setup解析

/ 默认分类 / 0 条评论 / 474浏览

一.load_setup详解

关于bootsect的主要功能和基础知识点就不多介绍了,这里我们主要是来记录下关于load_setup方法的主要功能和实现逻辑

bootsect中的load_setup方法的源码如下所示:

load_setup:
	mov	dx,#0x0000		// 设置DX寄存器为0x0000,选择驱动器0,磁头0
	mov	cx,#0x0002		// 设置CX寄存器为0x0002,选择扇区2,柱面0
	mov	bx,#0x0200		// 设置BX寄存器为0x0200,将数据读取到内存地址0x0200处
	mov	ax,#0x0200+SETUPLEN	// 设置AX寄存器为0x0200+SETUPLEN,调用BIOS中断服务2,读取多个扇区
	int	0x13			// 调用BIOS中断0x13读取磁盘
	jnc	ok_load_setup		// 如果没有错误,跳转到ok_load_setup标签
	mov	dx,#0x0000		// 重新设置DX寄存器为0x0000
	mov	ax,#0x0000		// 设置AX寄存器为0x0000,复位软盘驱动器
	int	0x13			// 调用BIOS中断0x13复位磁盘
	j	load_setup		// 加载setup

ok_load_setup:

// 获取磁盘驱动参数,特别是每轨扇区数

	mov	dl,#0x00             //将DL设置为0,表示驱动器号0(通常是软盘驱动器)
	mov	ax,#0x0800           //设置AX为0x0800,AH=0x08表示BIOS功能号是获取驱动器参数
	int	0x13                 //调用BIOS中断0x13以获取驱动器参数
	mov	ch,#0x00             //将CH设置为0,初始化CH寄存器
	seg cs                   // 使用代码段寄存器CS
	mov	sectors,cx           // 将CX中的值(每轨扇区数)存储到变量sectors中
	mov	ax,#INITSEG          // 将AX设置为INITSEG(初始化段地址)
	mov	es,ax                // 将额外段寄存器ES设置为AX的值


	mov	ah,#0x03             // 将AH设置为0x03,表示BIOS功能号是读取光标位置
	xor	bh,bh                // 将BH设置为0,表示页面号0
	int	0x10                 // 调用BIOS中断0x10以读取光标位置
	
	mov	cx,#24               // 将CX设置为24,表示字符串长度为24个字符
	mov	bx,#0x0007           // 将BX设置为0x0007,表示页面0和属性7(白色字符,黑色背景)
	mov	bp,#msg1             // 将BP设置为msg1标签的地址,msg1是将要显示的字符串
	mov	ax,#0x1301           // 将AX设置为0x1301,AH=0x13表示BIOS功能号是显示字符串并移动光标,AL=0x01是子功能号
	int	0x10
ok_load_setup:

// 获取磁盘驱动器参数,特别是每个磁道的扇区数

  mov dl,#0x00    // 设置DL寄存器为0x00,选择驱动器0
  mov ax,#0x0800    // 设置AX寄存器为0x0800,调用BIOS中断服务0x13的功能8,获取驱动器参数
  int 0x13      // 调用BIOS中断0x13获取驱动器参数
  mov ch,#0x00    // 将CH寄存器设置为0x00,清除高8位
  seg cs      // 加载CS段寄存器
  mov sectors,cx    // 将CX寄存器中的扇区数保存到sectors变量中
  mov ax,#INITSEG   // 设置AX寄存器为INITSEG
  mov es,ax     // 将ES段寄存器设置为AX的值

  mov ah,#0x03    // 设置AH寄存器为0x03,调用BIOS中断0x10的功能3,读取光标位置
  xor bh,bh     // 清除BH寄存器
  int 0x10      // 调用BIOS中断0x10读取光标位置
  
  mov cx,#24      // 设置CX寄存器为24,表示字符串长度
  mov bx,#0x0007    // 设置BX寄存器为0x0007,选择页面0,属性7(普通)
  mov bp,#msg1    // 设置BP寄存器为msg1字符串的地址
  mov ax,#0x1301    // 设置AX寄存器为0x1301,调用BIOS中断0x10的功能13,写字符串并移动光标
  int 0x10      // 调用BIOS中断0x10写字符串

// 好的,我们已经写完了消息,现在
// 我们要加载系统(到0x10000)

  mov ax,#SYSSEG    // 设置AX寄存器为SYSSEG
  mov es,ax   // 将ES段寄存器设置为AX的值,即0x010000
  call  read_it   // 调用read_it函数,读取系统
  call  kill_motor  // 调用kill_motor函数,关闭驱动器马达

// 在此之后,我们检查要使用哪个根设备。如果设备已定义(不为0),则不做任何操作,使用给定的设备。
// 否则,根据BIOS报告的当前扇区数选择/dev/PS0(2,28)或/dev/at0(2,8)。

  seg cs      // 加载CS段寄存器
  mov ax,root_dev   // 将root_dev的值移动到AX寄存器
  cmp ax,#0   // 比较AX寄存器与0
  jne root_defined  // 如果不等于0,跳转到root_defined
  seg cs      // 加载CS段寄存器
  mov bx,sectors    // 将sectors的值移动到BX寄存器
  mov ax,#0x0208    // 将AX寄存器设置为0x0208,对应于/dev/ps0 - 1.2Mb
  cmp bx,#15    // 比较BX寄存器与15
  je  root_defined  // 如果等于15,跳转到root_defined
  mov ax,#0x021c    // 将AX寄存器设置为0x021c,对应于/dev/PS0 - 1.44Mb
  cmp bx,#18    // 比较BX寄存器与18
  je  root_defined  // 如果等于18,跳转到root_defined
undef_root:
  jmp undef_root    // 无条件跳转到undef_root(死循环)
root_defined:
  seg cs      // 加载CS段寄存器
  mov root_dev,ax   // 将AX寄存器的值移动到root_dev

对于上面这段汇编程序,其实就是为了加载set_up程序到内存中,先进行mov指令,设置好相关的寄存器值,比如设置AH寄存器,AH寄存器是 x86 处理器架构中一个寄存器的高字节部分,它在 BIOS 中断调用中用来指定服务或功能号。不同的功能号会执行不同的服务。比如,调用视频服务中断 0x10 时,通过设置 AH 的不同值来选择具体的功能,如设置视频模式、读取光标位置等。

也就是说,不同的中断号中,可以通过设置不同的AH寄存器的AH值来让当前中断执行不同的功能,具体的映射关系在intel的开发者手册中可以找到。笔记最后面列举了一些简单的。

int 0x10 就是interrupt的意思,触发中断请求。所以实际上这部分代码也就是汇编层面的传参和执行对应的功能程序。

另外,load_setup方法中,最后我们看到的j load_setup,其实就是jmp load_setup的缩写,也就是无条件执行load_setup标签处的代码,这不是循环执行,而是前面有一个异常捕获逻辑,如果出现异常,比如在读取磁盘失败时执行(当 int 0x13 调用返回时 CF 标志设置为 1 表示错误),目的是重置磁盘系统并重新尝试加载 setup 部分。这样可以应对可能的软盘驱动器错误,通过复位操作来恢复正常工作状态,并再次尝试读取所需的数据。总的来说,这段代码是为了在读取磁盘失败时进行错误处理,通过复位磁盘系统并重新尝试加载 setup,确保系统能够正确读取启动所需的数据。

然后在最后,也就是下面这段代码

mov ax,#SYSSEG    // 设置AX寄存器为SYSSEG
  mov es,ax   // 将ES段寄存器设置为AX的值,即0x010000
  call  read_it   // 调用read_it函数,读取系统

bootsect加载了操作系统程序最后的240扇区的system模块的代码到内存,至此,操作系统的所有代码都已经加载完毕。整个操作系统代码都已经加载到内存了。

二.常用的 BIOS 中断及 AH 功能号

视频服务(中断 0x10)

磁盘服务(中断 0x13)

键盘服务(中断 0x16)

获取内存大小(中断 0x1A)