经过 【自制操做系统01】硬核讲解计算机的启动过程 和 【自制操做系统02】环境准备与启动区实现 的讲解,咱们已经实现了一个最简单的操做系统(仅仅一条机器指令)。html
今天咱们要再往前进一步,逐渐将这个最简单的操做系统完善起来。以前最简单的操做系统是写在启动区的 512 字节里,这么小的空间之后确定不能所有用来写操做系统的代码,因此它的主要任务就是将硬盘中更多的数据读取到内存里,并跳转到内存的那个位置开始运行。git
这里不得不回顾一下每节课都说到的四次跳跃:ide
其实咱们能够无限跳跃下去,只要感受某一个环节的任务复杂了,就能够分红两步来走。但也彻底能够从第三跳开始就不再跳转了,把全部操做系统须要的指令和数据都从硬盘中加载到内存,而后执行,但这样显然很差。oop
先不说别的,先发上来一份本章内容的所有代码学习
;----BIOS把启动区加载到内存的该位置,因此需设置地址偏移量 section mbr vstart=0x7c00 ;----设置堆栈地址 mov sp,0x7c00 ;----卷屏中断,目的是清屏 mov ax,0x0600 mov bx,0x0700 mov cx,0 mov dx,0x184f int 0x10 ;----直接往显存中写数据 mov ax,0xb800 mov gs,ax mov byte [gs:0x00],'m' mov byte [gs:0x02],'b' mov byte [gs:0x04],'r' ;----读取硬盘(第2扇区)并加载到内存(0x900) mov eax,0x02 ;起始扇区lba地址,LBA=(柱面号*磁头数+磁头号)*扇区数+扇区编号-1 mov bx,0x900 ;写入的内存地址,以后用 mov cx,4 ;待读入的扇区数 call read_disk jmp 0x900 ;----读硬盘方法,eax为lba扇区号,bx为待写入内存地址,cx为读入的扇区数 read_disk: mov esi,eax ;备份 mov di,cx ;备份 ;第一步,设置要读取的扇区数 mov dx,0x1f2 mov al,cl out dx,al mov eax,esi ;恢复 ;第二步,设置LBA地址 mov cl,8 ;0-7位写入0x1f3 mov dx,0x1f3 out dx,al ;8-15位写入0x1f4 mov dx,0x1f4 shr eax,cl out dx,al ;16-23位写入0x1f5 mov dx,0x1f5 shr eax,cl out dx,al ;24-27位写入0x1f6 mov dx,0x1f6 shr eax,cl and al,0x0f ;lba的24-27位 or al,0xe0 ;另外4位为1110,表示lba模式 out dx,al ;第三步,写入读命令 mov dx,0x1f7 mov al,0x20 out dx,al ;第四步,检测硬盘状态 .not_ready: nop in al,dx and al,0x88 ;第4位为1表示准备好,第7位为1表示忙 cmp al,0x08 jnz .not_ready ;第五步,读数据 mov ax,di mov dx,256 mul dx mov cx,ax mov dx,0x1f0 .go_on_read: in ax,dx mov [bx],ax add bx,2 loop .go_on_read ret ;----512字节的最后两字节是启动区标识 times 510-($-$$) db 0 db 0x55,0xaa
section loader vstart=0x900 mov byte [gs:0xa0],'l' mov byte [gs:0xa2],'o' mov byte [gs:0xa4],'a' mov byte [gs:0xa6],'d' mov byte [gs:0xa8],'e' mov byte [gs:0xaa],'r'
mbr.bin: mbr.asm nasm -I include/ -o out/mbr.bin mbr.asm -l out/mbr.lst loader.bin: loader.asm nasm -I include/ -o out/loader.bin loader.asm -l out/loader.lst os.raw: mbr.bin loader.bin ../bochs/bin/bximage -hd -mode="flat" -size=60 -q target/os.raw dd if=out/mbr.bin of=target/os.raw bs=512 count=1 dd if=out/loader.bin of=target/os.raw bs=512 count=4 seek=2 brun: make install make only-bochs-run only-bochs-run: ../bochs/bin/bochs -f ../bochs/bochsrc.disk -q install: make clean make -r os.raw clean: rm -rf target/* rm -rf out/*
若是你粗略地读了一下代码,起码能够知道 mbr.asm 中的代码,前半部分是在屏幕上输出一个 mbr 字符串,这是上节课为了作最小操做系统而用直观方式写的代码,无关紧要。后半部分仅仅是读取了几个扇区的硬盘数据,加载到内存中的某个位置,而后跳转到此位置,这部分是关键,也是 mbr 的职责所在。操作系统
那怎么读取硬盘中的数据呢,这就要从磁盘的结构提及。硬件的东西并非很懂,因此也只能说个大概。硬盘属于磁盘的一种,磁盘分为硬盘和软盘。但他们的逻辑结构是同样的:code
盘片(platter)
磁头(head)
磁道(track)
扇区(sector)
柱面(cylinder)htm
我不想管它怎么动的,我只须要想明白,肯定一个磁头、柱面、扇区,就肯定了一个 512 字节大小的区域,这就够了。这也就是硬盘的 CHS 表示法,即 Cylinder(柱面)、Head(磁头)、Sector(扇区),只要知道了硬盘的 CHS 的数目,便可肯定硬盘的容量,硬盘的容量 = 柱面数 × 磁头数 × 扇区数 × 512B。blog
若是不考虑这个物理结构,其实硬盘就是 n 多个 512 字节的区域构成的,咱们彻底能够从 0 开始编号,每 512 字节加一,这样就能够彻底不用考虑什么扇区啦,柱面啦,这种是我比较喜欢的(看来仍是软件工程师思想呀),这种方式叫作 LBA 表示法。接口
LBA = (柱面号 * 磁头数 + 磁头号) * 扇区数 + 扇区编号 - 1
因此 CPU 要和硬盘打交道,要么用这个 CHS 表示法,就至少要告诉硬盘柱面、磁头、扇区号是多少,要么用 LBA 表示法告诉硬盘一个 LBA 号码,而后再给硬盘一个是读仍是写的信号。硬盘制做厂商千千万,CPU制做厂商也是各不相同,天然就会想到必定有一个硬盘接口标准,这个标准就叫作 ATA 标准,也能够俗称为 IDE 硬盘接口技术标准。这个标准能够下载 AT_Attachment_with_Packet_Interface 共三册的内容,但咱们用不到那么多,我这里找到了一个还算原汁原味的中文版的论文 《IDE接口硬盘读写技术》 ,看这个基本就够用了。
CPU 与外设是经过 IO 接口交互的,因此最核心的就是这个技术标准定义的 IO 接口都有哪些,分别有什么做用
I/O地址 | 读(主机从硬盘读数据) | 写(主机数据写入硬盘) |
---|---|---|
1F0H | 数据寄存器 | 数据寄存器 |
1F1H | 错误寄存器(只读寄存器) | 特征寄存器 |
1F2H | 扇区计数寄存器 | 扇区计数寄存器 |
1F3H | 扇区号寄存器或 LBA 块地址 0~7 | 扇区号或 LBA 块地址 0~7 |
1F4H | 磁道数低 8 位或 LBA 块地址 8~15 | 磁道数低 8 位或 LBA 块地址 8~15 |
1F5H | 磁道数高 8 位或 LBA 块地址 16~23 | 磁道数高 8 位或 LBA 块地址 16~23 |
1F6H | 驱动器/磁头或 LBA 块地址 24~27 | 驱动器/磁头或 LBA 块地址 24~27 |
1F7H | 命令寄存器或状态寄存器 | 命令寄存器 |
因此若是要写一个程序来读文件的话,不难分析出整个过程就是:
这五步刚恰好对应着上面的代码
最后,别忘了咱们这些代码仍然是要加载到启动区的,因此最后两个字节依然要是启动区标识符 0x55 0xaa
写好了 mbr.asm,咱们再写一个 loader.asm,设置其起始地址为 0x900(由于读写磁盘后存入的内存位置就是这个,这是咱们本身定义的),并把它放在磁盘的第二扇区(这也是咱们本身定的,只要和读盘的代码保持一致就行)
#### loader.asm
section loader vstart=0x900 mov byte [gs:0xa0],'l' mov byte [gs:0xa2],'o' mov byte [gs:0xa4],'a' mov byte [gs:0xa6],'d' mov byte [gs:0xa8],'e' mov byte [gs:0xaa],'r'
剩下的精华就在于咱们的 Makefile 文件了,能够参考下上面的代码
执行 make brun,能够看到以下效果,说明加载磁盘中的 loader 代码到内存这个过程生效了。
若是你对自制一个操做系统感兴趣,不妨跟随这个系列课程看下去,甚至加入咱们,一块儿来开发。
《操做系统真相还原》这本书真的赞!强烈推荐
当你看到该文章时,代码可能已经比文章中的又多写了一些部分了。你能够经过提交记录历史来查看历史的代码,我会慢慢梳理提交历史以及项目说明文档,争取给每一课都准备一个可执行的代码。固然文章中的代码也是全的,采用复制粘贴的方式也是彻底能够的。
若是你有兴趣加入这个自制操做系统的大军,也能够在留言区留下您的联系方式,或者在 gitee 私信我您的联系方式。
本课程打算出系列课程,我写到哪以为能够写成一篇文章了就写出来分享给你们,最终会完成一个功能全面的操做系统,我以为这是最好的学习操做系统的方式了。因此中间遇到的各类坎也会写进去,若是你能持续跟进,跟着我一块写,必然会有很好的收货。即便没有,交个朋友也是好的哈哈。
目前的系列包括