Page 1 of 1
Calling "Higher Half" Code from Lower Half
Posted: Fri Apr 12, 2024 7:35 am
by mttarry
After setting up my higher half kernel and enabling paging, I started designing my PMM. Then I realized this is a bit backwards. So now I want to use my PMM to allocate memory for my paging structures. But in my PMM, I need to use functions that are mapped to the higher half (namely, memcpy() and memset()). So calling them outright before enabling paging causes a jump to unallocated memory because the MMU is not performing translations yet. As a workaround, I created a function pointer as such:
typedef void (*memset_func_ptr)(void *, const void *, size_t);
And set it to the VA of memset minus the higher half offset:
memset_func_ptr memset_lowerhalf = (void*)((uintptr_t)&memset - HIGHER_HALF_START_ADDR);
However I feel like this is bad design and leads to less readable code. Have any of you come up with a way to simplify interactions between the lower half and higher half? It just feels to me like setting up the linker file such that the bulk of the kernel is mapped to a virtual address is premature because paging hasn't been enabled yet. But maybe the purpose of this is for debugging the ELF binary? I'm not sure. Any insight would be greatly appreciated.
Re: Calling "Higher Half" Code from Lower Half
Posted: Fri Apr 12, 2024 8:50 am
by nullplan
Honestly, the cleanest approach is probably going to be to have the lower half and higher half in different executables. This is what I'm doing to avoid such problems, but then, I have to since I am developing a 64-bit OS (and the lower-half code must be 32-bit). In my case, the loader kernel gets the memory info from multiboot, puts itself in the reserved section, puts the main kernel in the reserved section, and then allocates all the paging structures. At the end, it removes itself from the reserved section and hands those structures in a defined way to the higher-half kernel. In this way, there is no added memory footprint since the loader kernel frees itself at the end.
If you insist on continuing on your path, you can probably automate the process somewhat with macros. Such as (untested)
Code: Select all
#define lower_half(x) ((__typeof__(&x))((uintptr_t)(x) - HIGHER_HALF_START_ADDR))
//and then you use it like:
lower_half(memcpy)(dest, src, len);
But I wouldn't want to use such an approach, because the compiler can emit undecorated calls to memcpy/memmove/memset literally everywhere. So that is why I would suggest separate executables. I think I remember Linux doing something similar, with a boot section that is separate from the rest of the kernel.
Re: Calling "Higher Half" Code from Lower Half
Posted: Fri Apr 12, 2024 9:21 am
by mttarry
nullplan wrote:Honestly, the cleanest approach is probably going to be to have the lower half and higher half in different executables. This is what I'm doing to avoid such problems, but then, I have to since I am developing a 64-bit OS (and the lower-half code must be 32-bit). In my case, the loader kernel gets the memory info from multiboot, puts itself in the reserved section, puts the main kernel in the reserved section, and then allocates all the paging structures. At the end, it removes itself from the reserved section and hands those structures in a defined way to the higher-half kernel. In this way, there is no added memory footprint since the loader kernel frees itself at the end.
If you insist on continuing on your path, you can probably automate the process somewhat with macros. Such as (untested)
Code: Select all
#define lower_half(x) ((__typeof__(&x))((uintptr_t)(x) - HIGHER_HALF_START_ADDR))
//and then you use it like:
lower_half(memcpy)(dest, src, len);
But I wouldn't want to use such an approach, because the compiler can emit undecorated calls to memcpy/memmove/memset literally everywhere. So that is why I would suggest separate executables. I think I remember Linux doing something similar, with a boot section that is separate from the rest of the kernel.
This is very helpful, thank you!