Operating Systems: The Road to User Mode

Hasini Samarathunga
6 min readAug 27, 2021
vector created by vectorjuice — freepik

This is the first article on introducing User Modes in a series of articles explaining the development of an x86 Operating System. It can be used as a stand-alone guide but it will make a lot more sense if you follow the series from the beginning. If you are all caught up, we can move along to our next big step in OS development.

So far we have managed to boot our Operating System, have it read input from the keyboard, and display the output. Let’s see what else we can make our OS do.

What if we can make a program we’ve written to run at the same privilege level as the kernel? For that, first, we need to understand User mode and Kernel mode.

What is User mode?

The User mode is the normal mode where the process only has limited access to system resources like hardware, memory, etc. This is the environment in which the user’s programs execute. Here you will not have the same privileges as in the Kernel mode.

This environment is there to prevent badly written user programs from messing with other programs or the kernel. A process can only access I/O Hardware registers to program it, execute OS kernel code, and access kernel data in Kernel mode.

What is Kernel mode?

Kernel mode is the privileged mode where the process has unrestricted access to system resources. Any crash in kernel mode brings down the whole system. Any crash in user mode, on the other hand, just stops the problematic process.

System Calls are the only way through which a process can go into kernel mode from user mode. The shift from user mode to kernel mode is shown in detail in the diagram below.

We have a long way to go until our OS can execute programs in user mode. In this article, I will show how to easily execute a small program in kernel mode.

A Very Simple Program

First, let’s create our program. We need it to be simple enough for our OS to handle. How about a very simple program that just writes a value to a register? Like this one,

A short and sweet program with one single task that we can check by just looking at the Bochs log.

Compiling

Make sure to compile the program into a flat binary. This is because the kernel cannot read advanced executable formats. NASM can do this with the flag -f:

nasm -f bin program.s -o program

Now, where do we put our program in our code? Let’s create a modules folder in our already existing iso folder for that.

mkdir -p iso/modules

Create the folder and move the file program to this folder iso/modules.

Loading an External Program

So how do we load the code we later want to execute into memory? In more advanced operating systems, that have all its features, 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.

Since our OS is not yet there, we will instead use a feature in GRUB called modules to load the program.

GRUB Modules

GRUB can load arbitrary files into memory from the ISO image. These files are usually called modules.

You can simply make the GRUB load a module by editing the file iso/boot/grub/menu.lst and adding the following line at the end of the file,

module /modules/program

Then we need to pass information about where to find the modules to our kmain.

To instruct GRUB how to load our modules, the loader.s file must be updated as follows:

Here we have also added instructions to the GRUB on aligning the modules on page boundaries when loading them.

In the register ebx , GRUB will also keep a pointer to a struct that, among other things, describes where the modules are loaded. So you need to push ebx on the stack before calling kmain to make it an argument for kmain.

push ebx                    ; multiboot info in ebx      
call kmain ; call the function

Finding the Program in Memory

Before we can execute our program we need to find it in the memory. To find wherein the memory did our program got loaded by the GRUB, we can write a simple C code.

The pointer in ebx points to a multiboot structure. This structure is described in this multiboot.h file that you can download from the below link,

The pointer passed to kmain in the ebx register is first casted to a multiboot_info_t pointer. The mods_addr in this structure is then casted to a multiboot_module_t pointer. The address of the first module is in the field mod_start .

Our c code will look something like this,

Jumping to the Code

The only thing left is to jump to the GRUB-loaded code. Calling the code from C is more convenient since it is easier to parse the multiboot structure in C than in assembly code (it can of course be done with jmp or call in assembly code as well).

But our C code will look like this,

Seems complicated? Don’t worry let me walk you through it.

The if statements are there to check if the module got loaded correctly by GRUB. The mods_count should be exactly 1.

The important part of our code is this,

typedef void (*call_module_t)(void);
call_module_t start_program = (call_module_t) address_of_module;
start_program();

First, we write a pointer to a function called call_module_tthat takes zero arguments. Then we cast the module address we got to a call_module_t . Then we simply call that function to jump to the program code.

Now when we start our kernel we’ll see this on our Boch console,

wait until it has run and entered the infinite loop in the program, and then halt Bochs, we should see deadbeef in the register eax from the Bochs log.

No matter how different our method was, the program we’ve written now runs at the same privilege level as the kernel.

If you are having problems with the implementation, you can find the complete code from my GitHub below.

To have applications executing at a different privilege level we’ll need to know about segmentation, which we have already done in this article, paging, and page frame allocation. I’ll be discussing them in the upcoming articles.

Hope to see you in the next article as well!

Thank you!

Reference: Helin, E., & Renberg, A. (2015). The little book about OS development

Read previous articles

--

--