第二课 一个简单的“引导程序”

  上一节中说到BIOS会将MBR中的主引导程序(512字节)加载到内存的0x7c00处,其中这512字节的主引导程序是软件程序,是操作系统的一部分,因此也是由操作系统开发者来编写的,BIOS将其加载到内存后,会自动跳到0x7c00处去执行。接下来我们自己实现一个“主引导程序”,功能很简单,就是让它打印一串字符串到屏幕上(真正的主引导程序是加载操作系统内核用的),注意,这段程序现在是独立运行在裸机上的,我们用汇编语言来实现它。

  这个过程我们需要调用中断向量表中的“打印函数”,调用的动作是通过int 0x10来实现的。

  编写程序之前,我们先来介绍几个要用到的指令:

  mov :赋值操作,将右操作数赋值给左操作数

  例:mov ax, 0  ;将0赋值给ax寄存器

  int :触发中断

  例:int 0x10 ; 触发0x10中断,对屏幕进行操作

  hlt :停止,CPU进入暂停状态,不进行任何操作

  例:hlt ;使程序进入睡眠状态

  汇编中的地址访问形式:段地址:段内偏移地址

  例:mov byte[0xb800:0x01] ; 0xb800:0x01 -> 0xb800 + 0x01

  标签:

  用于标识后续指令的地址,(与C语言中的标签等价)

  $ 与 $$

  $表示当前指令行地址,$$表示当前汇编段起始地址

  中断调用与函数调用的对应关系如下所示:

  

  实现真正的打印之前,要向bx寄存器中写入0x0f,向ah寄存器中写入0x0e,这些都是打印函数规定好的。而al寄存器中需要存入要打印的字符,寄存器中的内容准备好了之后使用int 0x10来调用打印函数,而后,就可以将字符打印到屏幕上。

  下面直接给出用汇编语言写的“主引导程序“。

  

org 0x7c00

start:
    mov ax, cs
	mov ss, ax
	mov ds, ax
	mov es, ax

	mov si, msg

print:
    mov al, [si]
	add si, 1
	cmp al, 0x00
	je last
	mov ah, 0x0e
	mov bx, 0x0f
	int 0x10
	jmp print

last:
    hlt
	jmp last

msg:
    db 0x0a, 0x0a
	db "Hello, DTOS!"
	db 0x0a, 0x0a
	times 510-($-$$) db 0x00
	db 0x55, 0xaa

  主引导程序实际待的起始地址是0x7c00,因此第一行的org 0x7c00告诉编译器,这段程序的加载地址是0x7c00,这样编译器就可以对地址进行正确的处理。

  然后将ss,ds,ed寄存器分别清零,msg标签处在内存中定义了一些数据,这些数据是实实在在在内存中和磁盘文件中占用空间的。msg就代表了这片数据所占用空间的起始地址。mov si, msg就是将这个起始地址送到si寄存器中。

  接下来print标号处定义了打印相关的功能,mov al, [si]就是取si所代表的内存地址处的一个字节数据,并放到al寄存器中,然后使si加1,指向下一个数据,此时先判断al中的数据是否是0x00,也就是ASCII码‘\0’,这个字符代表字符串的结束,如果确实为0x00,则跳转到last处,让cpu停机。如果不为0x00,则开始准备打印,正如我们上面说到的将0x0e存到ah寄存器中,将0x0f存到bx寄存器中,相当于格式化参数(%c),然后通过int 10来调用中断向量表里面的打印函数,打印完一个字符后继续跳转到print处进行下一次循环,直到到达字符串的末尾。

  下面我们讲一讲msg标号处的数据的定义,db伪指令定义一个字节,其后可以是数据,也可以是字符串。db 0x0a即定义一个字节,其中的数据初始化为0x0a,也就是换行符的ASCII码,db "Hello, DTOS!"相当于定义了11个字节。

  根据前面内容知道,主引导扇区为512字节大小,而BIOS判断存储介质是否包含主引导扇区的标准就是看看它的前512个字节的最后两个字节是不是0x55和0xaa,如果是,那么BIOS就认为这个存储介质有主引导扇区,接下来就读取主引导扇区到内存中了。

  前面写的程序不足以占满512字节,而我们的需求是要将最后两个字节写入0x55和0xaa,怎么办呢?我们让编译器去判断前面程序和数据占用空间的大小,并用适当数量的数据填满前面不足510字节的部分,然后在最后两个字节我们写入0x55和0xaa。

  times  510 - ($ - $$) db 0x00这条命令的意思就是填写510 - ($ - $$)个0x00到内存中,$代表times这条伪指令的地址,$$代表当前汇编程序的起始地址(即start处,0x7c00),相减后即为以上所写程序和数据占用的空间大小,再用510减去这个值即为所需要填充的空间的大小。最后,第511字节填入0x55,第512字节填入0xaa。

  用到的工具:汇编语言编译器nasm,创建虚拟盘的工具bximage,二进制写入工具dd。由于我们不会将程序真正的刻录到软盘上,因为那样太麻烦了,而且还需要一个真正的物理机器。所以我们使用虚拟盘创建工具bximage,它可以创建一个文件,这个文件存储数据时的格式和软盘是一样的。

  下面,我们实际做实验看现象,首先打开ubuntu虚拟机,到工作目录下建立boot.asm文件,敲入上述程序,使用编译器nasm进行编译,生成boot.bin文件,如下所示:

  下面创建虚拟盘,执行过程如下图:

这样,虚拟盘就创建好了,名为a.img,这个文件就相当于一个容量1.44M大小的全盘,见下图:

下面我们将boot.bin的内容刻录到这张软盘中,执行命令dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc,结果如下:

至此,这张软盘的前512字节就写入了主引导记录,因此,它就成为了一张启动盘了。

下面使用VmWare创建一个物理机器,详细过程不在贴出,创建好的机器如下,这是一个未安装任何操作系统的空的机器。

我们直接启动这个机器看看效果,如下所示:

从启动界面可以看到,BIOS扫描存储介质时,根本没有发现启动盘,也就是没有找到主引导扇区,因此给出Operating System not found的提示。

接下来,我们将软盘插入这台电脑中,首先将带有主引导扇区的软盘(a.img文件)拷贝到windows系统下,然后将其插入刚才创建的机器中,如下所示:

接下来,给这台机器加电,效果如下:

  可见,主引导程序成功被加载并运行了。

小插曲:

主引导程序部分是16位操作,因此,我们需要一个16位的汇编器,现存的汇编器大概有gas汇编器和nasm汇编器,nasm是16位的,用来编译英特尔格式汇编代码,gas是32位的用来编译AT&T格式汇编的,也是唯一支持AT&T格式的汇编器,因此,我们不能选择AT&T格式进行实验。linux的boot程序使用的是as86汇编器进行编译的,这个汇编器是16位的,但是资料比较少。as86是编写Minix操作系统的那个教授编写 的,这个汇编器支持的格式既不是标准的AT&T格式也不是Intel格式,只是和Intel格式比较接近。因此,我们选择了nasm汇编器。

本文参考狄泰软件学院操作系统教程。

原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9188822.html

时间: 2025-01-17 02:27:41

第二课 一个简单的“引导程序”的相关文章

编译原理 龙书 第二章 一个简单的算术式(+,-)翻译器实现

昨天晚上决定正面硬刚神课<编译原理>.硬上龙书. 下面是 一个简单的算术式中缀变后缀的翻译器. 这个也是 龙书中 一个C实现源码 .部分用c++改写. #include <iostream> #include <ctype.h> #include <stdlib.h> #include <stdio.h> using namespace std; int lookahead; void error()//错误处理 { cout<<&q

【批处理学习笔记】第二课:简单批处理命令(1)

1.echo 命令 打开回显或关闭请求回显功能,或显示消息.如果没有任何参数,echo [批处理与联机处理] 批处理与联机处理    命令将显示当前回显设置. 语法 echo [{on|off}] [message] Sample:@echo off / echo hello world 在实际应用中我们会把这条命令和重定向符号(也称为管道符号,一般用> >> ^)结合来实现输入一 些命令到特定的文件中.   2.举例:第一步:建立批处理文件第二步:写代码@echo offecho 欢迎

Grep第二课(简单介绍)

先简单介绍下,下一次介绍正则表达式regular expression(RE) .grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.Unix的grep家族包 括grep.egrep和fgrep.egrep和fgrep的命令只跟grep有很小不同.egrep是grep的扩展,支持更多的re元字符, fgrep

编译原理-第二章 一个简单的语法指导编译器-2.4 语法制导翻译

语法制导翻译: 定义:语法制导翻译是通过向一个文法的产生式附加一些规则或程序片段而得到的 功能:给定词法单元流,通过向一个文法产生式附加一些语义动 作,语法制导分析产生语法分析树,并实现翻译动作 相关概念: 属性:表示与某个程序构造相关的任意的量,因为用文法符号(终结符号或非终结符号)来表示程序构造,所以可将属性的概念从程序构造扩展到表示这些构造的文法符号上 综合属性:如果某个属性在语法分析树节点N上的值由N的子节点和N本身的属性值确定,则该属性为综合属性,其性质为只需对语法分析树进行一次自底向

编译原理-第二章 一个简单的语法指导编译器-2.3 语法定义

语法定义: 文法定义: 定义:用以描述程序设计语言语法的表示方法——“上下文无关文法”,简称“文法”,文法自然地描述了大多数程序设计语言构造地层次化语法结构 实例: 如果用变量expr来表示表达式,用变量stmt表示语句,则 相关概念: 产生式:使用箭头(→)表示"可以具有如下形式",用相关变量表示表达式和语句的构造规则产生的式子.每个生产式包括一个称为生产式头或左部的非终结符号,一个箭头,和一个称为生产式体或右部的由终结符号组成的序列. 终结符号:有时也称为词法单元,终结符号是该文法

《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型 (转)

第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以跳过本章. 本章将带你漫游使用实体框架建模的基本实例,建模是实体框架的核心特性,同时也是区别实体框架和微软早期的数据访问平台的特性.一旦建好模,你就可以面向模型编写代码,而不用面向关系数据库中的行和列. 本章以创建一个简单概念模型的实例开始,然后让实体框架创建底层的数据库,剩下的实例,将向你展示,如

【FLEX&amp;YACC】第二天制作一个简单计算器

首先写词法分析器: 词法分析器要返回记号: "+" 返回ADD "-" 返回SUB "*" 返回MUL "/" 返回DIV 输入的实数全部被当作double类型处理 换行符返回CR calc.l: %{#include <stdio.h>#include "y.tab.h"int yywrap(void){ /*免链接库文件*/    return 1;}%}%%[ \t]    { ;}&qu

学生成绩管理后台第二项任务:初步建模,搭建一个简单的网站

负责人:程琳茹 合伙人:李玉婷 签约员工:闫玉荣 前言:学生成绩管理后台,看起来是一个简单的项目,但是对于我们今后的发展很重要,建设一个管理后台有很多方法,这里我们主要使用Rstudio,在之后的文章中,会详细给出我们小组完成项目的过程与遇到的问题,欢迎大家借鉴,此外,同学们要积极参与讨论. 项目步骤:1.熟悉与安装Rstudio,并且配置好R内部环境. 2.建立好文件所存放的位置与确保文档可以正常使用. 3.搭建一个简单的网站,分别分为server.R端口与ui.R端口. 4.搭建好网站后,插

第二周:一个简单的时间片轮转多道程序内核代码及分析

吕松鸿+ 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.函数调用堆栈 1. 计算机工作的三个法宝 存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: 函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要,但有了高级语言及函数,堆栈成为了计算机的基础功能: enter pushl %ebp movl %esp,%ebp lea