工学1号馆

home

自制OS(3) 进入32位模式并导入C语言

By Wu Yudong on August 26, 2016

计算机启动时会自动加载和执行IPL程序,但IPL程序只能占用512字节。若直接用IPL写OS,空间不够用。所以IPL程序一般用于将真正的OS程序加载到内存某处(记作A),然后跳转到A。这样计算机就可以执行OS的程序了。

本篇通过修改上一篇的IPL,让它真正实现加载OS程序的功能。同时,将IPL程序代码和OS代码放到不同的源代码文件中;用C语言来编写以后的OS代码;用Makefile来编译源代码。

有了本篇的基础,就算是正式开始编写OS源代码了。

1、制作真正的IPL

磁盘的最初512字节是启动区,所以要装载下一个512字节的内容,这次修改ipl.nas内容,添加的内容如下:

		MOV		AX,0x0820
		MOV		ES,AX
		MOV		CH,0			; 柱面0
		MOV		DH,0			; 磁头0
		MOV		CL,2			; 扇区2

		MOV		AH,0x02			; AH=0x02 : 读盘
		MOV		AL,1			; 1个扇区
		MOV		BX,0
		MOV		DL,0x00			; A驱动器
		INT		0x13			; 调用磁盘BIOS
		JC		error

新指令JC(jump if carry):如果进位标志(carry flag)是1的话,就跳转

INT 0x13的意思是BIOS第0x13号函数

相关内容:

AH=0x02(读盘)

AH=0x03(写盘)

AH=0x04(检验)

AH=0x0c(寻道)

AL=处理对象的扇区数;(只能同时处理连续的扇区)

CH=柱面号 &oxff

CL=扇区号(0-5位)|(柱面号&ox300)>>2;

DH=磁头号;

DL=驱动器号;

ES:BX=缓冲地址;(检验及寻道时不使用)

返回值:

FLACS.CF==0:没有错误,AH==0

FLAGS.CF==0:有错误,错误号码存入AH内(与重置(reset)功能一样)


关于软盘的基础知识

QQ截图20160512151959

一张软盘有80个柱面、2个磁头、18个扇区(Cylinder:0~79;Header:0~2;Sector:1~18),1个扇区有512个字节,所以软盘的容量是80*2*18*512=1440KB。

启动IPL的启动区,位于C0-H0-S1(柱面0,磁头0,扇区1的缩写),下一个扇区就是C0-H0-S2

向一个软盘保存文件时,文件名会从0x2600开始往后存,文件的内容会从0x4200开始往后存。


接下来就是缓冲区地址:一个内存地址,将软盘上读取的数据装载到内存的哪个位置。如果使用一个寄存器来表示内存地址的话,BX只能表示0~0xffff的值,最大才64k内存,显然不够

解决办法是增加一个EBX的寄存器,这样就可以处理4G内存。但是在BIOS的年代还没有出现32位的寄存器。

这时候只能采用一个起辅助作用的段寄存器了。于是使用ES:BX这样的方式表示地址

例如写成“MOV AL, [ES:BX]”表示ES*16+BX的内存地址

从上面的代码可知:ES=0x8200, BX=0,也即是软盘的数据被加载到内存的"0x8200~0x83ff"之间,因为"0x8000~0x81ff"这512字节是留给启动区的,启动区的内容读到那里,"0x7c00~0x7dff"用于启动区,ox7e00以后直到ox9fbff为止的区域都没有特别的用途,OS可以随便使用

2、出错时的处理

接着添加下面的代码

; 读磁盘

		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
		MOV		DL,0x00			; A驱动器
		INT		0x13			; 重置驱动器
		JMP		retry

JNC指令:进位标志是0的话就跳转

JAE指令:也是跳转条件,是大于或者等于就跳转

接下来就是错误处理,AH=0x00,DL=0x00,INT=0x13(系统复位),即是复位软盘状态再读一次

3、读到18扇区

继续添加代码:

; 读磁盘

		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,0x020指令,所以稍微绕开
		ADD		CL,1			; CL+1
		CMP		CL,18			; 比较CL与18
		JBE		readloop		; 如果CL <= 18 跳转至readloop

JBE指令(jump if below or equal):意思是小于等于则跳转

要读取下一个扇区,只需要CL+=1,ES+=0x20(即512/16)

CL为扇区号,ES指定读入地址

通过上面的循环,我们将磁盘上的C0-H0-S2~C0-H0-S18共计512*17=8704字节的内容装载到了内存的0x8200~0xa3ff处

4、读入10个柱面

C0-H0-S18扇区的下一个扇区,是磁盘反面的C0-H1-S1,这次从0xa400读入

接着上面的代码,我们继续添加如下代码:

		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

JB指令:如果小于的话就跳转

到目前为止,我们已经把软盘最初的10*2*18*512=180KB内容完整无误地装载到内存

5、着手开发操作系统

接下来编写一个简单的程序,只是让它HLT,代码如下:

fin:
    HLT
    JMP fin

 

 

32位模式前期准备

修改haribote.nas的代码如下:

; haribote-os
; TAB=4

; 有关BOOT_INFO
CYLS	EQU		0x0ff0			; 设定启动区
LEDS	EQU		0x0ff1
VMODE	EQU		0x0ff2			; 关于颜色数目的信息。颜色的位数
SCRNX	EQU		0x0ff4			; 分辨率的X(screen X)
SCRNY	EQU		0x0ff6			; 分辨率的Y(screen Y)
VRAM	EQU		0x0ff8			; 图像缓冲区的开始地址

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

		MOV		AL,0x13			; VGA显卡,320x200x8bit位彩色
		MOV		AH,0x00
		INT		0x10
		MOV		BYTE [VMODE],8	; 记录画面模式
		MOV		WORD [SCRNX],320
		MOV		WORD [SCRNY],200
		MOV		DWORD [VRAM],0x000a0000

; 用BIOS取得键盘上各种LED指示灯的状态

		MOV		AH,0x02
		INT		0x16 			; keyboard BIOS
		MOV		[LEDS],AL

fin:
		HLT
		JMP		fin

6、开始导入C语言

haribote.nas修改名字为asmhead.nas,因为文件的前半部分为汇编,后面为C语言

; haribote-os boot asm
; TAB=4

BOTPAK    EQU        0x00280000        ; bootpackのロード先
DSKCAC    EQU        0x00100000        ; ディスクキャッシュの場所
DSKCAC0    EQU        0x00008000        ; ディスクキャッシュの場所(リアルモード)

; 有关BOOT_INFO
CYLS    EQU        0x0ff0            ; 设定启动区
LEDS    EQU        0x0ff1
VMODE    EQU        0x0ff2            ; 关于颜色数目的信息。颜色的位数。
SCRNX    EQU        0x0ff4            ; 分辨率的X(screen x)
SCRNY    EQU        0x0ff6            ; 分辨率的Y(screen y)
VRAM    EQU        0x0ff8            ; 图像缓冲区的开始地址

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

; 画面モードを設定

        MOV        AL,0x13            ; VGA显卡,320x200x8bit彩色
        MOV        AH,0x00
        INT        0x10
        MOV        BYTE [VMODE],8    ; 记录画面模式
        MOV        WORD [SCRNX],320
        MOV        WORD [SCRNY],200
        MOV        DWORD [VRAM],0x000a0000

; 用BIOS取得键盘上各种LED指示灯的状态

        MOV        AH,0x02
        INT        0x16             ; keyboard BIOS
        MOV        [LEDS],AL

; PICが一切の割り込みを受け付けないようにする
;    AT互換機の仕様では、PICの初期化をするなら、
;    こいつをCLI前にやっておかないと、たまにハングアップする
;    PICの初期化はあとでやる

        MOV        AL,0xff
        OUT        0x21,AL
        NOP                        ; OUT命令を連続させるとうまくいかない機種があるらしいので
        OUT        0xa1,AL

        CLI                        ; さらにCPUレベルでも割り込み禁止

; CPUから1MB以上のメモリにアクセスできるように、A20GATEを設定

        CALL    waitkbdout
        MOV        AL,0xd1
        OUT        0x64,AL
        CALL    waitkbdout
        MOV        AL,0xdf            ; enable A20
        OUT        0x60,AL
        CALL    waitkbdout

; プロテクトモード移行

[INSTRSET "i486p"]                ; 486の命令まで使いたいという記述

        LGDT    [GDTR0]            ; 暫定GDTを設定
        MOV        EAX,CR0
        AND        EAX,0x7fffffff    ; bit31を0にする(ページング禁止のため)
        OR        EAX,0x00000001    ; bit0を1にする(プロテクトモード移行のため)
        MOV        CR0,EAX
        JMP        pipelineflush
pipelineflush:
        MOV        AX,1*8            ;  読み書き可能セグメント32bit
        MOV        DS,AX
        MOV        ES,AX
        MOV        FS,AX
        MOV        GS,AX
        MOV        SS,AX

; bootpackの転送

        MOV        ESI,bootpack    ; 転送元
        MOV        EDI,BOTPAK        ; 転送先
        MOV        ECX,512*1024/4
        CALL    memcpy

; ついでにディスクデータも本来の位置へ転送

; まずはブートセクタから

        MOV        ESI,0x7c00        ; 転送元
        MOV        EDI,DSKCAC        ; 転送先
        MOV        ECX,512/4
        CALL    memcpy

; 残り全部

        MOV        ESI,DSKCAC0+512    ; 転送元
        MOV        EDI,DSKCAC+512    ; 転送先
        MOV        ECX,0
        MOV        CL,BYTE [CYLS]
        IMUL    ECX,512*18*2/4    ; シリンダ数からバイト数/4に変換
        SUB        ECX,512/4        ; IPLの分だけ差し引く
        CALL    memcpy

; asmheadでしなければいけないことは全部し終わったので、
;    あとはbootpackに任せる

; bootpackの起動

        MOV        EBX,BOTPAK
        MOV        ECX,[EBX+16]
        ADD        ECX,3            ; ECX += 3;
        SHR        ECX,2            ; ECX /= 4;
        JZ        skip            ; 転送するべきものがない
        MOV        ESI,[EBX+20]    ; 転送元
        ADD        ESI,EBX
        MOV        EDI,[EBX+12]    ; 転送先
        CALL    memcpy
skip:
        MOV        ESP,[EBX+12]    ; スタック初期値
        JMP        DWORD 2*8:0x0000001b

waitkbdout:
        IN         AL,0x64
        AND         AL,0x02
        JNZ        waitkbdout        ; ANDの結果が0でなければwaitkbdoutへ
        RET

memcpy:
        MOV        EAX,[ESI]
        ADD        ESI,4
        MOV        [EDI],EAX
        ADD        EDI,4
        SUB        ECX,1
        JNZ        memcpy            ; 引き算した結果が0でなければmemcpyへ
        RET
; memcpyはアドレスサイズプリフィクスを入れ忘れなければ、ストリング命令でも書ける

        ALIGNB    16
GDT0:
        RESB    8                ; ヌルセレクタ
        DW        0xffff,0x0000,0x9200,0x00cf    ; 読み書き可能セグメント32bit
        DW        0xffff,0x0000,0x9a28,0x0047    ; 実行可能セグメント32bit(bootpack用)

        DW        0
GDTR0:
        DW        8*3-1
        DD        GDT0

        ALIGNB    16
bootpack:

asmhead.nas

1. 把IPL程序作为一个独立的源文件(ipl10.nas)开发,编译后生成二进制文件(ipl10.bin)。

2. 把OS程序作为若干独立的源文件开发,编译后生成二进制文件(haribote.sys)。haribote.sys就是我们的OS程序。

3. 用二进制的方式把ipl10.bin写入haribote.img(磁盘映像文件,看作一个软盘即可)的第一个扇区(这样,计算机启动时就会自动加载ipl10.bin程序)。

4. 把haribote.sys作为一个文件复制到haribote.img。根据上文的预备知识可知,这个文件的内容会从软盘的0x4200位置开始往后存。

 

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)