Develop Your Own x86 Operating System(OS) #7

The Road to User Mode

Isuruni Rathnayaka
6 min readAug 27, 2021

Now that the kernel in our developed operating system boots, prints to screen and reads from keyboard. But we still have quite a way to go until the our OS can execute programs in user mode, so this chapter we will witness how to execute a small program in kernel mode.

If you haven’t read the fifth article of the series on Interrupt handling and reading inputs from keyboard, you can read it from here as its important to have a comprehensive understanding about this continuous process of developing your own operating system.

A process is a program under execution from RAM and comprises of various sections: Executable instructions, stack, heap and State. The user process cannot assess the kernel region. System Calls are a set of functions that a OS supports and a user process can use a system call to access the Kernel. When a system call is invoked the processor shifts from user mode to kernel mode.

Kernel Mode

In Kernel mode, the executing code has unrestricted access to the underlying hardware. It can execute any CPU instruction and reference any memory address. Kernel mode is generally reserved for the lowest-level, most trusted functions of the operating system. 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.

Our OS can not execute programs in user mode yet, but with this article let’s get to know how we can execute a program in kernel mode.

Loading an External Program

Here, we have to somehow load the code we want to execute into memory. But the problem is the place, where we can get the external program from. More advanced 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 media.

Without creating all these drivers and file systems, here 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 your menu.lst file will look like this”

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

mkdir -p iso/modules
“In iso folder you will see modules folder now”

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. 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:

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. Update your loader.s file with the push ebx and above code as follows:

“Now your loader.s file will look like this”

Executing a Program

In this section, let’s see how we can execute a program.

A Very Simple Program

As a test program let’s consider a very short program that writes a value to a register. This is an example of such a short program:

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

“Now you will see program.s file in iso/modules folder”

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
“Now you will see program file in iso/modules folder”

Finding the Program in Memory

First we must find the place where our program resides in memory. 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. let’s create multiboot.h file which describes the structure.

You can use the following code for it:

“You can see multiboot.h file in your working directory with the saved 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 code can be used for that:

Update your kmain.c file as follows:

“Finally your kmain. file will look like this”

You should always check whether the module has been loaded correctly by GRUB. For that check the flags field of the multiboot_info_t structure. Use the following code for it:

You should also check the field mods_count to make sure it is exactly 1, using the following piece of code.

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. The C code could look like this:

Update your kmain.c file with the above C code:

“Finally your kmain.c file will look like this”

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.

“EAX = deadbeef”

So, 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”. You can read that article from here.

References

LittleOSBook

Grub Modules

Hope you understand steps in executing a program in kernel mode. Let’s meet with the next article of Develop Your Own x86 Operating System(OS) series. Thank you so much for reading!!!!!!!!!!

Isuruni Rathnayaka

--

--

Isuruni Rathnayaka

Software Engineering Undergraduate - University of Kelaniya Sri Lanka