3

自己动手从零写桌面操作系统GrapeOS系列教程——24.加载并运行loader - 成宇佳

 1 year ago
source link: https://www.cnblogs.com/chengyujia/p/17255709.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

学习操作系统原理最好的方法是自己写一个简单的操作系统。


之前我们在电脑的启动过程中介绍过boot程序的主要任务就是加载并运行loader程序,本讲我们就来实现。
本讲代码文件共2个:

  • boot.asm
  • loader.asm

一、代码及讲解

本讲所用到的知识点都是之前已经用过的,只是在本讲中综合应用了一下。
关于如何读取文件在上一讲中已经介绍过了,我们只要在上讲代码中把要读取的文件名改成loader的文件名"LOADER  BIN"即可读取loader程序文件。
本讲的boot.asm就是在上讲的基础上稍微改了下,加了3处提示语句。程序一开始先清屏并在屏幕上输出字符串“GrapeOS boot start.”。然后从硬盘根目录查找LOADER.BIN程序文件,如果没有找到文件则在屏幕上输出字符串“Loader not found.”,如果找到了文件则在屏幕上输出字符串“Loader found.”。如果找到了文件则读取文件内容,读取完后通过jmp指令跳转到loader在内存中的起始地址,这样就完成了加载并运行loader。
boot.asm中的代码如下:

;--------------------定义常量--------------------
;FAT16目录项中各成员的偏移量:
;名称                 偏移   长度    描述
DIR_Name        equ    0     ;11    文件名8B,扩展名3B
DIR_Attr        equ    11    ;1     目录项属性
;Reserved       equ    12    ;10    保留位
DIR_WrtTime     equ    22    ;2     最后一次写入时间
DIR_WrtDate     equ    24    ;2     最后一次写入日期
DIR_FstClus     equ    26    ;2     起始簇号
DIR_FileSize    equ    28    ;4     文件大小

BOOT_ADDRESS equ 0x7c00 ;boot程序加载到内存的地址。
DISK_BUFFER  equ 0x7e00 ;读磁盘临时存放数据用的缓存区,放到boot程序之后。
DISK_SIZE_M equ 4 ;磁盘容量,单位M。
FAT1_SECTORS     equ 32 ;FAT1占用扇区数
ROOT_DIR_SECTORS equ 32 ;根目录占用扇区数
SECTOR_NUM_OF_FAT1_START     equ 1 ;FAT1表起始扇区号
SECTOR_NUM_OF_ROOT_DIR_START equ 33 ;根目录区起始扇区号
SECTOR_NUM_OF_DATA_START     equ 65 ;数据区起始扇区号,对应簇号为2。
SECTOR_CLUSTER_BALANCE       equ 63 ;簇号加上该值正好对应扇区号。
FILE_NAME_LENGTH     equ 11 ;文件名8字节加扩展名3字节共11字节。
DIR_ENTRY_SIZE       equ 32 ;目录项为32字节。
DIR_ENTRY_PER_SECTOR equ 16 ;每个扇区能存放目录项的数目。

LOADER_ADDRESS          equ 0x1000          ;loader程序加载到内存的地址。
STACK_BOTTOM            equ LOADER_ADDRESS  ;栈底地址(把栈放到loader程序前面)
VIDEO_SEGMENT_ADDRESS   equ 0xb800          ;显存的段地址(默认显示模式为25行80列字符模式)
VIDEO_CHAR_MAX_COUNT    equ 2000            ;默认屏幕最多显示字符数。

;--------------------MBR开始--------------------
org BOOT_ADDRESS
jmp boot_start
nop

;FAT16参数区:
BS_OEMName 	db 'GrapeOS '   ;厂商名称(8字节,含空格)
BPB_BytesPerSec dw 0x0200	;每扇区字节数
BPB_SecPerClus	db 0x01		;每簇扇区数
BPB_RsvdSecCnt	dw 0x0001	;保留扇区数(引导扇区的扇区数)
BPB_NumFATs	db 0x01		;FAT表的份数
BPB_RootEntCnt	dw 0x0200	;根目录可容纳的目录项数
BPB_TotSec16	dw 0x2000 	;扇区总数(4MB)
BPB_Media	db 0xf8 	;介质描述符
BPB_FATSz16	dw 0x0020	;每个FAT表扇区数
BPB_SecPerTrk	dw 0x0020	;每磁道扇区数
BPB_NumHeads	dw 0x0040	;磁头数
BPB_hiddSec	dd 0x00000000	;隐藏扇区数
BPB_TotSec32	dd 0x00000000	;如果BPB_TotSec16是0,由这个值记录扇区数。
BS_DrvNum	db 0x80		;int 13h的驱动器号
BS_Reserved1	db 0x00		;未使用
BS_BootSig	db 0x29		;扩展引导标记
BS_VolID	dd 0x00000000   ;卷序列号
BS_VolLab	db 'Grape OS   ';卷标(11字节,含空格)
BS_FileSysType	db 'FAT16   '	;文件系统类型(8字节,含空格)

;通过以上参数可知硬盘容量为4MB,共8K个扇区。扇区具体分布如下:
;区域名     扇区数      扇区号          字节偏移            说明
;引导扇区   1个扇区     扇区0           0x0000~0x01ff
;FAT1表     32个扇区    扇区1~32        0x0200~0x41ff   可记录8K-2个簇
;FAT2表     无          无              无              无
;根目录区   32个扇区    扇区33~64       0x4200~0x81ff   可容纳512个目录项
;数据区     8127个扇区  扇区65~0x1fff   0x8200~0x3fffff

;--------------------程序开始--------------------
boot_start:
;初始化寄存器
mov ax,cs
mov ds,ax
mov es,ax ;cmpsb会用到ds:si和es:di
mov ss,ax
mov sp,STACK_BOTTOM
mov ax,VIDEO_SEGMENT_ADDRESS
mov gs,ax ;本程序中gs专用于指向显存段

;清屏
call func_clear_screen

;打印字符串:"GrapeOS boot start."
mov si,boot_start_string
mov di,0 ;在屏幕第1行显示
call func_print_string

;读取loader文件开始
;读取根目录的第1个扇区(1个扇区可以存放16个目录项,我们用到的文件少,不会超过16个。)
mov esi,SECTOR_NUM_OF_ROOT_DIR_START 
mov di,DISK_BUFFER
call func_read_one_sector

;在16个目录项中通过文件名查找文件
cld ;cld将标志位DF置0,在串处理指令中控制每次操作后让si和di自动递增。std相反。下面repe cmpsb会用到。
mov bx,0 ;用bx记录遍历第几个目录项。
next_dir_entry:
mov si,bx
shl si,5 ;乘以32(目录项的大小)
add si,DISK_BUFFER              ;源地址指向目录项中的文件名。
mov di,loader_file_name_string  ;目标地址指向loader程序在硬盘中的正确文件名。
mov cx,FILE_NAME_LENGTH ;字符比较次数为FAT16文件名长度,每比较一个字符,cx会自动减一。
repe cmpsb ;逐字节比较ds:si和es:di指向的两个字符串。
jcxz loader_found ;当cx为0时跳转。cx为0表示上面比较的两个字符串相同。找到了loader文件。
inc bx
cmp bx,DIR_ENTRY_PER_SECTOR
jl next_dir_entry ;检查下一个目录项。
jmp loader_not_found ;没有找到loader文件。

loader_found:
;打印字符串:"Loader found."
mov si,loader_found_string
mov di,80 ;在屏幕第2行显示
call func_print_string

;从目录项中获取loader文件的起始簇号
shl bx,5 ;乘以32
add bx,DISK_BUFFER
mov bx,[bx+DIR_FstClus] ;loader的起始簇号

;读取FAT1表的第1个扇区(我们用到的文件少且小,只用到了该扇区中的簇号)
mov esi,SECTOR_NUM_OF_FAT1_START 
mov di,DISK_BUFFER ;放到boot程序之后
call func_read_one_sector

;按簇读loader
mov bp,LOADER_ADDRESS ;loader文件内容读取到内存中的起始地址
read_loader:
xor esi,esi ;esi清零
mov si,bx ;簇号
add esi,SECTOR_CLUSTER_BALANCE
mov di,bp
call func_read_one_sector
add bp,512 ;下一个目标地址

;获取下一个簇号(每个FAT表项为2字节)
shl bx,1 ;乘2,每个FAT表项占2个字节
mov bx,[bx+DISK_BUFFER]

;判断下一个簇号
cmp bx,0xfff8 ;大于等于0xfff8表示文件的最后一个簇
jb read_loader ;jb无符号小于则跳转,jl有符号小于则跳转。

read_loader_finish: ;读取loader文件结束
jmp LOADER_ADDRESS ;跳转到loader在内存中的地址

loader_not_found: ;没有找到loader文件。
;打印字符串:"Loader not found."
mov si,loader_not_found_string
mov di,80 ;在屏幕第2行显示
call func_print_string

stop:
hlt
jmp stop

;清屏函数(将屏幕写满空格就实现了清屏)
;输入参数:无。
;输出参数:无。
func_clear_screen:
mov ah,0x00 ;黑底黑字
mov al,' '  ;空格
mov cx,VIDEO_CHAR_MAX_COUNT ;循环控制
.start_blank:
mov bx,cx ;以下3行表示bx=(cx-1)*2 
dec bx
shl bx,1
mov [gs:bx],ax ;[gs:bx]表示字符对应的显存地址(从屏幕右下角往前清屏)
loop .start_blank
ret

;打印字符串函数。
;输入参数:ds:si,di。
;输出参数:无。
;ds:si 表示字符串起始地址,以0为结束符。
;di 表示字符串在屏幕上显示的起始位置(0~1999)。
func_print_string:
mov ah,0x07 ;ah表示字符属性,0x07表示黑底白字。
shl di,1 ;乘2(屏幕上每个字符对应2个显存字节)。
.start_char:
mov al,[si]
cmp al,0
jz .end_print
mov [gs:di],ax ;将字符和属性放到对应的显存中。
inc si
add di,2
jmp .start_char
.end_print:
ret

;读取硬盘1个扇区(主硬盘控制器主盘)
;输入参数:esi,ds:di。
;esi LBA扇区号
;ds:di 将数据写入到的内存起始地址
;输出参数:无。
func_read_one_sector:
;第1步:检查硬盘控制器状态
mov dx,0x1f7
.not_ready1:
nop ;nop相当于稍息 hlt相当于睡觉
in al,dx ;读0x1f7端口
and al,0xc0 ;第7位为1表示硬盘忙,第6位为1表示硬盘控制器已准备好,正在等待指令。
cmp al,0x40 ;当第7位为0,且第6位为1,则进入下一个步。
jne .not_ready1 ;若未准备好,则继续判断。
;第2步:设置要读取的扇区数
mov dx,0x1f2
mov al,1
out dx,al ;读取1个扇区
;第3步:将LBA地址存入0x1f3~0x1f6
mov eax,esi
;LBA地址7~0位写入端口0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位写入端口写入0x1f4
shr eax,8
mov dx,0x1f4
out dx,al
;LBA地址23~16位写入端口0x1f5
shr eax,8
mov dx,0x1f5
out dx,al
;第4步:设置device端口
shr eax,8
and al,0x0f ;LBA第24~27位
or al,0xe0 ;设置7~4位为1110,表示LBA模式,主盘
mov dx,0x1f6
out dx,al
;第5步:向0x1f7端口写入读命令0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第6步:检测硬盘状态
.not_ready2:
nop ;nop相当于稍息 hlt相当于睡觉
in al,dx ;读0x1f7端口
and al,0x88 ;第7位为1表示硬盘忙,第3位为1表示硬盘控制器已准备好数据传输。
cmp al,0x08 ;当第7位为0,且第3位为1,进入下一步。
jne .not_ready2 ;若未准备好,则继续判断。
;第7步:从0x1f0端口读数据
mov cx,256 ;每次读取2字节,一个扇区需要读256次。
mov dx,0x1f0
.go_on_read:
in ax,dx
mov [di],ax
add di,2
loop .go_on_read
ret

loader_file_name_string:db "LOADER  BIN",0  ;loader程序在硬盘中存储的文件名,共11个字节,含空格。
boot_start_string:db "GrapeOS boot start.",0
loader_not_found_string:db "Loader not found.",0
loader_found_string:db "Loader found.",0

times 510-($-$$) db 0
db 0x55,0xaa

目前我们的loader程序非常简单,只是向屏幕输出一行字符串“GrapeOS loader start.”。
loader.asm中的代码如下:

org 0x1000

;打印字符串:"GrapeOS loader start."
mov si,loader_start_string
mov di,160 ;屏幕第3行显示
call func_print_string

stop:
hlt
jmp stop

;打印字符串函数
;输入参数:ds:si,di。
;输出参数:无。
;si 表示字符串起始地址,以0为结束符。
;di 表示字符串在屏幕上显示的起始位置(0~1999)
func_print_string:
mov ah,0x07 ;ah 表示字符属性 黑底白字
shl di,1 ;乘2(屏幕上每个字符对应2个显存字节)
.start_char: 
mov al,[si]
cmp al,0
jz .end_print
mov [gs:di],ax
inc si
add di,2
jmp .start_char
.end_print:
ret

loader_start_string:db "GrapeOS loader start.",0

二、程序演示

编译boot和loader:

[root@CentOS7 Lesson24]# nasm boot.asm -o boot.bin
[root@CentOS7 Lesson24]# nasm loader.asm -o loader.bin

将boot写入到虚拟硬盘的第一个扇区:

[root@CentOS7 Lesson24]# dd conv=notrunc if=boot.bin of=/media/VMShare/GrapeOS.img

运行QEMU:

C:\Users\CYJ>qemu-system-i386 d:\GrapeOS\VMShare\GrapeOS.img

运行截图如下:

343777-20230325214227441-939371169.png

上面截图上显示“Loader not found.”,因为loader.bin文件还没有放入虚拟硬盘里,下面我们来放入。

[root@CentOS7 Lesson24]# mount /media/VMShare/GrapeOS.img /mnt/ -t msdos -o loop
[root@CentOS7 Lesson24]# cp loader.bin /mnt/
[root@CentOS7 Lesson24]# sync
[root@CentOS7 Lesson24]# umount /mnt/

重新运行QUME,截图如下:

343777-20230325214240810-1906312682.png

上面截图中显示“GrapeOS loader start.”,说明已成功加载并运行loader。


视频版地址:https://www.bilibili.com/video/BV1KV4y197sa/
配套的代码与资料在:https://gitee.com/jackchengyujia/grapeos-course
GrapeOS操作系统交流QQ群:643474045


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK