4 - 谁都能写的操作系统

来源:互联网 发布:ovid数据库 编辑:程序博客网 时间:2024/06/05 03:18

今天已经30号了,从开始决定写focus到现在,已经过去了快二十天了,今天才开始真正的进入正题,深感惭愧,之前忙毕业,实在忙的不可开交,请各位朋友见谅,谢谢哈。


andrew就不和大家磨嘴皮子了,开始给大家动点真格的了。。。嘿嘿。。。


先跟大家分享一下主引导扇区的概念。在我们使用的硬盘和早期的软盘,盘片的物理组织结构都是由扇区、柱面和盘面组成。一个扇区大小为512字节,一个柱面由18个扇区组成,一个盘面由80个柱面组成。在程序当中,扇区的标号是由1~18标记,柱面的编号由0~79标记,盘面由0开始标记,andrew开始就搞错了,因为andrew开始以为扇区标号是从0开始的,这个是比较容易出错的,主要是程序员的习惯性问题,你们懂的。。。那么在硬盘和软盘中的第一个扇区,即第0个盘面的第0个磁道的第1个扇区,在整个存储设备中具有特殊的地位,这个扇区称为主引导扇区(MBR),也有称主引导记录,因为这个扇区中存储着存储系统的分区表,但我们不考虑这些,因为我们暂时不关心存储系统的分区情况。这里大家需要知道存储设备中的第一个扇区就是主引导扇区即可,其大小为512字节。实验中,我们使用bochs模拟出来的软驱作为启动设备。


之前和大家分享过BIOS这个东东,大家都记得吧,如果不记得的话请看看第1篇文章哈。计算机上电后,CPU的各个寄存器会被初始化,这个时候CPU会从一个固定的地址去读取指令,这个地址就是0xffff0,其实这个地址就是BIOS的地址,在计算机刚上电的时候,CPU的地址线只有20位,寻址空间最大为1MB(为什么是这样的,以后会和大家介绍,莫着急哈),而我们看到CPU上电后执行的第一条指令地址为0xffff0,那么这个地址后最多只有16个字节的程序,不可能完成上电自检等一系列的功能,因此,其实地址0xffff0的地方只是一个跳转指令,这个跳转指令会跳转到BIOS的代码部分,然后执行BIOS的指令,为什么是这个样子的,andrew也不晓得,这要请教Inter的大叔们,哦,不对,应该是大爷们了。对于写操作系统来说,我们最关心的是,BIOS会将主引导扇区中512字节的代码拷贝到内存0x7c00的地方,然后跳转到这里去执行引导扇区的代码。
主引导扇区的代码就由我们自己来写了,BIOS先把我们的代码带上位,如果我们的代码不在这个时候掉链子的话,BIOS大哥就会把操作系统的控制大权,交给我们的代码了。

好了,那么今天我们就先试着,来体验一下,开始写我们的第一个操作系统哈。

BOOT_ADDR = 0x07c0entry startstart:jmpi run,BOOT_ADDRrun:mov ax,csmov ds,axmov es,axmov cx,#30mov dx,#0x1004mov bx,#0x000cmov bp,#msgmov ax,#0x1301int 0x10halt:jmp haltmsg:.byte 13,10.ascii " focus!".byte 13,10.ascii " I'm Andrew.".byte 13,10.org 510.word 0xAA55

以上是我们第一个操作系统的全部代码,代码在boot.s文件中。计算机刚上电的时候,只能够执行16位的指令,地址线能够寻址20位,以后会介绍为什么。在介绍focus开发环境的时候,我们说过,编写16位的汇编代码时,我们使用as86和ld86进行源程序的编译。as86汇编语法遵循的是Inter的汇编语法,语法近似于微软的MASM、NASM等汇编器,但和GNU as的汇编语法有很大的不同。andrew在介绍代码的时候会详细介绍使用到的语法规则。


在as86的汇编语法中,有三种语句,1-赋值语句;2-指示性语句;3-指令性语句。

赋值语句就是boot.s代码中的第一句

BOOT_ADDR = 0x07c0
相当于定义了一个符号常量,将0x07c0这个值赋予BOOT_ADDR这个符号,0x07c0左移四位就是0x7c00了,即这里的BOOT_ADDR表示的是段基址,学过微机原理的朋友肯定会明白,没学过的话,先不要紧,能理解多少就先理解多少,等以后讲到分段时,就明白了。

entry start
entry是关键字,告诉连接器程序的入口start,相当于我们在编写C程序时的main函数,其实这里要不要这一句都可以,因为在编译的时候,我们自己控制着代码的链接顺序,这部分代码始终都会被放在引导扇区中,并且第一个指令的地址必定为0x7c00的地方。

start:jmpi run,BOOT_ADDRrun:
指令性语句就是程序中进行实际操作的指令,即直接有CPU执行的指令。指令性语句通常由标号、操作符、操作数和注释四个部分组成,其中标号和注释是可选部分,操作数的个数根据操作符的不同,也不相同。start称为标号,标号有一个标示符后跟一个冒号":"组成,其总是位于一个语句的第一个字段,代表该条指令的地址,通常作为跳转指令的目标位置。jmpi是一个段间跳转指令,后跟两个参数run和BOOT_ADDR,第一个参数run既是下一条指令的标号,从源代码中可以看到哈,run在这条指令中作为偏移地址,第二个参数BOOT_ADDR是一个符号常量,值为0x07c0,在这条指令中作为段基址。jmpi指令执行后将会跳转到由run和BOOT_ADDR组成的地址去运行,跳转指令执行后,CS段寄存器就被设为了跳转指令中的段基址0x07c0,其实这条指令的主要作用也是为了更新CS段寄存器,因为BIOS将引导扇区的代码拷贝到内存0x7c00地址后,且开始执行引导扇区代码之前,CS段寄存器的值为0,IP寄存器的值为0x7c00。

run:mov ax,csmov ds,axmov es,ax

大家可以看到,前面的标号start和run所在的行都没有指令,其实他们都和下一行的指令是一伙的,即标号run和指令mov  ax,cs可以理解为一行。mov指令是数据传输指令,在as86汇编语法中,mov将第二个操作数的值拷贝到第一个操作数中,即将CS段寄存器中的值拷贝到AX寄存器中。后面两条语句也是同样的功能,其实这三句就是完成DS段寄存器和ES段寄存器的赋值。

mov cx,#30mov dx,#0x1004mov bx,#0x000cmov bp,#msgmov ax,#0x1301int 0x10
对寄存器进行了设置后,这一部分代码就是完成打印信息功能,打印信息功能主要调用了BIOS的0x10号中断,这里先暂时不讲BIOS的中断,等到下一期会专一补充BIOS的中断部分内容。

halt:jmp halt
这里的代码实现一个死循环功能,今天跑了这个程序,恼火的要死,代码跑起来后,我的ubuntu差点就崩溃了。。。无语。。。jmp是一个段内的短跳转指令,其后的操作数一个偏移地址,这样代码执行起来后,就不停的执行这条指令,即死循环。。。为了就是让屏幕显示的内容保留下来。

msg:.byte 13,10.ascii " focus!".byte 13,10.ascii " I'm Andrew.".byte 13,10

msg是一个标号,这里大家应该明白了,大家还没有见过指示性语句,指示性语句指示告诉编译器如何编译代码,并不生成代码。指示性语句由点号"."和表示符组成。如“.byte”,".byte"用于定义字节型数据,后跟要定义的数据,数据间以","分开。语句“.byte  13,10”定义了一个回车和换行。".ascii"伪操作符用于定义字符串,定义的字符串用双引号括起来。这一部分相当于定义了一些字符串数据,并没有可执行的代码。

.org 510.word 0xAA55
伪操作符org用于设置程序的计数器,即当前汇编的位置,在编译的时候,org语句后面的部分,编译的地址会以org设置的地址开始。这里org将编译的位置设置在第510字节处。".word"定义了一个字大小的数据0xAA55,共两个字节。而后面就没有任何的代码和数据了,因此,我们希望boot.s编译后的大小为512字节,正好可以完整的放在主引导扇区中。0xAA55是一个特殊的标记,用于标记该扇区为有效的引导扇区。BIOS会扫描有效的存储设备,通过验证这些存储设备的主引导扇区中的最后两个字节是否为0xAA55来确定是否为有效的启动设备。

代码时讲完了,但是代码要怎么编译和运行呢?接下来andrew和大家分享下,看看最终我们的第一个操作系统会是什么样。。。
在andrew的开发环境中,andrew新建了一个focusOS文件夹,与focus有关的所有代码和相关文件都在这个文件夹中,具体focusOS这个文件夹在系统的哪里不重要,下面来看看我们这一次实验中会用到哪些文件。


在focusOS目录下,focus.bxrc文件是bochs虚拟机的配置文件,这个之前已经介绍过,但这里需要注意一点,在focus.bxrc文件的第388行附近,

floppya: 1_44="focus", status=inserted

软盘的镜像名为focus,且用双引号括起。boot是一个文件夹,里面只有一个boot.s文件,建立一个文件夹是为了以后编写代码的方便,否则以后代码多了,还要重新整理,索性andrew就直接把启动代码的文件夹建好了,boot.s代码已经介绍过了,这里就略过了哈。至于bochsout.txt和parport.out这两个文件是bochs虚拟机运行时生成的文件,不必管它。那么接下来的重头戏就是Makefile文件了,Makefile文件决定着我们的代码如何编译。我们先来看看哈。。。

AS86 = as86LD86 = ld86BOOTDIR = bootfocus: $(BOOTDIR)/boot.s$(AS86) -o $(BOOTDIR)/boot.o $(BOOTDIR)/boot.s$(LD86) -s -o $(BOOTDIR)/boot $(BOOTDIR)/boot.odd bs=32 if=$(BOOTDIR)/boot of=focus skip=1clean:rm $(BOOTDIR)/boot.o $(BOOTDIR)/boot 

在Makefile中可以使用=号来定义变量,AS86和LD86、BOOTDIR就是定义的变量,=号后就是赋予他们的值,在引用变量的时候,使用$(变量名)的方式引用,比如

AS86 = as86
<pre name="code" class="plain">BOOTDIR = boot
$(AS86) -o $(BOOTDIR)/boot.o $(BOOTDIR)/boot.s

<pre name="code" class="plain">        as86 -o <span style="font-family: Arial, Helvetica, sans-serif;">boot</span><span style="font-family: Arial, Helvetica, sans-serif;">/boot.o  boot/boot.s</span>
上面定义了AS86和BOOTDIR两个变量,上面的两个语句一个是使用了变量,一个是没有使用变量,但意思完全一样。

focus: $(BOOTDIR)/boot.s$(AS86) -o $(BOOTDIR)/boot.o $(BOOTDIR)/boot.s$(LD86) -s -o $(BOOTDIR)/boot $(BOOTDIR)/boot.odd bs=32 if=$(BOOTDIR)/boot of=focus skip=1

这里介绍一下Makefile的规则,Makefile首先要声明目标和依赖,目标就是我们想要得到的文件,依赖就是我们要得到我们想要的文件,需要哪些文件,focus就是目标,boot.s就是生成focus所需要的依赖。接下来的三个语句,第一个语句是使用as86汇编器将boot.s汇编为目标代码boot.o,其中-o选项是指定输出文件,第二条语句是将目标代码boot.o链接为可执行程序,-s选项是链接时去掉符号信息。但是作为操作系统的代码和普通的应用程序代码不同,生成的可执行文件带有文件头,as86生成的文件带有32字节的文件头,因此,我们在裸机上跑就不需要了,第三条指令就是进行文件的拷贝,同时将输入文件boot的前32个字节忽略掉,这样我们就得到了一个没有文件头的镜像文件。当我们执行make命令时,生成的文件如下图所示:


生成的可执行文件boot在boot目录下面,大小为544字节,使用dd命令处理后,生成的操作系统镜像文件focus的大小为512字节,正好少了32字节的头结构,操作系统镜像的大小也正好可以放入512字节的引导扇区。

我们来运行虚拟机,让虚拟机加载镜像文件focus,看看结果如何。输入命令bochs  -f  focus.bxrc


虚拟机运行后,会出现一个选项表,选择6来启动focus。


bochs加载focus后,会出现一个调试的界面,和一个模拟的计算机终端界面,可以理解为bochs模拟计算机的屏幕,如下。


输入c,计算机开始从上电的步骤,开始启动,并加载focus操作系统。


ok!我们的focus操作系统正式运行起来了,现在的focus仅仅就是引导扇区的代码,并没有加载其他程序,focus运行时打印出两行字符串。虽然很简单,但已经介绍了计算机启动的关键内容了,也揭示着focus的开发步入了正规了,后面的日子里,andrew还是会抓紧时间了。。。


————————————————————————————

QQ:64879927

博客:http://blog.csdn.net/andrew_yau

请关注focus微信公共平台:OS的探索之旅


0 0
原创粉丝点击