Dav*_*vid 4 x86 operating-system real-mode protected-mode bootloader
我刚刚为我的操作系统完成了一个非常简单的引导加载程序,现在我正在尝试切换到保护模式并跳转到内核.
内核存在于第二个扇区(在引导加载程序之后)并且打开.
任何人都可以帮我解决我的代码吗?我添加了评论,以显示我的困惑在哪里.
谢谢.
BITS 16
global start
start:
; initialize bootloader and stack
mov ax, 0x07C0
add ax, 288
mov ss, ax
mov sp, 4096
mov ax, 0x07C0
mov ds, ax
call kernel_load
hlt
kernel_load:
mov si, k_load
call print
mov ax, 0x7C0
mov ds, ax
mov ah, 2
mov al, 1
push word 0x1000
pop es
xor bx, bx
mov cx, 2
mov dx, 0
int 0x13
jnc .kjump
mov si, k_fail
call print
ret
.kjump:
mov si, k_succ
call print
; this is where my confusion starts
; switch to protected mode???
mov eax, cr0
or eax, 1
mov cr0, eax
; jump to kernel?
jmp 0x1000:0
hlt
data:
k_load db "Initializing Kernel...", 10, 0
k_succ db "Kernel loaded successfully!", 10, 0
k_fail db "Kernel failed to load!", 10, 0
print:
mov ah, 0x0E
.printchar:
lodsb
cmp al, 0
je .done
int 0x10
jmp .printchar
.done:
ret
times 510-($-$$) db 0
dw 0xAA55
Run Code Online (Sandbox Code Playgroud)
在尝试进入保护模式之前,您需要设置几件事:
您需要内存中的全局描述符表.它至少需要这些选择器的空间:
在保护模式下,选择器是GDT或LDT的索引.代码和数据描述符告诉CPU在选择器加载该索引时要使用的基址和内存长度.
该LGDT指令设置LTDR.
TSS段告诉CPU您要在哪里存储TSS.最初内置于TSS中的一些功能非常有用,因为如果手动执行上下文切换会更快.但是,它有一点必不可少:它存储堆栈供内核在进程从ring3转换为ring0时使用.内核不能相信呼叫者在所有.它不能假设调用者没有发疯并破坏堆栈指针.当从ring3转换到ring0时,CPU从TSS加载堆栈指针,并在推送代码段和偏移返回地址之前将调用程序堆栈段和偏移量推送到内核堆栈.
该LTR指令使用TSS段加载任务寄存器.
IDT允许CPU查找发生各种事件时要执行的操作.基本目的是异常处理.CPU将异常实现为中断.操作系统必须为所有异常设置处理程序.
该LIDT指令装载IDTR.
硬件中断如下所述.
如果在处理异常时发生异常,则会发生双重故障异常.如果在处理双重故障时发生异常,则CPU会将其转换为关闭主板的关闭消息.当发生这种情况时,典型的主板将重置CPU,BIOS将在其引导启动代码中看到重置是意外的,并且它将重新启动.
硬件设备还提供硬件中断(与前面提到的软件中断相反).当设备需要服务时,会发生硬件中断.
如果您打算支持旧机器,那么您需要使用代码并处理8259中断控制器.
您需要代码来处理中断,保存上下文,确认中断,以及以某种方式调用驱动程序或将工作项排队到某个地方以维护硬件.
中断控制器设置为激活CPU处理中断,当硬件设备断言其中断控制线(在古代系统上),或当MSI中断数据包到达CPU时(在能够并配置为使用MSI的现代系统上) .
如果您想要最大的功能并且需要支持多个处理器,那么您必须......
APIC正如其名称所说:高级可编程中断控制器.
APIC允许对优先级,屏蔽和处理器间通信进行复杂的控制.在这里真正覆盖它太大而复杂.
分页被分解为两级查找.顶级称为页面目录.第二级称为页表.
每页包含1024个32位页面描述符.高20位是该页表条目的物理地址的高20位.较低位包含多个用于权限的标志,并允许操作系统检测内存的使用情况,以便智能地交换/丢弃/保留.
每个页面目录条目描述该内存范围的一个4KB页表的基址.页面目录的每个条目指向一个页面表,该表可以具有最多4MB的内存映射.
页表的每个页面描述符描述4KB范围内存的权限,访问历史和基址.
因此,操作系统必须为页面目录分配至少一个4KB页面,并为每4MB内存提交至少一个4KB页面.请注意,您可能具有稀疏映射,其中存在没有内存的大区域,并且如果您访问它,则会发生页面错误.
你可以使用PGbit来启用分页CR0.该PDBRMSR寄存器告诉CPU页目录的物理地址.
初始化GDT,IDT,TSS(并在内存中分配内核堆栈内存,用户堆栈内存(如果需要)).
在GDT内存的索引1和2处进行GDT代码和数据输入,并将它们设置为零基址,4GB限制,ring0.
将CR0位0置1 PE或保护使能位.
立即跳转到0x10:next-instruction下一条指令可能在链接器中解析到下一行的标签.(你可以将一个远指针推到堆栈上并通过它间接跳远).您需要从基地址中减去(cs << 4),因为跳转目标是相对于您在某个任意基础上组装的段,在实模式中设置cs.
你必须因为CPU做了一堆的权限检查,并建立了在保护模式不同的CPU中的一些内部的东西进入保护模式后,加载所有的段寄存器.
请注意,在该分支目标之后,您突然需要以不同方式开始组装指令.在远程跳转之前,你处于实模式,但是一旦加载了cs,CPU中就会发生很多变化,实际上它改变了解码指令的方式.它假设32位寄存器和地址,地址大小前缀表示它是16位.
在实模式中,反过来说,地址大小或操作数大小前缀告诉它是32位.因此,您需要使用某种汇编程序指令来告诉汇编程序反转这些前缀的用法并更改各种事项以处理32位模式.
显然你需要设置堆栈.在设置LDT,IDT等的描述符地址时,您必须多次处理线性地址.
现在您可以设置页面目录和页面表,加载PBDR.
每个页面目录条目都可以标记为在切换页表时不刷新.通常,内核模式对每个进程都具有相同的映射.
通常,每个进程都有自己的页面目录,并共享内核表.其用户模式分配是针对用户存储器范围的自己的私有页表进行的.
虽然不需要分页,但它可以提供许多非常酷的功能和保护.你可能想要它.
在启用分页并加载PDBR之后,按照每个定义,您完全处于保护模式,并且已经实现了一大块核心代码以在x86体系结构上实现操作系统.