Page 1 of 1

How to load and jump to "higher half" kernel?

Posted: Mon Dec 28, 2020 2:19 pm
by itarato
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)?

Re: How to load and jump to "higher half" kernel?

Posted: Mon Dec 28, 2020 2:24 pm
by iansjack
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?

Posted: Mon Dec 28, 2020 2:53 pm
by itarato
iansjack wrote:Why not just move (most of) the kernel to a higher address after loading it and transitioning to protected mode?
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.

Re: How to load and jump to "higher half" kernel?

Posted: Mon Dec 28, 2020 3:43 pm
by iansjack
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?

Posted: Mon Dec 28, 2020 4:07 pm
by bzt
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.
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.

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?

Posted: Mon Dec 28, 2020 4:09 pm
by itarato
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 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.

Re: How to load and jump to "higher half" kernel?

Posted: Mon Dec 28, 2020 4:15 pm
by itarato
bzt wrote:
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.
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.

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
Interesting idea, much appreciated for the link, reading your code now.

Re: How to load and jump to "higher half" kernel?

Posted: Mon Dec 28, 2020 8:01 pm
by xeyes
itarato wrote:
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 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.
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.

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.

Re: How to load and jump to "higher half" kernel?

Posted: Mon Dec 28, 2020 9:18 pm
by Octocontrabass
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.
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.

Re: How to load and jump to "higher half" kernel?

Posted: Tue Dec 29, 2020 2:07 am
by rdos
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.
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.

I think even today 10 man years is enough to create a decent OS.

Re: How to load and jump to "higher half" kernel?

Posted: Tue Dec 29, 2020 3:35 am
by nullplan
Back to the topic at hand.
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)?
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:

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
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:

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 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).

Re: How to load and jump to "higher half" kernel?

Posted: Tue Dec 29, 2020 4:52 am
by rdos
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?

Posted: Tue Dec 29, 2020 2:14 pm
by itarato
@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 :D
Super appreciate all the help!!!

Re: How to load and jump to "higher half" kernel?

Posted: Tue Dec 29, 2020 3:09 pm
by bzt
itarato wrote:maybe give a try to just using GRUB :D
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.

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