用java做操作系统内核:软盘读写

在前两节,我们将一段代码通过软盘加载到了系统内存中,并指示cpu执行加入到内存的代码,事实上,操作系统内核加载也是这么做的。只不过我们加载的代码,最大只能512 byte, 一个操作系统内核,少说也要几百兆,由此,系统内核不可能直接从软盘读入系统内存。

通常的做法是,被加载进内存的512 Byte程序,实际上是一个内核加载器,它运行起来后,通过读取磁盘,将存储在磁盘上的内核代码加载到指定的内存空间,然后再把cpu的控制权提交给加载进来的系统内核。

这就需要我们理解软盘的物理结构,以及软盘的数据读取方法。

软盘的物理结构如上图,一个盘面被划分成若干个圆圈,例如图中的灰色圆圈,我们称之为磁道,也可以称作柱面,一个磁道或柱面,又被分割成若干部分,每一部分,我们称之为一个扇区,一个扇区的大小正好是512k,从而,当我们把数据存储到软盘上时,数据会分解成若干个512Byte大小的块,然后写入到扇区里。

要读取数据时,磁头会挪动到扇区所在的磁道或柱面,然后盘面转动,当要读取的扇区转到磁头正下方时,磁头通电,通过电磁效应将扇区的数据读取到内存中。

从上图的左边图形可以了解,一个磁盘有两个盘面,每个盘面的组成跟右边图形一样,同时每个盘面对应一个磁头,所以当想从磁盘上读取数据时,需要确定数据在哪一个盘面,从而确定要用哪一个磁头来读取数据,然后确定哪一个磁道,最后再确定要读取的数据都存储在哪一个扇区。

对于我们要开发的系统,我们要模拟的是3.5寸软盘,这种软盘的特点是,它有两个盘面,因此就对应两个磁头,每个盘面有80个磁道,也就是柱面,编号分别为0-79. 每个柱面都有18个扇区,编号分别为1-18. 所以一个盘面可以存储的数据量大小为:

512 * 18 * 80

一个软盘有两个盘面,因此一个软盘可以存储的数据为:

2 * 512 * 18 * 80 = 1474560 Byte = 1440 KB = 1.5M

接下来,我们用java来模拟一个3.5寸软盘,以及它的读写逻辑。

Floppy.java 用于实现虚拟软盘

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;

public class Floppy {
    enum MAGNETIC_HEAD {
        MAGNETIC_HEAD_0,
        MAGETIC_HEAD_1
    };

    public int SECTOR_SIZE = 512;
    private int CYLINDER_COUNT = 80; //80个柱面
    private int SECTORS_COUNT = 18;
    private MAGNETIC_HEAD magneticHead = MAGNETIC_HEAD.MAGNETIC_HEAD_0;
    private int current_cylinder = 0;
    private int current_sector = 0;

    private HashMap<Integer,ArrayList<ArrayList<byte[]>> > floppy = new HashMap<Integer,ArrayList<ArrayList<byte[]>> >(); //一个磁盘两个面

    public Floppy() {

        initFloppy();
    }

    private void initFloppy() {

        //一个磁盘有两个盘面
        floppy.put(MAGNETIC_HEAD.MAGNETIC_HEAD_0.ordinal(), initFloppyDisk());
        floppy.put(MAGNETIC_HEAD.MAGETIC_HEAD_1.ordinal(), initFloppyDisk());
    }

    private ArrayList<ArrayList<byte[]>> initFloppyDisk() {
        ArrayList<ArrayList<byte[]>> floppyDisk = new ArrayList<ArrayList<byte[]>>(); //磁盘的一个面
        //一个磁盘面有80个柱面
        for(int i = 0; i < CYLINDER_COUNT; i++) {
            floppyDisk.add(initCylinder());
        }

        return floppyDisk;
    }

    private ArrayList<byte[]> initCylinder() {
        //构造一个柱面,一个柱面有18个扇区
        ArrayList<byte[]> cylinder = new ArrayList<byte[]> ();
        for (int i = 0; i < SECTORS_COUNT; i++) {
            byte[] sector = new byte[SECTOR_SIZE];
            cylinder.add(sector);
        }

        return cylinder;
    }

    public void setMagneticHead(MAGNETIC_HEAD head) {
        magneticHead = head;
    }

    public void setCylinder(int cylinder) {
        if (cylinder < 0) {
            this.current_cylinder = 0;
        }
        else if (cylinder >= 80) {
            this.current_cylinder = 79;
        }
        else {
            this.current_cylinder = cylinder;
        }
    }

    public void setSector(int sector) {
        //sector 编号从1到18
        if (sector < 0) {
            this.current_sector = 0;
        }
        else if (sector > 18) {
            this.current_sector = 18 - 1;
        }
        else {
            this.current_sector = sector - 1;
        }
    }

    public byte[] readFloppy(MAGNETIC_HEAD head, int cylinder_num, int sector_num) {
        setMagneticHead(head);
        setCylinder(cylinder_num);
        setSector(sector_num);

        ArrayList<ArrayList<byte[]>> disk = floppy.get(this.magneticHead.ordinal());
        ArrayList<byte[]> cylinder = disk.get(this.current_cylinder);

        byte[] sector = cylinder.get(this.current_sector);

        return sector;
    }

    public void writeFloppy(MAGNETIC_HEAD head, int cylinder_num, int sector_num, byte[] buf) {
        setMagneticHead(head);
        setCylinder(cylinder_num);
        setSector(sector_num);

        ArrayList<ArrayList<byte[]>> disk = floppy.get(this.magneticHead.ordinal());
        ArrayList<byte[]> cylinder = disk.get(this.current_cylinder);
        cylinder.set(this.current_sector, buf);
    }

    public void makeFloppy(String fileName) {
        try {
            DataOutputStream out = new DataOutputStream(new FileOutputStream(fileName));
            for (int head = 0; head <= MAGNETIC_HEAD.MAGETIC_HEAD_1.ordinal(); head++) {
                for (int cylinder = 0; cylinder < CYLINDER_COUNT; cylinder++) {
                    for (int sector = 1; sector <= SECTORS_COUNT; sector++) {
                        byte[] buf = readFloppy(MAGNETIC_HEAD.values()[head], cylinder, sector);
                        out.write(buf);
                    }
                }
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

读写虚拟软盘需要调用结构readFloppy 或 writeFloppy, 使用这些接口时必须指定磁头,柱面和扇区号,在主程序中,我将上节用汇编编译的操作系统内核写入到虚拟软盘中,然后将虚拟软盘写成磁盘文件, 具体代码如下:

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

public class OperatingSystem {

    private Floppy floppyDisk = new Floppy();

    private void writeFileToFloppy(String fileName) {
        File file = new File(fileName);
        InputStream in = null;

        try {
            in = new FileInputStream(file);
            byte[] buf = new byte[512];
            buf[510] = 0x55;
            buf[511] = (byte) 0xaa;
            if (in.read(buf) != -1) {
                //将内核读入到磁盘第0面,第0柱面,第1个扇区
                floppyDisk.writeFloppy(Floppy.MAGNETIC_HEAD.MAGNETIC_HEAD_0, 0, 1, buf);
            }
        } catch(IOException e) {
            e.printStackTrace();
            return;
        }
    }

    public OperatingSystem(String s) {
        writeFileToFloppy(s);
    }

    public void makeFllopy()   {
        floppyDisk.makeFloppy("system.img");
    }

    public static void main(String[] args) {
        OperatingSystem op = new OperatingSystem("boot.bat");
        op.makeFllopy();
    }
}

上面的代码运行后,在项目的根目录会生成一个system.img虚拟软盘文件,将该软盘加入虚拟机,我们制作的操作系统内核就能被虚拟机执行了:

上面代码可以通过如下git 命令下载:

git clone https://github.com/wycl16514/OS-kernel-development.git

用汇编语言实现软盘读写

使用汇编读取软盘的原理与上面描述的相同,我们要调用相应的BIOS中断,同时将要读取的磁头号,柱面,扇区号传给中断代码,读取软盘的汇编代码如下:

mov CH, 1 ;CH 用来存储柱面号

mov DH, 0 ;DH 用来存储磁头号

mov CL, 2 ;CL 用来存储扇区号

mov BX, msg ; ES:BX 数据存储缓冲区

mov AH, 0x02 ; AH = 02 表示要做的是读盘操作

mov AL, 1 ; AL 表示要练习读取几个扇区

mov DL, 0 ;驱动器编号,一般我们只有一个软盘驱动器,所以写死

;为0

INT 0x13 ;调用BIOS中断实现磁盘读取功能

JC error ; 如果读盘出现错误,跳转到error处执行相应代码

在计算机硬件中,有一个隐含的寄存器叫FLACS, 当BIOS调用出现错误时,FLACS寄存器的 CF 位会置1,同时把错误代码存入AH,如果没有错,那么FLACS寄存器的CF位会设置为0,所以 JC error 的意思是:

if (FLACS.CF == 1) {

error();

}

在前面,我们的内核加载到内存后,会打印出一条语句,而语句与内核代码都存储在同一个扇区中,这一次,我们将要打印的语句存储在第一柱面的第二扇区,内核加载如内存后,通过BIOS调用将要打印的语句从指定位置读出,然后再显示到屏幕上,代码如下:

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        ;CH 用来存储柱面号
    mov          DH, 0        ;DH 用来存储磁头号
    mov          CL, 2        ;CL 用来存储扇区号

    mov          BX, msg       ; ES:BX 数据存储缓冲区

    mov          AH, 0x02      ;  AH = 02 表示要做的是读盘操作
    mov          AL,  1        ; AL 表示要练习读取几个扇区
    mov          DL, 0         ;驱动器编号,一般我们只有一个软盘驱动器,所以写死
                               ;为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   ;出现错误打印error
    jmp   putloop

msg:
    RESB   64
errmsg:
    DB "error"

在上面的汇编代码中,readFloppy将要打印的字符串从磁盘中读出来,放入到缓冲区msg中,大家注意,msg下面是语句 RESB 64, 这一句的意思是分配64个字节的缓冲区。

将上面的汇编代码存成文件boot.asm ,然后使用命令:

nasm boot.asm -o boot.bat

把上面代码编译成二进制文件boot.bat.接下来,我们在生成虚拟软盘的java代码中把把要输出的语句写入到虚拟软盘的1柱面,2扇区,代码如下:

   public void makeFllopy()   {
        String s = "This is a text from cylinder 1 and sector 2";
        floppyDisk.writeFloppy(Floppy.MAGNETIC_HEAD.MAGNETIC_HEAD_0, 1, 2, s.getBytes());

        floppyDisk.makeFloppy("system.img");
    }

上面的代码中,我们把s字符串写入虚拟软盘的0磁头,1柱面,2扇区。把上面编译好的boot.bat也放入到java工程目录下,然后运行java代码,运行后在目录下会生成一个system.img虚拟映像,然后将该文件作为启动软盘加入虚拟机:

然后启动虚拟机,可以看到,我们的系统内核将字符串从软盘的1柱面,2扇区中读出来,显示到屏幕上:

代码可以根据以下的git 命令获取:

git clone https://github.com/wycl16514/OS-kernel-development.git

时间: 2024-11-10 13:28:12

用java做操作系统内核:软盘读写的相关文章

从操作系统内核看Java非阻塞IO事件检测

非阻塞服务器模型最重要的一个特点是,在调用读取或写入接口后立即返回,而不会进入阻塞状态.在探讨单线程非阻塞IO模型前必须要先了解非阻塞情况下Socket事件的检测机制,因为对于非阻塞模式最重要的事情是检测哪些连接有感兴趣的事件发生,一般会有如下三种检测方式. 应用程序遍历socket检测 如图所示,当多个客户端向服务器请求时,服务器端会保存一个socket连接列表,应用层线程对socket列表进行轮询尝试读取或写入.对于读取操作,如果成功读取到若干数据则对读取到的数据进行处理,读取失败则下个循环

AACOS:基于编译器和操作系统内核的算法设计与实现

AACOS:基于编译器和操作系统内核的算法设计与实现 [计算机科学技术] 谢晓啸 湖北省沙市中学 [关键词]: 编译原理,操作系统内核实现,算法与数据结构,算法优化 0.索引 1.引论 1.1研究内容 1.2研究目的 1.3研究提要 正文 2.1研究方法 2.2编译器部分 2.2.1从计算器程序中得到的编译器制作启示 2.2.2在编译器中其它具体代码的实现 2.2.3编译器中栈的高级应用 2.2.3编译器中树的高级应用 2.2.4编译器与有限状态机 2.3操作系统内核部分 2.3.1操作系统与底

Unix操作系统内核结构报告

Unix操作系统内核结构报告 1.有一个程序的代码如下: main() { int i ; for(i=0; i<3; i++) fork(); } 请问该程序运行时共建立了多少个进程?请用进程家族树来画出父子进程之间的关系. 解:一共建立了7个进程. 2.UNIX 系统中用“最近最少使用(LRU)” 算法来构建数据缓冲池.如果核心采用“先进先出(FIFO)”算法来构建缓冲池,则对缓冲区算法 getblk 来说,会造成功能上的区别主要有哪些? 解:getblk是把缓冲区分配给磁盘块的一个算法.用

操作系统内核的绝佳学习材料——JOS

操作系统内核的绝佳学习材料--JOS 前言:关于JOS和一些经验之谈 这一学期的操作系统课使用的是MIT用于教学的JOS操作系统,并且StonyBrook在其基础上做了大量改动,最重要的变化就是从32位移植到了64位.因为个人之前曾系统学习过Linux 0.11内核(<操作系统内核Hack:(四)内核雏形>,实现到时钟中断部分停下了),深知自己从零开始实现内核的工作量.即便是如我个人实现的MiniOS这种简单的不能再简单的,也是需要花费很多时间和精力的.虽然这些付出非常值得(在上这门课时给我带

操作系统内核Hack:(三)BootLoader制作

操作系统内核Hack:(三)BootLoader制作 关于本文涉及到的完整源码请参考MiniOS的v1_bootloader分支. 1.制作方法 现在我们已经了解了关于BootLoader的一切知识,让我们开始动手做一个BootLoader吧!但真正开始之前,我们还要做出一个选择,在之前的讨论中我们曾说过,有两种学习和制作引导程序和操作系统内核的路线:1)<Orange's:一个操作系统的实现>书中的路线:2)Linux 0.11的路线. 1.1 两种实现思路 具体来说,第一种路线就是将Boo

操作系统内核Hack:(一)实验环境搭建

操作系统内核Hack:(一)实验环境搭建 三四年前,心血来潮,入手<Orange's:一个操作系统的实现>学习操作系统内核,还配套买了王爽的<汇编语言(第二版)>和<80X86汇编语言程序设计教程>,虽然Orang's只看了不到三分之一,但当时还是很认真的,练习也做了不少.唯一遗憾的就是没有留下文字记录,导致现在忘得差不多一干二净了,后悔不已!如今想再捡起来,弥补当时的懒惰,虽然困难重重,但这么优秀的国产书怎么能看完就算了呢!而且当年还是在Windows下练习的,现在终

Redis介绍 &amp;&amp; Java客户端操作Redis

Redis介绍 && Java客户端操作Redis 本文内容 redis介绍 redis的 shell 客户端简介 redis的 java 客户端简介 环境配置 redis 2.8.17 64bit JDK1.6 redis介绍 大多数时候,我们都将 redis 称作是内存数据库,它在运行过程中,将键值对信息存储在内存中,同时在后台以异步的形式写入本地数据库中(默认是:dump.rdb,在 redis.conf 中配置,如果需要考虑安全的持久化需求需要开启 AOF 功能,详细介绍可以查看这

Java File操作汇总

作者:卿笃军 原文地址:http://blog.csdn.net/qingdujun/article/details/41223841 本文通过大量的示例,介绍和讲解了Java File操作. 1)创建文件  2)删除文件  3)判断文件是否存在  4)创建文件夹  5)文件类型判断  6)获取文件信息 7)获取目录下文件名  8)递归打印所有文件名  9)递归删除整个文件夹  10)Properties类 11)SequenceInputStream类:连接多个流  12)对象序列化实现Ser

HDFS基础和java api操作

1. 概括 适合一次写入多次查询情况,不支持并发写情况 通过hadoop shell 上传的文件存放在DataNode的block中,通过linux shell只能看见block,看不见文件(HDFS将客户端的大文件存放在很多节点的数据块中,Block本质上是一个逻辑概念,它是hdfs读写数据的基本单位) HDFS中,如果一个文件小于一个数据块的大小,并不占用整个数据块存储空间 2. fs 可以使用hdfs shell操作hdfs,常用 fs命令如下: eg: hadoop fs -cat fi