Boot

 

Files:

Ace3\src\kernel\i386\start.asm

Ace3\src\kernel\main.c

Ace3\src\kernel\i386\arch.c

 


Overview


Boot Loader:

Ace kernel depends on a multi-boot loader to boot. http://www.gnu.org/software/grub/manual/multiboot/multiboot.html
http://www.gnu.org/software/grub/manual/grub.html

 

_KernelEntry:

After GRUB loads an operating system, control is passed to the kernel. This is accomplished by using the kernel entry point field in the multi-boot structure. Since this entry will be coded in assembly, each architecture will contain its own entry point in its directory. The current implementation is for i386 and the kernel entry is defined in Ace3\src\kernel\i386\start.asm file.

 

I386 kernel_entry function creates kernel stack (move the esp to correct position) and setups the initial page table and calls the _cmain function.

 

cmain() is the first c function which will called in the booting process. It is an architecture independent function and defined in Ace3\src\kernel\main.c. The name cmain() instead of main() because in PE executable for defining main() requires definition of __main() function also. cmain() uses virtual addresses not physical addresses that is cmain() expects some virtual memory support from the boot loader and/or kernel entry although vm subsystem is not yet initialized.

 

cmain() expects the system to be in virtual memory(paged) mode because it will access all the global variables using virtual address(above 3gb) and not using physical address. So _KernelEntry should set up this environment before calling cmain(). In i386 implementation, InitKernelPageDirectory() is called by KernelEntry to switch to paged mode.

 

The other entries maps the kernel code and data. One more PTE is created for purpose of self mapping.

 


ArchInit():

Called by cmain() during boot and defined inside $(ARCH) folder. This function is responsible for initializing the architecture depended code. Note: this part is not responsible for initializing generic VM subsystem; however it is responsible anything that generic VM is depended.

 

Since currently only i386 is supported, it is discussed next.

 

i386 Initialization:

It consists of the following steps.

1)     Install GDT(Global Descriptor table) and IDT(Interrupt descriptor table)

2)     Detect Resources(available memory, CPUs, PIC etc)

3)     Initialize PM(physical memory)

4)     Enable Paging

 

Install GDT and IDT

GDT

In order to protect or differentiate code, data, stack and other segments in memory – protected segmented memory model is introduced in i386 (protected mode).

 

A program is composed of different segments: Text, Data, Stack, etc….. In real mode there is no way to protect data segment from being executed and there is no way to protect text segment being overwritten. In protected mode these can be achieved using different segments and setting appropriate permission flags. These segments are stored in a table called GDT- Global Descriptor table.

 

However for some reasons almost all i386 kernels don’t use the segmented model. Ace kernel also ignores the segmented memory model. Although the kernel does not use segmented model, i386 processor expects at least one segment to be defined in the GDT. So the kernel creates 4 entries in the beginning.

1) kernel code

2) kernel data

3) user code

4) user data

 

All the entries points to the same flat segment – 0 to 4GB. However the permission bit will be differ. Since the GDT is a fixed table – it is defined as static array. See – i386/gdt.c

 

IDT

Processor is responsible for invoking appropriate kernel functions when an interrupt or exception occurs. The processor uses the IDT(interrupt descriptor table) to load the kernel functions. The kernel is responsible for setting up these entry points during the boot.  See i386/idt.c and i386/idt_stub.asm

 

The first 32 entry in this table is reserved for exceptions (faults, traps and aborts). Ace kernel contains all its exception entry points in i386/exception.c. Currently the implementation is just to print the state of the machine and halt.

 

The remaining entries in the IDT table are used for external hardware interrupts and software interrupts.

 

External hardware interrupts are delivered by PIC - Programmable Interrupt controller to the processor. PIC designed to deliver interrupt at vector 0-7 which is incorrect in protected because the first 32 entries are used for exceptions. So the PIC has to be reprogrammed and it is done in i386/interrupts.c. This part will be rewritten to handle APIC.

 

Detect Resources

Detecting resources at the early stage is required because otherwise it is impossible to continue to initialize the other structures. For example, if the available memory and memory range is known then the system can initialize the pm subsystem.

InterruptHandler

Unlike exceptions, the entry points of IDT are slightly different. The IDT entries from 32 to 48 are entry into InterruptHandler(). This InterruptHandler has its own table for interrupt service routine selection. This design is because to capture the machine state before calling the ISR. This is mainly for debugging purpose and this behaviour must modified during design of first ISR.


Secondary Processor Start

All i386 processors start in real mode. This is applicable to secondary (application) processors also. Initially all the APs(Application Processor) will be in halt mode. The BSP(Bootstrap processor) sends STARTUP signals via APIC and starts the APs. Once a AP receives STARTUP interrupt it starts executing code specified by the STARTUP signal.

Ace kernel creates a thread structure including kernel stack for boot threads of each processor. The kernel stack’s starting is filled with real mode required to boot the processor(see trampoline.asm). The trampoline code just make sure the processor switches to protected mode and then calls SecondaryCPUEntry() defined in start.asm. This routine enables paging and calls SecondaryCpuStart() defined in arch.c. SecondaryCpuStart() fills the CPUID data for the current processor and initializes the scheduler for the current processor.
Loadable Kernel Modules

 

During boot kernel modules should loaded into the kernel address space. The kernel modules are loaded from disk/network to memory by boot loader and loaded address is given to kernel. To avoid dependency on different methods bootloaders(grub, efi..), Ace needs all module should be loaded as a single file.

 

The file format is

 

 

FileHeader:

MagicNumber – This is the first field in the file and it identifies the file as Ace kernel module holder. “0xACE”

TotalModules – Total number of modules present in the file. This field should be greater than 0.

 

 

ModuleHeader:

ModuleName – Name of the module(max – 30 characters)

Offset – The starting offset of the module contents from the file start.

Size – Size of the module

 

ModuleContents:

Contains binary content of the module.


The following functions are used to load the kernel module.

 

LoadBootModuleContainer() – To initialize and load module container

LoadBootModule(char * module_name) – To load a kernel module into kernel memory.

InitBootModule(void * kern_addr) – To start a already loaded kernel module.