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

news2024/11/18 12:48:31

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

文章目录

  • 📕系统的启动
    • 📖BIOS是什么
    • 📖如何启动
  • 📙部分基础知识和工具的使用
    • 📖各类重要寄存器
    • 📖nasm使用
    • 📖dd使用
    • 📖qemu简单使用
    • 📖Makefile编写
  • 📘启动区的制作
    • 📖完整代码
    • 📖ipl的解析
    • 📖boot_info解析
  • 📗实际效果

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

📕系统的启动

📖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写入到磁盘,所以虽然跳转了,但是没有发生什么。主要看一下没有调用画面前的状态。
在这里插入图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1506027.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《MySQL实战45讲》课程大纲

1MySQL实战45讲-01基础架构:一条SQL查询语句是如何执行的?2MySQL实战45讲-02日志系统:一条SQL更新语句是如何执行的?3MySQL实战45讲-03事务隔离:为什么你改了我还看不见?4MySQL实战45讲-04深入浅出索引&…

vue3 ref获取子组件显示 __v_skip : true 获取不到组件的方法 怎么回事怎么解决

看代码 问题出现了 当我想要获取这个组件上的方法时 为什么获取不到这个组件上的方法呢 原來: __v_skip: true 是 Vue 3 中的一个特殊属性,用于跳过某些组件的渲染。当一个组件被标记为 __v_skip: true 时,Vue 将不会对该组件进行渲染&am…

Springboot——JSR303校验

1. 请求参数的合法性校验 使用基于JSR303的校验框架实现,Springboot提供了JSR-303的支持,它就是spring-boot-starter-validation,他包括了很多的校验规则,只需要在模型中通过注解指定校验规则,在Controller方法上开启校…

map和set(二)——AVL树的简单实现

引入 二叉搜索树有其自身的缺陷,假如往树中 插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。简…

深入了解二叉搜索树:原理、实现与应用

目录 一、介绍二叉搜索树 二、二叉搜索树的基本性质 三、二叉搜索树的实现 四、总结 在计算机科学中,数据结构是构建算法和程序的基础。其中,二叉搜索树(Binary Search Tree,简称 BST)作为一种常见的数据结构&#…

从新手到高手:一站式 SQL Server 学习平台!

介绍:SQL Server是由微软公司开发的关系数据库管理系统(RDBMS),自1989年推出以来,已成为全球主流的数据库之一。以下是对SQL Server的详细介绍: 易用性与可伸缩性:SQL Server以其易用性和良好的…

题目:泡澡(蓝桥OJ 3898)

问题描述: 解题思路: 图解:(以题目样例为例子) 注意点:题目的W是每分钟最大出水量,因此有一分钟的用水量大于出水量则不通过。 补充:差分一般用于对一段区间每个元素加相同值&#x…

arp 代理配置示例

一、应用场景: 当 R1 和 R3 配置静态路由下一跳为接口的时候,让 R2 充当 arp 代理,允许 R1、R3 互访 二、拓朴如下: 三、配置代码: [R1] ip route-static 10.1.23.0 255.255.255.0 GigabitEthernet0/0/0[R2] interf…

Git学习笔记(流程图+示例)

概念 图中左侧为工作区,右侧为版本库。Git 的版本库里存了很多东西,其中最重要的就是暂存区。 • 在创建 Git 版本库时,Git 会为我们自动创建一个唯一的 master 分支,以及指向 master 的一个指 针叫 HEAD。(分支和HEAD…

服务器又被挖矿记录

写在前面 23年11月的时候我写过一篇记录服务器被挖矿的情况,点我查看。当时是在桌面看到了bash进程CPU占用异常发现了服务器被挖矿。 而过了几个月没想到又被攻击,这次比上次攻击手段要更高明点,在这记录下吧。 发现过程 服务器用的是4090…

【数据结构】详解时间复杂度和空间复杂度的计算

一、时间复杂度(执行的次数) 1.1时间复杂度的概念 1.2时间复杂度的表示方法 1.3算法复杂度的几种情况 1.4简单时间复杂度的计算 例一 例二 例三 1.5复杂时间复杂度的计算 例一:未优化冒泡排序时间复杂度 例二:经过优化…

Go语言必知必会100问题-19 浮点数溢出问题

问题呈现 在Go语言中,有两种浮点数类型(虚数除外):float32和float64. 浮点数是用来解决整数不能表示小数的问题。我们需要知道浮点数算术运算是实数算术运算的近似,下面通过例子说明浮点数运算采用近似值的影响以及如…

LeetCode:143.重排链表

143. 重排链表 解题过程 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; …

python 蓝桥杯之并查集

文章目录 总述合并过程查找过程算法实战实战1 总述 并查集(Disjoint-set Union,简称并查集)是一种用来管理元素分组情况的数据结构。它主要用于解决集合的合并与查询问题,通常涉及到以下两种操作: 合并(Uni…

Redis基础篇:初识Redis(认识NoSQL,单机安装Redis,配置Redis自启动,Redis客户端的基本使用)

目录 1.认识NoSQL2.认识Redis3.安装Redis1.单机安装Redis2.配置redis后台启动3.设置redis开机自启 4.Redis客户端1.Redis命令行客户端2.图形化桌面客户端 1.认识NoSQL NoSQL(Not Only SQL)数据库是一种非关系型数据库,它不使用传统的关系型数…

Android14 Handle机制

Handle是进程内部, 线程之间的通信机制. handle主要接受子线程发送的数据, 并用此数据配合主线程更新UI handle可以分发Message对象和Runnable对象到主线程中, 每个handle实例, 都会绑定到创建他的线程中, 它有两个作用,: (1) 安排消息在某个主线程中某个地方执行 (2) 安排…

解放生产力,AI加持你也能做这些事!

去年网上流行一个说法叫一人企业或超级IP。一个人就是一家公司,可以更加专注于自身核心技能。既能对工作拥有更大的自主性和控制力,又能舍弃了传统公司管理等繁琐的事务工作,可以全面释放自己的兴趣和潜力。 这个概念给笔者留下了比较深的印…

开源的python 游戏开发库介绍

本文将为您详细讲解开源的 Python 游戏开发库,以及它们的特点、区别和应用场景。Python 社区提供了多种游戏开发库,这些库可以帮助您在 Python 应用程序中实现游戏逻辑、图形渲染、声音处理等功能。 1. Pygame 特点 - 基于 Python 的游戏开发库。…

第3章 数据链路层(1)

3.1数据链路层的功能 加强物理层传输原始比特流的功能,将可能出差错的物理连接改成逻辑上无差错的数据链路[节点的逻辑通道] 3.1.1 为网络提供服务 (1).无确认的无连接服务 适合通信质量好的有线传输链路(实时通信或误码率较低的通信信道)【例如以太网】(2).有确认的无连接服务…

WIN32部分知识介绍

🌈前言:此篇博客是为下一篇的《贪吃蛇》的做的前戏工作,这篇会讲到贪吃蛇所用到的一些工具以及函数。 首先在讲WIN32的内容时我们想了解一下他的基本概念: Windows 这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外…