@[TOC]Boot/bootsect.S
以下代码是从吴成兵老师整理的Linux0.11的源码中,移植过来的。如果有侵权的地方,可以通知我,然后我删除。如果没有问题的话,以后文章将不再重复说明来源。
因为我现在不会使用优快云的汇编格式块,所以使用C语言的格式来说明代码。其次,我会删除一些源代码中的一些英文注释,只保留代码部分。
如果有任何问题也可以在优快云询问我(我是真小白。),或者私聊我QQ:705318074。好的废话不多说直接开始吧
SYSSIZE = 0x3000
当时林纳斯认为0x3000也就是192KB(这里有一个问题,因为在文章中写的是196KB,而实际上应该是192KB,我不知道原因,我将按照192KB来概述),对于当时的linux系统来说是足够了的。注意这个SYSSIZE是段地址,所以需要向左偏移16位,否则会得到12KB错误的结果
/*
*这里是汇编的格式,定义了几个全局标识符。
*/
.globl begtext, begdata, begbss, endtext, enddata, ednbss
.text
begtext:
.data
begdata:
.bss
begbss:
/*
*下面是系统加载时的一些参数
*SETUPLEN是setup.s这个文件编译后所占据的扇区数:4,也就是4*512Byte
*BOOTSEG是CPU上电以后BIOS将读取到的正确的引导程序复制到内存的段地址
*INITSEG是bootsect.s后面被复制的地址,也就是说bootsect.s会被复制到0x90000这个地址
*SETUPLEN是setup.s会被加载的地方,刚好0x200是512byte,所以没有空袭。
*SYSSEG是系统会被加载到的地址
*ENDSEG是指示系统在何处停止加载。
*/
SETUPLEN = 4
BOOTSEG = 0x07C0
INITSEG = 0x9000
SETUPSEG = 0x9020
SYSSEG = 0x1000
ENDSEG = SYSSEG + SYSSIZE
ROOT_DEV = 0x306
ROOT_DEV这个参数有点复杂,等到了实际引用的地方再详细讲一讲。现在放下,只需要了解这个是一个启动盘的标识。
/*
*这段代码是将从0x07C00处复制512Byte到0x90000处,也就是把bootsect.s文件复制到0x90000处
*因为x86汇编语言中不允许直接对段寄存器赋立即数,所以使用AX寄存器来当媒介
*在x86中,DS:SI指向数据源地址,ES:DI指向数据目标地址,复制次数在CX寄存器中保存。
*sub是减法指令,也就是将SI和DI置零
*rep是重复执行这条指令下面的指令,重复次数在CX中保存,执行一遍以后CX会自减。
*movw是指一次移动一个字(word)。
*go是一个相对偏移,也就是只是相对于start这个标识的偏移。因此jmpi go, INITSEG这个语句是这么理解
*的,其中go是赋予ip的数值,是段内偏移,INITSEG是段地址。
*因为即使在0x07C00处复制完以后,程序会执行到0x07C00:go处执行,因为上面的代码已经将整个程序复制
*到了0x90000处,所以跳转到0x9000:go处执行是一样的,
*/
entery 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
/*
*这段代码使DS和ES段寄存器变成当前CS指向的段地址了,也就是0x9000
*/
go:
mov AX, CS
mov DS, AX
mov ES, AX
/*
*这两句代码定义了引导程序临时栈的位置也就是SS:SP(0x9000:0xFF00),linus觉得栈要远大于512字节,
*之所以称为临时栈,是因为在后面执行中,这个位置回事system所在位置,所以肯定会被覆盖的。
*/
mov SS, AX
mov SP, #0xFF00
/*
*下面这段代码负责加载setup.s这个文件,需要调用BIOS提供的0x13号中断,
*AH中保存着13号中断的子程序编号,02h也就是读扇区。
*AL中保存着扇区数
*CH是柱面,CL是起始扇区
*DH是磁头,
*DL是驱动器,00h~7Fh是软盘,80h~FFh是硬盘
*这里要说明一下,在我们所有编程中计数都是从0开始的,但是在扇区中却是从1开始的,
*这就是为什么CL是2而不是1的原因。
*因为读扇区成功后,CF标识位为0,AL为读出的扇区数。
*读成功就跳转到ok_load_setup处执行
*否则复位驱动器,然后重新读,毕竟现在系统都没有起来,什么都解决不了。
*/
load_setup:
mov DX, #0x0000
mov CX, #0x0002
mov BX, #0x0200
mov AX, #0x0200 + SETUPLEN
int 0x13
jnc ok_load_setup
mov DX, #0x0000
mov AX, #0x0000
int 0x13
j load_setup
/*
*下面这段得到磁盘驱动器的参数,
*0x13号中断的08H功能,
*CH保存着柱面低8位, CL的位7-6为柱面的高2位,CL的5-0为扇区数
*seg会影响下面的一条语句,也就是
*mov CS:[sectors], CX,
*在sectors这个变量中保存了一个柱面的扇区个数。
*因为这个功能也会更改ES段寄存器,所以要更改回来
*/
ok_load_setup:
mov DL, #0x00
mov AX, #0x0800
int 0x13
mov CH, #0x00
seg cs
mov sectors, CX
mov AX, #INITSEG
mov ES, AX
至于0x13号中断有什么其他功能,为什么会改变ES段寄存器,有兴趣的可以自己去学习,我这里给一个参考网址:0x13号中断介绍
/*
*这段代码是读取显示器的坐标信息。
*使用03h功能,读光标位置,BH=页号,DH=行,DL=列
*/
mov AH, #0x03
xor BH, BH
int 0x10
/*
*这段代码是向屏幕输出Loading ststem ...
*13h号功能是显示字符串
*CX=字符串长度,BH=页号,BL=属性
*ES:BP=字符串地址
*AL设置光标属性,这里是光标跟随移动
*/
mov CX, #24
mov BX, #0007
mov BP, #msg1
mov AX, #0x1301
int 0x10
从这里开始,代码变的有一点难度了,我尽量写的通俗易懂。
/*
*因为上面写字符串修改过ES段寄存器,所以在使用的时候需要重新赋值。
*在一切都准备好以后,开始加载system了
*读取完成以后停止驱动器马达
*/
mov AX, #SYSSEG
mov ES, AX
call read_it
call kill_motor
/*
*这里判断启动盘是什么类型的软盘,360K, 1.2Mb, 1.44Nb
*在CS:[root_dev]中保存了磁盘驱动器类型,将这个值传给AX
*如果AX不等于0,也就是说root_dev已经被定义了,启动盘已经被设置了,什么都不动
*直接跳转到root_defined处
*如果AX为0,说明驱动器没有定义,然后我们就需要判断这个驱动器是什么类型的了,因为当时linus只有
*软盘,所以判断了常用的两种软盘。
*先读1.2Mb类型的软盘参数到AX中,然后将CS:[sectors]所保存的驱动器扇区数传入BX
*判断BX是否等于15,如果相等,说明这个磁盘驱动器是1.2Mb的磁盘,跳转到root_defined处执行
*否则传入1.44Mb软盘参数到AX中,判断扇区数是否为18,是就跳转到root_defined处执行
*如果都不是,那就不知道怎么办了,就让他在这里死循环死机吧
*/
seg CS
mov AX, root_dev
cmp AX, #0
jne root_defined
seg CS
mov BX, sectors
mov AX, #0x0208
cmp BX, #15
je root_defined
mov AX, #0x021C
cmp BX, #18
je root_defined
undef_root:
jmp undef_root
/*
*执行到这里,说明驱动器是已知了的,参数保存在AX中,所以我们需要保存驱动器参数
*/
root_defined:
seg CS
mov root_dev, AX
/*
*到这里来,bootsect.s的任务已经完成了,剩下的任务就让setup.s来完成了
*/
jmpi 0, SETUPSEG
大头来了,这里是一大堆重复头疼的环节。
sread: .word 1+ SETUPLEN
head: .word 0
track: .word 0
本来准备再写一点的,但是发现后面的是一个整体,所以就不分开了,直接一个整体放到下一篇文章去。