注:以下程序为原创,若发现任何BUG,欢迎指正;若有问题,欢迎交流;权利归原作者所有,若转载,请注明出处;若能有益于一二访客,幸甚。
昨天学习了VGA显示的一些东西,今天准备学习一下读取软盘的知识。
1.babyos将使用的引导过程
1)系统上电或reset时,处理器执行一些初始化,CPU处于实模式2)处理器会执行一个位于已知位置处的代码,PC中这个位置位于BIOS,它保存在主板上的闪存中
3)控制权交给BIOS后,它寻找一个可引导的设备(软盘、硬盘等),BIOS读取引导扇区(512字节)到内存0x7c00处,并跳转到该地址执行
4)引导扇区中存放的指令可以使用BIOS中断,它将会读取软盘中内核部分到一个临时地址(如0x10000,不覆盖0x7c00处的boot代码即可)
5)将内核前512字节(load.s, 它主要负责将内核剩余部分拷贝到load.s后面)移动到0x0处,将GDT拷贝到0x80000处。为什么不一次全部将内核放到0x0处呢?因为内核可能较大,会覆盖掉0x7c00处的代码。
6)开启A20总线,置位CR0的bit 0,开启保护模式,加载GDT到GDTR,跳转到GDT第二项(第一项为空GDT),即load.s处执行
7)load.s将内核剩余部分移动到load.s后面,即0x200开始的地址处。然后执行初始化代码。
8)初始化代码,至此系统启动成功。
所以首当其冲的问题就是如何读软盘。
2.软盘的结构
3.5寸1.44M 软盘,如图floppy_struct.png 所示,有两个磁头,正反两面各一个;80个磁道(即80个圆圈);每个磁道有18个扇区;每个扇区为512字节。容量 = 512字节/扇区 * 2面 * 80磁道(柱面)/面 * 18扇区/磁道 = 1440 KB
磁头,即面:编号[0, 1]
80个磁道,即柱面(圆圈):编号[0, 79]
18个扇区:编号[1, 18]
相对扇区号[0, 2879]:
相对扇区号按照柱面排序,即从最外头的圆圈到最里头的圆圈。
0柱面正面(即磁头号为0)的1-18扇区为0-17号相对扇区,0柱面反面(即磁头号为2)的1-18扇区为18-35号相对扇区,然后是1柱面,2柱面,直到79柱面。如下:
0柱面,0磁头,1扇区 0
0柱面,0磁头,2扇区 1
……
0柱面,0磁头,18扇区 17
0柱面,1磁头,1扇区 18
……
0柱面,1磁头,18扇区 35
1柱面,0磁头,1扇区 36
……
1柱面,0磁头,18扇区 53
1柱面,1磁头,1扇区 54
……
1柱面,1磁头,18扇区 71
2柱面,0磁头,1扇区 72
……
3.利用BIOS 中断读取软盘
-------------------------------------------------------------------
INT 0x13,功能02
-----------------------------------------------------------
参数:
AH 02
AL 读取扇区数
CH 柱面[0, 79]
CL 扇区[1, 18]
DH 磁头[0, 1]
DL 驱动器(0x0 ~ 0x7f表示软盘,0x80 ~ 0xff表示硬盘)
ES:BX 缓冲区地址,即数据读到这里
返回值:
CF = 0表示操作成功,此时AH=0,AL=传输的扇区数
CF = 1即carry位置位(可用JC表示跳转)表示操作失败,AH=状态代码
--------------------------------------------------------------------
4.相对扇区号的计算
1)知道柱面号,磁头号,扇区号计算相对扇区号由上面可知0号柱面包含了相对扇区号[0,35],1号柱面包含相对扇区号[36,71],依次类推。
设相对扇区号为N,则
柱面号CH = N / 36;
令x = N % 36;
则x范围为[0,35],其中[0,17] 为磁头号0, [18,35]为磁头号1.
则磁头号DH = x / 18;
零y = x % 18; y范围[0, 17]
则扇区号CL = y + 1。
2)知道相对扇区号,计算柱面号、磁头号、扇区号
N = 36*CH + 18*DH + CL;
由此式子,也可计算:
CH = N / 36
DH = (N % 36) / 18
CL = (N % 36) % 18 + 1
5.读取一个扇区
实验:将一些数据写入软盘的第二个扇区(第一个扇区是引导扇区),然后用BIOS 中断读取该扇区的数据,并显示在屏幕上。然后看读取的数据是否与写入的数据相同。注:第二个扇区相对扇区号为1.写数据的C代码:
/*************************************************************************
> File: write_data.c
> Author: 孤舟钓客
> Mail: guzhoudiaoke@126.com
> Time: 2012年12月26日 星期三 01时20分26秒
************************************************************************/
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp;
fp = fopen("./data", "wb");
int i;
char *str = "baby os, guzhoudiaoke@126.com ";
int len = strlen(str);
for (i = 0; i < len; i++)
fprintf(fp, "%c", str[i]);
for (i = 512-len; i > 0; i--)
fprintf(fp, "%c", i % 26 + 'A');
return 0;
}
汇编代码:
# This program draws color pixels at mode 0x13
# 2012-12-26 01:31
# guzhoudiaoke@126.com
.include "boot.inc"
.section .text
.global _start
.code16
_start:
jmp main
#--------------------------------------------------------------
# 清屏函数:
# 设置屏幕背景色,调色板的索引0指代的颜色为背景色
clear_screen: # 清屏函数
movb $0x06, %ah # 功能号0x06
movb $0, %al # 上卷全部行,即清屏
movb $0, %ch # 左上角行
movb $0, %ch # 左上角列
movb $24, %dh # 右下角行
movb $79, %dl # 右下角列
movb $0x07, %bh # 空白区域属性
int $0x10
ret
#---------------------------------------------------------------
# 直接写显存显示一些文字函数:
# 调用前需要设置DS:SI为源地址,DI为显示位置,
# CX 为显示的字符个数, AL为颜色属性
draw_some_text:
# ES:DI is the dst address, DS:SI is the src address
movw $VIDEO_SEG_TEXT, %bx
movw %bx, %es
copy_a_char:
movsb
stosb
loop copy_a_char
ret
#----------------------------------------------------------------
# 读取软盘第二个扇区:
# 使用BIOS INT 0x13中断,使用前需要设置ES:BX作为缓冲区
read_one_sect:
movb $0x02, %ah # 功能号
movb $0x01, %al # 读取扇区数
movb $0x00, %ch # 柱面号
movb $0x02, %cl # 扇区号
movb $0x00, %dh # 磁头号
movb $0x00, %dl # 驱动器号
re_read: # 若调用失败则重新调用
int $0x13
jc re_read # 若进位位(CF)被置位,表示调用失败
ret
main:
movw %cx, %ax
movw %ax, %ds
movw %ax, %es
call clear_screen # 清屏
movw $0, %ax
movw %ax, %ds
leaw msg_str, %si
xorw %di, %di
movw msg_len, %cx
movb $TEXT_COLOR,%al
call draw_some_text # 绘制字符串
movw $BUFFER_SEG,%ax
movw %ax, %es # ES:BX 为缓冲区地址
xorw %bx, %bx
call read_one_sect
# 下面调用绘制函数,在屏幕上显示读取的信息
movw $BUFFER_SEG,%ax
movw %ax, %ds # ds:si 为源地址
xorw %si, %si
movw $160, %di # 第一行已经打印了msg_str,从第二行开始显示
movw $512, %cx # 显示512个字符
movb $0x01, %al
call draw_some_text
1:
jmp 1b
msg_str:
.asciz "The data of the second sect of the floppy (sect 1):"
msg_len:
.int . - msg_str - 1
.org 0x1fe, 0x90
.word 0xaa55
实验结果:
6.读取任意扇区(给定相对扇区号)
实验,写用C语言写入文件,该文件包含512个‘a’,512个1……512个‘z’, 循环50次,将该文件写入软盘(相对扇区号1~50*26),然后读取给定的相对扇区号的扇区,将读取的内容打印到屏幕上。并与写入的数据比较,验证读取的正确性。
C代码用于写文件:
/*************************************************************************
> File: write_data.c
> Author: 孤舟钓客
> Mail: guzhoudiaoke@126.com
> Time: 2012年12月26日 星期三 20时16分45秒
************************************************************************/
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("usage: ./write_data file_name");
exit(0);
}
FILE *fp;
fp = fopen(argv[1], "wb");
int i, j, k;
for (i = 0; i < 50; i++)
{
for (j = 'a'; j <= 'z'; j++)
{
for (k = 0; k < 512; k++)
{
fprintf(fp, "%c", (char)j);
}
}
}
return 0;
}
汇编代码:
# This program draws color pixels at mode 0x13
# 2012-12-26 20:23:42
# guzhoudiaoke@126.com
.include "boot.inc"
.section .text
.global _start
.code16
_start:
jmp main
#--------------------------------------------------------------
# 清屏函数:
# 设置屏幕背景色,调色板的索引0指代的颜色为背景色
clear_screen: # 清屏函数
movb $0x06, %ah # 功能号0x06
movb $0, %al # 上卷全部行,即清屏
movb $0, %ch # 左上角行
movb $0, %ch # 左上角列
movb $24, %dh # 右下角行
movb $79, %dl # 右下角列
movb $0x07, %bh # 空白区域属性
int $0x10
ret
#---------------------------------------------------------------
# 直接写显存显示一些文字函数:
# 调用前需要设置DS:SI为源地址,DI为在屏幕上的显示位置,
# CX 为显示的字符个数, AL为颜色属性
draw_some_text:
# ES:DI is the dst address, DS:SI is the src address
movw $VIDEO_SEG_TEXT, %bx
movw %bx, %es
copy_a_char:
movsb
stosb
loop copy_a_char
ret
#----------------------------------------------------------------
# 读取软盘一个扇区:
# 使用BIOS INT 0x13中断,使用前需要设置ES:BX作为缓冲区
# AX为相对扇区号
read_one_sect:
movb $36, %dl
divb %dl
movb %al, %ch # 柱面号=N / 36, 假设x = N % 36
movb %ah, %al # AL = N % 36
movb $0, %ah # AX = N % 36
movb $18, %dl
divb %dl
movb %al, %dh # 磁头号DH = x / 18
movb %ah, %cl
incb %cl # 扇区号CL = x % 18 + 1
movb $0x00, %dl # 驱动器号DL
movb $0x02, %ah # 功能号
movb $0x01, %al # 读取扇区数
re_read: # 若调用失败则重新调用
int $0x13
jc re_read # 若进位位(CF)被置位,表示调用失败
ret
#-------------------------------------------------------------------
# 该函数读取指定的若干扇区号
# 需要指定ES:BX作为缓冲区
read_sects:
movw $0x00, %si # 已经读取的扇区数
leaw sect_no, %di
1:
movw (%di), %ax # 获取相对扇区号
addw $2, %di
call read_one_sect
incw %si
incw %bx
cmpw num_to_read, %si
jne 1b
ret
main:
movw %cx, %ax
movw %ax, %ds
movw %ax, %es
call clear_screen # 清屏
# 显示提示信息
movw $0, %ax
movw %ax, %ds
leaw msg_str, %si
xorw %di, %di
movw msg_len, %cx
movb $TEXT_COLOR,%al
call draw_some_text # 绘制字符串
# 读取软盘
movw $BUFFER_SEG, %ax
movw %ax, %es # ES:BX 为缓冲区地址
xorw %bx, %bx
call read_sects
# 在屏幕上显示读取的信息
# movw $BUFFER_SEG,%ax
# movw %ax, %ds # ds:si 为源地址
# movw $0, %si
# movw $320, %di # 第一行已经打印了msg_str,从第二行开始显示
# movw $512, %cx # 显示字符数
# movb $0x01, %al
# call draw_some_text
# 将缓冲区中前data_len个字节拷贝到data_save
xorw %ax, %ax
movw %ax, %ds
movw num_to_read,%cx
movw $BUFFER_SEG,%ax
movw %ax, %ds
xorw %ax, %ax
movw %ax, %es
movw $0, %si
movw $data_save, %di
cld
rep movsb
# 下面调用绘制函数,在屏幕上显示读取的信息
xorw %ax, %ax
movw %ax, %ds # ds:si 为源地址
leaw data_save, %si
movw $160, %di # 第一行已经打印了msg_str,从第二行开始显示
movw num_to_read,%cx # 显示字符数
movb $0x01, %al
call draw_some_text
1:
jmp 1b
msg_str:
.asciz "The data read from floppy:"
msg_len:
.short . - msg_str - 1
sect_no:
# 下面的扇区数据为:"babyosguzhoudiaoke"
# sect: 2+26*1, 1+26*2, 2+26*3, 25+26*4, 15+26*5, 19+26*6,
# 7+26*11, 21+26*12, 26+26*13, 8+26*14, 15+26*15, 21+26*16,
# 4+26*31, 9+26*32, 1+26*33, 15+26*34, 11+26*35, 5+26*36
.short 28, 53, 80, 129, 145, 175
.short 293, 333, 364, 372, 379, 411
.short 810, 841, 859, 899, 921, 941
num_to_read:
.short 18
data_save:
.asciz "XXXXXXXXXXXXXXXXXX"
.org 0x1fe, 0x90
.word 0xaa55