How to load and jump to "higher half" kernel?
How to load and jump to "higher half" kernel?
Following an os tutorial I got stuck with user mode - specifically how to establish paging.
Reading the little os book it explains that in order to play nice - the kernel should be placed high enough in order to let user processes use the lower part of virtual address space (0x0000_0000 ..) - as well as even there the kernel should be a bit higher (0xC000_0000 to the full VMA mapping and an additional 0x0010_0000 on the start address of the kernel code - if I understand correctly it's a best practice so it's not messing with the boot code memory space).
This means - the kernel should be loaded to the physical 0x0010_0000 address (or at least around that, 4k aligned) and then after enabling paging the virtual 0xC010_0000 address would be resolved to the physical 0x0010_0000.
This makes sense to me and the osdev page has a section on explaining some implementation ideas.
However I'm still missing a some significant context. My project implements the boot logic so I don't use Grub. This means I start with 16 bit real mode and I have to load the kernel from disk to memory while I have disk interrupts in bios, meaning I can only move the basic kernel code to 16 bit space (so 0x10_0000) is not a chance at this stage. All the tutorials I've found was mentioning a single kernel that is re-mapped when paging is enabled and already placed above the 16bit address space.
Does this mean that if I have a kernel in 16bit space, I need a second kernel (or maybe just call it pre-kernel and main-kernel) at 0x10_0000? And would I need to load that second kernel from disk after switching to protected mode (with 32bit memory addressing)?
Reading the little os book it explains that in order to play nice - the kernel should be placed high enough in order to let user processes use the lower part of virtual address space (0x0000_0000 ..) - as well as even there the kernel should be a bit higher (0xC000_0000 to the full VMA mapping and an additional 0x0010_0000 on the start address of the kernel code - if I understand correctly it's a best practice so it's not messing with the boot code memory space).
This means - the kernel should be loaded to the physical 0x0010_0000 address (or at least around that, 4k aligned) and then after enabling paging the virtual 0xC010_0000 address would be resolved to the physical 0x0010_0000.
This makes sense to me and the osdev page has a section on explaining some implementation ideas.
However I'm still missing a some significant context. My project implements the boot logic so I don't use Grub. This means I start with 16 bit real mode and I have to load the kernel from disk to memory while I have disk interrupts in bios, meaning I can only move the basic kernel code to 16 bit space (so 0x10_0000) is not a chance at this stage. All the tutorials I've found was mentioning a single kernel that is re-mapped when paging is enabled and already placed above the 16bit address space.
Does this mean that if I have a kernel in 16bit space, I need a second kernel (or maybe just call it pre-kernel and main-kernel) at 0x10_0000? And would I need to load that second kernel from disk after switching to protected mode (with 32bit memory addressing)?
Re: How to load and jump to "higher half" kernel?
Why not just move (most of) the kernel to a higher address after loading it and transitioning to protected mode?
Re: How to load and jump to "higher half" kernel?
That could be done indeed. I reckon since the 16bit part is quite limited I would still need the ability to load more. Thanks for confirming about this option.iansjack wrote:Why not just move (most of) the kernel to a higher address after loading it and transitioning to protected mode?
Re: How to load and jump to "higher half" kernel?
Alternatively, use a proper boot loader such as GRUB to do the boring stuff so that you can get on with more interesting aspects of OS design.
Re: How to load and jump to "higher half" kernel?
In your boot loader you could use a read sector function in protmode that temporarily switches to real mode, calls BIOS int to read the sector, then copies the data into its final position (above 16 bit addressing). This way your kernel won't be limited by the 16 bit addressing, because you'd only store one sector below 1M at a time.itarato wrote:That could be done indeed. I reckon since the 16bit part is quite limited I would still need the ability to load more. Thanks for confirming about this option.
This is what I do in my loader (but I read 8 sectors at once, aka. one memory page). Here's the code if you need an example (fasm dialect, MIT licensed).
Cheers,
bzt
Re: How to load and jump to "higher half" kernel?
I was afraid to head that. From your or other folks' experience - using a custom bootloader - how much extra pain will I get apart from the higher half kernel (compare to using GRUB)? My goal is to learn some architecture/CS fundamentals so as far it's a useful hassle I don't mind doing the extra mile.iansjack wrote:Alternatively, use a proper boot loader such as GRUB to do the boring stuff so that you can get on with more interesting aspects of OS design.
Re: How to load and jump to "higher half" kernel?
Interesting idea, much appreciated for the link, reading your code now.bzt wrote:In your boot loader you could use a read sector function in protmode that temporarily switches to real mode, calls BIOS int to read the sector, then copies the data into its final position (above 16 bit addressing). This way your kernel won't be limited by the 16 bit addressing, because you'd only store one sector below 1M at a time.itarato wrote:That could be done indeed. I reckon since the 16bit part is quite limited I would still need the ability to load more. Thanks for confirming about this option.
This is what I do in my loader (but I read 8 sectors at once, aka. one memory page). Here's the code if you need an example (fasm dialect, MIT licensed).
Cheers,
bzt
Re: How to load and jump to "higher half" kernel?
The full scope of a modern OS is vastly out of reach (not even in the sense of out of reach for design & implementation, but more like out of reach even for comprehension) for any individual/small team. For example, at this point MSFT has probably spent well north of 50k men*20years = 1million man years on Windows already, yet it is quite fair to say that there are still plenty of bugs to fix (of course, a good part of that 1million man years have been spent on adding bugs to it and preventing others from adding bugs to it, but hopefully you get the point).itarato wrote:I was afraid to head that. From your or other folks' experience - using a custom bootloader - how much extra pain will I get apart from the higher half kernel (compare to using GRUB)? My goal is to learn some architecture/CS fundamentals so as far it's a useful hassle I don't mind doing the extra mile.iansjack wrote:Alternatively, use a proper boot loader such as GRUB to do the boring stuff so that you can get on with more interesting aspects of OS design.
I don't know about you, but I truly can't comprehend/imagine in a concrete way how would "working on a hobby for well over 1 million years" feel like.
So you probably have to pick your battle. More interested in boot loader and all those really early boot stuff? Maybe focus on that and try to make Linux or even Windows (winload) happy by bootstrapping them, without worrying too much about a kernel or higher half. You can even dig into firmware (like, what did BIOS do before jumping into your boot loader?) after that.
The "high level" problems aren't running away or timing out, and will always be there waiting for you after you're "done" with your boot loader/firmware projects.
-
- Member
- Posts: 5568
- Joined: Mon Mar 25, 2013 7:01 pm
Re: How to load and jump to "higher half" kernel?
You'll learn a lot about how the firmware works, and how the firmware often doesn't work quite how you'd expect. Look forward to frustrating hours troubleshooting bugs that only occur on one piece of hardware. You probably won't learn much about the x86 architecture or CS fundamentals - most of that happens after you escape from the firmware.itarato wrote:From your or other folks' experience - using a custom bootloader - how much extra pain will I get apart from the higher half kernel (compare to using GRUB)? My goal is to learn some architecture/CS fundamentals so as far it's a useful hassle I don't mind doing the extra mile.
Re: How to load and jump to "higher half" kernel?
Most of the work on Windows has gone into useless stuff & trying to obscure & monopolize the OS market. Additionally, large teams are not as productive as single individuals.xeyes wrote: The full scope of a modern OS is vastly out of reach (not even in the sense of out of reach for design & implementation, but more like out of reach even for comprehension) for any individual/small team. For example, at this point MSFT has probably spent well north of 50k men*20years = 1million man years on Windows already, yet it is quite fair to say that there are still plenty of bugs to fix (of course, a good part of that 1million man years have been spent on adding bugs to it and preventing others from adding bugs to it, but hopefully you get the point).
I don't know about you, but I truly can't comprehend/imagine in a concrete way how would "working on a hobby for well over 1 million years" feel like.
I think even today 10 man years is enough to create a decent OS.
Re: How to load and jump to "higher half" kernel?
Back to the topic at hand.
Yes, this can be optimized by loading multiple sectors at once. For copying to high memory, there is a BIOS function to do this for you:
If the kernel grows beyond 15 MB, you probably have other problems. But in that case, the gdt.dst field will overflow into the access rights field, and remove the "writable" bit, so that probably won't work. And the base address will overflow to zero. Also not great.
If you want to save on data memory, you can also try to implement this yourself. You can enter protected mode, load a large data segment, perform the copy, load a small data segment, and return to real mode. In most cases, you can even just leave the large data segment loaded, but will have to reload it every time you enter protected mode, since BIOS might have overwritten it. So far, I have not heard of 386+ BIOSes breaking in unreal mode, but you never know. But doing it yourself allows you to construct your own GDT, which allows you to add whatever segments you might need for the kernel (at least 32-bit kernel code and data, I should think).
No, you just load the kernel into low memory, then copy it to high memory. What you want to do is run the BIOS functions to load the sectors containing your kernel, then copy them to the buffer at 1MB. Fundamentally:itarato wrote:Does this mean that if I have a kernel in 16bit space, I need a second kernel (or maybe just call it pre-kernel and main-kernel) at 0x10_0000? And would I need to load that second kernel from disk after switching to protected mode (with 32bit memory addressing)?
Code: Select all
kernel_addr = 1 << 20
For each kernel sector:
load sector to 0x8000
copy sector from 0x8000 to kernel_addr
kernel_addr += sector_size
Code: Select all
gdt: dq 0, 0 ; needed by BIOS
dw 511 ; sector_size - 1
db 0, 0x80, 0 ; source address as 24-bit number
db 0x93 ; access rights
dw 0
dw 511 ; sector_size - 1
.dst: db 0, 0, 0x10 ; destination address
db 0x93
dw 0
dq 0, 0 ; needed by BIOS
[...]
copy_sector:
; assumes ES==CS
mov si, gdt
mov ah, 0x87
mov cx, 256; sector_size / 2
int 0x15
jc error
add dword [gdt.dst], sector_size ; assumes kernel will never be bigger than 15MB
ret
If you want to save on data memory, you can also try to implement this yourself. You can enter protected mode, load a large data segment, perform the copy, load a small data segment, and return to real mode. In most cases, you can even just leave the large data segment loaded, but will have to reload it every time you enter protected mode, since BIOS might have overwritten it. So far, I have not heard of 386+ BIOSes breaking in unreal mode, but you never know. But doing it yourself allows you to construct your own GDT, which allows you to add whatever segments you might need for the kernel (at least 32-bit kernel code and data, I should think).
Carpe diem!
Re: How to load and jump to "higher half" kernel?
I don't think unreal mode has much of an advantage. I read the data to below 1MB with BIOS and then call a MoveData(source, dest, size) procedure that enters protected mode, does the copy anywhere in 32-bit memory space, and then goes back to real mode. This code is about 50 lines of x86 assembly, so I see no reason to use unreal mode as you still must make sure that the limits are not damaged after calling BIOS.
Code: Select all
source_sel EQU 8
dest_sel EQU 10h
flat_sel EQU 18h
LoadGdt:
load_gdt0:
DW 27h
DD 0
DW 0
load_gdt_source:
DW 0FFFFh
DD 92000000h
DW 0
load_gdt_dest:
DW 0FFFFh
DD 92300000h
DW 0
load_gdt_flat:
DW 0FFFFh
DD 92000000h
DW 008Fh
load_gdt_cs:
DW 0FFFFh
DD 9A000000h
DW 0
InitGdt Proc near
mov ax,cs
movzx eax,ax
shl eax,4
add eax,OFFSET LoadGdt
mov dword ptr cs:load_gdt0+2,eax
lgdt fword ptr cs:load_gdt0
;
mov ax,cs
movzx eax,ax
shl eax,4
or dword ptr cs:load_gdt_cs+2,eax
ret
InitGdt Endp
; PARAMETERS: ESI Linear source address
; EDI Linear dest address
; ECX Number of bytes to move
MoveData Proc near
push ds
push es
pushad
;
mov eax,esi
mov dword ptr cs:load_gdt_source+2,eax
mov al,92h
xchg al,byte ptr cs:load_gdt_source+5
mov byte ptr cs:load_gdt_source+7,al
;
mov eax,edi
mov dword ptr cs:load_gdt_dest+2,eax
mov al,92h
xchg al,byte ptr cs:load_gdt_dest+5
mov byte ptr cs:load_gdt_dest+7,al
mov word ptr cs:MoveDataRmCs,cs
;
cli
mov eax,cr0
or al,1
mov cr0,eax
;
db 0EAh
dw OFFSET MoveDataPm
dw 20h
MoveDataPm:
mov ax,source_sel
mov ds,ax
mov ax,dest_sel
mov es,ax
xor esi,esi
xor edi,edi
rep movs byte ptr es:[edi],[esi]
;
mov eax,cr0
and al,NOT 1
mov cr0,eax
;
db 0EAh
dw OFFSET MoveDataRm
MoveDataRmCs:
dw 0
MoveDataRm:
sti
popad
pop es
pop ds
ret
MoveData Endp
Re: How to load and jump to "higher half" kernel?
@xeyes - your point on the invested time on high end OS' is quite eye opening
@nullplan, @rdos - thanks heaps for the directions. I think I see a pattern now. This will be my next attempt to implement. I definitely want to give it a go as it seems feasible and after that maybe give a try to just using GRUB
Super appreciate all the help!!!
@nullplan, @rdos - thanks heaps for the directions. I think I see a pattern now. This will be my next attempt to implement. I definitely want to give it a go as it seems feasible and after that maybe give a try to just using GRUB
Super appreciate all the help!!!
Re: How to load and jump to "higher half" kernel?
Just to be clear, GRUB does not support "higher half" kernels, you'd need a small trampoline code in your kernel that sets up paging and jumps to higher half.itarato wrote:maybe give a try to just using GRUB
To be precise, paging is entirely out of the scope of Multiboot, so GRUB does not enable paging at all for 32 bit prot mode, and it only uses identity mappings for 64 bit long mode. That's one of the main reasons why I've created my own boot loader which can load 64 bit higher half kernels out-of-the-box in the spirit of the K.I.S.S. principle (no trampoline code needed, the kernel contains 64 bit mode code only and _start label has a higher half address).
Cheers,
bzt