Higher Half Kernel: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(Small grammar fix, consistency update (first uppercase hex is used, then lowercase))
mNo edit summary
 
(19 intermediate revisions by 12 users not shown)
Line 1: Line 1:
{{Stub}}
{{Template:Kernel designs}}
{{Template:Kernel designs}}


It is traditional and generally good to have your kernel mapped in every user process. Linux, for instance (and many other Unices) reside at the virtual addresses ''0xC0000000 - 0xFFFFFFFF'' of every address space, leaving the range ''0x00000000 - 0xBFFFFFFF'' for user code, data, stacks, libraries, etc.
It is traditional and generally good to have your kernel mapped in every user process. Linux and many other Unices, for instance, reside at virtual addresses ''0xC0000000 0xFFFFFFFF'' of every address space, leaving the range ''0x00000000 0xBFFFFFFF'' for user code, data, stacks, libraries, etc. Kernels that have such design are said to be "in the higher half" by opposition to kernels that use lowest virtual addresses for themselves, and leave higher addresses for the applications.

Kernels that have such design are said to be "in the higher half" by opposition to kernels that use lowest virtual addresses for themselves, and leave higher addresses for the applications.
In addition, there is some non-x86 ISA (the [[MIPS Overview|MIPS]] [[User:Schol-r-lea/Understanding RISC vs CISC|RISC architecture]] and [[AArch64|ARM]]) which partly forces the issue. On MIPS and ARM systems, addresses using the high bit (either bit 31 or bit 63, depending on the system word width) are reserved for use in Supervisor mode, and are exception trapped when in User mode.


Advantages of a higher half kernel are:
Advantages of a higher half kernel are:
* It's easier to set up [[Virtual 8086 Mode|VM86]] processes since the region below 1MB is userspace.
* It's easier to set up [[Virtual 8086 Mode|VM86]] processes since the region below 1 MB is userspace.
* More generically, user applications are not dependent on how many memory is kernel space (Your application can be linked to 0x400000 regardless of whether kernel is at ''0xC0000000'', ''0x80000000'' or ''0xE0000000'' ...), which makes ABI's nicer.
* More generally, user applications are not dependent on how much memory is kernel space (your application can be linked to 0x400000 regardless of whether kernel is at ''0xC0000000'', ''0x80000000'' or ''0xE0000000'' ...), which makes the ABI nicer.
* If your OS is 64-bits, then 32-bit applications will be able to use the full 32-bit address space.
* If your OS is 64-bit, then 32-bit applications will be able to use the full 32-bit address space.
* 'mnemonic' invalid pointers such as ''0xCAFEBABE'', ''0xDEADBEEF'', ''0xDEADC0DE'', etc. can be used.
* 'Mnemonic' invalid pointers such as ''0xCAFEBABE'', ''0xDEADBEEF'', ''0xDEADC0DE'', etc. can be used.


== Initialization ==
== Bootloader support ==
To make things easier, some bootloaders natively support higher half kernels, by directly loading and mapping a kernel to the higher half in virtual memory.
To setup a higher half kernel, you have to map your kernel to the appropriate virtual address. How to do this basically depends on '''when''' you'd like your kernel to believe it's in the higher end, and '''when''' you set up paging.


* [[BOOTBOOT]] only supports higher half kernels by design. It has example Hello World kernels written in [[C]], [[Pascal]], [[Rust]] and [[Go]]
=== Custom Bootloader ===
* The [[Limine]] protocol also only support higher half kernels by design. See [[Limine Bare Bones]] for a tutorial on how to write a simple 64-bit higher half kernel using Limine.
The easiest way is to load your kernel to any physical location you wish (for instance in the lowest 1MB) and prepare page tables that will perform the appropriate translation. Let's say you loaded your kernel at ''0x00010000'' to ''0x0009FFFF'' and want it to appear at ''0xC0010000'', you could do the following:


== Initialization ==
* Pick 3 page-aligned (0x1000-aligned) addresses where you'll put your page directory and system tables. Make sure they are zeroed (''memclr'' them or ''memset'' them to 0).
To setup a higher half kernel, you have to map your kernel to the appropriate virtual address. When using a boot protocol which supports higher half kernels directly, such as [[BOOTBOOT]], or [[Limine]], your kernel will already be properly mapped.
* Fill the lowest 256 entries of one table to set up [[Identity Paging]] for at least the BIOS and your bootloader (it's probably best to use 1:1 mapping for the entire lowest 1MB).
* In the other table, fill entry #0x10 (#16) with ''0x00010003'', entry #0x11 (#17) with ''0x00011003'', and so on (do this for every page your kernel has or needs).
* Fill entry #0x0 (#0) of the directory with the address of the first table (and make sure it's set to ''present'').
* Fill entry #0x300 (#768) of the directory with the address of the second table (and make sure it's set to ''present'').


How to do this basically depends on '''when''' you'd like your kernel to believe it's in the higher end, and '''when''' you set up paging. Without a boot loader help, you'll need a small trampoline code which runs in lower half, sets up higher half paging and jumps.
When switching to [[Protected Mode]], use this assembly example:


== See Also ==
<source lang="asm">
mov eax, physical_address_of_the_directory ; Get the physical address of the page directory...
mov cr3, eax ; ... and store it in CR3.
mov eax, cr0 ; Get what's in CR0...
or eax, 0x80000001 ; ... enable protected mode and paging ...
mov cr0, eax ; ... and put the new value back in CR0.
</source>


=== The GDT Trick ===
=== Articles ===
* [[Higher Half x86 Bare Bones]]
If you don't want to enable paging right from the start, it is still possible to have your kernel appearing in the higher half. [http://www.osdever.net/tutorials/pdf/memory1.pdf Tim Robinson's GDT Trick] works by using [[segmentation]] to select an appropriate base for the code and data segments. Say you've loaded your kernel at ''0x10000'' and we want it to appear at ''0xC0000000'', then all we need to do is find a base _X_, such as _X_ + ''0xC0000000'' = ''0x10000''. The bootloader will then initialize the [[GDT]] with cs.base = 0x40010000 = ds.base. This also means that special care must be taken for VRAM (video RAM) access, as ''0xB8000'' is now somewhere above 1GB. Either use a special 0-based additional data-segment or use


=== Threads ===
<source lang="c">
* [[Topic:11160|I wrote a simple HigherHalf kernel]]
#define logical_to_physical(x) (((void*) x) + 0x40010000)
#define physical_to_logical(x) (((void*) x) - 0x40010000)
short *vram = physical_to_logical(0xB8000);
</source>

When you eventually enable paging, create the page tables as mentioned above (keeping in mind that you need to undo the address conversion again, e.g.

<source lang="c">
pgentry *pagedirectory = physical_to_logical(0x9D000);
pgentry *lowesttable = physical_to_logical(0x9C000);
pgentry *kerneltable = physical_to_logical(0x9B000);
/* prepare the lowest table and the kernel table... */
pagedirectory[0] = mkpgentry(0x9C000, PG_PRESENT | any_other_flag_you_might_need);
pagedirectory[0xC0000000 >> 12] = mkpgentry(0x9B000, PG_PRESENT | PG_SYSTEM | any_other_flag_you_might_need);
set_cr3_and_update_gdt(0x9D000);
</source>

Along with setting CR3's value, you'll have to clear the base of all the previous segments and reload segment registers so that, when ''set_cr3_and_update_gdt'' returns, any memory references now use the 0-based code and data segment and that the page tables perform the translation required to make ''0x10000'' appear at ''0xC0000000''.

=== With GRUB From the Start ===
[[GRUB]] will load your kernel at the desired physical target address, but it will leave segments in the [[GDT]] with a 0 base and it will not enable paging.

=== With GRUB Later On ===
Even with GRUB it's still possible to use TimRobinson's GDT trick: just set up a "fake" GDT then jump to your C code and enable [[paging]]. See [[Higher Half With GDT]]

== See Also ==
*[http://forum.osdev.org/viewtopic.php?t=11160 I wrote a simple HigherHalf kernel]


[[Category:Kernel]]
[[Category:Kernel]]

Latest revision as of 12:03, 8 September 2022

This page is a stub.
You can help the wiki by accurately adding more contents to it.
Kernel Designs
Models
Other Concepts

It is traditional and generally good to have your kernel mapped in every user process. Linux and many other Unices, for instance, reside at virtual addresses 0xC0000000 – 0xFFFFFFFF of every address space, leaving the range 0x00000000 – 0xBFFFFFFF for user code, data, stacks, libraries, etc. Kernels that have such design are said to be "in the higher half" by opposition to kernels that use lowest virtual addresses for themselves, and leave higher addresses for the applications.

In addition, there is some non-x86 ISA (the MIPS RISC architecture and ARM) which partly forces the issue. On MIPS and ARM systems, addresses using the high bit (either bit 31 or bit 63, depending on the system word width) are reserved for use in Supervisor mode, and are exception trapped when in User mode.

Advantages of a higher half kernel are:

  • It's easier to set up VM86 processes since the region below 1 MB is userspace.
  • More generally, user applications are not dependent on how much memory is kernel space (your application can be linked to 0x400000 regardless of whether kernel is at 0xC0000000, 0x80000000 or 0xE0000000 ...), which makes the ABI nicer.
  • If your OS is 64-bit, then 32-bit applications will be able to use the full 32-bit address space.
  • 'Mnemonic' invalid pointers such as 0xCAFEBABE, 0xDEADBEEF, 0xDEADC0DE, etc. can be used.

Bootloader support

To make things easier, some bootloaders natively support higher half kernels, by directly loading and mapping a kernel to the higher half in virtual memory.

  • BOOTBOOT only supports higher half kernels by design. It has example Hello World kernels written in C, Pascal, Rust and Go
  • The Limine protocol also only support higher half kernels by design. See Limine Bare Bones for a tutorial on how to write a simple 64-bit higher half kernel using Limine.

Initialization

To setup a higher half kernel, you have to map your kernel to the appropriate virtual address. When using a boot protocol which supports higher half kernels directly, such as BOOTBOOT, or Limine, your kernel will already be properly mapped.

How to do this basically depends on when you'd like your kernel to believe it's in the higher end, and when you set up paging. Without a boot loader help, you'll need a small trampoline code which runs in lower half, sets up higher half paging and jumps.

See Also

Articles

Threads