各位如果看到博客内有广告,可以动手点一点,谢谢

MENU

用Java做一个最小的操作系统内核2

November 19, 2018 • Read: 761 • 操作系统

上一节,我用Java制作了一个虚拟软盘,当把虚拟软盘插入虚拟机,启动机器后,屏幕就打印出了Java程序中设定的语句,然后卡死。

在Java代码中,有一个二进制数组imgContent,它里面存储的实际上是一端二进制代码,当虚拟机设为从软盘启动后,这段代码会被BIOS读到内存中,然后指示CPU去执行imgContent所存储的二进制代码

汇编代码

下面我就将imgContent数组中的内容,用汇编语言实现,并且详细阐述其作用,首先看看汇编语言怎么写

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

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

首先看第一行代码org 0x7c00org的意思是“起始,起源”,org后面的7c00是物理内存地址,假设物理内存是一个字节数组,例如byte[] memory,如果你有2M内容,就需要new2097152字节的内存,byte[] memory = new byte[2097152]org 0x7c00的意思就是将汇编编译后的二进制数据从memory[0x7c00]处写入memory。至于为什么是0x7c00,这就要问微软

jmp entry中的jmp起始就是c语言中的gotojmp entry其实是让CPU跳转到entry处,执行entry下面的代码,如果entry是一个函数名,jmp entry相当于调用entry()函数

db 0x90RESB 18这段代码做的是一些初始化工作。jmp entry对应的机器码长度是3字节,那么db 0x90的作用就是memory[0x7c00+3]=9x90,也就是说db 9x90实际上做的是赋值操作

DBdb是相同的作用,所以DB "OSKERNEL"的意思是strcpy(memory + 0x7c00 + 3 + 1,"OSKERNEL"),也就是把"OSKERNEL"这个字符串拷贝到内存0x7c00 + 3 + 1处。3就是jmp entry 所占的3字节长度,1就是db ox90所复制的那个字节的长度

DWDB是相同的意思,只不过DB是将数据赋值给一个字节,DW是将数据赋值给两个字节,从上面代码也能看出来DW 512,因为512转换为二进制肯定超过了8位,所以不能用DB

DD 0xFFFFFFFF就是将0xFFFFFFFF存储到四个字节长的内存中

RESB 18表示把接下来的18个字节的内容全部初始化为0,类似于下面的Java代码

byte[] block = new byte[18];
for(int i = 0;i < 18;i++)
    block[i] = 0;

接下来看entry代码,它的作用是初始化一系列寄存器,寄存器相当于Java程序中定义的变量,ax是一个2字节长的寄存器,mov ax,0就是把数值0放到ax寄存器中,类似Java的char ax = 0,char类型的数据在Java中也是2字节长。

类似的,mov ss,ax相当于Java中的char ss = ax

其中比较重要的语句是mov si,msgmsg相当于一段内存

msg:
    DB    0x0a,  0x0a
    db    "hello, world"
    db    0x0a

上面这段代码类似于C语言中的char* msg = "\n\nhello,world\n"\n的ASCII值就是0x0a。那么mov si,msg就相当于把msg内存的真实地址放到寄存器si里,如果用C语言表示就是char* si = msg

接着看后面的代码mov al,[si][si]表示读取si存储的内存地址中一个字节长度的信息,这行代码的含义就是把[si]内容数据存储到寄存器al中,ax是两字节长的寄存器,这样ax就可以分解成两部分,第一部分是al,第二部分是ah,对应C语言就相当于char ax[2]al表示的是ax[0]ah表示的是ax[1]mov ai,[si]转换成C语言就是char al = *si

add si,1表示将寄存器si中的数值加1,也就相当于C语言中的si++

cmp al,0表示将寄存器al寄存器中的数据跟0比较,看al中的值是否等于0

je fin中的je表示jump if equal,也就是如果al的值确实等于0,那么就跳转到fin所表示的代码处去执行,转换成C语言就是

if(al == 0)
    goto fin

mov ah,0xe就是把0xe赋值给ahmov bx,15同理

接下来调用的是一个中断函数,我们在写C或Java程序时,往往需要调用一些系统库函数,例如printf,或System.out.print,中断函数就是BIOS提供给汇编语言的库函数,这些库函数都存放在一个数组里,int 0x10的意思是在库函数数组中取出第0x10个库函数执行

有的函数调用时需要传递参数,如果想要调用BIOS提供的函数,在屏幕上输出字符,那么就要将传递的参数放入到指定的寄存器中。BIOS提供的编号为0x10的库函数可以实现这个功能,按照规定,要把寄存器ah的值设置为0x0e,把要输出字符的ASCII值放到寄存器al,同时要把寄存器bh的值设为0,字符的颜色可以通过寄存器bl的值来设定。

代码段

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

就相当于C语言中的

do {
    char al = *si;
    si++;
    if(al == 0)
        goto fin
    print("%c",al);
} while(true);

fin处的代码就两条语句,htl表示halt,也就是让CPU进入休眠状态,如果此时我们点击一下键盘或动一下鼠标,那么CPU就会被唤醒,然后执行hlt后面的语句jmp fin,又回到fin开始处去执行,进入无限循环

编译汇编代码

把上面的汇编代码存储成一个文件:boot.asm,然后利用汇编编译器nasm来编译,nasm boot.asm,编译后会得到一个二进制文件,内容如下:

e94e 0090 4f53 4b45 524e 454c 0002 0101
0002 e000 400b f009 0012 0002 0000 0000
0040 0b00 0000 0029 ffff ffff 4d59 4649
5253 544f 5320 2046 4154 3132 2020 2000
0000 0000 0000 0000 0000 0000 0000 0000
00b8 0000 8ed0 8ed8 8ec0 be73 7c8a 0481
c601 003c 0074 09b4 0ebb 0f00 cd10 ebed
f4eb fd0a 0a68 656c 6c6f 2c20 776f 726c
640a 00

修改Java代码

把这个二进制文件改名为boot.bat,拷贝到上一个Java程序的工程目录下,然后把Java程序进行修改,将这段二进制数据都入到imgContent数组中,代码如下

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

public class OperatingSystem {
    private int[] imgContent = new int[] { 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f, 0x49, 0x50, 0x4c, 0x00, 0x02,
            0x01, 0x01, 0x00, 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00, 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff, 0xff, 0xff, 0xff, 0x48, 0x45, 0x4c, 0x4c, 0x4f, 0x2d,
            0x4f, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x8e, 0xd0,
            0xbc, 0x00, 0x7c, 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a, 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74,
            0x09, 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb, 0xee, 0xf4, 0xeb, 0xfd };

    private ArrayList<Integer> imgByteToWrite = new ArrayList<Integer>();

    private void readKernelFromFile(String fileName) {
        File file = new File(fileName);
        InputStream in = null;
        try {
            in = new FileInputStream(file);
            int tempbyte;
            while ((tempbyte = in.read()) != -1) {
                imgByteToWrite.add(tempbyte);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        imgByteToWrite.add(0x55);
        imgByteToWrite.add(0xaa);
        imgByteToWrite.add(0xf0);
        imgByteToWrite.add(0xff);
        imgByteToWrite.add(0xff);
    }

    public OperatingSystem(String s) {
        readKernelFromFile("boot.bat");
        int len = 0x168000;
        int curSize = imgByteToWrite.size();
        for (int l = 0; l < len - curSize; l++) {
            imgByteToWrite.add(0);
        }
    }

    public void makeFllopy() {
        try {
            DataOutputStream out = new DataOutputStream(new FileOutputStream("system.img"));
            for (int i = 0; i < imgByteToWrite.size(); i++) {
                out.writeByte(imgByteToWrite.get(i).byteValue());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        OperatingSystem op = new OperatingSystem("hello, this is my first line of my operating system code");
        op.makeFllopy();
    }
}

启动虚拟机

执行后,上面的代码会生成一个虚拟软盘system.img,把该软盘加入虚拟机,配置虚拟机从软盘启动,得到结果如下:

Archives Tip
QR Code for this page
Tipping QR Code
Leave a Comment

已有 2 条评论
  1. jassor jassor

    大大大大神好厉害!这是用java写的虚拟机内核吗?@(太开心)我要多长时间才能学会这个呀!

    1. mathor mathor

      @jassor是的,操作系统内核,我会持续更新,只要跟着学就能会