In booting, you generally have a program that loads your kernel and assorted modules into memory, the program that sets up the environment your kernel expects, and then the main program. How you organize this is entirely up to you.
Many people use a boot stub in their kernels. That is, they have a program as actual part of the kernel, actually containing the entry point, where a short assembly stub sets up a 32-bit C environment (for instance, clearing the direction flag, and passing the multiboot parameters as actual C parameters) and calls the boot stub main function. The boot stub main function then allocates some memory for the page tables, sets them up with identity mapping for the boot stub, and the mapping you want for the kernel. The rest of the kernel is linked to above 3GB. So essentially something like this (untested, off the top of my head, assuming ELF):
boots.s:
Code:
/* must be ".data", to avoid overwriting the kernel at run time */
.section ".data","aw",@progbits
.skip 4096
kstack:
.text
.align 4
mbhead:
.long 0x1badb002
.long 2
.long -0x1badb002-2
.long 0,0,0,0,0,0,0,0,0
.global _start
_start:
movl $kstack, %esp
cld
pushl %ebx
call stubmain
1:
cli
hlt
jmp 1b
.global go_kernel
/* enables paging and enters the kernel. Takes one argument: The address of the page directory. */
go_kernel:
movl 4(%esp), %eax
movl %eax, %cr3
movl $0x80000001, %eax
movl %eax, %cr0
jmp _realstart
.global go_kernel_end
go_kernel_end:
Then bootc.c:
Code:
#include <stdint.h>
#include <stdnoreturn.h>
#define PF_PRESENT 1
#define PF_WRITE 2
#define PF_USER 4
extern noreturn go_kernel(uint32_t* pagedir);
extern char go_kernel_end[];
extern char ktext_offs[];
extern char ktext_start[];
extern char ktext_end[];
extern char kdata_offs[];
extern char kdata_start[];
extern char kdata_end[];
static uint32_t *pagedir;
static void mmap(uint32_t vaddr, uint32_t paddr, uint32_t len, uint32_t flags)
{
/* assuming non-PAE paging for simplicity here */
uint32_t *pagetab;
if (!pagedir)
pagedir = zalloc_page();
if (vaddr & 0xfff)
{
len += vaddr & 0xfff;
paddr -= vaddr & 0xfff;
vaddr &= 0xfffff000;
}
len = (len + 4095) & -4096;
while (len)
{
int pgd = (vaddr >> 22) & 0x3ff;
int pgt = (vaddr >> 12) & 0x3ff;
if (!pagedir[pgd])
{
pagedir[pgd] = zalloc_page() | PF_PRESENT | PF_WRITE;
}
pagetab = (uint32_t*)(pagedir[pgd] & -4096);
pagetab[pgt] = paddr | flags;
vaddr += 4096;
paddr += 4096;
len -= 4096;
}
}
void stubmain(struct multiboot_info* info)
{
/* whatever */
mmap((uint32_t)go_kernel, (uint32_t)go_kernel, go_kernel_end - (char*)go_kernel, PF_PRESENT);
mmap((uint32_t)ktext_start, (uint32_t)ktext_offs, ktext_end - ktext_start, PF_PRESENT);
mmap((uint32_t)kdata_start, (uint32_t)kdata_offs, kdata_end - kdata_offs, PF_PRESENT | PF_WRITE);
go_kernel(pagedir);
}
Then the linker script:
Code:
ENTRY(_start)
/SECTIONS/: {
. = 0x100000; /* load to 1MB */
.text.early : {
boot?.o(.text*)
boot?.o(.rodata*)
}
. = ALIGN(4096)
.data.early : {
boot?.o(.data*)
boot?.o(.bss*)
boot?.o(COMMON)
}
ktext_offs = .
. = 0xC0000000
.text : {
_stext = .
ktext_start = .
*(.text*)
*(.rodata*)
_etext = .
ktext_end = .
}
. = ALIGN(., 4096)
kdata_offs = ktext_offs + (. - _stext)
.data : {
kdata_start = .
*(.data*)
_edata = .
*(.bss*)
*(COMMON)
_end = .
kdata_end = .
}
}
This should at least give you a starting point. The kernel then needs to contain a symbol _realstart, which should be written in assembly and set up a boot stack and call the kernel main, which should then clear the bss section (everything between _edata and _end). Also, currently the multiboot info is not funelled through to the kernel, which you might want to rectify. And I don't know if the linker script magic does what it's supposed to. ktext_offs and kdata_offs are supposed to be the physical addresses of their respective sections.
Also, currently no stack is allocated. After turning on paging, the world only consists of the kernel data and text. Which isn't a problem if a boot stack is part of your kernel data, but since the boot stub already needs a page allocator, you might as well allocate the memory needed at the start and switch stacks within go_kernel.
Also also, you are never going to get rid of all assembly. Changing the PG bit is necessarily an assembly operation as directly after it you need to be able to avoid using the stack at all between turning on paging and switching stacks. Unless you identity map the stack as well, which seems... weird, to say the least.