(3)打造简单OS-MBR引导区转移加载简单程序(突破512限制)

         在第一节<(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文件

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

实例一显示结果:

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

posted @ 2019-03-29 00:35  jadeshu  阅读(366)  评论(0编辑  收藏  举报