Build your own Operating System #6

Gayan Malinda
5 min readAug 27, 2021

--

The Road to User Mode

Hello everyone!

This is the sixth article of the “Build your own Operating System“ article series. I like to suggest, please refer to previous articles, before reading this. It will help you to a better understanding of this article.

Now that the kernel boots, prints to screen and reads from keyboard. Usually, a kernel is not supposed to do the application logic itself, but leave that for applications. The kernel creates the proper abstractions (for memory, files, devices) to make application development easier, performs tasks on behalf of applications (system calls) and schedules processes.

User Mode

User mode, in contrast with kernel mode, is the environment in which the user’s programs execute. In User mode, the executing code has no ability to directly access hardware or reference memory. Most of the code running on the computer will execute in user mode.

Loading an External Program

We need to load the code we want to execute into memory. More feature-complete operating systems usually have drivers and file systems that enable them to load the software from a CD-ROM drive, a hard disk or other persistent media.

Instead of creating all these drivers and file systems we will use a feature in GRUB called modules to load the program.

GRUB Modules

GRUB modules are the arbitrary files that the GRUB can load into memory from the ISO image. In order to load a module using the GRUB, update the file iso/boot/grub/menu.lst in your working directory that we created in an earlier article and add the following line at the end of the file:

module /modules/program

Now create the folder called iso/modules with the below command using the terminal:

mkdir -p iso/modules

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, Update loader.s file as follows:

MAGIC_NUMBER equ 0x1BADB002 ; define the magic number constantALIGN_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) sectionalign 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 multiboot header exists to allow a bootloader ( GRUB) to load the kernel to whom the header belongs in a way that that kernel expects. The multiboot header also includes the Multiboot magic number which allows GRUB to find the location of the multiboot header. Information in the header will either change how the kernel will be loaded into memory or request that the kernel would like some extra information.

GRUB will also store a pointer to a struct in the register ebx, that 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. Now your loader.s file looks like this.

Executing a Program

A Very Simple Program

As a test program let’s consider 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.

Create program.s file inside the iso/modules folder and save with the below short program in it:

This is an example of such a short program.

Our kernel cannot parse advanced executable formats, so we need to compile the code into a flat binary. NASM can do this with the flag -f. Use the below command on your terminal for that:

nasm -f bin program.s -o program

Finding the Program in Memory

We must locate the program’s memory location before we run it. Assuming that the contents of ebx is passed as an argument to kmain, we can do this from C. The pointer in ebx points to a multiboot structure.

The pointer that describes the addresses of the modules which is in ebx register, points to a multiboot structure . The figure is shown below.

let’s create multiboot.h file which describes the structure with below code.

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 files with the given code can be used for that. Add those files to your working directory.

Also, update Makefile OBJECTs.

Jumping to the Code

Finally let’s jump to the code loaded by GRUB. This can be done with jmp or call instructions in assembly. But as always it is easier to parse the multiboot structure in C code than assembly code, calling the code from C is more convenient.

Update your kmain.c file with the below C code.

Now, boot your OS using the make run command.

Halting Bochs after a while and then display Bochslog.txt using “cat bochslog.txt” command on the terminal to check whether the register contains the correct number. If you see 0xDEADBEEF in the register EAX , then your program has executed successfully.

Congratulations! Now you have executed a small program in the kernel mode.

The Beginning of User Mode

The program we’ve written in this article runs at the same privilege level as the kernel. To enable applications to execute at a different privilege level (User mode) we’ll have to do paging as well as page frame allocation besides segmentation, which we discussed in an earlier article. In the next article we will come across with an introduction to virtual memory and paging, which will lead the path in obtaining, “working user mode programs”.

Hope you understand these steps. Let’s meet with the next article of the “Build your own Operating System” series. Thank you so much for reading!!!

The Little OS Book: https://littleosbook.github.io/book.pdf

Grub Modules

--

--

Gayan Malinda

Software Engineering Undergraduate - University of Kelaniya, Sri Lanka