1. org 07c00h     ;伪指令,告诉编译器程序会被加载到7c00
  2.  
  3. mov ax, cs
  4. mov ds, ax
  5. mov es, ax
  6. call DispStr      ;调用显示字符串例程
  7. jmp $ ;无限循环
  8. DispStr:
  9. mov ax, BootMessage
  10. mov bp, ax ;ES:BP=字符串地址
  11. mov cx, 22 ;CX=字符串长度
  12.  
  13. mov ax, 01301h ;AH=13AL=01h
  14. mov bx, 000ch ;页号为0(BH=0)黑底红字(BL=0Ch,高亮)
  15. mov dl, 0
  16. int 10h ;10h号中断
  17. ret ;pop IP
  18. BootMessage: db "Hello world! I'm Joey!"
  19. times 510-($-$$)db 0          ;填充剩下的空间,使生成的二进制代码恰好为512字节
  20. dw 0xaa55 ;结束标志

a.编译

首先编译源码:

nasm boot.asm -o boot.bin

或者

nasm boot.asm -o boot.com(在DOS中运行)

然后创建一个软盘映像,进入安装好的Bochs目录下:

bximage

Create new floppy or hard disk image | fd | 1.44 | a.img 在Bochs目录下生成了一个空的a.img。

在linux中执行,如果是windows下的则不需要conv=notrunc:

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

b.运行

用VMware虚拟机用软盘的方式加载这个a.img,结果如下:

源码解析:

1.org 07c00h和dw 0xaa55和times 510-($-$$) db 0

当计算机电源打开,会先进行加电自检(POST),然后寻找启动盘,如果是选择从软盘启动,计算机会检查软盘的0面0磁道1扇区,如果发现它以0xaa55结束,则BIOS认为它是一个引导扇区。一个正确的引导扇区除了以0xaa55结束之外,还应该包含一段512字节的执行码。一旦BIOS发现了引导扇区,就会将这512字节的内容装载到内存地址0000:7c00处,然后跳转到0000:7c00处将控制权彻底交给这段引导代码。

org 07c00h就是告诉编译器,这段代码是要被加载到这个地址的。所以BootMessage的偏移地址是相对于0000:07c00h处开始的。BootMessage实际上也是一个地址,不是相对量

2.mov ax, cs、mov ds, ax、mov es, ax

让ds和es指向当前代码段。以便在以后进行数据操作的时候能定位到正确的位置。

3.call DispStr

调用显示字符串的函数,在NASM中,任何不被[]括起来的标签或变量名都被认为是地址,如果是访问标签中的内容则必须使用[]。所以mov ax, BootMessage会把"Hello,OS world!"这个字符串的首地址传给寄存器ax。然后mov bp, ax赋给bp,如果没写org指令,则BootMessage编译的时候就是相对于本程序加载到地址0的偏移地址,但是这里是相对于0000:07c00h为基址的偏移地址,所以这里显示了org的作用。

4.int 10h

10H中断是由BIOS对显示器和屏幕所提供的服务程序。使用int 10h服务程序时,必须先指定ah寄存器。

功能13h:

功能描述:在Teletype模式下显示字符串
入口参数:AH=13H
BH=页码
BL=属性(若AL=00H或 01H)
CX=显示字符串长度
(DH、DL)=坐标(行、列)

ES:BP=显示字符串的地址
AL=显示输出方式
0—— 字符串中只含显示字符,其显示属性在BL中。显示后,光标位置不变
1——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变
2 ——字符串中含显示字符和显示属性。显示后,光标位置不变
3——字符串中含显示字符和显示属性。显示后,光标位置改变
出口参数:无

5.$和$$

$-$$表示本行距离程序开始处的相对距离。times 510-($-$$) db 0表示将0这个字节重复510-($-$$)遍,也就是在剩下的空间中不停地填充0,直到程序有510字节为止。这样,加上结束标志0xaa55占用的2字节,恰好是512字节。

c.调试

对bochsrc.bxrc文件右键Debbuger,然后start。可以先在07c00h处设一个断点,引导扇区就是从这里开始执行的,所以这里就是我们的入口地址,b 0x7c00。然后单步执行。

b 0x7c00——在0x7c00处设置断点

info break——显示当前所有断点信息

c——让代码继续执行,直到遇上断点

r——查看通用寄存器值

x /64bx 0x7c00——从0x7c00地址处开始的64字节的内存数据(x是线性地址,xp是物理地址)

x /nuf addr ——【显示指定内存地址的数据,addr可以是线性的内存地址,也可以是虚址 格式是基址:偏移或者基址寄存器:偏移

n 显示的数据长度

u 数据单元大小 b,h,w,g分别对应1,2,4,8字节

f 数据显示格式 x,d,u,o,t,c分别对应十六进制、十进制、无符号十进制、八进制、二进制、字符串】

s——单步执行

n——单步执行,遇到函数则跳过

trace-reg on——每走一步都显示主要寄存器的值

print-stack——查看堆栈

sreg——段寄存器

creg——查看控制寄存器(cr0,cr1,cr2,cr3)

dreg——查看调试寄存器(dr0-dr7)

info idt——展示中断描述表

info ivt——展示中断向量表(保护模式下无效)

info gdt——展示全局描述表

info tss——展示当前的任务状态段

info cr——展示CR0-CR4寄存器状态 (无法使用)

info flags——展示标志寄存器   (无法使用)

这里再插一句,标志寄存器的查看方法:

eflags 0x00000002: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf af pf cf    (均为置位)

eflags 0x00000046: id vip vif ac vm rf nt IOPL=0 of df if tf sf ZF af PF cf (ZF,PF置位)

0x00000002是标志寄存器的实际数值,后面的zf,sf等为标志位,小写时标志位未置位,大写为已置位。

d.在DOS中运行

在以后的代码中会出现程序总字节数超过512字节的情况,那么直接运行就会崩溃,所以得在DOS系统中运行,并代码需改成

  1. org 0100h

首先安装DOSBOX,可以在64位机器上运行。

打开dos,输入mount d d:\MyDos,执行d:,然后运行程序,输入boot.com,结果如下:

源码及软盘映像

操作系统开发系列—1.HelloWorld ●的更多相关文章

  1. 操作系统开发系列—10.内核HelloWorld ●

    a.我们先来体验一下在Linux下用汇编编程的感觉,见代码 [section .data] ; 数据在此 strHello db "Hello, world!", 0Ah STRL ...

  2. 操作系统开发系列—13.g.操作系统的系统调用 ●

    在我们的操作系统中,已经存在的3个进程是运行在ring1上的,它们已经不能任意地使用某些指令,不能访问某些权限更高的内存区域,但如果一项任务需要这些使用指令或者内存区域时,只能通过系统调用来实现,它是 ...

  3. 操作系统开发系列—12.f.在内核中添加中断处理 ●

    因为CPU只有一个,同一时刻要么是客户进程在运行,要么是操作系统在运行,如果实现进程,需要一种控制权转换机制,这种机制便是中断. 要做的工作有两项:设置8259A和建立IDT. /*========= ...

  4. 操作系统开发系列—9.Loader

    一个操作系统从开机到开始运行,大致经历“引导—>加载内核入内存—>跳入保护模式—>开始执行内核”这样一个过程.也就是说,在内核开始执行之前不但要加载内核,而且还有准备保护模式等一系列 ...

  5. 操作系统开发系列—13.i.进程调度 ●

    上面的三个进程都是延迟相同的时间,让我们修改一下,尝试让它们延迟不同的时间. void TestA() { int i = 0; while (1) { disp_str("A." ...

  6. 操作系统开发系列—13.h.延时操作

    计数器的工作原理是这样的:它有一个输入频率,在PC上是1193180HZ.在每一个时钟周期(CLK cycle),计数器值会减1,当减到0时,就会触发一个输出.由于计数器是16位的,所以最大值是655 ...

  7. 操作系统开发系列—13.e.三进程

    我们再来添加一个任务,首先添加一个进程体: void TestC() { int i = 0x2000; while(1){ disp_str("C"); disp_int(i++ ...

  8. 操作系统开发系列—13.d.多进程 ●

    进程此时不仅是在运行而已,它可以随时被中断,可以在中断处理程序完成之后被恢复.进程此时已经有了两种状态:运行和睡眠.我们已经具备了处理多个进程的能力,只需要让其中一个进程处在运行态,其余进程处在睡眠态 ...

  9. 操作系统开发系列—解释typedef void (*int_handler) ();

    于是我换了一个思路来理解这个typedef 我们首先看常规的变量定义: int INT//定义了一个名为INT的int型变量. char *c//定义了一个名为c的char型指针变量 void(*Fu ...

随机推荐

  1. swift 如何实现点击view后显示灰色背景

    有这样一种场景,当我们点击view的时候,需要过0.几秒显示一个灰色或者别的颜色的背景 用button来实现,只有按下去的时候才会出现,往往在快速按下,快速抬起的时候是看不出这个变化的 下边是解决方案 ...

  2. (转载)基于LBS地图的开发,满足地图上有头像的需求

    最近做的项目主要是LBS这块 主打成员定位功能 我们的UI设计是这样的 乍一看上去是挺好挺美观的 不同的人会显示不同的头像 可是当人扎堆的时候 问题就来了 当人多的时候(例如上图所示) 地图滑动起来就 ...

  3. hibernate笔记--单向一对多映射方法

    上一篇讲的是单向多对一的表关系,与单向一对多的关系正好相反,如下图所示关系: ,可以看出年级表和学生表是一对多的关系,一条年级信息对应多条学生信息,在hibernate中成为单向的一对多的映射关系,应 ...

  4. MyEclipse打不开jsp文件 报错“Failed to create the part's controls"

    本来写好了一套网站html代码,需要移植到myeclipse的jsp页面中,当复制代码完毕后,也是可以运行的并不报错,但一直有弹框提示有空指针,当关闭页面后再次打开就歇菜了,提示 我预感到这可能是my ...

  5. wcf DataTable作为返回类型

    如果这个DataTable是自己定义的,则再声明DataTable dt=new DataTable("exampleName")时,一定要调用带有一个参数的DataTable个构 ...

  6. TP框架执行流程分许

    1. index.php 入口文件 2. ThinkPHP/ThinkPHP.php 在php5.3版本以后 设置常量有两种方式: const name = value; 作用域根据当前命名空间决定 ...

  7. OpenCV2邻域和模板操作

    在图像处理中,通过当前位置的邻域像素计算新的像素值是很常见的操作.当邻域包含图像的上几行和下几行时,就需要同时扫描图像的若干行,这就是图像的邻域操作了.至于模板操作是实现空间滤波的基础,通常是使用一个 ...

  8. SQL Server 存储(1/8):理解数据页结构

    我们都很清楚SQL Server用8KB 的页来存储数据,并且在SQL Server里磁盘 I/O 操作在页级执行.也就是说,SQL Server 读取或写入所有数据页.页有不同的类型,像数据页,GA ...

  9. GridView的使用

    首先,gridview是封装好的,直接在设计界面使用,基本不需要写代码: 一.绑定数据源 GridView最好与LinQDatasourse配合使用,相匹配绑定数据: 二.样式控制 1.自动套用样式 ...

  10. jquery鼠标移动div内容上下左右滚动

    jquery鼠标移动div内容上下左右滚动 点击这里查看效果:http://keleyi.com/keleyi/phtml/jqtexiao/9.htm <!DOCTYPE html PUBLI ...