动手写操作系统3----软盘读写逻辑实现

        系统从软盘启动,加载软盘第一个扇区作为引导扇区来加载操作系统,第一个扇区大小为512byte,一般用来跳转到操作系统代码起始处,第一个扇区称为引导区,现在来研究一下软盘的物理结构,引导扇区的数据格式以及软盘的读写逻辑实现。

软盘物理结构

软盘的物理结构,以及软盘的数据读取方法

              

        软盘的物理结构如上图,一个盘面被划分成若干个圆圈,例如图中的灰色圆圈,我们称之为磁道,也可以称作柱面,一个磁道或柱面,又被分割成若干部分,每一部分,我们称之为一个扇区,一个扇区的大小正好是512Byte,从而,当我们把数据存储到软盘上时,数据会分解成若干个512Byte大小的块,然后写入到扇区里。要读取数据时,磁头会挪动到扇区所在的磁道或柱面,然后盘面转动,当要读取的扇区转到磁头正下方时,磁头通电,通过电磁效应将扇区的数据读取到内存中。一个磁盘有两个盘面,每个盘面的组成跟右边图形一样,同时每个盘面对应一个磁头,所以当想从磁盘上读取数据时,需要确定数据在哪一个盘面,从而确定要用哪一个磁头来读取数据,然后确定哪一个磁道,最后再确定要读取的数据都存储在哪一个扇区。

       3.5寸软盘,有两个盘面head,对应两个磁头,每个盘面有80个磁道cylinder,也就是柱面,编号分别为0-79. 每个柱面都有18个扇区sector,编号分别为1-18. 所以一个盘面可以存储的数据量大小为:
512 * 18 * 80
       一个软盘有两个盘面,因此一个软盘可以存储的数据为:
2 * 512 * 18 * 80 = 1474560 Byte = 1440 KB

引导扇区数据格式

       引导扇区是软盘的第0个扇区,数据结构叫BPB(BIOS Parameter Block)

其中BPB_开头的属于BPB,以BS_开头的只是BOOT Sector的一部分,不属于BPB。

名称 开始字节 长度 内容 参考值
BS_jmpBOOT 0 3 一个短跳转指令 jmp Label_07c00H
nop
BS_OEMName 3 8 厂商名 'QingFeng'
BPB_BytesPerSec 11 2 每扇区字节数(Bytes/Sector) 0x200
BPB_SecPerClus 13 1 每簇扇区数(Sector/Cluster) 0x1
BPB_ResvdSecCnt 14 2 Boot记录占用多少扇区 ox1
BPB_NumFATs 16 1 共有多少FAT表 0x2
BPB_RootEntCnt 17 2 根目录区文件最大数 0xE0
BPB_TotSec16 19 2 扇区总数 0xB40[2*80*18]
BPB_Media 21 1 介质描述符 0xF0
BPB_FATSz16 22 2 每个FAT表所占扇区数 0x9
BPB_SecPerTrk 24 2 每磁道扇区数(Sector/track) 0x12
BPB_NumHeads 26 2 磁头数(面数) 0x2
BPB_HiddSec 28 4 隐藏扇区数 0
BPB_TotSec32 32 4 如果BPB_TotSec16=0,则由这里给出扇区数 0
BS_DrvNum 36 1 INT 13H的驱动器号 0
BS_Reserved1 37 1 保留,未使用 0
BS_BootSig 38 1 扩展引导标记(29h) 0x29
BS_VolID 39 4 卷序列号 0
BS_VolLab 43 11 卷标 'QingFeng'
BS_FileSysType 54 8 文件系统类型 'FAT12'
引导代码及其他内容 62 448 引导代码及其他数据 引导代码(剩余空间用0填充)
结束标志0x55AA 510 2 第510字节为0x55,第511字节为0xAA 0x55AA

参考以上格式来理解一下hello world汇编代码

org  0x7c00  ;CPU上电第一条指令位置  

jmp  entry  ;跳转到程序入口处
db   0x90
DB   "OSKERNEL"   ;厂商名
DW   512  ;每扇区字节数(Bytes/Sector)
DB   1  ;每簇扇区数(Sector/Cluster)
DW   1  ;Boot记录占用多少扇区
DB   2  ;共有多少FAT表
DW   224  ;根目录区文件最大数
DW   2880  ;扇区总数
DB   0xf0  ;介质描述符
DW   9  ;每个FAT表所占扇区数
DW   18  ;每磁道扇区数(Sector/track)
DW   2  ;磁头数(面数)
DD   0  ;隐藏扇区数
DD   2880  ;如果BPB_TotSec16=0,则由这里给出扇区数
DB   0,0,0x29  ;INT 13H的驱动器号,保留,未使用,扩展引导标记(29h)
DD   0xFFFFFFFF  ;卷序列号
DB   "MYFIRSTOS  "  ;卷标
DB   "FAT12   "  ;文件系统类型
RESB  18  ;

entry:
    mov  ax, 0
    mov  ss, ax
    mov  ds, ax
    mov  es, ax
    mov  si, msg

putloop:
    mov  al, [si]
    add  si, 1
    cmp  al, 0
    je   fin
    mov  ah, 0x0e
    mov  bx, 15
    int  0x10
    jmp  putloop

fin:
    HLT
    jmp  fin

msg:
    DB    0x0a,  0x0a
    db    "hello, world"
    db    0x0a
    db    0
TIMES 0x1FE-($-$$) DB 0x00
DB 0x55, 0xAA  ;结束标志

初始化寄存器,主要利用中断0x10来显示字符,具体使用说明如下:

以电传的方式写入字符串(AH=0x13)
------------------------------------------------------------------
			INT 0x10功能0x13
--------------------------------------------------------------
描述:
	以电传打字机的方式显示字符串
接受参数:
	AH			0x13
	AL			显示模式
	BH			视频页
	BL			属性值(如果AL=0x00或0x01)
	CX			字符串的长度
	DH,DL		屏幕上显示起始位置的行、列值
	ES:BP		字符串的段:偏移地址
返回值:
	无
显示模式(AL):
	0x00:字符串只包含字符码,显示之后不更新光标位置,属性值在BL中
	0x01:字符串只包含字符码,显示之后更新光标位置,属性值在BL中
	0x02:字符串包含字符码及属性值,显示之后不更新光标位置
	0x03:字符串包含字符码及属性值,显示之后更新光标位置
-------------------------------------------------------------------

软盘读写逻辑C语言实现

利用C语言来实现软盘读写逻辑,首先明确一下C语言操作文件常用api    fopen   fclose    fseek    ftell

1. FILE *fopen( const char * filename, const char * mode );
2. int fclose( FILE *fp );
3. size_t fwrite ( void * ptr, size_t size, size_t count, FILE *fp );
   从内存中的ptr指向的地址开始,将连续n*size字节的内容写入fp文件中。该函数的返回值是实际写入的数据块个数。
4. size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
   从文件fp中,连续读取n*size字节的内容,并存入ptr指向的内存空间。该函数的返回值是实际读入的数据块个数。
5. int fseek( FILE *stream, long offset, int origin );
   stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置
6. long ftell(File *fp)
   返回文件指针当前位置到文件开头的长度。

软盘读写实现类

floppy.h

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//根据文件名创建一个3.5寸虚拟软盘
FILE* initFloppy(char * fileName);

//从一个虚拟软盘fp中读取磁头head,柱面cylinder,扇区sector的数据到buf中
void readFloppy(int cylinder, int head, int sector, FILE *fp, char *buf);

//将buf中数据读取到虚拟软盘fp中读取磁头head,柱面cylinder,扇区sector
void writeFloppy(int cylinder, int head, int sector, FILE *fp, char *buf);

floppy.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

FILE* initFloppy(char *fileName){
   FILE *fp = fopen(fileName, "w");
   char buf[512];
   memset(buf, 0, 512);
   for(int cylinder=0; cylinder<80; cylinder++){
       for(int head=0; head<2; head++){
	      for(int sector=1; sector<=18; sector++){
	           fwrite(buf, 512, 1, fp);
	      }    
       }
   }
   return fp;
}

void readFloppy(int cylinder, int head, int sector, FILE *fp, char *buf){
    int index = 18*2*512*cylinder + 18*512*head + 512*(sector-1);
    int tmp = (int)ftell(fp);
    fseek(fp, index, SEEK_SET);
    fread(buf, 512, 1, fp);
    fseek(fp, tmp, SEEK_SET);
}

void writeFloppy(int cylinder, int head, int sector, FILE *fp, char *buf){
    int index = 18*2*512*cylinder + 18*512*head + 512*(sector-1);
    int tmp = (int)ftell(fp);
    fseek(fp, index, SEEK_SET);
    fwrite(buf, 512, 1, fp);
    fseek(fp, tmp, SEEK_SET);
}

模拟2*18*80=2880个扇区,以扇区为单位来进行读写,可以将汇编语言得到的文件写到虚拟软盘上来制作一张虚拟软盘。

makeFloppy.c

#include <stdio.h>
#include <stdlib.h>
#include "floppy.h"

int main(int argc, char *argv[]){
    FILE* src = fopen("boot", "r");
    FILE* img = initFloppy("system.img");
    if(src == NULL){
	printf("The file not found");
	exit(0);
    }
    char buf[512];
    memset(buf, 0, 512);
    fread(buf, 512, 1, src);
    writeFloppy(0, 0, 1, img, buf);
    fclose(src);
}

gcc编译以上文件

gcc makeFloppy.c floppy.c -o makeFloppy

执行可执行文件makeFloppy可以得到最终的虚拟软盘文件system.img,格式如下:

                                  

汇编语言读写软盘扇区

上述屏幕打印文件直接写在引导扇区,下面增强一下功能,将要显示的文件存放于非引导扇区,系统启动从其他扇区读取数据显示到屏幕中。系统读取软盘数据要使用13H中断,具体参数说明如下:

功能描述:读扇区
入口参数:AH=02H
AL=扇区数
CH=柱面
CL=扇区
DH=磁头
DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘
ES:BX=缓冲区的地址
出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明

       程序初始化在内存中申请64字节空间缓冲区,然后利用INT 13H中断将软盘中某个扇区数据读取到缓冲区,之前利用INT 10H中断将缓冲区数据显示到屏幕上。

汇编代码如下:

org  0x7c00

jmp  entry
db   0x90
DB   "OSKERNEL"
DW   512
DB   1
DW   1
DB   2
DW   224
DW   2880
DB   0xf0
DW   9
DW   18
DW   2
DD   0
DD   2880
DB   0,0,0x29
DD   0xFFFFFFFF
DB   "MYFIRSTOS  "
DB   "FAT12   "
RESB  18

entry:
    mov  ax, 0
    mov  ss, ax
    mov  ds, ax
    mov  es, ax
    mov  si, msg

readFloppy:
    mov ch, 1 ;磁道号cylinder
    mov dh, 0 ;磁头号head
    mov cl, 2 ;扇区号sector
    
    mov bx, msg ;数据存储缓冲区
    mov ah, 0x02 ;读扇区
    mov al, 1 ;连续读取扇区数量
    mov dl, 0 ;驱动器编号

    INT 0x13 ;调用BIOS中断

    jc error

putloop:
    mov  al, [si]
    add  si, 1
    cmp  al, 0
    je   fin
    mov  ah, 0x0e
    mov  bx, 15
    int  0x10
    jmp  putloop

fin:
    HLT
    jmp  fin

error:
    mov si, errmsg
    jmp putloop

msg:
    RESB 64

errmsg:
    DB "error"

TIMES 0x1FE-($-$$) DB 0x00

DB 0x55, 0xAA

主要增加了从磁头0 磁道1 扇区2位置处读取数据到缓冲区msg,调用BIOS中断来显示字符串。

往软盘磁头0 磁道1 扇区2位置处写入字符串

#include <stdio.h>
#include <stdlib.h>
#include "floppy.h"
#include <string.h>

int main(int argc, char *argv[]){

    printf("src %s \n", argv[1]);
    FILE* src = fopen(argv[1], "r");

    printf("img %s \n", argv[2]);
    FILE* img = initFloppy(argv[2]);

    if(src == NULL){
	printf("The file not found");
	exit(0);
    }
    
    //写入引导扇区
    char buf[512];
    memset(buf, 0, 512);
    fread(buf, 512, 1, src);
    writeFloppy(0, 0, 1, img, buf);

    //head0 cylinder1 sector2 写入字符串
    memset(buf, 0, 512);
    char str[100] = {"Hello World cylinder1 sector2"};
    strncpy(buf, str, 100); 
    writeFloppy(1, 0, 2, img, buf);
    fclose(src);
}

为了便于执行将程序简单修改一下,搞了个简单脚本  编译汇编代码  编译c语言  制作软盘文件

#!/bin/bash
nasm boot.asm
echo "nasm boot.asm"
gcc floppy.c makeFloppy.c -o makeFloppy
echo "gcc floppy.c makeFloppy.c -o makeFloppy"
./makeFloppy boot system.img
echo "./makeFloppy boot system.img"

利用virtualBox加载软盘文件结果如下:             

 

代码位置:https://github.com/ChenWenKaiVN/bb_os_core/tree/develop

BIOS中断总结

中断号 功能说明
INT 10H

显示服务(Video Service——INT 10H)

INT 13H

直接磁盘服务(Direct Disk Service——INT 13H)

INT 14H

串行口服务(Serial Port Service——INT 14H)

INT 15H

杂项系统服务(Miscellaneous System Service——INT 15H)

INT 16H

键盘服务(Keyboard Service——INT 16H)

INT 17H

并行口服务(Parallel Port Service——INT 17H)

INT 1AH

时钟服务(Clock Service——INT 1AH)

直接系统服务(Direct System Service)

INT 00H —“0”作除数
INT 01H —单步中断
INT 02H —非屏蔽中断(NMI)
INT 03H —断点中断
INT 04H —算术溢出错误
INT 05H —打印屏幕和BOUND越界
INT 06H —非法指令错误
INT 07H —处理器扩展无效
INT 08H —时钟中断
INT 09H —键盘输入
INT 0BH —通信口(COM2:)
INT 0CH —通信口(COM1:)
INT 0EH —磁盘驱动器输入/输出
INT 11H —读取设备配置
INT 12H —读取常规内存大小(返回值AX为内存容量,以K为单位)
INT 18H —ROM BASIC
INT 19H —重启动系统
INT 1BH —CTRL+BREAK处理程序
INT 1CH —用户时钟服务
INT 1DH —指向显示器参数表指针
INT 1EH —指向磁盘驱动器参数表指针
INT 1FH —指向图形字符模式表指针

参考地址:

https://blog.csdn.net/Zllvincent/article/details/83515294

https://blog.csdn.net/tyler_download/article/details/51815483

https://blog.csdn.net/weixin_34342578/article/details/94067240

https://www.cnblogs.com/sea-stream/p/10850003.html

https://blog.csdn.net/m0_37329910/article/details/86081024

https://blog.csdn.net/BobYuan888/article/details/80153283   C语言文件读写函数总结

https://blog.csdn.net/weixin_37656939/article/details/79684611  BIOS 中断大全

发布了302 篇原创文章 · 获赞 93 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/u014106644/article/details/104207872
今日推荐