概述
有点不好意思了,说了好久,才说到实质性的东西,好了,赶紧给客官上菜。
代码2-1(c2/a/boot/mbr.S) 1 ;主引导程序 2 ;------------------------------------------------------------ 3 SECTION MBR vstart=0x7c00 4 mov ax,cs 5 mov ds,ax 6 mov es,ax 7 mov ss,ax 8 mov fs,ax 9 mov sp,0x7c00 10 11 ; 清屏 利用0x06号功能,上卷全部行,则可清屏。 12 ; ----------------------------------------------------------- 13 ;INT 0x10 功能号:0x06 功能描述:上卷窗口 14 ;------------------------------------------------------ 15 ;输入: 16 ;AH 功能号= 0x06 17 ;AL = 上卷的行数(如果为0,表示全部) 18 ;BH = 上卷行属性 19 ;(CL,CH) = 窗口左上角的(X,Y)位置 20 ;(DL,DH) = 窗口右下角的(X,Y)位置 21 ;无返回值: 22 mov ax, 0x600 23 mov bx, 0x700 24 mov cx, 0 ; 左上角: (0, 0) 25 mov dx, 0x184f ; 右下角: (80,25), 26 ; VGA文本模式中,一行只能容纳80个字符,共25行。 27 ; 下标从0开始,所以0x18=24,0x4f=79 28 int 0x10 ; int 0x10 29 30 ;;;;;;;;; 下面这三行代码是获取光标位置 ;;;;;;;;; 31 ;.get_cursor获取当前光标位置,在光标位置处打印字符. 32 mov ah, 3 ; 输入: 3号子功能是获取光标位置,需要存入ah寄存器 33 mov bh, 0 ; bh寄存器存储的是待获取光标的页号 34 35 int 0x10 ; 输出: ch=光标开始行,cl=光标结束行 36 ; dh=光标所在行号,dl=光标所在列号 37 38 ;;;;;;;;; 获取光标位置结束 ;;;;;;;;;;;;;;;; 39 40 ;;;;;;;;; 打印字符串 ;;;;;;;;;;; 41 ;还是用10h中断,不过这次是调用13号子功能打印字符串 42 mov ax, message 43 mov bp, ax ; es:bp 为串首地址, es此时同cs一致, 44 ; 开头时已经为sreg初始化 45 46 ; 光标位置要用到dx寄存器中内容,cx中的光标位置可忽略 47 mov cx, 5 ; cx 为串长度,不包括结束符0的字符个数 48 mov ax, 0x1301 ;子功能号13是显示字符及属性,要存入ah寄存器, 49 ; al设置写字符方式 ah=01: 显示字符串,光标跟随移动 50 mov bx, 0x2 ; bh存储要显示的页号,此处是第0页, 51 ; bl中是字符属性, 属性黑底绿字(bl = 02h) 52 int 0x10 ; 执行BIOS 0x10 号中断 53 ;;;;;;;;; 打字字符串结束 ;;;;;;;;;;;;;;; 54 55 jmp $ ; 使程序悬停在此 56 57 message db "1 MBR" 58 times 510-($-$$) db 0 59 db 0x55,0xaa
简短说一下代码功能,在屏幕上打印字符串“1 MBR”,背景色为黑色,前景色为绿色。
由于还没有给大家讲解显卡的使用方法,故本段代码中关于“打印显示”的操作都利用bios给我们建立好的例程就好了,这里第0x10号中断便是负责有关打印的例程。
0x10中断是最为强大的bios中断了,调用的方法是把功能号送入ah寄存器,其它参数按照bios中断手册的要求放在适当的寄存器中,随后执行int 0x10即可。我们不用太细致琢磨bios功能调用了,大家可以参数代码中的注释了解下即可,毕竟咱们这里用bios中断只是临时的,以后也用不到了。第3行的“vstart=0x7c00”表示本程序在编译时,告诉编译器,把我的起始地址编译为0x7c00。第4~8行是用cs寄存器的值去初始化其它寄存器。由于bios是通过jmp 0:0x7c00跳转到MBR的,故cs此时为0。对于ds、es、fs、gs这类sreg,cpu中不能直接给它们赋值,没有从立即数到段寄存器的电路实现。只有通过其它寄存器来中转,这里我们用的是通用寄存器ax来中转。如mov ds: 0x7c00,这样就错了。
第9行是初始化栈指针,在cpu上运行的程序得遵从cpu的规则,mbr也是程序,是程序就要用到栈。目前0x7c00以下暂时是安全的区域,就用它来当做栈来用。第11~28行是清屏。因为在bios工作中,会有一些输出,如检测硬件的结果信息。为了让大家看清楚我们在MBR中的输出字符串,故先把bios的输出清掉,这里演示的是bios中断int 0x10的用法。第30~35行是做打印前的工作,先获取光标位置,目的是避免打印字符混乱,覆盖别人的输出。其实这是防君子不防小人的做法,万一别人不在光标处打印,自己打印的内容同样也会被别人覆盖。不管别人了,咱们做好自己的就行,老老实实地只在光标处打印。不知道这是否能提醒大家,字符打印的位置,不一定要在光标处,字符的位置只和显存中的地址有关,和光标是没关系的,这只是人为的加个约束,毕竟光标在视觉上告诉了我们当前字符写到哪里了,完全是为了好看,不要以为光标就是新打印字符的位置。更多细节,以后讲显卡时会提到。
这里还用到了页的概念,您看第33行,往bh寄存器中写入了0,这是告诉bios例程,我要获取第0页当前的光标。什么是页呢?显示器有很多种模式,如图形模式、文本模式等,在文本模式中,又可以工作于80*25和40*25等显示方式,默认情况下,所有个人计算机上的显卡在加电后都将自己置为80*25这种显示方式。80*25是指一屏可以显示25行、每行80列的字符,也就是2000个字符。但由于一个字符是要用两字节来表示,低字符是字符的ascii编码,高字节是字符属性,故显示一屏字符需要用4000字节(实际上,分配给一屏的容量是4KB),这一屏就称为一页,0页是默认页。
第38行~52行是往光标处打印字符。说一下第48行的mov ax,0x1301,13对应的是ah寄存器,这是调用0x13号子功能。01对应的是al寄存器,表示的是写字符方式,其低2位才有意义,各位功能描述如下:
- al=0,显示字符串,并且光标返回起始位置
- al=1,显示字符串,并且光标跟随到新位置
- al=2,显示字符串及其属性,并且光标返回起始位置。
- al=3,显示字符串及其属性,光标跟随到新位置。
第55行是执行了个死循环,$是本行指令的地址,这属于伪指令,是汇编器在编译期间分配的地址。在最终编译出来的程序中,$会被替换为指令实际所在行的地址。jmp是个近跳转,$是jmp自己的地址,于是跳到自己所在的地址再执行自己,又是跳到自己所在的地址再继续执行跳转,这样便实现了死循环。可见cpu可乖了,它只会埋头做事,并不会觉得有什么不妥,靠谱值得依赖^_^。
第57行是定义打印的字符串。第58行的$$是指本section的起始地址,上面说过了$是本行所在的地址,故$-$$是本行到本section的偏移量。由于MBR的最后两个字节是固定的内容,分别是0x55和0xaa,要预留出这2个字节,故本扇区内前512-2=510字节要填填满,那到底要用多少字节才能填满此扇区呢。用510字节减去上面通过$-$$得到的偏移量,其结果便是本扇区内的剩余量,也就是要填充的字节数。由此可见第50行的“times 510-($-$$) db 0”是在用0将本扇区剩余空间填充。
代码说完了,可还有两件大事要做,1是编译,2是如何将编译后的文件存储到0盘0道1扇区中成为MBR,以供bios大神加载之用。
前面介绍了nasm的用法,咱们马上来编译汇编代码。nasm -o mbr.bin mbr.S回车,您看,这样就编译成功了,我连-f都没有指定吧。按理说此文件大小是512字节,咱们用ls命令验证一下:ls -lb mbr.bin回车,以下是ls的输出:
-rw-rw-r--. 1 work work 512 7月 26 21:10 mbr.bin
用过linux的同学对这个输出还是很熟悉的,若头一次用linux的同学也不要慌张,这里面好多的信息并不重要,只要看看中间部分就好了,512,果然是512字节,这下心里踏实了,下一步是考虑如何将此文件写入0盘0道1扇区。
这里再给大家介绍另一个linux命令:dd。dd是用于磁盘操作的命令,功能太强大了,有如穿甲弹一样,可以深入磁盘的任何一个扇区,无坚不摧。所以,它也可以删除linux操作系统自己的文件,是把双刃剑。
还是先看帮助文件,man dd回车,为了节约大家的时间,我只把咱们今后用到的几个选项摘了出来,还是那句话,够用就行了,需要时再学。
if=FILE
read from FILE instead of stdin
此项是指定要读取的文件。
of=FILE
write to FILE instead of stdout
此项是指定把数据输出到哪个文件。
bs=BYTES
read and write BYTES bytes at a time (also see ibs=,obs=)
此项指定块的大小,dd是以块为单位来进行IO操作的,得告诉人家块是多大字节。此项是统计配置了输入块大小ibs和输出块大小obs。这两个可以单独配置。
count=BLOCKS
copy only BLOCKS input blocks
此项是指定拷贝的块数
seek=BLOCKS
skip BLOCKS obs-sized blocks at start of output
此项是指定当我们把块输出到文件时想要跳过多少个块
conv=CONVS
convert the file as per the comma separated symbol list
此项是指定如何转换文件
append append mode (makes sense only for output; conv=notrunc suggested)
这句话建议在追加数据时,conv最好用notrunc方式,也就是不打断文件。
齐了,dd的介绍就到这了,赶紧试验一下这个神奇的工具吧。
dd if=/your_path/mbr.bin of=/your_path/bochs/hd60M.img bs=512 count=1 conv=notrunc
各位看官,请将上面命令行中的your_path替换为您自己的实际路径。
输入文件是刚刚编译出来的mbr.bin,输出是我们虚拟出来的硬盘hd60M.img,块大小指定为512字节,只操作1块,即总共1*512=512字节。由于想写入第0块,所以没用seek指定跳过的块数。执行上面的命令后,会有如下输出:
记录了1+0 的读入
记录了1+0 的写出
512字节(512 B)已复制,0.313312 秒,1.6 kB/秒
这就说明命令执行成功了,mbr.bin已经写进hd60M.img的第0块了。借鉴美国宇航员阿姆斯特朗的一句话:虽然这只是简单的一小步,但却是实现我们自己系统的一大步。记得当初我可是非常激动呢。
启动bochs测试一下,我习惯到bochs安装目录下启动它,bin/bochs –f bochsrc.disk回车,接着会显示如图:
默认是[6],开始模拟啦。回车
由于咱们编译的是可调试的版本,所以会停下来,bochs等待咱们键入下一步的命令。见图:
大家看到,这一下弹出了两个界面,前面的那个是bochs所模拟的机器,可以认为它就是台电脑了,不仅仅是电脑的显示器。后面的界面是bochs的控制台,咱们控制bochs运行就要在这里输入命令。现在击活后面的bochs控制台,输入字符c后,回车,bochs所模拟的机器就开始运行了。这里键入的c是continue,调试方法同gdb类似,详细的bochs操作方法咱们会在下一章中介绍。
MBR跑起来后,就会出现下面的效果,如图
Mbr跑起来了,这可是脱离操作系统的程序哟,写操作系统我们迈出了第一步,加油。
最后
以上就是文艺羽毛为你收集整理的一步步编写操作系统 09 写个mbr的全部内容,希望文章能够帮你解决一步步编写操作系统 09 写个mbr所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复