30天自制操作系统——第三天

30天自制操作系统——第三天

今天是该系列的第三天,继续昨天的开发,今天的任务主要有以下几项: 1、制作真正的IPL ;2、读取软盘中的数据放入磁盘当中; 3、从启动区执行操作系统; 4、 开始导入c语言。



一、制作真正的IPL

IPL: 启动程序加载器。一般来说操作系统不会很小的,也就是说512字节完全无法转载整个操作系统,因此,我们会将加载操作系统本身的程序放入启动区当中,因此可以称之为IPL。

本次我们将进一步添加读取磁盘的代码加入到汇编程序当中。在此之前,我们先了解一些基础的知识:

BIOS 的0x13号中断:

BIOS 0x13号中有如下操作:磁盘的读、写、扇区校验、寻道等等:

AH = 0X02 ;读盘
AH = 0X03 ;写盘
AH = 0X04 ;校验
AH = 0X0C ;寻道
AL = 处理对象的上去数(只能同时处理连续的扇区)
CH = 柱面号 & 0XFF
CL =扇区号(0-5位) | (柱面号 & 0x30)>>2
DH =磁头号
DL =驱动器号
ES:BX = 缓冲地址;(校验与寻道时不使用)
返回值:
CF=0 ;没有错误,AH = 0
CF=1 ;有错无,错误号码存入AH中
除此之外用以下代码可以做到复位软盘:
AH=0X00 DL=0X00 int 0x13

在这里插入图片描述

至此,我们开始将软盘的数据写入内存(直接从第二个扇区开始,因为第一个扇区是启动区,之前已经加载过了)
写入到0x8000~0x81ff中。其中本次使用的软盘,柱面有80个(0 ~ 79号)用C表示;磁头有正反两个,用大写H表示;扇区有18个(1 ~ 18)用大写字母S表示。也就是说我们一开始写入的便是C0-H0-S2号。

;上面省略
;程序主主体
    entry:
;将启动区的初始段地址(0000)设置给以及将启动扇区的偏移地址(7c00)进行设置到栈和数据段中
         mov ax,0
         mov ss,ax
         mov sp,0x7c00
         mov ds,ax
         mov es,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 失败次数+1
         cmp si,5 ;si >= 5 时,跳转到error
         JAE error
         ;系统复位,bios里的功能,即复位软盘
         mov ah,0x00 
         mov dl,0x00 ;重置a驱动器
         int 0x13
         jmp retry

    fin: 
         HLT ;cpu停止运行,等待指令,节约资源
         jmp fin ; 无限循环


    error:
         moc si,msg

;程序启动后我们想要显示的内容
    putloop:
         mov al,[si]
         add si,1   ; 给SI加1,循环打印
         cmp al,0
         je fin  ;若数据内存该处的值为零,则该程序终止
         mov ah,0x0e  ; 显示一个文字,调用0x0e号子中断程序
         mov bh,0   ;第0页
         mov bl,11001010B  ; 指定字符颜色
         int 0x10 ; 设置中断程序。10h号中断代表显示字符串,调用bios的中断
         jmp putloop 

    msg:
        DB		0x0a, 0x0a		; 换行两次
		DB		"load error"
		DB      0x0a
		DB      "OS made by GXU yuan"
		DB		0x0a			; 换行
		DB		0

		RESB	0x7dfe-$		; 填写0x00直到0x001fe

		DB		0x55, 0xaa  ;这里标志着有启动程序,因为计算机一般先读最后两字节进行判断是否含启动程序的!!

运行后如下:
在这里插入图片描述如果程序出错才会弹出错误信息!!

二、将剩余区域的全部读完

这里直接给出代码:

hello-os
;TAB=4

cyls equ 10

    ORG 0x7c00 ;启动程序的装载地址一般为0x7c00~0x7dff

    jmp entry
;标准的FAT12格式软盘的必备专用的代码 Stand FAT12 format floppy code,即书写在开头的文件描述系统
		DB		0x90
		DB		"HELLOIPL"		; 启动扇区名称(8字节)
		DW		512				; 每个扇区(sector)大小(必须512字节)
		DB		1				; 簇(cluster)大小(必须为1个扇区)
		DW		1				; FAT起始位置(一般为第一个扇区)
		DB		2				; FAT个数(必须为2)
		DW		224				; 根目录大小(一般为224项)
		DW		2880			; 该磁盘大小(必须为2880扇区1440*1024/512)
		DB		0xf0			; 磁盘类型(必须为0xf0)
		DW		9				; FAT的长度(必须是9扇区)
		DW		18				; 一个磁道(track)有几个扇区(必须为18)
		DW		2				; 磁头数(必须是2)
		DD		0				; 不使用分区,必须是0
		DD		2880			; 重写一次磁盘大小
		DB		0,0,0x29		; 意义不明(固定)
		DD		0xffffffff		; (可能是)卷标号码
		DB		"HARIBOTEOS "	; 磁盘的名称(必须为11字字节,不足填空格)
		DB		"FAT12   "		; 磁盘格式名称(必须8字,不足填空格)
		RESB	18				; 先空出18字节

;程序主主体
    entry:
;将启动区的初始段地址(0000)设置给以及将启动扇区的偏移地址(7c00)进行设置到栈和数据段中
         mov ax,0
         mov ss,ax
         mov sp,0x7c00
         mov ds,ax
         mov es,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 失败次数+1
         cmp si,5 ;si >= 5 时,跳转到error
         JAE error
         ;系统复位,bios里的功能,即复位软盘
         mov ah,0x00 
         mov dl,0x00 ;重置a驱动器
         int 0x13
         jmp retry

    next:
         mov ax,es ;把内存地址后移0x200,因为一个扇区512字节!!! 且段地址算法是先乘以16的,但是这里不需要逻辑上乘16 因此直接加上0x0020
         add ax,0x0020
         mov es,ax 
         add cl,1 
         cmp cl,18  ;cl< = 18则跳转
         jbe readloop
         mov cl,1
         add dh,1 ;dh 是磁头
         cmp dh,2
         jb readloop ;如果dh<2则跳转到readloop
         mov dh,0
         add ch,1 ;ch 是柱面
         cmp ch,CYLS
         jb readloop ;如果ch<cyls,则跳转到readloop


    fin: 
         HLT ;cpu停止运行,等待指令,节约资源
         jmp fin ; 无限循环


    error:
         moc si,msg

;程序启动后我们想要显示的内容
    putloop:
         mov al,[si]
         add si,1   ; 给SI加1,循环打印
         cmp al,0
         je fin  ;若数据内存该处的值为零,则该程序终止
         mov ah,0x0e  ; 显示一个文字,调用0x0e号子中断程序
         mov bh,0   ;第0页
         mov bl,11001010B  ; 指定字符颜色
         int 0x10 ; 设置中断程序。10h号中断代表显示字符串,调用bios的中断
         jmp putloop 

    msg:
        DB		0x0a, 0x0a		; 换行两次
		DB		"load error"
		DB      0x0a
		DB      "OS made by GXU yuan"
		DB		0x0a			; 换行
		DB		0

		RESB	0x7dfe-$		; 填写0x00直到0x001fe

		DB		0x55, 0xaa  ;这里标志着有启动程序,因为计算机一般先读最后两字节进行判断是否含启动程序的!!

这里的CYLS EQU 10 就行相当于给CYLS进行定义,equ即equal的缩写。

经过使用以上代码后,就可以将软盘读取的数据将内存0x08200~0x34fff的地方,至此,当我们打开这个系统时候就会发现速度明显慢了很多。
在这里插入图片描述

三、从启动区执行操作系统

需要建立一个新的汇编语言文件:haribote.nas

haribote.nas:
fin:
   HLT
   jmp fin

然后,我们需要makefile文件,使得我们能够将haribote.nas生成文件harbibote.sys这个文件,并将sys文件存入到haribote.img的磁盘映像当中(其实就是把这个文件存入到软盘中,再把这个软盘进行打包成映像文件):

TOOLPATH = ../../z_tools/
MAKE     = $(TOOLPATH)make.exe -r
NASK     = $(TOOLPATH)nask.exe
EDIMG    = $(TOOLPATH)edimg.exe
IMGTOL   = $(TOOLPATH)imgtol.com
COPY     = copy
DEL      = del

#默认操作

default :
	$(MAKE) img

#文件生成规则

yuan-ipl10-os.bin : yuan-ipl10-os.nas Makefile
	$(NASK)  yuan-ipl10-os.nas yuan-ipl10-os.bin yuan-ipl10-os.list

haribote.sys : haribote.nas Makefile
	$(NASK) haribote.nas haribote.sys haribote.lst

haribote.img : yuan-ipl10-os.bin haribote.sys Makefile
	$(EDIMG)   imgin:../../z_tools/fdimg0at.tek wbinimg src:yuan-ipl10-os.bin len:512 from:0 to:0 copy from:haribote.sys to:@: imgout:haribote.img


#命令

img :
	$(MAKE) haribote.img

run :
	$(MAKE) img
	$(COPY) haribote.img ..\..\z_tools\qemu\fdimage0.bin 
	$(MAKE) -C ../../z_tools/qemu

install :
	$(MAKE) img
	$(IMGTOL) w a: haribote.img


clean :
	-$(DEL) yuan-ipl10-os.bin 
	-$(DEL) yuan-ipl10-os.list
	-$(DEL) haribote.sys 
	-$(DEL) haribote.lst

src_only :
	$(MAKE) clean
	-$(DEL) haribote.img

输入make img 后会生成对应的镜像文件,然后我们打开这个镜像文件进行查看:

  • 发现在0x002600附件会有文件名:
    在这里插入图片描述
  • 发现在0x004200处,存在着具体的文件内容:
    在这里插入图片描述
    至此,我们可以进行如下总结,软盘保存文件时:
  • 文件名写在0x002600以后的地方;
  • 文件内容写在0x004200以后的地方。

知道了上述的规律后,我们就可以将操作系统本身的内容写入到sys这个文件当中,然后利用启动区进行加载,引导计算机读取到sys这个操作系统的文件当中去,就可以运行操作系统了。

因此,我们需要修改一下haribote.nas与之前的ipl10.nas(或者是IPl.nas或者之前我自己改的yuan-hello-os.nas 反正就是读取磁盘的那个汇编文件就对了)

haribote.nas:
    ORG 0xc200 ;该操作系统程序最终被装载到的内存地址
    ;即0x8000+0x4200=0xc200; 0x8000是内存存放软盘的地址,0x4200是软盘存放文件的地方

;设置屏幕模式
    mov al,0x13 ;利用bios中断程序,设置vga显卡,320x200x8位的色彩
    mov ah,0x00 ;第0号子中断程序
    int 0x10
ipl10.nas:(新增内容)
;阅读后,运行yuan_os.sys操作系统
          MOV       [0x0ff0],CH         ; 将cyls的值移动到0x0ff0当中,为了告诉yuan_os.sys磁盘装载内容的结束地址,因为cyls代表了10个柱面
          JMP       0xc200

在这里插入图片描述
以下我解释以下0x10号bios中断:

设置显卡模式:
AH = 0X00;
AL = 模式:
     0x03 : 16字符模式,80x25
     0x12 : VGA 图形模式,640x480x4位彩色模式,独特的4面存储模式
     0x13 : VGA 图形模式,320x200x8位彩色模式,调色板模式
     0x6a : 拓展VGA 图形模式,800x600x4,独特的四面存储模式

此时,我们运行后,就会发现是一片漆黑的画面:
在这里插入图片描述

四、开始导入c语言

由于进入32位模式后,由16位编写的BIOS难以调用,因此我们事先先将可能要用到的BIOS存入内存当中:

haribote.nas:

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
SCRNY   EQU 	0X0ff6  ;分辨率的y
VRAM    EQU 	0X0ff8 ;图像缓冲区开始地址

    ORG 0xc200 ;程序最终被装载到内存的地址
    ;即0x8000+0x4200=0xc2000x8000是内存存放软盘的地址,0x4200是软盘存放文件的地方

;设置屏幕模式
    mov al,0x13 ;利用bios中断程序,设置vga显卡,320x200x8位的色彩
    mov ah,0x00 ;第0号子中断程序
    int 0x10
    mov byte [VMODE],8  ; 屏幕的模式(参考C语言的引用)
    mov word [SCRNX],320
    mov word [SCRNY],200
    mov dword [VRAM],0x000a0000

;用BIOS取得键盘上各种LED指示灯的状态
    mov ah,0x02 
    int 0x16 ;键盘BIOS
    mov [LEDS],al

然后,下面写入进入32位操作系统,并能够使用c语言的汇编(具体含义之后解释,现在先导入):(这串代码就放在上一部分的后面即可!!)

; 防止PIC接受所有中断
;	AT兼容机的规范、PIC初始化
;	然后之前在CLI不做任何事就挂起
;	PIC在同意后初始化

		MOV		AL,0xff
		OUT		0x21,AL
		NOP						; 不断执行OUT指令
		OUT		0xa1,AL

		CLI						; 进一步中断CPU

; 让CPU支持1M以上内存、设置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(禁用分页)
		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:

此时,我们就可以导入c语言了。但是在正式写入之前,先了解一下c文件是怎么机器语言的:

  1. 使用ccl.exe 将.c文件生成.gas;
  2. 使用gas2nask.exe 将.gas 生成 .nas ;
  3. 使用nask.exe 将 .nas 生成 .obj
  4. 使用obi2bim.exe 将.obj 生成 .bim
  5. 使用 bim2hrb.exe 将.bim 生成 .hrb。
  6. 此时已经生成了.hrb的二进制文件,最后再利用copy命令将.bin与.hrb结合起来生成.sys文件,存入到.img中。

ccl.exe是原作者自己开发的基于gcc编译器改造而来的,因为原本gcc是生成的gas的汇编文件,因此还需要使用gas2nask.exe生成.nas的汇编文件。

然后再用nask.exe将该nas汇编文件进行翻译一下,将nas先生成对应的目标文件obj,再将该目标文件与其他文件进行链接,然后生成一个二进制的映像文件,最后再转成hrb这个二进制文件即可。

再了解上述原理后,我们先做一个事情:由于c中没有hlt这个cpu停转的命令,因此本着环保主义的精神,我们自己由汇编来编写一个c的库函数:建立naskfunc.nas

//naskfunc.nas:
; naskfunc
; TAB = 4

[FORMAT "WCOFF"]    ;制作的目标文件的模式,因为要与c文件对接,因此需要生成一个中间的文件
[BITS 32]           ;制作32位的机器语言模式

;制作目标文件的信息
[FILE "naskfunc.nas"]   ;源文件名信息
         
         GLOBAL     _io_hlt     ;程序中包含的函数名,一定要以_开头,为了衔接c语言的函数库

; 以下是实际的函数内容
[SECTION .text]          ;目标文件中写了这些之后再写程序

_io_hlt:   ;void io_hlt(void);
        HLT 
        RET          

然后这里,写下一个主体的c语言文件:bootpack.c

//bootpack.c
void io_hlt(void);

void HariMain(void)
{
	fin:
	    io_hlt();
	    //自建的hlt函数,源目标程序在naskfunc.nas中,c语言本身没有hlt这个命令
	    goto fin;
}

最后,改造一下makefile文件里的内容,使得能够自主的执行链接过程:

TOOLPATH = ../z_tools/
INCPATH  = ../z_tools/haribote/

MAKE     = $(TOOLPATH)make.exe -r
NASK     = $(TOOLPATH)nask.exe
CC1      = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
GAS2NASK = $(TOOLPATH)gas2nask.exe -a
OBJ2BIM  = $(TOOLPATH)obj2bim.exe
BIM2HRB  = $(TOOLPATH)bim2hrb.exe
RULEFILE = $(TOOLPATH)haribote/haribote.rul
EDIMG    = $(TOOLPATH)edimg.exe
IMGTOL   = $(TOOLPATH)imgtol.com
COPY     = copy
DEL      = del

# 默认

default :
	$(MAKE) img

# 具体指令操作

ipl10.bin : ipl10.nas Makefile
	$(NASK) ipl10.nas ipl10.bin ipl10.lst

asmhead.bin : asmhead.nas Makefile
	$(NASK) asmhead.nas asmhead.bin asmhead.lst

bootpack.gas : bootpack.c Makefile
	$(CC1) -o bootpack.gas bootpack.c

bootpack.nas : bootpack.gas Makefile
	$(GAS2NASK) bootpack.gas bootpack.nas

bootpack.obj : bootpack.nas Makefile
	$(NASK) bootpack.nas bootpack.obj bootpack.lst

naskfunc.obj : naskfunc.nas Makefile
	$(NASK) naskfunc.nas naskfunc.obj naskfunc.lst

bootpack.bim : bootpack.obj naskfunc.obj Makefile
	$(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
		bootpack.obj naskfunc.obj
# 3MB+64KB=3136KB

bootpack.hrb : bootpack.bim Makefile
	$(BIM2HRB) bootpack.bim bootpack.hrb 0

haribote.sys : asmhead.bin bootpack.hrb Makefile
	copy /B asmhead.bin+bootpack.hrb haribote.sys

haribote.img : ipl10.bin haribote.sys Makefile
	$(EDIMG)   imgin:../z_tools/fdimg0at.tek \
		wbinimg src:ipl10.bin len:512 from:0 to:0 \
		copy from:haribote.sys to:@: \
		imgout:haribote.img

# 命令

img :
	$(MAKE) haribote.img

run :
	$(MAKE) img
	$(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin
	$(MAKE) -C ../z_tools/qemu

install :
	$(MAKE) img
	$(IMGTOL) w a: haribote.img

clean :
	-$(DEL) *.bin
	-$(DEL) *.lst
	-$(DEL) *.gas
	-$(DEL) *.obj
	-$(DEL) bootpack.nas
	-$(DEL) bootpack.map
	-$(DEL) bootpack.bim
	-$(DEL) bootpack.hrb
	-$(DEL) haribote.sys

src_only :
	$(MAKE) clean
	-$(DEL) haribote.img

最后,运行一下:

在这里插入图片描述

总结

以上就是第三天的内容,今天还是做了很多事情,而且调试+理解(建议还是读一下源代码,并且自己敲一下),感觉就算跟着别人学开发操作系统也是真的任务艰巨,在此对各位祖师爷respect!!!

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值