30天自制操作系统.pdf
http://www.100md.com
2019年12月25日
![]() |
| 第1页 |
![]() |
| 第5页 |
![]() |
| 第16页 |
![]() |
| 第23页 |
![]() |
| 第38页 |
![]() |
| 第574页 |
参见附件(15979KB,953页)。
30天自制操作系统,编写一个简单的操作系统难不难?当然是难的,特别对于零基础的学员,那么这本书可以帮助你从第一行代码开始编写一个属于自己的操作系统,学习吧。

30天自制操作系统介绍
《30天自制操作系统》是一本兼具趣味性、实用性与学习性的操作系统图书。作者从计算机的构造、汇编语言、C语言开始解说,让读者在实践中掌握算法。在这本书的指导下,从零编写所有代码,30天后就可以制作出一个具有窗口系统的32位多任务操作系。
《30天自制操作系统》适合操作系统爱好者和程序设计人员阅读。
30天自制操作系统作者资料
川合秀实(Hidemi Kawai),生于1975年,是一位以“轻量化”编程思想见长的“非主流”开发者。2000年因自行开发的OSASK项目而名声大噪。OSASK是一个开源的32位微型操作系统,它并非以Linux等内核为基础,而是完全从零开始开发,在一张软盘的容量下实现了GUI、多任务、多语言等高级特性,启动时间只需1秒。本书的内容可以看成是作者以OSASK为蓝本,教会读者从零开始开发一个操作系统,同时可以让初学者在编写操作系统的过程中,了解操作系统背后更多的知识。
30天自制操作系统特点
1、只需30天,从零开始编写一个五脏俱全的图形操作系统
2、39.1K迷你系统,实现多任务、汉子显示、文件压缩,还能听歌看图玩游戏
3、日本编程天才,揭开CPU、内存、磁盘以及操作系统底层工作模式的神秘面纱
30天自制操作系统部分目录
第0天 着手开发之前
1 前言
2 何谓操作系统
3 开发操作系统的各种方法
4 无知则无畏
5 如何开发操作系统
6 操作系统开发中的困难
7 学习本书时的注意事项(重要!)
8 各章内容摘要
第1天 从计算机结构到汇编程序入门
1 先动手操作
2 究竟做了些什么
3 初次体验汇编程序
4 加工润色
第2天 汇编语言学习与Makefile入门
1 介绍文本编辑器
2 继续开发
3 先制作启动区
4 Makefile入门
第3天 进入32位模式并导入C语言
1 制作真正的IPL
2 试错
3 读到18扇区
4 读入10个柱面
5 着手开发操作系统
6 从启动区执行操作系统
7 确认操作系统的执行情况
8 32位模式前期准备
9 开始导入C语言
10 实现HLT(harib00j)
第4天 C语言与画面显示的练习
1 用C语言实现内存写入(harib01a)
2 条纹图案(harib01b)
3 挑战指针(harib01c)
4 指针的应用(1)(harib01d)
5 指针的应用(2)(harib01e)
6 色号设定(harib01f)
7 绘制矩形(harib01g)
8 今天的成果(harib01h)
第5天 结构体、文字显示与GDT/IDT初始化
1 接收启动信息(harib02a)
2 试用结构体(harib02b)
3 试用箭头记号(harib02c)
4 显示字符(harib02d)
5 增加字体(harib02e)
6 显示字符串(harib02f)
7 显示变量值(harib02g)
8 显示鼠标指针(harib02h)
9 GDT与IDT的初始化(harib02i)
第6天 分割编译与中断处理
1 分割源文件(harib03a)
2 整理Makefile(harib03b)
3 整理头文件(harib03c)
4 意犹未尽
5 初始化PIC(harib03d)
6 中断处理程序的制作(harib03e)
第7天 FIFO与鼠标控制
1 获取按键编码(hiarib04a)
2 加快中断处理(hiarib04b)
3 制作FIFO缓冲区(hiarib04c)
4 改善FIFO缓冲区(hiarib04d)
5 整理FIFO缓冲区(hiarib04e)
6 总算讲到鼠标了(harib04f)
7 从鼠标接受数据(harib04g)
第8天 鼠标控制与32位模式切换
1 鼠标解读(1)(harib05a)
2 稍事整理(harib05b)
3 鼠标解读(2)(harib05c)
4 移动鼠标指针(harib05d)
5 通往32位模式之路
30天自制操作系统截图


书名:30天自制操作系统
作者:川合秀实
译者:周自恒, 李黎明, 曾祥江, 张文旭
ISBN:978-7-115-28796-0
本书由北京图灵文化发展有限公司发行数字版。版权所有,侵权必
究。
您购买的图灵电子书仅供您个人使用,未经授权,不得以任何方式复制
和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐
号等维权措施,并可能追究法律责任。目录
版 权 声 明
译 者 序
前 言
第0天 着手开发之前
1 前言
2 何谓操作系统
3 开发操作系统的各种方法
4 无知则无畏
5 如何开发操作系统
6 操作系统开发中的困难
7 学习本书时的注意事项(重要!)
8 各章内容摘要
第1天 从计算机结构到汇编程序入门
1 先动手操作
2 究竟做了些什么
3 初次体验汇编程序
4 加工润色
第2天 汇编语言学习与Makefile入门
1 介绍文本编辑器
2 继续开发
3 先制作启动区
4 Makefile入门
第3天 进入32位模式并导入C语言
1 制作真正的IPL
2 试错
3 读到18扇区
4 读入10个柱面
5 着手开发操作系统
6 从启动区执行操作系统
7 确认操作系统的执行情况
8 32位模式前期准备
9 开始导入C语言
10 实现HLT(harib00j)第4天 C语言与画面显示的练习
1 用C语言实现内存写入(harib01a)
2 条纹图案(harib01b)
3 挑战指针(harib01c)
4 指针的应用(1)(harib01d)
5 指针的应用(2)(harib01e)
6 色号设定(harib01f)
7 绘制矩形(harib01g)
8 今天的成果(harib01h)
第5天 结构体、文字显示与GDTIDT初始化
1 接收启动信息(harib02a)
2 试用结构体(harib02b)
3 试用箭头记号(harib02c)
4 显示字符(harib02d)
5 增加字体(harib02e)
6 显示字符串(harib02f)
7 显示变量值(harib02g)
8 显示鼠标指针(harib02h)
9 GDT与IDT的初始化(harib02i)
第6天 分割编译与中断处理
1 分割源文件(harib03a)
2 整理Makefile(harib03b)
3 整理头文件(harib03c)
4 意犹未尽
5 初始化PIC(harib03d)
6 中断处理程序的制作(harib03e)
第7天 FIFO与鼠标控制
1 获取按键编码(hiarib04a)
2 加快中断处理(hiarib04b)
3 制作FIFO缓冲区(hiarib04c)
4 改善FIFO缓冲区(hiarib04d)
5 整理FIFO缓冲区(hiarib04e)
6 总算讲到鼠标了(harib04f)
7 从鼠标接受数据(harib04g)
第8天 鼠标控制与32位模式切换
1 鼠标解读(1)(harib05a)
2 稍事整理(harib05b)3 鼠标解读(2)(harib05c)
4 移动鼠标指针(harib05d)
5 通往32位模式之路
第9天 内存管理
1 整理源文件(harib06a)
2 内存容量检查(1)(harib06b)
3 内存容量检查(2)(harib06c)
4 挑战内存管理(harib06d)
第10天 叠加处理
1 内存管理(续)(harib07a)
2 叠加处理(harib07b)
3 提高叠加处理速度(1)(harib07c)
4 提高叠加处理速度(2)(harib07d)
第11天 制作窗口
1 鼠标显示问题(harib08a)
2 实现画面外的支持(harib08b)
3 shtctl的指定省略(harib08c)
4 显示窗口(harib08d)
5 小实验(harib08e)
6 高速计数器(harib08f)
7 消除闪烁(1)(harib08g)
8 消除闪烁(2)(harib08h)
第12天 定时器(1)
1 使用定时器(harib09a)
2 计量时间(harib09b)
3 超时功能(harib09c)
4 设定多个定时器(harib09d)
5 加快中断处理(1)(harib09e)
6 加快中断处理(2)(harib09f)
7 加快中断处理(3)(harib09g)
第13天 定时器(2)
1 简化字符串显示(harib10a)
2 重新调整FIFO缓冲区(1)(harib10b)
3 测试性能(harib10c~harib10f)
4 重新调整FIFO缓冲区(2)(harib10g)
5 加快中断处理(4)(harib10h)
6 使用“哨兵”简化程序(harib10i)第14天 高分辨率及键盘输入
1 继续测试性能(harib11a ~ harib11c)
2 提高分辨率(1)(harib11d)
3 提高分辨率(2)(harib11e)
4 键盘输入(1)(harib11f)
5 键盘输入(2)(harib11g)
6 追记内容(1)(harib11h)
7 追记内容(2)(harib11i)
第15天 多任务(1)
1 挑战任务切换(harib12a)
2 任务切换进阶(harib12b)
3 做个简单的多任务(1)(harib12c)
4 做个简单的多任务(2)(harib12d)
5 提高运行速度(harib12e)
6 测试运行速度(harib12f)
7 多任务进阶(harib12g)
第16天 多任务(2)
1 任务管理自动化(harib13a)
2 让任务休眠(harib13b)
3 增加窗口数量(harib13c)
4 设定任务优先级(1)(harib13d)
5 设定任务优先级(2)(harib13e)
第17天 命令行窗口
1 闲置任务(harib14a)
2 创建命令行窗口(harib14b)
3 切换输入窗口(harib14c)
4 实现字符输入(harib14d)
5 符号的输入(harib14e)
6 大写字母与小写字母(harib14f)
7 对各种锁定键的支持(harib14g)
第18天 dir命令
1 控制光标闪烁(1)(harib15a)
2 控制光标闪烁(2)(harib15b)
3 对回车键的支持(harib15c)
4 对窗口滚动的支持(harib15d)
5 mem命令(harib15e)
6 cls命令(harib15f)7 dir命令(harib15g)
第19天 应用程序
1 type命令(harib16a)
2 type命令改良(harib16b)
3 对FAT的支持(harib16c)
4 代码整理(harib16d)
5 第一个应用程序(harib16e)
第20天 API
1 程序整理(harib17a)
2 显示单个字符的API(1)(harib17b)
3 显示单个字符的API(2)(harib17c)
4 结束应用程序(harib17d)
5 不随操作系统版本而改变的API(harib17e)
6 为应用程序自由命名(harib17f)
7 当心寄存器(harib17g)
8 用API显示字符串(harib17h)
第21天 保护操作系统
1 攻克难题——字符串显示API(harib18a)
2 用C语言编写应用程序(harib18b)
3 保护操作系统(1)(harib18c)
4 保护操作系统(2)(harib18d)
5 对异常的支持(harib18e)
6 保护操作系统(3)(harib18f)
7 保护操作系统(4)(harib18g)
第22天 用C语言编写应用程序
1 保护操作系统(5)(harib19a)
2 帮助发现bug(harib19b)
3 强制结束应用程序(harib19c)
4 用C语言显示字符串(1)(harib19d)
5 用C语言显示字符串(2)(harib19e)
6 显示窗口(harib19f)
7 在窗口中描绘字符和方块(harib19g)
第23天 图形处理相关
1 编写malloc(harib20a)
2 画点(harib20b)
3 刷新窗口(harib20c)
4 画直线(harib20d)5 关闭窗口(harib20e)
6 键盘输入API(harib20f)
7 用键盘输入来消遣一下(harib20g)
8 强制结束并关闭窗口(harib20h)
第24天 窗口操作
1 窗口切换(1)(harib21a)
2 窗口切换(2)(harib21b)
3 移动窗口(harib21c)
4 用鼠标关闭窗口(harib21d)
5 将输入切换到应用程序窗口(harib21e)
6 用鼠标切换输入窗口(harib21f)
7 定时器API(harib21g)
8 取消定时器(harib21h)
第25天 增加命令行窗口
1 蜂鸣器发声(harib22a)
2 增加更多的颜色(1)(harib22b)
3 增加更多的颜色(2)(harib22c)
4 窗口初始位置(harib22d)
5 增加命令行窗口(1)(harib22e)
6 增加命令行窗口(2)(harib22f)
7 增加命令行窗口(3)(harib22g)
8 增加命令行窗口(4)(harib22h)
9 变得更像真正的操作系统(1)(harib22i)
10 变得更像真正的操作系统(2)(harib22j)
第26天 为窗口移动提速
1 提高窗口移动速度(1)(harib23a)
2 提高窗口移动速度(2)(harib23b)
3 提高窗口移动速度(3)(harib23c)
4 提高窗口移动速度(4)(harib23d)
5 启动时只打开一个命令行窗口(harib23e)
6 增加更多的命令行窗口(harib23f)
7 关闭命令行窗口(1)(harib23g)
8 关闭命令行窗口(2)(harib23h)
9 start命令(harib23i)
10 ncst命令(harib23j)
第27天 LDT与库
1 先来修复bug(harib24a)2 应用程序运行时关闭命令行窗口(harib24b)
3 保护应用程序(1)(harib24c)
4 保护应用程序(2)(harib24d)
5 优化应用程序的大小(harib24e)
6 库(harib24f)
7 整理make环境(harib24g)
第28天 文件操作与文字显示
1 alloca(1)(harib25a)
2 alloca(2)(harib25b)
3 文件操作API(harib25c)
4 命令行API(harib25d)
5 日文文字显示(1)(harib25e)1
6 日文文字显示(2)(harib25f)
7 日文文字显示(3)(harib25g)
第29天 压缩与简单的应用程序
1 修复bug(harib26a)
2 文件压缩(harib26b)
3 标准函数
4 非矩形窗口(harib26c)
5 bball(harib26d)
6 外星人游戏(harib26e)
第30天 高级的应用程序
1 命令行计算器(harib27a)
2 文本阅览器(harib27b)
3 MML播放器(harib27c)
4 图片阅览器(harib27d)
5 IPL的改良(harib27e)
6 光盘启动(harib27f)
第31天 写在开发完成之后
1 继续开发要靠大家的努力
2 关于操作系统的大小
3 操作系统开发的诀窍
4 分享给他人使用
5 关于光盘中的软件
6 关于开源的建议
7 后记
8 毕业典礼9 附录
版 权 声 明
30 Nichi De Dekiru OS Jisaku Nyuumon by 川合秀実
Copyright ? 2006 Hidemi Kawai
All rights reserved.
Original Japanese edition Published by Mynavi Corporation.
This Simplified Chinese edition is published by arrangement with Mynavi
Corporation.
本书中文简体字版由Mynavi Corporation授权人民邮电出版社独家出版。
未经出版者书面许可,不得以任何方式复制或抄袭本书内容。
版权所有,侵权必究。译 者 序
《30天自制操作系统》中文版终于和国内读者见面了。标题一出,有人
说“XX天”这种标题真不靠谱,不过,作者取这个标题,并非随随便便
之举。打个比方,“30天学会核物理”看起来“假大空”,如果改成“30天
自制微型反应堆”呢?虽然可能还是太难了,但至少你知道30天之后一
定能做出一个反应堆来(即便简陋)。这本书正是属于后者:不管多简
单,它都是一个真正意义上的操作系统,更何况它还真不简单,40KB
便实现了图形界面、多任务等高级功能。只要跟着作者的脚步,你也能
做到。即便只是抄抄代码,也必定有所收获。
这本书的定位是零基础的读者,作者甚至找了中学生来试读,语言通俗
易懂,轻松幽默。作为译者,我很喜欢这样的风格,因为可以把很多好
玩的流行词汇代入进去,不会破坏原书的意境,还能让大家看起来更有
意思。从技术角度来看,这本书并没有过多地解释技术细节。作者认
为,自制操作系统最终的目的还是为了好玩。因此,想从这本书系统学
习计算机原理、汇编语言、C语言等知识是不现实的,但你一定能够获
得另一种完全不同的体验。
这本书的一大特色是“从失败中学习”,每次我们为这个操作系统实现一
些功能,一开始总会有一些漏洞和缺陷,甚至根本不能工作。这些漏洞
都是刻意安排的。作者花了很大篇幅来引导读者去寻找并发现这些漏
洞,并从中学习如何让系统变得更加完善。这种思路非常有趣,也符合
实际开发过程,先苦后甜乃是成就感和幸福感的源泉。市面上的技术类
书籍,很少有这种“试错”的过程,因为这需要精心的安排,而且占用大
量的篇幅。这正是这本书的与众不同之处,也是我认为值得向大家推荐
它的主要理由。
如果你是一位高手,可能会觉得这本书的内容并不是那么系统和有条
理,甚至觉得做出来的操作系统在很多方面的处理都很简陋,算不上一
个实用的系统。连作者自己都说:“这本书无论在哪个方面都只有半瓶
醋。”不过,作者是在带领大家从零开始编写一个系统,而并不是以一
个现成的内核(如Linux、FreeBSD)为基础——后者才是目前自制系统
的主流方式。然而,只有从零开始,才能真正了解系统底层是如何运作
的,对于在其他内核上构筑系统也大有裨益。另外,千万别忘了读一读
最后那个叫做“这也能叫自制操作系统?太坑爹了!”的专栏,作者早就预料到了读者的各种吐槽,看过之后,你可能就会理解作者的良苦用心
了。
这本书讲到了“日文显示”,在翻译上相当纠结。由于操作系统都是底层
代码,牵一发而动全身,为了不改动原书的结构和代码,中文版在原汁
原味保留原书文字的基础上,补充了一些中文显示的相关内容,以体现
两者在实现上的异同。好在基本上只要替换字库和编码方式,就可以实
现中文显示,甚至比日文还简单些。这部分补充内容是我自己写的,但
我自知才疏学浅,不敢班门弄斧,如有错误或疏漏,欢迎各位高手随时
拍砖。此外,关于光盘中代码的注释,由于量大繁杂,恕无法翻译成中
文(书中代码注释已翻译),非常抱歉。如果发现注释为乱码,请用
UltraEdit等编辑器以Shift-JIS编码打开,就可以看到正常的日文了。
最后,在这里衷心感谢其他三位译者,以及图灵公司各位编辑的共同努
力,使得这本书能够最终问世,希望所有对编写操作系统有兴趣的读者
都能从中获益。
周自恒
2012年9月于上海前 言
“好想编写一个操作系统呀!”笔者的朋友曾说这是所有程序员都曾经怀
揣的一个梦想。说“所有的程序员”可能有点夸张了,不过作为程序员的
梦想,它至少也应该能排进前十名吧。
也许很多人觉得编写操作系统是个天方夜谭,这一定是操作系统业界的
一个阴谋(笑)。他们故意让大家相信编写操作系统是一件非常困难的
事情,这样就可以高价兜售自己开发的操作系统,而且操作系统的作者
还会被顶礼膜拜。那么实际情况又怎么样呢?和别的程序相比,其实编
写操作系统并没有那么难,至少笔者的感觉是这样。
在各位读者之中,也许有人曾经挑战过操作系统的编写,但因为太难而
放弃了。拥有这样经历的人也许不会认同笔者的观点。其实你错了,你
的失败并不是因为编写操作系统太难,而是因为没有人告诉你那其实是
一件很简单的事而已。
不仅是编写操作系统,任何事都是一样的。如果讲解的人认为它很难,那就不可能把它讲述得通俗易懂,即便是同样的内容,也会讲得无比复
杂。这样的讲解,肯定是很难懂的。
那么,你想不想和笔者一起再挑战一次呢?如果你曾经梦想过编写自己
的操作系统,一定会觉得乐在其中的。
可能有人会说,这本书足足有700多页,怎么会“有趣”和“简单”呢?
唔,这么一说笔者也觉得挺心虚的,不过其实也只是长了那么一点点
啦。平均下来的话,每天只有大约23页的内容,你看,也没有那么长
吧?
这本书的文风非常轻松,也许你不知不觉中就会读得很快。但是这样的
话可能印象不会很深,最好还是能静下心来慢慢地读。书中所展示的程
序代码和文字的说明同样重要,因此也希望大家仔细阅读。只要注意这
些,理解本书的内容就应该没有问题了。
在本书中,我们使用C语言和汇编语言来编写操作系统,不过不必担
心,你可以在阅读本书的同时来逐步学习关于这些编程语言的知识。本书在这方面写得非常仔细,如果能有人通过本书终于把C语言中的指针
给搞懂了,那笔者的目的也就达到了。即便是从这样的水平开始,30天
后你也能够编写出一个很棒的操作系统,请大家拭目以待吧!第0天 着手开发之前
前言
何谓操作系统
开发操作系统的各种方法
无知则无畏
如何开发操作系统
操作系统开发中的困难
学习本书时的注意事项(重要!)
各章内容摘要1 前言
现在,挑选自己喜欢的配件来组装一台世界上独一无二的、个性化的
PC(个人电脑)对我们来说已不再困难。不仅如此,只要使用合适的
编译器1,我们就可以自己编写游戏、制作自己的工具软件;使用网页
制作工具,我们还可以轻而易举地制作主页;如果看过名著《CPU制作
法》2的话,就连自制CPU也不在话下。
1 英文为compiler,指能够将源代码编译成机器码的软件。
2《CPU制作法》,渡波郁著,每日Communications出版公司,ISBN 4-8399-0986-5。
然而,在“自制领域”里至今还有一个无人涉足的课题——自己制作操作
系统(OS)3,它看起来太难以至于初学者不敢轻易挑战。电脑组装也
好,游戏、工具软件制作也好,主页也好,CPU也好,这些都已经成为
初学者能够尝试的项目,而唯独操作系统被冷落在一边,实在有些遗
憾。“既然还没有这样的书,那我就来写一本。”这就是笔者撰写本书的
初衷。
3 Operating System的缩写,汉语译作“操作系统”。Windows、Linux、MacOS、MS-DOS等软件的总称。
也许是因为面向初学者的书太少的缘故吧,一说起操作系统,大家就会
觉着那东西复杂得不得了,简直是高深莫测。特别是像Windows和
Linux这些操作系统,庞大得一张光盘都快装不下了,要是一个人凭着
兴趣来开发的话,不知道需要历经多么漫长的过程才能完成。笔者也认
为,像这么复杂的操作系统,单凭一个人来做,一辈子都做不出来。
不过大家也不必担心太多。笔者就成功地开发过一个小型操作系统,其
大小还不到80KB4。麻雀虽小,五脏俱全,这个操作系统的功能还是很
完整的。有人也许会怀疑:“这么小的操作系统,是不是只有命令行窗
口5啊?要不就是没有多任务6?”不,这些功能都有。
4 kilobyte,程序及数据大小的度量单位,1字节(byte)的1024倍。
一张软盘的容量是1440KB。顺便提一下,1024KB等于1MB(兆字节)。1字节是8个比特,正好能记录8位0和1的信息。B到底是指字
节(byte),还是指比特(bit),有时容易混淆。这里根据一般的
规则,用大写B表示字节,小写b表示比特。
5 console,通过键盘输入命令的一种方式,基本上只用文字进行计
算机操作,是MS-DOS等老式操作系统的主流操作方式。
6 在操作系统的世界里,运行中的程序叫做“任务”,而同时执行多
个任务的方式就被称为“多任务”(multitask)。
怎么样,只有80KB的操作系统,大家不觉得稍作努力就可以开发出来
吗?即使是初学者,恐怕也会觉得这不是件难事吧?没错,我们用一个
月的时间就能写出自己的操作系统!所以大家不用想得太难,我们轻轻
松松地一起来写写看吧。
以本书作者为主角开发的操作系统OSASK7
7 笔者与他人一起合作开发的操作系统(趁机宣传一下)。虽然只
有小小的78KB,不过为了做它也花了好几年的时间。而这次能在
短时间内开发完成操作系统,是因为我们较好地总结了开发操作系统所必要的知识。也就是说,如果笔者在年轻时可以看到现在这本
书的话,可能在短时间内就能开发出OSASK了,所以笔者很羡慕
大家呀。
大家一听到编译后的文件大小为80KB可能会觉得它作为程序来讲
已经很小了,不过曾经编过程序的人可以查一查自己编的程序
(.exe文件)的大小,这样就能体会到80KB到底是难是易了。
没编过程序的人也可以下载一个看上去不是很复杂的自由软件,看
看它的可执行文件有多大。Windows 2000的计算器程序大约是
90KB,大家也可以根据这个想象一下。
本书对于不打算自己写操作系统,甚至连想都没想过这个问题的人来说
也会大有裨益。举个例子,读本自己组装PC的书就能知道PC是由哪些
组件构成的,PC的性能是由哪些部分决定的;读本如何编写游戏的
书,就能明白游戏是怎样运行的;同理,读了本书,了解了操作系统的
开发过程,就能掌握操作系统的原理。所以说,对操作系统有兴趣的
人,哪怕并不想自己做一个出来,也可以看看这本书。
阅读本书几乎不需要相关储备知识,这一点稍后还会详述。不管是用什
么编程语言,只要是曾经写过简单的程序,对编程有一些感觉,就已经
足够了(即使没有任何编程经验,应该也能看懂),因为这本书主要就
是面向初学者的。书中虽然有很多C语言程序,但实际上并没有用到很
高深的C语言知识,所以就算是曾经因为C语言太难而中途放弃的人也
不用担心看不懂。当然,如果具备相关知识的话,理解起来会相对容易
一些,不过即使没有相关知识也没关系,书中的说明都很仔细,大家可
以放心。
本书以IBM PCAT兼容机(也就是所谓的Windows个人电脑)为对象进
行说明。至于其他机型8,比如Macintosh(苹果机)或者PC-9821等,虽
然本书也参考了其中某些部分,但基本上无法开发出在这些机型上运行
的操作系统,这一点还请见谅。严格地说,不是所有能称为AT兼容机
的机型都可以开发我们这个操作系统,我们对机器的配置要求是CPU高
于386(因为我们要开发32位操作系统)。换句话说,只要是能运行
Windows 95以上操作系统的机器就没有问题,况且现在市面上(包括二
手市场)恐怕都很难找到Windows 95以下的机器了,所以我们现在用的机型一般都没问题。
8 本书所讲的操作系统内容仅用Macintosh是开发不了的,并且开发
出的操作系统也不能直接在Macintosh上运行。但是在PC上开发的
操作系统,可以通过模拟器在Macintosh上运行。
另外,大家也不用担心内存容量和硬盘剩余空间,我们需要使用的空间
并不大。只要满足以上条件,就算机器又老又慢,也能用来开发我们的
操作系统。2 何谓操作系统
说老实话,其实笔者也不是很清楚。估计有人会说:“连这个都不懂,还写什么书?”不好意思……笔者见过很多种操作系统,有的功能非常
多,而有的功能特别少。在比较了各种操作系统之后,笔者还是没有找
到它们功能的共同点,无法下定义。结果就是,软件作者坚持说自己做
的就是操作系统,而周围的人也不深究,就那样默认了,以至于什么软
件都可以算是操作系统。笔者现在就是这么认为的。
既然就操作系统而言各有各的说法,那笔者也可以反过来利用这一点,一开始就根据自己的需要来定义操作系统,然后开发出一个满足自己定
义条件的软件就可以了。这当然也算是开发操作系统了。哪怕做一个
MS-DOS那样的,在一片漆黑的画面上显示出白字,输入个命令就能执
行的操作系统也可以,这对笔者来说很简单。
但这样肯定会让一些读者大失所望。现在初学者也都见多识广,一提到
操作系统,大家就会联想到Windows、Linux之类的庞然大物,所以肯
定期待自制操作系统至少能任意显示窗口、实现鼠标光标控制、同时运
行几个应用程序,等等。所以为了满足读者的期待,我们这次就来开发
一个具有上述功能的操作系统。3 开发操作系统的各种方法
开发操作系统的方法也是各种各样的。
笔者认为,最好的方法就是从既存操作系统中找一个跟自己想做的操作
系统最接近的,然后在此基础上加以改造。这个方法是最节省时间的。
但本书却故意舍近求远,一切从零开始,完完全全是自己从头做起,这
是因为笔者想向各位读者介绍从头到尾开发操作系统的全过程。如果我
们找一个现成的操作系统,然后在此基础上删删改改的话,那这本书就
不能涉及操作系统全盘的知识了,这样肯定无法让读者朋友满意。不过
由于是全部从零做起,所以篇幅长些,还请读者朋友们耐下心来慢慢
看。
要开发操作系统,首先遇到的问题就是使用什么编程语言,这次我们想
以C语言为主。“啊,C语言啊?”笔者仿佛已经听到大家抱怨的声音了
(苦笑)。“这都什么年代了,用C语言多土啊”、“用C++多好呀”、“还
是Java好”、“不,我就喜欢Delphi”、“我还是觉得Visual Basic最好”……
大家个人喜好习惯各不相同。这种心情笔者都能理解,但为了讲解时能
简单一些,笔者还是想用C语言,请大家见谅。C语言功能虽不多,但
用起来方便,所以用来开发操作系统刚好合适。要是用其他语言的话,仅讲解语言本身就要花很长时间,大家恐怕就没兴趣看下去了。
在这里先向大家传授一个从零开始开发操作系统的诀窍,那就是不要一
开始就一心想着要开发操作系统,先做一个有点操作系统样子的东西就
行了。如果我们一上来就要开发一个完整的操作系统的话,要做的东西
太多,想想脑袋都大了,到时恐怕连着手的勇气也没有了。笔者就是因
为这个,几年间遇到了很多挫折。所以在这本书里,我们不去大张旗鼓
地想着要开发一个操作系统,而是编写几个像操作系统的演示程序1就
行了。其实在开发演示程序的过程中大家就会逐步发现,演示程序不再
是简单的演示程序,而是越来越像一个操作系统了。
1 演示程序的英文是demonstration。指不是为了使用,而是为了演
示给人看的软件。4 无知则无畏
当我们打算开发操作系统时,总会有人从旁边跳出来,罗列出一大堆专
业术语,问这问那,像内核怎么做啦,外壳怎么做啦,是不是单片啦,是不是微内核啦,等等。虽然有时候提这些问题也是有益的,但一上来
就问这些,当然会让人无从回答。
要想给他们一个满意答复,让他们不再从旁指手画脚的话,还真得多学
习,拿出点像模像样的见解才行。但我们是初学者,没有必要去学那些
麻烦的东西,费时费力且不说,当我们知道现有操作系统在各方面都考
虑得如此周密的时候,就会发现自己的想法太过简单而备受打击没了干
劲。如果被前人的成果吓倒,只用这些现有的技术来做些拼拼凑凑的工
作,岂不是太没意思了。
所以我们这次不去学习那些复杂的东西,直接着手开发。就算知道一大
堆专业术语、专业理论,又有什么意思呢?还不如动手去做,就算做出
来的东西再简单,起码也是自己的成果。而且自己先实际操作一次,通
过实践找到其中的问题,再来看看是不是已经有了这些问题的解决方
案,这样下来更能深刻地理解那些复杂理论。不管怎么说,反正目前我
们也无法回答那些五花八门的问题,倒不如直接告诉在一旁指手画脚的
人们:我们就是想用自己的方法做自己喜欢的事情,如果要讨论高深的
问题,就另请高明吧。
■■■■■
其实反过来看,什么都不知道有时倒是好事。正是因为什么都不知道,我们才可能会认真地去做那些专家们嗤之以鼻的没意义的“傻事”。也许
我们大多时候做的都没什么意义,但有时也可能会发掘出专家们千虑一
失的问题呢。专家们在很多方面往往会先入为主,甚至根本不去尝试就
断定这也不行那也不行,要么就浅尝辄止。因此能够挑战这些问题的,就只有我们这种什么都不知道的门外汉。任何人都能通过学习成为专
家,但是一旦成为专家,就再也找不回门外汉的挑战精神了。所以从零
开始,在没有各种条条框框限制的情况下,能做到什么程度就做到什么
程度,碰壁以后再回头来学习相关知识,也为时未晚。
实际上笔者也正是这样一路磕磕绊绊地走过来,才有了今天。笔者没去过教授编程的学校,也几乎没学什么复杂的理论就开始开发操作系统
了。但也正是因为这样,笔者做出的操作系统与其他的操作系统大不相
同,非常有个性,所以得到了专家们的一致好评,而且现在还能有机会
写这本书,向初学者介绍经验。总地说来,笔者从着手开发直到现在,每天都是乐在其中的。
正是像笔者这样自己摸着石头过河,一路磕磕绊绊走过来的人,讲出的
东西才简单易懂。不过在讲解过程中会涉及失败的经验,以及如何重新
修正最终取得成功,所以已经懂了的人看着可能会着急。不好意思,如
果碰到这种情况请忍耐一下吧。
读了这部分内容或许有人会觉得“是不是什么都不学习才是最好的啊”,其实那倒不是。比如工作上需要编写某些程序,或者一年之内要完成某
些任务,这时没有时间去故意绕远路,所以为了避免不必要的失败,当
然是先学习再着手开发比较好。但这次我们是因为自己的兴趣而学习操
作系统的开发的,既然是兴趣,那就是按自己喜欢的方式慢慢来,这样
就挺好的。5 如何开发操作系统
操作系统(OS)一般打开电源开关就会自动执行。这是怎么实现的
呢?一般在Windows上开发的可执行文件(~.exe),都要在操作系统
启动以后,双击一下才能运行。我们这次想要做的可不是这种可执行程
序,而是希望能够做到把含有操作系统的CD-ROM或软盘插入电脑,或
者将操作系统装入硬盘后,只要打开电源开关就能自动运行。
为了开发这样的操作系统,我们准备按照如下的步骤来进行。
1 source program,为了生成机器码所写的程序代码。可通过编译器
编译成机器语言。
2 CPU能够直接理解的语言,由二进制的0和1构成。其实源代码也
是由 0和1构成的(后述)。
也就是说,所谓开发操作系统,就是想办法制作一张“含有操作系统
的,能够自动启动的磁盘”。
这里出现的“映像文件”一词,简单地说就是软盘的备份数据。我们想要
把特定的内容写入磁盘可不是拿块磁铁来在磁盘上晃晃就可以的。所以
我们要先做出备份数据,然后将这些备份数据写入磁盘,这样才能做出
符合我们要求的磁盘。
软盘的总容量是1440KB,所以作为备份数据的映像文件也恰好是
1440KB。一旦我们掌握了制作磁盘映像的方法,就可以按自己的想法
制作任意内容的磁盘了。这里希望大家注意的是,开发操作系统时需要利用Windows等其他的操
作系统。这是因为我们要使用文本编辑器或者C编译器,就必须使用操
作系统。既然是这样,那么世界上第一个操作系统又是怎么做出来的
呢?在开发世界上第一个操作系统时,当然还没有任何现成的操作系统
可供利用,因此那时候人们不得不对照着CPU的命令代码表,自己将0
和1排列起来,然后再把这些数据写入磁盘(估计那个时候还没有磁
盘,用的是其他存储设备)。这是一项非常艰巨的工作。所以恐怕最初
的操作系统功能非常有限,做好之后人们再利用它来开发一个稍微像点
样的操作系统,然后再用这个来开发更实用的操作系统……操作系统应
该就是这样一步一步发展过来的。
由于这次大部分初学者都是Windows用户,所以决定使用Windows这个
现成的操作系统,Windows9598Me2000XP中任意一个版本都可以。
肯定也有人会说还是Linux好用,所以笔者也总结了一下Linux上的做
法,具体内容写在了帮助与支持3里,有需要的人请一定看一看。
3 http:hrb.osask.jp。
另外,如果C编译器和映像文件制作工具等不一样的话,开发过程中就
会产生一些细微的差别,这很难一一解释,所以笔者就直接把所有的工
具都放到附带光盘里了。这些几乎都是笔者所发布的免费软件,它们大
都是笔者为了开发后面的OSASK操作系统而根据需要自己编写的。这
些工具的源代码也是公开的。除此之外,我们还会用到其他一些免费软
件,所有这些软件的功能我们会在使用的时候详细介绍。6 操作系统开发中的困难
现在市面上众多的C编译器都是以开发Windows或Linux上的应用程序为
前提而设计的,几乎从来没有人想过要用它们来开发其他的软件,比如
自己的操作系统。笔者所提供的编译器,也是以Windows版的gcc1为基
础稍加改造而做成的,与gcc几乎没什么不同。或许也有为开发操作系
统而设计的C编译器,不过就算有,恐怕也只有开发操作系统的公司才
会买,所以当然会很贵。这次我们用不了这么高价的软件。
1 GNU项目组开发的免费C编译器,GNU C Compiler的简称。有时
也指GUN开发的各种编译器的集合(GNU Compiler Collection)。
因为这些原因,我们只能靠开发应用程序用的C编译器想方设法编写出
一个操作系统来。这实际上是在硬来,所以当中就会有很多不方便的地
方。
就比如说printf(“hello\n”);吧,这个函数总是出现在C语言教科书的第一
章,但我们现在就连它也无法使用。为什么呢?因为printf这个函数是
以操作系统提供的功能为前提编写的,而我们最开始的操作系统可是什
么功能都没有。因此,如果我们硬要执行这个函数的话,CPU会发生一
般保护性异常2,直接罢工。刚开始的时候不仅是printf,几乎所有的函
数都无法使用。
2 电脑的CPU非常优秀,如果接到无视OS保护的指令或不可能执行
的指令时,首先会保存当前状态,中断正在执行的程序,然后调用
事先设定的函数。这种机制称为异常保护功能,比如除法异常、未
定义指令异常、栈异常等。不能归类到任何异常类型中去的异常事
态被称为一般保护异常。这种异常保护功能或许会让老Windows用
户想起那噩梦般的蓝屏画面,但是如果经历过操作系统开发以后,大家就会觉得这种机制实在是太有用了。
关于这次开发语言的选择,如果非要说出个所以然的话,其实也是
因为C语言还算是很少依赖操作系统功能的语言,基本上只要不用
函数就可以了。如果用C++的话,像newdelete这种基本而重要的运算符都不能用了,另外对于类的做法也会有很多要求,这样就无法
发挥C++语言的优势了。当然,为了使用这些函数去开发操作系
统,只要我们想办法,还是能够克服种种困难的。但是如果做到这
个份上,我们不禁会想,到底是在用C++做操作系统呢,还是在为
了C++而做操作系统呢。对别的语言而言这个问题会更加突出,所
以这次还是决定使用C语言,希望大家予以理解。
顺便插一句,在开发操作系统时不会受到限制的语言大概就只有汇编语
言3了。还是汇编语言最厉害4(笑)。但是如果本书仅用汇编来编写操
作系统的话,恐怕没几个人会看,所以就算是做事管前不顾后的笔者也
不得不想想后果。
3 Assembler,与机器语言最接近的一种编程语言。过去掌握这种语
言的人会备受尊敬,而现在这种人恐怕要被当作怪人了,真是可悲
啊。原本汇编语言的正式名称应该是Assembly语言,而Assembler
一般指的是编译程序。不过像笔者这样的老程序员,往往不对这两
个词进行区分,统称为Assembler。
4 读到这里,大家可能还不理解为什么这么说,越往后看就越能慢
慢体会到了。
另外,在开发操作系统时,需要用到CPU上的许多控制操作系统的
寄存器5。一般的C编译器都是用于开发应用程序的,所以根本没有
任何操作这些寄存器的命令。另外,C编译器还具有非常优秀的自
动优化功能,但有时候这反而会给我们带来麻烦。
5 Register,有些类似机器语言中的变量。对CPU而言,内存是外部
存储装置,在CPU内核之中,存储装置只有寄存器。全部寄存器的
容量加起来也不到1KB。
归根到底,为了克服以上这些困难,有些没法用C语言来编写的部分,我们就只好用汇编语言来写了。这个时候,我们就必须要知道C编译器
到底是怎样把程序编译成机器语言的。如果不能够与C编译器保持一致
的话,就不能将汇编语言编写的部分与C语言编写的部分很好地衔接起
来。这可是在编写普通的C语言程序时所体会不到哦!不过相比之下,今后的麻烦可比这种好处多得多啊(苦笑)。同样,如果用C++来编写操作系统,也必须知道C++是如何把程序
编译成机器语言的。当然,C++比C功能更多更强,编译规则也更
复杂,所以解释起来也更麻烦,我们选用C语言也有这一层理由。
总之,如果不理解自己所使用的语言是如何进行编译的,就没法用
这种语言来编写操作系统。
书店里有不少C语言、C++的书,当然也还有Delphi、Java等其他各种编
程语言的书,但这么多书里没有一本提到过“这些源代码编译过后生成
的机器语言到底是什么样的”。不仅如此,虽然我们是在通过程序向
CPU发指令的,但连CPU的基本结构都没有人肯给我们讲一讲。作为一
个研究操作系统的人,真觉得心里不是滋味。为了弥补这一空缺,我们
这本书就从这些基础讲起(但也仅限于此次开发操作系统所必备的基础
知识)。
我们具备了这样的知识以后,说不定还会改变对程序设计的看法。以前
也许只想着怎么写出漂亮的源代码来,以后也许就会更注重编译出来的
是怎样的机器语言。源代码写得再漂亮,如果不能编译成自己希望的机
器语言,不能正常运行的话,也是毫无意义的。反过来说,即便源代码
写得难看点儿,即便只有特定的C编译器才能编译,但只要能够得到自
己想要的机器语言就没有问题了。虽然不至于说“只要编译出了想要的
机器语言,源代码就成了一张废纸”,但从某种意义上说还真就是这
样。
对于开发操作系统的人而言,源程序无非是用来得到机器语言的“手
段”,而不是目的。浪费太多时间在手段上就是本末倒置了。
对了,还有一点或许会有人担心,所以在这里事先说明一下:虽然操作
系统是用C语言和汇编语言编写的,但并不是用C++编写的应用程序就
无法在这个操作系统上运行。编写应用程序所用的语言,与开发操作系
统所使用的语言是没有任何关系的,大家大可不必担心。7 学习本书时的注意事项(重要!)
本书从第1章开始,写的是每一天实际开发的内容,虽然一共分成了30
天,但这些都是根据笔者现在的能力和讲解的长度来大概切分的,并不
是说读者也必须得一天完成一章。每个人觉得难的地方各不相同,有时
学习一章可能要花上一星期的时间,也有时可能一天就能学会三章的内
容。
当然,学习过程中可能会遇到看不太懂的章节,这种时候不要停下来,先接着往下读上个一两章也许会突然明白过来。如果往后看还是不明白
的话,就先确认一下自己已经理解到哪一部分了,然后回过头来再从不
懂的地方重新看就是了。千万别着急,看第二遍时,没准就会豁然开朗
了。
如果已经弄清了哪里没理解,而且没理解的部分看了很多遍还是不明白
的话,大家可以参阅我们的帮助与支持页面1,或许“问题与解
答”(QA)页里会有解说。
1 http:hrb.osask.jp。
■■■■■
本书对C语言的指针和结构体的说明与其他书籍有很大区别。这是因为
本书先讲CPU的基本结构,然后讲汇编,最后再讲C语言,而其他的书
都不讲这些基础知识,刚一提到指针,马上就转到变量地址如何如何
了。所以就算大家“觉得”已经明白了那些书里讲的指针,也不要把本书
的指针部分跳过去,相信这次大家能真正地理解指针。当然,如果真的
已经弄明白了的话,大概看看就可以了。
■■■■■
从现在开始我们来一点一点地开发操作系统,我们会将每个阶段的进展
情况总结出来,这些中间成果都刻在附带光盘里了,只要简单地复制一
下就能马上运行。关于这些程序,有些需要注意的地方,我们在这里简
单说明一下。
比如最初出现的程序是“helloos0”,下一个出现的程序是“helloos1”。 即使我们以helloos0为基础,把书中讲解的内容一个不漏地全部做上一
遍,也不能保证肯定可以得到后面的helloos1。书中可能偶尔有讲解得
很完整的地方,但其实大多部分都讲得不够明确,这主要是因为笔者觉
得这些地方不讲那么仔细大家肯定也能明白。
笔者说这些主要就是想要告诉大家,不仅要看书里的内容,更要好好看
程序。有时候书上写得很含糊,读起来晦涩难懂,但一看程序马上就明
白了。本书的主角不是正文内容,而是附录中的程序。正文仅仅是介绍
程序是如何做出来的。
所以说从这个意义上讲,与其说这是“一本附带光盘的书”,倒不如说这
是“一张附带一本大厚书的光盘”(笑)。
■■■■■
关于程序还有一点要说明的——这里收录的程序的版权全部归笔者所
有。可是,读了这本书后打算开发自己的操作系统的话,可能有不少地
方要仿照着附带程序来做;也有人可能想把程序的前期部分全盘照搬过
来用;还有人可能想接着本书最后的部分继续开发自己的操作系统。
这是一本关于操作系统的教材,如果大家有上面这些想法却不能自由使
用附录程序的话,这教材也就没什么意义了,所以大家可以随意使用这
些程序,也不用事先提出任何申请。尽管大家最后做出来的操作系统中
可能会包含笔者编写的程序,不过也不用在版权声明中署上笔者的名
字。大家可以把它当作自己独立开发的操作系统,也可以卖了它去赚
钱。就算大家靠这个系统成了亿万富翁,笔者也不会要分毫的分成,大
家大可放心2。
2 在版权署名时,如果有人执意要署上笔者的名字,笔者也不反
对。另外,要是大家一不小心发了大财,一定要给笔者分红的话,笔者当然也会心存感激地接受下来(笑)。
而且这不只是买了本书的人才能享受的特权,从图书馆或朋友那儿借书
看的人,甚至在书店里站着只看不买的人,也都享有以上权利。当然,大家要是买了这本书,对笔者、对出版社都是一个帮助。(笑)
在引用本书程序时,只有一点需要注意,那就是大家开发的操作系统的
名字。因为它已经不是笔者所开发的操作系统了,所以请适当地改个名字,以免让人误解,仅此一点请务必留意。不管程序的内部是多么相
像,它都是大家自己负责发布的另外一个不同的操作系统。给它起个响
亮的名字吧。
以上声明仅适用于书中的程序,以及附带光盘中收录的用作操作系统教
材的程序。本书正文和附带光盘中的其他工具软件不在此列。复制或修
改都受到著作权法的保护。请在法律允许范围内使用这些内容。与光盘
中的工具软件相关的许可权会放在本书最后一章予以说明。8 各章内容摘要
估计看过目录大家就能大概了解各章内容了,但因为目录里项目太多,所以在这里概括总结一下。如果有人想要保留一份神秘感,想边看边
猜“后面的内容会是什么”,那么可以跳过本节不读(笑)。这一部分可
以说是全书的灯塔,当大家在阅读本书的过程中感觉有什么不放心的时
候,就回过头来重新看看本节内容吧。
第一周(第1天 ~ 第7天)
一开始首先要考虑怎么来写一个“只要一通电就能运行的程序”。这部分
用C语言写起来有些困难,所以主要还是用汇编语言来写。
这步完成之后,下一步就要写一个从磁盘读取操作系统的程序。这时即
便打开电脑电源,它也不会自动地将操作系统全部都读进来,它只能读
取磁盘上最开始的512字节的内容,所以我们要编写剩余部分的载入程
序。这个程序也要用汇编语言编写。
一旦完成了这一步,以后的程序就可以用C语言来编写了。我们就尽快
使用C语言来学习开发显示画面的程序。同时,我们也能慢慢熟悉C语
言语法。这个时候我们好像在做自己想做的事,但事实上我们还没有自
由操纵C语言。
接下来,为了实现“移动鼠标”这一雄心,我们要对CPU进行细致的设
定,并掌握中断处理程序的写法。从全书总体看来,这一部分是水平相
当高的部分,笔者也觉得放在这里有些不妥,但从本书条理上讲,这些
内容必须放在这里,所以只好请大家忍耐一下了。在这里,CPU的规格
以及电脑复杂的规格都会给我们带来各种各样的麻烦。而且开发语言既
有C语言,又有汇编语言,这又给我们造成了更大的混乱。这个时候我
们一点儿也不会觉得这是在做自己想做的事,怎么看都像是在“受人摆
布”。
渡过这个痛苦的时期,第一周就该结束了。
第二周(第8天 ~ 第14天)
一周的苦战还是很有意义的,回头一看,我们就会发现自己还是斩获颇丰的。这时我们已经基本掌握了C语言的语法,连汇编语言的水平也能
达到本书的要求了。
所以现在我们就可以着手开发像样的操作系统了。但是这一次我们又要
为算法头痛了。即使掌握了编程语言的语法,如果不懂得好的算法的
话,也还是不能开发出来自己想要的操作系统。所以这一周我们就边学
习算法边慢慢地开发操作系统。不过到了这一阶段,我们就能感觉到基
本上不会再受技术问题限制了。
第三周(第15天 ~ 第21天)
现在我们的技术已经相当厉害了,可以随心所欲地开发自己的操作系统
了。首先是要支持多任务,然后是开发命令行窗口,之后就可以着手开
发应用程序了。到本周结束时,就算还不够完备,我们也能拿出一个可
以称之为操作系统的软件了。
第四周(第22天 ~ 第28天)
在这个阶段,我们可以尽情地给操作系统增加各种各样的功能,同时还
可以开发出大量像模像样的应用程序来。这个阶段我们已经能做得很好
了,这可能也是我们最高兴的时期。这部分要讲解的内容很少,笔者也
不用再煞费苦心地去写那些文字说明了,可以把精力都集中在编程上
(笑)。对了,说起文字才想起来,正好在这个时期可以让我们的操作
系统显示文字了。
免费赠送两天(第29天 ~ 第30天)
剩下的两天用来润色加工。这两天我们来做一些之前没来得及做,但做
起来既简单又有趣的内容。
■■■■■
以上就是从第1天到第30天的内容摘要,越到后面介绍越短,这也说明
最开始的内容是最复杂的。那么,就让我们做好准备,开始第一天的学
习吧。啊,大家不用紧张,放松!放松!第1天 从计算机结构到汇编程序入门
先动手操作
究竟做了些什么
初次体验汇编程序
加工润色1 先动手操作
与其啰啰嗦嗦地写上一大堆,还不如实际动手开发来得轻松,我们这就
开始吧。而且我们一上来就完全抛开前面的说明,既不用C语言,也不
用汇编程序,而是采用一个迥然不同的工具来进行开发(笑)。
■■■■■
有一种工具软件名为“二进制编辑器”(Binary Editor)1,是一种能够直
接对二进制数进行编辑的软件。我们现在要用它来编辑出下图这样的文
件。
1 原文直译为“二进制编辑器”(Binary Editor),在中国“二进制编
辑器”、“十六进制编辑器”这两种说法都有,这里尊重原著保留
了“二进制编辑器”的说法。——译者注
也许有人会说“这样的工具我从来没有见过呀”,没关系,下面我们来详
细地介绍一下。
首先打开下面这个网页:
http:www.vcraft.jpsoftbz.html
2
2 如果此网页连接不上,也可用google等检索工具来搜索一下,从
别处下载Bz1621.lzh。
用BZ打开helloos.img时的画面点击“在此下载”(Download)的链接,下载文件Bz1621.lzh (在此非常
感谢c.mos公司无偿公开这么好的软件)。当你读到本书的时候,也许
会有新的版本发布,所以文件名可能会有所不同。接下来,安装下载下
来的文件,然后双击启动Bz.exe程序。如果不能正常启动的话,可以参
考上面网页的“★注意★”一项,按照上面的安装指导进行操作。
顺利启动的话屏幕上会出现如下画面。
BZ起动时的画面
好,让我们赶紧来输入吧,只要从键盘上直接输入EB4E904845……就
可以了,简单吧。其中字符之间的空格是这个软件在显示时为方便阅读
自动插入的,不用自己从键盘上输入。另外,右边的.N.HELLOIPL……
部分,也不用从键盘输入,这是软件自动显示的。可能版本或者显示模
式不一样的时候,右侧显示的内容会与下面的截图有所不同。不过不用
往心里去,这些内容完全是锦上添花的东西,即使不一样也没事。
输入到000037位置时的画面
从000090开始后面全都是00,一直输入到最后168000这个地址。如果一直按着键盘上的“0”不放手的话,画面上的0就会不停地增加,但因为个
数相当多,也还是挺花时间的。如果家里有只猫的话,倒是可以考虑请
它来帮忙按住这个键(日本的谚语:想让猫来搭把手,形容人手不足,连猫爪子都想借用一下),或者也可以干脆就用透明胶把这个键粘上。
168000
附近的
画面
因为一下子输入到最后实在是挺花时间的,大家也许想保存一下中间结
果,这时可以从菜单上选择“文件”(File)→“另存为”(Save As),画
面上就会弹出保存文件的对话框。我们可以随便取个名字进行保存,笔
者推荐使用“helloos.img”。当想要打开保存过的文件时,首先要启动
Bz.exe,从菜单上选择“文件”(File)→“打开”(Open),然后选择目
标文件,这样原来保存的内容就能显示出来了。可是这个时候不管我们
怎么努力按键盘,它都一点反应也没有。这是怎么回事?难道必须要一
次性输入到最后吗?这个大家不必担心,其实只要从菜单里选择“编
辑”(Edit)→“只读”(Read Only)就可以进入编辑状态啦。好了,我
们继续输入。
如果家里的猫自由散漫惯了,不肯帮忙,而大家又不想用透明胶粘键盘
这种土方法的话,不妨这样:用鼠标选择一部分0,然后从菜单选择“编辑”(Edit)→“复制”(Copy)。简简单单复制粘贴几次就可以大功告成
了,这工具还真方便呀。
哦,对了,差点忘记一件重要的事——在地址0001F0和001400附近还有
些地方不全是00,要像下图那样把它们也改过来,然后整体检查一下,确认没有输入错误。
0001F0附近
001400附近
下面,我们把输入的内容保存下来就完成了软盘映像文件的制作,这时
查看一下文件属性,应该能看到文件大小正好是1474560字节
(=1440×1024字节)。然后我们将这个文件写入软盘(具体后述),并
用它来启动电脑。如下所示,画面上会显示出“hello, world”这个字符
串。目前的程序虽然简单,但毕竟一打开电脑它就能够自动启动,还能
在屏幕上显示出一句话来,已经小小成功了哦。不过,我们现在还没有
结束这个程序的方法,所以想要结束的时候,只能把软盘取出来后切断
电脑电源,或者重新启动。至于最关键的往磁盘上写映像文件的方法,笔者已经预先准备好了一个
程序。在介绍它的使用方法之前,我们先把笔者准备的工具全都安装进
来吧,这样后面讲解起来比较省事。下面我们就来看怎么安装这些工
具。
■■■■■
打开附带光盘,里面有一个名为tolset
3的文件夹,把这个文件夹复制到
硬盘的任意一个位置上。现在里面的东西还不多,只有3MB左右,不过
以后我们自己开发的软件也都要放到这个文件夹里,所以往后它会越来
越大,因此硬盘上最好留出100MB左右的剩余空间。工具安装到此结
束,我们既不用修改注册表,也不用设定路径参数,就这么简单。而且
以后不管什么时候,都可以把这整个文件夹移动到任何其他地方。用这
些工具,我们不仅可以开发操作系统,还可以开发简单的Windows应用
程序或OSASK应用程序等。
3 tool set的缩写,“工具套件”的意思。
接下来我们打开刚才安装的tolset文件夹,在文件夹的名字上单击鼠标右
键,从弹出的菜单上选择“新建”(New)→“文件夹”(Folder)。画面
上会显示出缺省的文件夹名“新建文件夹”(New Folder),我们要把它
改为“helloos0”,并把前面保存的映像文件helloos.img复制到这个文件夹
里。另外,刚才安装的tolset文件夹下有个名为z_new_w的子文件夹,其
中有!cons_9x.bat和!cons_nt.bat这两个文件,要把它们也复制粘贴到
helloos0文件夹里。接着,在文件夹helloos0里单击鼠标右键,从弹出的菜单中选择“新
建”(New)→“文本文件”(Text Document),并将文件命名
为“run.bat”,回车后屏幕上会显示“如果改变文件扩展名,可能会导致文
件不可用。确实要更改吗?”的对话框,我们选择“是”,创建run.bat文
件。然后在run.bat文件名上单击鼠标右键,在弹出的菜单上选择“编
辑”(Edit),输入下面内容并保存。
run.bat
copy helloos.img ..\z_tools\qemu\fdimage0.bin..\z_tools\make.exe -C ..z_toolsqemu
然后按照同样的步骤,创建install.bat,并将下列内容输入进去。
install.bat.. \z_tools\imgtol.com w a: helloos.img
其实以上步骤创建的所有文件都已经给事先给大家准备好了,就放在附
带光盘中名为projects\01_day\helloos0的子文件夹里。所以大家只要把光
盘上的helloos0复制下来,粘帖到硬盘的tolset文件夹里,所有的准备工
作就瞬间完成了。
■■■■■
好了,现在我们就来把这个有点像操作系统的软件安装到软盘上吧。随
便从附近的小店里买片新软盘来,在Windows下格式化一下(格式化方
法:把软盘插入磁盘驱动器后打开“我的电脑”,在“3.5吋软
盘”(3.5inches Floppy)A:上单击鼠标右键,再选择“格式化”(Format)
即可)。对了,这个时候不要选择“快速格式化”选项。然后用鼠标左键
双击helloos0文件夹里的 !cons_nt.bat文件(Windows9598Me的用户需
要双击!cons_9x.bat),屏幕上就会出现一个命令行窗口(console)。我
们先仔细确认一下软盘是否已经插好,然后在命令行窗口上输
入“install”并回车,这样安装操作就开始了。稍候片刻,等安装程序执
行完毕,我们的操作系统启动盘也就做好了。完成安装之后,也可以关
闭刚才的命令行窗口了。现在我们就用这张操作系统启动软盘来启动一下电脑试试吧,肯定跟刚
才一样,会显示出“hello, world”的字样来。
在这里要提醒大家几点:一是软盘虽然不要求必须用全新的,但如果太
旧的话,在读写过程中容易出问题,所以最好还是不要用太旧的软盘。
另外,就算是新盘,如果太便宜的话有时也用不了,若是发现有问题,就需要再去买一张。最后一点,一旦格式化或者往软盘内安装操作系
统,就会把里面原有的东西全部覆盖掉,所以大家千万不要用存有重要
文件的软盘来尝试哦。
■■■■■
看到这里,大家可能会有各种问题:“这些我都明白,可是既要专门去
买张软盘,又要重启电脑,实在太麻烦了,难道就没有什么更简单的方
法吗?”、“我家的电脑根本就没有软驱呀”、“我的电脑没有什么重启按
钮,也没有关电源的开关,一旦启动了这个奇怪的操作系统,就没法终
止啦”。其实这些问题笔者已经考虑到了,所以特意准备了一个模拟
器。我们有了这个模拟器,不用软盘,也不用终止Windows,就可以确
认所开发的操作系统启动以后的动作,很方便呢。
使用模拟器的方法也非常简单,我们只需要在用!cons_nt.bat(或者
是!cons_9x.bat)打开的命令行窗口中输入“run”指令就可以了。然后一
个名叫QEMU的非常优秀的免费PC模拟器就会自动运行。QEMU不是笔
者开发的,它是由国外的一些天才们开发出来的。感谢他们!
“我按照你说的一步一步地做了一遍,可是不行呀!怎么回事呢?”会遇
到这种情况的人肯定是个非常认真的人,可能真的完全按照上面步骤用
二进制编辑器自己做了一个helloos.img文件出来。出现这种问题,肯定
是因为文件中有输入错误的地方,虽然笔者不知道具体错在哪儿,不过
建议最好检查一下000000到000090,以及0001F0前后的数据。如果还是
不行的话,那就干脆用附带光盘中笔者做的helloos.img好了。
可能有些人嫌麻烦,懒得自己输入,上来就直接使用光盘里的
helloos.img文件,这当然也没什么不可以;但笔者认为这种体验(一点
一点地输入,再千辛万苦地纠错,最终功夫不负有心人取得成功)本身
更难能可贵,建议大家最好还是亲自尝试一下。
■■■■■就这样,我们没有去改造现成的操作系统,而是从零开始开发了一个,并让它运转了起来(当然,如果别人承认这是个操作系统的话)。这太
了不起了!大家完全可以在朋友们面前炫耀一番了。仅学习了几个小时
开发的一个初学者,就能从零开始做出一个操作系统,这本书不错吧
(笑)?这次我们考虑到从键盘直接输入比较麻烦,所以就只让它显示
了一条消息;如果能再多输入一些内容的话,那仅用这种方法就可以开
发任意一个操作系统(当然最大只能到1440KB)。现在唯一的问题
是,我们还不知道之前输入的那些“EB 4E 90 48 45……”到底是什么意
思(而这也正是我们所面临的最大问题)。今天剩下的时间,以及以后
的29天时间里,我们都会讲解这个问题。2 究竟做了些什么
为什么用这种方法就能开发出操作系统来呢?现在搞清楚这个问题,会
对我们今后的理解很有帮助,所以在这里要稍做说明。
首先我们要了解电脑的结构。电脑的处理中心是CPU,即“central
process unit”的缩写,翻译成中文就是“中央处理单元”,顾名思义,它就
是处理中心。如果我们把别的元件当作中心来使用的话,那它就叫做
CPU了,所以无论什么时候CPU都总是处理中心。不过这个CPU除了与
别的电路进行电信号交换以外什么都不会,而且对于电信号,它也只能
理解开(ON)和关(OFF)这两种状态,真是个没用的人呀(虽然它
不是人吧,大家领会精神)。
CPU
我们平时会用电脑写文章、听音乐、修照片以及做其他各种各样的事
情,我们用电脑所做的这些,其实本质上都不过是在与CPU交换电信号
而已,而且电信号只有开(ON)和关(OFF)这两种状态。再说直白
一点,CPU根本无法理解文章的内容,更不会鉴赏音乐、照片,它只会
机械地进行电信号的转换。CPU有计算指令,所以它能够进行整数的加
减乘除运算,也可以处理负数、计算小数以及10的100次方这样庞大的
数值,它甚至能够处理我们初中才学到的平方根和高中才学到的对数、三角函数,而且所有这些计算仅通过一条指令就能简单实现。虽然CPU
功能如此强大,但它其实根本不理解数的概念。CPU就是个集成电路
板,它只是忠实地执行电信号给它的指令,输出相应的电信号。
这些概念可能不太容易理解,还是让我们来看个的具体例子吧。比如
说,让我们用1来表示开(ON),用0来表示关(OFF),这样比较容
易理解。我们可以用32×16=512个开(ON)和关(OFF)的集合(=电
信号的集合),来显示出下面这个不甚好看的人头像。我们也可以用0000 0000 0000 0000 0000 0100 1010 0010 这32个电信号的
集合来表示1186这个整数。(注:用二进制表示1186的话,就是100
1010 0010)。我们还可以用0100 1011 0100 1111 0100 1111 0100 0010这
32个电信号的集合来表示“BOOK”这个单词(注:这实际上就是电脑内
部保存这个单词时的电信号集合)。
CPU能看见的就只有这些开(ON)和关(OFF)的电信号。换句话
说,假如我们给CPU发送这么一串电信号:
0000 0100 0011 1000 0000 1110 0001 0000
这信号可能是一幅画的部分数据,可能是个二进制整数,可能是一段音
乐旋律,可能是文章中的一段文字,也可能是保存了的游戏的一部分数
据,或者是程序中的一行代码,不管它是什么,CPU都一窍不通。CPU
不懂这些,也不在乎这些,它只是默默地、任劳任怨地按照程序的指令
进行相应的处理。
■■■■■
看到这里,或许有人会认为是先有了这么多要做的事情,所以人类才发
明了CPU,而实际上并不是这样。最早人们发明CPU只是为了处理电信
号,那个时候没有人能想到它后来会成为这么有用的机器。不过后来人
们发现,一旦把电信号的开(ON)关(OFF)与数字0和1对应起来,就能将二进制数转换为电信号,同时电信号也可以转换回二进制数。所
以,虽然CPU依然只能处理电信号,但它从此摇身一变,成了神奇的二
进制数计算机。
因为我们可以把十进制数转换成二进制数,也能把二进制数还原成十进
制数,所以人们又发明了普通的计算机。后来,我们发现只要给每个文
字都编上号(即文字编码),就可以建立一个文字与数字的对应关系,从而就可以把文字也转换成电信号,让CPU来处理文章(比如进行文字
输入或者字词检索等)。依此类推,人们接着又找到了将图像、音乐等
等转换成电信号的方法,使CPU的应用范围越来越广。不过CPU还是一
如既往,只能处理电信号。
而且我们能用CPU来处理的并不仅仅只有数据,我们还可以用电信号向
CPU发出指令。其实我们所编写的程序最终都要转换成所谓的机器语
言,这些机器语言就是以电信号的形式发送给CPU的。这些机器语言不
过就是一连串的指令代码,实际上也就是一串0和1的组合而已。
软盘的原理也有异曲同工之妙,简单说来,就是把二进制的0和1转换为
磁极的N极和S极而已,所以我们只用0和1就可以写出映像文件来。不
仅是映像文件,计算机所能处理的各种文件最终都是用0和1写成的。因
此可以说,不能仅用0和1来表达的内容,都不能以电信号的形式传递给
CPU,所以这种内容是计算机所无法处理的。
■■■■■
而“二进制编辑器”就是用来编辑二进制数的,我们可以很方便地用它来
输入二进制数,并保存成文件。所以它就是我们的秘密武器,也就是说
只要有了二进制编辑器,随便什么文件我们都能做出来。(厉害吧!)
如果大家在商店里看到一个软件,很想要而又不想花那么多钱的话,那
就干脆就回家用二进制编辑器自己做一个算啦!用这个方法我们完全可
以自己制作出一个与店里商品一模一样的东西来。看上一个500万像素
的数码相机,但是太贵了买不起?那有什么关系?我们只要有二进制编
辑器在手,就可以制作出毫不逊色于相机拍摄效果的图像,而且想做几
张就可以做几张。要是C编译器太贵了买不起,也不用郁闷。即使没有
C编译器,我们也可以用二进制编辑器做出一个与编译器生成文件完全
一样的执行文件,而且就连C编译器本身都可以用二进制编辑器做出
来。
有了这么强大的工具,制作操作系统就是小菜一碟。道理就是这么简
单,所以我们这次不费吹灰之力就做了个操作系统出来也是理所当然
的。或许有人会想“就为了讲这么个小事,有必要长篇大论写这么多
吗?”其实不然,如果我们对CPU的基础有了彻底的理解,以后的内容
就好懂多了。
■■■■■“喂,且慢,我明白了二进制编辑器就是编辑二进制数的软件,可是在
你让我输入你的helloos.img的时候,除了0和1以外,不是还让我输入了
很多别的东西吗?你看,第一个不就是E吗?这哪里是什么二进制数?
分明是个英文字母嘛!”……噢,不好意思,这说得一点错都没有。
虽然二进制数与电信号有很好的一一对应关系,但它有一个缺点,那就
是位数实在太多了,举个例子来说,如果我们把1234写成二进制数,就
成了10011010010,居然长达11位。而写成十进制数,只用4位就够了。
因为这样也太浪费纸张了,所以计算机业界普遍使用十六进制数。十进
制数的1234写成十六进制数,就是4D2,只用3位就够了。
那为什么非要用十六进制数呢,用十进制数不是也挺好的吗?实际上,我们可以非常简便地把二进制数写成十六进制数。
二进制数和十六进制数对照表 0000 – 0 0100 – 4 1000 – 8 1100 – C
0001 – 1 0101 – 5 1001 – 9 1101 – D
0010 – 2 0110 – 6 1010 – A 1110 – E
0011 – 3 0111 – 7 1011 – B 1111 – F
有了这个对照表,我们就能轻松进行二进制与十六进制之间的转换了。
将二进制转换为十六进制时,只要从二进制数的最后一位开始,4位4位
地替换过来就行了。如:
100 1101 0010 → 4D2
反之,把十六进制数的4D2转换为二进制数的100 1101 0010也很简单,只要用上面的对照表反过来变换一下就行了。而十进制数变换起来就没
这么简单了。同理,八进制数是把3位一组的二进制数作为一个八进制
位来变换的,这种计数法在计算机业界也偶有使用。
因此我们在输入EB的时候,实际上是在输入11101011,所以它其实是
个十六进制编辑器,但笔者习惯称它为二进制编辑器,希望大家不要见
怪。
■■■■■
虽然笔者对二进制编辑器如此地赞不绝口,但用它也解决不了什么实际
问题。因为这就相当于“只要有了笔和纸,什么优秀的小说都能写出
来”一样。笔和纸不过就是笔和纸而已,实际上对创作优秀的小说也帮
不上多大的忙。所以大家在写程序时,用的都是文本编辑器和编译器,没有谁只用二进制编辑器来做程序的。大家照相用的也都是数码照相
机,没有谁只用二进制编辑器来做图像文件。因此,我们用二进制编辑
器进行的开发就到此为止,接下来我们要调转方向,开始用编程语言来
继续我们的开发工作。不过有了这次的经验,我们就知道了如果今后遇
到什么特殊情况还可以使用二进制编辑器,它是非常有用的。而且后面
章节中我们偶尔也会用到它。3 初次体验汇编程序
好,现在就让我们马上来写一个汇编程序,用它来生成一个跟刚才完全
一样的helloos.img吧。我们这次使用的汇编语言编译器是笔者自己开发
的,名为“nask”,其中的很多语法都模仿了自由软件里享有盛名的汇编
器“NASM”,不过在“NASM”的基础之上又提高了自动优化能力。
超长的源代码
DB 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
DB 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
DB 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
(为节省纸张,这里省略中间的18万4314行)
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
我们使用复制粘帖的方法,就可以写出这样一个超长的源代码来,将其
命名为“helloos.nas”,并保存在helloos0中。仔细看一下就能发现这个文
件内容与我们用二进制编辑器输入的内容是一模一样的。
接着,我们用“!cons_nt.bat”或是“!cons_9x.bat”(我们在前面已经说过,要根据Windows的版本决定用哪一个。以后每次都这样解释一遍的话比
较麻烦,所以我们就将它简写为!cons好了) 打开一个命令行窗口
(console),输入以下指令(提示符部分不用输入):
提示符1
>..\z_tools\nask.exe helloos.nas helloos.img
1 prompt,出现在命令行窗口中,提示用户进行输入的信息。
这样我们就得到了映像文件helloos.img。
好,我们的第一个汇编语言程序就这样做成了!……不过这么写程序也
太麻烦了,要做个18万行的程序,不但浪费时间,还浪费硬盘空间。与
其这样还不如用二进制编辑器呢,不用输入“0x”、“,”什么的,还能轻松
一点。
■■■■■其实要解决这个问题并不难,如果我们不只使用DB指令,而把RESB指
令也用上的话,就可以一下将helloos.nas缩短了,而且还能保证输出的
内容不变,具体我们来看下面。
正常长度的源程序
DB 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
DB 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
DB 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
DB 0xff, 0xff, 0xff, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x2d, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41
DB 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00
RESB 16
DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
DB 0xee, 0xf4, 0xeb, 0xfd, 0x0a, 0x0a, 0x68, 0x65
DB 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72
DB 0x6c, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 368
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
我们自己动手输入这段源程序比较麻烦,所以笔者把它放在附带光盘的
projects\01_day\ helloos1目录下了。大家只要把helloos1文件夹复制粘
帖到tolset文件夹里就可以了。之前的helloos0文件夹以后就不用了,我
们可以把它删除,也可以放在那里留作纪念。顺便说一下,笔者将
helloos0文件夹名改为了helloos1,删掉了其中没用的文件,新建并编辑
了需要用到的文件,这样就做出了新的helloos1文件夹。操作系统就是
这样一点一点地成长起来的。
每次进行汇编编译的时候,我们都要输入刚才的指令,这太麻烦了,所
以笔者就做了一个批处理文件2asm.bat。有了这个批处理文件,我们只
要在用“!cons”打开的命令行窗口里输入“asm”,就可以生成helloos.img
文件。在用“asm”作成img文件后,再执行“run”指令,就可以得到与刚
才一样的结果。
2 batch file,基本上只是将命令行窗口里输入的命令写入文本文
件。虽然还有功能更强的处理,但本书中我们用不到。所谓批处理就是批量处理,即一次处理一连串的命令。
■■■■■
DB指令是“define byte”的缩写,也就是往文件里直接写入1个字节的指
令。笔者喜欢用大写字母来写汇编指令,但小写的“db”也是一样的。
在汇编语言的世界里,这个指令是程序员的杀手锏,也就是说只要有了
DB指令,我们就可以用它做出任何数据(甚至是程序)。所以可以
说,没有用汇编语言做不出来的文件。文本文件也好,图像文件也好,只要能叫上名的文件,我们都能用汇编语言写出来。而其他的语言(比
如C语言)就没有这么万能。
RESB指令是“reserve byte”的略写,如果想要从现在的地址开始空出10
个字节来,就可以写成RESB 10,意思是我们预约了这10个字节(大家
可以想象成在对号入座的火车里,预订了10个连号座位的情形)。而且
nask不仅仅是把指定的地址空出来,它还会在空出来的地址上自动填入
0x00,所以我们这次用这个指令就可以输出很多的0x00,省得我们自己
去写18万行程序了,真是帮了个大忙。
这里还要说一下,数字的前面加上0x,就成了十六进制数,不加0x,就
是十进制数。这一点跟C语言是一样的。4 加工润色
刚才我们把程序变成了短短的22行,这成果令人欣喜。不过还有一点不
足就是很难看出这些程序是干什么的,所以我们下面就来稍微改写一
下,让别人也能看懂。改写后的源文件增加到了48行,它位于附带光盘
的projects\01_day\helloos2目录下,大家可以直接把helloos2文件夹复制
到tolset里。现在helloos1也可以删掉了(每个文件夹都是独立的,用完
之后就可以删除,以后不再赘述。当然放在那里留作纪念也是可以
的)。
现在的程序有50行,也占不了多少地方,所以我们将它写在下面了。
有模有样的源代码; hello-os; TAB=4; 以下这段是标准FAT12格式软盘专用的代码
DB 0xeb, 0x4e, 0x90
DB HELLOIPL ; 启动区的名称可以是任意的字符串(8字节)
DW 512 ; 每个扇区(sector)的大小(必须为512字节)
DB 1 ; 簇(cluster)的大小(必须为1个扇区)
DW 1 ; FAT的起始位置(一般从第一个扇区开始)
DB 2 ; FAT的个数(必须为2)
DW 224 ; 根目录的大小(一般设成224项)
DW 2880 ; 该磁盘的大小(必须是2880扇区)
DB 0xf0 ; 磁盘的种类(必须是0xf0)
DW 9 ; FAT的长度(必须是9扇区)
DW 18 ; 1个磁道(track)有几个扇区(必须是18)
DW 2 ; 磁头数(必须是2)
DD 0 ; 不使用分区,必须是0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明,固定
DD 0xffffffff ;(可能是)卷标号码
DB HELLO-OS ; 磁盘的名称(11字节)
DB FAT12 ; 磁盘格式名称(8字节)
RESB 18 ; 先空出18字节; 程序主体
DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
DB 0xee, 0xf4, 0xeb, 0xfd; 信息显示部分
DB 0x0a, 0x0a ; 2个换行
DB hello, world
DB 0x0a ; 换行 DB 0
RESB 0x1fe- ; 填写0x00,直到 0x001fe
DB 0x55, 0xaa; 以下是启动区以外部分的输出
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
■■■■■
这里有几点新内容,我们逐一来看一下。首先是“;”命令,这是个注释命
令,相当于C语言或是C++中的“”。正是因为有它,我们才可以在源代
码里加入很多注释。
其次是DB指令的新用法。我们居然可以直接用它写字符串。在写字符
串的时候,汇编语言会自动地查找字符串中每一个字符所对应的编码,然后把它们一个字节一个字节地排列起来。这个功能非常方便,也就是
说,当我们想要变更输出信息的时候,就再也不用自己去查字符编码表
了。
再有就是DW指令和DD指令,它们分别是“define word”和“ddefine
double-word”的缩写,是DB指令的“堂兄弟”。word的本意是“单词”,但
在计算机汇编语言的世界里,word指的是“16位”的意思,也就是2个字
节。“double-word”是“32位”的意思,也就是4个字节。
对了,差点忘记说RESB 0x1fe-了。这个美元符号的意思如果不讲,恐
怕谁也搞不明白,它是一个变量,可以告诉我们这一行现在的字节数
(如果严格来说,有时候它还会有别的意思,关于这一点我们明天再
讲)。在这个程序里,我们已经在前面输出了132字节,所以这里的就
是132。因此nask先用0x1fe减去132,得出378这一结果,然后连续输出
378个字节的0x00。
那这里我们为什么不直接写378,而非要用呢?这是因为如果将显示信
息从“hello, world”变成“this is a pen.”的话,中间要输出0x00的字节数也
会随之变化。换句话说,我们必须保证软盘的第510字节(即第0x1fe字
节)开始的地方是55 AA。如果在程序里使用美元符号的话,汇编
语言会自动计算需要输出多少个00,我们也就可以很轻松地改写输出信息了。
■■■■■
既然可以毫不费力地改写显示的信息,就一定要好好发挥这一功能,让
我们的操作系统显示出自己喜欢的一句话,让它成为一个只属于我们自
己的、世界上独一无二的操作系统。不过遗憾的是现在它还不能显示汉
字。当然大家也可以尝试一下,但由于这个程序还没有显示汉字的功
能,所以显示出来的都是乱码,因此大家先将就一下,用英语或拼音
吧。
■■■■■
最后再给大家解释一下程序中出现的几个专门术语。时间不早了,我们
今天就到这吧。其他的留待明天再说。 TAB=4 ………… 有的文本编辑器可以调整TAB键的宽度。请使用这种编辑器的人将TAB键的宽度设定成4,这样源程序更容易读。可能有人说,我这里只能用记事本
(notepad),TAB键宽度固定为8,想调都没法调。没关系,明天笔者来推荐一个好用的文本编辑器。
FAT12格式 … (FAT12 Format)用Windows或MS-DOS格式化出来的软盘就是这种格式。我们的helloos也采用了这种格式,其中容纳了我们开发的操作系统。这个格式兼容
性好,在Windows上也能用,而且剩余的磁盘空间还可以用来保存自己喜欢的文件。
启动区 …………
(boot sector)软盘第一个的扇区称为启动区。那么什么是扇区呢?计算机读写软盘的时候,并不是一个字节一个字节地读写的,而是以512字节为一个单位
进行读写。因此,软盘的512字节就称为一个扇区。一张软盘的空间共有1440KB,也就是1474560字节,除以512得2880,这也就是说一张软盘共有2880个扇
区。那为什么第一个扇区称为启动区呢?那是因为计算机首先从最初一个扇区开始读软盘,然后去检查这个扇区最后2个字节的内容。
如果这最后2个字节不是0x55 AA,计算机会认为这张盘上没有所需的启动程序,就会报一个不能启动的错误。(也许有人会问为什么一定是0x55 AA呢?那
是当初的设计者随便定的,笔者也没法解释)。如果计算机确认了第一个扇区的最后两个字节正好是0x55 AA,那它就认为这个扇区的开头是启动程序,并开
始执行这个程序。
IPL ……………
initial program loader的缩写。启动程序加载器。启动区只有区区512字节,实际的操作系统不像hello-os这么小,根本装不进去。所以几乎所有的操作系统,都
是把加载操作系统本身的程序放在启动区里的。有鉴于此,有时也将启动区称为IPL。但hello-os没有加载程序的功能,所以HELLOIPL这个名字不太顺理成
章。如果有人正义感特别强,觉得“这是撒谎造假,万万不能容忍!”,那也可以改成其他的名字。但是必须起一个8字节的名字,如果名字长度不到8字节的
话,需要在最后补上空格。
启动 ……………
(boot)boot这个词本是长靴(boots)的单数形式。它与计算机的启动有什么关系呢?一般应该将启动称为start的。实际上,boot这个词是bootstrap的缩写,原指靴子上附带的便于拿取的靴带。但自从有了《吹牛大王历险记》(德国)这个故事以后,bootstrap这个词就有了“自力更生完成任务”这种意思(大家如果
对详情感兴趣,可以在Google上查找,也可以在帮助和支持网页http:hrb.osask.jp 上提问)。而且,磁盘上明明装有操作系统,还要说读入操作系统的程序
(即IPL)也放在磁盘里,这就像打开宝物箱的钥匙就在宝物箱里一样,是一种矛盾的说法。这种矛盾的操作系统自动启动机制,被称为bootstrap方式。boot这
个说法就来源于此。如果是笔者来命名的话,肯定不会用bootstrap 这么奇怪的名字,笔者大概会叫它“多级火箭式”吧。第2天 汇编语言学习与Makefile入门
介绍文本编辑器
继续开发
先制作启动区
Makefile入门1 介绍文本编辑器
笔者要向大家推荐一个文本编辑器TeraPad,可以从下面这个网站下
载,这是一款免费软件(在此感谢寺尾进先生的慷慨奉献!)。
http:www5f.biglobe.ne.jp~t-susumulibrarytpad.html
1
1 这个编辑器是日文版的,译者推荐一个可编辑中文的文本编辑器
Notepad++,可以从这个网站下载:http:notepad-plus-plus.org。
这也是个免费软件。下载以后解压缩,大家可以在解压后的文件夹
里找到“Notepad++”,然后双击鼠标左键就可以安装软件了。
大家下载的时候,可能版本会升级,所以文件名也许会略有不同。
它的使用方法与记事本(notepad)基本上是一样的。它有很多选
项,大家可以根据自己的喜好进行相应的设置。这里介绍几个非常
有用的设置。
设置中文模式方法:
从菜单选择“Encoding”→“Character
set”→“Chinese”→“GB2312(Simplified)”
大家可以按照如下步骤设置Tab键所对应的字符数。从菜单选
择“Settings”→“Preference”,会弹出一个对话框,选择“Language
MenuTab Settings”,就会显示出语言和TAB键的设置窗口。在TAB
键设置的下半部可以看到TAB键的宽度设置,默认值是4。如果要
用空格代替TAB,则勾选“Replace by space”前面的选择框就可以
了。其他还有显示文章行号,显示换行符、文件结束符等很多设
置。笔者没有设置显示这些符号,因为这样画面看起来比较整洁。
不过人各有所好,大家可以试一下各种设置,选择一组自己喜欢
的。设置完成后,请按“OK”按钮关闭对话框。——译者注
笔者虽然从昨天开始介绍了很多免费软件,但并没有强制大家使用的意
思,如果大家已经有了自己喜欢的二进制编辑器或者文本编辑器的话,那就还用它们吧。即便使用不同的软件,开发出来的程序也是一样的,所以笔者没有特意把这些免费软件放在光盘里。大家不用太在意笔者推荐的软件,尽管用自己喜欢的就是了。2 继续开发
昨天我们还没有详细地讲解helloos.nas中的注释部分,其中要掌握程序
核心之前的内容和启动区以外的内容,需要具备软盘方面的一些具体知
识,而这在以后我们还会讲到,所以这两部分暂时先保留。
这样一来,尚未讲解清楚的就只有程序核心部分了,那么我们下面就把
它改写成更简单易懂的形式吧。先把projects02_day中的helloos3复制到
tolset中,然后打开其中的helloos.nas文件。这个文件太长了,我们节选
一部分来讲解。
helloos.nas节选; hello-os; TAB=4
ORG 0x7c00 ; 指明程序的装载地址; 以下的记述用于标准FAT12格式的软盘
JMP entry
DB 0x90---(中略)---; 程序核心
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
fin:
HLT ; 让CPU停止,等待指令
JMP fin ; 无限循环
msg:
DB 0x0a, 0x0a ; 换行2次 DB hello, world
DB 0x0a ; 换行
DB 0
这段程序里有很多新指令,我们从上到下依次来看看。
■■■■■
首先是ORG指令。这个指令会告诉nask,在开始执行的时候,把这些机
器语言指令装载到内存中的哪个地址。如果没有它,有几个指令就不能
被正确地翻译和执行。另外,有了这条指令的话,美元符的含义
也随之变化,它不再是指输出文件的第几个字节,而是代表将要读入的
内存地址。
ORG指令来源于英文“origin”,意思是“源头、起点”。它会告诉nask,程
序要从指定的这个地址开始,也就是要把程序装载到内存中的指定地
址。这里指定的地址是0x7c00,至于指定它的原因我们会在后文(本节
末尾)详述。
下一个是JMP指令,它相当于C语言的goto语句,来源于英文的jump,意思是“跳转”。简单吧!
再下面是“entry:”,这是标签的声明,用于指定JMP指令的跳转目的地
等。这与C语言很像。entry这个词是“入口”的意思。
■■■■■
然后我们来看看MOV指令。MOV指令应该是最常用的指令了,即便在
这段程序里,MOV指令的使用次数也仅次于DB指令。这个指令的功能
非常简单,即赋值。虽然简单,但笔者认为,只要完全掌握了MOV指
令,也就理解了汇编语言的一大半。所以,我们在这里详细地讲解一下
这个指令。
“MOV AX,0”,相当于“AX=0;”这样一个赋值语句。同样,“MOV
SS,AX”就相当于“SS=AX;”。或许有人会问:“这个AX和SS是什么东
西?”这个问题我们待会儿再回答。
MOV命令源自英文“move”,意思是“移动”。“赋值”与“移动”虽然有些相
似,但毕竟还是不同的。一般说来,如果我们把一个东西移走了,它原来所占用的位置就会空出来。但是,在执行了“MOV SS,AX”语句之
后,AX并没有变“空”,还保留着原来的值不变。所以这实际上是“赋
值”,而不是“移动”。如果用“COPY”指令来打比方,理解起来就简单多
了。至于为什么成了MOV指令,笔者也搞不明白。
■■■■■
现在来说说AX和SS。CPU里有一种名为寄存器的存储电路,在机器语
言中就相当于变量的功能。具有代表性的寄存器有以下8个。各个寄存
器本来都是有名字的,但现在知道这些名字的机会已经不多了,所以在
这里顺便介绍一下。
AX——accumulator,累加寄存器
CX——counter,计数寄存器
DX——data,数据寄存器
BX——base,基址寄存器
SP——stack pointer,栈指针寄存器
BP——base pointer,基址指针寄存器
SI——source index,源变址寄存器
DI——destination index,目的变址寄存器
这些寄存器全都是16位寄存器,因此可以存储16位的二进制数。虽然它
们都有上面这种正式名称,但在平常使用的时候,人们往往用简单的英
文字母来代替,称它们为“AX寄存器”、“SI寄存器”等。
其实寄存器的全名还是很能说明它本来的意义的。比如在这8个寄存器
中,不管使用哪一个,差不多都能进行同样的计算,但如果都用AX来
进行各种运算的话,程序就可以写得很简洁。
“ADD CX,0x1234”编译成81 C1 34 12,是一个4字节的命令。
而 “ADD AX,0x1234”编译成05 34 12,是一个3字节的命令。从上面例子可以看出,这里所说的“程序可以写得简洁”是指“用机器语
言写程序”的情况,从汇编语言的源代码上是看不到这些区别的。如果
我们不懂机器语言,就会有很多地方难以理解。
再说说别的寄存器,CX是为方便计数而设计的,BX则适合作为计算内
存地址的基点。其他的寄存器也各有优点。
关于AX、CX、 DX、 BX 这几个寄存器名字的由来,虽然我们找不到
缩写为X的单词,但这个X表示扩展(extend)的意思。之所以说扩展是
因为在这之前CPU的寄存器都是8位的,而现在一下变成了16位,扩展
了一倍,所以发明者在原来寄存器的名字后面加了个X,意思是说“扩张
了一倍,了不起吧!”。大家可能注意到了这几个寄存器的排列顺序,它并不遵循名称的字母顺序。没错,其实这是按照机器语言中寄存器的
编号顺序排列的,可不是笔者随手瞎写的哦。
这8个寄存器全部合起来也才只有16个字节。换句话说,就算我们把这8
个寄存器都用上,CPU也只能存储区区16个字节。
另一方面,CPU中还有8个8位寄存器。
AL——累加寄存器低位(accumulator low)
CL——计数寄存器低位(counter low)
DL——数据寄存器低位(data low)
BL——基址寄存器低位(base low)
AH——累加寄存器高位(accumulator high)
CH——计数寄存器高位(counter high)
DH——数据寄存器高位(data high)
BH——基址寄存器高位(base high)名字看起来有点像,其实这是有原因的:AX寄存器共有16位,其中0位
到7位的低8位称为AL,而8位到15位的高8位称为AH。所以,如果以
为“再加上这8个8位寄存器,CPU就又可以多保存8个字节了”就大错特
错了,CPU还是那个CPU,依然只能存储区区16个字节。CPU的存储能
力实在是太有限了。
那BP、SP、SI、DI怎么没分为“L”和“H”呢?能这么想,就说明大家已
经做到举一反三了,但可惜的是这几个寄存器不能分为“L”和“H”。如果
无论如何都要分别取高位或低位数据的话,就必须先用“MOV,AX,SI”将SI的值赋到AX中去,然后再用AL、AH来取值。这貌似是英特尔
(Intel)的设计人员的思维模式。
“喂,我家的电脑是32位的,可不是16位。这样就能以32位为单位来处
理数据了吧?那32位的寄存器在哪儿呀?”大家可能会有这样的疑问,下面笔者就来回答这个问题。
EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI
这些就是32位寄存器。这次的程序虽然没有用到它们,但如果想用也是
完全可以使用的。在16位寄存器的名字前面加上一个E就是32位寄存器
的名字了。这个字母E其实还是来源于 “Extend”(扩展)这个词。在当时主流为16位的时代里,能扩展到32位算是个飞跃了。虽说EAX是个32
位寄存器,但其实跟前面一样,它有一部分是与AX共用的,32位中的
低16位就是AX,而高16位既没有名字,也没有寄存器编号。也就是
说,虽然我们可以把EAX作为2个16位寄存器来用,但只有低16位用起
来方便;如果我们要用高16位的话,就需要使用移位命令,把高16位移
到低16位后才能用。
这么说来,就是32位的CPU也只能存储区区32字节,存储能力还真是小
得可怜。
有的读者用的电脑可能是64位的,但我们这次不使用64位模式,所以这
里也就不再赘述了。
关于寄存器本来笔者就想介绍到这儿,但是突然想起来,还有一个段寄
存器(segment register),所以在这里一并给大家介绍一下吧。这些段
寄存器都是16位寄存器。
ES——附加段寄存器(extra segment)
CS——代码段寄存器(code segment)
SS——栈段寄存器(stack segment)
DS——数据段寄存器(data segment)
FS——没有名称(segment part 2)
GS——没有名称(segment part 3)
关于段寄存器的具体内容,我们保留到明天再详细讲解。现在,我们暂
时先在这些寄存器里放上0就可以了。
好,到这里寄存器已经讲得差不多了。
■■■■■
那么接下来我们继续看程序,下一个看不懂的语句应该是“MOV
SI,msg”吧。MOV是赋值,意思是SI=msg,而msg是下面将会出现的标
号。“把标号赋值给寄存器?这到底是怎么回事?”为了理解这个谜团,我们先回到JMP指令。
前面我们已经看到了“JMP entry”这个指令,其实把它写成“JMP
0x7c50”也完全没有问题。本来JMP指令的基本形式就是跳转到指定的
内存地址,因此这个指令就是让CPU去执行内存地址0x7c50的程序。
之所以可以用“JMP entry”来代替“JMP 0x7c50”,是因为entry就是
0x7c50。在汇编语言中,所有标号都仅仅是单纯的数字。每个标号对应
的数字,是由汇编语言编译器根据ORG指令计算出来的。编译器计算出
的“标号的地方对应的内存地址”就是那个标号的值。
所以,如果我们在这个程序中写了“MOV AX,entry”,那它就会把0x7c50
代入到AX寄存器里,我们代入到AX寄存器中的就是这个简单的数字。
大家可不要以为写在“entry”下面的程序也都被储存了,这是不可能的。
那么“MOV SI,msg”会怎么样呢?由于在这里msg的地址是0x7c74,所以
这个指令就是把0x7c74代入到SI寄存器中去。
■■■■■
下面我们来看“MOV AL,[SI]”。如果这个命令是“MOV AL,SI”的话,不
用多说大家也都能明白它的意思,可这里用方括号把SI括了起来。如果
在汇编语言中出现这个方括号,寄存器所代表的意思就完全不一样了。
这个记号代表“内存”。如果大家自己组装过电脑,就知道所谓“内存”,指的是256MB或512MB的那个零件。
内存
到现在为止,内存这个词我们已经使用了很多次了,可一直都还没有正
式讲解过,那内存到底是什么呢?简单地用一句话来概括,它就是一个超大规模的存储单元“住宅区”。用“住宅区”来比喻内存再合适不过了,它能充分体现出存储单元紧密、整齐地排列在一起的样子。英语中
memory是“记忆”的意思,这里我们把它译成“内存”。
通过对寄存器的讲解,现在大家都知道了CPU的存储能力很差,如果我
们想让CPU处理大量信息,就必须给它另外准备一套用于存储的电路。
因为即便是32位的CPU,把所有普通的寄存器都加在一起,最多也只能
存储32个字节的数据。就算把段寄存器也全部用上,也才只有44字节。
这么小的存储空间,就连启动电脑所必需的启动区数据都放不下。
现在大家已经知道了存储单元的必要性,那么我们下面就讲内存。内存
并不在CPU的内部,而是在CPU的外面。所以对于CPU来说,内存实际
上是外部存储器。这点很重要,就是说CPU要通过自己的一部分管脚
(引线)向内存发送电信号,告诉内存说:“喂,把5678号地址的数据
通过我的管脚传过来(严格说来,CPU和内存之间还有称为芯片
(chipset)的控制单元)!”CPU向内存读写数据时,就是这样进行信
息交换的。
CPU与内存之间的电信号交换,并不仅仅是为了存取数据。因为从根本
上讲,程序本身也是保存在内存里的。程序一般都大于44字节,不可能
保存在寄存器中,所以规定程序必须放在内存里。CPU在执行机器语言
时,必须从内存一个命令一个命令地读取程序,顺序执行。
内存虽然如此重要,但它的位置却离CPU相当远。就算是只有10厘米左
右的距离吧,可这与CPU中的半导体相比已经非常遥远了。所以,当
CPU向内存请求数据或者输出数据的时候,内存需要花很长时间才能够
完整无误地实现CPU的要求(CPU运行速度极快,所以即使在10厘米这
么短的距离内传送电信号,所花的时间都不容忽视)。所以,虽然内存
比寄存器的存储能力大很多个数量级,但使用内存时速度很慢。CPU访
问内存的速度比访问寄存器慢很多倍,记住这一点,我们才能开发出执
行速度快的程序来。■■■■■
基础知识我们讲完了,下面再回到汇编语言。MOV指令的数据传送源
和传送目的地不仅可以是寄存器或常数,也可以是内存地址。这个时
候,我们就使用方括号([ ])来表示内存地址。另外,BYTE、WORD、DWORD等英文词也都是汇编语言的保留字,下面举个例子
吧。
MOV BYTE [678],123
这个指令是要用内存的“678”号地址来保存“123”这个数值。虽然指令里
有数字,看起来像那么回事,但实际上内存和CPU一样,根本就没有什
么数值的概念。所谓的“678”,不过就是一大串开(ON)或者关
(OFF)的电信号而已。当内存收到这一串信号时,电路中的某8个存
储单元就会响应,这8个存储单元会记住代表“123”的开(ON)或关
(OFF)的电信号。为什么是8位呢?这是因为指令里指定了“BYTE”。
同样,我们还可以写成:
MOV WORD [678],123
在这种情况下,内存地址中的678号和旁边的679号都会做出反应,一共
是16位。这时,123被解释成一个16位的数值,也就是
0000000001111011,低位的01111011保存在678号,高位的00000000保
存在旁边的679号。像这样在汇编语言里指定内存地址时,要用下面这种方式来写:
数据大小 [地址]
这是一个固定的组合。如果我们指定“数据大小”为BYTE,那么使用的
存储单元就只是地址所指定的字节。如果我们指定“数据大小”为
WORD,则相邻的一个字节也会成为这个指令的操作对象。如果指定为
DWORD,则与WORD相邻的两个字节,也都成为这个指令的操作对象
(共4个字节)。这里所说的相邻,指的是地址增加方向的相邻。
至于内存地址的指定方法,我们不仅可以使用常数,还可以用寄存器。
比如“BYTE [SI]”、“WORD [BX]”等等。如果SI中保存的是987的
话,“BYTE [SI]”就会被解释成“BYTE [987]”,即指定地址为987的内
存。
虽然我们可以用寄存器来指定内存地址,但可作此用途的寄存器非常有
限,只有BX、BP、SI、DI这几个。剩下的AX、CX、DX、SP不能用来
指定内存地址,这是因为CPU没有处理这种指令的电路,或者说没有表
示这种处理的机器语言。没有对应的机器语言当然也就不能进行这样的处理了,如果有意见的话,就写邮件找英特尔的大叔们吧(笑)。笔者
没有勇气找英特尔的大叔们抱怨,所以想把DX内存里的内容赋值给AL
的时候,就会这样写:
MOV BX, DX
MOV AL, BYTE [BX]
■■■■■
根据以上说明我们知道可以用下面这个指令将SI地址的1字节内容读入
到AL。
MOV AL, BYTE [SI]
可是MOV指令有一个规则1,那就是源数据和目的数据必须位数相同。
也就是说,能向AL里代入的就只有BYTE,这样一来就可以省略
BYTE,即可以写成:
MOV AL, [SI]
1 如果违反这一规则,比如写“MOV AX,CL”的话,汇编语言就找不
到相对应的机器语言,编译时会出错。
哦,这样就与程序中的写法一样了。现在总算把这个指令解释清楚了,所以这个指令的意思就是“把SI地址的1个字节的内容读入AL中”。
■■■■■
ADD是加法指令。若以C语言的形式改写“ADD SI,1”的话,就是
SI=SI+1。“add”的英文原语意为“加”。
CMP是比较指令。或许有人想,比较指令是干什么的呢?简单说来,它
是if语句的一部分。譬如C语言会有这种语句:
if(a==3){ 处理; }
即对a和3进行比较,将其翻译成机器语言时,必须先写“CMP a,3”,告
诉CPU比较的对象,然后下一步再写“如果二者相等,需要做什么”。这里是“CMP AL,0”,意思就是将AL中的值与0进行比较。这个指令源自
英文中的compare,意为“比较”。
JE是条件跳转指令中之一。所谓条件跳转指令,就是根据比较的结果决
定跳转或不跳转。就JE指令而言,如果比较结果相等,则跳转到指定的
地址;而如果比较结果不等,则不跳转,继续执行下一条指令。因此,CMP AL, 0
JE fin
这两条指令,就相当于:
if (AL == 0) { goto fin; }
这条指令源自于英文“jump if equal”,意思是如果相等就跳转。顺便说
一句,fin是个标号,它表示“结束”(finish)的意思,笔者经常使用。
■■■■■
INT是软件中断指令。如果现在就讲中断机制的话,肯定会让人头昏脑
胀的,所以我们暂时先把它看作一个函数调用吧。这个指令源自英
文“interrupt”,是“中途打断”的意思。
电脑里有个名为BIOS的程序,出厂时就组装在电脑主板上的ROM2单元
里。电脑厂家在BIOS中预先写入了操作系统开发人员经常会用到的一
些程序,非常方便。BIOS是英文“basic input output system”的缩写,直
译过来就是“基本输入输出系统(程序)”。
2 只读存储器,不能写入,切断电源以后内容不会消失。ROM
是“read only memory”的缩写。
最近的BIOS功能非常多,甚至包括了电脑的设定画面,不过它的本质
正如其名,就是为操作系统开发人员准备的各种函数的集合。而INT就
是用来调用这些函数的指令。INT的后面是个数字,使用不同的数字可
以调用不同的函数。这次我们调用的是0x10(即16)号函数,它的功能
是控制显卡。
虽然制造厂家给我们准备好了BIOS,但其用法鲜为人知。不过这些很容易查到,笔者就做了一个关于BIOS的网页,下面给大家介绍一下。
http:community.osdev.info?(AT)BIOS
比如我们现在想要显示文字,先假设一次只显示一个字,那么具体怎么
做才能知道这个功能的使用方法呢?
首先,既然是要显示文字,就应该看跟显卡有关的函数。这么看来,INT 0x10好像有点关系,于是在上面网页上搜索,然后就能找到以下内
容(网页的原文为日语)。
显示一个字符
AH=0x0e;
AL=character code;
BH=0;
BL=color code;
返回值:无
注:beep、退格(back space)、CR、LF都会被当做控制字符处理
所以,如果大家按照这里所写的步骤,往寄存器里代入各种值,再调用
INT 0x10,就能顺利地在屏幕上显示一个字符出来3。
3 因为这里的BL中放入了彩色字符码,所以一旦这里变更,显示的
字符的颜色也应该变化。但笔者试了试,颜色并没有变。尚不清楚
为什么只能显示白色,只能推测现在这个画面模式下,不能简单地
指定字符颜色。
■■■■■
最后一个新出现的指令是HLT。这个指令很少用,会让它在第2天的内
容里就登台亮相的,估计全世界就只有笔者了。不过由于笔者对它的偏
好,就让笔者在这里多说两句吧(笑)。HLT是让CPU停止动作的指令,不过并不是彻底地停止(如果要彻底停
止CPU的动作,只能切断电源),而是让CPU进入待机状态。只要外部
发生变化,比如按下键盘,或是移动鼠标,CPU就会醒过来,继续执行
程序。说到这,请大家再仔细看看这个程序,我们会发现其实不管有没
有HLT指令,JMP fin都是无限循环,不写HLT指令也可以。所以很少有
人一开始就向初学者介绍HLT指令,因为这样只会让话变得很长。
然而笔者讨厌让CPU毫无意义地空转。如果没有HLT指令, CPU就会不
停地全力去执行JMP指令,这会使CPU的负荷达到100%,非常费电。这
多浪费呀。我们仅仅加上一个HLT指令,就能让CPU基本处于睡眠状
态,可以省很多电。什么都不干,还要耗费那么多电,这就是浪费。即
便是初学者,最好也要一开始就养成待机时使用HLT指令的习惯。或者
说,恰恰应该在初学阶段,就养成这样的好习惯。这样既节能环保,又
节约电费,或许还能延长电脑的使用寿命呢。
对了,HLT指令源自英文“halt”,意思是“停止”。
■■■■■
说了这么多,终于把这个程序从头到尾都讲完了。总结一下就是这样
的:
用C语言改写后的helloos.nas程序节选
entry:
AX = 0;
SS = AX;
SP = 0x7c00;
DS = AX;
ES = AX;
SI = msg;
putloop:
AL = BYTE [SI];
SI = SI + 1;
if (AL == 0) { goto fin; }
AH = 0x0e;
BX = 15;
INT 0x10;
goto putloop;
fin:
HLT;
goto fin;
就是有了这个程序,我们才能够把msg里写的数据,一个字符一个字符地显示出来,并且数据变成0以后,HLT指令就会让程序进入无限循
环。“hello, world”就是这样显示出来的。
■■■■■
对了,我们还没有说ORG的0x7c00是怎么回事呢。ORG指令本身刚才已
经讲过,就不再重复了,但这个0x7c00又是从哪儿冒出来的呢?换成
1234是不是就不行啊?嗯,还真是不行,我们要是把它换成1234的话,程序马上就不动了。
大家所用的电脑里配置的,大概都是64MB,甚至512MB这样非常大的
内存。那是不是这些内存我们想怎么用就能怎么用呢?也不是这样的。
比如说,内存的0号地址,也就是最开始的部分,是BIOS程序用来实现
各种不同功能的地方,如果我们随便使用的话,就会与BIOS发生冲
突,结果不只是BIOS会出错,而且我们的程序也肯定会问题百出。另
外,在内存的0xf0000号地址附近,还存放着BIOS程序本身,那里我们
也不能使用。
内存里还有其他不少地方也是不能用的,所以我们作为操作系统开发
者,不得不注意这一点。在我们作为一般用户使用Windows或Linux
时,不用想这些麻烦事,因为操作系统已经都处理好了,而现在,我们
成了操作系统开发者,就需要为用户来考虑这些问题了。只用语言文字
来讲解内存哪个部分不能用的话,不够清楚直观,所以还是要画张地
图。正好这里就有一张内存分布图,让我们一起来看看。
http:community.osdev.info?(AT)memorymap
虽然称之为地图,可实际上根本就不像地图,网页的作者也太会偷工减
料了吧。话说这个网页的作者,其实就是笔者本人,不好意思啦。大家
要是仔细看的话,会发现其中很多东西都是不知所云(都是笔者不好,真是对不起),不过在“软件用途分类”这里,有一句话可是非常重要
的,一定不能漏掉。
0x00007c00-0x00007dff :启动区内容的装载地址
程序中ORG指令的值就是这个数字。而且正是因为我们使用的是这个同
样的数字,所以程序才能正常运行。看到这,大家可能会问:“为什么是0x7c00呢? 0x7000不是更简单、好
记吗?”其实笔者也是这么想的,不过没办法,当初规定的就是
0x7c00。做出这个规定的应该是IBM的大叔们,不过估计他们现在都成
爷爷了。
一旦有了规定,人们就会以此为前提开发各种操作系统,因此以后就算
有人说“现在地址变成0x7000-0x71ff了,请大家跟着改一下”,也只是空
口号,不可能实现。因为硬要这么做的话,那现有的操作系统就必须全
部加以改造才能在这台新电脑上运行,这样的电脑兼容性不好,根本就
卖不出去。
今后也许大家还会提出很多疑问:“为什么是这样呢?”这些都是当年
IBM和英特尔的大叔们规定的。如果非要深究的话,我们倒是也能找到
一些当时时代背景下的原因,不过要把这些都说清楚的话,这本书恐怕
还要再加厚一倍,所以关于这些问题我们就不过多解释了。3 先制作启动区
考虑到以后的开发,我们不要一下子就用nask来做整个磁盘映像,而是
先只用它来制作512字节的启动区,剩下的部分我们用磁盘映像管理工
具来做,这样以后用起来就方便了。
如此一来,我们就有了projects02_day的helloos4这个文件夹。
首先我们把heloos.nas的后半部分截掉了,这是因为启动区只需要最初的
512字节。现在这个程序就仅仅是用来制作启动区的,所以我们把文件
名也改为ipl.nas。
然后我们来改造asm.bat,将输出的文件名改成ipl.bin。另外,也顺便输
出列表文件ipl.lst。这是一个文本文件,可以用来简单地确认每个指令
是怎样翻译成机器语言的。到目前为止我们都没有输出过这个文件,那
是因为1440KB的列表文件实在太大了,而这次只需要输出512字节,所
以没什么问题。
另外我们还增加了一个makeimg.bat。它是以ipl.bin为基础,制作磁盘映
像文件helloos.img的批处理文件。它利用笔者自己开发的磁盘映像管理
工具edimg.exe,先读入一个空白的磁盘映像文件,然后在开头写入
ipl.bin的内容,最后将结果输出为名为helloos.img的磁盘映像文件。详
情请参考makeimg.bat的内容。
这样,从编译到测试的步骤就变得非常简单了,我们只要双击!cons,然
后在命令行窗口中按顺序输入asm→makeimg→run这3个命令就完成了。4 Makefile入门
到helloos4为止,做出来的程序与笔者最初开发时所写的源程序是完全
一样的。在开发的过程中,笔者使用了一个名为Makefile的东西,在这
里给大家介绍一下。
Makefile就像是一个非常聪明的批处理文件。
■■■■■
Makefile的写法相当简单。首先生成一个不带扩展名的文件Makefile,然后再用文本编辑器写入以下内容。
文件生成规则
ipl.bin : ipl.nas Makefile..z_toolsnask.exe ipl.nas ipl.bin ipl.lst
helloos.img : ipl.bin Makefile..z_toolsedimg.exe imgin:..z_toolsfdimg0at.tek wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img
号表示注释。下一行“ipl.bin : ipl.nas Makefile”的意思是,如果想要制
作文件ipl.bin,就先检查一下ipl.nas和Makefile这两个文件是否都准备好
了。如果这两个文件都有了,Make工具就会自动执行Makefile的下一
行。
至于helloos.img,Makefile的写法也是完全一样的。其中的“\”是续行符
号,表示这一行太长写不下,跳转到下一行继续写。
我们需要调用make.exe来让这个Makefile发挥作用。为了能更方便地从
命令行窗口运行这个工具,我们来做个make.bat。make.bat就放在tolset
的z_new_w文件夹中,可以直接把它复制过来用。
■■■■■
做好以上这些准备后,用!cons打开一个命令行窗口(console),然后
输入“make -r ipl.bin”。这样make.exe就会启动了,它首先读取Makefile
文件,寻找制作ipl.bin的方法。因为ipl.bin的做法就写在Makefile里,make.exe找到了这一行就去执行其中的命令,顺利生成ipl.bin。然后我
们再输入“make -r helloos.img”看看,果然它还是会启动make.exe,并按
照Makefile指定的方法来执行。
到此为止好像也没什么特别的,我们再尝试一下把helloos.img和ipl.bin
都删除后,再输入“make -r helloos.img”命令。make 首先很听话地试图
生成helloos.img,但它会发现所需要的ipl.bin还不存在。于是就去
Makefile里寻找ipl.bin的生成方法,找到后先生成ipl.bin,在确认ipl.bin
顺利生成以后,就回来继续生成helloos.img。它很聪明吧。
下面,我们不删除文件,再输入命令“make -r helloos.img”执行一次的
话,就会发现,仅仅输出一行“‘helloos.img’已是最新版本
(‘helloos.img’is up to date)”的信息,什么命令都不执行。也就是说,make知道helloos.img已经存在,没必要特意重新再做一次了。它越来越
聪明了吧。
让我们再考验考验make.exe。我们来编辑ipl.nas中的输出信息,把它改
成“How are you?”并保存起来。而ipl.bin 和helloos.img保持刚才的样子不
删除,在这种情况下我们再来执行一次“make- r helloos.img”。本以为这
次它还会说没必要再生成一次呢,结果我们发现,make.exe又从ipl.bin
开始重新生成输出文件。这也就是说,make.exe不仅仅判断输入文件是
否存在,还会判断文件的更新日期,并据此来决定是否需要重新生成输
出文件,真是太厉害了。
■■■■■
现在大家知道了Makefile比批处理文件高明,但每次都输入“make -r
helloos.img”的话也很麻烦,其实有个可以省事的窍门。当然,可以
将“make -r helloos.img”这个命令写成makeimg.bat,但这么做还是离不开
批处理文件,所以我们换个别的方法,在Makefile里增加如下内容。
命令
img :..z_toolsmake.exe -r helloos.img
修改之后,我们只要输入“make img”,就能达到与“make -r
helloos.img”一样的效果。这样就省事多了。makeimg.bat已经没用了,把它删掉。另外顺便把下面内容也一并加进去吧。asm :..z_toolsmake.exe -r ipl.bin
run :..z_toolsmake.exe img
copy helloos.img ..\z_tools\qemu\fdimage0.bin..z_toolsmake.exe -C ..z_toolsqemu
install :..z_toolsmake.exe img..z_toolsimgtol.com w a: helloos.img
这样一来,“run.bat”、“install.bat”也都用不着了。不但用不着,现在还
更方便了呢。比如只要输入“make run”,它会首先执行“make img”,然
后再启动模拟器。
到目前为止,我们为了节约时间,避免每次都从汇编语言的编译开始重
新生成已有的输出文件,特意把批处理文件分成了几个小块。而现在有
了Makefile,它会自动跳过没有必要的命令,这样不管任何时候,我们
都可以放心地去执行“make img”了。而且就算直接“make run”也可以顺
利运行。“make install”也是一样,只要把磁盘装到驱动器里,这个命令
就会自动作出判断,如果已经有了最新的helloos.img就直接安装,没有
的话就先自动生成新的helloos.img,然后安装。
■■■■■
笔者把以上这些都总结在projects02_day下的helloos5文件夹里了,顺便
又另外添加了几个命令。一个命令是“make clean”,它可以删除掉最终
成果 (这里是helloos.img)以外的所有中间生成文件,把硬盘整理干
净;还有一个命令是“make src_only”,它可以把源程序以外的文件全都
删除干净。另外,笔者还增加了make命令的默认动作,当执行不带参数
的make时,就相当于执行“make img”命令(默认动作写在Makefile的最
前头)。
功能增加了这么多,而文件数量却减少到5个,看上去清爽多了吧。像
源文件这种真的必不可少的文件,多几个倒也没什么不好,但像批处理
文件这种可有可无的东西太多,堆在那里乱糟糟的就会让人很不舒服。
这样整理一下,我们以后的开发工作就会更加轻松愉快了。
■■■■■啊,有一点忘了告诉大家,这个make.exe是GNU项目组的人开发的,公
开供大家免费使用的一款软件。gcc的作者也是这个GNU项目组。真是
太感谢了!
按照现在的速度真的能在一个月后开发出一个操作系统吗?笔者也有点
担心。不过应该没问题,虽说现在的进展比当初的计划稍慢一些,不过
刚开始的时候说明肯定会多一些,等到后面用C语言来开发的时候,速
度就能上来了。嗯,就是这样……笔者满怀希望地自言自语中(苦
笑)。那么我们明天见!
COLUMN-1 数据也能“执行”吗?机器语言也能“显示”吗?
在helloos5中,如果我们把最开始的JMP entry 写成JMP msg,到底
会怎样呢? ……
首先,可不可以这么写呢?完全可以!nask不会报错,别的汇编语
言也不会报错。在汇编语言里,标号归根到底不过就是一个表示内
存地址的数字而已,至于JMP跳转的地方是机器语言还是字符编
码,汇编语言中不考虑这些问题。
那么如果执行这个程序,CPU会怎么样呢?首先最初的命令是0A
0A,意思是“OR CL,[BP+SI]”,也就是把CL寄存器的内容和BP+SI
内存地址的内容做逻辑或(OR)运算(过几天会出现这个命
令),结果放入CL寄存器。接着的命令是68 65 6C,也就是PUSH
0x6c65的意思(过几天这个命令也会出现),它将0x6c65储存进
栈。……就这样, CPU执行的命令很混乱,但CPU只能按照电信号
的指令来进行处理,所以即使不明其意,也会一板一眼地照单执
行。
结果,要么画面上出现怪异的字符,要么软盘或硬盘上的数据突然
被覆盖。虽然电脑并没有坏掉(因为CPU还在全速执行指令),但
看上去却像坏了一样。所以大家一定不要尝试这样做。
不过人无完人,搞不好通宵写了一夜程序,稀里糊涂之下就将本应
写entry的地方,错写成了msg,这也不是不可能发生。要是因为这
而丢失了重要文件,可就损失惨重了,所以CPU具有预防这种事故
的功能。但是这种功能只有在操作系统做了各种相应设置后才会起
作用(几天后也会讲到)。所以,在开发操作系统的阶段,我们还不能指望这种保护功能。从某种程度上来说,我们操作系统开发者
一直都是在提心吊胆地做开发。
那么反过来会怎样呢?也就是假设要把机器语言当作文字来显示,会出现什么结果呢?程序里有一句是“MOV SI,msg”,我们把它写
成“MOV SI,entry”看看。首先画面上会显示一个编码是B8的字符
(估计是个表情符号或者别的什么符号),下一个字符碰巧是00,所以显示就到此结束了。这种情况不会出现恶劣的后果,大家试一
试也无妨。
通过以上的尝试,最终证明,不管是CPU还是内存,它们根本就不
关心所处理的电信号到底代表什么意思。这么一来,说不定我们拿
数码相机拍一幅风景照,把它作为磁盘映像文件保存到磁盘里,就
能成为世界上最优秀的操作系统!这看似荒谬的情况也是有可能发
生的。但从常识来看,这样做成的东西肯定会故障百出。反之,我
们把做出的可执行文件作为一幅画来看,也没准能成为世界上最高
水准的艺术品。不过可以想象的是,要么文件格式有错,要么显示
出来的图是乱七八糟的。第3天 进入32位模式并导入C语言
制作真正的IPL
试错
读到18扇区
读入10个柱面
着手开发操作系统
从启动区执行操作系统
确认操作系统的执行情况
32位模式前期准备
开始导入C语言
实现HLT(harib00j) 1 制作真正的IPL
到昨天为止我们讲到的启动区,虽然也称为IPL(Initial Program
Loader,启动程序装载器),但它实质上并没有装载任何程序。而从今
天起,我们要真的用它来装载程序了。
把我们的操作系统叫作hello-os很不给劲,干脆改个名字吧。我们就叫
它“纸娃娃操作系统”。所谓纸娃娃,意思就是说那是用纸糊起来的,虚
有其表,不是真娃娃,就像拍电影时用的岩石等道具,其实都是中间空
空的冒牌货。也就是说我们现在要开发的操作系统,只是看上去像操作
系统,而其实是个没有内容的纸娃娃,所以大家不用想得太困难,轻轻
松松来做就好了。
虽然今后我们要一直称它为“纸娃娃操作系统”,而且在相当长的一段时
间里也只是把它当作一种演示程序,但到最后我们肯定能开发出一个像
模像样的操作系统,这一点请大家放心。
稍微扯远点,其实仔细想一想,这种虚有其表的“纸娃娃”又何止是
操作系统呢。就说CPU吧,其实它根本就不懂什么“数”的概念,只
是我们设计了一个电路,只要同时传给它电信号0011和0110,它就
能输出结果为1001的电信号,而这种电路我们就称之为加法电路。
只有人才会把这个结果解读为3+6=9,CPU只是处理这些电信号。
换句话说,虽然CPU根本就不懂什么数字,但却能给出正确的计算
结果,这就是笔者所谓的“纸娃娃”。
开发过游戏程序的人就会明白,比如我们和计算机下象棋的时候,可能会觉得计算机水平很高,但实际上计算机对象棋规则一窍不
通,仅仅是在执行一个程序而已。就算计算机走出了一着妙棋,也
根本不是因为它下手毫不留情啊,或者聪明啊,或者求胜心切什么
的,这些都是表象,其实它只是按部就班地执行程序而已。也就是
说它本身没有内涵,只有一个唬人的外壳,所以才叫“纸娃
娃”。“纸娃娃”太厉害了!……操作系统就算是虚有其表、虚张声
势又怎样?没什么不好的,这样就可以了!
■■■■■那么我们先从简单的程序开始吧。因为磁盘最初的512字节是启动区,所以要装载下一个512字节的内容。我们来修改一下程序。改好的程序
就是projects03_day下的harib00a1,像以前一样,我们把它复制到tolset
里来。
1 harib是日语中haribote(纸娃娃)的前面几个字母。——译者注
这次添加的内容大致如下。
本次添加的部分
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV AH,0x02 ; AH=0x02 : 读盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JC error
新出现的指令只有JC。真好,讲起来也轻松了。所谓JC,是“jump if
carry”的缩写,意思是如果进位标志(carry flag)是1的话,就跳转。这
里突然冒出来“进位标志”这么个新词,不过大家不用担心,很快就会明
白的。
■■■■■
至于“INT 0x13”这个指令,我们虽然知道这是要调用BIOS的0x13号函
数,但还不明白它到底是干什么用的,那就来查一下吧。当然还是来看
下面这个(AT)BIOS网页,http:community.osdev.info?(AT)BIOS
我们可以找到如下的内容:
磁盘读、写,扇区校验(verify),以及寻道(seek)AH=0x02;(读盘)
AH=0x03;(写盘)
AH=0x04;(校验)
AH=0x0c;(寻道)
AL=处理对象的扇区数;(只能同时处理连续的扇区)
CH=柱面号 0xff;
CL=扇区号(0-5位)|(柱面号0x300) 2;
DH=磁头号;
DL=驱动器号;
ES:BX=缓冲地址;(校验及寻道时不使用)
返回值:
FLACS.CF==0:没有错误,AH==0
FLAGS.CF==1:有错误,错误号码存入AH内(与重置
(reset)功能一样)
我们这次用的是AH=0x02,哦,原来是“读盘”的意思。
■■■■■
返回值那一栏里的FLAGS.CF又是什么意思呢?这就是我们刚才讲到的
进位标志。也就是说,调用这个函数之后,如果没有错,进位标志就是
0;如果有错,进位标志就是1。这样我们就能明白刚才为什么要用JC指
令了。
进位标志是一个只能存储1位信息的寄存器,除此之外,CPU还有其他
几个只有1位的寄存器。像这种1位寄存器我们称之为标志。标志在英文
中为flag,是旗帜的意思。标志之所以叫flag是因为它的开和关就像升旗降旗的状态一样。
所谓进位标志,本来是用来表示有没有进位(carry)的,但在CPU的标
志中,它是最简单易用的,所以在其他地方也经常用到。这次就是用来
报告BIOS函数调用是否有错的。
其他几个寄存器我们也来依次看一下吧。CH、CL、DH、DL分别是柱
面号、扇区号、磁头号、驱动器号,一定不要搞错。在上面的程序中,柱面号是0,磁头号是0,扇区号是2,磁盘号是0。
■■■■■
在有多个软盘驱动器的时候,用磁盘驱动器号来指定从哪个驱动器的软
盘上读取数据。现在的电脑,基本都只有1个软盘驱动器,而以前一般
都是2个。既然现在只有一个,那不用多想,指定0号就行了。
知道了从哪个软盘驱动器读取数据之后,我们接着看从那个软盘的什么
地方来读取数据。如果手头有不用的软盘,希望大家能把它拆开看看。拆开后可以看到,中间有一个8厘米的黑色圆盘,那是一层薄薄的磁性胶片。从外向内,一圈一圈圆环状的区域,分别称为柱面0,柱面1,……,柱面79。一共
有80个柱面。这并不是说工厂就是这样一圈一圈地生产软盘的,只是我
们将它作为一个数据存储媒体,是这样组织它的数据存储方式的。柱面
在英文中是cylinder,原意是圆筒。磁盘的柱面,尽管高度非常低,但
我们可以把它看成是一个套一个的同心圆筒,它正是因此得名的。下面讲一下磁头。磁头是个针状的磁性设备,既可以从软盘正面接触磁
盘,也可以从软盘背面接触磁盘。与光盘不同,软盘磁盘是两面都能记
录数据的。因此我们有正面和反面两个磁头,分别是磁头0号和磁头1
号。
最后我们看一下扇区。指定了柱面和磁头后,在磁盘的这个圆环上,还
能记录很多位信息,按照整个圆环为单位读写的话,实在有点多,所以
我们又把这个圆环均等地分成了几份。软盘分为18份,每一份称为一个
扇区。一个圆环有18个扇区,分别称为扇区1、扇区2、……扇区18。扇
区在英文中是sector,意思是指领域、扇形。
综上所述,1张软盘有80个柱面,2个磁头,18个扇区,且一个扇区有
512字节。所以,一张软盘的容量是:
80×2×18×512 = 1 474 560 Byte = 1 440KB
含有IPL的启动区,位于C0-H0-S1(柱面0,磁头0,扇区1的缩写),下
一个扇区是C0-H0-S2。这次我们想要装载的就是这个扇区。
■■■■■
剩下的大家还不明白的就是缓冲区地址了吧。这是个内存地址,表明我
们要把从软盘上读出的数据装载到内存的哪个位置。一般说来,如果能
用一个寄存器来表示内存地址的话,当然会很方便,但一个BX只能表
示0~0xffff的值,也就是只有0~65535,最大才64K。大家的电脑起码
也都有64M内存,或者更多,只用一个寄存器来表示内存地址的话,就
只能用64K的内存,这太可惜了。
于是为了解决这个问题,就增加了一个叫EBX的寄存器,这样就能处理
4G内存了。这是CPU能处理的最大内存量,没有任何问题。但EBX的导
入是很久以后的事情,在设计BIOS的时代,CPU甚至还没有32位寄存
器,所以当时只好设计了一个起辅助作用的段寄存器(segment
register)。在指定内存地址的时候,可以使用这个段寄存器。
我们使用段寄存器时,以ES:BX这种方式来表示地址,写成“MOV AL,[ES:BX]”,它代表ES×16+BX的内存地址。我们可以把它理解成先用ES
寄存器指定一个大致的地址,然后再用BX来指定其中一个具体地址。这样如果在ES里代入0xffff,在BX里也代入0xffff,就是1 114 095字
节,也就是说可以指定1M以内的内存地址了。虽然这也还是远远不到
64M,但当时英特尔公司的大叔们,好像觉得这就足够了。在最初设计
BIOS的时代,这种配置已经很能满足当时的需要了,所以我们现在也
还是要遵从这一规则。因此,大家就先忍耐一下这1MB内存的限制吧。
这次,我们指定了ES=0x0820,BX=0,所以软盘的数据将被装载到内存
中0x8200到0x83ff的地方。可能有人会想,怎么也不弄个整点的数,比
如0x8000什么的,那多好。但0x8000~0x81ff这512字节是留给启动区
的,要将启动区的内容读到那里,所以就这样吧。
那为什么使用0x8000以后的内存呢?这倒也没什么特别的理由,只是因
为从内存分布图上看,这一块领域没人使用,于是笔者就决定将我们
的“纸娃娃操作系统”装载到这一区域。 0x7c00~0x7dff用于启动区,0x7e00以后直到0x9fbff为止的区域都没有特别的用途,操作系统 可以随
便使用。
■■■■■
到目前为止我们开发的程序完全没有考虑段寄存器,但事实上,不管我
们要指定内存的什么地址,都必须同时指定段寄存器,这是规定。一般
如果省略的话就会把“DS:”作为默认的段寄存器。
以前我们用的“MOV CX,[1234]”,其实是“MOV CX,[DS:1234]”的意
思。“MOV AL,[SI]”,也就是“MOV AL,[DS:SI]”的意思。在汇编语言
中,如果每回都这样写就太麻烦了,所以可以省略默认的段寄存器
DS。
因为有这样的规则,所以DS 必须预先指定为0,否则地址的值就要加上
这个数的16倍,就会读写到其他的地方,引起混乱。非常成功
好,我们来执行这个程序看看吧。如果程序有什么错,它就会显示错误
信息。但估计不会出什么问题吧。没错的话,它就什么都不做(笑)。
所以,如果屏幕上不显示任何错误信息的话,我们就成功了。
哎呀,有件事差点忘了,Makefile中可以使用简单的变量,于是笔者用
变量改写了这次的Makefile。怎么样,是不是比之前稍微容易理解一
些?2 试错
软盘这东西很不可靠,有时会发生不能读数据的状况,这时候重新再读
一次就行了。所以即使出那么一、两次错,也不要轻易放弃,应该让它
再试几次。当然如果让它一直重试下去的话,要是磁盘真的坏了,程序
就会陷入死循环,所以我们决定重试5次,再不行的话就真正放弃。改
良后的程序就是projects03_day下的harib00b。
本次添加的部分;读磁盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 读入磁盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC fin ; 没出错的话跳转到fin
ADD SI,1 ; 往SI加1
CMP SI,5 ; 比较SI与5
JAE error ; SI >= 5时,跳转到error
MOV AH,0x00
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry
还是从新出现的指令开始讲吧。JNC是另一个条件跳转指令,是“Jump
if not carry”的缩写。也就是说进位标志是0的话就跳转。JAE也是条件跳
转,是“Jump if above or equal”的缩写,意思是大于或等于时跳转。
现在说说出错时的处理。重新读盘之前,我们做了以下的处理,AH=0x00,DL=0x00,INT 0x13。通过前面介绍的(AT)BIOS的网页
我们知道,这是“系统复位”。它的功能是复位软盘状态,再读一次。剩
下的内容都很简单,只要读一读程序就能懂。
嗯,今天进展不错,继续努力吧。3 读到18扇区
我们趁着现在这劲头,再往后多读几个扇区吧。下面来看看
projects03_day下的harib00c。
本次添加的部分;读磁盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV C ......
作者:川合秀实
译者:周自恒, 李黎明, 曾祥江, 张文旭
ISBN:978-7-115-28796-0
本书由北京图灵文化发展有限公司发行数字版。版权所有,侵权必
究。
您购买的图灵电子书仅供您个人使用,未经授权,不得以任何方式复制
和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐
号等维权措施,并可能追究法律责任。目录
版 权 声 明
译 者 序
前 言
第0天 着手开发之前
1 前言
2 何谓操作系统
3 开发操作系统的各种方法
4 无知则无畏
5 如何开发操作系统
6 操作系统开发中的困难
7 学习本书时的注意事项(重要!)
8 各章内容摘要
第1天 从计算机结构到汇编程序入门
1 先动手操作
2 究竟做了些什么
3 初次体验汇编程序
4 加工润色
第2天 汇编语言学习与Makefile入门
1 介绍文本编辑器
2 继续开发
3 先制作启动区
4 Makefile入门
第3天 进入32位模式并导入C语言
1 制作真正的IPL
2 试错
3 读到18扇区
4 读入10个柱面
5 着手开发操作系统
6 从启动区执行操作系统
7 确认操作系统的执行情况
8 32位模式前期准备
9 开始导入C语言
10 实现HLT(harib00j)第4天 C语言与画面显示的练习
1 用C语言实现内存写入(harib01a)
2 条纹图案(harib01b)
3 挑战指针(harib01c)
4 指针的应用(1)(harib01d)
5 指针的应用(2)(harib01e)
6 色号设定(harib01f)
7 绘制矩形(harib01g)
8 今天的成果(harib01h)
第5天 结构体、文字显示与GDTIDT初始化
1 接收启动信息(harib02a)
2 试用结构体(harib02b)
3 试用箭头记号(harib02c)
4 显示字符(harib02d)
5 增加字体(harib02e)
6 显示字符串(harib02f)
7 显示变量值(harib02g)
8 显示鼠标指针(harib02h)
9 GDT与IDT的初始化(harib02i)
第6天 分割编译与中断处理
1 分割源文件(harib03a)
2 整理Makefile(harib03b)
3 整理头文件(harib03c)
4 意犹未尽
5 初始化PIC(harib03d)
6 中断处理程序的制作(harib03e)
第7天 FIFO与鼠标控制
1 获取按键编码(hiarib04a)
2 加快中断处理(hiarib04b)
3 制作FIFO缓冲区(hiarib04c)
4 改善FIFO缓冲区(hiarib04d)
5 整理FIFO缓冲区(hiarib04e)
6 总算讲到鼠标了(harib04f)
7 从鼠标接受数据(harib04g)
第8天 鼠标控制与32位模式切换
1 鼠标解读(1)(harib05a)
2 稍事整理(harib05b)3 鼠标解读(2)(harib05c)
4 移动鼠标指针(harib05d)
5 通往32位模式之路
第9天 内存管理
1 整理源文件(harib06a)
2 内存容量检查(1)(harib06b)
3 内存容量检查(2)(harib06c)
4 挑战内存管理(harib06d)
第10天 叠加处理
1 内存管理(续)(harib07a)
2 叠加处理(harib07b)
3 提高叠加处理速度(1)(harib07c)
4 提高叠加处理速度(2)(harib07d)
第11天 制作窗口
1 鼠标显示问题(harib08a)
2 实现画面外的支持(harib08b)
3 shtctl的指定省略(harib08c)
4 显示窗口(harib08d)
5 小实验(harib08e)
6 高速计数器(harib08f)
7 消除闪烁(1)(harib08g)
8 消除闪烁(2)(harib08h)
第12天 定时器(1)
1 使用定时器(harib09a)
2 计量时间(harib09b)
3 超时功能(harib09c)
4 设定多个定时器(harib09d)
5 加快中断处理(1)(harib09e)
6 加快中断处理(2)(harib09f)
7 加快中断处理(3)(harib09g)
第13天 定时器(2)
1 简化字符串显示(harib10a)
2 重新调整FIFO缓冲区(1)(harib10b)
3 测试性能(harib10c~harib10f)
4 重新调整FIFO缓冲区(2)(harib10g)
5 加快中断处理(4)(harib10h)
6 使用“哨兵”简化程序(harib10i)第14天 高分辨率及键盘输入
1 继续测试性能(harib11a ~ harib11c)
2 提高分辨率(1)(harib11d)
3 提高分辨率(2)(harib11e)
4 键盘输入(1)(harib11f)
5 键盘输入(2)(harib11g)
6 追记内容(1)(harib11h)
7 追记内容(2)(harib11i)
第15天 多任务(1)
1 挑战任务切换(harib12a)
2 任务切换进阶(harib12b)
3 做个简单的多任务(1)(harib12c)
4 做个简单的多任务(2)(harib12d)
5 提高运行速度(harib12e)
6 测试运行速度(harib12f)
7 多任务进阶(harib12g)
第16天 多任务(2)
1 任务管理自动化(harib13a)
2 让任务休眠(harib13b)
3 增加窗口数量(harib13c)
4 设定任务优先级(1)(harib13d)
5 设定任务优先级(2)(harib13e)
第17天 命令行窗口
1 闲置任务(harib14a)
2 创建命令行窗口(harib14b)
3 切换输入窗口(harib14c)
4 实现字符输入(harib14d)
5 符号的输入(harib14e)
6 大写字母与小写字母(harib14f)
7 对各种锁定键的支持(harib14g)
第18天 dir命令
1 控制光标闪烁(1)(harib15a)
2 控制光标闪烁(2)(harib15b)
3 对回车键的支持(harib15c)
4 对窗口滚动的支持(harib15d)
5 mem命令(harib15e)
6 cls命令(harib15f)7 dir命令(harib15g)
第19天 应用程序
1 type命令(harib16a)
2 type命令改良(harib16b)
3 对FAT的支持(harib16c)
4 代码整理(harib16d)
5 第一个应用程序(harib16e)
第20天 API
1 程序整理(harib17a)
2 显示单个字符的API(1)(harib17b)
3 显示单个字符的API(2)(harib17c)
4 结束应用程序(harib17d)
5 不随操作系统版本而改变的API(harib17e)
6 为应用程序自由命名(harib17f)
7 当心寄存器(harib17g)
8 用API显示字符串(harib17h)
第21天 保护操作系统
1 攻克难题——字符串显示API(harib18a)
2 用C语言编写应用程序(harib18b)
3 保护操作系统(1)(harib18c)
4 保护操作系统(2)(harib18d)
5 对异常的支持(harib18e)
6 保护操作系统(3)(harib18f)
7 保护操作系统(4)(harib18g)
第22天 用C语言编写应用程序
1 保护操作系统(5)(harib19a)
2 帮助发现bug(harib19b)
3 强制结束应用程序(harib19c)
4 用C语言显示字符串(1)(harib19d)
5 用C语言显示字符串(2)(harib19e)
6 显示窗口(harib19f)
7 在窗口中描绘字符和方块(harib19g)
第23天 图形处理相关
1 编写malloc(harib20a)
2 画点(harib20b)
3 刷新窗口(harib20c)
4 画直线(harib20d)5 关闭窗口(harib20e)
6 键盘输入API(harib20f)
7 用键盘输入来消遣一下(harib20g)
8 强制结束并关闭窗口(harib20h)
第24天 窗口操作
1 窗口切换(1)(harib21a)
2 窗口切换(2)(harib21b)
3 移动窗口(harib21c)
4 用鼠标关闭窗口(harib21d)
5 将输入切换到应用程序窗口(harib21e)
6 用鼠标切换输入窗口(harib21f)
7 定时器API(harib21g)
8 取消定时器(harib21h)
第25天 增加命令行窗口
1 蜂鸣器发声(harib22a)
2 增加更多的颜色(1)(harib22b)
3 增加更多的颜色(2)(harib22c)
4 窗口初始位置(harib22d)
5 增加命令行窗口(1)(harib22e)
6 增加命令行窗口(2)(harib22f)
7 增加命令行窗口(3)(harib22g)
8 增加命令行窗口(4)(harib22h)
9 变得更像真正的操作系统(1)(harib22i)
10 变得更像真正的操作系统(2)(harib22j)
第26天 为窗口移动提速
1 提高窗口移动速度(1)(harib23a)
2 提高窗口移动速度(2)(harib23b)
3 提高窗口移动速度(3)(harib23c)
4 提高窗口移动速度(4)(harib23d)
5 启动时只打开一个命令行窗口(harib23e)
6 增加更多的命令行窗口(harib23f)
7 关闭命令行窗口(1)(harib23g)
8 关闭命令行窗口(2)(harib23h)
9 start命令(harib23i)
10 ncst命令(harib23j)
第27天 LDT与库
1 先来修复bug(harib24a)2 应用程序运行时关闭命令行窗口(harib24b)
3 保护应用程序(1)(harib24c)
4 保护应用程序(2)(harib24d)
5 优化应用程序的大小(harib24e)
6 库(harib24f)
7 整理make环境(harib24g)
第28天 文件操作与文字显示
1 alloca(1)(harib25a)
2 alloca(2)(harib25b)
3 文件操作API(harib25c)
4 命令行API(harib25d)
5 日文文字显示(1)(harib25e)1
6 日文文字显示(2)(harib25f)
7 日文文字显示(3)(harib25g)
第29天 压缩与简单的应用程序
1 修复bug(harib26a)
2 文件压缩(harib26b)
3 标准函数
4 非矩形窗口(harib26c)
5 bball(harib26d)
6 外星人游戏(harib26e)
第30天 高级的应用程序
1 命令行计算器(harib27a)
2 文本阅览器(harib27b)
3 MML播放器(harib27c)
4 图片阅览器(harib27d)
5 IPL的改良(harib27e)
6 光盘启动(harib27f)
第31天 写在开发完成之后
1 继续开发要靠大家的努力
2 关于操作系统的大小
3 操作系统开发的诀窍
4 分享给他人使用
5 关于光盘中的软件
6 关于开源的建议
7 后记
8 毕业典礼9 附录
版 权 声 明
30 Nichi De Dekiru OS Jisaku Nyuumon by 川合秀実
Copyright ? 2006 Hidemi Kawai
All rights reserved.
Original Japanese edition Published by Mynavi Corporation.
This Simplified Chinese edition is published by arrangement with Mynavi
Corporation.
本书中文简体字版由Mynavi Corporation授权人民邮电出版社独家出版。
未经出版者书面许可,不得以任何方式复制或抄袭本书内容。
版权所有,侵权必究。译 者 序
《30天自制操作系统》中文版终于和国内读者见面了。标题一出,有人
说“XX天”这种标题真不靠谱,不过,作者取这个标题,并非随随便便
之举。打个比方,“30天学会核物理”看起来“假大空”,如果改成“30天
自制微型反应堆”呢?虽然可能还是太难了,但至少你知道30天之后一
定能做出一个反应堆来(即便简陋)。这本书正是属于后者:不管多简
单,它都是一个真正意义上的操作系统,更何况它还真不简单,40KB
便实现了图形界面、多任务等高级功能。只要跟着作者的脚步,你也能
做到。即便只是抄抄代码,也必定有所收获。
这本书的定位是零基础的读者,作者甚至找了中学生来试读,语言通俗
易懂,轻松幽默。作为译者,我很喜欢这样的风格,因为可以把很多好
玩的流行词汇代入进去,不会破坏原书的意境,还能让大家看起来更有
意思。从技术角度来看,这本书并没有过多地解释技术细节。作者认
为,自制操作系统最终的目的还是为了好玩。因此,想从这本书系统学
习计算机原理、汇编语言、C语言等知识是不现实的,但你一定能够获
得另一种完全不同的体验。
这本书的一大特色是“从失败中学习”,每次我们为这个操作系统实现一
些功能,一开始总会有一些漏洞和缺陷,甚至根本不能工作。这些漏洞
都是刻意安排的。作者花了很大篇幅来引导读者去寻找并发现这些漏
洞,并从中学习如何让系统变得更加完善。这种思路非常有趣,也符合
实际开发过程,先苦后甜乃是成就感和幸福感的源泉。市面上的技术类
书籍,很少有这种“试错”的过程,因为这需要精心的安排,而且占用大
量的篇幅。这正是这本书的与众不同之处,也是我认为值得向大家推荐
它的主要理由。
如果你是一位高手,可能会觉得这本书的内容并不是那么系统和有条
理,甚至觉得做出来的操作系统在很多方面的处理都很简陋,算不上一
个实用的系统。连作者自己都说:“这本书无论在哪个方面都只有半瓶
醋。”不过,作者是在带领大家从零开始编写一个系统,而并不是以一
个现成的内核(如Linux、FreeBSD)为基础——后者才是目前自制系统
的主流方式。然而,只有从零开始,才能真正了解系统底层是如何运作
的,对于在其他内核上构筑系统也大有裨益。另外,千万别忘了读一读
最后那个叫做“这也能叫自制操作系统?太坑爹了!”的专栏,作者早就预料到了读者的各种吐槽,看过之后,你可能就会理解作者的良苦用心
了。
这本书讲到了“日文显示”,在翻译上相当纠结。由于操作系统都是底层
代码,牵一发而动全身,为了不改动原书的结构和代码,中文版在原汁
原味保留原书文字的基础上,补充了一些中文显示的相关内容,以体现
两者在实现上的异同。好在基本上只要替换字库和编码方式,就可以实
现中文显示,甚至比日文还简单些。这部分补充内容是我自己写的,但
我自知才疏学浅,不敢班门弄斧,如有错误或疏漏,欢迎各位高手随时
拍砖。此外,关于光盘中代码的注释,由于量大繁杂,恕无法翻译成中
文(书中代码注释已翻译),非常抱歉。如果发现注释为乱码,请用
UltraEdit等编辑器以Shift-JIS编码打开,就可以看到正常的日文了。
最后,在这里衷心感谢其他三位译者,以及图灵公司各位编辑的共同努
力,使得这本书能够最终问世,希望所有对编写操作系统有兴趣的读者
都能从中获益。
周自恒
2012年9月于上海前 言
“好想编写一个操作系统呀!”笔者的朋友曾说这是所有程序员都曾经怀
揣的一个梦想。说“所有的程序员”可能有点夸张了,不过作为程序员的
梦想,它至少也应该能排进前十名吧。
也许很多人觉得编写操作系统是个天方夜谭,这一定是操作系统业界的
一个阴谋(笑)。他们故意让大家相信编写操作系统是一件非常困难的
事情,这样就可以高价兜售自己开发的操作系统,而且操作系统的作者
还会被顶礼膜拜。那么实际情况又怎么样呢?和别的程序相比,其实编
写操作系统并没有那么难,至少笔者的感觉是这样。
在各位读者之中,也许有人曾经挑战过操作系统的编写,但因为太难而
放弃了。拥有这样经历的人也许不会认同笔者的观点。其实你错了,你
的失败并不是因为编写操作系统太难,而是因为没有人告诉你那其实是
一件很简单的事而已。
不仅是编写操作系统,任何事都是一样的。如果讲解的人认为它很难,那就不可能把它讲述得通俗易懂,即便是同样的内容,也会讲得无比复
杂。这样的讲解,肯定是很难懂的。
那么,你想不想和笔者一起再挑战一次呢?如果你曾经梦想过编写自己
的操作系统,一定会觉得乐在其中的。
可能有人会说,这本书足足有700多页,怎么会“有趣”和“简单”呢?
唔,这么一说笔者也觉得挺心虚的,不过其实也只是长了那么一点点
啦。平均下来的话,每天只有大约23页的内容,你看,也没有那么长
吧?
这本书的文风非常轻松,也许你不知不觉中就会读得很快。但是这样的
话可能印象不会很深,最好还是能静下心来慢慢地读。书中所展示的程
序代码和文字的说明同样重要,因此也希望大家仔细阅读。只要注意这
些,理解本书的内容就应该没有问题了。
在本书中,我们使用C语言和汇编语言来编写操作系统,不过不必担
心,你可以在阅读本书的同时来逐步学习关于这些编程语言的知识。本书在这方面写得非常仔细,如果能有人通过本书终于把C语言中的指针
给搞懂了,那笔者的目的也就达到了。即便是从这样的水平开始,30天
后你也能够编写出一个很棒的操作系统,请大家拭目以待吧!第0天 着手开发之前
前言
何谓操作系统
开发操作系统的各种方法
无知则无畏
如何开发操作系统
操作系统开发中的困难
学习本书时的注意事项(重要!)
各章内容摘要1 前言
现在,挑选自己喜欢的配件来组装一台世界上独一无二的、个性化的
PC(个人电脑)对我们来说已不再困难。不仅如此,只要使用合适的
编译器1,我们就可以自己编写游戏、制作自己的工具软件;使用网页
制作工具,我们还可以轻而易举地制作主页;如果看过名著《CPU制作
法》2的话,就连自制CPU也不在话下。
1 英文为compiler,指能够将源代码编译成机器码的软件。
2《CPU制作法》,渡波郁著,每日Communications出版公司,ISBN 4-8399-0986-5。
然而,在“自制领域”里至今还有一个无人涉足的课题——自己制作操作
系统(OS)3,它看起来太难以至于初学者不敢轻易挑战。电脑组装也
好,游戏、工具软件制作也好,主页也好,CPU也好,这些都已经成为
初学者能够尝试的项目,而唯独操作系统被冷落在一边,实在有些遗
憾。“既然还没有这样的书,那我就来写一本。”这就是笔者撰写本书的
初衷。
3 Operating System的缩写,汉语译作“操作系统”。Windows、Linux、MacOS、MS-DOS等软件的总称。
也许是因为面向初学者的书太少的缘故吧,一说起操作系统,大家就会
觉着那东西复杂得不得了,简直是高深莫测。特别是像Windows和
Linux这些操作系统,庞大得一张光盘都快装不下了,要是一个人凭着
兴趣来开发的话,不知道需要历经多么漫长的过程才能完成。笔者也认
为,像这么复杂的操作系统,单凭一个人来做,一辈子都做不出来。
不过大家也不必担心太多。笔者就成功地开发过一个小型操作系统,其
大小还不到80KB4。麻雀虽小,五脏俱全,这个操作系统的功能还是很
完整的。有人也许会怀疑:“这么小的操作系统,是不是只有命令行窗
口5啊?要不就是没有多任务6?”不,这些功能都有。
4 kilobyte,程序及数据大小的度量单位,1字节(byte)的1024倍。
一张软盘的容量是1440KB。顺便提一下,1024KB等于1MB(兆字节)。1字节是8个比特,正好能记录8位0和1的信息。B到底是指字
节(byte),还是指比特(bit),有时容易混淆。这里根据一般的
规则,用大写B表示字节,小写b表示比特。
5 console,通过键盘输入命令的一种方式,基本上只用文字进行计
算机操作,是MS-DOS等老式操作系统的主流操作方式。
6 在操作系统的世界里,运行中的程序叫做“任务”,而同时执行多
个任务的方式就被称为“多任务”(multitask)。
怎么样,只有80KB的操作系统,大家不觉得稍作努力就可以开发出来
吗?即使是初学者,恐怕也会觉得这不是件难事吧?没错,我们用一个
月的时间就能写出自己的操作系统!所以大家不用想得太难,我们轻轻
松松地一起来写写看吧。
以本书作者为主角开发的操作系统OSASK7
7 笔者与他人一起合作开发的操作系统(趁机宣传一下)。虽然只
有小小的78KB,不过为了做它也花了好几年的时间。而这次能在
短时间内开发完成操作系统,是因为我们较好地总结了开发操作系统所必要的知识。也就是说,如果笔者在年轻时可以看到现在这本
书的话,可能在短时间内就能开发出OSASK了,所以笔者很羡慕
大家呀。
大家一听到编译后的文件大小为80KB可能会觉得它作为程序来讲
已经很小了,不过曾经编过程序的人可以查一查自己编的程序
(.exe文件)的大小,这样就能体会到80KB到底是难是易了。
没编过程序的人也可以下载一个看上去不是很复杂的自由软件,看
看它的可执行文件有多大。Windows 2000的计算器程序大约是
90KB,大家也可以根据这个想象一下。
本书对于不打算自己写操作系统,甚至连想都没想过这个问题的人来说
也会大有裨益。举个例子,读本自己组装PC的书就能知道PC是由哪些
组件构成的,PC的性能是由哪些部分决定的;读本如何编写游戏的
书,就能明白游戏是怎样运行的;同理,读了本书,了解了操作系统的
开发过程,就能掌握操作系统的原理。所以说,对操作系统有兴趣的
人,哪怕并不想自己做一个出来,也可以看看这本书。
阅读本书几乎不需要相关储备知识,这一点稍后还会详述。不管是用什
么编程语言,只要是曾经写过简单的程序,对编程有一些感觉,就已经
足够了(即使没有任何编程经验,应该也能看懂),因为这本书主要就
是面向初学者的。书中虽然有很多C语言程序,但实际上并没有用到很
高深的C语言知识,所以就算是曾经因为C语言太难而中途放弃的人也
不用担心看不懂。当然,如果具备相关知识的话,理解起来会相对容易
一些,不过即使没有相关知识也没关系,书中的说明都很仔细,大家可
以放心。
本书以IBM PCAT兼容机(也就是所谓的Windows个人电脑)为对象进
行说明。至于其他机型8,比如Macintosh(苹果机)或者PC-9821等,虽
然本书也参考了其中某些部分,但基本上无法开发出在这些机型上运行
的操作系统,这一点还请见谅。严格地说,不是所有能称为AT兼容机
的机型都可以开发我们这个操作系统,我们对机器的配置要求是CPU高
于386(因为我们要开发32位操作系统)。换句话说,只要是能运行
Windows 95以上操作系统的机器就没有问题,况且现在市面上(包括二
手市场)恐怕都很难找到Windows 95以下的机器了,所以我们现在用的机型一般都没问题。
8 本书所讲的操作系统内容仅用Macintosh是开发不了的,并且开发
出的操作系统也不能直接在Macintosh上运行。但是在PC上开发的
操作系统,可以通过模拟器在Macintosh上运行。
另外,大家也不用担心内存容量和硬盘剩余空间,我们需要使用的空间
并不大。只要满足以上条件,就算机器又老又慢,也能用来开发我们的
操作系统。2 何谓操作系统
说老实话,其实笔者也不是很清楚。估计有人会说:“连这个都不懂,还写什么书?”不好意思……笔者见过很多种操作系统,有的功能非常
多,而有的功能特别少。在比较了各种操作系统之后,笔者还是没有找
到它们功能的共同点,无法下定义。结果就是,软件作者坚持说自己做
的就是操作系统,而周围的人也不深究,就那样默认了,以至于什么软
件都可以算是操作系统。笔者现在就是这么认为的。
既然就操作系统而言各有各的说法,那笔者也可以反过来利用这一点,一开始就根据自己的需要来定义操作系统,然后开发出一个满足自己定
义条件的软件就可以了。这当然也算是开发操作系统了。哪怕做一个
MS-DOS那样的,在一片漆黑的画面上显示出白字,输入个命令就能执
行的操作系统也可以,这对笔者来说很简单。
但这样肯定会让一些读者大失所望。现在初学者也都见多识广,一提到
操作系统,大家就会联想到Windows、Linux之类的庞然大物,所以肯
定期待自制操作系统至少能任意显示窗口、实现鼠标光标控制、同时运
行几个应用程序,等等。所以为了满足读者的期待,我们这次就来开发
一个具有上述功能的操作系统。3 开发操作系统的各种方法
开发操作系统的方法也是各种各样的。
笔者认为,最好的方法就是从既存操作系统中找一个跟自己想做的操作
系统最接近的,然后在此基础上加以改造。这个方法是最节省时间的。
但本书却故意舍近求远,一切从零开始,完完全全是自己从头做起,这
是因为笔者想向各位读者介绍从头到尾开发操作系统的全过程。如果我
们找一个现成的操作系统,然后在此基础上删删改改的话,那这本书就
不能涉及操作系统全盘的知识了,这样肯定无法让读者朋友满意。不过
由于是全部从零做起,所以篇幅长些,还请读者朋友们耐下心来慢慢
看。
要开发操作系统,首先遇到的问题就是使用什么编程语言,这次我们想
以C语言为主。“啊,C语言啊?”笔者仿佛已经听到大家抱怨的声音了
(苦笑)。“这都什么年代了,用C语言多土啊”、“用C++多好呀”、“还
是Java好”、“不,我就喜欢Delphi”、“我还是觉得Visual Basic最好”……
大家个人喜好习惯各不相同。这种心情笔者都能理解,但为了讲解时能
简单一些,笔者还是想用C语言,请大家见谅。C语言功能虽不多,但
用起来方便,所以用来开发操作系统刚好合适。要是用其他语言的话,仅讲解语言本身就要花很长时间,大家恐怕就没兴趣看下去了。
在这里先向大家传授一个从零开始开发操作系统的诀窍,那就是不要一
开始就一心想着要开发操作系统,先做一个有点操作系统样子的东西就
行了。如果我们一上来就要开发一个完整的操作系统的话,要做的东西
太多,想想脑袋都大了,到时恐怕连着手的勇气也没有了。笔者就是因
为这个,几年间遇到了很多挫折。所以在这本书里,我们不去大张旗鼓
地想着要开发一个操作系统,而是编写几个像操作系统的演示程序1就
行了。其实在开发演示程序的过程中大家就会逐步发现,演示程序不再
是简单的演示程序,而是越来越像一个操作系统了。
1 演示程序的英文是demonstration。指不是为了使用,而是为了演
示给人看的软件。4 无知则无畏
当我们打算开发操作系统时,总会有人从旁边跳出来,罗列出一大堆专
业术语,问这问那,像内核怎么做啦,外壳怎么做啦,是不是单片啦,是不是微内核啦,等等。虽然有时候提这些问题也是有益的,但一上来
就问这些,当然会让人无从回答。
要想给他们一个满意答复,让他们不再从旁指手画脚的话,还真得多学
习,拿出点像模像样的见解才行。但我们是初学者,没有必要去学那些
麻烦的东西,费时费力且不说,当我们知道现有操作系统在各方面都考
虑得如此周密的时候,就会发现自己的想法太过简单而备受打击没了干
劲。如果被前人的成果吓倒,只用这些现有的技术来做些拼拼凑凑的工
作,岂不是太没意思了。
所以我们这次不去学习那些复杂的东西,直接着手开发。就算知道一大
堆专业术语、专业理论,又有什么意思呢?还不如动手去做,就算做出
来的东西再简单,起码也是自己的成果。而且自己先实际操作一次,通
过实践找到其中的问题,再来看看是不是已经有了这些问题的解决方
案,这样下来更能深刻地理解那些复杂理论。不管怎么说,反正目前我
们也无法回答那些五花八门的问题,倒不如直接告诉在一旁指手画脚的
人们:我们就是想用自己的方法做自己喜欢的事情,如果要讨论高深的
问题,就另请高明吧。
■■■■■
其实反过来看,什么都不知道有时倒是好事。正是因为什么都不知道,我们才可能会认真地去做那些专家们嗤之以鼻的没意义的“傻事”。也许
我们大多时候做的都没什么意义,但有时也可能会发掘出专家们千虑一
失的问题呢。专家们在很多方面往往会先入为主,甚至根本不去尝试就
断定这也不行那也不行,要么就浅尝辄止。因此能够挑战这些问题的,就只有我们这种什么都不知道的门外汉。任何人都能通过学习成为专
家,但是一旦成为专家,就再也找不回门外汉的挑战精神了。所以从零
开始,在没有各种条条框框限制的情况下,能做到什么程度就做到什么
程度,碰壁以后再回头来学习相关知识,也为时未晚。
实际上笔者也正是这样一路磕磕绊绊地走过来,才有了今天。笔者没去过教授编程的学校,也几乎没学什么复杂的理论就开始开发操作系统
了。但也正是因为这样,笔者做出的操作系统与其他的操作系统大不相
同,非常有个性,所以得到了专家们的一致好评,而且现在还能有机会
写这本书,向初学者介绍经验。总地说来,笔者从着手开发直到现在,每天都是乐在其中的。
正是像笔者这样自己摸着石头过河,一路磕磕绊绊走过来的人,讲出的
东西才简单易懂。不过在讲解过程中会涉及失败的经验,以及如何重新
修正最终取得成功,所以已经懂了的人看着可能会着急。不好意思,如
果碰到这种情况请忍耐一下吧。
读了这部分内容或许有人会觉得“是不是什么都不学习才是最好的啊”,其实那倒不是。比如工作上需要编写某些程序,或者一年之内要完成某
些任务,这时没有时间去故意绕远路,所以为了避免不必要的失败,当
然是先学习再着手开发比较好。但这次我们是因为自己的兴趣而学习操
作系统的开发的,既然是兴趣,那就是按自己喜欢的方式慢慢来,这样
就挺好的。5 如何开发操作系统
操作系统(OS)一般打开电源开关就会自动执行。这是怎么实现的
呢?一般在Windows上开发的可执行文件(~.exe),都要在操作系统
启动以后,双击一下才能运行。我们这次想要做的可不是这种可执行程
序,而是希望能够做到把含有操作系统的CD-ROM或软盘插入电脑,或
者将操作系统装入硬盘后,只要打开电源开关就能自动运行。
为了开发这样的操作系统,我们准备按照如下的步骤来进行。
1 source program,为了生成机器码所写的程序代码。可通过编译器
编译成机器语言。
2 CPU能够直接理解的语言,由二进制的0和1构成。其实源代码也
是由 0和1构成的(后述)。
也就是说,所谓开发操作系统,就是想办法制作一张“含有操作系统
的,能够自动启动的磁盘”。
这里出现的“映像文件”一词,简单地说就是软盘的备份数据。我们想要
把特定的内容写入磁盘可不是拿块磁铁来在磁盘上晃晃就可以的。所以
我们要先做出备份数据,然后将这些备份数据写入磁盘,这样才能做出
符合我们要求的磁盘。
软盘的总容量是1440KB,所以作为备份数据的映像文件也恰好是
1440KB。一旦我们掌握了制作磁盘映像的方法,就可以按自己的想法
制作任意内容的磁盘了。这里希望大家注意的是,开发操作系统时需要利用Windows等其他的操
作系统。这是因为我们要使用文本编辑器或者C编译器,就必须使用操
作系统。既然是这样,那么世界上第一个操作系统又是怎么做出来的
呢?在开发世界上第一个操作系统时,当然还没有任何现成的操作系统
可供利用,因此那时候人们不得不对照着CPU的命令代码表,自己将0
和1排列起来,然后再把这些数据写入磁盘(估计那个时候还没有磁
盘,用的是其他存储设备)。这是一项非常艰巨的工作。所以恐怕最初
的操作系统功能非常有限,做好之后人们再利用它来开发一个稍微像点
样的操作系统,然后再用这个来开发更实用的操作系统……操作系统应
该就是这样一步一步发展过来的。
由于这次大部分初学者都是Windows用户,所以决定使用Windows这个
现成的操作系统,Windows9598Me2000XP中任意一个版本都可以。
肯定也有人会说还是Linux好用,所以笔者也总结了一下Linux上的做
法,具体内容写在了帮助与支持3里,有需要的人请一定看一看。
3 http:hrb.osask.jp。
另外,如果C编译器和映像文件制作工具等不一样的话,开发过程中就
会产生一些细微的差别,这很难一一解释,所以笔者就直接把所有的工
具都放到附带光盘里了。这些几乎都是笔者所发布的免费软件,它们大
都是笔者为了开发后面的OSASK操作系统而根据需要自己编写的。这
些工具的源代码也是公开的。除此之外,我们还会用到其他一些免费软
件,所有这些软件的功能我们会在使用的时候详细介绍。6 操作系统开发中的困难
现在市面上众多的C编译器都是以开发Windows或Linux上的应用程序为
前提而设计的,几乎从来没有人想过要用它们来开发其他的软件,比如
自己的操作系统。笔者所提供的编译器,也是以Windows版的gcc1为基
础稍加改造而做成的,与gcc几乎没什么不同。或许也有为开发操作系
统而设计的C编译器,不过就算有,恐怕也只有开发操作系统的公司才
会买,所以当然会很贵。这次我们用不了这么高价的软件。
1 GNU项目组开发的免费C编译器,GNU C Compiler的简称。有时
也指GUN开发的各种编译器的集合(GNU Compiler Collection)。
因为这些原因,我们只能靠开发应用程序用的C编译器想方设法编写出
一个操作系统来。这实际上是在硬来,所以当中就会有很多不方便的地
方。
就比如说printf(“hello\n”);吧,这个函数总是出现在C语言教科书的第一
章,但我们现在就连它也无法使用。为什么呢?因为printf这个函数是
以操作系统提供的功能为前提编写的,而我们最开始的操作系统可是什
么功能都没有。因此,如果我们硬要执行这个函数的话,CPU会发生一
般保护性异常2,直接罢工。刚开始的时候不仅是printf,几乎所有的函
数都无法使用。
2 电脑的CPU非常优秀,如果接到无视OS保护的指令或不可能执行
的指令时,首先会保存当前状态,中断正在执行的程序,然后调用
事先设定的函数。这种机制称为异常保护功能,比如除法异常、未
定义指令异常、栈异常等。不能归类到任何异常类型中去的异常事
态被称为一般保护异常。这种异常保护功能或许会让老Windows用
户想起那噩梦般的蓝屏画面,但是如果经历过操作系统开发以后,大家就会觉得这种机制实在是太有用了。
关于这次开发语言的选择,如果非要说出个所以然的话,其实也是
因为C语言还算是很少依赖操作系统功能的语言,基本上只要不用
函数就可以了。如果用C++的话,像newdelete这种基本而重要的运算符都不能用了,另外对于类的做法也会有很多要求,这样就无法
发挥C++语言的优势了。当然,为了使用这些函数去开发操作系
统,只要我们想办法,还是能够克服种种困难的。但是如果做到这
个份上,我们不禁会想,到底是在用C++做操作系统呢,还是在为
了C++而做操作系统呢。对别的语言而言这个问题会更加突出,所
以这次还是决定使用C语言,希望大家予以理解。
顺便插一句,在开发操作系统时不会受到限制的语言大概就只有汇编语
言3了。还是汇编语言最厉害4(笑)。但是如果本书仅用汇编来编写操
作系统的话,恐怕没几个人会看,所以就算是做事管前不顾后的笔者也
不得不想想后果。
3 Assembler,与机器语言最接近的一种编程语言。过去掌握这种语
言的人会备受尊敬,而现在这种人恐怕要被当作怪人了,真是可悲
啊。原本汇编语言的正式名称应该是Assembly语言,而Assembler
一般指的是编译程序。不过像笔者这样的老程序员,往往不对这两
个词进行区分,统称为Assembler。
4 读到这里,大家可能还不理解为什么这么说,越往后看就越能慢
慢体会到了。
另外,在开发操作系统时,需要用到CPU上的许多控制操作系统的
寄存器5。一般的C编译器都是用于开发应用程序的,所以根本没有
任何操作这些寄存器的命令。另外,C编译器还具有非常优秀的自
动优化功能,但有时候这反而会给我们带来麻烦。
5 Register,有些类似机器语言中的变量。对CPU而言,内存是外部
存储装置,在CPU内核之中,存储装置只有寄存器。全部寄存器的
容量加起来也不到1KB。
归根到底,为了克服以上这些困难,有些没法用C语言来编写的部分,我们就只好用汇编语言来写了。这个时候,我们就必须要知道C编译器
到底是怎样把程序编译成机器语言的。如果不能够与C编译器保持一致
的话,就不能将汇编语言编写的部分与C语言编写的部分很好地衔接起
来。这可是在编写普通的C语言程序时所体会不到哦!不过相比之下,今后的麻烦可比这种好处多得多啊(苦笑)。同样,如果用C++来编写操作系统,也必须知道C++是如何把程序
编译成机器语言的。当然,C++比C功能更多更强,编译规则也更
复杂,所以解释起来也更麻烦,我们选用C语言也有这一层理由。
总之,如果不理解自己所使用的语言是如何进行编译的,就没法用
这种语言来编写操作系统。
书店里有不少C语言、C++的书,当然也还有Delphi、Java等其他各种编
程语言的书,但这么多书里没有一本提到过“这些源代码编译过后生成
的机器语言到底是什么样的”。不仅如此,虽然我们是在通过程序向
CPU发指令的,但连CPU的基本结构都没有人肯给我们讲一讲。作为一
个研究操作系统的人,真觉得心里不是滋味。为了弥补这一空缺,我们
这本书就从这些基础讲起(但也仅限于此次开发操作系统所必备的基础
知识)。
我们具备了这样的知识以后,说不定还会改变对程序设计的看法。以前
也许只想着怎么写出漂亮的源代码来,以后也许就会更注重编译出来的
是怎样的机器语言。源代码写得再漂亮,如果不能编译成自己希望的机
器语言,不能正常运行的话,也是毫无意义的。反过来说,即便源代码
写得难看点儿,即便只有特定的C编译器才能编译,但只要能够得到自
己想要的机器语言就没有问题了。虽然不至于说“只要编译出了想要的
机器语言,源代码就成了一张废纸”,但从某种意义上说还真就是这
样。
对于开发操作系统的人而言,源程序无非是用来得到机器语言的“手
段”,而不是目的。浪费太多时间在手段上就是本末倒置了。
对了,还有一点或许会有人担心,所以在这里事先说明一下:虽然操作
系统是用C语言和汇编语言编写的,但并不是用C++编写的应用程序就
无法在这个操作系统上运行。编写应用程序所用的语言,与开发操作系
统所使用的语言是没有任何关系的,大家大可不必担心。7 学习本书时的注意事项(重要!)
本书从第1章开始,写的是每一天实际开发的内容,虽然一共分成了30
天,但这些都是根据笔者现在的能力和讲解的长度来大概切分的,并不
是说读者也必须得一天完成一章。每个人觉得难的地方各不相同,有时
学习一章可能要花上一星期的时间,也有时可能一天就能学会三章的内
容。
当然,学习过程中可能会遇到看不太懂的章节,这种时候不要停下来,先接着往下读上个一两章也许会突然明白过来。如果往后看还是不明白
的话,就先确认一下自己已经理解到哪一部分了,然后回过头来再从不
懂的地方重新看就是了。千万别着急,看第二遍时,没准就会豁然开朗
了。
如果已经弄清了哪里没理解,而且没理解的部分看了很多遍还是不明白
的话,大家可以参阅我们的帮助与支持页面1,或许“问题与解
答”(QA)页里会有解说。
1 http:hrb.osask.jp。
■■■■■
本书对C语言的指针和结构体的说明与其他书籍有很大区别。这是因为
本书先讲CPU的基本结构,然后讲汇编,最后再讲C语言,而其他的书
都不讲这些基础知识,刚一提到指针,马上就转到变量地址如何如何
了。所以就算大家“觉得”已经明白了那些书里讲的指针,也不要把本书
的指针部分跳过去,相信这次大家能真正地理解指针。当然,如果真的
已经弄明白了的话,大概看看就可以了。
■■■■■
从现在开始我们来一点一点地开发操作系统,我们会将每个阶段的进展
情况总结出来,这些中间成果都刻在附带光盘里了,只要简单地复制一
下就能马上运行。关于这些程序,有些需要注意的地方,我们在这里简
单说明一下。
比如最初出现的程序是“helloos0”,下一个出现的程序是“helloos1”。 即使我们以helloos0为基础,把书中讲解的内容一个不漏地全部做上一
遍,也不能保证肯定可以得到后面的helloos1。书中可能偶尔有讲解得
很完整的地方,但其实大多部分都讲得不够明确,这主要是因为笔者觉
得这些地方不讲那么仔细大家肯定也能明白。
笔者说这些主要就是想要告诉大家,不仅要看书里的内容,更要好好看
程序。有时候书上写得很含糊,读起来晦涩难懂,但一看程序马上就明
白了。本书的主角不是正文内容,而是附录中的程序。正文仅仅是介绍
程序是如何做出来的。
所以说从这个意义上讲,与其说这是“一本附带光盘的书”,倒不如说这
是“一张附带一本大厚书的光盘”(笑)。
■■■■■
关于程序还有一点要说明的——这里收录的程序的版权全部归笔者所
有。可是,读了这本书后打算开发自己的操作系统的话,可能有不少地
方要仿照着附带程序来做;也有人可能想把程序的前期部分全盘照搬过
来用;还有人可能想接着本书最后的部分继续开发自己的操作系统。
这是一本关于操作系统的教材,如果大家有上面这些想法却不能自由使
用附录程序的话,这教材也就没什么意义了,所以大家可以随意使用这
些程序,也不用事先提出任何申请。尽管大家最后做出来的操作系统中
可能会包含笔者编写的程序,不过也不用在版权声明中署上笔者的名
字。大家可以把它当作自己独立开发的操作系统,也可以卖了它去赚
钱。就算大家靠这个系统成了亿万富翁,笔者也不会要分毫的分成,大
家大可放心2。
2 在版权署名时,如果有人执意要署上笔者的名字,笔者也不反
对。另外,要是大家一不小心发了大财,一定要给笔者分红的话,笔者当然也会心存感激地接受下来(笑)。
而且这不只是买了本书的人才能享受的特权,从图书馆或朋友那儿借书
看的人,甚至在书店里站着只看不买的人,也都享有以上权利。当然,大家要是买了这本书,对笔者、对出版社都是一个帮助。(笑)
在引用本书程序时,只有一点需要注意,那就是大家开发的操作系统的
名字。因为它已经不是笔者所开发的操作系统了,所以请适当地改个名字,以免让人误解,仅此一点请务必留意。不管程序的内部是多么相
像,它都是大家自己负责发布的另外一个不同的操作系统。给它起个响
亮的名字吧。
以上声明仅适用于书中的程序,以及附带光盘中收录的用作操作系统教
材的程序。本书正文和附带光盘中的其他工具软件不在此列。复制或修
改都受到著作权法的保护。请在法律允许范围内使用这些内容。与光盘
中的工具软件相关的许可权会放在本书最后一章予以说明。8 各章内容摘要
估计看过目录大家就能大概了解各章内容了,但因为目录里项目太多,所以在这里概括总结一下。如果有人想要保留一份神秘感,想边看边
猜“后面的内容会是什么”,那么可以跳过本节不读(笑)。这一部分可
以说是全书的灯塔,当大家在阅读本书的过程中感觉有什么不放心的时
候,就回过头来重新看看本节内容吧。
第一周(第1天 ~ 第7天)
一开始首先要考虑怎么来写一个“只要一通电就能运行的程序”。这部分
用C语言写起来有些困难,所以主要还是用汇编语言来写。
这步完成之后,下一步就要写一个从磁盘读取操作系统的程序。这时即
便打开电脑电源,它也不会自动地将操作系统全部都读进来,它只能读
取磁盘上最开始的512字节的内容,所以我们要编写剩余部分的载入程
序。这个程序也要用汇编语言编写。
一旦完成了这一步,以后的程序就可以用C语言来编写了。我们就尽快
使用C语言来学习开发显示画面的程序。同时,我们也能慢慢熟悉C语
言语法。这个时候我们好像在做自己想做的事,但事实上我们还没有自
由操纵C语言。
接下来,为了实现“移动鼠标”这一雄心,我们要对CPU进行细致的设
定,并掌握中断处理程序的写法。从全书总体看来,这一部分是水平相
当高的部分,笔者也觉得放在这里有些不妥,但从本书条理上讲,这些
内容必须放在这里,所以只好请大家忍耐一下了。在这里,CPU的规格
以及电脑复杂的规格都会给我们带来各种各样的麻烦。而且开发语言既
有C语言,又有汇编语言,这又给我们造成了更大的混乱。这个时候我
们一点儿也不会觉得这是在做自己想做的事,怎么看都像是在“受人摆
布”。
渡过这个痛苦的时期,第一周就该结束了。
第二周(第8天 ~ 第14天)
一周的苦战还是很有意义的,回头一看,我们就会发现自己还是斩获颇丰的。这时我们已经基本掌握了C语言的语法,连汇编语言的水平也能
达到本书的要求了。
所以现在我们就可以着手开发像样的操作系统了。但是这一次我们又要
为算法头痛了。即使掌握了编程语言的语法,如果不懂得好的算法的
话,也还是不能开发出来自己想要的操作系统。所以这一周我们就边学
习算法边慢慢地开发操作系统。不过到了这一阶段,我们就能感觉到基
本上不会再受技术问题限制了。
第三周(第15天 ~ 第21天)
现在我们的技术已经相当厉害了,可以随心所欲地开发自己的操作系统
了。首先是要支持多任务,然后是开发命令行窗口,之后就可以着手开
发应用程序了。到本周结束时,就算还不够完备,我们也能拿出一个可
以称之为操作系统的软件了。
第四周(第22天 ~ 第28天)
在这个阶段,我们可以尽情地给操作系统增加各种各样的功能,同时还
可以开发出大量像模像样的应用程序来。这个阶段我们已经能做得很好
了,这可能也是我们最高兴的时期。这部分要讲解的内容很少,笔者也
不用再煞费苦心地去写那些文字说明了,可以把精力都集中在编程上
(笑)。对了,说起文字才想起来,正好在这个时期可以让我们的操作
系统显示文字了。
免费赠送两天(第29天 ~ 第30天)
剩下的两天用来润色加工。这两天我们来做一些之前没来得及做,但做
起来既简单又有趣的内容。
■■■■■
以上就是从第1天到第30天的内容摘要,越到后面介绍越短,这也说明
最开始的内容是最复杂的。那么,就让我们做好准备,开始第一天的学
习吧。啊,大家不用紧张,放松!放松!第1天 从计算机结构到汇编程序入门
先动手操作
究竟做了些什么
初次体验汇编程序
加工润色1 先动手操作
与其啰啰嗦嗦地写上一大堆,还不如实际动手开发来得轻松,我们这就
开始吧。而且我们一上来就完全抛开前面的说明,既不用C语言,也不
用汇编程序,而是采用一个迥然不同的工具来进行开发(笑)。
■■■■■
有一种工具软件名为“二进制编辑器”(Binary Editor)1,是一种能够直
接对二进制数进行编辑的软件。我们现在要用它来编辑出下图这样的文
件。
1 原文直译为“二进制编辑器”(Binary Editor),在中国“二进制编
辑器”、“十六进制编辑器”这两种说法都有,这里尊重原著保留
了“二进制编辑器”的说法。——译者注
也许有人会说“这样的工具我从来没有见过呀”,没关系,下面我们来详
细地介绍一下。
首先打开下面这个网页:
http:www.vcraft.jpsoftbz.html
2
2 如果此网页连接不上,也可用google等检索工具来搜索一下,从
别处下载Bz1621.lzh。
用BZ打开helloos.img时的画面点击“在此下载”(Download)的链接,下载文件Bz1621.lzh (在此非常
感谢c.mos公司无偿公开这么好的软件)。当你读到本书的时候,也许
会有新的版本发布,所以文件名可能会有所不同。接下来,安装下载下
来的文件,然后双击启动Bz.exe程序。如果不能正常启动的话,可以参
考上面网页的“★注意★”一项,按照上面的安装指导进行操作。
顺利启动的话屏幕上会出现如下画面。
BZ起动时的画面
好,让我们赶紧来输入吧,只要从键盘上直接输入EB4E904845……就
可以了,简单吧。其中字符之间的空格是这个软件在显示时为方便阅读
自动插入的,不用自己从键盘上输入。另外,右边的.N.HELLOIPL……
部分,也不用从键盘输入,这是软件自动显示的。可能版本或者显示模
式不一样的时候,右侧显示的内容会与下面的截图有所不同。不过不用
往心里去,这些内容完全是锦上添花的东西,即使不一样也没事。
输入到000037位置时的画面
从000090开始后面全都是00,一直输入到最后168000这个地址。如果一直按着键盘上的“0”不放手的话,画面上的0就会不停地增加,但因为个
数相当多,也还是挺花时间的。如果家里有只猫的话,倒是可以考虑请
它来帮忙按住这个键(日本的谚语:想让猫来搭把手,形容人手不足,连猫爪子都想借用一下),或者也可以干脆就用透明胶把这个键粘上。
168000
附近的
画面
因为一下子输入到最后实在是挺花时间的,大家也许想保存一下中间结
果,这时可以从菜单上选择“文件”(File)→“另存为”(Save As),画
面上就会弹出保存文件的对话框。我们可以随便取个名字进行保存,笔
者推荐使用“helloos.img”。当想要打开保存过的文件时,首先要启动
Bz.exe,从菜单上选择“文件”(File)→“打开”(Open),然后选择目
标文件,这样原来保存的内容就能显示出来了。可是这个时候不管我们
怎么努力按键盘,它都一点反应也没有。这是怎么回事?难道必须要一
次性输入到最后吗?这个大家不必担心,其实只要从菜单里选择“编
辑”(Edit)→“只读”(Read Only)就可以进入编辑状态啦。好了,我
们继续输入。
如果家里的猫自由散漫惯了,不肯帮忙,而大家又不想用透明胶粘键盘
这种土方法的话,不妨这样:用鼠标选择一部分0,然后从菜单选择“编辑”(Edit)→“复制”(Copy)。简简单单复制粘贴几次就可以大功告成
了,这工具还真方便呀。
哦,对了,差点忘记一件重要的事——在地址0001F0和001400附近还有
些地方不全是00,要像下图那样把它们也改过来,然后整体检查一下,确认没有输入错误。
0001F0附近
001400附近
下面,我们把输入的内容保存下来就完成了软盘映像文件的制作,这时
查看一下文件属性,应该能看到文件大小正好是1474560字节
(=1440×1024字节)。然后我们将这个文件写入软盘(具体后述),并
用它来启动电脑。如下所示,画面上会显示出“hello, world”这个字符
串。目前的程序虽然简单,但毕竟一打开电脑它就能够自动启动,还能
在屏幕上显示出一句话来,已经小小成功了哦。不过,我们现在还没有
结束这个程序的方法,所以想要结束的时候,只能把软盘取出来后切断
电脑电源,或者重新启动。至于最关键的往磁盘上写映像文件的方法,笔者已经预先准备好了一个
程序。在介绍它的使用方法之前,我们先把笔者准备的工具全都安装进
来吧,这样后面讲解起来比较省事。下面我们就来看怎么安装这些工
具。
■■■■■
打开附带光盘,里面有一个名为tolset
3的文件夹,把这个文件夹复制到
硬盘的任意一个位置上。现在里面的东西还不多,只有3MB左右,不过
以后我们自己开发的软件也都要放到这个文件夹里,所以往后它会越来
越大,因此硬盘上最好留出100MB左右的剩余空间。工具安装到此结
束,我们既不用修改注册表,也不用设定路径参数,就这么简单。而且
以后不管什么时候,都可以把这整个文件夹移动到任何其他地方。用这
些工具,我们不仅可以开发操作系统,还可以开发简单的Windows应用
程序或OSASK应用程序等。
3 tool set的缩写,“工具套件”的意思。
接下来我们打开刚才安装的tolset文件夹,在文件夹的名字上单击鼠标右
键,从弹出的菜单上选择“新建”(New)→“文件夹”(Folder)。画面
上会显示出缺省的文件夹名“新建文件夹”(New Folder),我们要把它
改为“helloos0”,并把前面保存的映像文件helloos.img复制到这个文件夹
里。另外,刚才安装的tolset文件夹下有个名为z_new_w的子文件夹,其
中有!cons_9x.bat和!cons_nt.bat这两个文件,要把它们也复制粘贴到
helloos0文件夹里。接着,在文件夹helloos0里单击鼠标右键,从弹出的菜单中选择“新
建”(New)→“文本文件”(Text Document),并将文件命名
为“run.bat”,回车后屏幕上会显示“如果改变文件扩展名,可能会导致文
件不可用。确实要更改吗?”的对话框,我们选择“是”,创建run.bat文
件。然后在run.bat文件名上单击鼠标右键,在弹出的菜单上选择“编
辑”(Edit),输入下面内容并保存。
run.bat
copy helloos.img ..\z_tools\qemu\fdimage0.bin..\z_tools\make.exe -C ..z_toolsqemu
然后按照同样的步骤,创建install.bat,并将下列内容输入进去。
install.bat.. \z_tools\imgtol.com w a: helloos.img
其实以上步骤创建的所有文件都已经给事先给大家准备好了,就放在附
带光盘中名为projects\01_day\helloos0的子文件夹里。所以大家只要把光
盘上的helloos0复制下来,粘帖到硬盘的tolset文件夹里,所有的准备工
作就瞬间完成了。
■■■■■
好了,现在我们就来把这个有点像操作系统的软件安装到软盘上吧。随
便从附近的小店里买片新软盘来,在Windows下格式化一下(格式化方
法:把软盘插入磁盘驱动器后打开“我的电脑”,在“3.5吋软
盘”(3.5inches Floppy)A:上单击鼠标右键,再选择“格式化”(Format)
即可)。对了,这个时候不要选择“快速格式化”选项。然后用鼠标左键
双击helloos0文件夹里的 !cons_nt.bat文件(Windows9598Me的用户需
要双击!cons_9x.bat),屏幕上就会出现一个命令行窗口(console)。我
们先仔细确认一下软盘是否已经插好,然后在命令行窗口上输
入“install”并回车,这样安装操作就开始了。稍候片刻,等安装程序执
行完毕,我们的操作系统启动盘也就做好了。完成安装之后,也可以关
闭刚才的命令行窗口了。现在我们就用这张操作系统启动软盘来启动一下电脑试试吧,肯定跟刚
才一样,会显示出“hello, world”的字样来。
在这里要提醒大家几点:一是软盘虽然不要求必须用全新的,但如果太
旧的话,在读写过程中容易出问题,所以最好还是不要用太旧的软盘。
另外,就算是新盘,如果太便宜的话有时也用不了,若是发现有问题,就需要再去买一张。最后一点,一旦格式化或者往软盘内安装操作系
统,就会把里面原有的东西全部覆盖掉,所以大家千万不要用存有重要
文件的软盘来尝试哦。
■■■■■
看到这里,大家可能会有各种问题:“这些我都明白,可是既要专门去
买张软盘,又要重启电脑,实在太麻烦了,难道就没有什么更简单的方
法吗?”、“我家的电脑根本就没有软驱呀”、“我的电脑没有什么重启按
钮,也没有关电源的开关,一旦启动了这个奇怪的操作系统,就没法终
止啦”。其实这些问题笔者已经考虑到了,所以特意准备了一个模拟
器。我们有了这个模拟器,不用软盘,也不用终止Windows,就可以确
认所开发的操作系统启动以后的动作,很方便呢。
使用模拟器的方法也非常简单,我们只需要在用!cons_nt.bat(或者
是!cons_9x.bat)打开的命令行窗口中输入“run”指令就可以了。然后一
个名叫QEMU的非常优秀的免费PC模拟器就会自动运行。QEMU不是笔
者开发的,它是由国外的一些天才们开发出来的。感谢他们!
“我按照你说的一步一步地做了一遍,可是不行呀!怎么回事呢?”会遇
到这种情况的人肯定是个非常认真的人,可能真的完全按照上面步骤用
二进制编辑器自己做了一个helloos.img文件出来。出现这种问题,肯定
是因为文件中有输入错误的地方,虽然笔者不知道具体错在哪儿,不过
建议最好检查一下000000到000090,以及0001F0前后的数据。如果还是
不行的话,那就干脆用附带光盘中笔者做的helloos.img好了。
可能有些人嫌麻烦,懒得自己输入,上来就直接使用光盘里的
helloos.img文件,这当然也没什么不可以;但笔者认为这种体验(一点
一点地输入,再千辛万苦地纠错,最终功夫不负有心人取得成功)本身
更难能可贵,建议大家最好还是亲自尝试一下。
■■■■■就这样,我们没有去改造现成的操作系统,而是从零开始开发了一个,并让它运转了起来(当然,如果别人承认这是个操作系统的话)。这太
了不起了!大家完全可以在朋友们面前炫耀一番了。仅学习了几个小时
开发的一个初学者,就能从零开始做出一个操作系统,这本书不错吧
(笑)?这次我们考虑到从键盘直接输入比较麻烦,所以就只让它显示
了一条消息;如果能再多输入一些内容的话,那仅用这种方法就可以开
发任意一个操作系统(当然最大只能到1440KB)。现在唯一的问题
是,我们还不知道之前输入的那些“EB 4E 90 48 45……”到底是什么意
思(而这也正是我们所面临的最大问题)。今天剩下的时间,以及以后
的29天时间里,我们都会讲解这个问题。2 究竟做了些什么
为什么用这种方法就能开发出操作系统来呢?现在搞清楚这个问题,会
对我们今后的理解很有帮助,所以在这里要稍做说明。
首先我们要了解电脑的结构。电脑的处理中心是CPU,即“central
process unit”的缩写,翻译成中文就是“中央处理单元”,顾名思义,它就
是处理中心。如果我们把别的元件当作中心来使用的话,那它就叫做
CPU了,所以无论什么时候CPU都总是处理中心。不过这个CPU除了与
别的电路进行电信号交换以外什么都不会,而且对于电信号,它也只能
理解开(ON)和关(OFF)这两种状态,真是个没用的人呀(虽然它
不是人吧,大家领会精神)。
CPU
我们平时会用电脑写文章、听音乐、修照片以及做其他各种各样的事
情,我们用电脑所做的这些,其实本质上都不过是在与CPU交换电信号
而已,而且电信号只有开(ON)和关(OFF)这两种状态。再说直白
一点,CPU根本无法理解文章的内容,更不会鉴赏音乐、照片,它只会
机械地进行电信号的转换。CPU有计算指令,所以它能够进行整数的加
减乘除运算,也可以处理负数、计算小数以及10的100次方这样庞大的
数值,它甚至能够处理我们初中才学到的平方根和高中才学到的对数、三角函数,而且所有这些计算仅通过一条指令就能简单实现。虽然CPU
功能如此强大,但它其实根本不理解数的概念。CPU就是个集成电路
板,它只是忠实地执行电信号给它的指令,输出相应的电信号。
这些概念可能不太容易理解,还是让我们来看个的具体例子吧。比如
说,让我们用1来表示开(ON),用0来表示关(OFF),这样比较容
易理解。我们可以用32×16=512个开(ON)和关(OFF)的集合(=电
信号的集合),来显示出下面这个不甚好看的人头像。我们也可以用0000 0000 0000 0000 0000 0100 1010 0010 这32个电信号的
集合来表示1186这个整数。(注:用二进制表示1186的话,就是100
1010 0010)。我们还可以用0100 1011 0100 1111 0100 1111 0100 0010这
32个电信号的集合来表示“BOOK”这个单词(注:这实际上就是电脑内
部保存这个单词时的电信号集合)。
CPU能看见的就只有这些开(ON)和关(OFF)的电信号。换句话
说,假如我们给CPU发送这么一串电信号:
0000 0100 0011 1000 0000 1110 0001 0000
这信号可能是一幅画的部分数据,可能是个二进制整数,可能是一段音
乐旋律,可能是文章中的一段文字,也可能是保存了的游戏的一部分数
据,或者是程序中的一行代码,不管它是什么,CPU都一窍不通。CPU
不懂这些,也不在乎这些,它只是默默地、任劳任怨地按照程序的指令
进行相应的处理。
■■■■■
看到这里,或许有人会认为是先有了这么多要做的事情,所以人类才发
明了CPU,而实际上并不是这样。最早人们发明CPU只是为了处理电信
号,那个时候没有人能想到它后来会成为这么有用的机器。不过后来人
们发现,一旦把电信号的开(ON)关(OFF)与数字0和1对应起来,就能将二进制数转换为电信号,同时电信号也可以转换回二进制数。所
以,虽然CPU依然只能处理电信号,但它从此摇身一变,成了神奇的二
进制数计算机。
因为我们可以把十进制数转换成二进制数,也能把二进制数还原成十进
制数,所以人们又发明了普通的计算机。后来,我们发现只要给每个文
字都编上号(即文字编码),就可以建立一个文字与数字的对应关系,从而就可以把文字也转换成电信号,让CPU来处理文章(比如进行文字
输入或者字词检索等)。依此类推,人们接着又找到了将图像、音乐等
等转换成电信号的方法,使CPU的应用范围越来越广。不过CPU还是一
如既往,只能处理电信号。
而且我们能用CPU来处理的并不仅仅只有数据,我们还可以用电信号向
CPU发出指令。其实我们所编写的程序最终都要转换成所谓的机器语
言,这些机器语言就是以电信号的形式发送给CPU的。这些机器语言不
过就是一连串的指令代码,实际上也就是一串0和1的组合而已。
软盘的原理也有异曲同工之妙,简单说来,就是把二进制的0和1转换为
磁极的N极和S极而已,所以我们只用0和1就可以写出映像文件来。不
仅是映像文件,计算机所能处理的各种文件最终都是用0和1写成的。因
此可以说,不能仅用0和1来表达的内容,都不能以电信号的形式传递给
CPU,所以这种内容是计算机所无法处理的。
■■■■■
而“二进制编辑器”就是用来编辑二进制数的,我们可以很方便地用它来
输入二进制数,并保存成文件。所以它就是我们的秘密武器,也就是说
只要有了二进制编辑器,随便什么文件我们都能做出来。(厉害吧!)
如果大家在商店里看到一个软件,很想要而又不想花那么多钱的话,那
就干脆就回家用二进制编辑器自己做一个算啦!用这个方法我们完全可
以自己制作出一个与店里商品一模一样的东西来。看上一个500万像素
的数码相机,但是太贵了买不起?那有什么关系?我们只要有二进制编
辑器在手,就可以制作出毫不逊色于相机拍摄效果的图像,而且想做几
张就可以做几张。要是C编译器太贵了买不起,也不用郁闷。即使没有
C编译器,我们也可以用二进制编辑器做出一个与编译器生成文件完全
一样的执行文件,而且就连C编译器本身都可以用二进制编辑器做出
来。
有了这么强大的工具,制作操作系统就是小菜一碟。道理就是这么简
单,所以我们这次不费吹灰之力就做了个操作系统出来也是理所当然
的。或许有人会想“就为了讲这么个小事,有必要长篇大论写这么多
吗?”其实不然,如果我们对CPU的基础有了彻底的理解,以后的内容
就好懂多了。
■■■■■“喂,且慢,我明白了二进制编辑器就是编辑二进制数的软件,可是在
你让我输入你的helloos.img的时候,除了0和1以外,不是还让我输入了
很多别的东西吗?你看,第一个不就是E吗?这哪里是什么二进制数?
分明是个英文字母嘛!”……噢,不好意思,这说得一点错都没有。
虽然二进制数与电信号有很好的一一对应关系,但它有一个缺点,那就
是位数实在太多了,举个例子来说,如果我们把1234写成二进制数,就
成了10011010010,居然长达11位。而写成十进制数,只用4位就够了。
因为这样也太浪费纸张了,所以计算机业界普遍使用十六进制数。十进
制数的1234写成十六进制数,就是4D2,只用3位就够了。
那为什么非要用十六进制数呢,用十进制数不是也挺好的吗?实际上,我们可以非常简便地把二进制数写成十六进制数。
二进制数和十六进制数对照表 0000 – 0 0100 – 4 1000 – 8 1100 – C
0001 – 1 0101 – 5 1001 – 9 1101 – D
0010 – 2 0110 – 6 1010 – A 1110 – E
0011 – 3 0111 – 7 1011 – B 1111 – F
有了这个对照表,我们就能轻松进行二进制与十六进制之间的转换了。
将二进制转换为十六进制时,只要从二进制数的最后一位开始,4位4位
地替换过来就行了。如:
100 1101 0010 → 4D2
反之,把十六进制数的4D2转换为二进制数的100 1101 0010也很简单,只要用上面的对照表反过来变换一下就行了。而十进制数变换起来就没
这么简单了。同理,八进制数是把3位一组的二进制数作为一个八进制
位来变换的,这种计数法在计算机业界也偶有使用。
因此我们在输入EB的时候,实际上是在输入11101011,所以它其实是
个十六进制编辑器,但笔者习惯称它为二进制编辑器,希望大家不要见
怪。
■■■■■
虽然笔者对二进制编辑器如此地赞不绝口,但用它也解决不了什么实际
问题。因为这就相当于“只要有了笔和纸,什么优秀的小说都能写出
来”一样。笔和纸不过就是笔和纸而已,实际上对创作优秀的小说也帮
不上多大的忙。所以大家在写程序时,用的都是文本编辑器和编译器,没有谁只用二进制编辑器来做程序的。大家照相用的也都是数码照相
机,没有谁只用二进制编辑器来做图像文件。因此,我们用二进制编辑
器进行的开发就到此为止,接下来我们要调转方向,开始用编程语言来
继续我们的开发工作。不过有了这次的经验,我们就知道了如果今后遇
到什么特殊情况还可以使用二进制编辑器,它是非常有用的。而且后面
章节中我们偶尔也会用到它。3 初次体验汇编程序
好,现在就让我们马上来写一个汇编程序,用它来生成一个跟刚才完全
一样的helloos.img吧。我们这次使用的汇编语言编译器是笔者自己开发
的,名为“nask”,其中的很多语法都模仿了自由软件里享有盛名的汇编
器“NASM”,不过在“NASM”的基础之上又提高了自动优化能力。
超长的源代码
DB 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
DB 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
DB 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
(为节省纸张,这里省略中间的18万4314行)
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
我们使用复制粘帖的方法,就可以写出这样一个超长的源代码来,将其
命名为“helloos.nas”,并保存在helloos0中。仔细看一下就能发现这个文
件内容与我们用二进制编辑器输入的内容是一模一样的。
接着,我们用“!cons_nt.bat”或是“!cons_9x.bat”(我们在前面已经说过,要根据Windows的版本决定用哪一个。以后每次都这样解释一遍的话比
较麻烦,所以我们就将它简写为!cons好了) 打开一个命令行窗口
(console),输入以下指令(提示符部分不用输入):
提示符1
>..\z_tools\nask.exe helloos.nas helloos.img
1 prompt,出现在命令行窗口中,提示用户进行输入的信息。
这样我们就得到了映像文件helloos.img。
好,我们的第一个汇编语言程序就这样做成了!……不过这么写程序也
太麻烦了,要做个18万行的程序,不但浪费时间,还浪费硬盘空间。与
其这样还不如用二进制编辑器呢,不用输入“0x”、“,”什么的,还能轻松
一点。
■■■■■其实要解决这个问题并不难,如果我们不只使用DB指令,而把RESB指
令也用上的话,就可以一下将helloos.nas缩短了,而且还能保证输出的
内容不变,具体我们来看下面。
正常长度的源程序
DB 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
DB 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
DB 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
DB 0xff, 0xff, 0xff, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x2d, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41
DB 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00
RESB 16
DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
DB 0xee, 0xf4, 0xeb, 0xfd, 0x0a, 0x0a, 0x68, 0x65
DB 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72
DB 0x6c, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 368
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
我们自己动手输入这段源程序比较麻烦,所以笔者把它放在附带光盘的
projects\01_day\ helloos1目录下了。大家只要把helloos1文件夹复制粘
帖到tolset文件夹里就可以了。之前的helloos0文件夹以后就不用了,我
们可以把它删除,也可以放在那里留作纪念。顺便说一下,笔者将
helloos0文件夹名改为了helloos1,删掉了其中没用的文件,新建并编辑
了需要用到的文件,这样就做出了新的helloos1文件夹。操作系统就是
这样一点一点地成长起来的。
每次进行汇编编译的时候,我们都要输入刚才的指令,这太麻烦了,所
以笔者就做了一个批处理文件2asm.bat。有了这个批处理文件,我们只
要在用“!cons”打开的命令行窗口里输入“asm”,就可以生成helloos.img
文件。在用“asm”作成img文件后,再执行“run”指令,就可以得到与刚
才一样的结果。
2 batch file,基本上只是将命令行窗口里输入的命令写入文本文
件。虽然还有功能更强的处理,但本书中我们用不到。所谓批处理就是批量处理,即一次处理一连串的命令。
■■■■■
DB指令是“define byte”的缩写,也就是往文件里直接写入1个字节的指
令。笔者喜欢用大写字母来写汇编指令,但小写的“db”也是一样的。
在汇编语言的世界里,这个指令是程序员的杀手锏,也就是说只要有了
DB指令,我们就可以用它做出任何数据(甚至是程序)。所以可以
说,没有用汇编语言做不出来的文件。文本文件也好,图像文件也好,只要能叫上名的文件,我们都能用汇编语言写出来。而其他的语言(比
如C语言)就没有这么万能。
RESB指令是“reserve byte”的略写,如果想要从现在的地址开始空出10
个字节来,就可以写成RESB 10,意思是我们预约了这10个字节(大家
可以想象成在对号入座的火车里,预订了10个连号座位的情形)。而且
nask不仅仅是把指定的地址空出来,它还会在空出来的地址上自动填入
0x00,所以我们这次用这个指令就可以输出很多的0x00,省得我们自己
去写18万行程序了,真是帮了个大忙。
这里还要说一下,数字的前面加上0x,就成了十六进制数,不加0x,就
是十进制数。这一点跟C语言是一样的。4 加工润色
刚才我们把程序变成了短短的22行,这成果令人欣喜。不过还有一点不
足就是很难看出这些程序是干什么的,所以我们下面就来稍微改写一
下,让别人也能看懂。改写后的源文件增加到了48行,它位于附带光盘
的projects\01_day\helloos2目录下,大家可以直接把helloos2文件夹复制
到tolset里。现在helloos1也可以删掉了(每个文件夹都是独立的,用完
之后就可以删除,以后不再赘述。当然放在那里留作纪念也是可以
的)。
现在的程序有50行,也占不了多少地方,所以我们将它写在下面了。
有模有样的源代码; hello-os; TAB=4; 以下这段是标准FAT12格式软盘专用的代码
DB 0xeb, 0x4e, 0x90
DB HELLOIPL ; 启动区的名称可以是任意的字符串(8字节)
DW 512 ; 每个扇区(sector)的大小(必须为512字节)
DB 1 ; 簇(cluster)的大小(必须为1个扇区)
DW 1 ; FAT的起始位置(一般从第一个扇区开始)
DB 2 ; FAT的个数(必须为2)
DW 224 ; 根目录的大小(一般设成224项)
DW 2880 ; 该磁盘的大小(必须是2880扇区)
DB 0xf0 ; 磁盘的种类(必须是0xf0)
DW 9 ; FAT的长度(必须是9扇区)
DW 18 ; 1个磁道(track)有几个扇区(必须是18)
DW 2 ; 磁头数(必须是2)
DD 0 ; 不使用分区,必须是0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明,固定
DD 0xffffffff ;(可能是)卷标号码
DB HELLO-OS ; 磁盘的名称(11字节)
DB FAT12 ; 磁盘格式名称(8字节)
RESB 18 ; 先空出18字节; 程序主体
DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
DB 0xee, 0xf4, 0xeb, 0xfd; 信息显示部分
DB 0x0a, 0x0a ; 2个换行
DB hello, world
DB 0x0a ; 换行 DB 0
RESB 0x1fe- ; 填写0x00,直到 0x001fe
DB 0x55, 0xaa; 以下是启动区以外部分的输出
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
■■■■■
这里有几点新内容,我们逐一来看一下。首先是“;”命令,这是个注释命
令,相当于C语言或是C++中的“”。正是因为有它,我们才可以在源代
码里加入很多注释。
其次是DB指令的新用法。我们居然可以直接用它写字符串。在写字符
串的时候,汇编语言会自动地查找字符串中每一个字符所对应的编码,然后把它们一个字节一个字节地排列起来。这个功能非常方便,也就是
说,当我们想要变更输出信息的时候,就再也不用自己去查字符编码表
了。
再有就是DW指令和DD指令,它们分别是“define word”和“ddefine
double-word”的缩写,是DB指令的“堂兄弟”。word的本意是“单词”,但
在计算机汇编语言的世界里,word指的是“16位”的意思,也就是2个字
节。“double-word”是“32位”的意思,也就是4个字节。
对了,差点忘记说RESB 0x1fe-了。这个美元符号的意思如果不讲,恐
怕谁也搞不明白,它是一个变量,可以告诉我们这一行现在的字节数
(如果严格来说,有时候它还会有别的意思,关于这一点我们明天再
讲)。在这个程序里,我们已经在前面输出了132字节,所以这里的就
是132。因此nask先用0x1fe减去132,得出378这一结果,然后连续输出
378个字节的0x00。
那这里我们为什么不直接写378,而非要用呢?这是因为如果将显示信
息从“hello, world”变成“this is a pen.”的话,中间要输出0x00的字节数也
会随之变化。换句话说,我们必须保证软盘的第510字节(即第0x1fe字
节)开始的地方是55 AA。如果在程序里使用美元符号的话,汇编
语言会自动计算需要输出多少个00,我们也就可以很轻松地改写输出信息了。
■■■■■
既然可以毫不费力地改写显示的信息,就一定要好好发挥这一功能,让
我们的操作系统显示出自己喜欢的一句话,让它成为一个只属于我们自
己的、世界上独一无二的操作系统。不过遗憾的是现在它还不能显示汉
字。当然大家也可以尝试一下,但由于这个程序还没有显示汉字的功
能,所以显示出来的都是乱码,因此大家先将就一下,用英语或拼音
吧。
■■■■■
最后再给大家解释一下程序中出现的几个专门术语。时间不早了,我们
今天就到这吧。其他的留待明天再说。 TAB=4 ………… 有的文本编辑器可以调整TAB键的宽度。请使用这种编辑器的人将TAB键的宽度设定成4,这样源程序更容易读。可能有人说,我这里只能用记事本
(notepad),TAB键宽度固定为8,想调都没法调。没关系,明天笔者来推荐一个好用的文本编辑器。
FAT12格式 … (FAT12 Format)用Windows或MS-DOS格式化出来的软盘就是这种格式。我们的helloos也采用了这种格式,其中容纳了我们开发的操作系统。这个格式兼容
性好,在Windows上也能用,而且剩余的磁盘空间还可以用来保存自己喜欢的文件。
启动区 …………
(boot sector)软盘第一个的扇区称为启动区。那么什么是扇区呢?计算机读写软盘的时候,并不是一个字节一个字节地读写的,而是以512字节为一个单位
进行读写。因此,软盘的512字节就称为一个扇区。一张软盘的空间共有1440KB,也就是1474560字节,除以512得2880,这也就是说一张软盘共有2880个扇
区。那为什么第一个扇区称为启动区呢?那是因为计算机首先从最初一个扇区开始读软盘,然后去检查这个扇区最后2个字节的内容。
如果这最后2个字节不是0x55 AA,计算机会认为这张盘上没有所需的启动程序,就会报一个不能启动的错误。(也许有人会问为什么一定是0x55 AA呢?那
是当初的设计者随便定的,笔者也没法解释)。如果计算机确认了第一个扇区的最后两个字节正好是0x55 AA,那它就认为这个扇区的开头是启动程序,并开
始执行这个程序。
IPL ……………
initial program loader的缩写。启动程序加载器。启动区只有区区512字节,实际的操作系统不像hello-os这么小,根本装不进去。所以几乎所有的操作系统,都
是把加载操作系统本身的程序放在启动区里的。有鉴于此,有时也将启动区称为IPL。但hello-os没有加载程序的功能,所以HELLOIPL这个名字不太顺理成
章。如果有人正义感特别强,觉得“这是撒谎造假,万万不能容忍!”,那也可以改成其他的名字。但是必须起一个8字节的名字,如果名字长度不到8字节的
话,需要在最后补上空格。
启动 ……………
(boot)boot这个词本是长靴(boots)的单数形式。它与计算机的启动有什么关系呢?一般应该将启动称为start的。实际上,boot这个词是bootstrap的缩写,原指靴子上附带的便于拿取的靴带。但自从有了《吹牛大王历险记》(德国)这个故事以后,bootstrap这个词就有了“自力更生完成任务”这种意思(大家如果
对详情感兴趣,可以在Google上查找,也可以在帮助和支持网页http:hrb.osask.jp 上提问)。而且,磁盘上明明装有操作系统,还要说读入操作系统的程序
(即IPL)也放在磁盘里,这就像打开宝物箱的钥匙就在宝物箱里一样,是一种矛盾的说法。这种矛盾的操作系统自动启动机制,被称为bootstrap方式。boot这
个说法就来源于此。如果是笔者来命名的话,肯定不会用bootstrap 这么奇怪的名字,笔者大概会叫它“多级火箭式”吧。第2天 汇编语言学习与Makefile入门
介绍文本编辑器
继续开发
先制作启动区
Makefile入门1 介绍文本编辑器
笔者要向大家推荐一个文本编辑器TeraPad,可以从下面这个网站下
载,这是一款免费软件(在此感谢寺尾进先生的慷慨奉献!)。
http:www5f.biglobe.ne.jp~t-susumulibrarytpad.html
1
1 这个编辑器是日文版的,译者推荐一个可编辑中文的文本编辑器
Notepad++,可以从这个网站下载:http:notepad-plus-plus.org。
这也是个免费软件。下载以后解压缩,大家可以在解压后的文件夹
里找到“Notepad++”,然后双击鼠标左键就可以安装软件了。
大家下载的时候,可能版本会升级,所以文件名也许会略有不同。
它的使用方法与记事本(notepad)基本上是一样的。它有很多选
项,大家可以根据自己的喜好进行相应的设置。这里介绍几个非常
有用的设置。
设置中文模式方法:
从菜单选择“Encoding”→“Character
set”→“Chinese”→“GB2312(Simplified)”
大家可以按照如下步骤设置Tab键所对应的字符数。从菜单选
择“Settings”→“Preference”,会弹出一个对话框,选择“Language
MenuTab Settings”,就会显示出语言和TAB键的设置窗口。在TAB
键设置的下半部可以看到TAB键的宽度设置,默认值是4。如果要
用空格代替TAB,则勾选“Replace by space”前面的选择框就可以
了。其他还有显示文章行号,显示换行符、文件结束符等很多设
置。笔者没有设置显示这些符号,因为这样画面看起来比较整洁。
不过人各有所好,大家可以试一下各种设置,选择一组自己喜欢
的。设置完成后,请按“OK”按钮关闭对话框。——译者注
笔者虽然从昨天开始介绍了很多免费软件,但并没有强制大家使用的意
思,如果大家已经有了自己喜欢的二进制编辑器或者文本编辑器的话,那就还用它们吧。即便使用不同的软件,开发出来的程序也是一样的,所以笔者没有特意把这些免费软件放在光盘里。大家不用太在意笔者推荐的软件,尽管用自己喜欢的就是了。2 继续开发
昨天我们还没有详细地讲解helloos.nas中的注释部分,其中要掌握程序
核心之前的内容和启动区以外的内容,需要具备软盘方面的一些具体知
识,而这在以后我们还会讲到,所以这两部分暂时先保留。
这样一来,尚未讲解清楚的就只有程序核心部分了,那么我们下面就把
它改写成更简单易懂的形式吧。先把projects02_day中的helloos3复制到
tolset中,然后打开其中的helloos.nas文件。这个文件太长了,我们节选
一部分来讲解。
helloos.nas节选; hello-os; TAB=4
ORG 0x7c00 ; 指明程序的装载地址; 以下的记述用于标准FAT12格式的软盘
JMP entry
DB 0x90---(中略)---; 程序核心
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
fin:
HLT ; 让CPU停止,等待指令
JMP fin ; 无限循环
msg:
DB 0x0a, 0x0a ; 换行2次 DB hello, world
DB 0x0a ; 换行
DB 0
这段程序里有很多新指令,我们从上到下依次来看看。
■■■■■
首先是ORG指令。这个指令会告诉nask,在开始执行的时候,把这些机
器语言指令装载到内存中的哪个地址。如果没有它,有几个指令就不能
被正确地翻译和执行。另外,有了这条指令的话,美元符的含义
也随之变化,它不再是指输出文件的第几个字节,而是代表将要读入的
内存地址。
ORG指令来源于英文“origin”,意思是“源头、起点”。它会告诉nask,程
序要从指定的这个地址开始,也就是要把程序装载到内存中的指定地
址。这里指定的地址是0x7c00,至于指定它的原因我们会在后文(本节
末尾)详述。
下一个是JMP指令,它相当于C语言的goto语句,来源于英文的jump,意思是“跳转”。简单吧!
再下面是“entry:”,这是标签的声明,用于指定JMP指令的跳转目的地
等。这与C语言很像。entry这个词是“入口”的意思。
■■■■■
然后我们来看看MOV指令。MOV指令应该是最常用的指令了,即便在
这段程序里,MOV指令的使用次数也仅次于DB指令。这个指令的功能
非常简单,即赋值。虽然简单,但笔者认为,只要完全掌握了MOV指
令,也就理解了汇编语言的一大半。所以,我们在这里详细地讲解一下
这个指令。
“MOV AX,0”,相当于“AX=0;”这样一个赋值语句。同样,“MOV
SS,AX”就相当于“SS=AX;”。或许有人会问:“这个AX和SS是什么东
西?”这个问题我们待会儿再回答。
MOV命令源自英文“move”,意思是“移动”。“赋值”与“移动”虽然有些相
似,但毕竟还是不同的。一般说来,如果我们把一个东西移走了,它原来所占用的位置就会空出来。但是,在执行了“MOV SS,AX”语句之
后,AX并没有变“空”,还保留着原来的值不变。所以这实际上是“赋
值”,而不是“移动”。如果用“COPY”指令来打比方,理解起来就简单多
了。至于为什么成了MOV指令,笔者也搞不明白。
■■■■■
现在来说说AX和SS。CPU里有一种名为寄存器的存储电路,在机器语
言中就相当于变量的功能。具有代表性的寄存器有以下8个。各个寄存
器本来都是有名字的,但现在知道这些名字的机会已经不多了,所以在
这里顺便介绍一下。
AX——accumulator,累加寄存器
CX——counter,计数寄存器
DX——data,数据寄存器
BX——base,基址寄存器
SP——stack pointer,栈指针寄存器
BP——base pointer,基址指针寄存器
SI——source index,源变址寄存器
DI——destination index,目的变址寄存器
这些寄存器全都是16位寄存器,因此可以存储16位的二进制数。虽然它
们都有上面这种正式名称,但在平常使用的时候,人们往往用简单的英
文字母来代替,称它们为“AX寄存器”、“SI寄存器”等。
其实寄存器的全名还是很能说明它本来的意义的。比如在这8个寄存器
中,不管使用哪一个,差不多都能进行同样的计算,但如果都用AX来
进行各种运算的话,程序就可以写得很简洁。
“ADD CX,0x1234”编译成81 C1 34 12,是一个4字节的命令。
而 “ADD AX,0x1234”编译成05 34 12,是一个3字节的命令。从上面例子可以看出,这里所说的“程序可以写得简洁”是指“用机器语
言写程序”的情况,从汇编语言的源代码上是看不到这些区别的。如果
我们不懂机器语言,就会有很多地方难以理解。
再说说别的寄存器,CX是为方便计数而设计的,BX则适合作为计算内
存地址的基点。其他的寄存器也各有优点。
关于AX、CX、 DX、 BX 这几个寄存器名字的由来,虽然我们找不到
缩写为X的单词,但这个X表示扩展(extend)的意思。之所以说扩展是
因为在这之前CPU的寄存器都是8位的,而现在一下变成了16位,扩展
了一倍,所以发明者在原来寄存器的名字后面加了个X,意思是说“扩张
了一倍,了不起吧!”。大家可能注意到了这几个寄存器的排列顺序,它并不遵循名称的字母顺序。没错,其实这是按照机器语言中寄存器的
编号顺序排列的,可不是笔者随手瞎写的哦。
这8个寄存器全部合起来也才只有16个字节。换句话说,就算我们把这8
个寄存器都用上,CPU也只能存储区区16个字节。
另一方面,CPU中还有8个8位寄存器。
AL——累加寄存器低位(accumulator low)
CL——计数寄存器低位(counter low)
DL——数据寄存器低位(data low)
BL——基址寄存器低位(base low)
AH——累加寄存器高位(accumulator high)
CH——计数寄存器高位(counter high)
DH——数据寄存器高位(data high)
BH——基址寄存器高位(base high)名字看起来有点像,其实这是有原因的:AX寄存器共有16位,其中0位
到7位的低8位称为AL,而8位到15位的高8位称为AH。所以,如果以
为“再加上这8个8位寄存器,CPU就又可以多保存8个字节了”就大错特
错了,CPU还是那个CPU,依然只能存储区区16个字节。CPU的存储能
力实在是太有限了。
那BP、SP、SI、DI怎么没分为“L”和“H”呢?能这么想,就说明大家已
经做到举一反三了,但可惜的是这几个寄存器不能分为“L”和“H”。如果
无论如何都要分别取高位或低位数据的话,就必须先用“MOV,AX,SI”将SI的值赋到AX中去,然后再用AL、AH来取值。这貌似是英特尔
(Intel)的设计人员的思维模式。
“喂,我家的电脑是32位的,可不是16位。这样就能以32位为单位来处
理数据了吧?那32位的寄存器在哪儿呀?”大家可能会有这样的疑问,下面笔者就来回答这个问题。
EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI
这些就是32位寄存器。这次的程序虽然没有用到它们,但如果想用也是
完全可以使用的。在16位寄存器的名字前面加上一个E就是32位寄存器
的名字了。这个字母E其实还是来源于 “Extend”(扩展)这个词。在当时主流为16位的时代里,能扩展到32位算是个飞跃了。虽说EAX是个32
位寄存器,但其实跟前面一样,它有一部分是与AX共用的,32位中的
低16位就是AX,而高16位既没有名字,也没有寄存器编号。也就是
说,虽然我们可以把EAX作为2个16位寄存器来用,但只有低16位用起
来方便;如果我们要用高16位的话,就需要使用移位命令,把高16位移
到低16位后才能用。
这么说来,就是32位的CPU也只能存储区区32字节,存储能力还真是小
得可怜。
有的读者用的电脑可能是64位的,但我们这次不使用64位模式,所以这
里也就不再赘述了。
关于寄存器本来笔者就想介绍到这儿,但是突然想起来,还有一个段寄
存器(segment register),所以在这里一并给大家介绍一下吧。这些段
寄存器都是16位寄存器。
ES——附加段寄存器(extra segment)
CS——代码段寄存器(code segment)
SS——栈段寄存器(stack segment)
DS——数据段寄存器(data segment)
FS——没有名称(segment part 2)
GS——没有名称(segment part 3)
关于段寄存器的具体内容,我们保留到明天再详细讲解。现在,我们暂
时先在这些寄存器里放上0就可以了。
好,到这里寄存器已经讲得差不多了。
■■■■■
那么接下来我们继续看程序,下一个看不懂的语句应该是“MOV
SI,msg”吧。MOV是赋值,意思是SI=msg,而msg是下面将会出现的标
号。“把标号赋值给寄存器?这到底是怎么回事?”为了理解这个谜团,我们先回到JMP指令。
前面我们已经看到了“JMP entry”这个指令,其实把它写成“JMP
0x7c50”也完全没有问题。本来JMP指令的基本形式就是跳转到指定的
内存地址,因此这个指令就是让CPU去执行内存地址0x7c50的程序。
之所以可以用“JMP entry”来代替“JMP 0x7c50”,是因为entry就是
0x7c50。在汇编语言中,所有标号都仅仅是单纯的数字。每个标号对应
的数字,是由汇编语言编译器根据ORG指令计算出来的。编译器计算出
的“标号的地方对应的内存地址”就是那个标号的值。
所以,如果我们在这个程序中写了“MOV AX,entry”,那它就会把0x7c50
代入到AX寄存器里,我们代入到AX寄存器中的就是这个简单的数字。
大家可不要以为写在“entry”下面的程序也都被储存了,这是不可能的。
那么“MOV SI,msg”会怎么样呢?由于在这里msg的地址是0x7c74,所以
这个指令就是把0x7c74代入到SI寄存器中去。
■■■■■
下面我们来看“MOV AL,[SI]”。如果这个命令是“MOV AL,SI”的话,不
用多说大家也都能明白它的意思,可这里用方括号把SI括了起来。如果
在汇编语言中出现这个方括号,寄存器所代表的意思就完全不一样了。
这个记号代表“内存”。如果大家自己组装过电脑,就知道所谓“内存”,指的是256MB或512MB的那个零件。
内存
到现在为止,内存这个词我们已经使用了很多次了,可一直都还没有正
式讲解过,那内存到底是什么呢?简单地用一句话来概括,它就是一个超大规模的存储单元“住宅区”。用“住宅区”来比喻内存再合适不过了,它能充分体现出存储单元紧密、整齐地排列在一起的样子。英语中
memory是“记忆”的意思,这里我们把它译成“内存”。
通过对寄存器的讲解,现在大家都知道了CPU的存储能力很差,如果我
们想让CPU处理大量信息,就必须给它另外准备一套用于存储的电路。
因为即便是32位的CPU,把所有普通的寄存器都加在一起,最多也只能
存储32个字节的数据。就算把段寄存器也全部用上,也才只有44字节。
这么小的存储空间,就连启动电脑所必需的启动区数据都放不下。
现在大家已经知道了存储单元的必要性,那么我们下面就讲内存。内存
并不在CPU的内部,而是在CPU的外面。所以对于CPU来说,内存实际
上是外部存储器。这点很重要,就是说CPU要通过自己的一部分管脚
(引线)向内存发送电信号,告诉内存说:“喂,把5678号地址的数据
通过我的管脚传过来(严格说来,CPU和内存之间还有称为芯片
(chipset)的控制单元)!”CPU向内存读写数据时,就是这样进行信
息交换的。
CPU与内存之间的电信号交换,并不仅仅是为了存取数据。因为从根本
上讲,程序本身也是保存在内存里的。程序一般都大于44字节,不可能
保存在寄存器中,所以规定程序必须放在内存里。CPU在执行机器语言
时,必须从内存一个命令一个命令地读取程序,顺序执行。
内存虽然如此重要,但它的位置却离CPU相当远。就算是只有10厘米左
右的距离吧,可这与CPU中的半导体相比已经非常遥远了。所以,当
CPU向内存请求数据或者输出数据的时候,内存需要花很长时间才能够
完整无误地实现CPU的要求(CPU运行速度极快,所以即使在10厘米这
么短的距离内传送电信号,所花的时间都不容忽视)。所以,虽然内存
比寄存器的存储能力大很多个数量级,但使用内存时速度很慢。CPU访
问内存的速度比访问寄存器慢很多倍,记住这一点,我们才能开发出执
行速度快的程序来。■■■■■
基础知识我们讲完了,下面再回到汇编语言。MOV指令的数据传送源
和传送目的地不仅可以是寄存器或常数,也可以是内存地址。这个时
候,我们就使用方括号([ ])来表示内存地址。另外,BYTE、WORD、DWORD等英文词也都是汇编语言的保留字,下面举个例子
吧。
MOV BYTE [678],123
这个指令是要用内存的“678”号地址来保存“123”这个数值。虽然指令里
有数字,看起来像那么回事,但实际上内存和CPU一样,根本就没有什
么数值的概念。所谓的“678”,不过就是一大串开(ON)或者关
(OFF)的电信号而已。当内存收到这一串信号时,电路中的某8个存
储单元就会响应,这8个存储单元会记住代表“123”的开(ON)或关
(OFF)的电信号。为什么是8位呢?这是因为指令里指定了“BYTE”。
同样,我们还可以写成:
MOV WORD [678],123
在这种情况下,内存地址中的678号和旁边的679号都会做出反应,一共
是16位。这时,123被解释成一个16位的数值,也就是
0000000001111011,低位的01111011保存在678号,高位的00000000保
存在旁边的679号。像这样在汇编语言里指定内存地址时,要用下面这种方式来写:
数据大小 [地址]
这是一个固定的组合。如果我们指定“数据大小”为BYTE,那么使用的
存储单元就只是地址所指定的字节。如果我们指定“数据大小”为
WORD,则相邻的一个字节也会成为这个指令的操作对象。如果指定为
DWORD,则与WORD相邻的两个字节,也都成为这个指令的操作对象
(共4个字节)。这里所说的相邻,指的是地址增加方向的相邻。
至于内存地址的指定方法,我们不仅可以使用常数,还可以用寄存器。
比如“BYTE [SI]”、“WORD [BX]”等等。如果SI中保存的是987的
话,“BYTE [SI]”就会被解释成“BYTE [987]”,即指定地址为987的内
存。
虽然我们可以用寄存器来指定内存地址,但可作此用途的寄存器非常有
限,只有BX、BP、SI、DI这几个。剩下的AX、CX、DX、SP不能用来
指定内存地址,这是因为CPU没有处理这种指令的电路,或者说没有表
示这种处理的机器语言。没有对应的机器语言当然也就不能进行这样的处理了,如果有意见的话,就写邮件找英特尔的大叔们吧(笑)。笔者
没有勇气找英特尔的大叔们抱怨,所以想把DX内存里的内容赋值给AL
的时候,就会这样写:
MOV BX, DX
MOV AL, BYTE [BX]
■■■■■
根据以上说明我们知道可以用下面这个指令将SI地址的1字节内容读入
到AL。
MOV AL, BYTE [SI]
可是MOV指令有一个规则1,那就是源数据和目的数据必须位数相同。
也就是说,能向AL里代入的就只有BYTE,这样一来就可以省略
BYTE,即可以写成:
MOV AL, [SI]
1 如果违反这一规则,比如写“MOV AX,CL”的话,汇编语言就找不
到相对应的机器语言,编译时会出错。
哦,这样就与程序中的写法一样了。现在总算把这个指令解释清楚了,所以这个指令的意思就是“把SI地址的1个字节的内容读入AL中”。
■■■■■
ADD是加法指令。若以C语言的形式改写“ADD SI,1”的话,就是
SI=SI+1。“add”的英文原语意为“加”。
CMP是比较指令。或许有人想,比较指令是干什么的呢?简单说来,它
是if语句的一部分。譬如C语言会有这种语句:
if(a==3){ 处理; }
即对a和3进行比较,将其翻译成机器语言时,必须先写“CMP a,3”,告
诉CPU比较的对象,然后下一步再写“如果二者相等,需要做什么”。这里是“CMP AL,0”,意思就是将AL中的值与0进行比较。这个指令源自
英文中的compare,意为“比较”。
JE是条件跳转指令中之一。所谓条件跳转指令,就是根据比较的结果决
定跳转或不跳转。就JE指令而言,如果比较结果相等,则跳转到指定的
地址;而如果比较结果不等,则不跳转,继续执行下一条指令。因此,CMP AL, 0
JE fin
这两条指令,就相当于:
if (AL == 0) { goto fin; }
这条指令源自于英文“jump if equal”,意思是如果相等就跳转。顺便说
一句,fin是个标号,它表示“结束”(finish)的意思,笔者经常使用。
■■■■■
INT是软件中断指令。如果现在就讲中断机制的话,肯定会让人头昏脑
胀的,所以我们暂时先把它看作一个函数调用吧。这个指令源自英
文“interrupt”,是“中途打断”的意思。
电脑里有个名为BIOS的程序,出厂时就组装在电脑主板上的ROM2单元
里。电脑厂家在BIOS中预先写入了操作系统开发人员经常会用到的一
些程序,非常方便。BIOS是英文“basic input output system”的缩写,直
译过来就是“基本输入输出系统(程序)”。
2 只读存储器,不能写入,切断电源以后内容不会消失。ROM
是“read only memory”的缩写。
最近的BIOS功能非常多,甚至包括了电脑的设定画面,不过它的本质
正如其名,就是为操作系统开发人员准备的各种函数的集合。而INT就
是用来调用这些函数的指令。INT的后面是个数字,使用不同的数字可
以调用不同的函数。这次我们调用的是0x10(即16)号函数,它的功能
是控制显卡。
虽然制造厂家给我们准备好了BIOS,但其用法鲜为人知。不过这些很容易查到,笔者就做了一个关于BIOS的网页,下面给大家介绍一下。
http:community.osdev.info?(AT)BIOS
比如我们现在想要显示文字,先假设一次只显示一个字,那么具体怎么
做才能知道这个功能的使用方法呢?
首先,既然是要显示文字,就应该看跟显卡有关的函数。这么看来,INT 0x10好像有点关系,于是在上面网页上搜索,然后就能找到以下内
容(网页的原文为日语)。
显示一个字符
AH=0x0e;
AL=character code;
BH=0;
BL=color code;
返回值:无
注:beep、退格(back space)、CR、LF都会被当做控制字符处理
所以,如果大家按照这里所写的步骤,往寄存器里代入各种值,再调用
INT 0x10,就能顺利地在屏幕上显示一个字符出来3。
3 因为这里的BL中放入了彩色字符码,所以一旦这里变更,显示的
字符的颜色也应该变化。但笔者试了试,颜色并没有变。尚不清楚
为什么只能显示白色,只能推测现在这个画面模式下,不能简单地
指定字符颜色。
■■■■■
最后一个新出现的指令是HLT。这个指令很少用,会让它在第2天的内
容里就登台亮相的,估计全世界就只有笔者了。不过由于笔者对它的偏
好,就让笔者在这里多说两句吧(笑)。HLT是让CPU停止动作的指令,不过并不是彻底地停止(如果要彻底停
止CPU的动作,只能切断电源),而是让CPU进入待机状态。只要外部
发生变化,比如按下键盘,或是移动鼠标,CPU就会醒过来,继续执行
程序。说到这,请大家再仔细看看这个程序,我们会发现其实不管有没
有HLT指令,JMP fin都是无限循环,不写HLT指令也可以。所以很少有
人一开始就向初学者介绍HLT指令,因为这样只会让话变得很长。
然而笔者讨厌让CPU毫无意义地空转。如果没有HLT指令, CPU就会不
停地全力去执行JMP指令,这会使CPU的负荷达到100%,非常费电。这
多浪费呀。我们仅仅加上一个HLT指令,就能让CPU基本处于睡眠状
态,可以省很多电。什么都不干,还要耗费那么多电,这就是浪费。即
便是初学者,最好也要一开始就养成待机时使用HLT指令的习惯。或者
说,恰恰应该在初学阶段,就养成这样的好习惯。这样既节能环保,又
节约电费,或许还能延长电脑的使用寿命呢。
对了,HLT指令源自英文“halt”,意思是“停止”。
■■■■■
说了这么多,终于把这个程序从头到尾都讲完了。总结一下就是这样
的:
用C语言改写后的helloos.nas程序节选
entry:
AX = 0;
SS = AX;
SP = 0x7c00;
DS = AX;
ES = AX;
SI = msg;
putloop:
AL = BYTE [SI];
SI = SI + 1;
if (AL == 0) { goto fin; }
AH = 0x0e;
BX = 15;
INT 0x10;
goto putloop;
fin:
HLT;
goto fin;
就是有了这个程序,我们才能够把msg里写的数据,一个字符一个字符地显示出来,并且数据变成0以后,HLT指令就会让程序进入无限循
环。“hello, world”就是这样显示出来的。
■■■■■
对了,我们还没有说ORG的0x7c00是怎么回事呢。ORG指令本身刚才已
经讲过,就不再重复了,但这个0x7c00又是从哪儿冒出来的呢?换成
1234是不是就不行啊?嗯,还真是不行,我们要是把它换成1234的话,程序马上就不动了。
大家所用的电脑里配置的,大概都是64MB,甚至512MB这样非常大的
内存。那是不是这些内存我们想怎么用就能怎么用呢?也不是这样的。
比如说,内存的0号地址,也就是最开始的部分,是BIOS程序用来实现
各种不同功能的地方,如果我们随便使用的话,就会与BIOS发生冲
突,结果不只是BIOS会出错,而且我们的程序也肯定会问题百出。另
外,在内存的0xf0000号地址附近,还存放着BIOS程序本身,那里我们
也不能使用。
内存里还有其他不少地方也是不能用的,所以我们作为操作系统开发
者,不得不注意这一点。在我们作为一般用户使用Windows或Linux
时,不用想这些麻烦事,因为操作系统已经都处理好了,而现在,我们
成了操作系统开发者,就需要为用户来考虑这些问题了。只用语言文字
来讲解内存哪个部分不能用的话,不够清楚直观,所以还是要画张地
图。正好这里就有一张内存分布图,让我们一起来看看。
http:community.osdev.info?(AT)memorymap
虽然称之为地图,可实际上根本就不像地图,网页的作者也太会偷工减
料了吧。话说这个网页的作者,其实就是笔者本人,不好意思啦。大家
要是仔细看的话,会发现其中很多东西都是不知所云(都是笔者不好,真是对不起),不过在“软件用途分类”这里,有一句话可是非常重要
的,一定不能漏掉。
0x00007c00-0x00007dff :启动区内容的装载地址
程序中ORG指令的值就是这个数字。而且正是因为我们使用的是这个同
样的数字,所以程序才能正常运行。看到这,大家可能会问:“为什么是0x7c00呢? 0x7000不是更简单、好
记吗?”其实笔者也是这么想的,不过没办法,当初规定的就是
0x7c00。做出这个规定的应该是IBM的大叔们,不过估计他们现在都成
爷爷了。
一旦有了规定,人们就会以此为前提开发各种操作系统,因此以后就算
有人说“现在地址变成0x7000-0x71ff了,请大家跟着改一下”,也只是空
口号,不可能实现。因为硬要这么做的话,那现有的操作系统就必须全
部加以改造才能在这台新电脑上运行,这样的电脑兼容性不好,根本就
卖不出去。
今后也许大家还会提出很多疑问:“为什么是这样呢?”这些都是当年
IBM和英特尔的大叔们规定的。如果非要深究的话,我们倒是也能找到
一些当时时代背景下的原因,不过要把这些都说清楚的话,这本书恐怕
还要再加厚一倍,所以关于这些问题我们就不过多解释了。3 先制作启动区
考虑到以后的开发,我们不要一下子就用nask来做整个磁盘映像,而是
先只用它来制作512字节的启动区,剩下的部分我们用磁盘映像管理工
具来做,这样以后用起来就方便了。
如此一来,我们就有了projects02_day的helloos4这个文件夹。
首先我们把heloos.nas的后半部分截掉了,这是因为启动区只需要最初的
512字节。现在这个程序就仅仅是用来制作启动区的,所以我们把文件
名也改为ipl.nas。
然后我们来改造asm.bat,将输出的文件名改成ipl.bin。另外,也顺便输
出列表文件ipl.lst。这是一个文本文件,可以用来简单地确认每个指令
是怎样翻译成机器语言的。到目前为止我们都没有输出过这个文件,那
是因为1440KB的列表文件实在太大了,而这次只需要输出512字节,所
以没什么问题。
另外我们还增加了一个makeimg.bat。它是以ipl.bin为基础,制作磁盘映
像文件helloos.img的批处理文件。它利用笔者自己开发的磁盘映像管理
工具edimg.exe,先读入一个空白的磁盘映像文件,然后在开头写入
ipl.bin的内容,最后将结果输出为名为helloos.img的磁盘映像文件。详
情请参考makeimg.bat的内容。
这样,从编译到测试的步骤就变得非常简单了,我们只要双击!cons,然
后在命令行窗口中按顺序输入asm→makeimg→run这3个命令就完成了。4 Makefile入门
到helloos4为止,做出来的程序与笔者最初开发时所写的源程序是完全
一样的。在开发的过程中,笔者使用了一个名为Makefile的东西,在这
里给大家介绍一下。
Makefile就像是一个非常聪明的批处理文件。
■■■■■
Makefile的写法相当简单。首先生成一个不带扩展名的文件Makefile,然后再用文本编辑器写入以下内容。
文件生成规则
ipl.bin : ipl.nas Makefile..z_toolsnask.exe ipl.nas ipl.bin ipl.lst
helloos.img : ipl.bin Makefile..z_toolsedimg.exe imgin:..z_toolsfdimg0at.tek wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img
号表示注释。下一行“ipl.bin : ipl.nas Makefile”的意思是,如果想要制
作文件ipl.bin,就先检查一下ipl.nas和Makefile这两个文件是否都准备好
了。如果这两个文件都有了,Make工具就会自动执行Makefile的下一
行。
至于helloos.img,Makefile的写法也是完全一样的。其中的“\”是续行符
号,表示这一行太长写不下,跳转到下一行继续写。
我们需要调用make.exe来让这个Makefile发挥作用。为了能更方便地从
命令行窗口运行这个工具,我们来做个make.bat。make.bat就放在tolset
的z_new_w文件夹中,可以直接把它复制过来用。
■■■■■
做好以上这些准备后,用!cons打开一个命令行窗口(console),然后
输入“make -r ipl.bin”。这样make.exe就会启动了,它首先读取Makefile
文件,寻找制作ipl.bin的方法。因为ipl.bin的做法就写在Makefile里,make.exe找到了这一行就去执行其中的命令,顺利生成ipl.bin。然后我
们再输入“make -r helloos.img”看看,果然它还是会启动make.exe,并按
照Makefile指定的方法来执行。
到此为止好像也没什么特别的,我们再尝试一下把helloos.img和ipl.bin
都删除后,再输入“make -r helloos.img”命令。make 首先很听话地试图
生成helloos.img,但它会发现所需要的ipl.bin还不存在。于是就去
Makefile里寻找ipl.bin的生成方法,找到后先生成ipl.bin,在确认ipl.bin
顺利生成以后,就回来继续生成helloos.img。它很聪明吧。
下面,我们不删除文件,再输入命令“make -r helloos.img”执行一次的
话,就会发现,仅仅输出一行“‘helloos.img’已是最新版本
(‘helloos.img’is up to date)”的信息,什么命令都不执行。也就是说,make知道helloos.img已经存在,没必要特意重新再做一次了。它越来越
聪明了吧。
让我们再考验考验make.exe。我们来编辑ipl.nas中的输出信息,把它改
成“How are you?”并保存起来。而ipl.bin 和helloos.img保持刚才的样子不
删除,在这种情况下我们再来执行一次“make- r helloos.img”。本以为这
次它还会说没必要再生成一次呢,结果我们发现,make.exe又从ipl.bin
开始重新生成输出文件。这也就是说,make.exe不仅仅判断输入文件是
否存在,还会判断文件的更新日期,并据此来决定是否需要重新生成输
出文件,真是太厉害了。
■■■■■
现在大家知道了Makefile比批处理文件高明,但每次都输入“make -r
helloos.img”的话也很麻烦,其实有个可以省事的窍门。当然,可以
将“make -r helloos.img”这个命令写成makeimg.bat,但这么做还是离不开
批处理文件,所以我们换个别的方法,在Makefile里增加如下内容。
命令
img :..z_toolsmake.exe -r helloos.img
修改之后,我们只要输入“make img”,就能达到与“make -r
helloos.img”一样的效果。这样就省事多了。makeimg.bat已经没用了,把它删掉。另外顺便把下面内容也一并加进去吧。asm :..z_toolsmake.exe -r ipl.bin
run :..z_toolsmake.exe img
copy helloos.img ..\z_tools\qemu\fdimage0.bin..z_toolsmake.exe -C ..z_toolsqemu
install :..z_toolsmake.exe img..z_toolsimgtol.com w a: helloos.img
这样一来,“run.bat”、“install.bat”也都用不着了。不但用不着,现在还
更方便了呢。比如只要输入“make run”,它会首先执行“make img”,然
后再启动模拟器。
到目前为止,我们为了节约时间,避免每次都从汇编语言的编译开始重
新生成已有的输出文件,特意把批处理文件分成了几个小块。而现在有
了Makefile,它会自动跳过没有必要的命令,这样不管任何时候,我们
都可以放心地去执行“make img”了。而且就算直接“make run”也可以顺
利运行。“make install”也是一样,只要把磁盘装到驱动器里,这个命令
就会自动作出判断,如果已经有了最新的helloos.img就直接安装,没有
的话就先自动生成新的helloos.img,然后安装。
■■■■■
笔者把以上这些都总结在projects02_day下的helloos5文件夹里了,顺便
又另外添加了几个命令。一个命令是“make clean”,它可以删除掉最终
成果 (这里是helloos.img)以外的所有中间生成文件,把硬盘整理干
净;还有一个命令是“make src_only”,它可以把源程序以外的文件全都
删除干净。另外,笔者还增加了make命令的默认动作,当执行不带参数
的make时,就相当于执行“make img”命令(默认动作写在Makefile的最
前头)。
功能增加了这么多,而文件数量却减少到5个,看上去清爽多了吧。像
源文件这种真的必不可少的文件,多几个倒也没什么不好,但像批处理
文件这种可有可无的东西太多,堆在那里乱糟糟的就会让人很不舒服。
这样整理一下,我们以后的开发工作就会更加轻松愉快了。
■■■■■啊,有一点忘了告诉大家,这个make.exe是GNU项目组的人开发的,公
开供大家免费使用的一款软件。gcc的作者也是这个GNU项目组。真是
太感谢了!
按照现在的速度真的能在一个月后开发出一个操作系统吗?笔者也有点
担心。不过应该没问题,虽说现在的进展比当初的计划稍慢一些,不过
刚开始的时候说明肯定会多一些,等到后面用C语言来开发的时候,速
度就能上来了。嗯,就是这样……笔者满怀希望地自言自语中(苦
笑)。那么我们明天见!
COLUMN-1 数据也能“执行”吗?机器语言也能“显示”吗?
在helloos5中,如果我们把最开始的JMP entry 写成JMP msg,到底
会怎样呢? ……
首先,可不可以这么写呢?完全可以!nask不会报错,别的汇编语
言也不会报错。在汇编语言里,标号归根到底不过就是一个表示内
存地址的数字而已,至于JMP跳转的地方是机器语言还是字符编
码,汇编语言中不考虑这些问题。
那么如果执行这个程序,CPU会怎么样呢?首先最初的命令是0A
0A,意思是“OR CL,[BP+SI]”,也就是把CL寄存器的内容和BP+SI
内存地址的内容做逻辑或(OR)运算(过几天会出现这个命
令),结果放入CL寄存器。接着的命令是68 65 6C,也就是PUSH
0x6c65的意思(过几天这个命令也会出现),它将0x6c65储存进
栈。……就这样, CPU执行的命令很混乱,但CPU只能按照电信号
的指令来进行处理,所以即使不明其意,也会一板一眼地照单执
行。
结果,要么画面上出现怪异的字符,要么软盘或硬盘上的数据突然
被覆盖。虽然电脑并没有坏掉(因为CPU还在全速执行指令),但
看上去却像坏了一样。所以大家一定不要尝试这样做。
不过人无完人,搞不好通宵写了一夜程序,稀里糊涂之下就将本应
写entry的地方,错写成了msg,这也不是不可能发生。要是因为这
而丢失了重要文件,可就损失惨重了,所以CPU具有预防这种事故
的功能。但是这种功能只有在操作系统做了各种相应设置后才会起
作用(几天后也会讲到)。所以,在开发操作系统的阶段,我们还不能指望这种保护功能。从某种程度上来说,我们操作系统开发者
一直都是在提心吊胆地做开发。
那么反过来会怎样呢?也就是假设要把机器语言当作文字来显示,会出现什么结果呢?程序里有一句是“MOV SI,msg”,我们把它写
成“MOV SI,entry”看看。首先画面上会显示一个编码是B8的字符
(估计是个表情符号或者别的什么符号),下一个字符碰巧是00,所以显示就到此结束了。这种情况不会出现恶劣的后果,大家试一
试也无妨。
通过以上的尝试,最终证明,不管是CPU还是内存,它们根本就不
关心所处理的电信号到底代表什么意思。这么一来,说不定我们拿
数码相机拍一幅风景照,把它作为磁盘映像文件保存到磁盘里,就
能成为世界上最优秀的操作系统!这看似荒谬的情况也是有可能发
生的。但从常识来看,这样做成的东西肯定会故障百出。反之,我
们把做出的可执行文件作为一幅画来看,也没准能成为世界上最高
水准的艺术品。不过可以想象的是,要么文件格式有错,要么显示
出来的图是乱七八糟的。第3天 进入32位模式并导入C语言
制作真正的IPL
试错
读到18扇区
读入10个柱面
着手开发操作系统
从启动区执行操作系统
确认操作系统的执行情况
32位模式前期准备
开始导入C语言
实现HLT(harib00j) 1 制作真正的IPL
到昨天为止我们讲到的启动区,虽然也称为IPL(Initial Program
Loader,启动程序装载器),但它实质上并没有装载任何程序。而从今
天起,我们要真的用它来装载程序了。
把我们的操作系统叫作hello-os很不给劲,干脆改个名字吧。我们就叫
它“纸娃娃操作系统”。所谓纸娃娃,意思就是说那是用纸糊起来的,虚
有其表,不是真娃娃,就像拍电影时用的岩石等道具,其实都是中间空
空的冒牌货。也就是说我们现在要开发的操作系统,只是看上去像操作
系统,而其实是个没有内容的纸娃娃,所以大家不用想得太困难,轻轻
松松来做就好了。
虽然今后我们要一直称它为“纸娃娃操作系统”,而且在相当长的一段时
间里也只是把它当作一种演示程序,但到最后我们肯定能开发出一个像
模像样的操作系统,这一点请大家放心。
稍微扯远点,其实仔细想一想,这种虚有其表的“纸娃娃”又何止是
操作系统呢。就说CPU吧,其实它根本就不懂什么“数”的概念,只
是我们设计了一个电路,只要同时传给它电信号0011和0110,它就
能输出结果为1001的电信号,而这种电路我们就称之为加法电路。
只有人才会把这个结果解读为3+6=9,CPU只是处理这些电信号。
换句话说,虽然CPU根本就不懂什么数字,但却能给出正确的计算
结果,这就是笔者所谓的“纸娃娃”。
开发过游戏程序的人就会明白,比如我们和计算机下象棋的时候,可能会觉得计算机水平很高,但实际上计算机对象棋规则一窍不
通,仅仅是在执行一个程序而已。就算计算机走出了一着妙棋,也
根本不是因为它下手毫不留情啊,或者聪明啊,或者求胜心切什么
的,这些都是表象,其实它只是按部就班地执行程序而已。也就是
说它本身没有内涵,只有一个唬人的外壳,所以才叫“纸娃
娃”。“纸娃娃”太厉害了!……操作系统就算是虚有其表、虚张声
势又怎样?没什么不好的,这样就可以了!
■■■■■那么我们先从简单的程序开始吧。因为磁盘最初的512字节是启动区,所以要装载下一个512字节的内容。我们来修改一下程序。改好的程序
就是projects03_day下的harib00a1,像以前一样,我们把它复制到tolset
里来。
1 harib是日语中haribote(纸娃娃)的前面几个字母。——译者注
这次添加的内容大致如下。
本次添加的部分
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV AH,0x02 ; AH=0x02 : 读盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JC error
新出现的指令只有JC。真好,讲起来也轻松了。所谓JC,是“jump if
carry”的缩写,意思是如果进位标志(carry flag)是1的话,就跳转。这
里突然冒出来“进位标志”这么个新词,不过大家不用担心,很快就会明
白的。
■■■■■
至于“INT 0x13”这个指令,我们虽然知道这是要调用BIOS的0x13号函
数,但还不明白它到底是干什么用的,那就来查一下吧。当然还是来看
下面这个(AT)BIOS网页,http:community.osdev.info?(AT)BIOS
我们可以找到如下的内容:
磁盘读、写,扇区校验(verify),以及寻道(seek)AH=0x02;(读盘)
AH=0x03;(写盘)
AH=0x04;(校验)
AH=0x0c;(寻道)
AL=处理对象的扇区数;(只能同时处理连续的扇区)
CH=柱面号 0xff;
CL=扇区号(0-5位)|(柱面号0x300) 2;
DH=磁头号;
DL=驱动器号;
ES:BX=缓冲地址;(校验及寻道时不使用)
返回值:
FLACS.CF==0:没有错误,AH==0
FLAGS.CF==1:有错误,错误号码存入AH内(与重置
(reset)功能一样)
我们这次用的是AH=0x02,哦,原来是“读盘”的意思。
■■■■■
返回值那一栏里的FLAGS.CF又是什么意思呢?这就是我们刚才讲到的
进位标志。也就是说,调用这个函数之后,如果没有错,进位标志就是
0;如果有错,进位标志就是1。这样我们就能明白刚才为什么要用JC指
令了。
进位标志是一个只能存储1位信息的寄存器,除此之外,CPU还有其他
几个只有1位的寄存器。像这种1位寄存器我们称之为标志。标志在英文
中为flag,是旗帜的意思。标志之所以叫flag是因为它的开和关就像升旗降旗的状态一样。
所谓进位标志,本来是用来表示有没有进位(carry)的,但在CPU的标
志中,它是最简单易用的,所以在其他地方也经常用到。这次就是用来
报告BIOS函数调用是否有错的。
其他几个寄存器我们也来依次看一下吧。CH、CL、DH、DL分别是柱
面号、扇区号、磁头号、驱动器号,一定不要搞错。在上面的程序中,柱面号是0,磁头号是0,扇区号是2,磁盘号是0。
■■■■■
在有多个软盘驱动器的时候,用磁盘驱动器号来指定从哪个驱动器的软
盘上读取数据。现在的电脑,基本都只有1个软盘驱动器,而以前一般
都是2个。既然现在只有一个,那不用多想,指定0号就行了。
知道了从哪个软盘驱动器读取数据之后,我们接着看从那个软盘的什么
地方来读取数据。如果手头有不用的软盘,希望大家能把它拆开看看。拆开后可以看到,中间有一个8厘米的黑色圆盘,那是一层薄薄的磁性胶片。从外向内,一圈一圈圆环状的区域,分别称为柱面0,柱面1,……,柱面79。一共
有80个柱面。这并不是说工厂就是这样一圈一圈地生产软盘的,只是我
们将它作为一个数据存储媒体,是这样组织它的数据存储方式的。柱面
在英文中是cylinder,原意是圆筒。磁盘的柱面,尽管高度非常低,但
我们可以把它看成是一个套一个的同心圆筒,它正是因此得名的。下面讲一下磁头。磁头是个针状的磁性设备,既可以从软盘正面接触磁
盘,也可以从软盘背面接触磁盘。与光盘不同,软盘磁盘是两面都能记
录数据的。因此我们有正面和反面两个磁头,分别是磁头0号和磁头1
号。
最后我们看一下扇区。指定了柱面和磁头后,在磁盘的这个圆环上,还
能记录很多位信息,按照整个圆环为单位读写的话,实在有点多,所以
我们又把这个圆环均等地分成了几份。软盘分为18份,每一份称为一个
扇区。一个圆环有18个扇区,分别称为扇区1、扇区2、……扇区18。扇
区在英文中是sector,意思是指领域、扇形。
综上所述,1张软盘有80个柱面,2个磁头,18个扇区,且一个扇区有
512字节。所以,一张软盘的容量是:
80×2×18×512 = 1 474 560 Byte = 1 440KB
含有IPL的启动区,位于C0-H0-S1(柱面0,磁头0,扇区1的缩写),下
一个扇区是C0-H0-S2。这次我们想要装载的就是这个扇区。
■■■■■
剩下的大家还不明白的就是缓冲区地址了吧。这是个内存地址,表明我
们要把从软盘上读出的数据装载到内存的哪个位置。一般说来,如果能
用一个寄存器来表示内存地址的话,当然会很方便,但一个BX只能表
示0~0xffff的值,也就是只有0~65535,最大才64K。大家的电脑起码
也都有64M内存,或者更多,只用一个寄存器来表示内存地址的话,就
只能用64K的内存,这太可惜了。
于是为了解决这个问题,就增加了一个叫EBX的寄存器,这样就能处理
4G内存了。这是CPU能处理的最大内存量,没有任何问题。但EBX的导
入是很久以后的事情,在设计BIOS的时代,CPU甚至还没有32位寄存
器,所以当时只好设计了一个起辅助作用的段寄存器(segment
register)。在指定内存地址的时候,可以使用这个段寄存器。
我们使用段寄存器时,以ES:BX这种方式来表示地址,写成“MOV AL,[ES:BX]”,它代表ES×16+BX的内存地址。我们可以把它理解成先用ES
寄存器指定一个大致的地址,然后再用BX来指定其中一个具体地址。这样如果在ES里代入0xffff,在BX里也代入0xffff,就是1 114 095字
节,也就是说可以指定1M以内的内存地址了。虽然这也还是远远不到
64M,但当时英特尔公司的大叔们,好像觉得这就足够了。在最初设计
BIOS的时代,这种配置已经很能满足当时的需要了,所以我们现在也
还是要遵从这一规则。因此,大家就先忍耐一下这1MB内存的限制吧。
这次,我们指定了ES=0x0820,BX=0,所以软盘的数据将被装载到内存
中0x8200到0x83ff的地方。可能有人会想,怎么也不弄个整点的数,比
如0x8000什么的,那多好。但0x8000~0x81ff这512字节是留给启动区
的,要将启动区的内容读到那里,所以就这样吧。
那为什么使用0x8000以后的内存呢?这倒也没什么特别的理由,只是因
为从内存分布图上看,这一块领域没人使用,于是笔者就决定将我们
的“纸娃娃操作系统”装载到这一区域。 0x7c00~0x7dff用于启动区,0x7e00以后直到0x9fbff为止的区域都没有特别的用途,操作系统 可以随
便使用。
■■■■■
到目前为止我们开发的程序完全没有考虑段寄存器,但事实上,不管我
们要指定内存的什么地址,都必须同时指定段寄存器,这是规定。一般
如果省略的话就会把“DS:”作为默认的段寄存器。
以前我们用的“MOV CX,[1234]”,其实是“MOV CX,[DS:1234]”的意
思。“MOV AL,[SI]”,也就是“MOV AL,[DS:SI]”的意思。在汇编语言
中,如果每回都这样写就太麻烦了,所以可以省略默认的段寄存器
DS。
因为有这样的规则,所以DS 必须预先指定为0,否则地址的值就要加上
这个数的16倍,就会读写到其他的地方,引起混乱。非常成功
好,我们来执行这个程序看看吧。如果程序有什么错,它就会显示错误
信息。但估计不会出什么问题吧。没错的话,它就什么都不做(笑)。
所以,如果屏幕上不显示任何错误信息的话,我们就成功了。
哎呀,有件事差点忘了,Makefile中可以使用简单的变量,于是笔者用
变量改写了这次的Makefile。怎么样,是不是比之前稍微容易理解一
些?2 试错
软盘这东西很不可靠,有时会发生不能读数据的状况,这时候重新再读
一次就行了。所以即使出那么一、两次错,也不要轻易放弃,应该让它
再试几次。当然如果让它一直重试下去的话,要是磁盘真的坏了,程序
就会陷入死循环,所以我们决定重试5次,再不行的话就真正放弃。改
良后的程序就是projects03_day下的harib00b。
本次添加的部分;读磁盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 读入磁盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC fin ; 没出错的话跳转到fin
ADD SI,1 ; 往SI加1
CMP SI,5 ; 比较SI与5
JAE error ; SI >= 5时,跳转到error
MOV AH,0x00
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry
还是从新出现的指令开始讲吧。JNC是另一个条件跳转指令,是“Jump
if not carry”的缩写。也就是说进位标志是0的话就跳转。JAE也是条件跳
转,是“Jump if above or equal”的缩写,意思是大于或等于时跳转。
现在说说出错时的处理。重新读盘之前,我们做了以下的处理,AH=0x00,DL=0x00,INT 0x13。通过前面介绍的(AT)BIOS的网页
我们知道,这是“系统复位”。它的功能是复位软盘状态,再读一次。剩
下的内容都很简单,只要读一读程序就能懂。
嗯,今天进展不错,继续努力吧。3 读到18扇区
我们趁着现在这劲头,再往后多读几个扇区吧。下面来看看
projects03_day下的harib00c。
本次添加的部分;读磁盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV C ......
您现在查看是摘要介绍页, 详见PDF附件(15979KB,953页)。





