在上一篇《自制 os 极简教程 2:史上最难的 hello world》中,我们已经完成了最基本的环境搭建并实现了从零自制操作系统的 hello world 程序,下面我急速过一遍主要步骤:
急速回顾
;BIOS把启动区加载到内存的该位置
;所以需设置地址偏移量
p mbr vstart=0x7c00
;直接往显存中写数据
mov ax,0xb800 ;这条就是第一条指令
mov gs,ax
mov byte [gs:0x00],'h'
mov byte [gs:0x02],'e'
mov byte [gs:0x04],'l'
mov byte [gs:0x06],'l'
mov byte [gs:0x08],'o'
mov byte [gs:0x0a],' '
mov byte [gs:0x0c],'w'
mov byte [gs:0x0e],'o'
mov byte [gs:0x10],'r'
mov byte [gs:0x12],'l'
mov byte [gs:0x14],'d'
jmp $
;512字节的最后两字节是启动区标识
times 510-($-$$) db 0
db 0x55,0xaa
nasm -o boot boot.s
创建虚拟磁盘映像:
bximage -mode=create -hd=60 -q os.raw
填充第一扇区:dd if=boot of=os.raw bs=512 count=1
bochs -f bochs.properties
然后就该写内核了吧
你很自然地就想到,最最开始的 hello world 流程打通了,之后要做的就是把之前在屏幕上打印 hello world 的代码,替换成操作系统代码就好了呀,好简单。
那我们就把刚刚的 boot.s 里的汇编代码删掉,开始写内存管理,写文件系统,写进程管理,写设备驱动,最后来个死循环的 shell 程序让系统怠速运行,等待用户输入命令,一个麻雀操作系统就此完成!完美!
No No No
你离真正开始写这些内核程序,还差十万八千里呢。因为在此之前,你还需要用程序对一些硬件进行调教,才能让硬件配合我们现在要求越来越多的操作系统。
那我不调教硬件,直接上来就写内核程序不可以吗?当然可以!
这样你就写出了一个,在实模式下运行的、没有分页机制、没有中断机制(键盘鼠标都没用)的 16 位操作系统,香么?(这句话并不严谨,只是想表达这个意思)
所以,还是乖乖学习如何调教 Intel 大佬们设计出的硬件吧,其实本质上就是照着 Intel 手册上的说明,写对应的程序就好了。
GO
开始调教硬件
这部分对于初次接触的人,可以说是非常非常难!所以我先把要做的全部事情列举出来:
打开 A20 地址线
开启分段机制
内存某位置写好段描述符表
加载段描述符表地址到 gdtr 寄存器
将 cr0 寄存器的 pe 位置 1
从实模式切换到保护模式
开启分页机制
在内存某位置写好页目录表和页表
加载页目录表地址到 cr3 寄存器
将 cr0 寄存器的 pg 位置 1
开启中断机制
在内存某位置写好中断描述符表
初始化可编程中断控制器 PIC
加载中断描述符表(idt)地址到 idtr 寄存器
我是不是把你给劝退了?别急呀,睁大眼睛好好看下,有没有发现一个规律?是不是基本都长这个样子
开启 XXX 机制
在内存某位置写好 XXX
加载 XXX 地址到 XXX 寄存器
将 XXX 寄存器的 XXX 位置 1
没错,这个基本上就是好多时候软件和硬件协同配合的一种方式,软件在内存某个位置,按照硬件要求的数据结构写上数据,然后通过专有的寄存器告诉硬件内存的起始地址,然后再通过另一个专有寄存器某一位上是 1 还是 0 来控制该功能关闭还是开启。
按照这个思路看,整个过程就清晰很多啦,接下来我们把整个过程揉碎了看。
这里我们不能按照代码执行顺序,得先看最后一项
从实模式到保护模式
打开 A20 地址线
开启分段机制
内存某位置写好段描述符表
加载段描述符表地址到 gdtr 寄存器
将 cr0 寄存器的 pe 位置 1
从实模式切换到保护模式
开启分页机制
在内存某位置写好页目录表和页表
加载页目录表地址到 cr3 寄存器
将 cr0 寄存器的 pg 位置 1
开启中断机制
在内存某位置写好中断描述符表
初始化可编程中断控制器 PIC
加载中断描述符表(idt)地址到 idtr 寄存器
我这人喜欢直面问题,什么是实模式和保护模式?实模式与保护模式的区别是什么?怎么进入保护模式?我先来简单阐述下这三个问题
什么是实模式和保护模式
Intel 8086 是一个由 Intel 于 1978 年所设计的 16 位微处理器芯片,是 x86 架构的鼻祖。紧接着 Intel 又推出了第一款 32 位的 cpu Intel 80286(很快被淘汰,80386更经典一些),这款 cpu 由于和之前有很多不同的"保护"特性,所以称为保护模式,也是与此同时,之前的 8086 这个 16 位 cpu 才有了实模式的叫法。
所以什么是实模式和保护模式,其实就是 Intel 给自己的处理器特性命的一个名字而已,具体有哪些特性那就是细节问题了,但最起码有一点刚刚已经有所透露,那就是保护模式至少是 32 位的,而实模式是 16 位的(即使一个 32 位的 cpu 也有实模式)
实模式与保护模式的区别是什么
位数:实模式 16 位,保护模式 32 位
地址计算:实模式下的地址是段寄存器地址左移 4 位 + 偏移地址得到物理地址。保护模式下段寄存器存入了段选择子,在段描述符表中寻找段基址,再加上偏移地址得到物理地址(如果开启分页下这里为虚拟地址,还需要再次经过 MMU 转换为最终的物理地址)
寻址空间:这个我觉得是 1 的推论,就是实模式寻址空间是 1M,保护模式是 4G
安全性:保护模式下分了四个特权级(0、1、2、3),有两个是我们熟悉的用户态(3)和内核态(0)。比如全局描述符表的段描述符中就有记录特权级的位,配合硬件机制可以在某些情况对程序做到保护,这种"保护"我们得之后慢慢体会。
怎么进入保护模式
进入保护模式在 CPU 层面特别简单,它只是一个开关,即把 cro 寄存器(机器状态字)的位 0 置为 1 即可。
一段几乎固定的代码即可完成( lmsw 汇编指令即是加载机器状态字的指令)
mov ax, 0x0001
lmsw ax
这很简单,但重点确是进入保护模式之前的那些准备工作。这一步就好比你和你女朋友/男朋友结婚一样,实际操作上来说,就是去民政局领个证,但领证之前的相识、表白、相爱、分手、复合,这些才是关键。
所以,我们接下来看一看,进入保护模式之前,都需要做哪些准备工作。
打开 A20 地址线
打开 A20 地址线
开启分段机制
内存某位置写好段描述符表
加载段描述符表地址到 gdtr 寄存器
将 cr0 寄存器的 pe 位置 1
从实模式切换到保护模式
开启分页机制
在内存某位置写好页目录表和页表
加载页目录表地址到 cr3 寄存器
将 cr0 寄存器的 pg 位置 1
开启中断机制
在内存某位置写好中断描述符表
初始化可编程中断控制器 PIC
加载中断描述符表(idt)地址到 idtr 寄存器
简单理解,这一步就是为了突破地址信号线 20 位的宽度,变成 32 位可用。这是由于 8086 CPU 只有 20 位的地址线,所以如果程序给出 21 位的内存地址数据,那多出的一位就被忽略了,比如如果经过计算得出一个内存地址为 1 0000 00000000 00000000 ,那实际上内存地址相当于 0
当 CPU 到了 32 位时代之后,由于要考虑兼容性,还必须保持一个只能用 20 位地址线的模式,所以如果你不手动开启的话,即使地址线已经有 32 位了,仍然会限制只能使用其中的 20 位。
开启 A20 地址线也很简单,同样也是一段几乎固定的代码即可完成
in al,0x92
or al,0000_0010b
out 0x92,al
cli
开启分段机制
打开 A20 地址线
开启分段机制
内存某位置写好段描述符表
加载段描述符表地址到 gdtr 寄存器
将 cr0 寄存器的 pe 位置 1
从实模式切换到保护模式
开启分页机制
在内存某位置写好页目录表和页表
加载页目录表地址到 cr3 寄存器
将 cr0 寄存器的 pg 位置 1
开启中断机制
在内存某位置写好中断描述符表
初始化可编程中断控制器 PIC
加载中断描述符表(idt)地址到 idtr 寄存器
这个是本文最难理解的地方了!还记得上面说的地址线有 20 位这个事么?不知道你有没有想过,16 位的实模式怎么可能产生 20 位的地址线呢?这又是一个历史遗留问题。
当时的 CPU 只有 16 位的,但已经可以生产出 1 MB 大小的内存了,CPU 的位数没有跟上内存大小的发展,那 16 位的 CPU 如何产生 20 位的地址信号给内存呢?大佬们想出了如下办法。
在实模式下,程序员给出的线性地址并不是最终的物理地址,物理地址是由段基址左移 4 位 + 段内偏移地址(程序员给出的线性地址)计算得出的,所以才勉强可以表示 20 位。
之后当然又是由于兼容性问题,这个别扭的设计一直保留了下来,包括你现在正在用的 CPU (x86 架构的并且在实模式下的)也是如此。
然而保护模式下不一样了
在保护模式下,段基址寄存器中存的数据,被理解为段选择子,根据这个值去我们自己在内存中写好的全局描述符表中,找到对应的段描述符,从中取出段基址。用这个段基址加上偏移地址,得到最终的物理地址(如果开启了分页则还需要分页机制的转换,不过逻辑地址和页表的事以后再说,不冲突,因为我们还没有开启分页,目前就是这么转换的)。
物理地址的计算过程,就这么点区别,原来是段寄存器左移四位,现在是段寄存器里的值是用于在全局描述符表中找到相应的段描述符,进而找到段基址。
但段描述符中可不只有段基址,还有很多其他信息,正是这些信息使得 CPU 和我们程序配合可以玩出很多花样,那接下来我们就看看段描述符的具体结构。
段描述符是存储在全局描述符表中的
全局描述符表是一张表,在内存中也就是个数组,里面是一个个的段描述符紧挨着。
段描述符里面各个位的含义,要是展开讲能说上两三篇博客,这里就不完全展开了,只简单关注重点几项。
段基址:用于计算物理地址用的,上面说过了
段界限:可以设定范围,防止程序访问超出这个范围的地址
TYPE:描述符有很多类型,包括后面要说的中断门、陷阱门、LDT,但这里我们只用到了代码段类型和数据段类型。
按照这个结构,我们通过汇编语言直接在内存的某位置把它写出来(这里的值只是其中一种比较合理的设计方案,不用究其细节)
;这里用一个标识表示全局描述符表的内存起始位置
gdt:
;0描述符(没用)
dd 0x00000000
dd 0x00000000
;1描述符(4GB代码段描述符)
dd 0x0000ffff
dd 0x00cf9800
;2描述符(4GB数据段描述符)
dd 0x0000ffff
dd 0x00cf9200
;3描述符(28Kb的视频段描述符)
dd 0x80000007
dd 0x00c0920b
这里我们拿视频段描述符来分析,提取(拼凑)出段基址的数据,转换为十六进制是 0xb8000。怎么样熟不熟悉,这恰好是显卡黑白模式在内存中的映射的起始地址。忘了的同学还是要回顾一下《计算机启动流程》
这样我们之后再访问内存的时候,就可以指定段选择子是 0x3 << 3 这个值了(全局描述符表中第三个描述符代表的段描述符),然后偏移地址就可以从 0 开始写了啦,这样就很方便,也不用担心越界,如下
;定义一个常量表示视频段选择子
SELECTOR_VIDEO equ 0x0003<<3
;把视频段选择子加载到段寄存器gs
mov eax, SELECTOR_VIDEO
mov gs, ax
;直接往显卡内存上写数据
mov byte [gs:0x00],'h'
mov byte [gs:0x02],'e'
mov byte [gs:0x04],'l'
mov byte [gs:0x06],'l'
mov byte [gs:0x08],'o'
下面我们看进入保护模式后进入内核前的下一个准备工作。
开启内存分页机制
打开 A20 地址线
开启分段机制
内存某位置写好段描述符表
加载段描述符表地址到 gdtr 寄存器
将 cr0 寄存器的 pe 位置 1
从实模式切换到保护模式
开启分页机制
在内存某位置写好页目录表和页表
加载页目录表地址到 cr3 寄存器
将 cr0 寄存器的 pg 位置 1
开启中断机制
在内存某位置写好中断描述符表
初始化可编程中断控制器 PIC
加载中断描述符表(idt)地址到 idtr 寄存器
开启内存仍然很简单,就是把 cr0 寄存器(机器状态字)的 pg 位置 1 即可。
但还是如保护模式一样,最重要的不是开启那一下,而是之前做的准备工作。我们先从什么是分页机制讲起。
什么是分页机制
首先要明确一件事,分段机制和分页机制并不是二选一的关系,分页机制也要建立在分段机制的基础之上。
也就是说,在没有开启分页机制时,由程序员给出的逻辑地址,需要先通过分段机制转换成物理地址。但在开启分页机制后,逻辑地址仍然要先通过分段机制进行转换,只不过转换后不再是最终的物理地址,而是虚拟地址(或者叫线性地址),然后再通过一次分页机制转换,得到最终的物理地址。
分页机制的原理
计算机中有一个硬件,叫 MMU,中文名字叫内存管理单元,有时也叫 PMMU,分页内存管理单元。由这个部件来负责将虚拟地址转换为物理地址。
想让这个分页机制生效,除了开启分页机制开关之外,我们程序员还需要在内存中写入页目录表和页表,这种页表方案叫做二级页表,第一级叫页目录表PDE,第二级叫页表PTE。他们的结构如下。
准备好这些之后,MMU 就可以帮我们进行转换了。首先把线性地址被拆分成
高10位:中间10位:后12位
高 10 位负责在页目录表中找到一个页目录项,这个页目录项的值加上中间 10 位拼接后的地址去页表中去寻找一个页表项,这个页表项的值,再加上后 12 位偏移地址,就是是最终的物理地址。
比如我们的虚拟地址(已经经过了分段机制的转换)是
0xC02509
二进制表示就是
0000000011 0000000010 010100001001
我们看一下它的转换过程
设置页表并开启分页
说了那么多,其实代码就几行搞定了,主要是设计页表的方式,这里我们效仿 linux-0.11 的做法。
当时 linux-0.11 认为,总共可以使用的内存不会超过 16M,也即最大地址空间为 0xFFFFFF。
而按照当前的页目录表和页表这种机制,1 个页目录表最多包含 1024 个页目录项(也就是 1024 个页表),1 个页表最多包含 1024 个页表项(也就是 1024 个页),1 页为 4KB(因为有 12 位偏移地址),因此,16M 的地址空间可以用 1 个页目录表 + 4 个页表搞定。
4(页表数)* 1024(页表项数) * 4KB(一页大小)= 16MB
所以,下面这段代码就是,将页目录表放在内存地址的最开头(0x0)处。之后紧挨着这个页目录表,放置 4 个页表。最终将页目录表和页表填写好数值,来覆盖整个 16MB 的内存。最后,开启分页。
;内存开始 0x000 处设置页目录表
_pg_dir:
.org 0x1000 pg0: ;第1个页表在内存0x1000位置
.org 0x2000 pg1: ;第2个页表在内存0x2000位置
.org 0x3000 pg2: ;第3个页表在内存0x3000位置
.org 0x4000 pg3: ;第4个页表在内存0x4000位置
...
;将4个页目录项填写好
mov dword [_pg_dir], pg0+7
mov dword [_pg_dir+4], pg1+7
mov dword [_pg_dir+8], pg2+7
mov dword [_pg_dir+12], pg3+7
;设置4个页表中所有项的内容
mov ecx, 1000
mov edi, pg3+4092
mov eax, 0xfff007
mov dword [edi],eax
cp:
sub eax,0x1000
sub edi,4
mov dword [edi],eax
cmp eax,0x007
jne cp
;开启分页
;即设置 cr3 寄存器(页目录表基址寄存器)
xor eax,eax
mov cr3,eax
mov eax,cr0
or eax,0x80000000
mov cr0,eax
开启中断机制
打开 A20 地址线
开启分段机制
内存某位置写好段描述符表
加载段描述符表地址到 gdtr 寄存器
将 cr0 寄存器的 pe 位置 1
从实模式切换到保护模式
开启分页机制
在内存某位置写好页目录表和页表
加载页目录表地址到 cr3 寄存器
将 cr0 寄存器的 pg 位置 1
开启中断机制
在内存某位置写好中断描述符表
初始化可编程中断控制器 PIC
加载中断描述符表(idt)地址到 idtr 寄存器
这部分的主体是在 c 语言中实现的(初始化 PIC 用汇编实现更方便),也可以理解为进入内核后(c 语言的 main 方法)才实现的。但由于我认为它同样属于进入内核前干的一些 脏活累活,所以就划到内核前讲啦~
这部分重在理解原理,由于代码会在之后的 C 语言中实现,所以先不用管具体实现哦~
简单说,中断就是,由外部设备经过一个中断控制器给 CPU 一个电信号,或者由软件执行某些指令给 CPU 一个电信号,表示中断来啦。然后 CPU 根据这个电信号所表示的中断号,去由我们提前设置好的中断向量表中寻找中断服务程序,跳转过去执行。
中断的分类
> 外部中断
外部中断通过两个引脚连接到 CPU 上,一个是可屏蔽中断 INTR,一个是不可屏蔽中断 NMI
INTR:硬盘、打印机、网卡等设备发出的中断信号,可通过 eflags 寄存器的 IF 位将所有这些外部设备的中断屏蔽
NMI:电源掉电、内存读写错误、总线奇偶校验错误等灾难性的错误,不可屏蔽,CPU 必须立刻处理
对于可屏蔽中断,Linux 的处理方式是分成 上半部 和 下半部。上半部执行时关闭中断,立刻执行完毕;下半部执行时打开中断,此时如果有其他中断进来,则让给其他中断。
> 内部中断
内部中断可分为软中断和异常,二者均是不可屏蔽的(即不受 eflags 的 IF 位影响)
软中断:就是软件发起的中断,最常见的也是我们之后进行系统调用的,就是 int 8 位立即数,可表示 256 中中断。还有一些不常用的,甚至可以叫做异常,下面简单列出
int3:中断向量号3,调试断点指令
into:中断向量号4,中断溢出指令
bound:中断向量号5,检查数组索引越界指令
ud2:中断向量号6,未定义指令,常用于软件测试中主动发起这个中断
异常:指令执行期间 CPU 内部产生的错误引起,如分母为 0 将发起 6 号中断(异常),未定义的指令发起 6 号中断
Fault(故障):可恢复的错误。发生此中断时,CPU 将机器状态恢复到异常之前的状态,之后调用中断处理程序,结束后返回。常见的如 缺页异常
Trap(陷阱):有意的异常。通常是调试程序中用 int3 指令主动触发。
Abort(终止):不可恢复的异常。直接将此程序从进程表中去掉。
中断号
我们知道一个中断对应着一个中断号(中断向量号),后面我们可以设定可编程中断控制器 PIC 来指定键盘产生什么中断号,鼠标产生什么中断号,等等。这些都是我们自己可以定的。
当然 Intel 爷爷指定了一些出厂就设定好的中断号,不用你管,你也改不了。比如 0 表示除零异常,6表示非法指令,等等。
中断描述符表 IDT
一个中断号来了之后,怎么对应到它要执行哪一段程序呢?就是通过查找中断描述符表 IDT
IDT 与 GDT(全局描述符表)类似,也是由 8 字节长的描述符组成的一个数组。所以我们关心的其实是里面的中断描述符的样子。中断描述符属于门描述符的一种,所以也叫中断门描述符。
通过门描述符可以找到一段程序的入口地址,同时也可以实现特权级的翻越。(比如 linux 的系统调用是通过中断门实现的,从而特权级可以从用户态翻越到内核态)
但不同门描述符的触发条件不同,由于现代操作系统基本不用任务门和调用门(调用门本来是用来进行系统调用的,但 linux 通过中断来实现了),陷阱门只在调试的时候会用到,所以这里我们只关注中断门即可,而中断门的触发只能通过中断信号,具体就是上面说的各种产生中断的类型。
中断门描述符的结构如下(这里顺带写出其他门描述符,不用管)
如何找到中断描述符表呢?你猜的没错,也是正如找全局描述符表,页表等一样,有个专门的 IDTR寄存器 存储它的内存起始地址,有个 lidt
指令负责加载 IDTR。经典做法,我们见过太多次了,就不多说啦,不理解的可以从本系列开头开始看哟。
中断处理过程
上图就表示了整个中断处理的过程。
首先处理器根据中断向量号,在中断描述符表中找到对应的中断描述符,从中断描述符中提取出段选择子和段内偏移地址,之后就按照段机制所要求的那样,转换为最终的物理地址,这个地址就是中断服务程序的内存地址。
中断控制器 8259A 芯片
我们之前说过,外部设备发出中断信号,进入 CPU 的 INT 引脚上。但如果有多个外部设备近乎同时发送中断信号,CPU 先处理哪一个呢?未被处理的中断信号又记录在哪里呢?这时候就需要有个中间的代理设备来负责这个事情。
这个代理设备叫做可编程中断控制器 PIC,其中 8259A 芯片是最常见的一种,我们这里把它的内部结构展示出来。
由于是硬件相关,就不展开细说了, 作为程序员我们必须进行一个非常无聊的步骤,就是要为其进行重新编程,不然 PIC 就默认会将 IRQ0x00 ~ IRQ0x0F 引脚对应在中断号 0x00 ~ 0x0F ,而 0x00 ~ 0x1F 这些中断号是被 Intel 保留作为自己保留的中断号,所以这些中断号不能动。
所以这里我们对其重新编程,将 IRQ0x00 ~ IRQ0x0F 对应到中断号 0x20 ~ 0x2F 上。
mov al,0x11
out 0x20,al
dw 0x00eb,0x00eb
out 0xA0,al
dw 0x00eb,0x00eb
mov al,0x20
out 0x21,al
dw 0x00eb,0x00eb
mov al,0x28
out 0xA1,al
dw 0x00eb,0x00eb
mov al,0x04
out 0x21,al
dw 0x00eb,0x00eb
mov al,0x02
out 0xA1,al
dw 0x00eb,0x00eb
mov al,0x01
out 0x21,al
dw 0x00eb,0x00eb
out 0xA1,al
dw 0x00eb,0x00eb
mov al,0xFF
out 0x21,al
dw 0x00eb,0x00eb
out 0xA1,al
这段给硬件编程的代码,就没必要在这里细考究了,完成这一步之后,我们的 PIC 所能采集到的中断以及映射关系如下。
PIC 请求号 | 中断号 | 用途 |
---|---|---|
IRQ0 | 0x20 | 时钟中断 |
IRQ1 | 0x21 | 键盘中断 |
IRQ2 | 0x22 | 接连从芯片 |
IRQ3 | 0x23 | 串口2 |
IRQ4 | 0x24 | 串口1 |
IRQ5 | 0x25 | 并口2 |
IRQ6 | 0x26 | 软盘驱动器 |
IRQ7 | 0x27 | 并口1 |
IRQ8 | 0x28 | 实时钟中断 |
IRQ9 | 0x29 | 保留 |
IRQ10 | 0x2a | 保留 |
IRQ11 | 0x2b | 保留 |
IRQ12 | 0x2c | 鼠标中断 |
IRQ13 | 0x2d | 数学协处理器 |
IRQ14 | 0x2e | 硬盘中断 |
IRQ15 | 0x2f | 保留 |
具体中断相关的代码,就留在下一章的进入内核之后再说啦,这里先了解原理就好。
万事俱备,进入内核!
进入内核开始实现主要功能之前,我们已经把 各种和硬件打交道的脏活累活 做得差不多了,这些部分基本比较固定,不太会发挥自己的主观思想,顶多也就是某些数据结构的内存位置自己来定,段选择子按照自己方便来分而已。
下面把我们今天完成的列出来!给自己鼓鼓掌吧!
打开 A20 地址线
开启分段机制
内存某位置写好段描述符表
加载段描述符表地址到 gdtr 寄存器
将 cr0 寄存器的 pe 位置 1
从实模式切换到保护模式
开启分页机制
在内存某位置写好页目录表和页表
加载页目录表地址到 cr3 寄存器
将 cr0 寄存器的 pg 位置 1
开启中断机制
在内存某位置写好中断描述符表
初始化可编程中断控制器 PIC
加载中断描述符表(idt)地址到 idtr 寄存器
写在最后
本章的内容真的是非常非常多,涉及的知识点也是非常多、杂、细,包括分段、分页、中断、以及 Intel 遗留下来的实模式与保护模式这个历史。
但这些内容,又在真正的操作系统实现中,占了非常少的一部分,就好比在打地基吧,对了解操作系统的思想其实贡献不大。
所以我把这么多的内容,都放在一个章节来讲了,以便用更多的章节来疏通主流程,因为我们这个教程毕竟不是手把手教你写,所以这些细节的内容,大家理解就好。
如果你看的一头雾水,那就对了,你需要花非常多的时间,才能把这些琐碎的事搞明白。如果这一章你看得明明白白,而且觉得我好多地方说的都不够准确,那建议直接啃大部头的书籍,完全没问题~
所以要是实在不懂,那这章就过了,留个印象就好啦,问题不大。
配套代码地址:
https://gitee.com/sunym1993/flashos_simple_labs
交流群:273392557(本人可能较忙没太多时间经营,大家想加的话可以先加着,相互讨论)
新手推荐书籍:《30天自制操作系统》《操作系统真相还原》《Linux内核设计的艺术》
低并发编程,周一很颓废,周四很硬核