在第一节<(1)汇编写入引导区,虚拟机启动步骤>中讲解到一个简单屏幕显示一川字符串,第二节讲到BIOS启动过程!

第一节中基本原理就是将那个汇编代码用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就可以执行命令了。

      简单的说,整个开机流程到操作系统之前的动作应该是这样的:

  1.BIOS:开机主动执行的韧体,会认识第一个开机的设备

  2.MBR:第一个可开机设备的第一个扇区内的主引导分区块,内含引导加载程序。

  3.引导加载程序:一个可读取内核文件来执行的软件。

  4.内核文件:开始操作系统的功能。

BIOS与MBR都是硬件本身会支持的功能,至于Boot loader(引导加载程序)则是操作系统安装在MBR上面的一套软件。由于MBR仅有466bytes而已,因此这个引导程序是非常小而完美的。这个boot loader的主要任务有下面几项:

  • 提供菜单:用户可以选择不同的开机选项,这也是多重引导的重要功能呢。
  • 载入内核文件:直接执行可开机的程序区段来开始操作系统。
  • 转交其他loader:将引导加载功能转交给其他loader负责。

1)那么首先开始写主引导boot程序(作用:将硬盘的第0面0磁道2扇区[0磁头0柱面2扇区](C0-H0-S2)读取1个扇区的内容到内存中0X8000位置,然后跳转到这个位置执行指令)

实例一、

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

实例二、

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

实例一、利用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!"

实例二、利用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文件

用虚拟机打开,详情见第一节

实例一显示结果:

实例二显示结果:(黑底蓝字)

(3)打造简单OS-MBR引导区转移加载简单程序(突破512限制)的更多相关文章

  1. java反射并不是什么高深技术,面向对象语言都有这个功能,而且功能也很简单,就是利用jvm动态加载时生成的class对象

    java反射并不是什么高深技术,面向对象语言都有这个功能. 面向对象语言都有这个功能,而且功能也很简单,就是利用jvm动态加载时生成的class对象,去获取类相关的信息 2.利用java反射可以调用类 ...

  2. u-boot-2011.06在基于s3c2440开发板的移植之引导内核与加载根文件系统

    http://www.linuxidc.com/Linux/2012-09/70510.htm  来源:Linux社区  作者:赵春江 uboot最主要的功能就是能够引导内核启动.本文就介绍如何实现该 ...

  3. 编写简单的 NT 式驱动程序的加载与卸载工具

    写驱动的加载需要用到五个函数: OpenSCManager() CreateService() OpenService() StartService() CloseServiceHandle() 这五 ...

  4. 简单的ajax遮罩层(加载进度圈)cvi_busy_lib.js

    cvi_busy_lib.js cvi_busy_lib.js 是一个基于ajax的遮罩js,遮罩区域为body区域.使用比较简单. 效果: 在下面的Js代码,标注为红色标记为需要设置的参数. 1.g ...

  5. [JavaScript] 前端模块加载简单实现(require)

    模块加载的简单实现 (function(win) { var baseUrl; var paths; var script_cache = {}; var script_queue = []; var ...

  6. 简单方法解决bootstrap3 modal异步加载只一次的问题

    用过bootstrap3自身的modal的remote属性的人可能都有相同的疑惑:就是点击弹出modal后再次点击会从缓存中加载内容,而不会再次走后台,解决办法就是只要让modal本身的属性发生变化, ...

  7. Android 懒加载简单介绍

    1.懒加载介绍 1.1.效果预览 1.2.效果讲解 当页面可见的时候,才加载当前页面. 没有打开的页面,就不会预加载. 说白了,懒加载就是可见的时候才去请求数据. 1.3.懒加载文章传送门 参考文章: ...

  8. Android ndk 加载简单的gif 图像

    首先获取一个安卓权限 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"> ...

  9. Uboot 引导内核时加载地址与入口地址问题

    如果使用 mkimage 生成内核镜像文件的话,会在内核的前头加上了 64 bytes 的信息头,供建立 tag 之用.bootm 命令会首先判断 bootm xxx 这个指定的地址 xxx 与 -a ...

随机推荐

  1. 2017-10-22—光电二极管

    光通信基本每天都会接触光电二极管,光模块说简单点就是电光转换和光电转换. 光传输模块在整体产品架构上则包括光学次模块(Optical Subassembly;OSA)及电子次模块(Electrical ...

  2. h5中不能用js来直接获取网络码和机器码的。

    h5中不能用js来获取mac的.是可以获取ip的.代码  <script>var fso = new ActiveXObject("Scripting.FileSystemObj ...

  3. 在Windows上安装Nexus 3.2.0-01

      在Windows上安装Nexus 环境: Windows 7 apache-maven-3.3.9 JDK 1.8 下载Nexus: https://sonatype-download.globa ...

  4. [LeetCode] Encode N-ary Tree to Binary Tree 将N叉树编码为二叉树

    Design an algorithm to encode an N-ary tree into a binary tree and decode the binary tree to get the ...

  5. transient关键字的使用

    实例说明 在保存对象时,会将对象的状态也一并保存,然而有些状态是不应该被保存的,如表示密码的属性.此时可以使用transient关键字来修饰不想保存的属性. 关键技术 transient关键字用来防止 ...

  6. Java演算法-「馬踏棋盤問題」

    /* * 馬踏棋盤問題:(貪婪法求解) * 棋盤有64個位置,“日”字走法,剛好走滿整個棋盤 */ //下一個走法的方向類 class Direction{ int x; int y; int way ...

  7. 解决vue webApp使用lib-flexible和px2rem引用第三方ui库后,样式变小问题

    首先,需要卸载项目中的postcss-px2rem. npm uninstall postcss-px2rem --save-dev 其次,安装postcss-px2rem-exclude npm i ...

  8. mvc模式的理解

    一开始总是觉得dao层和service层没有区别,甚至觉得service层根本就是多余的,service层就是把dao层的内容调用了一下,然后重写了一次,后来才理解到,dao层是数据链路层,是与数据库 ...

  9. JAR包结构,META-INF/MANIFEST.MF文件详细说明[全部属性][打包][JDK]

    转载请注:[https://www.cnblogs.com/applerosa/p/9736729.html] 常见的属性 jar文件的用途 压缩的和未压缩的 jar工具 可执行的JAR 1.创建可执 ...

  10. 三报文握手 四报文握手 TCP运输连接管理

    三报文握手 四报文握手  TCP运输连接管理