1 汇编实现
1.1 汇编实现
汇编代码如下:
org 0x7c00;
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
上面的汇编代码主要是设置了一些初始化数据,然后调用bios中断,将某个缓冲区中的字符打印到屏幕上,然后进入一个死循环。代码段的详细解释如下:
/*
org 的意思是origin, 中文意思是“起始,起源,” org 后面的7c00 是物理内存地址,假设物理内存是一个byte类型
的大数组,例如byte[] memory, 如果你有2 G内容,换算成字节就是2097152, 也就相当于memory数组有2097152字
节,于是当虚拟机上电,然后new一块内存 byte[] memory = new byte[2097152]. org 0x7c00 的意思是将本汇编
编译后的二进制数据从memory[0x7c00]处写入memory.
*/
org 0x7c00;
/*
jmp entry 中的jmp 其实就是c语言中的语句goto, jmp entry 其实是让cpu跳转到entry 处,执行entry下面的代
码,如果entry是一个函数名字的话,jmp entry 相当于调用entry函数,类比于java就是函数调用:entry();
*/
jmp entry
// 下面的代码一直到RESB 18这行代码是可以直接删除的,我这里之所以放在这里是为了记录一下汇编知识点
/*
jmp entry 对应的机器代码,长度是3字节,那么db 0x90 的意思就是 memory[0x7c00+3] = 0x90, 也就是说db
0x90 实际上做的是赋值操作,db 0x90 表示将给定位置处的一个字节赋予数值0x90, 赋值的内存位置就在0x7c00+3处。
*/
db 0x90
/*
DB 和 db 是同一个意思, 那么DB “OSKERNEL” 意思是,strcpy(memory + 0x7c00 + 3 + 1, “OSKERNEL”); 也就
是把”OSKERNEL”这个字符串拷贝到内存0x7c00 + 3 + 1 处, 3是什么意思呢,3就是jmp entry 编译成二进制代码后
的数据长度, 1 就是db 0x90 所所赋值的那个字节的长度。DW 跟DB是一个意思, DB 是将数据赋值给一个字节,由于
一个字节只有8位,那么赋值给这个字节的数据大小不能超过256, 512大于256,所以需要两个字节才能存储512这个数
据,DD 0xFFFFFFFF 就是把0xFFFFFFFF存储到四个字节长的内存中, 语句RESB 18 表示把接下来的18个字节的内存全
部初始化为0,转换为java代码就类似于:
byte[] block = new byte[18];
for (int i = 0; i < 18; i++) {
block[i] = 0;
}
*/
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:
/*
先做的是初始化一系列寄存器,寄存器其实相当于java程序中,我们定义的变量,ax 是一个2字节长的寄存器, mov
ax, 0是把数值0放入到ax寄存器中,类比于java 就是:
char ax = 0;
char类型的数据在java中是两个字节长,跟寄存器ax的长度一样,类似的,语句: mov ss, ax 相当于java的 char
ss = ax; 后面的语句意思类推。
*/
mov ax, 0
mov ss, ax
mov ds, ax
mov es, ax
/*
我们要注意看语句 mov si, msg. msg 相当于一段内存,
msg:
DB 0x0a, 0x0a
db “hello, world”
db 0x0a
就类似于 C 语言中的char* msg = “\n\nhello,world\n”. 字符’\n’的ascii值就是0xa.
mov si, msg 就相当于把msg内存的起始地址放入到寄存器si里。如果用C语言做类比,那么就相当于: char* si = msg;
*/
mov si, msg
putloop:
/*
mov al, [si]
[si]表示读取si存储的内存地址处的一个字节长度的信息,
mov al, [si] ,把该字节的数据存储到寄存器al 中,ax是两个字节长度的寄存器,这样ax就可以分解成两部分,第一
部分就对应于al, 第二部分就对应于ah,
也就是al, ah合起来就是ax, 对应于C语言就相当于 char ax[2], al 表示的是ax[0], ah 表示的就是 ax[1], mov al, [si], 转换成C语言就是 char al = *si;
*/
mov al, [si]
/* add si, 1 表示将寄存器si中的数值加1,也就相当于C语言的 si++; */
add si, 1
/* cmp al, 0 表示将al寄存器中的数据跟0比较,看al中的值是否等于0 */
cmp al, 0
/*
je fin 中的 je 表示 jump if equal, 也就是如果al 的值确实等于0,那么就跳转到fin所表示的代码处去执行,转
换成C语言就是 :
if (al == 0) {
goto fin
}
*/
je fin
/*
mov ah, 0xe 就是把0xe赋值给寄存器 ah, mov bx ,15 同理。接下来要调用一个中断,中断其实就是一个函数调用,
我们在写c语言或java程序时,往往需要调用一些系统库函数,例如printf, 或java的System.out.print. 中断就是
bios提供给汇编语言的库函数,这些库函数都放入到一个数组里,int 0x10 意思是在库函数数组中取出第0x10个库函
数,然后执行该库函数的代码。
我们知道,函数调用时需要传递参数,那么调用bios提供的函数时,怎么传递参数呢,做法是,把需要传递的参数放入到
指定的寄存器中,例如想要在屏幕上输出字符,那么bios提供的编号为0x10的库函数可以实现这个功能,同时按规定,
要把寄存器ah设置为0x0e, 把要输出的字符的ascii值放入到寄存器al, 同时要把寄存器设bh的值设置成0,字符的颜色
可以通过寄存器bl的值来设定。看起来相当麻烦,这是由于我们做的是非常底层的编程,所以麻烦也就不可避免。
*/
mov ah, 0x0e
mov bx, 15
int 0x10
jmp putloop
/*
于是代码片段:
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
}
printf(“%c”, al);
} while(true);
*/
/*
hlt 表示 halt, 也就是让cpu进入休眠状态,如果此时我们点击一下键盘,或动一下鼠标,那么cpu就被唤醒,然后执行hlt后面的语句:
jmp fin
也就是跳转到fin开始处去执行,也就是进入了死循环。
*/
fin:
HLT
jmp fin
msg:
DB 0x0a, 0x0a
db "hello, world"
db 0x0a
db 0
1.2 使用nasm编译器进行编译
nasm boot.asm -o boot.bat 。
2 利用java生成软盘文件
2.1 利用java生成软盘文件的代码如下
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 ArrayList<Integer> imgByteToWrite = new ArrayList<Integer>();
private void readKernelFormatFile(String fileName)
{
File file = new File(fileName);
FileInputStream in = null;
try
{
in = new FileInputStream(file);
int val = 0;
while ((val = in.read()) != -1)
{
imgByteToWrite.add(val);
}
in.close();
}
catch (IOException e)
{
e.printStackTrace();
return;
}
// 当前代码之后至510字节全部写0
int len = 510;
int curlen = imgByteToWrite.size();
for (int i=curlen; i<len; i++)
{
imgByteToWrite.add(0);
}
// 第511、512字节为磁盘主引导扇区的有效标志,必须为0x55、0xaa
imgByteToWrite.add(0x55);
imgByteToWrite.add(0xaa);
//imgByteToWrite.add(0xf0);
//imgByteToWrite.add(0xff);
//imgByteToWrite.add(0xff);
}
public OperatingSystem(String fileName) {
readKernelFormatFile(fileName);
// 1.44MB大小的软盘
int len = 0x168000;
int curSize = imgByteToWrite.size();
for (int i=curSize; i<len; i++) {
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
OperatingSystem op = new OperatingSystem("boot.bat");
op.makeFllopy();
}
}
参考资料:
本文介绍了一种使用汇编语言打印字符串至屏幕并利用Java生成软盘文件的方法,通过具体代码示例详细解析了汇编指令的功能及nasm编译器的使用,同时展示了如何通过Java代码创建软盘映像。
828

被折叠的 条评论
为什么被折叠?



