【自制操作系统】系统启动流程,工具使用和启动区的制作

📝本文介绍
本文主要从系统系统的启动流程开始,中间介绍一些所用工具的使用方法,最后将完成一个启动区的制作。此次的启动区只涉及到汇编代码。
👋作者简介:一个正在积极探索的本科生
📱联系方式:943641266(QQ)
🚪Github地址:https://github.com/sankexilianhua
🔑Gitee地址:https://gitee.com/Java_Ryson
由于本人的知识所限,如果文章有问题,欢迎大家联系并指出,博主会在第一时间修正。


  开始之前,先跟大家说明:本文会涉及到较多地默认设置,也就是从最开始做计算机,做操作系统等等的前辈们留下来的。一些感到疑惑或者博主没有说清楚的地方可以搜索一下,说不定是固定设置或者遗留问题,也可以在评论区提问。

📕系统的启动

📖BIOS是什么

  BIOS是英文"Basic Input Output System"的缩略词,直译过来后中文名称就是"基本输入输出系统"。在IBM PC兼容系统上,是一种业界标准的固件接口。BIOS是个人电脑启动时加载的第一个软件。
  其实,它是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、开机后自检程序和系统自启动程序,它可从CMOS中读写系统设置的具体信息。 其主要功能是为计算机提供最底层的、最直接的硬件设置和控制。此外,BIOS还向作业系统提供一些系统参数。系统硬件的变化是由BIOS隐藏,程序使用BIOS功能而不是直接控制硬件。现代作业系统会忽略BIOS提供的抽象层并直接控制硬件组件。
  其实,计算机开始时,将执行的第一个程序,就可以认为是这个程序。(除了最开始的一个跳转指令)。

📖如何启动

  在讲述如何启动之前,或许需要先讲述我自己对于计算机硬件的理解:硬件是一台巨大的状态机。这里会涉及到一个状态机的概念。状态机实际上就是条件的改变可能会引起状态的改变。学过数逻应该会更加清楚一点。硬件实际上也差不多。根据pc寄存器的值,一条条从内存的地方取出指令,之后译码,去执行,根据所执行的指令,就会改变相应寄存器的状态(值),最终可以实现整个计算机像另一状态的变化。
  这个问题还会涉及到内存布局的问题,在实模式底下,到底内存如何分配呢?这里有篇博客,用来防止照片失效。

在这里插入图片描述
在实模式底下,我们能操作的内存只有1M(为什么大家可以自行搜索)。
  现在,我们正式开始介绍系统启动的4个跳跃。
  第一跳 :系统启动时会先跳转到0xFFFF0这个位置。这里是BIOS程序的入口。
  第二条我们可以发现,这个位置到0xFFFFF只剩下16字节的空间,大概率只能放得下一条指令,所以我们用来跳转,跳到一个更加大的空间,去执行我们需要的任务。这里跳转指令会跳转到0xfe05b。
  第三跳:这里就开始执行一些硬件自检等活动,完成后,**加载(从外存中复制到内存)启动区的程序到0x7c00(规定)**并跳转到该位置。
  第四跳:启动区的程序主要是加载操作系统内核,之后会跳转到内核存储处开始执行。
  更加详细的内容,大家也可以查看这篇博客

📙部分基础知识和工具的使用

📖各类重要寄存器

8位寄存器:

  • AL——累加寄存器低位(accumulator low)
  • CL——计数寄存器低位(counter low)
  • DL——数据寄存器低位(data low)
  • BL——基址寄存器低位(base low)
  • AH——累加寄存器高位(accumulator high)
  • CH——计数寄存器高位(counter high)
  • DH——数据寄存器高位(data high)

16位寄存器:

  • AX——accumulator,累加寄存器
  • CX——counter,计数寄存器
  • DX——data,数据寄存器
  • BX——base,基址寄存器
  • SP——stack pointer,栈指针寄存器
  • BP——base pointer,基址指针寄存器
  • SI——source index,源变址寄存器
  • DI——destination index,目的变址寄存器

32位寄存器是在16位寄存器前添加上e,如eax,ebx,ecx等.64位则变e为r,rax,rcx等。当然64位也有r0,r1,r2等等的写法,具体内容可自行了解。

📖nasm使用

nasm这里就只介绍最简单的用法目前来说足够本文使用。nasm主要是一些参数等的使用,用多了熟悉后会更加顺手。这就不是把一些参数贴上来能解决的。

nasm -o xxx.bin xxx.asm
或
nasm xxx.asm -o xxx.bin

这里的-o指的是将目标文件命名为什么。后面的asm文件就是我们所写的汇编文件。其余的一些参数可自行了解

📖dd使用

dd命令,可以往磁盘中写相应的数据。

dd if=? of=? seek=? bs=? count=?

if指向要输入的文件,of指向要输出的文件。(通俗就是把if的文件写入of)。seek是否要跳过前几个部分。如seek=1,就会跳过512个字节。bs用于指定块大小,默认情况下都为512字节。count指的是处理多少块数据。

📖qemu简单使用

  qemu的用法也很多,但我们这里主要用来模拟x86的64位系统就可以,当然其他系统可以启动应该也行。
  首先要介绍的是创建磁盘映像的功能。

qemu-img create -f xxx name(eg:os.raw) size
eg:qemu-img create -f raw os.raw 1440k

  接下来是使用我们制作好的磁盘映像来启动

qemu-system-x86_64 os.raw
qemu-system-x86_64 -derive file=? if=floppy

如果我们制作的是软盘映像,最好指明是floppy,否则读取磁盘时可能会出现问题(博主就因为这个问题困扰了很久)

📖Makefile编写

  这里涉及到另一个工具,make。 大家可以自行去网络上搜索下载一个,并如同之前一样,将其添加进环境变量。这样之后不论在哪个文件夹下都可以方便使用。这个工具是为了方便使用和规范化整理。
  使用这个工具涉及到了Makefile的书写,这里放上本文使用到的makefile,不够用正规,不过目前足够使用。

ipl.bin: ipl.asm 
	nasm -o ipl.bin ipl.asm
boot_info.bin:boot_info.asm
	nasm -o boot_info.bin boot_info.asm
		
os.raw:	ipl.bin boot_info.bin
	qemu-img create -f raw os.raw 1440k
	dd if=ipl.bin of=os.raw bs=512 count=1 
	dd if=boot_info.bin of=os.raw seek=1 bs=512 count=31 seek=1 

run:
	make -r os.raw
	make clean
	qemu-system-x86_64 -drive file=os.raw,if=floppy,index=0,media=disk,format=raw
clean:
	del ipl.bin
	del boot_info.bin

这样,我们在命令行时就无需每次都手打这些指令,直接make run就可以了,就会主动去执行这些指令。
  那么这个是怎么执行的呢?我们可以看到,run中首先要制作一个os.raw,但所以会去上面寻找os.raw的制作方法,需要什么?冒号后面可以看到,需要ipl.bin boot_info.bin,但目前没有,那就再找。找到这两个,分别需要其asm文件,这两个文件是我们一早就写好的,于是就开始制作两个bin,制作完成后,回来制作os.raw,先创建一个空映像,之后用dd将其写入。之后回来执行,clean,clean里主要是清除一些中间中间文件,让整个文件夹看起来清爽一些。由于是在window下,所以我们使用del命令。linux就使用rm。之后启动qemu就可以了。
  跟着这个不太规范的makefile写个一两次,大致理解之后就可以开始玩自己的makefile了。

📘启动区的制作

📖完整代码

  我们这里先放上一份完整的代码,再逐一去说明
ipl.asm:

;告诉BIOS把启动区加载到内存的该位置。

	ORG 0x7c00

	CYLS equ 10

;调用BIOS 清屏
	mov ax,0x0600
	mov bx,0x700
	mov cx,0
	mov dx,0x184f
	int 0x10

;清屏完后,输出os介绍信息
entry:
	;先设置各种寄存器
	xor ax, ax
	mov ds, ax
	mov es, ax
	mov ss, ax               
	mov sp, 0x9000          
	mov si,msg
print_loop:
	mov al,[si]
	add si,1
	cmp al,0
	
	je read_disk
	mov ah,0x0e
	mov bx,15
	int 0x10
	jmp print_loop
	
read_disk:
	mov ax,0x0820
	mov es,ax
	mov ch,0	;磁道号
	mov cl,2	;扇区号
	mov dh,0	;磁头号
	mov bx,0	;读入内存哪块区域,同时需要看es的值
read_disk_loop:
	mov si,0	;记录失败次数
retry:
	;设置入口参数
	mov ah,0x02 ;设置功能号,0x02标识读,03为写
	mov al,1	;读入扇区数
	mov dl,0	;驱动器号
	int 0x13	;调用磁盘BIOS
	JNC next	;,没出错,就接着读下一个
	;JC 	error	
	mov ah,0x00	;能到这里就说明一定有出错
	mov dl,0x00
	int 0x13	;重置驱动器
	inc si
	cmp si,5
	jbe retry
	jmp error;;出错超过5次就跳转到error
next:
	mov ax,es
	add ax,0x0020 ; 512/16=32 这里寻址是[ES:BX] 所以原本512字节要除16
	mov es,ax
	add cl,1	
	cmp cl,18 	;一个道18个扇区
	jbe read_disk_loop
	mov cl,1	;重置扇区号
	add dh,1	;磁头号,两面
	cmp dh,2
	jb  read_disk_loop
	mov dh,0	;
	add ch,1
	cmp CH,CYLS ;总读取磁道数
	JB	read_disk_loop
	jmp 0x8200
	
fin:
	hlt
	jmp fin

error:
	mov si,error_info
error_loop:
	mov al,[si]
	add si,1
	cmp al,0
	
	je fin
	mov ah,0x0e
	mov bx,15
	int 0x10
	jmp error_loop
	
msg:
	DB	0x0a			;换行
	DB	"hello-os"
	DB	0	 

error_info:
	DB	0x0a			;换行
	DB	"read_disk_error"
	DB	0	 


	resb 510-($-$$);将剩下的空间用0填满
	DB 0x55,0xaa

boot_info.asm:

CYLS equ 0x0ff0
LEDS equ 0x0ff1
VMODE equ 0x0ff2
SCRNX equ 0x0ff4
SCRNY equ 0x0ff6
VRAM equ 0x0ff8

	org 0xc200 ; 这个程序将要被装载到内存的什么地方呢?	
	mov al,0x13 ; VGA显卡,320x200x8位彩色
	mov ah,0x00
	int 0x10
	mov byte[VMODE],8
	mov word[SCRNX],320
	mov word[SCRNY],200
	mov dword [VRAM],0x000a0000
	
	;用BOIS取得键盘上各种LED指示灯的状态
	mov ah,0x02
	int 0x16	;键盘BIOS
	mov [LEDS],al
fin:
	HLT
	JMP fin

📖ipl的解析

  看完完整代码之后,我们再来逐一分析。

	ORG 0x7c00
	CYLS equ 10

ORG是Origin的缩写:起始地址,源。在汇编语言源程序的开始通常都用一条ORG伪指令来实现规定程序的起始地址。如果不用ORG规定则汇编得到的目标程序将从0000H开始。也就是cs:0处。
第二条的CYLS实际上就只是一个常量的定义,equ实际上就是equal,也就是我们把CYLS定义为10这个数。

;调用BIOS 清屏
	mov ax,0x0600
	mov bx,0x700
	mov cx,0
	mov dx,0x184f
	int 0x10

这里和底下的一些具有int的代码片段都大致相同,根据调用BIOS程序的规定,设置好指定的寄存器值后,调用BIOS中断。int就是interrupt。

	;先设置各种寄存器
	xor ax, ax
	mov ds, ax
	mov es, ax
	mov ss, ax               
	mov sp, 0x9000          

这一段,实际上就是在初始化一些寄存器。xor异或,同一个数异或实际上就相当于清零。所以也有使用mov ax,0的写法。mov指令这个就相当于copy吧,或者用c语言的=来理解也可以,因为mov后原寄存器的值不会变化。

	mov si,msg
print_loop:
	mov al,[si]
	add si,1
	cmp al,0
	
	je read_disk
	mov ah,0x0e
	mov bx,15
	int 0x10
	jmp print_loop
	
msg:
	DB	0x0a			;换行
	DB	"hello-os"
	DB	0	 

  这一段就是输出一个字符串了。这里我们把msg拿上来一起看。db就是define byte,dw是define word。而dd 是define double word。使用DB作为数据类型的时候,字符串长度不受限制默认字符串的每一个字符占一个字节,并且存储过程中,是按照一个字符占一个字节的方式,顺序依次存储的。这里讲msg赋值给si,那么si指向的就是该字符串的首地址。之后一个一个输出就可以了。同样的,设置好寄存器后,调用BIOS10号中断程序来处理。
  这里还有另一种方法,就是直接往显存中写数据。可以查看这篇博客,这里不再赘述。

read_disk:
	mov ax,0x0820
	mov es,ax
	mov ch,0	;磁道号
	mov cl,2	;扇区号
	mov dh,0	;磁头号
	mov bx,0	;读入内存哪块区域,同时需要看es的值
read_disk_loop:
	mov si,0	;记录失败次数
retry:
	;设置入口参数
	mov ah,0x02 ;设置功能号,0x02标识读,03为写
	mov al,1	;读入扇区数
	mov dl,0	;驱动器号
	int 0x13	;调用磁盘BIOS
	JNC next	;,没出错,就接着读下一个
	;JC 	error	
	mov ah,0x00	;能到这里就说明一定有出错
	mov dl,0x00
	int 0x13	;重置驱动器
	inc si
	cmp si,5
	jbe retry
	jmp error;;出错超过5次就跳转到error
next:
	mov ax,es
	add ax,0x0020 ; 512/16=32 这里寻址是[ES:BX] 所以原本512字节要除16
	mov es,ax
	add cl,1	
	cmp cl,18 	;一个道18个扇区
	jbe read_disk_loop
	mov cl,1	;重置扇区号
	add dh,1	;磁头号,两面
	cmp dh,2
	jb  read_disk_loop
	mov dh,0	;
	add ch,1
	cmp CH,CYLS ;总读取磁道数
	JB	read_disk_loop
	jmp 0x8200

  好了,到了启动区最重要的功能,读磁盘内容了。读磁盘有两种方式,一种是chs的方法,一种是使用lba地址的方法,两种方法需要设置的寄存器值有一些差别,大家要注意看其对应的要求。chs就是我们使用的物理结构,柱面,磁头,扇区。而lba就不管这个了,把磁盘看成一个大的空间,分成512个字节一小部分,计算时就是(柱面号*磁头数+磁头号)*扇区数+扇区编号-1。
  当然,读磁盘有可能出现各种问题导致某一次失败,所以我们需要让其有重来的机会。我们这里将其设定为5次,5次及之前,出现错误都会重置后跳转到retry继续读取,如果超过五次,就会跳转到error,打印read_disk_error。而next之后实际上就是要继续读取的写法了,1440kb的软盘结构:2面、80道/面、18扇区/道、512字节/扇区。所以才会有后面的那些判断。
  还有一个小问题,就是防止的地方,由于一次读进来一个扇区,512字节,所以每次之后都需要把内存防止位置在加上512字节,由于我们在es上操作,es实际计算地址时是[ES:BX],es需要左移4位也就是×16。(为什么,就是intel的遗留问题了,有兴趣可以搜索看看,没兴趣就算了。)所以512÷16=32,化为16进制就是0x20,所以才会每次给es加上0x20。但es又不能直接进行加减运算,所以只好先拿出来给ax,做完运算再放回去。

fin:
	hlt
	jmp fin

这里就是停止,hlt会让cpu停止,等待下一个操作,而不是一直运行着,浪费资源。

📖boot_info解析

	org 0xc200 ; 这个程序将要被装载到内存的什么地方呢?	
	mov al,0x13 ; VGA显卡,320x200x8位彩色
	mov ah,0x00
	int 0x10
	mov byte[VMODE],8
	mov word[SCRNX],320
	mov word[SCRNY],200
	mov dword [VRAM],0x000a0000

  部分注释已经在上面,里面的一些参数依照如下来设置的:

设置显卡模式(video mode)
AH=0x00;
AL=模式:(省略了一些不重要的画面模式)
0x03:16色字符模式,80 × 25
0x12:VGA 图形模式,640 × 480 × 4位彩色模式,独特的4面存
储模式
0x13:VGA 图形模式,320 × 200 × 8位彩色模式,调色板模式
0x6a:扩展VGA 图形模式,800 × 600 × 4位彩色模式,独特的4
面存储模式(有的显卡不支持这个模式)
返回值:无

所以这实际上也只是一个设置好寄存器调用的问题。

📗实际效果

这里还没有把boot_info写入到磁盘,所以虽然跳转了,但是没有发生什么。主要看一下没有调用画面前的状态。
在这里插入图片描述

调用后,光标应是消失状态。
在这里插入图片描述

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值