在第一节<(1)汇编写入引导区,虚拟机启动步骤>中讲解到一个简单屏幕显示一川字符串,第二节讲到BIOS启动过程!c++
第一节中基本原理就是将那个汇编代码用nasm汇编器进行汇编成二进制,而后把这二进制文件写入模拟的软盘system.img[磁盘]的第0面0磁道第1扇区中!而后虚拟机加载此映射文件。编程
BIOS读取硬盘0盘面0磁道1扇区[0磁头0柱面1扇区](C0-H0-S1)的MBR(主引导记录)到内存中指定区域(具体是BIOS提供的int 19中断例程加载MBR到RAM的0X00007C00H开始处),设置程序计数器到指定区域(EIP=0X00007C00),而后CPU开始执行MBR的指令(即CPU使用权交由MBR来掌控)。函数
因为如今系统内核通常都很大很大,MBR根本存储不下,因而人们想到了用boot loader(加载别处指定硬盘位置数据到内存指定位置,而后跳转CPU到内存指定位置在执行指令,这样就跳出了512限制),而后CPU就能够执行命令了。oop
简单的说,整个开机流程到操做系统以前的动做应该是这样的:测试
1.BIOS:开机主动执行的韧体,会认识第一个开机的设备this
2.MBR:第一个可开机设备的第一个扇区内的主引导分区块,内含引导加载程序。spa
3.引导加载程序:一个可读取内核文件来执行的软件。操作系统
4.内核文件:开始操做系统的功能。.net
BIOS与MBR都是硬件自己会支持的功能,至于Boot loader(引导加载程序)则是操做系统安装在MBR上面的一套软件。因为MBR仅有466bytes而已,所以这个引导程序是很是小而完美的。这个boot loader的主要任务有下面几项:code
1)那么首先开始写主引导boot程序(做用:将硬盘的第0面0磁道2扇区[0磁头0柱面2扇区](C0-H0-S2)读取1个扇区的内容到内存中0X8000位置,而后跳转到这个位置执行指令)
实例1、
org 0x7c00 ;指定起始位置 LOAD_ADDR EQU 0X8000 ;加载地址位置 entry: mov ax, 0 ;清0 mov ss, ax mov ds, ax mov es, ax mov si, ax readFloppy: mov CH, 0 ;CH 用来存储柱面号 mov DH, 0 ;DH 用来存储磁头号 mov CL, 2 ;CL 用来存储扇区号 mov BX, LOAD_ADDR ; ES:BX 数据存储缓冲区 mov AH, 0x02 ; AH = 02 表示要作的是读盘操做 mov AL, 1 ; AL 表示要练习读取几个扇区 mov DL, 0 ;驱动器编号,通常咱们只有一个软盘驱动器,因此写死为0 INT 0x13 ;调用BIOS中断实现磁盘读取功能 JC fin ;出错 跳转到fin jmp LOAD_ADDR ;CPU-EIP跳转到0x8000位置 fin: HLT jmp fin
实例2、
org 0x7c00; LOAD_ADDR EQU 0X8000 ;MBR BIOS加载此二进制代码到内存0X8000地址上,CPU运行 entry: mov ax, 0 mov ss, ax mov ds, ax mov es, ax mov ss, ax mov si, ax mov sp, 0x7c00 mov ax, 0xb800 mov gs, ax ;设置显示器配置 mov ax, 0600h mov bx, 0700h mov cx, 0 ; 左上角: (0, 0) mov dx, 184fh ; 右下角: (80,25), ; 由于VGA文本模式中,一行只能容纳80个字符,共25行。 ; 下标从0开始,因此0x18=24,0x4f=79 int 10h ; int 10h readSection1: ;读取0磁头0柱面2扇区数据到内存的BX地址处 mov BX, LOAD_ADDR ; BX = 0X8000 ES:BX数据存储缓冲区的地址 mov AH, 0x02 ; AH = 02表示要作的是读盘操做 mov AL, 1 ; AL表示要连续读取几个扇区 mov CH, 0 ;CH 用来存储柱面号 mov CL, 2 ;CL 用来存储扇区号 mov DH, 0 ;DH 用来存储磁头号 mov DL, 0 ;驱动器编号,通常咱们只有一个软盘驱动器,因此写死为0 INT 0x13 ;调用BIOS中断实现磁盘读取功能 JC fin jmp LOAD_ADDR ;EIP跳转到地址0x8000处 fin: jmp $ ; 使程序悬停在此
2)CPU-EIP跳转到0x8000位置上(这里设置0x8000H上,固然 你能够本身设置未使用的内存地址上都是能够的),这时候咱们能够写简单的一些系统内核,下面进行测试
该段汇编主要是向显卡循环显示一个一个字符,最后取值为0就跳转fin执行HLT让CPU睡眠,死循环!
要显示一个字符,int 0x10则知足条件
AH=0X0E;AL=须要显示的字符code;BH=0;BL=颜色code
实例1、利用BIOS中断int 0x10来显示文本
org 0x8000 ;起始地址0x8000 entry: mov ax, 0 ;清理 mov ss, ax mov ds, ax mov es, ax mov si, msg ;将msg地址给si putloop: mov al, [si] ;取si值给al add si, 1 cmp al, 0 ;al与0比较 je fin ;al为0时跳转fin ; 如下三行是为了显示AL中保存的字符 mov ah, 0x0e ;AH必须为0x0e;在Teletype模式下显示字符 mov bx, 15 ;BH = 0, BL = 15,合起来就是BX=15,这个15是指颜色的编号为15 int 0x10 ;执行BIOS中段,简单理解一个函数,该函数的地址是0x10,该函数的做用是显示一个字符 jmp putloop fin: HLT jmp fin msg: DB "jadeshu create OS kernel!"
实例2、利用BIOS中断int 0x10来显示文本
org 0x8000 entry: mov ax, 0 mov ss, ax mov ds, ax mov es, ax mov ss, ax ;在光标位置处打印字符. mov ah, 3 ; 输入: 3号子功能是获取光标位置,须要存入ah寄存器 mov bh, 0 ; bh寄存器存储的是待获取光标的页号 int 0x10 ; 输出: ch=光标开始行,cl=光标结束行 ; dh=光标所在行号,dl=光标所在列号 ;; 打印字符串 ;; mov ax, msg mov bp, ax ; es:bp 为串首地址, es此时同cs一致, ; 开头时已经为sreg初始化 ; 光标位置要用到dx寄存器中内容,cx中的光标位置可忽略 mov cx, 25 ; cx 为串长度,不包括结束符0的字符个数 mov ax, 0x1301 ; 子功能号13是显示字符及属性,要存入ah寄存器, ; al设置写字符方式 ah=01: 显示字符串,光标跟随移动 mov bx, 0x2 ; bh存储要显示的页号,此处是第0页, ; bl中是字符属性, 属性黑底绿字(bl = 02h) int 0x10 ; 执行BIOS 0x10 号中断 jmp $ ; 使程序悬停在此 msg: DB "jadeshu create OS kernel!"
分别将上面的两个汇编程序用nasm进行汇编成二进制!生成后命令为boot和kernel!
虽然咱们将引导区MBR和内核都简单写出来了,可是咱们还没法运行,那么接下来就须要将这两个二进制文件写人咱们本身用软件模拟的软盘内。
下面开始创建一个模拟软盘文件,模拟硬盘一个盘面(该盘面有两面,80个柱面[磁道],每一个柱面有18个扇区)512*18*2*80=1474560约等于1.4M硬盘
HardDisk.h
#ifndef __HARDDISK_H__ #define __HARDDISK_H__ #define SECTOR_SIZE 512 #define CYLINDER_COUNT 80 #define SECTORS_COUNT 18 #include <string> class CHardDisk { public: CHardDisk(); ~CHardDisk(); // 设置硬盘盘面、柱面、扇区 void setMagneticHead(unsigned int head) { this->head = head; } void setCylinder(int cylinder) { this->current_cylinder = cylinder; } void setSector(int sector) { this->current_sector = sector; } // 获取扇区数据 char* getDiskBuffer(unsigned int head, int cylinder_num, int sector_num); // 将buf数据写入指定扇区 void setDiskBuffer(unsigned int head, int cylinder_num, int sector_num, char* buf); // 制做映像文件 void makeVirtualDisk(const char* name = "system.img"); void writeFileToDisk(const char* fileName, bool bootable, int cylinder, int beginSec); private: unsigned int head = 0; // 默认盘面 int current_cylinder = 0; // 当前磁道号 int current_sector = 0; // 当前扇区号 char* disk0[CYLINDER_COUNT][SECTORS_COUNT+1]; char* disk1[CYLINDER_COUNT][SECTORS_COUNT+1]; }; #endif
HardDisk.cpp
#include "HardDisk.h" CHardDisk::CHardDisk() { char* buf = nullptr; // 初始化硬盘,在分配和初始化内存中的数据 for (int i = 0; i < CYLINDER_COUNT; i++) { for (int j = 1; j < SECTORS_COUNT+1; j++) { buf = new char[SECTOR_SIZE]; // 效率低下 memset(buf, 0, SECTOR_SIZE); this->disk0[i][j] = buf; buf = new char[SECTOR_SIZE]; memset(buf, 0, SECTOR_SIZE); this->disk1[i][j] = buf; } } } CHardDisk::~CHardDisk() { // 释放分配的内存 for (int i = 0; i < CYLINDER_COUNT; i++) { for (int j = 1; j < (SECTORS_COUNT + 1); j++) { delete[] this->disk0[i][j]; delete[] this->disk1[i][j]; } } } char* CHardDisk::getDiskBuffer(unsigned int head, int cylinder_num, int sector_num) { this->setMagneticHead(head); this->setCylinder(cylinder_num); this->setSector(sector_num); if (head == 0) { return this->disk0[cylinder_num][sector_num]; } else { return this->disk1[cylinder_num][sector_num]; } } void CHardDisk::setDiskBuffer(unsigned int head, int cylinder_num, int sector_num, char* buf) { char* bufTmp = getDiskBuffer(head, cylinder_num, sector_num); //memcpy_s(bufTmp, SECTOR_SIZE, buf, SECTOR_SIZE); memcpy(bufTmp, buf, SECTOR_SIZE); } void CHardDisk::makeVirtualDisk(const char* name) { printf("准备开始写入......\r\n"); FILE* file = nullptr; fopen_s(&file, name, "wb"); for (int cylinder = 0; cylinder < CYLINDER_COUNT; cylinder++) { // 读完0面就读同一位置的1面数据 for (int head = 0; head <= 1; head++) { for (int sector = 1; sector < (SECTORS_COUNT+1); sector++) { char* buf = getDiskBuffer(head, cylinder, sector); // 将软件模拟的磁盘内容写入指定文件内 fwrite(buf, 1, SECTOR_SIZE, file); } } } fclose(file); printf("写入成功\r\n"); } void CHardDisk::writeFileToDisk(const char* fileName, bool bootable, int cylinder, int beginSec) { FILE* file = nullptr; fopen_s(&file, fileName, "rb"); if (!file) { printf("读取文件不存在\r\n"); } char* buf = new char[512]; memset(buf, 0, 512); if (bootable) { buf[510] = 0x55; buf[511] = 0xaa; } //求得文件的大小 fseek(file, 0, SEEK_END); int size = ftell(file); rewind(file); if (size > SECTOR_SIZE) { // 文件数据大于512字节,另做处理 //while (fread(buf, 1, size, file) == size) //{ // setDiskBuffer(0, cylinder, beginSec, buf); // beginSec++; // if (beginSec > 18) { // beginSec = 1; // cylinder++; // } //} } else { fread(buf, 1, size, file); setDiskBuffer(0, cylinder, beginSec, buf); } fclose(file); file = nullptr; }
main.cpp
#include "HardDisk.h" int main() { CHardDisk disk; // 章节一案例 //disk.writeFileToDisk("test", true, 0, 1); //disk.makeVirtualDisk("system01.img"); // 章节二案例 // 将boot二进制文件写入0柱面1扇区 disk.writeFileToDisk("boot", true, 0, 1); // 将kernel二进制文件写入1柱面2扇区 disk.writeFileToDisk("kernel", false, 0, 2); disk.makeVirtualDisk("system02.img"); system("pause"); return 0; }
最后生成system02.img文件
用虚拟机打开,详情见第一节
实例一显示结果:
实例二显示结果:(黑底蓝字)