Build Your Own OS #6

Niroshan Pushparaj
4 min readAug 27, 2021

Hello everyone, in the previous article we discussed about interrupts handling. In this article we are going to discuss about creating user modes to execute user programs.

User modes

There are two modes of operation in the operating systems. These are user mode and kernel 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 operating system or an interrupt or a system call occurs.

This environment is less privileged than the kernel, and will prevent (badly written) user programs from messing with other programs or the kernel. Badly written kernels are free to mess up what they want.

Loading an External Program

Usually, operating systems have drivers and file systems that enable them to load the software from a CD-ROM drive, a hard disk or other persistent media.

For that, we will be using a feature in GRUB, called modules instead of creating all these drivers and file systems.

GRUB modules

GRUB can load any arbitrary into the memory from the ISO image. Those files are usually referred to as modules.

In order to make GRUB loads a module, you have to add the following line to the end of the iso/boot/grub/menu.lst file. Which will use the program file as a module.

module /modules/program

Now, we need to create the /modules directory in the /iso directory. Use the following command:

mkdir -p iso/modules

Creating a program

Then we have to create a very simple program and compile it to make a binary file from that. Copy the following code and save it in a file called program.s in the /modules directory.

; test module à copier dans iso/modules                           
; set eax to some distinguishable number, to read from the log afterwards
bits 32
start:
mov eax, 0xDEADBEEF ; enter infinite loop, nothing more to do
jmp start

Compiling the program

In order to run the above created program, we need to compile it first. Since our kernel cannot parse advanced executable formats we need to compile the code into a flat binary. NASM can do this with the flag -f:

nasm -f bin program.s -o program

IMPORTANT: Make sure to execute this command in a new terminal window from in the /modules directory. Or you can replace the correct path.

Instruct the GRUB

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

; in file `loader.s`


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

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 probably want to push ebx on the stack before calling kmain to make it an argument for kmain.

Finding the Program in Memory

Before jumping to the program we must find where it resides in memory. 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 [19]. Download the multiboot.h file from http://www.gnu.org/software/grub/manual/multiboot/html_node/multiboot.h.html, which describes 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;
}

However, before just blindly following the pointer, you should check that the module got loaded correctly by GRUB. This can be done by checking the flags field of the multiboot_info_t structure. You should also check the field mods_count to make sure it is exactly 1. For more details about the multiboot structure, see the multiboot documentation .

Jumping to the Code

The only thing left to do is to jump to the code loaded by GRUB. Since it is easier to parse the multiboot structure in C than assembly code, calling the code from C is more convenient (it can of course be done with jmp or call in assembly code as well). The C code could look like this:

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 */

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!

You can view my codes here.

I hope you have successfully integrated user modes to your operating system. Stay tuned for more articles!

Thank You!!!

--

--