《自己动手写操作系统》——制作一个简单的操作系统

运行一个简单的操作系统

本文是基于《自己动手写操作系统》的第一章和第二章总结的。

源码编译

    org 07c00h
    mov ax, cs
    mov ds, ax
    mov es, ax
    call DispStr
    jmp $

DispStr:
    mov ax, BootMessage
    mov bp, ax
    mov ax, 01301h
    mov bx, 000ch
    mov dl, 0
    int 10h
    ret

BootMessage:    db  "Hello, OS world!"
times 510-($-$$) db 0

dw 0xaa55

linux下用nasm进行编译,得到boot.bin二进制文件。编译指令为:

nasm -o boot.bin boot.asm

镜像制作

通过linux下的命令dd制作镜像文件boot.img,命令为:

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

虚拟机安装配置

本文采用VirtualBox虚拟机软件,相关下载和安装操作可以参考博文。下面我们需要创建一个新的虚拟机:

xxxxxx
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

然后我们需要将虚拟机设置为从我们制作的镜像boot.img中启动:(1)添加软盘;(2)设置启动扫描顺序

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

运行

点击启动即可运行虚拟机。
在这里插入图片描述
但是不知为何只显示一个H,而不是整个字符串!!

源码分析

为了分析上述问题,我们将对源码展开分析。

反汇编

linux下采用ndisasm将二进制文件boot.bin反汇编1,观察其中具体执行的指令:

ndisasm -o 0x7c00 boot.bin >> disboot.asm
00007C00  8CC8              mov ax,cs
00007C02  8ED8              mov ds,ax
00007C04  8EC0              mov es,ax
00007C06  E80200            call 0x7c0b
00007C09  EBFE              jmp short 0x7c09

首先是原汇编代码中的三条mov指令,给数据段寄存器赋值。然后就调用位于0x7c0b内存处的指令,也就是用来显示字符串的代码段DispStr

00007C0B  B81B7C            mov ax,0x7c1b
00007C0E  89C5              mov bp,ax
00007C10  B80113            mov ax,0x1301
00007C13  BB0C00            mov bx,0xc
00007C16  B200              mov dl,0x0
00007C18  CD10              int 0x10
00007C1A  C3                ret

这段代码将字符串的内存地址0x7c1b复制给了寄存器bp,之后的几行代码的解释分别为2

  • mov bp,axes:bp = 串地址.
  • mov ax, 01301hah = 13,al = 01h,ah是设定服务模式,13h的意思是显示字符串。al设置光标位置,01h的意思是光标跟随字符串,具体参考博文
  • mov bx, 000chbx用来设置字符串的属性,颜色、闪烁、背景等等,这里表示页号为 0(bh = 0)黑底红字(bl = 0Ch,高亮)。
  • mov dl, 0将值 0 存储到寄存器 dl 中。这个值告诉计算机我们要将字符显示在屏幕的左上角。
  • int 10h这是一个中断指令,会将控制权转移给 BIOS。BIOS 根据先前设置的参数,在屏幕上显示字符。

因此可以知道,之所以只显示一个字符H,应该就是缺少了给cx寄存器赋值这一步,cx是设置字符串的长度。将DispStr代码段改成如下所示:

DispStr:
    mov ax, BootMessage
    mov bp, ax
    mov ax, 01301h
    mov bx, 000ch
    mov cx, 0010h	;- 设置cx为16个字符长度
    mov dl, 0
    int 10h
    ret

重新操作一遍之后,就能正确地输出字符串了。
在这里插入图片描述

0x07C00

0x07C00BIOS将引导程序加载到的内存地址,那么有一个问题就是,既然这部分代码已知必然会被加载到这个位置,那为什么汇编中还需要语句org 07c00h?我们下面直接阅读编译的二进制文件:

#- 声明 org 07c00h
  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
00000000: 8C C8 8E D8 8E C0 E8 02 00 EB FE B8 1E 7C 89 C5    .H.X.@h..k~8.|.E
00000010: B8 01 13 BB 0C 00 B9 10 00 B2 00 CD 10 C3 48 65    8..;..9..2.M.CHe
00000020: 6C 6C 6F 2C 20 4F 53 20 77 6F 72 6C 64 21 00 00    llo,.OS.world!..
#- 未声明 org 07c00h
  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
00000000: 8C C8 8E D8 8E C0 E8 02 00 EB FE B8 1E 00 89 C5    .H.X.@h..k~8...E
00000010: B8 01 13 BB 0C 00 B9 10 00 B2 00 CD 10 C3 48 65    8..;..9..2.M.CHe
00000020: 6C 6C 6F 2C 20 4F 53 20 77 6F 72 6C 64 21 00 00    llo,.OS.world!..

可以看到,两种情况下,编译得到的二进制文件只存在微小的区别,即00000000:0D处。该处的机器指令翻译过来为:mov ax 0x07c1emov ax 0x0001e,对应原汇编中的mov ax, BootMessage。当程序加载到了0000:7c00处,由于段内偏移了7c00,此时字符串的起始位置就变成了7c00+1e=7c1e。第一段程序由于二进制指令是B8 1E 7C,因此会去0000:7c1e寻找字符串,这是正确的。而第二段程序由于二进制指令是B8 1E 00,而0000:001e此时显然不是字符串所在地,因此会发生错误。综上,org 07c00h这条指令是告诉汇编器,这段程序之后会被加载到段内偏移地址为7c00的内存上,因此需要在编译的时候,把所有偏移地址都加上这个偏移,才能正确映射。

总结

  • 将一段显示字符串的汇编代码编译成二进制,并制作成镜像文件;
  • 安装虚拟机软件,加载镜像文件,输出字符串不符合预期;
  • 通过反汇编发现,目前的10H中断需要额外指定cx寄存器的值;
  • org 07c00h的作用是告诉汇编器这段代码将会加载在段内偏移地址为7c00处的内存中,因此需要将代码中所有地址都进行这个偏移。

  1. 要正确反汇编一个’DOS.COM’文件,反汇编器必须知道文件中的第一条指令是被装载到内存何处,ndisasm缺省地认为你给它的每一个文件都是装载到0处的,所以你必须告诉它这一点。'-o'选项允许你为你正反汇编的声明一个不同的起始地址。它的参数可以是任何NASM数值格式:缺省是十进制,如果它以’‘$’‘或’‘0x’‘开头,或以’‘H’结尾,它是十六进制的,如果以’‘Q’‘结尾,它是8进制的,如果是’‘B’'结尾,它是二进制的。 ↩︎

  2. 这部分解释参考了博文. ↩︎

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书在详细分析操作系统原理的基础上,用丰富的实例代码,一步一步地指导读者用C语言和汇编语言编一个具备操作系统基本功能的操作系统框架。本书不同于其他的理论型书籍,而是提供给读者一个动手实践的路线图。书中讲解了大量在开发操作系统中需注意的细节问题,这些细节不仅能使读者更深刻地认识操作系统的核心原理,而且使整个开发过程少走弯路。全书共分7章。 本书适合各类程序员、程序开发爱好者阅读,也可作为高等院校操作系统课程的实践参考书。 折叠 作品目录 第1章 马上动手一个最小的"操作系统"1 1.1 准备工作1 1.2 10分钟完成的操作系统1 1.3 Boot Sector3 1.4 代码解释3 1.5 水面下的冰山5 1.6 回顾6 第2章 搭建你的工作环境7 2.1 虚拟计算机(Virtual PC)7 2.1.1 Virtual PC初体验8 2.1.2 创建你的第一个Virtual PC9 2.1.3 虚拟软盘研究12 2.1.4 虚拟软盘实战14 2.2 编译器(NASM & GCC)18 2.3 安装虚拟Linux19 2.4 在虚拟Linux上访问Windows文件夹26 2.5 安装虚拟PCDOS26 2.6 其他要素29 2.7 Bochs29 2.7.1 Bochs vs. Virtual PC vs. VMware30 2.7.2 Bochs的使用方法31 2.7.3 用Bochs进行调试33 2.7.4 在Linux上开发34 2.8 总结与回顾36 第3章 保护模式(Protect Mode)37 3.1 认识保护模式37 3.1.1 GDT(Global Descriptor Table) 42 3.1.2 实模式到保护模式,不一般的jmp45 3.1.3 描述符属性47 3.2 保护模式进阶50 3.2.1 海阔凭鱼跃50 3.2.2 LDT(Local Descriptor Table)58 3.2.3 特权级62 3.3 页式存储82 3.3.1 分页机制概述83 3.3.2 编代码启动分页机制84 3.3.3 PDE和PTE85 3.3.4 cr388 3.3.5 回头看代码88 3.3.6 克勤克俭用内存90 3.3.7 进一步体会分页机制100 3.4 中断和异常107 3.4.1 中断和异常机制109 3.4.2 外部中断111 3.4.3 编程操作8259A113 3.4.4 建立IDT116 3.4.5 实现一个中断117 3.4.6 时钟中断试验119 3.4.7 几点额外说明121 3.5 保护模式下的I/O122 3.5.1 IOPL122 3.5.2 I/O许可位图(I/O Permission Bitmap)123 3.6 保护模式小结123 第4章 让操作系统走进保护模式125 4.1 突破512字节的限制125 4.1.1 FAT12126 4.1.2 DOS可以识别的引导盘131 4.1.3 一个简单的Loader132 4.1.4 加载Loader入内存133 4.1.5 向Loader交出控制权142 4.1.6 整理boot.asm142 4.2 保护模式下的"操作系统"144 第5章 内核雏形146 5.1 用NASM在LinuxHello World146 5.2 再进一步,汇编和C同步使用148 5.3 ELF(Executable and Linkable Format)150 5.4 从Loader到内核155 5.4.1 用Loader加载ELF155 5.4.2 跳入保护模式161 5.4.3 重新放置内核170 5.4.4 向内核交出控制权175 5.4.5 操作系统的调试方法176 5.5 扩充内核184 5.5.1 切换堆栈和GDT184 5.5.2 整理我们的文件夹191 5.5.3 Makefile191 5.5.4 添加中断处理200 5.5.5 两点说明218 5.6 小结219 第6章 进程221 6.1 迟到的进程221 6.2 概述222 6.2.1 进程介绍222 6.2.2 未雨绸缪--形成进程的必要考虑222 6.2.3 参考的代码224 6.3 最简单的进程224 6.3.1 简单进程的关键技术预测225 6.3.2 第一步--ring0→ring1227 6.3.3 第二步--丰富中断处理程序243 6.3.4 进程体设计技巧254 6.4 多进程256 6.4.1 添加一个进程体256 6.4.2 相关的变量和宏257 6.4.3 进程表初始化代码扩充258 6.4.4 LDT260 6.4.5 修改中断处理程序261 6.4.6 添加一个任务的步骤总结263 6.4.7 号外:Minix的中断处理265 6.4.8 代码回顾与整理269 6.5 系统调用280 6.5.1 实现一个简单的系统调用280 6.5.2 get_ticks的应用286 6.6 进程调度292 6.6.1 避免对称--进程的节奏感292 6.6.2 优先级调度总结300 第7章 输入/输出系统302 7.1 键盘302 7.1.1 从中断开始--键盘初体验302 7.1.2 AT、PS/2键盘304 7.1.3 键盘敲击的过程304 7.1.4 解析扫描码309 7.2 显示器325 7.2.1 初识TTY325 7.2.2 基本概念326 7.2.3 寄存器328 7.3 TTY任务332 7.3.1 TTY任务框架的搭建334 7.3.2 多控制台340 7.3.3 完善键盘处理346 7.3.4 TTY任务总结354 7.4 区分任务和用户进程354 7.5 printf357 7.5.1 为进程指定TTY357 7.5.2 printf()的实现358 7.5.3 系统调用write()361 7.5.4 使用printf()363 后记366

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值