(4)打造简单OS-loader硬盘加载和C++写入文件

0.简要说明:

        我们完全可以使用bochs创建映像文件,如https://blog.csdn.net/jadeshu/article/details/89046838   ,那么为什么还去用C++去模拟文件呢,主要更深刻的理解和自己动手,比直接创建的文件映像更深刻,了解的内容也更多!!!

       当然如果想省事的话,您也可以直接用bochs直接创建映射软盘和硬盘文件,然后用dd   ld 等命令把MBR Loader直接写入到该映射文件中对应的扇区中即可!!!

       如下: 

将mbr.bin文件写入虚拟磁盘boot.img,写入到第一个扇区中。

   dd if=mbr.bin of=boot.img bs=512 count=1 conv=notrunc   

   CHS方式扇区从1开始索引,LBA方式扇区从0开始索引
   第一扇区 0~512字节
   第二扇区 512~1024字节
   第三扇区 1024~1536字节  

  将loader.bin文件写入虚拟磁盘boot.img,写入偏移2个扇区(即第三个扇区),count写入4块大小文件
  即512*2 = 1024
   dd if=loader.bin of=boot.img bs=512 count=4  seek=2 conv=notrunc

       建议初学者还是按照提供的内容进行学习!直接模拟硬盘!跟着一步一步做,不容易出错!

1.loader硬盘加载(文本模式) 

    后面会讲到,单独一节做个小实验,用图形模式去显示图形,我们还是先来看下下面的代码

   图形模式小实验入口:https://blog.csdn.net/jadeshu/article/details/103092821

   mbr.s    

;主引导程序 
;------------------------------------------------------------

SECTION MBR vstart=0x7c00         
   mov ax,cs      
   mov ds,ax
   mov es,ax
   mov ss,ax
   mov fs,ax
   mov sp,0x7c00
   mov ax,0xb800
   mov gs,ax

; 清屏
;利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10   功能号:0x06	   功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
   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

   mov eax, 0x2	 		; 起始扇区lba地址,从间隔第二个扇区开始
   mov bx, 0x900        ; 写入的地址
   mov cx, 1			; 待读入的扇区数,读取1个扇区内容
   call rd_disk_m_16	; 以下读取程序的起始部分(一个扇区)
  
   jmp 0x900
       
;-------------------------------------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:	   
;-------------------------------------------------------------------------------
				       ; eax=LBA扇区号
				       ; ebx=将数据写入的内存地址
				       ; ecx=读入的扇区数
      mov esi,eax	  ;备份eax
      mov di,cx		  ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数
      mov dx,0x1f2
      mov al,cl
      out dx,al            ;读取的扇区数

      mov eax,esi	   ;恢复ax

;第2步:将LBA地址存入0x1f3 ~ 0x1f6

      ;LBA地址7~0位写入端口0x1f3
      mov dx,0x1f3                       
      out dx,al                          

      ;LBA地址15~8位写入端口0x1f4
      mov cl,8
      shr eax,cl
      mov dx,0x1f4
      out dx,al

      ;LBA地址23~16位写入端口0x1f5
      shr eax,cl
      mov dx,0x1f5
      out dx,al

      shr eax,cl
      and al,0x0f	   ;lba第24~27位
      or al,0xe0	   ; 设置7~4位为1110,表示lba模式
      mov dx,0x1f6
      out dx,al

;第3步:向0x1f7端口写入读命令,0x20 
      mov dx,0x1f7
      mov al,0x20                        
      out dx,al

;第4步:检测硬盘状态
  .not_ready:
      ;同一端口,写时表示写入命令字,读时表示读入硬盘状态
      nop
      in al,dx
      and al,0x88	   ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
      cmp al,0x08
      jnz .not_ready	   ;若未准备好,继续等。

;第5步:从0x1f0端口读数据
      mov ax, di
      mov dx, 256
      mul dx
      mov cx, ax	   ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字,
			   ; 共需di*512/2次,所以di*256
      mov dx, 0x1f0
  .go_on_read:
      in ax,dx
      mov [bx],ax
      add bx,2		  
      loop .go_on_read
      ret

  ; times 510-($-$$) db 0
  ; db 0x55,0xaa

   其中代码大概意思是:

       (1)首先初始化各个段寄存器

       (2)清屏,设置为文本模式

       (3)读取偏移第二个扇区(也就是后面C++写入文件偏移的2*512=1024位置)的512字节内容加载到0X900地址处,

       (4)最后(jmp 0X900)让直接调转到0X900地址处执行   

上面汇编代码应用到了LBA逻辑块知识,需要学习的进入https://blog.csdn.net/jadeshu/article/details/89072512  

 

  loader.s 


section loader vstart=0x900

; 输出背景色绿色,前景色红色,并且跳动的字符串"Jade OS"
mov byte [gs:0x00],'J'
mov byte [gs:0x01],0xA4     ; A表示绿色背景闪烁,4表示前景色为红色

mov byte [gs:0x02],'a'
mov byte [gs:0x03],0xA4

mov byte [gs:0x04],'d'
mov byte [gs:0x05],0xA4   

mov byte [gs:0x06],'e'
mov byte [gs:0x07],0xA4

mov byte [gs:0x08],' '
mov byte [gs:0x09],0xA4

mov byte [gs:0x0a],'O'
mov byte [gs:0x0b],0xA4

mov byte [gs:0x0c],'S'
mov byte [gs:0x0d],0xA4

jmp $		       ; 通过死循环使程序悬停在此

程序说明图:

(1)是将硬盘中的MBR加载到内存0X7C00开始的位置,由BIOS完成

(2)mbr.s中将loader加载到内存0X900开始的位置

    执行流程 BIOS[CS:IP 0XFFF0:FFF0]--》CS:IP[0:0X7C00]----(MBR将loader加载到0X900)--->CS:IP[0:0X900]---(loader中)

其中代码大概意思是:

  起始地址为0x900,上面mbr.s汇编代码指定了文本模式显示,并指定的gs为0XB800,而0XB8000~0XB8FFF地址是在实模式下用于文本模式显示适配器,可以看下面实模式下的内存布局图!

    往这段内存处赋值,即可显示相关的字符! 

 

最后进行汇编编译 

        nasm mbr.S -o mbr                  编译产生mbr文件
        nasm loader.S -o loader          编译产生loader文件

2.C++写入文件并创建

  HardDisk.h

#ifndef __HARDDISK_H__
#define __HARDDISK_H__

#define SECTOR_SIZE 512		// 1个扇区占字节大小
#define CYLINDER_COUNT 121	// 柱面
#define SECTORS_COUNT 63	// 1个柱面的扇区数
#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];
	char* disk1[CYLINDER_COUNT][SECTORS_COUNT];
};

#endif

  HardDisk.cpp

#include "HardDisk.h"

CHardDisk::CHardDisk()
{
	char* buf = nullptr;
	// 初始化硬盘,在分配和初始化内存中的数据
	for (int i = 0; i < CYLINDER_COUNT; i++)
	{
		for (int j = 0; j < SECTORS_COUNT; 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 = 0; j < SECTORS_COUNT; 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 if(head == 1)
	{
		return this->disk1[cylinder_num][sector_num];
	}
	
	return nullptr;
}

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);
	printf("已经写入到(磁头:%d - 柱面:%d - 扇区:%d)\n", head, cylinder_num, sector_num);
}

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 = 0; sector < SECTORS_COUNT; 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");
		return; 
	}
	char* buf = new char[512];
	memset(buf, 0, 512);
	if (bootable) {
		buf[510] = (char)0x55;
		buf[511] = (char)0xaa;
	}

	//求得文件的大小  
	fseek(file, 0, SEEK_END);
	int size = ftell(file);
	rewind(file);

	if (size > SECTOR_SIZE)
	{
		int count_test = 0;
		// 文件数据大于512字节,另作处理
		while (!feof(file)) {
			fread(buf, 1, SECTOR_SIZE, file);
			setDiskBuffer(this->head, cylinder, beginSec, buf);
			memset(buf, 0, SECTOR_SIZE);
			beginSec++;
			if (beginSec >= SECTORS_COUNT) {
				beginSec = 0;
				count_test++;
				if (count_test % 2 == 0)
				{
					cylinder++;
				}
				this->head == 0 ? this->head = 1 : this->head = 0;
			}
		}

	}
	else
	{
		fread(buf, 1, size, file);
		setDiskBuffer(0, cylinder, beginSec, buf);
	}

	fclose(file);
	file = nullptr;
}

  main.cpp

#include "HardDisk.h"

int main()
{
	CHardDisk disk;

	printf("==========mbr=========\n");
	disk.writeFileToDisk("mbr", true, 0, 0);	//0-512

	// 将loader二进制文件写入0柱面偏移2扇区
	printf("==========loader=========\n");
	disk.writeFileToDisk("loader", false, 0, 2);  // 1024-
	
	disk.makeVirtualDisk("boot.img");

	system("pause");
	return 0;
}

编译结果如下:(我这里把loader文件做的大一点,是为了演示写入具体的过程!!如果仅编译上面的loader汇编代码不会有这么多文件写入),因机械磁盘效率问题,我们都是写入同一个柱面,这里有2个磁头,所以写完磁头0柱面0,然后直接写磁头1柱面0.。。随后磁头0柱面1,磁头1柱面1.。。随后磁头0柱面2 磁头1柱面2.。。。这样继续存储,读去也是,这样磁头不用倒来倒去,提高效率!

3.进入实验阶段

   打开bochs模拟器,选择硬盘启动方式,更改配置文件,如https://blog.csdn.net/jadeshu/article/details/89046838 所示

  在cmd中输入 bochs.exe -f bosh.src     (其中bosh.src是配置文件,选择硬件方式boot:disk )

然后界面如下所示

posted @ 2019-06-23 16:04  jadeshu  阅读(346)  评论(0编辑  收藏  举报