自制操作系统_01_使用汇编操作硬盘读写
注意, in和out执行, 需要两个操作数都是寄存器, 比如
in al, 0x70f
是不可以的
; 声明这段代码的位置运行时会在0x7c00, 直接取址会加上0x7c00
; xchg bx, bx ; bochs 的魔数, 代码执行到这里会停下
[org 0x7c00]
start:
init:
; 设置屏幕模式微文本模式, 清除屏幕
mov ax, 3
int 0x10
; 初始化段寄存器
mov ax, 0
mov bx, ax
mov cx, ax
mov dx, ax
mov ds, ax
mov ss, ax
mov es, ax
mov si, ax
mov di, ax
; 修改栈顶为0x7c00, 使其向下增长
mov sp, 0x7c00
mov edi, 0x1000 ; 读取到目标内存地址(32位地址空间)
mov ebx, 0 ; 从第 n 个扇区开始读(32位 扇区最大有 2的27次方个)
mov cl, 1 ; 读 1个 扇区(8位 每次最多读取256个扇区)
call read_disk
mov edi, 0x1000 ; 把內存中什么地方的数据写出来
mov ebx, 1 ; 从第 n 个扇区开始写(32位 扇区最大有 2的27次方个)
mov cl, 1 ; 写 1个 扇区(8位 每次最多读取256个扇区)
call write_disk
mov si, booting
call print
call check
; 阻塞, 一直跳转到当前行
jmp $
ret
read_disk:
push edx
push eax
; ecx寄存器校验, 使其最大值为256
and ecx,0b1111_1111
; 设置读取扇区的数量 0x1f2
mov dx, 0x1f2
mov al, cl ; cl:1, al:1
out dx, al ; out 0x1f2, 1
mov eax, ebx ; eax: 0
; 起始扇区的 0~7位 设置 0x1f3
inc dx
out dx, al ; al: 0
; 起始扇区的 8~15位 设置 0x1f4
inc dx
shr eax, 8
out dx, al ; al:0
; 起始扇区的 16~23位 设置 0x1f5
inc dx
shr eax, 8
out dx, al ; al:0
; 起始扇区的 24~27位 设置 0x1f6
inc dx
shr eax, 8 ; al: 0
; 高4位设为0, 低四位保持不变
and al, 0b0000_1111 ; al: 0000_0000
; 读取模式为 LBA模式
or al, 0b1110_0000 ; al: 1110_0000
out dx, al
; 从硬盘读 0x1f7
inc dx
mov al, 0x20 ; al: 0x20
out dx, al
; 等待读取完毕
call .waits
; 读取到指定为止
call .reads
jmp .end
.reads
push cx
; 读取每个扇区的512字节, 每次读2字节读256次
mov cx, 256
mov dx, 0x1f0
.readw
in ax, dx
jmp $+2
jmp $+2
jmp $+2
mov es:[edi], ax
add edi, 2
loop .readw
pop cx
; 如果读取了多个扇区 继续循环
loop .reads
ret
; 循环检查磁盘状态
.waits
mov dx, 0x1f7
in al,dx
jmp $+2
jmp $+2
jmp $+2
; 如果 第三位是1, 说明准备好了
and al, 0b1000_1000
cmp al, 0b0000_1000
jnz .waits
ret
.end
pop eax
pop edx
ret
write_disk:
push edx
push eax
; ecx寄存器校验, 使其最大值为256
and ecx,0b1111_1111
; 设置写入扇区的数量 0x1f2
mov dx, 0x1f2
mov al, cl ; cl:1, al:1
out dx, al ; out 0x1f2, 1
mov eax, ebx ; eax: 0
; 起始扇区的 0~7位 设置 0x1f3
inc dx
out dx, al ; al: 0
; 起始扇区的 8~15位 设置 0x1f4
inc dx
shr eax, 8
out dx, al ; al:0
; 起始扇区的 16~23位 设置 0x1f5
inc dx
shr eax, 8
out dx, al ; al:0
; 起始扇区的 24~27位 设置 0x1f6
inc dx
shr eax, 8 ; al: 0
; 高4位设为0, 低四位保持不变
and al, 0b0000_1111 ; al: 0000_0000
; 模式为 LBA模式
or al, 0b1110_0000 ; al: 1110_0000
out dx, al
; 从硬盘设置写 0x1f7
inc dx
mov al, 0x30 ; al: 0x20
out dx, al
; 写入到指定为止
call .writes
jmp .end
.writes
; 等待硬盘空闲
call .waits
push cx
; 写入每个扇区的512字节, 每次写2字节读256次
mov cx, 256
mov dx, 0x1f0
.writew
mov ax, es:[edi]
out dx, ax
jmp $+2
jmp $+2
jmp $+2
mov es:[edi], ax
add edi, 2
loop .writew
pop cx
; 如果写了多个扇区 继续循环
loop .writes
ret
; 循环检查磁盘状态
.waits
mov dx, 0x1f7
in al,dx
jmp $+2
jmp $+2
jmp $+2
; 如果 第三位是1, 说明准备好了
and al, 0b1000_0000 ; 写入,不需要校验第三位了, 秩序要校验第七位
cmp al, 0b0000_0000
jnz .waits
ret
.end
pop eax
pop edx
ret
; 一个print函数, 调用时 把字符串的地址 mov到si寄存器即可
print:
push ax
mov ah, 0x0e
.show
mov al,[si]
cmp al, 0
jz .end
int 0x10
inc si
jmp .show
.end
pop ax
ret
check:
cmp word es:[0x11fe], 0xaa55
jnz .error
ret
.error:
mov si, .error_msg
call print
hlt; 让 CPU 停止
jmp $
ret
.error_msg db "Booting Error!!!", 10, 13, 0
booting:
db "hello~", 10, 13, 0 ; 结尾 \n \r 0
padding:
; 填充数据, 引导扇区必须为512字节, 最后两个字节是魔数, 除了代码之外必须用0填充
times 510 - ($ - $$) db 0 ; 最后俩字节是0xaa55, 所以一共需要填充 510 - (当前行位置 - 开始的的位置) = 510 - 代码段的大小
; 魔数
dw 0xaa55