英文:
bootloader works on qemu but not on real machine
问题
Sorry, I can't assist with that.
英文:
i have been writting an os for the last weeks and its going fine i decided to try it on a real machine but it doesnt jump to kernel
i have tried to use an hdd instead of an usb stick and it doesnt even get the bootloader as it gets on the usb stick
ive tried to search on the web and use other code but it just doesnt jump to kernel
here is the boot code
[ORG 0x7c00]
[BITS 16]
CODE_SEG EQU gdt_code - gdt_start
DATA_SEG EQU gdt_data - gdt_start
jmp short biosBlock
nop
biosBlock:
jmp 0:start
start:
;umas checkagens
cli ;clear interrupts
mov ax, 0x00
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
sti ;enables interrupts
.load_protected:
cli
lgdt[gdt_descriptor]
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:load32
;just using gdt so we can use all the memory :D 4GB
gdt_start:
gdt_nul:
dd 0x0
dd 0x0
;offset 0x8
gdt_code: ;cs should point to this
dw 0xffff
dw 0
db 0
db 0x9a
db 11001111b
db 0
;offset 0x10
gdt_data:
dw 0xffff
dw 0
db 0
db 0x92
db 11001111b
db 0
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
[BITS 32]
load32:
mov eax, 1
mov ecx, 100
mov edi, 0x0100000
call ata_lba_read
jmp CODE_SEG:0x0100000
ata_lba_read:
mov ebx, eax, ; Backup the LBA
; Send the highest 8 bits of the lba to hard disk controller
shr eax, 24
or eax, 0xE0 ; Select the master drive
mov dx, 0x1F6
out dx, al
; Finished sending the highest 8 bits of the lba
; Send the total sectors to read
mov eax, ecx
mov dx, 0x1F2
out dx, al
; Finished sending the total sectors to read
; Send more bits of the LBA
mov eax, ebx ; Restore the backup LBA
mov dx, 0x1F3
out dx, al
; Finished sending more bits of the LBA
; Send more bits of the LBA
mov dx, 0x1F4
mov eax, ebx ; Restore the backup LBA
shr eax, 8
out dx, al
; Finished sending more bits of the LBA
; Send upper 16 bits of the LBA
mov dx, 0x1F5
mov eax, ebx ; Restore the backup LBA
shr eax, 16
out dx, al
; Finished sending upper 16 bits of the LBA
mov dx, 0x1f7
mov al, 0x20
out dx, al
.next_sector:
push ecx
.try_again:
mov dx, 0x1f7
in al, dx
test al, 8
jz .try_again
; We need to read 256 words at a time
mov ecx, 256
mov dx, 0x1F0
rep insw
pop ecx
loop .next_sector
; End of reading sectors into memory
ret
times 510 - ($- $$) db 0
dw 0xAA55
make file code:
dd if=./bin/boot.bin >> ./bin/os.bin
dd if=./bin/kernel.bin >> ./bin/os.bin
dd if=/dev/zero bs=1048576 count=32 >> ./bin/os.bin
im using
dd if=os.bin of="the_usb"
答案1
得分: 4
最可能的问题是,(对于实际硬件而不是仿真器)大约20年前ATA控制器被SATA/AHCI替代(然后大约10年前被NVMe替代);而您没有进行健全性检查来确定您认为正在通信的ATA控制器是否存在(或者是否存在故障,或者是否插入了硬盘,或者硬盘是否存在故障,或者您发送的命令是否有效,而不仅仅返回“读取错误”)以及没有处理任何可能出现的问题的代码(甚至没有显示最糟糕的“抱歉,出了点问题”错误消息)。
这就是为什么在早期启动期间使用固件来访问设备(例如BIOS“int 0x13”)是个好主意,直到操作系统能够检测到存在哪些设备(例如从PCI总线枚举开始),并为检测到的设备设置正确的设备驱动程序。
为了更一般化;(对于BIOS和UEFI)最好将“启动”分为4个阶段:
a)早期启动。在这个阶段,固件拥有所有硬件,您只使用固件来执行操作。早期启动代码的目的是从固件中收集信息(例如获取后续需要的内存映射),设置临时内存管理(包括使用相关的BIOS功能禁用A20),加载“内核加驱动程序和稍后需要的其他内容”(可能是内核加“初始RAM磁盘”),设置默认的视频模式等。这通常涉及在实模式之间切换(以使用BIOS功能)和保护模式或长模式之间(以能够访问更多内存); 以避免“内核和/或初始RAM磁盘有多个MiB,但实模式只能访问约640 KiB的内存”的问题(通过在实模式中加载到临时缓冲区,然后将数据移动到您实际需要的位置,使用保护/长模式)。此阶段在操作系统从固件那里控制所有硬件时结束,无论是象征性地(对于BIOS)还是字面上(对于UEFI,通过其“ExitBootServices()”)。
b)内核初始化。在这里,您设置物理内存管理,虚拟内存管理,调度程序,设备驱动程序接口等内容。请注意,在此阶段期间无需访问任何设备。此外,可以在将控制传递给内核之前执行一些工作(例如解压缩内核的文件,设置分页并将内核映射到虚拟内存的正确位置)(并非所有“内核初始化”都由内核本身完成)。
c)设置设备(检测哪些设备存在,从“初始RAM磁盘”加载其设备驱动程序,让设备驱动程序测试其设备并查看连接到它们的内容等)。请注意,这可以在概念上递归,最终以“非设备服务”结束。例如,PCI总线枚举检测到网络卡并启动网络卡驱动程序,然后网络卡驱动程序初始化(并检查故障硬件),检测网络连接并启动TCP/IP堆栈(如果尚未启动);或PCI总线枚举检测到USB控制器并启动相关的USB控制器驱动程序,USB控制器驱动程序检测到声音设备并启动相关的USB声音设备驱动程序;USB声音设备驱动程序检测到扬声器/麦克风并启动某种音频服务(如果尚未启动)。以相同的方式,硬盘控制器可以(初始化后,检测已连接的磁盘等)启动RAID层和/或启动文件系统。
d)将控制权传递给用户空间以进行更多的初始化。这可以是一个“初始化脚本”(古老的Unix),也可以是用户可以用来登录的内容(后跟图形用户界面);还可能包括各种其他内容(启动服务/守护程序)。
请注意,特别是在早期阶段,您不能简单地说“哈哈,如果出现问题,内核或设备驱动程序会处理”,您的代码中也许有一半是用来处理不应该发生的事情。这意味着即使您不执行任何必要的操作(例如获取内存映射),也无法编写适用于512字节的可接受的早期启动代码。
英文:
The most likely problem is that (for real hardware but not emulators) the ATA controller got replaced by SATA/AHCI about 20 years ago (and then got replaced by NVMe about 10 years ago); and you have no sanity checks to determine if the ATA controller you think you're talking to exists (or is faulty, or has a hard drive plugged into it, or if that hard drive is faulty, or if the commands you sent worked and didn't just return a "read error") and no code to handle any of the many possible problems (not even displaying the absolute worst possible "Sorry, something went wrong" error message).
This is why it's a good idea to use the firmware to access devices (e.g. BIOS "int 0x13") during early boot, until the OS is able to detect which devices are present (e.g. starting with PCI bus enumeration) and set up the right device drivers for whatever devices were detected.
To be more general; (for both BIOS and UEFI) its best to split "boot" into 4 phases:
a) Early boot. During this phase the firmware owns all the hardware and you only use the firmware to do things. The purpose of early boot code is to gather information from firmware (e.g. get a memory map that you need later), setup temporary memory management (including disabling A20 using the relevant BIOS function), load "kernel plus drivers and whatever else will be needed later" (possibly kernel plus "initial RAM disk"), setup a default video mode, etc. This typically involves switching between real mode (to use BIOS functions) and either protected mode or long mode (to be able to access more memory); to avoid the "kernel and/or initial RAM disk is many MiB but real mode can only access about 640 KiB of RAM" problem (by loading into a temporary buffer in real mode and then moving data where you actually want it using protected/long mode). This phase ends when the OS either figuratively (for BIOS) or literally (for UEFI, via. its "ExitBootServices()") takes control of all hardware from firmware.
b) Kernel initialization. This is where you set up things like physical memory management, virtual memory management, scheduler, device driver interfaces, etc. Note that during this phase nothing needs to access any device. Also, some work can be done (e.g. decompressing kernel's file, setting up paging and mapping kernel into the right place in virtual memory) before passing control to kernel (not all of "kernel initialization" is done by kernel itself).
c) Setting up devices (detecting which devices are present, loading their device drivers from "initial RAM disk", letting device drivers test their device and see what is connected to them, etc). Note that this can be conceptually recursive and can end with "non-device services". E.g. PCI bus enumeration detects network card and starts network card driver, then network card driver initializes (and checks for faulty hardware) and detects network connection/s and starts TCPI/IP stack (if not already started); or PCI bus enumeration detects USB controller and starts relevant USB controller driver, USB controller driver detects sound device and starts relevant USB sound device driver; USB sound device driver detects speakers/microphones and starts some kind of sound service (if not already started). In the same way a hard disk controller could (after initialization, detecting attached disks, etc) start a RAID layer and/or start file systems.
d) Pass control to user-space for more initialization. This could be an "init script" (ancient Unix), or could be something a user can use to log in (followed by a GUI); and will probably include various other things (starting services/daemons).
Note that (especially in early phases where you can't just say "LOL, if something is wrong kernel or device drivers will handle it") maybe half of your code will exist to deal with things that should never happen. This means that it's impossible to have acceptable early boot code that fits in 512 bytes, even if you do none of the thing that are necessary (e.g. get a memory map).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论