自己动手写操作系统 第四章总结
先大体对程序流程有一个认识
%define _BOOT_DEBUG_
%ifdef _BOOT_DEBUG_
org 0100h
%else
org 07c00h ;告诉编译器程序将要载入到这里
%endif
%ifdef _BOOT_DEBUG_
BaseOfStack equ 0100h ;栈底,栈向低地址生长
%else
BaseOfStack equ 07c00h ;
%endif
;事实上从这里開始都是磁盘的头
jmp short LABEL_START
nop ;这个nop为什么不可缺少!!!!!!!!!!!!!!
;原因看书上p126上的引导扇区的格式,能够看到開始处必须是一个跳转指令
;jmp LABEL_START
;nop
;接下来是fat12磁盘的头,作用是在boot sector的開始出,注意这里的位置是哪里
BS_OEMName DB 'ForrestY' ;OEM String,产商名字
BPB_BytesPerSec DW 512 ; 每扇区的字节数
BPB_SecPerClus DB 1 ;
BPB_RsvdSecCnt DW 1 ;Boot记录多少扇区
BPB_NumFATs DB 2 ;共同拥有多少FAT表
BPB_RootEntCnt DW 224 ;根文件夹文件数最大值
BPB_TotSec16 DW 2880 ;逻辑扇区总数
BPB_Media DB 0xF0 ;媒体描写叙述符
BPB_FATSz16 DW 9 ;每FAT扇区数
BPB_SecPerTrk DW 18 ;每磁道扇区数
BPB_NumHeads DW 2 ;磁头数
BPB_HiddSec DD 0 ;隐藏扇区数
BPB_TotSec32 DD 0
BS_DrvNum DB 0
BS_Reserved1 DB 0
BS_BootSig DB 29h
BS_VolID DD 0
BS_VolLab DB 'Tinix0.01 '
BS_FileSysType DB 'Fat12 '
LABEL_START:
mov ax,cs ;
mov ds,ax ; 初始化数据段
mov es,ax ;初始化es 为这个段的事实上,由于用到int 10h中断须要es:bp作为串的偏移地址
mov ss, ax
mov sp, BaseOfStack
;清屏 int 10h
mov ax, 0600h
mov bx, 0700h
mov cx, 0
mov dx, 0184fh
int 10h
;因为使用了堆栈,所以这里要初始化堆栈
; call DispStr
; jmp $
;从这里開始是在A盘根文件夹寻找loader.bin的程序
xor ah, ah
xor dl, dl
int 13h
mov word [wSectorNo], SectorNoOfRootDirectory ;当中wsectorno是一个变量,当中存放的是根文件夹的第一个扇区号,这是一个常量19,同一时候RootDirSectors是根文件夹占用空间,这里是14,这些值都是開始定义了的
LABEL_SEARCH_IN_ROOT_DIR_BEGIN: ;要找一个文件名称就在根文件夹下寻找即可了,从这里開始就像do{}while開始循环了
cmp word [wRootDirSizeForLoop], 0 ;这里wrootdirsizeforloop是一个变量,初始值为上面那个14
jz LABEL_NO_LOADERBIN ;假设为零,那么就是到了循环结束还没找到文件,跳转到没找到那里,结束
dec word [wRootDirSizeForLoop] ;假设运行到了这里说明还没循环结束,那么就让循环索引减一
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader ;这里BaseOfLoader:OffsetOfLoader是调用readsector函数的时候的缓存取,也就是中断int 13h的缓存区es:bx,每个扇区的内容,都将临时保存到这里
mov ax, [wSectorNo]
mov cl, 1 ;这两个是函数readsector的參数,ax代表逻辑扇区号,cl代表要读的扇区数为1
mov si, LoaderFileName ; ds:si当中数据段ds被初始化为本程序開始处所以ds:si就是"Loader bin"的字符串位置
mov di, OffsetOfLoader ; es和ds都被初始化为了一样的,也就是程序段的開始处, 如今准备比較offsetofloader中的值了,可是这里baseofloader:offsetofloader是一整个扇区,所以等一下还须要提取出当中的文件名称才行,注意查看书128也的根文件夹条目结构Dir_Name的位置,事实上不用提取,由于偏移量为零,比較前11个字节旧能够了
cld
mov dx, 10h ;每一个扇区512字节,每一个根文件夹32字节,所以一共同拥有512/32=16个文件夹入口,这个dx是为接下来的循环做准备,要遍历整个扇区的根文件夹
LABEL_SEARCH_FOR_LOADERBIN:
cmp dx, 0
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
dec dx ;假设没有读完一个扇区就继续读下一个文件夹
mov cx, 11 ;规定文件名称在文件夹入口偏移处为0的前11个字节里
LABEL_CMP_FILENAME:
cmp cx, 0
jz LABEL_FILENAME_FOUND ;假设循环能运行11次,说明找到了文件名称,假设文件名称不匹配是运行不了11次的
dec cx
lodsb ;作用,把ds:si的值写到al中,接下来和es:di中的字符比較,这两个ds es分别存了待比較的字符串,这个指令会导致si自增或自减,可是不会改变di的值,所以要在LABEL_GO_ON中手动更改
cmp al, byte [es:di]
jz LABEL_GO_ON ;假设相等,继续比較
jmp LABEL_DIFFERENT; 假设字符不匹配,则跳出循环
LABEL_GO_ON:
inc di
jmp LABEL_CMP_FILENAME ;因为lodsb仅仅改动了si的值,所以我们须要手动改动di的值
LABEL_DIFFERENT:
and di, 0FFE0h
add di, 20h;这两步是为了让,di指向下一文件夹条目
mov si, LoaderFileName; 让si知会字符串文件名称開始处
jmp LABEL_SEARCH_FOR_LOADERBIN;假设字符布匹配,那么跳回到下个文件夹入口開始运行
LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:;假设一个扇区读完了,那么旧跳到下一个扇区去,知道循环完所有的14个扇区
add word [wSectorNo], 1
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN
LABEL_NO_LOADERBIN:
mov dh, 2 ;dh是DispStr的參数,相当于字符串数组,用数字来索引字符串
Call DispStr
%ifdef _BOOT_DEBUG_
mov ax, 4c00h
int 21h
%else
jmp $
%endif
LABEL_FILENAME_FOUND:
mov ax, RootDirSectors ;在接下来的某行里实用,在deltasector那一行
and di, 0FFE0h ;di原来指向es:di中的文件名称字符串的结尾处,这里两个语句先是让di,指向第一个字符处,add 01ah后指向偏移26字节的地方,也就是fistcul第一个簇,在数据区中
add di, 01Ah
mov cx, word [es:di] ;把两字节的长度赋值到cx中,也就是loader.bin在数据区的第一个扇区,注意这个并非逻辑扇区,不过簇
push cx
add cx, ax
add cx, DeltaSectorNo ;deltasector是17用来计算该扇区的位置,如今cx中存的是逻辑扇区号了
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader ;es:bx如今是之前那个baseofloader:offsetofloader的位置了,用完了,能够覆盖掉了
mov ax, cx ;ax如今是第一个数据区的逻辑扇区号
LABEL_GOON_LOADING_FILE: ;有了loader.bin的第一个逻辑扇区号,如今能够開始载入进内存中了
push ax ;暂存ax,bx由于要调用int 10h须要这两个寄存器
push bx
mov ah, 0Eh
mov al, '.'
mov bl, 0Fh
int 10h
pop bx
pop ax ;一用完旧恢复这两个寄存器的值,如今ax中的是loader.bin的第一个扇区号
mov cl, 1 ;參数ax,是逻辑扇区号,cl是要读几个扇区
call ReadSector ;调用readsector来读取该扇区的内容,缓冲区为es:bx,刚刚已经初始化过了
;如今開始读取fatentry
pop ax
call GetFATEntry
cmp ax, 0FFFh ;假设是0fffh旧说明这是最后一簇了
jz LABEL_FILE_LOADED ;读取完就跳出循环
push ax ;还没有读取完loader.bin,这里保存是给下一轮循环的getfatentry作为參数的,这里保存了下一个entry的数值
mov dx, RootDirSectors ;同一时候这个entry不仅是下一个entry的位置,同一时候也是下一个数据所在的簇号(相应一个扇区,算法为逻辑扇区=簇号+ RootDirSectors + SectorNoofRootDirectory - 2),在这里也就是ax + 14 + 17,
add ax, dx
add ax, DeltaSectorNo ;到这里旧得到了数据区的扇区,供下一次的readsector来使用
add bx, [BPB_BytesPerSec] ;bx+512指向下一个空白的地方来存放下一个数据扇区,初始值是offsetofloader
jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED: ;假设读取完了loader.bin,那么就让程序跳转到该内存进行指令的运行,如今就開始将控制权交给loader.bin运行了!注意loader.bin就是一个可运行的,里面都是可运行的,所以直接跳转到我们之前用readsector载入到的内存地方就能够了(这个内存是es:bx, 且通过add, [BPB_BytesPerSec], 一直在增长)
mov dh, 1
call DispStr
jmp BaseOfLoader:OffsetOfLoader ;这个内存如今放的就是loader.binn的程序,如今相当于在运行loader.bin中的代码了
;这个函数用来读取8+4=12结构的fatentry,但眼下给出的是簇数,这个fatentry的内容既是当下一个fatentry的位置,也是相应数据取数据的簇号,也就是第几个12结构,等下要依据奇偶性得出这个12位的入口,可是如今不知道,这个12位的东西也不过存了下一个loader.bin的文件的下一个扇区这个的信息是包括同一文件的下一个扇区的簇数,这个簇数还要计算才干得到逻辑扇区
GetFATEntry:
push es
push bx
push ax ;es:bx原来的值baseofloader:offsetofloader,ax原来的值是待读取的fatentry也就是8+4=12的那个值,存在fat表中的信息
mov ax, BaseOfLoader
sub ax, 0100h
mov es, ax ;在baseofloader后面留出4Kb的空间,用来存放fat?什么fat?fat表还是什么东西
pop ax ;这是ax中的还是待读取的fatentry项,的簇数不是12位的
mov byte [bOdd], 0
mov bx, 3
mul bx ;这以后ax的值变了,
mov bx, 2
div bx ;ax的值变成了该fatentry在fat中的偏移量
cmp dx, 0 ;这里dx是dx:ax * 3 /2 的余数,用来推断ax是奇数还是偶数
jz LABEL_EVEN
mov byte [bOdd], 1 ;推断完后ax的值还是没变的,如今知道了奇偶性,也知道了是第几个fatentry,能够開始找到这个fatentry了
LABEL_EVEN: ;假设是偶数个
xor dx, dx
mov bx, [BPB_BytesPerSec] ;每扇区字节数512
div bx ;ax是所在扇区号,dx是在该扇区的偏移量
push dx
mov bx, 0 ;es:bx = baseofloader -100: 00
add ax, SectorNoOfFat1 ;ax就是fatentry所在的扇区号了,sectorNooffat1是fat1表所在的第一个扇区号,也就是1
mov cl, 2 ;一次读取两个扇区,由于fat可能会占用两个扇区
call ReadSector
pop dx ;dx中存放的是在该扇区的偏移量
add bx, dx ;ex:bx就是该entry所在的偏移量
mov ax, [es:bx] ;把entry所在的字读取出来
cmp byte [bOdd], 1 ;依据奇偶进行fatentry内容的筛选
jnz LABEL_EVEN_2 ;假设是奇数旧跳转
shr ax, 4 ;假设是偶数,仅仅取前12位,注意书上的图p130是有错误的,所以获得entry的方法事实上不是非常难~
LABEL_EVEN_2: ;也就是说是奇数
and ax, 0FFFFh ;仅仅取后12位
LABEL_GET_FAT_ENTRY_OK:
pop bx
pop es
ret
;刚刚用到的常量
BaseOfLoader equ 09000h ;用int 13h得到的缓存区,用来暂存扇区
OffsetOfLoader equ 0100h
DeltaSectorNo equ 17 ;用来计算簇号和扇区的关系的
RootDirSectors equ 14 ;根文件夹占用扇区个数
SectorNoOfRootDirectory equ 19 ;根文件夹開始的扇区数
SectorNoOfFat1 equ 1
;变量
wRootDirSizeForLoop dw RootDirSectors ;控制循环次数的变量
wSectorNo dw 0 ;要读的扇区号
bOdd db 0 ;奇数还是偶数
;字符串
LoaderFileName db "LOADER BIN", 0 ;字符串以0结尾,假设要用,可是为什么要以零结尾呢?
MessageLength equ 9
BootMessage: db "Booting "
Message1 db "Ready. "
Message2 db "No LOADER"
DispStr:
mov ax, MessageLength
mul dh ;字符串长度是固定的,索引乘以长度就是字符串的开头
add ax, BootMessage ;Booting,也就是字符串数组的開始位置
mov bp, ax
mov ax, ds ;ds被初始化为本程序的開始处
mov es, ax ;这三个语句确定了串的地址,如今es是程序開始, bp是该字符串開始到该位置的偏移量 es:bp,就是串的地址
;从这里開始为中断int 10准备參数
mov cx, MessageLength ;cx是串的长度
mov ax,01301h
mov bx,0007ch
mov dl,0
int 10h
ret
;要载入loader进入内存必需要读取软盘,由于loader.bin是直接复制到软盘数据区的,所以如今写一个读取软盘的函数,这里仅仅是读软盘数据,依据逻辑扇区读取,等下要写一个寻找loader.bin文件位置的函数
ReadSector:
push bp
mov bp, sp
sub esp, 2
mov byte [bp - 2], cl
push bx
mov bl, [BPB_SecPerTrk]
div bl
inc ah
mov cl, ah
mov dh, al
shr al, 1
mov ch, al
and dh, 1
pop bx
mov dl, [BS_DrvNum]
.GoOnReading:
mov ah, 2
mov al, byte [bp - 2]
int 13h
jc .GoOnReading
add esp, 2
pop bp
ret
times 510-($-$$) db 0
dw 0xaa55