该目录下实现了简易的 mbr 与 loader ,Intel 8086 有20条地址线,因此可以访问 1MB 的内存空间,是 0x00000 到 0xFFFFF,为了解 mbr 和 loader 的作用,我们先看看在 intel 8086 中,实模式下的 1MB 内存布局是什么样子
起始 | 结束 | 大小 | 用途 |
---|---|---|---|
FFFF0 | FFFFF | 16B | BIOS 入口地址,此地址属于BIOS 代码,当操作系统刚开始加载时, CPU 默认 CS:IP 值为ffff:0000,通过此部分是16字节的跳转指令, jmp f000:e05b 跳转到入口 |
F0000 | FFFEF | 64KB-16B | 系统BIOS范围是F0000~FFFFF共64KB,最上面16字节为入口地址 |
C8000 | EFFFF | 160KB | 映射硬件适配器的ROM或内存映射式I/O |
C0000 | C7FFF | 32KB | 显示适配器BIOS |
B8000 | BFFFF | 32KB | 用于文本模式显示适配器 |
B0000 | B7FFF | 32KB | 用于黑白显示适配器 |
A0000 | AFFFF | 64KB | 用于彩色显示适配器 |
9FC00 | 9FFFF | 1KB | 扩展BIOS数据区 |
7E00 | 9FBFF | 622080B | 可用区域 |
7C00 | 7DFF | 512B | MBR被BIOS加载到此处,共512字节 |
500 | 7BFF | 30464B | 可用区域 |
400 | 4FF | 256B | BIOS数据区 |
000 | 3FF | 1KB | Interrupt Vector Table(中断向量表) |
因为20位地址线只能映射 1MB 的地址,而这 1MB 映射了 DRAM , ROM 和显卡三部分.
地址 0x00000-0x9FFFF 这部分内存是 DRAM ,也就是插在主板上的内存条(地址总线不完全只能访问电脑中的内存条),这部分的空间范围是 640KB .
中间的 0xA0000-0xEFFFF 这部分主要留给其他一些需要通过地址总线访问的外设,因此我们不能把所有的地址完全映射到 DRAM ,因此提前预留这部分的地址空间给外设用,比如显存,硬盘控制器等,留够以后,地址总线上其他的可用地址在指向 DRAM ,也就是我们指的插到主板上的内存调,我们眼中的物理内存.
顶部的 0xF0000-0xFFFFF 这部分是 ROM ,这里面存放的是 BIOS 的代码, BIOS 主要工作是检测和初始化硬件,怎么初始化的?硬件自己提供了一些初始化的功能调用, BIOS 直接调用就好. BIOS 还建立的中断向量表,这样就可以通过"int 中断号"来实现相关的硬件调用了,当然BIOS建立的这些功能就是对硬件的IO操作,也就是输入输出,但由于就 64KB 大小的空间,不可能把所有硬件的IO操作实现得面面俱到,而且也没必要实现那么多,毕竟是在实模式之下,对硬件支持得再丰富也白搭,精彩的世界是在进入保护模式以后才开始,所以挑一些重要的,保证计算机能运行的那些硬件的基本IO操作,这就是BIOS称为基本输入输出系统的原因.
机器加电时,进入的实模式, CPU 访问 CS*16+EIP 这个地址,CS段寄存器值为0xF000,EIP值为0x0000FFF0,所以机器启动时CPU将访问0xFFFF0(该地址为BIOS入口地址),实模式下1M地址中的0xF0000-0xFFFFF这个内存地址就是ROM,其存储的就是BIOS代码(16字节大小:JMP F000:E05B
),接着CPU执行地址为 0xFE05B 中的指令,而系统 BIOS 范围是 F0000~FFFFF ,此属于BIOS代码,为BIOS程序的真正内容.
因为 BIOS 在 ROM 中,所以不能更改(也没有更改的必要),因此我们只需要知道他做了什么事情就可以了,在上述检测进行完之后,最后一项工作是校验启动盘中位于0盘0道1扇区的内容(即 MBR )
MBR (主引导记录)
-
MBR 只能是 512 字节,而且最后两字节为0x55,0xaa(魔数).
-
MBR 冲虚段的入口地址为0x7c00 ,所以在 MBR 的代码中我们可以看到 SECTION MBR vstart=0x7c00 的设置.
-
将硬盘的0盘0道1扇区填充为 MBR 程序的内容(通过 dd 命令),然后给计算机配置此硬盘为启动盘,这样计算机启动时,就能够自动从BIOS到MBR了.
此操作系统 MBR 中主要做了什么?
通过 ROM 中默认的 BIOS 程序,我们成功进入了 MBR 程序,在 MBR 中我们主要干了下面几件事情.
- 输出字符串 MBR
- 从磁盘的第二扇区读取 loader (这个可以随意放,我选择放在第二扇区),将读取的内容存到 0x900 这个内存地址中,之后当 mbr jmp 到了 0x900 是,就会执行 0x900 这块地址的指令,也就是 loader 中的内容
- 因为按照规定 MBR 的大小必须是 512 字节,而且最后两个字节必须是魔数0x55,0xaa,因此在代码的最后如果不足 512 字节,还需要用类似与 times 510-( − - −$) db 0 这样的指令凑满 512 字节
此操作系统 loader 主要做了什么?
当 mbr 跳转到 loader 之后,就到来 loader 大显身手的时候了,在 loader 程序中我们主要做这几件事情.
- 我们通过上文提到的 BIOS 中断获取整个计算机中的物理内存
- 跳转进入保护模式,因此需要在 loader.S 中定义代码段,数据段,显示段,还要定义页表(我采用二级页表的形式).
- 加载内核
这里牵扯到了新的概念,保护模式是什么,为什么要有保护模式?
为什么要有保护模式?
在实模式下,操作系统和用户程序是属于同一特权级的没有区别,逻辑地址就直接对应物理地址,用户程序是可以通过修改段机制访问到所有地址,很显然这样很不安全,除此之外,在访问内存的时候,需要不断的更换段基址,因为一个段的大小只有 64KB ,内存一共也只有 1M ,并且每次只能运行一个程序,这显然不能适应现在多核的场景,因此就有了带有分段机制的保护模式.
保护模式有什么特点?
- 保护模式赋予不同的进程不同的特权等级,操作系统为最高的 0 级,用户进程为 3 级,将用户资源和操作系统资源隔离,更加安全.
- CPU 和操作系统通过分段机制,根据段描述符(8字节,是描述段的结构,信息包括段基质,段界限,段类型,段是否可读,段的方向(由低到高还是由高到低)等等)
- 在 CPU 发展到 32 位后,地址总线和数据总线扩展到来 32 位,通用寄存器的大小也扩展为 32 位,这样能访问的内存空间编程了 4G ,可以不需要段基址了,不过兼容性依旧保存了段基址+偏移地址的访问方式来访问最终的物理地址,这也就是传说中的平坦模式.这里有个概念需要明确一下,是什么模式以处理器是多少位并没有关系,即使是 32 位系统,在刚开机时都是实模式,只有在经过 loader 的一系列操作之后,才会变为保护模式.
分段机制概述
上文提到了段描述符,一个段描述符保存一个段的信息,有一个专门的数据结构保存着多个段描述符,称为描述符表, 80386/80486 CPU 共有3 种描述符表:全局描述符表( GDT ),局部描述符表( LDT )和中断描述符表( IDT ).描述符表由描述符顺序排列组成,占一定的内存.
段描述符是一个 8 字节 64 位的结构.
在低32位中 0-15 位和32位 16-19 位代表段界限,描述段能达到的边界,具体的边界值要结合 23 位的 G 来看, G 为 1 是,笔叟暗示段界限的粒度为4 KB,G = 0 时,表示段界限的粒度为 1 Byte,实际的段界限 - (段描述符里的段界限 + 1) * 段界限粒度大小 - 1.低 32 位的 16-31 位和高 32 位的 0-7 位及 24-31 位共同描述段基址的 32 位,因为历史遗留原因,为了把段基址扩展到 32 位,把段界限扩展为 20 位,只能继续往后面添加,所以会段界限和段基址会分散在不同的地方.
S 代表一个段是系统段还是数据段,在 CPU 眼里,凡是硬件使用到的东西称为系统,凡是软件使用到的东西称为数据,所以代码段,数据段,栈段等也属于 S 中所代表的的数据段.
Type 指定段的类型,一共四位.只有S决定了,Type才有它的意义.下图是Type在系统段和数据段里不同的意义.
我们主要看一下数据段下Type的意义.当段为代码段时,Type由X、R、C、A组成,分别代表是否可执行、是否可读、是否一致、是否被访问过.当段位数据段时,Type由X、W、E、A组成,分别代表是否可执行、是否可写、扩展方向、是否被访问过.
DPL代表段属于哪一个特权级别.
P代表内存段是否存在,0代表段不存在,1代表段存在.
AVL代表可用的位,操作系统可以随意使用,没有特殊含义.
L代表代码段是64位还是32位.
D/B.对于代码段来说此位是D,用来给代码段指定是使用16位还是32位有效地址和操作数的.对于栈段来说此位是B,用来给栈段指定使用的是sp寄存器还是esp寄存器,sp寄存器的最大寻址范围是0xFFFF,esp寄存器的最大寻址范围是0xFFFFFFFF.
G代表段界限的粒度,是4KB还是1B.
全局描述符表示共用的,多个程序都可以在这个表定义自己的段描述符.我们进入保护模式的其中一个步骤之一就是加载全局描述符表,让CPU知道全局描述符表的位置,在操作内存的时候,CPU就会根据描述符的信息检查这操作是否有效.
A20 地址线
在实模式下,A20地址线是默认禁用的,原因是还未进入保护模式之前,地址总线还是要模拟20位的效果,即只保留20位以内的地址,如果地址超过20位,地址就会回绕到0,将地址20位(从0开始算)舍弃,所以要将A20地址线给禁用掉.但进入保护模式后,我们需要恢复地址总线的原貌,即使地址超过20位,地址也不应该回绕到0,所以此时将A20地址线打开,我们就能访问超过20位的地址了.因此,打开A20地址线,是进入保护模式的步骤之一.
CR0 的 PE 位
进入保护模式的最后一个步骤是,打开CR0的PE位,CR0是控制寄存器.控制寄存器是CPU的窗口,它既可以展示CPU的内部状态,也可以控制CPU的运行机制.CR0的第0位,PE位,就是保护模式的开关,我们打开PE位,就是告诉CPU接下来我们要进入保护模式.
进入保护模式
由上面可以知道,进入保护模式的步骤如下:
1. 打开 A20 地址线
2. 加载 GDT
3. 将 CR0 的 PE 位置为 1
另一个值得注意的指令是:jmp dword SELECTOR_CODE:p_mode_start,这个指令是用来刷新流水线的,因为在进入保护模式之前,p_mode_start后面的指令也会被放上流水线,指令会按照16位译码,其实本来应该按照32位译码才能正常执行,所以我们需要清除流水线上的这些指令,保证这些指令按32位译码,这样才能正常地运行下去.
分页模式
经过了一系列操作,终于实现了分段,操作系统也进入了保护模式,分段模式解决了一些实模式留下的问题,但是分段模式还有一个缺陷没有解决.
- 由于分段模式是以进程为单位分配内存的,本来剩余的内存空间是足以分配给进程的,但由于这些剩余的内存片并不连续,我们就不能分配这些内存给对应的进程了.
因此,为了解决这个问题,就有了分页模式,分页就是通过映射的方式,将连续的线性地址转化为不连续的物理地址;这样,在处理器进入分页模式之后,用户直接访问的并不是物理地址,而是分页模式下的虚拟地址.
注意:分段是分页的基础,段页式的内存布局映射如下
分页实现
为了节省分页的开销,我们采取的每个内存页的大小为 4KB,这样在 32 位下,内存块的数量就是 1MB 左右,但是单纯使用页大小的为 4KB 的一级页表也有一定的问题,因为一个页表项是 4 字节, 1M 个内存块就是 4MB 的开销,这样算下来,一个进程光页表就要占据 4MB 的开销,而且很多时候根本也用不到这么多的内存映射,所以这是一笔很大的内存开销,因此,我们使用二级页表的形式去实现分页功能,另外,目前 Linux 已经发展到了5级页表.
二级页表比一级页表多了一层,我们说这一层为页目录,在二级页表中,我们在一级页表中按照 4MB 为单位,通过 4KB(4*1KB) 的页目录表将 4GB 的内存地址进行映射,每个页目录项指向一个页表,当对应的页目录表项没有使用,则不需要有对应的页表,而且页表之间也不需要连续存储.
-
页目录表表项结构
-
页表表项结构
上图就是页目录项和页表项的格式.可以看出,由于页表或者页的物理地址都是4KB对齐的(低12位全是零,原因是页目录表和页表中对应的页目录项和页表项都是 1024(10位) 个,每项都是 4B(2位) ,所以单个页目录表和页表都是 4KB ),所以上图中只保留了物理基地址的高20位(bit[31:12]).低12位可以安排其他用途.
【P】:存在位.为1表示页表或者页位于内存中.否则,表示不在内存中,必须先予以创建或者从磁盘调入内存后方可使用.
【R/W】:读写标志.为1表示页面可以被读写,为0表示只读.当处理器运行在0、1、2特权级时,此位不起作用.页目录中的这个位对其所映射的所有页面起作用.
【U/S】:用户/超级用户标志.为1时,允许所有特权级别的程序访问;为0时,仅允许特权级为0、1、2的程序访问.页目录中的这个位对其所映射的所有页面起作用.
【PWT】:Page级的Write-Through标志位.为1时使用Write-Through的Cache类型;为0时使用Write-Back的Cache类型.当CR0.CD=1时(Cache被Disable掉),此标志被忽略.对于我们的实验,此位清零.
【PCD】:Page级的Cache Disable标志位.为1时,物理页面是不能被Cache的;为0时允许Cache.当CR0.CD=1时,此标志被忽略.对于我们的实验,此位清零.
【A】:访问位.该位由处理器固件设置,用来指示此表项所指向的页是否已被访问(读或写),一旦置位,处理器从不清这个标志位.这个位可以被操作系统用来监视页的使用频率.
【D】:脏位.该位由处理器固件设置,用来指示此表项所指向的页是否写过数据.
【PS】:Page Size位.为0时,页的大小是4KB;为1时,页的大小是4MB(for normal 32-bit addressing )或者2MB(if extended physical addressing is enabled).
【G】:全局位.如果页是全局的,那么它将在高速缓存中一直保存.当CR4.PGE=1时,可以设置此位为1,指示Page是全局Page,在CR3被更新时,TLB内的全局Page不会被刷新.
【AVL】:被处理器忽略,软件可以使用.
页目录表的基址存在页目录基址寄存器 PDBR (控制寄存器 CR3 )
一个页目录项的32位含义比较丰富,这都是为分页机制,分页算法设计的,为了实现分页基址我们需要注意:
- 实现分页机制,分段是前提
- 需要提前实现好页目录表和页表
- 页目录表的基地址写入控制寄存器 CR3
- 控制寄存器 CR0 的 PG 位设置为1,表示开启分页基址
二级页表的地址映射
- 线性地址,分段得到的地址,再到页表中找到对应的页表项,再到物理地址,映射过程(二级分页)
- 虚拟地址高10位*4,作为页目录表内的偏移地址,加上目录表的物理地址(CR3寄存器含有页目录表基地址),就能得到页目录的物理地址.读取页目录表的内容,可以得到页表的物理地址
- 虚拟地址的中间10位*4,作为页表内的偏移地址,加上步骤1的页表物理地址,将得到页表项的物理地址.读取该页表项的内容,可以得到分配的物理页的地址.
- 虚拟地址高10位和中间10位分别是页目录表和页表的索引值,所以需要乘以4.低12位不是索引值,其范围是0-0xfff,作为页内偏移.步骤2的物理地址加上此偏移,得到最终的物理地址.
在保护模式下,线程是运行的调度的最小单位,进程是资源分配的最小单位,每个进程有自己独立的进程地址空间,因此每个进程都有自己 4G 的虚拟空间,所以每个进程都有会有自己的页目录和页表.
页表总结:
如果使用二级页表,理论上,每次访问内存需要经过三次访问内存才能访问到真正的物理单元.
第一次访问内存是通过段地址寄存器(CR3)中得到页目录表基址,加上虚拟地址高 10位 * 4 的偏移量获取页表基址.
第二次访问内存是根据获取到的页表基址加上虚拟地址中间 10位 * 4 的偏移地址访问得到的页表项地址.
第三次访问内存是虚拟地址最后 12 位的值加上页表基址得到的真正物理地址,然后访问物理地址.
TLB
上面说道每次访问物理地址需要多次访问内存,因为访问内存中的页目录,页表太花时间,比起 CPU 执行指令或者访问寄存器慢一个数量级,因此,目前 Linux 操作系统通过 CPU 缓存( TLB 高速缓存)加快了这个过程,环节了处理器和与内存访问速度之间的不匹配,保存这虚拟地址的高 20 位到物理地址高20位的映射,这样就能加快对内存的访问,不用每次访问内存都要访问页表,因此,每次处理器访问内存时,其实是先访问的 TLB ,所以一定要保证 TLB 的有效性.
因此,每次当页目录和页表数据被改变的时候,我们就要负责维护 TLB 的有效性,更新 TLB ,这里有两种方法,一种是通过 invlpg 指令刷新某个虚拟地址对应的条目,另一种是重新加载页目录,使整个 TLB 失效,进而重新加载 TLB 数据.
进入分页模式
由上文可得,进入分页模式需要三个步骤
- 准备好页目录和页表
- 将页目录的地址加载到 CR3 控制寄存器
- 将 CR0 控制寄存器的 PG 位打开
这段过程主要在 loader.S 中实现,以下为代码部分
[bits 32]
p_mode_start:
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
mov esp,LOADER_STACK_TOP
mov ax, SELECTOR_VIDEO
mov gs, ax
; 创建页目录及页表并初始化页内存位图
call setup_page
;要将描述符表地址及偏移量写入内存gdt_ptr,一会用新地址重新加载
sgdt [gdt_ptr] ; 存储到原来gdt所有的位置
;将gdt描述符中视频段描述符中的段基址+0xc0000000
mov ebx, [gdt_ptr + 2]
or dword [ebx + 0x18 + 4], 0xc0000000 ;视频段是第3个段描述符,每个描述符是8字节,故0x18.
;段描述符的高4字节的最高位是段基址的31~24位
;将gdt的基址加上0xc0000000使其成为内核所在的高地址
add dword [gdt_ptr + 2], 0xc0000000
add esp, 0xc0000000 ; 将栈指针同样映射到内核地址
; 把页目录地址赋给cr3
mov eax, PAGE_DIR_TABLE_POS
mov cr3, eax
; 打开cr0的pg位(第31位)
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
;在开启分页后,用gdt新的地址重新加载
lgdt [gdt_ptr] ; 重新加载
mov byte [gs:160], 'V' ;视频段段基址已经被更新,用字符v表示virtual addr
jmp $
;------------- 创建页目录及页表 ---------------
setup_page:
;先把页目录占用的空间逐字节清0
mov ecx, 4096
mov esi, 0
.clear_page_dir:
mov byte [PAGE_DIR_TABLE_POS + esi], 0
inc esi
loop .clear_page_dir
;开始创建页目录项(PDE)
.create_pde: ; 创建Page Directory Entry
mov eax, PAGE_DIR_TABLE_POS
add eax, 0x1000 ; 此时eax为第一个页表的位置及属性
mov ebx, eax ; 此处为ebx赋值,是为.create_pte做准备,ebx为基址.
; 下面将页目录项0和0xc00都存为第一个页表的地址,
; 一个页表可表示4MB内存,这样0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的页表,
; 这是为将地址映射为内核地址做准备
or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.
mov [PAGE_DIR_TABLE_POS + 0x0], eax ; 第1个目录项,在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性(7)
mov [PAGE_DIR_TABLE_POS + 0xc00], eax ; 一个页表项占用4字节,0xc00表示第769个页表占用的目录项,0xc00以上的目录项用于内核空间,
; 也就是页表的0xc0000000~0xffffffff共计1G属于内核,0x0~0xbfffffff共计3G属于用户进程.
sub eax, 0x1000
mov [PAGE_DIR_TABLE_POS + 4092], eax ; 使最后一个目录项指向页目录表自己的地址
;下面创建页表项(PTE)
mov ecx, 256 ; 1M低端内存 / 每页大小4k = 256
mov esi, 0
mov edx, PG_US_U | PG_RW_W | PG_P ; 属性为7,US=1,RW=1,P=1
.create_pte: ; 创建Page Table Entry
mov [ebx+esi*4],edx ; 此时的ebx已经在上面通过eax赋值为0x101000,也就是第一个页表的地址
add edx,4096
inc esi
loop .create_pte
;创建内核其它页表的PDE
mov eax, PAGE_DIR_TABLE_POS
add eax, 0x2000 ; 此时eax为第二个页表的位置
or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性US,RW和P位都为1
mov ebx, PAGE_DIR_TABLE_POS
mov ecx, 254 ; 范围为第770~1023的所有目录项数量
mov esi, 769
.create_kernel_pde:
mov [ebx+esi*4], eax
inc esi
add eax, 0x1000
loop .create_kernel_pde
ret
setup_page,首先将页目录的4K内存清空位0,避免之前存在的数据指向错误的地方,然后开始创建页目录项,将第1个和第769个页目录项设为第一个页表的地址(这里页目录和页表项从1开始算);将最后一个页目录设为页目录的地址,将第一个页表映射到低1M的物理内存;将第7701023的页目录项设置成第2255个页表的地址,第2~255个页表是紧接着第1个页表之后的.流程图如下:
下面解释一下各个步骤的意义:
-
清空页目录的内存作初始化,防止原来存在的数据指向错误的地方.
-
将第1个和第769个页目录项设置为第一个页表的地址,而后面第一个页表映射到了低1M物理内存,低1M物理内存存储着内核程序.主要是因为打开分页模式之后,首先获得的是虚拟地址,然后将这个虚拟地址转换到最终的物理地址.所以试图访问内核程序的地址已经变成了虚拟地址了,如果最后转换到的物理地址不是原来的物理地址就会出问题.举个例子,假设内核程序在打开分页模式之前,通过地址0x900读写变量A,此时的地址是线性地址,也是物理地址,因为还没打开分页模式;但是打开分页模式之后,我们再想读写这个变量A时,提交的还是0x900,但是这个地址已经变成了虚拟地址了,处理器最终要访问的是物理地址,而变量A的物理地址仍然是0x900,所以需要将虚拟地址0x900映射到物理地址0x900,即一一对应,这样才能保证之前的程序能够正确运行.综上,我们需要将虚拟地址空间的低1M与物理地址的低1M进行一对一映射.将第769个页目录项设置为第一个页表的地址,主要是将虚拟地址空间的高1G内存作为内核程序的空间,以后试图请求内核程序的帮助都会访问高1G内存的空间;而低1M内存也属于内核程序的一部分,所以将0xc000 0000~0xc001 0000也映射到低1M的物理地址.
-
将最后一个页目录项指向页目录.想一想,如果我们高10位索引到了最后一项页目录,那中10位相当于也在索引页目录,那么低12位最终索引的是页表,对不对?所以,将最后一个页目录项指向页目录的作用是对页表进行操作.再细想一下,我们通过什么地址可以对页表进行操作.
a. 我想最终访问页目录表,获得页目录项存储的页表地址.将高10位设置为最后一项页目录的索引,将中10位也设为最后一项页目录的索引,低12为再索引页目录表即可.
b. 我想最终访问页表,获得页表项存储的页地址.将高10位设置为最后一项页目录的索引,将中10位设为某个页目录项的索引,低12位就可以索引页表了.
- 将第7701023的页目录项设置为第2255个页表的地址,按书上的说法是与之后建立用户进程相关,咱不在这里讨论.
调用setup_page之后,将视频段描述符段基址+0xc000 0000,禁止用户进程直接访问显存,只能通过高1G的内核空间去访问显存.将栈指针和GDT也映射到内核地址空间.最后按三部曲打开分页模式.
总结一下:
mbr
被加载到物理地址0x7c00,有BIOS读取磁盘的mbr分区(即磁盘的第一个扇区-512字节)
mbr负责读取磁盘2-4扇区的loader内容,加载在物理内存 可用区域,我们选择了0x9000,mbr结束自己,跳转到loader入口地址
loader
loader建立分段,分页机制等,并读取内核所在的磁盘区域,把内核加载到内存,然后跳转到内核入口处,结束自己.
之后就开始了内核代码的编写了.
参考资料
- 《从实模式到保护模式》
- 《操作系统真相还原》
- https://www.cnblogs.com/thougr/p/12158456.html
- https://www.cnblogs.com/thougr/p/11874962.html
- https://blog.csdn.net/qq_33620667/article/details/60145621
- https://www.cnblogs.com/nullecho/p/10266467.html