我刚刚为我的操作系统完成了一个非常简单的启动程序,现在我试图切换到保护模式并跳转到内核。
内核存在于第二个扇区(紧跟引导加载程序之后)并且开启。
任何人都可以帮我解决我的代码吗?我添加了评论以显示我的困惑。
谢谢。
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
答案 0 :(得分:7)
在尝试进入保护模式之前,您需要设置几项内容:
您需要内存中的全局描述符表。它至少需要这些选择器的空间:
在保护模式下,选择器是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页面。请注意,您可能有稀疏映射,其中存在没有内存的大区域,如果您访问它,则会发生页面错误。
使用PG
位CR0
启用分页功能。 PDBR
MSR寄存器告诉CPU页面目录的物理地址。
初始化GDT,IDT,TSS(并在内存中分配内核堆栈内存,用户堆栈内存(如果需要)。
在GDT内存的索引1和2处搜索GDT代码和数据条目,并将它们设置为零基址,4GB限制,ring0。
将CR0位0,PE
或保护使能位置1。
立即跳转到0x10:next-instruction
,其中next-instruction可能在链接器中解析为下一行的标签。 (你可以将一个远指针推到堆栈上并通过它间接跳远)。您需要从基址减去(cs<<<<<<<<<<<<<<&#4),因为跳转目标是相对于您在某个任意基础上组装的段,在实模式cs
中设置。
在进入保护模式后,必须加载所有段寄存器,因为CPU会执行一系列权限检查,并在CPU中设置多个内部内容,这些内容在保护模式下是不同的。
请注意,在该分支目标之后,您突然需要以不同方式开始汇编指令。在远程跳转之前,你处于实模式,但是一旦加载了cs,CPU中就会发生很多变化,实际上它改变了解码指令的方式。它假设32位寄存器和地址,地址大小前缀表示它是16位。
在实模式下,反过来说,地址大小或操作数大小前缀告诉它是32位。因此,您需要使用某种汇编程序指令来告诉汇编程序反转这些前缀的用法并更改各种事项以处理32位模式。
显然你需要设置堆栈。在设置LDT,IDT等的描述符地址时,您必须多次处理线性地址。
现在您可以设置页面目录和页面表,加载PBDR
。
切换页表时,每个页面目录条目都可以标记为不刷新。通常,内核模式对每个进程都有相同的映射。
通常,每个进程都有自己的页面目录,并共享内核表。其用户模式分配是针对用户内存范围的自己的私有页表进行的。
虽然不需要分页,但它可以提供许多非常酷的功能和保护。你可能想要它。
启用分页并加载PDBR后,按照每个定义,您完全处于保护模式,并且已经实现了一大块核心代码,以在x86架构上实现操作系统。
答案 1 :(得分:0)
遗憾的是,仅通过阅读英特尔/ AMD文档无法解决这个问题。看看他们的修订历史,人们不禁会认为这些年来他们的文档仍然存在错误。