How To Build An Operating System: User Modes- Part 06

Pubudu Wickramathunge
4 min readAug 27, 2021

This is the sixth article of this building an own operating system article series. If you didn’t read the first five articles in this series, I recommend you to read those articles first.

A kernel is not supposed to do the application logic itself, but leave that for applications. The kernel creates the proper abstractions to make application development easier, performs tasks on behalf of applications, and schedules processes.

User Mode

The system is in user mode when the operating system is running a user application such as handling a text editor. The transition from user mode to kernel mode occurs when the application requests the help of the operating system or an interrupt or a system call occurs.

The mode bit is set to 1 in the user mode. It is changed from 1 to 0 when switching from user mode to kernel mode.

There’s quite a way to go until the OS created in this article series can execute programs in user mode, but this article will show how to easily execute a small program in kernel mode.

Loading an External Program

GRUB Modules

We will use a feature in GRUB called modules to load the program.

GRUB can load arbitrary files into memory from the ISO image, and these files are usually referred to as modules. To make GRUB load a module, edit the file iso/boot/grub/menu.lst and add the following line at the end of the file:

module /modules/program

Now create the folder iso/modules using this command:

mkdir -p iso/modules

The application program will be created later in this article.

The code that calls kmain must be updated to pass information to kmain about where it can find the modules. We also want to tell GRUB that it should align all the modules on page boundaries when loading them.

To instruct GRUB how to load our modules, the “multiboot header” - the first bytes of the kernel - must be updated as follows:

MAGIC_NUMBER  equ 0x1BADB002      ; define the magic number constant
ALIGN_MODULES equ 0x00000001 ; tell GRUB to align modules

; calculate the checksum (all options + checksum should equal 0)
CHECKSUM equ -(MAGIC_NUMBER + ALIGN_MODULES)

section .text: ; start of the text (code) section
align 4 ; the code must be 4 byte aligned
dd MAGIC_NUMBER ; write the magic number
dd ALIGN_MODULES ; write the align modules instruction
dd CHECKSUM ; write the checksum

The below code shows the updated “loader.s” file.

GRUB will also store a pointer to a struct in the register ebx that, among other things, describes at which addresses the modules are loaded. Therefore, you want to push ebx on the stack before calling kmain to make it an argument for kmain.

Executing a Program

A program written at this stage can only perform a few actions. Therefore, a very short program that writes a value to a register suffices as a test program. Halting Bochs after a while and then check that register contains the correct number by looking in the Bochs log will verify that the program has run.

This is an example of such a short program:

After compiling this code using nasm -f bin program.s -o program we should move the program file to the folder iso/modules.

Finding the Program in Memory

To find the program, Assuming that the contents of ebx is passed as an argument to kmain, we can do this entirely from C. The pointer in ebx points to a multiboot structure. Following multiboot.h file describe the structure.

The pointer passed to kmain in the ebx register can be cast to a multiboot_info_t pointer. The address of the first module is in the field mods_addr. The following code shows an example:

int kmain(/* additional arguments */ unsigned int ebx)
{
multiboot_info_t *mbinfo = (multiboot_info_t *) ebx;
unsigned int address_of_module = mbinfo->mods_addr;
}

Now we should check that the module got loaded correctly by GRUB. To that, we need to check the flags field of the multiboot_info_t structure. We should also check the field mods_count to make sure it is exactly 1.

Finally, we need to jump to the code loaded by GRUB. It is easier to call the code from C, we should update the kmain.c file with the following code.

typedef void (*call_module_t)(void);
call_module_t start_program = (call_module_t) address_of_module;
start_program();
/* we'll never get here, unless the module code returns */

The final kmain.c file will look like this,

If we start the kernel, wait until it has run and entered the infinite loop in the program, and then halt Bochs, we should see 0xDEADBEEF in the register eax via the Bochs log.

We have successfully started a program in our OS!

This is the end of this week's article. See you with the next article.

Thanks for reading!.

References,

--

--

Pubudu Wickramathunge

Software Engineering Undergraduate at University of Kelaniya