今天开始看《Orange’S:一个操作系统的实现》一书。这里是ubuntu 16.04下开发环境的搭建以及实现一个最小的操作系统(准确地说应该是一个引导扇区)。
工欲善其事,必先利其器。自制一个操作系统需要的工具如下:
- 汇编编译器NASM
- 虚拟机算计Bochs
- 软盘绝对扇区读写工具(这里直接使用强大的
dd
)
现在就来安装这些工具
安装NASM
通过源代码安装,官网下载最新版源代码,当前最新的是nasm-2.12.01
版本,下面的内容以此来演示。
个人软件一般安装到/usr/local
下,所以压缩包解压到/usr/local
下
tar zxvf nasm-2.12.01.tar.gz
cd nasm-2.12.01
./configure
make
sudo make install
安装Bochs
首先官网下载最新版源代码。当前最新的是bochs-2.6.8
版本,下面的内容以此来演示。
不用ubuntu的包管理软件来安装一方面原因是包管理安装的不一定是最新版,另一方面原因是包管理默认安装的可能没有调试功能。
个人软件一般安装到/usr/local
下,所以压缩包解压到/usr/local
下
接下来按照如下步骤安装:
tar zxvf bochs-2.6.8.tar.gz
cd bochs-2.6.8
./configure --enable-debugger --enable-disasm
make
sudo make install
安装过程中我遇到了如下错误
fatal error: X11/Xlib.h: No such file or directory
fatal error: X11/extensions/Xrandr.h: No such file or directory
这是由于缺少必要的依赖库。网上查找资料得到:第一行依赖于库libx11-dev,第二行依赖于库libxrandr-dev 因此分别安装这两个库即可解决问题:
sudo apt-get install libx11-dev libxrandr-dev
然后继续按照上面的步骤安装即可成功。
工具安装好了,下面通过一个输出`Hello, OS world!`的操作系统来对操作进行演示。 ## 一个简单的源程序 首先必须有源代码:
org 07c00h
mov ax, cs
mov ds, ax
mov es, ax
call DispStr
jmp $
DispStr:
mov ax, BootMessage
mov bp, ax
mov cx, 16
mov ax, 01301h
mov bx, 000ch
mov dl, 0
int 10h
ret
BootMessage: db "Hello, OS world!"
times 510-($-$$) db 0
dw 0xaa55
要理解这段代码,首先要会汇编(废话),其次要知道计算机启动操作系统的过程。
当计算机电源被打开时,首先进行加电自检(POST),然后寻找启动盘,如果是选择从软盘启动,计算机就会检查软盘的0面0磁道1扇区,如果发现此扇区以0xAA55
结束,那么BIOS就认为它是一个引导扇区。一旦BIOS发现引导扇区,它就会将这512字节的内容装载到内存地址0000:7c00
处,然后跳转到0000:7c00
处将控制权彻底交给这段引导代码。到此位置,计算机不再由BIOS中固有的程序来控制,而变成操作系统的一部分来控制。
了解了启动过程,再来看上面的程序。
org 07c00h
org是一个伪指令,这行代码告诉编译器,将来这段程序要被加载到内存偏移地址0x7c00h
处。
mov ax, cs
、mov
、
ds, axmov es, ax
:使ds
和es
两个段寄存器指向与cs
相同的段,以便以后在进行数据操作的时候能定位到正确的位置。
call DispStr
:调用子程序DispStr
,子程序以ret
返回。
int 10h
:是BIOS提供关于屏幕和显示器操作的程序。他前面4行都使用来设置参数的。AH=13h表示调用显示字符串的子程序。这时ES:BP = 串地址、CX
= 串长度 、DH, DL = 起始行列、BH = 页号、AL=01h时BL表示显示属性。
jmp $
:这里的$
表示当前行被汇编后的地址。举个例子,将汇编编译生成的二进制文件反汇编,形成下面的代码:
00007c09 EBFE jmp short 0x7c09
在这里是实现无线循环的功能。
times 510-($-$$) db 0
:这里的$$
表示一个节(section)的开始处被汇编后的地址。这个程序只有一个节,所以$$
表示的是程序被编译后的起始地址,也就是0x7c00
。因此$-$$
表示的是本行距离程序开始处的相对距离。这句话的意思是将512字节中剩下的部分赋值为0
(最后两个字节除外)。
dw 0xaa55
:将最后两个字节赋值为0xaa55
,让BIOS把此扇区当作引导扇区,从而将此扇区装入内存0000:7c00
处得到执行。
这段代码的大致功能就是这些,接下来就是如何使用Bochs
引导并执行它。虚拟机当然不能引导源程序,执行上面的程序首先必须将其编译并制成软盘启动盘。
将源程序编译成可执行文件制作启动盘
使用NASM编译
安装好了NASM,源程序的编译过程一行命令搞定
nasm boot.asm -o boot.bin
这样就得到了一个512字节的boot.bin
。接下来需要一个虚拟软盘存放上面的启动程序(放在扇区的0面0磁道1扇区,上面有介绍)
生成虚拟软盘
生成虚拟软盘使用的是Bochs组件中的一个工具:bximage
,它既可以生成虚拟软盘,还可以生成虚拟硬盘(虚拟磁盘也被成为磁盘映像)。创建一个软盘映像的过程如下:
$ bximage
?========================================================================
bximage
Disk Image Creation / Conversion / Resize and Commit Tool for Bochs
$Id: bximage.cc 12690 2015-03-20 18:01:52Z vruppert $
?========================================================================
1.?Create new floppy or hard disk image
2.?Convert hard disk image to other format (mode)
3.?Resize hard disk image
4.?Commit ‘undoable‘ redolog to base image
5.?Disk image info
0.?Quit
Please choose one [0] 1 <<输入1,代表创建一个软盘或硬盘映像
Create image
Do you want to create a floppy disk image or a hard disk image?
Please type hd or fd. [hd] fd <<输入fd,代表软盘
Choose the size of floppy disk image to create, in megabytes.
Please type 160k, 180k, 320k, 360k, 720k, 1.2M, 1.44M, 1.68M, 1.72M, or 2.88M.
[1.44M] <<直接回车,默认左边内容
What should be the name of the image?
[a.img] <<直接回车,默认左边内容
Creating floppy image ‘a.img‘ with 2880 sectors
The following line should appear in your bochsrc:
floppya: image="a.img", status=inserted
最后提示你在bochsrc中如何进行配置,到现在为止可执行程序(计算机)好了,虚拟软盘也好了,接下来就是将计算机写入虚拟软盘中了
将引导扇区写进软盘中
linux下使用dd
命令:
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
其中: `bs=512`:bs是用来规划一个block的大小,如果未指定则默认是512Bytes(一个扇区的大小) `count=1`:多少个bs的意思 `conv=notrunc`:如果不用它的话,软盘映像文件a.img会被截断(truncated),因为boot.bin比a.img小。
现在一切准备就绪了,接下来就是打开计算机电源,调试运行啦。 ## 如何用Bochs进行调试 Bochs是一个虚拟计算机,既然是虚拟计算机,首先要对他进行配置,告诉他你需要的计算机内存是多大的,硬盘映像和软盘映像都是那些文件等等。我的配置如下:
###################################################################
# Configuration file for Bochs
###################################################################
# how much memory the emulated machine will have
megs: 32
# filename of ROM images
romimage: file=/usr/local/bochs-2.6.8/bios/BIOS-bochs-latest, address=0xfffe0000
vgaromimage: file=/usr/local/bochs-2.6.8/bios/VGABIOS-elpin-2.40
# what disk images will be used
floppya: 1_44=/home/zpl/coding/os/a.img, status=inserted
# choose the boot disk
boot: floppy
# where do we send log message?
log: bochsout.txt
# disable the mouse
mouse: enabled=0
# enable key mapping, using US layout as default
keyboard: type=mf, serial_delay=200, paste_delay=100000
romimage
和vgaromimage
指定的文件对应的其实就是真实机器的BIOS和VGA
BIOS。
floppya
指定的是使用哪个文件作文软盘的映像
为了避免错误,上述的文件最好以绝对路径的方式给出
现在只做了最简单的一些配置,详细的配置参考官方手册,讲的很详细。
接下来打开bochs并调试:
$ bochs
?========================================================================
Bochs x86 Emulator 2.6.8
Built from SVN snapshot on May 3, 2015
Compiled on May 13 2016 at 04:39:37
?========================================================================
00000000000i[ ] BXSHARE not set. using compile time default ‘/usr/local/share/bochs‘
00000000000i[ ] reading configuration from .bochsrc
?------------------------------
Bochs Configuration: Main Menu
?------------------------------
This is the Bochs Configuration Interface, where you can describe the
machine that you want to simulate. Bochs has already searched for a
configuration file (typically called bochsrc.txt) and loaded it if it
could be found. When you are satisfied with the configuration, go
ahead and start the simulation.
You can also start bochs with the -q option to skip these menus.
1.?Restore factory default configuration
2.?Read options from...
3.?Edit options
4.?Save options to...
5.?Restore the Bochs state from...
6.?Begin simulation
7.?Quit now
Please choose one: [6] <<这里选择功能菜单
00000000000i[ ] installing x module as the Bochs GUI
00000000000i[ ] using log file bochsout.txt
Next at t=0
(0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0
<bochs:1> b 0x7c00<<在内存0x7c00处设置断点
<bochs:2> c<<运行到下一个断点处(此处是0x7c00)
(0) Breakpoint 1, 0x00007c00 in ?? ()
Next at t=29625394
(0) [0x000000007c00] 0000:7c00 (unk. ctxt): mov ax, cs ; 8cc8
<bochs:3> n<<向下执行一不步
Next at t=29625395
(0) [0x000000007c02] 0000:7c02 (unk. ctxt): mov ds, ax ; 8ed8
<bochs:4> n
Next at t=29625396
(0) [0x000000007c04] 0000:7c04 (unk. ctxt): mov es, ax ; 8ec0
<bochs:5> n
Next at t=29625397
(0) [0x000000007c06] 0000:7c06 (unk. ctxt): call .+2 (0x00007c0b) ; e80200
<bochs:6> trace-reg on<<让Bochs每走一步都显示主要寄存器的值
Register-Tracing enabled for CPU0
<bochs:7> n
Next at t=29626661
eax: 0x00001301 4865
ecx: 0x00090010 589840
edx: 0x00000000 0
ebx: 0x0000000c 12
esp: 0x0000ffd6 65494
ebp: 0x00007c1e 31774
esi: 0x000e0000 917504
edi: 0x0000ffac 65452
eip: 0x00007c09
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x000000007c09] 0000:7c09 (unk. ctxt): jmp .-2 (0x00007c09) ; ebfe
<bochs:8> x /64xb 0x7c00<<查看0x7c00开始的64字节内存
[bochs]:
0x00007c00 < bogus+ 0> : 0x8c 0xc8 0x8e 0xd8 0x8e 0xc0 0xe8 0x02
0x00007c08 < bogus+ 8> : 0x00 0xeb 0xfe 0xb8 0x1e 0x7c 0x89 0xc5
0x00007c10 < bogus+ 16> : 0xb9 0x10 0x00 0xb8 0x01 0x13 0xbb 0x0c
0x00007c18 < bogus+ 24> : 0x00 0xb2 0x00 0xcd 0x10 0xc3 0x48 0x65
0x00007c20 < bogus+ 32> : 0x6c 0x6c 0x6f 0x2c 0x20 0x4f 0x53 0x20
0x00007c28 < bogus+ 40> : 0x77 0x6f 0x72 0x6c 0x64 0x21 0x00 0x00
0x00007c30 < bogus+ 48> : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00007c38 < bogus+ 56> : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
<bochs:9>
在运行到jmp指令时即可出现如下界面:
看,左上角我们写的Hello,OS world!
。到这里,一个最简单的操作系统实现出来了。关于Bochs调试的参数,以后详细介绍。