码迷,mamicode.com
首页 > 编程语言 > 详细

30天自制操作系统(三)进入32位模式并导入C语言

时间:2017-10-19 21:17:54      阅读:395      评论:0      收藏:0      [点我收藏+]

标签:模式   back   最简   img   条件   跳转   efi   变量   补充   

1 制作真正的IPL

 

IPL(Initial Program Loader),启动程序装载器,但是之前并没有实质性的装载任何程序,这次作者要开始装载程序了。

虽然现在开发的操作系统啥功能也没有,作者说轻轻松松做。

起始我觉得吧,有了启动程序加载器之后,相当于给了我们一个给CPU传送指令的入口,我们想让CPU干啥,就给它传指令和数据就可以啦。操作系统嘛,就是一个启动程序装载器调用的一个很大的、很复杂的函数而已,突然感觉自己有些升华了( ^_^ )。

 

CPU就是一个干活很快的家伙,但是智商基本为0,现在我们已经牵住了CPU的牛鼻子了。

 

这里还是磁盘的第一个扇区,也就是512字节,然后作者又修改了以下程序,加了点东西。

 

 

看到读盘这一块,我突然有点明白了,为啥要从扇区2,开始读盘,第一块扇区的index其实是0,所以1去哪儿了呢?

参考上一篇:30天自制操作系统(二)汇编语言学习与Makefile入门

 

技术分享

JC指令

JC即"jump if carry"的缩写,意思是,如果进位标志(carry flag)是1的话,就跳转。

AH=0x02,INT 0x13是读盘的意思,这个可以查找BIOS的调用可知。

 

Flags标志寄存器

 

 

从软盘的哪儿读数据

 

软盘的磁头有两个Head:磁头0,读正面;磁头1,读反面

一共80个柱面Cylinder:柱面0,柱面1,...,柱面79

一共18个扇区Sector:扇区1,扇区2,...,扇区18(╥﹏╥...),mlgb,为啥不是从0开始,所以上面的解释,不好意思我错了,直接读了启动扇区的下一个扇区。

含IPL的启动区就是C0-H0-S1,那么下一个扇区就是C0-H0-S2,这个扇区就是我们想装载的扇区了。

技术分享

 

用一个寄存器来表示地址的话,例如BX,只能表示0~0xffff的值,也就是0~65535,最大64KB。

为了解决这个问题,首先考虑到的是段寄存器,后来就是想EBX这样的扩展寄存器,可以处理4GB的内存。

使用段寄存器的方式是ES:BX。

 

作者把第二个扇区读到了0x0820,因为0x7c00到~0x7dff用于启动区,0x8000~0x81ff这512字节也是留给启动区的,要将启动区的内容读到这里。

但是为啥又说0x7e00以后直至0x9fbff为止的区域都没有特别的用途。

 

说说为什么会有0x8000这一说:是有些操作系统会把操作系统的代码放到0x8000,这是因为BIOS读完启动扇区以后,会跳转到0x7C00启动,占用0x7C00-0x7DFF这一段(512字节),而一般bootloader还需要一个栈空间或者读磁盘的交换空间,一般是放到0x7E00-0x7FFF这512字节里,所以有些操作系统的镜像起点是0x8000。但是BIOS里读取启动扇区都是加载到0x7C00上的。

 

 

技术分享

 

我一直疑惑一个问题,每个磁道的扇区一样,那为什么每个扇区的字节数还一样多捏?欲知详情,请戳此处。

 

 

 

不管要怎么处理内存看,都必须同时指定段寄存器,如果省略的话,会把"DS:"作为默认的段寄存器。

MOV CX, [1234]的意思,其实是MOV CX, [DS:1234]。

 

因为有这样的规则,所以DS必须预先指定为0,否则地址的值就要加上这个数的16倍,就会读到其他地方。

 

 

2 试错

 

就是读失败了之后就多读几次

 

; hello-os
; TAB=4

        ORG        0x7c00             ; 指明程序的装载地址

; 以下这部分记录的是FAT12格式的软盘

        JMP        entry
        DB         0x90
        DB         "HELLOIPL"         ; 启动扇区的名称可以是任意的字符串 (8字节)
        DW         512                ; 每个扇区(sector)的大小(必须是512字节)
        DB         1                  ; 簇(cluster)的大小 (必须为512字节)
        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字节

; 程序主体

entry:
        MOV        AX,0               ; 初始化寄存器
        MOV        SS,AX
        MOV        SP,0x7c00
        MOV        DS,AX
        
; 新加了一部分读盘

        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            ; AH和INT指令配合使用
        MOV        DL,0x00            ; A驱动器
        INT        0x13               ; 重置驱动器
        JMP        retry

fin:
        HLT                           ; 让CPU停止等待指令
        JMP        fin                ; 无限循环
        
error:
        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

msg:
        DB         0x0a, 0x0a         ; 换行2次
        DB         "load error"
        DB         0x0a               ; 换行
        DB         0
        RESB       0x7dfe-$           ; 填写0x00, 直到0x7dfe
        DB         0x55, 0xaa

 

下面这个就是试错的部分

 

技术分享

JNC指令

JNC是"Jump if not carry"的缩写,也就是进位标志还是0的话就跳转。

JAE也是条件跳转,是"Jump if above or equal"的缩写,意思是大于或等于就跳转。

 

AH=0x00, DL=0x00, INT 0x13是BIOS的"系统复位",是用来复位软盘的状态,再读一次。

3 读到18扇区

突然想到软盘就18个扇区,这不就是都读完么?(我错了,一个柱面就有10个扇区

 

; hello-os
; TAB=4

        ORG        0x7c00             ; 指明程序的装载地址

; 以下这部分记录的是FAT12格式的软盘

        JMP        entry
        DB         0x90
        DB         "HELLOIPL"         ; 启动扇区的名称可以是任意的字符串 (8字节)
        DW         512                ; 每个扇区(sector)的大小(必须是512字节)
        DB         1                  ; 簇(cluster)的大小 (必须为512字节)
        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字节

; 程序主体

entry:
        MOV        AX,0               ; 初始化寄存器
        MOV        SS,AX
        MOV        SP,0x7c00
        MOV        DS,AX

; 新加了一部分读盘

        MOV        AX,0x0820
        MOV        ES,AX
        MOV        CH,0               ; 柱面0
        MOV        DH,0               ; 磁头0
        MOV        CL,2               ; 扇区2
        
        
readloop:
        MOV        SI,0               ; 记录失败次数的计数器
retry:
        MOV        AH,0x02            ; AH=0x02 : 读入磁盘
        MOV        AL,1               ; 1个扇区
        MOV        BX,0
        MOV        DL,0x00            ; A驱动器
        INT        0x13               ; 调用磁盘BIOS
        JNC        next               ; 没出错时跳转到next
        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
next:
        MOV        AX,ES              ; 把内存地址往后移0x200=512字节
        ADD        AX,0x0020
        MOV        ES,AX              ; 因为没有ADD ES, 0x20指令, 所以这里稍微绕个弯
        ADD        CL,1               ; 往CL里加1
        CMP        CL,18              ; CL和18比较
        JBE        readloop           ; CL <= 18 则跳转到readloop

fin:
        HLT                           ; 让CPU停止等待指令
        JMP        fin                ; 无限循环

error:
        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

msg:
        DB         0x0a, 0x0a         ; 换行2次
        DB         "load error"
        DB         0x0a               ; 换行
        DB         0
        RESB       0x7dfe-$           ; 填写0x00, 直到0x7dfe
        DB         0x55, 0xaa

 

起始感觉做的事情也很简单,每读一个扇区,就把软盘加载到内存中的位置往后挪,直到18个扇区全部读完了。

JBE指令

JBE是"jump if below or equal"的缩写,就是小于等于则跳转

 

给ES加上0x20就行,0x20是十六进制下,512除以16的结果,或者ADD AX, 512/16也行,或则往BX里加上512也可以。

 

后来读者又解释了为啥要循环来读的原因:

虽然在调用读盘函数的INT 0x13处,将AL设置为17就行,这样,程序一下子能将扇区2~18共17个扇区的数据完整读出来,但是BIOS读盘有补充说明:指定处理的扇区数,范围在0x01~0xff【指定0x02以上的数值时,要特别注意能够连续处理多个扇区的条件,如果是FD(Floppy Disk),似乎不能跨多个磁道,也不能超过64KB的界限。】

 

 

现在已经将磁盘上的C0-H0-S2到C0-H0-S18的512x17=8704字节的内容,装载到了内存的0x8200~0xa3ff处。

 

4 读入10个柱面

 

; hello-os
; TAB=4

CYLS    EQU        10                 ; 读取的柱面数

        ORG        0x7c00             ; 指明程序的装载地址

; 以下这部分记录的是FAT12格式的软盘

        JMP        entry
        DB         0x90
        DB         "HELLOIPL"         ; 启动扇区的名称可以是任意的字符串 (8字节)
        DW         512                ; 每个扇区(sector)的大小(必须是512字节)
        DB         1                  ; 簇(cluster)的大小 (必须为512字节)
        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字节

; 程序主体

entry:
        MOV        AX,0               ; 初始化寄存器
        MOV        SS,AX
        MOV        SP,0x7c00
        MOV        DS,AX

; 新加了一部分读盘

        MOV        AX,0x0820
        MOV        ES,AX
        MOV        CH,0               ; 柱面0
        MOV        DH,0               ; 磁头0
        MOV        CL,2               ; 扇区2
        
        
readloop:
        MOV        SI,0               ; 记录失败次数的计数器
retry:
        MOV        AH,0x02            ; AH=0x02 : 读入磁盘
        MOV        AL,1               ; 1个扇区
        MOV        BX,0
        MOV        DL,0x00            ; A驱动器
        INT        0x13               ; 调用磁盘BIOS
        JNC        next               ; 没出错时跳转到next
        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
next:
        MOV        AX,ES              ; 把内存地址往后移0x200
        ADD        AX,0x0020
        MOV        ES,AX              ; 因为没有ADD ES, 0x20指令, 所以这里稍微绕个弯
        ADD        CL,1               ; 往CL里加1
        CMP        CL,18              ; CL和18比较
        JBE        readloop           ; CL <= 18 则跳转到readloop
        
        MOV        CL,1
        ADD        DH,1
        CMP        DH,2
        JB        readloop            ; 如果DH<2, 则跳转到readloop
        MOV        DH,0
        ADD        CH,1
        CMP        CH,CYLS
        JB        readloop            ; 如果CH<CYLS, 则跳转到readloop

fin:
        HLT                           ; 让CPU停止等待指令
        JMP        fin                ; 无限循环

error:
        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

msg:
        DB         0x0a, 0x0a         ; 换行2次
        DB         "load error"
        DB         0x0a               ; 换行
        DB         0
        RESB       0x7dfe-$           ; 填写0x00, 直到0x7dfe
        DB         0x55, 0xaa

 

注意前面定义了一个变量,读完这10个柱面之后,最初的10x2x18x512=180KB的内容就都读到内存中了。

 

5 着手开发操作系统

 

这个就是最简单的操作系统了

 

fin:
        HLT
        JMP        fin

我有点疑惑,之前的文件中不都有这个么?

这里作者将将启动区编译成了.img镜像文件,然后将上面两行代码编译成了.sys文件,然后再用工具将.sys文件写入到了.img文件。

最终我们要从启动区执行这个.sys文件,至于.sys还将可以写啥,你懂的?!

 

总结:向一个空的软盘保存文件时:

(1)文件名会写到0x002600的地方

(2)文件的内容会写到0x004200的地方

6 从启动区执行操作系统

现在作者想要执行的是软盘0x004200的代码,但是又说磁盘上的内容装载到了0x8000号地址,老子无语了不是读到了0x8200么,怎么又要跑到了0x8000,启动程序不是在0x7c00的地方装着的么,刚开始没想通。

 

说说为什么会有0x8000这一说:是有些操作系统会把操作系统的代码放到0x8000,这是因为BIOS读完启动扇区以后,会跳转到0x7C00启动,占用0x7C00-0x7DFF这一段(512字节),而一般bootloader还需要一个栈空间或者读磁盘的交换空间,一般是放到0x7E00-0x7FFF这512字节里,所以有些操作系统的镜像起点是0x8000。但是BIOS里读取启动扇区都是加载到0x7C00上的。

 

后来明白了,第二个扇区是从0x8200开始的,所以,磁盘的首地址在0x8000没毛病,真是扎心了,老铁

 

下面的就好说了,那么软盘0x4200的就是内存的0x8000+0x4200=0xc200的位置了。

 

这样的话在我们要执行的那个最简单的操作系统加上一个ORG 0xc200,然后再启动装载程序读完软盘之后,加一个JMP 0xc200不就好了?

7 确认操作系统的执行

为了让它表现,作者弄了一个开机画面,不好意思,都是黑屏的。

这个是读盘结束,然后跳转到"操作系统的一段代码"

        CMP        CH,CYLS
        JB        readloop        ; CH < CYLS 则跳转到readloop

; 已完成读盘,准备运行haribote.sys!

        MOV        [0x0ff0],CH        ; 请注意IPL已经读取了多远
        JMP        0xc200

error:
        MOV        SI,msg

 

不知道为什么要存到0xff0处这个位置?

 

接下来是"操作系统"

 

; haribote-os
; TAB=4

        ORG        0xc200             ; 这个程序要被装载到内存的什么地方呢

        MOV        AL,0x13            ; VGA显卡, 320x200x8位彩色
        MOV        AH,0x00
        INT        0x10
fin:
        HLT
        JMP        fin

 

AL中存放的是VGA的模式。

 

上面这个读盘的只能读10个柱面,同时还想把磁盘装载内容结束的地址告诉.sys文件,所以在JMP 0xc200之前加了一条将CYLS的值写到内存地址0x0ff0中。

 

这就清楚了撒,然后作者让"make run"就行了,系统启动

30天自制操作系统(三)进入32位模式并导入C语言

标签:模式   back   最简   img   条件   跳转   efi   变量   补充   

原文地址:http://www.cnblogs.com/tuhooo/p/7694251.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!