软盘上FAT12文件系统的解读和制作

3.5寸1.44MB软盘结构说明,只有一块磁性圆盘,2个面。每个面有80个磁道,每个磁道18个扇区,既然有2个面,那么需要2个磁头读取数据。所以,一块3.5寸1.44MB软盘可以存储的字节数是18 * 80 * 2 = 2880个扇区 = 1440KB = 1.44MB

FAT12文件系统的结构

启动区,软盘第一扇区,启动区要求最后2个字节的内容是0xAA55,即0x1fe处是55,0x1ff处是AA。BIOS会检查最后2个字节内容,满足条件将第一扇区加载0x7c00开始的内存中,并将CPU跳转到0x7c00处开始运行(图中扇区编号从0开始)

FAT区,将存储文件需要的簇号和扇区对应起来,FAT12有2个FAT表,数据是一样的,暂且就认为FAT2是FAT1的备份

根目录区(RootDir),记录文件信息(目录看做特殊文件),每个文件信息需要32字节存储,关键信息有文件名称、起始簇号、文件大小。根目录能存放的文件项就定为224项吧,32字节 * 224项 = 7168字节 = 14个扇区,那么数据区的起始扇区就可以固定了

数据区,存放文件的二进制数据,按照扇区(512字节)存放。

簇,简单理解为将数据区的扇区分组吧(也可以看成是一种单位),一个簇由多少个扇区组成。例:一个簇由2个扇区组成,那么数据区的第0、1号扇区和第2、3号扇区组成了2个簇。FAT12文件系统中固定1个簇就由一个扇区组成,即数据区第0个扇区就是2号簇,第1个扇区就是3号簇,规定可用簇号从2开始。

一个文件在1.44MB软盘FAT12文件系统上如何保存?在根目录区的文件信息存储结构中没有记录文件数据所占用的扇区有哪些,只是记录了一个起始簇号。首先,通过这个起始簇号在FAT表中找到文件所使用的所有簇号,将这些簇号和数据区的扇区对应起来就可以找到文件所有扇区,怎么找到文件使用到的所有簇号见下图:

 以loader.bin文件为例,参照上图分析,已知文件1028字节,扇区大小是512字节,所以文件需要3个扇区存放。FAT12中的1个簇就是一个扇区,所以文件需要使用3个簇。在FAT表中找到3个未使用的项,未使用的项的值规定为0吧。上图中,文件起始簇号是2,也就是FAT表中的第2项,存放了3,3说明文件下一个簇号是3,那么去FAT表中的第3项取得数值是4,4说明文件的再下一个簇号是4,继续从FAT表中的第4项取值,结果发现是0xFF8,说明结束了。也需你已经明白了:簇号就是FAT表项的索引。最终,loader.bin文件使用到的簇号有2、3、4,簇号是知道了,但是并不知道文件数据保存在了哪些扇区?继续看下图:

 上面说到loader.bin文件使用到的簇号有2、3、4,簇号就是FAT表项的索引。现在就将这些簇号和存储文件的扇区对应起来,直接看上图最右边,扇区编号 = 簇号 - 2,是的就是这么简单。簇号2对应数据区的第0扇区,簇号3对应了数据区第1扇区。所以,loader.bin在数据区用到的扇区编号是0、1、2。那么都知道loader.bin存放在了哪些扇区,就可以读取文件了。

再来一个栗子,换种图示:看着图中的文字理解一下

上图中,loader.txt起始簇号是2,找到FAT表中的第2项,里面存放的是0xFF8,说明loader.txt只使用到了2这个簇号,对应着数据区的扇区编号是 2 - 2 = 0号扇区。test.bin起始簇号是3,去FAT表中的第3项,存放的是4,下一簇号是4,去FAT表中的第4项,...,所以test.bin使用到的簇号有3、4、5,对应了数据区第1、2、3号扇区。看着上图和文字理解理解。后面开始介绍如何制作一个软盘映像文件。 

准备一张软盘,什么?你手上没有软盘!可以使用img文件,在img文件上格式化出来一个FAT12文件系统。什么,文件上面还可以再建立文件系统?首先,创建一个img文件并写入默认数据,这个默认数据写入什么都行,这里就全部给0吧,直到文件内容字节数是指定大小,如果将文件内容看成是一个个的存储单元,逻辑上就可以看成一块空白磁盘。如下图,成功制作了一块空白软盘。大小是1400KB = 1.44MB

然后,在文件上写入特定内容,将文件里面的内容按照文件系统的结构写入数据,就相当于在这个逻辑磁盘上建立了文件系统。啊,文件系统怎么理解:简单认为一个数据结构,这个数据结构有整个磁盘那么大,这个数据结构中按照文件系统的要求存放了一些数据,这些数据记录了磁盘有多大、多少个扇区、空闲扇区多少个等等数据。我们甚至可以将整个物理磁盘的二进制文件原封不动地读入到一个img文件中,然后把这个img文件作为一个磁盘挂载到系统上,让操作系统认为它是一个磁盘,磁盘映像文件就是这么来的吗?^_^。如下制作一个带有FAT12文件系统的软盘,怎么做如下:
0、需要一个可以以二进制方式编辑文件的工具,或者是一个可以直接生成一个二进制文件的软件(工具)。或者自己熟悉高级编程语言的话更好,直接写程序使用IO API创建一个指定大小的二进制空白文件。
1、使用工具制作出一个1.44MB的img文件作为软盘,暂且将img文件称作为软盘映像文件吧,软盘映像文件制作很方便,直接向一个文件写入1440KB的二进制数据,写入任何数据都行,类似于有了一张空白软盘,这个时候软盘上还没有文件系统。
2、在img文件上建立FAT12文件系统,可以使用Free DOS来格式化这个img文件,使用VirtualBox创建虚拟机运行FreeDOS,将floppy144.img挂在到FreeDOS下格式化。FreeDOS官网http://wiki.freedos.org,但是发现FreeDOS格式化出来的软盘上存在一些默认文件,如果觉得默认文件不碍事就可以使用了。但是笔者觉得默认文件太碍事,经过一番折腾,写了一个java程序专门生成一个带FAT12文件系统的软盘,可以随意写入文件。
3、在floppy144.img的启动区(第一个扇区)写入启动信息
4、在floppy144.img的FAT12文件系统中写入编译链接成的加载程序二进制文件start.bin(loader.bin)
5、在floppy144.img的FAT12文件系统中写入编译链接成的操作系统二进制文件os.bin
6、打开VirtualBox创建一个虚拟机,虚拟机介质选择floppy144.img,VirtualBox虚拟机设置为从软盘中启动,然后启动虚拟机进入系统。

使用java写一个可以生成1.44MB且带有FAT12文件系统的软盘,并且可以随意写入文件,代码有注释且可以结合以上图片理解。程序大体结构见下图:

启动区BootSector:

package my.floppy;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 软盘引导扇区(第一扇区)
 */
public class BootSector {
    /** 一个短跳转指令,进入启动区代码,JMP指令2B + 0x90, 3B, 写入时需要转为byte */
    private short[] BS_jmpBoot =    new short[]{0xE0, 0x23, 0x90};
    /** 厂商名称,没要求,随便填 */
    private String BS_OEMName =     "HELLOIPL";
    /** 每个扇区的字节数,这里是软盘,一个扇区512字节 */
    private short BPB_BytesPerSec = 512;
    /** 每簇扇区数,簇:固定数量的一组扇区? */
    private byte BPB_SecPerClus =   1;
    /** Boot(引导数据占了几个扇区?)记录占用多少个扇区 */
    private short BPB_RsvdSecCnt =  1;
    /** 共有多少张FAT表 */
    private byte BPB_NumFATs =      2;
    /** 根目录文件数最大值 */
    private short BPB_RootEntCnt =  224;
    /** 扇区总数,1.44MB软盘扇区数是2880 */
    private short BPB_TotSec16 =    2880;
    /** 介质描述符,介质类型?磁盘种类?0xF0(软盘), 1B */
    private short BPB_Media =       0xF0;
    /** 每个FAT扇区数 2B */
    private short BPB_FATSz16 =     9;
    /** 每个磁道扇区数,1.44MB软盘的一个磁道是18个扇区 2B */
    private short BPB_SecPerTrk =   18;
    /** 磁头数,1.44MB软盘2个磁头 2B */
    private short BPB_NumHeads =    2;
    /** 隐藏扇区数,和分区有关?0-不使用分区, 4B */
    private int BPB_HiddSec =       0;
    /** 如果BPB_TotSec16是0,这个值记录扇区数,这里直接设置为和BPB_TotSec16相同 2880, 4B */
    private int BPB_TotSec32 =      2880;
    /** 中断0x13的驱动号(驱动器号、设备号) */
    private byte BS_DrvNum =        0;
    /** 未使用 */
    private byte BS_Reservedl =     0;
    /** 扩展引导标记(0x29) */
    private byte BS_BootSig =       0x29;
    /** 卷序列号,4B */
    private int BS_VolID =          0xFFFFFFFF;
    /** 卷标 11B 不足的空格补齐 */
    private String BS_VolLab =      "HELLOOS    ";
    /** 文件系统类型 8B 不足的空格补齐 */
    private String BS_FileSysType = "FAT12   ";
    /** 引导代码,数据及其他填充字符等 */
    private byte[] BootCodeOrOther = new byte[448];
    /** 可引导扇区标识, 如果是0xAA55, BIOS就会把这个扇区加载内存0x7c00开始的地方, 2B */
    private int BootableFlag =       0xAA55;

    private ByteArrayOutputStream bos = new ByteArrayOutputStream();
    byte[] toByteArray() throws IOException {
        bos.write(BS_jmpBoot[0]);
        bos.write(BS_jmpBoot[1]);
        bos.write(BS_jmpBoot[2]);
        bos.write(BS_OEMName.getBytes("utf-8"));
        bos.write(BPB_BytesPerSec);
        bos.write(BPB_BytesPerSec >> 8);
        bos.write(BPB_SecPerClus);
        bos.write(BPB_RsvdSecCnt);
        bos.write(BPB_RsvdSecCnt >> 8);
        bos.write(BPB_NumFATs);
        bos.write(BPB_RootEntCnt);
        bos.write(BPB_RootEntCnt >> 8);
        bos.write(BPB_TotSec16);
        bos.write(BPB_TotSec16 >> 8);
        bos.write(BPB_Media);
        bos.write(BPB_FATSz16);
        bos.write(BPB_FATSz16 >> 8);
        bos.write(BPB_SecPerTrk);
        bos.write(BPB_SecPerTrk >> 8);
        bos.write(BPB_NumHeads);
        bos.write(BPB_NumHeads >> 8);
        bos.write(BPB_HiddSec);
        bos.write(BPB_HiddSec >> 8);
        bos.write(BPB_HiddSec >> 16);
        bos.write(BPB_HiddSec >> 24);
        bos.write(BPB_TotSec32);
        bos.write(BPB_TotSec32 >> 8);
        bos.write(BPB_TotSec32 >> 16);
        bos.write(BPB_TotSec32 >> 24);
        bos.write(BS_DrvNum);
        bos.write(BS_Reservedl);
        bos.write(BS_BootSig);
        bos.write(BS_VolID);
        bos.write(BS_VolID >> 8);
        bos.write(BS_VolID >> 16);
        bos.write(BS_VolID >> 24);
        bos.write(BS_VolLab.getBytes("utf-8"));
        bos.write(BS_FileSysType.getBytes("utf-8"));
        bos.write(BootCodeOrOther);
        bos.write(BootableFlag);
        bos.write(BootableFlag >> 8);
        return bos.toByteArray();
    }

    /**
     * 不使用默认的启动区数据, 而是从一个二进制文件读取, 启动区最后2个字节要求是0xAA55
     * @param filePath 文件路径
     * @return 启动区数据
     * @throws IOException
     */
    public byte[] loadFromFile(String filePath) throws IOException {
        // 启动区数据来自文件, 要求校验所有数据以满足FAT12
        if(filePath != null && filePath.length() > 0) {
            File file = new File(filePath);
            FileInputStream fis = new FileInputStream(file);
            byte[] read_buf = new byte[512];
            if(fis.read(read_buf) == -1) throw new IllegalStateException("文件没有内容");
            fis.close();

            if(read_buf[510] == (byte)0x55 && read_buf[511] == (byte)0xAA) {
                return read_buf;
            } else {
                // 不是启动区数据文件, 使用默认的
                return toByteArray();
            }
        } else {
            throw new IllegalStateException("文件路径错误");
        }
    }
}

FAT区:

package my.floppy;

import java.util.ArrayList;
import java.util.List;

public class FAT {
    /** FAT大小(字节数), 9 * 512B = 4608B = 36864Bit = 3072个FAT项 */
    public static int FATSz = 9 * 512;
    /** FAT项数组, 每个FAT项12Bit, 数组元素共3072个, 先定义成short类型方便操作, 实际每个FATEntry是12位 */
    private short[] FATItems = new short[(FATSz * 8) / 12];
    public FAT() {
        // FAT的前2项保留, 索引是0, 1, 所以实际可用簇号是从2开始的
        FATItems[0] = 0xFF0;
        FATItems[1] = 0xFFF;
    }

    /**
     * >=0xFF8 文件结束
     * 0xFF7 坏簇
     * 从FATItems中找到没有使用的簇号, 簇号是FAT表项的索引<br/>
     * 所有操作都是怎么简单怎么来, 反正最后可以生成一个FAT12格式的软盘就行, 不考虑软盘读写效率最优
     * @param size 需要多少个簇号
     * @return 返回指定数量的可用簇号
     */
    public Integer[] allocFATEntry(int size) {
        if(size < 1) return null;

        List<Integer> va = new ArrayList<Integer>();
        // FAT第0、1项保留, 这里代码有BUG, 但是不想再写了, 应该不会有文件超过剩余扇区大小
        for (int i = 2; i < FATItems.length && size > 0; i++) {
            if(FATItems[i] == 0) {
                va.add(i);
                size--;
            }
        }

        // 申请到指定数量的簇size会等于0
        if(size != 0) throw new IllegalStateException("剩余的可用簇小于size了,size=" + size);

        if(va.size() > 1) {
            for (int i = 0; i < va.size() - 1; i++) {
                // 前一个簇号是FAT表总的索引, 找到前一个簇号对应的FAT中的项, 这个项保存了下一个簇的簇号
                FATItems[va.get(i)] = va.get(i + 1).shortValue();
            }
            // 最后一个簇号对应的FAT项中设置为0xFF8, 即文件结束
            FATItems[va.get(va.size() - 1)] = 0x0FF8;
        } else {
            // 文件结束簇
            FATItems[va.get(0)] = 0x0FF8;
        }

        return va.toArray(new Integer[va.size()]);
    }

    public byte[] FATBytes(){
        byte[] b = new byte[FATSz];
        byte[] c = new byte[((FATSz * 8) / 12) * 3]; // 先把每个4位拆分存放在一个byte中
        int index = 0;
        for (int i = 0; i < FATItems.length; i++) {
            c[index++] = (byte) (FATItems[i] & 0x000f);
            c[index++] = (byte) ((FATItems[i] & 0x00f0) >> 4);
            c[index++] = (byte) ((FATItems[i] & 0x0f00) >> 8);
        }

        // 两个byte合并成一个byte
        index = 0;
        for (int i = 0; i < b.length; i++) {
            b[i] = (byte) (c[index++] | (c[index++] << 4));
        }

        return b;
    }

}

根目录区RootDir:

package my.floppy;

public class RootDir {
    /** 根目录区字节数 224项 * 32B = 7168B = 14个扇区 */
    private byte[] data = new byte[7168];

    /**
     * 向根目录区写入一个文件信息块(32B)
     * @param rootDirEntryByte 一个根目录项byte数组(32字节)
     * @return true-写入成功
     */
    public boolean wrt(byte[] rootDirEntryByte) {
        // 先找到空闲位置, 文件项的第一个字节是0xe5-文件删除, 0x00-不存在文件信息, 暂时不考虑文件删除
        for (int i = 0; i < data.length - 32; i += 32) {
            if(data[i] == 0x00) {
                for (int j = i, x = 0; x < 32; j++, x++) {
                    data[j] = rootDirEntryByte[x];
                }
                return true;
            }
        }
        // 目录项都用完了, 文件信息写入失败
        return false;
    }

    public byte[] rootDirBytes(){
        return data;
    }
}

根目录区文件信息项RootDirEntry:

package my.floppy;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * 根目录区的根目录项类, 存储一个文件的信息
 */
public class RootDirEntry {
    // 文件属性常量
    public static final byte
            /** 只读文件 */
            DIR_ATTR_READONLY = 0x01,
            /** 隐藏文件 */
            DIR_ATTR_HIDD = 0x02,
            /** 系统文件 */
            DIR_ATTR_SYS = 0x04,
            /** 非文件信息(比如磁盘名称等) */
            DIR_ATTR_NOTFILE = 0x08,
            /** 目录 */
            DIR_ATTR_DIR = 0x10,
            /** 一般文件 */
            DIR_ATTR_NORMAL = 0x20;

    /** 文件名8B 扩展名3B, 共11B. 必须为大写!!!经过FreeDOS测试发现的, 小写的文件名无法使用type查看, 提示找不到文件 */
    private String DIR_Name;
    /** 文件属性 1B */
    private byte DIR_Attr;
    /** 保留位 10B */
    private byte[] DIR_Reserved = new byte[10];
    /** 最后一次写入时间 2B */
    private int DIR_WrtTime;
    /** 最后一次写入日期 2B */
    private int DIR_WrtDate;
    /** 文件开始簇号 2B */
    private int DIR_FstClus;
    /** 文件大小 4B */
    private int DIR_FileSize;

    /**
     * 读入文件,向FAT、数据区写入文件数据<br/>
     * 1、将文件数据读入到字节数组中, 可以计算出文件大小(字节数), 文件需要多少个扇区存放 <br/>
     * 2、调用fat对象的allocFATEntry方法为文件分配一系列可用的簇号<br/>
     * 3、在根目录区生成文件信息(文件名称、起始簇号、文件大小等属性需要设置好)<br/>
     * 4、通过步骤2得到的一系列簇号对应到数据区的扇区, 将文件数据写入这些扇区<br/>
     * @param filePath 文件路径, 分隔符要求使用 '/'
     * @param fat FAT区对象
     * @param dataArea 数据区对象
     * @param rootDir 根目录区对象
     * @return 文件字节数组
     * @throws IOException
     */
    public Byte[] readFile(String filePath, FAT fat, DataArea dataArea, RootDir rootDir) throws IOException {
        if(filePath != null && filePath.length() > 0) {
            // 处理文件名和后缀, 文件名最大8B, 后缀最大3B
            int index = filePath.lastIndexOf('/');
            String name = filePath.substring(index + 1);
            index = name.lastIndexOf('.');
            String suffix = "   ";
            if (index > -1) { // 文件名有后缀,没有后缀都是空格填充
                suffix = name.substring(index + 1);
            }
            if(name.indexOf('.') != -1) {
                name = name.substring(0, name.lastIndexOf('.'));
            }
            if(name.length() > 8) name = name.substring(0, 8);
            if(suffix.length() > 3) suffix = suffix.substring(0, 3);

            // 要写入软盘的文件不存在直接返回
            File file = new File(filePath);
            if(!file.exists()) return null;

            // 填充文件名称和后缀名称, 保证文件名称 + 后缀的长度是 11个字节
            String blankChar = "        "; // 先处理文件名 8空格
            if(name.length() < 8) {
                name = new StringBuilder().append(name).append(blankChar.substring(0, 8 - name.length())).toString();
            }
            if(suffix.length() < 3) { // 再处理扩展名 3空格
                suffix = new StringBuilder().append(suffix).append(blankChar.substring(0, 3 - suffix.length())).toString();
            }

            DIR_Name = name.toUpperCase(Locale.ENGLISH) + suffix.toUpperCase(Locale.ENGLISH);
            DIR_Attr = DIR_ATTR_NORMAL;
            DIR_WrtTime = 0x00ff;
            DIR_WrtDate = 0x00ff;
            DIR_FstClus = 2;
            DIR_FileSize = (int) file.length(); // 这里不是真正的文件大小,后面赋值

            // 读取文件数据:根据文件大小从FAT中申请可用簇号, 设置根目录项文件信息, 向数据区写入文件数据
            FileInputStream fis = new FileInputStream(file);
            List<Byte> file_byte = new ArrayList<Byte>();
            byte[] read_buf = new byte[1024];
            int len = -1;
            while ((len = fis.read(read_buf)) != -1) {
                for (int i = 0; i < len; i++) {
                    file_byte.add(read_buf[i]);
                }
            }
            fis.close();
            if(file_byte.size() == 0) throw new IllegalStateException("未读取到文件数据");

            DIR_FileSize = file_byte.size(); // 实际文件大小
            // 文件需要多少个扇区存放, 刚好被512整除时就是 secCnt, 对512取余不等于0的说明还有部分文件数据不满512字节, 也需要一个扇区
            int secCnt = DIR_FileSize / 512;
            if(DIR_FileSize % 512 != 0) secCnt += 1;

            // 从FAT中获取可用的FATEntry, FATEntry == 0可用, FAT12中一个簇是一个扇区,使用了多少个簇即使用了多少个扇区
            Integer[] clusIndex = fat.allocFATEntry(secCnt);
            DIR_FstClus = clusIndex[0]; // 文件起始簇号

            // 结合申请的簇将文件写入数据区, 将簇号和数据区的扇区对应起来
            // FAT12一个簇是一个扇区, 簇号从2开始, 数据区起始扇区号0, 簇号2对应了数据区0扇区, 簇号3对应了数据区1号扇区
            for (int i = 0; i < clusIndex.length; i++) {
                int clusN = clusIndex[i];
                byte[] oneSecByte = new byte[512];
                for (int j = 0; j < 512 && !file_byte.isEmpty(); j++) {
                    // 向数据区写入是以一个扇区为单位, 如果剩余文件内容不足一个扇区时,后面的字节都是0
                    oneSecByte[j] = file_byte.remove(0);
                }
                dataArea.wrt(clusN - 2, oneSecByte);
            }

            // FAT  数据区写入相关数据后, 还需要向根目录区写入文件信息
            boolean flag = rootDir.wrt(rootDirEntryBytes());
            if(!flag) throw new IllegalStateException("根目录区数据写入出错");

            return file_byte.toArray(new Byte[file_byte.size()]);
        }

        return null;
    }

    public byte[] rootDirEntryBytes() throws IOException {
        // 一个目录项32字节
        byte[] rootDirEntryByte = new byte[32];
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        bos.write(DIR_Name.getBytes("utf-8"));
        bos.write(DIR_Attr);
        bos.write(DIR_Reserved);
        bos.write(DIR_WrtTime);
        bos.write(DIR_WrtTime >> 8);
        bos.write(DIR_WrtDate);
        bos.write(DIR_WrtDate >> 8);
        bos.write(DIR_FstClus);
        bos.write(DIR_FstClus >> 8);
        bos.write(DIR_FileSize);
        bos.write(DIR_FileSize >> 8);
        bos.write(DIR_FileSize >> 16);
        bos.write(DIR_FileSize >> 24);
        return bos.toByteArray();
    }
}

数据区DataArea:

package my.floppy;

public class DataArea {
    /** 19 + 14 = 33扇区, 2880 - 33 = 2847, 剩余2847个扇区给到数据区, 2847 * 512 =  */
    private byte[] data = new byte[2847 * 512];

    /**
     * 将数据写入指定扇区
     * @param sec 指定数据区的扇区编号[0 ~ 2846]
     * @param data 文件一扇区数据, 最大写入512(一个扇区), 右外层调用者控制写入扇区数
     * @return 是否成功
     */
    public boolean wrt(int sec, byte[] data) {
        if(sec >= 0 && sec <= 2846) {
            int index = sec * 512;
            for (int i = 0; i < data.length; i++, index++) {
                this.data[index] = data[i];
            }

            return true;
        } else {
            return false;
        }
    }

    public byte[] dataAreaByte(){
        return data;
    }
}

主程序Floppy144:

package my.floppy;

import java.io.*;

public class Floppy144 {
    /** 1.44MB软盘字节数 = 每个扇区字节数 * 扇区数 */
    private static int floppy144Size = 512 * 2880;
    /** 整张1.44MB软盘字节数据存放在byte数组中, 准备完成后直接将这个byte数组中的数据写出到img文件中 */
    private static byte[] floppy144ImgBytes = new byte[floppy144Size];

    public static void main(String[] args) throws IOException {
        BootSector bootSector = new BootSector();
        FAT fat = new FAT(); // FAT12文件系统中FAT表是2个, 写出2次就行
        RootDir rootDir = new RootDir();
        DataArea dataArea = new DataArea();

        String filePath = "";
        RootDirEntry rootDirEntry = new RootDirEntry();

        filePath = "D:/projects/diy-x86os-myself/diy-200lines-os/n00.01/start.bin";
        rootDirEntry.readFile(filePath, fat, dataArea, rootDir);
        filePath = "D:/projects/diy-x86os-myself/diy-200lines-os/n00.01/os.bin";
        rootDirEntry.readFile(filePath, fat, dataArea, rootDir);
        filePath = "C:/Users/ChenHao/Desktop/loader.txt";
        rootDirEntry.readFile(filePath, fat, dataArea, rootDir);

        // 0 - 启动扇区
        // wrt(0, bootSector.toByteArray());
        wrt(0, bootSector.loadFromFile("D:/projects/diy-x86os-myself/diy-200lines-os/n00.01/boot.bin"));
        // 1 - 9扇区 FAT1
        wrt(1, fat.FATBytes());
        // 10 - 18扇区 FAT2, FAT1/FAT2数据是一样的,写入2次
        wrt(10, fat.FATBytes());
        // 19 - 32扇区 根目录区
        wrt(19, rootDir.rootDirBytes());
        // 33 - 结束 数据区
        wrt(33, dataArea.dataAreaByte());

        // 保存软盘数据成img文件
        ByteArrayOutputStream bos = new ByteArrayOutputStream(floppy144Size);
        bos.write(floppy144ImgBytes);
        String outputFilePath = "C:\\Users\\ChenHao\\Desktop\\floppy144.img"; // 这里修改成输出img文件的路径
        FileOutputStream fos = new FileOutputStream(new File(outputFilePath));
        bos.writeTo(fos);
        fos.close();
    }

    /**
     * 向1.44MB软盘写入输入,
     * @param sec 起始扇区号, 从0开始
     * @param data 要写入的数据
     * @return 返回结束扇区的下一扇区号
     */
    public static int wrt(int sec, byte[] data) {
        int index = sec * 512;
        for (int i = 0; i < data.length && index < floppy144ImgBytes.length; i++, index++) {
            floppy144ImgBytes[index] = data[i];
        }

        int endSec = index / 512;
        if(index % 512 != 0) endSec += 1;

        return endSec;
    }
}

测试,通过VirtualBox运行的FreeDOS系统查看的软盘映像文件,命令行数据A: 进入软盘映像文件,此时的软盘映像文件是作为一块软盘识别的,来使用一下dir命令,同时使用了type 命令查看loader.txt文件的内容

一张完整大图:

参考资料:

余渊《Orange'S:一个操作系统的实现》第103页.

川合秀实《30天自制操作系统》第24页、第366页.

  • 1
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值