Implementation of Sach Os (Part9)

Sachithra_Manamperi
4 min readSep 27, 2021

This is the ninth in a series of articles on the subject.. I’m following the Reference Book “The little book about OS development” by Erik Helin and Adam Renberg.If you haven't read previous article Here’s the link to part 8.👇

This article will describe how to activate user modes in an operating system.User mode is nearly at hand now; it just takes a few steps to get there. They can be challenging to implement since there are several locations where little mistakes might lead to issues that are difficult to find. To allow user mode, we need to add two extra segments to the GDT. When we configure things for user mode, there are a few things that every user-mode process needs.

Page frames are separate for code, data, and stack. For the time being, a single page frame for the stack and enough page frames to accommodate the program’s code is adequate.
The binary of the GRUB module must be transferred to the page frames that contain the program’s code.

Segmentation for user mode

The first thing we must do is add two more segments to the GDT. They are quite similar to what we did at the segmentation stage when we were originally putting up the GDT.

A page directory and page tables are required to map the above-mentioned page frames into memory. Because the code and data should be mapped in starting at 0x00000000 and rising in size, at least two-page tables are necessary, and the stack should start immediately below the kernel, at 0xBFFFFFFB, and climb towards lower places.

Switch to user mode

The next step is to enter user mode. The x86 is unusual in that there is no direct way to switch to user mode.
The only way to execute code with a privilege level lower than the current privilege level (CPL) is to issue an exception return instruction (IRET).

We have to update the kmain file like this

We have to create user_mode.s file like this

Here is the user_prgram.c file we created

We construct the stack as though the CPU had raised an inter-privilege level interrupt to enable us to enter user mode. The stack should look something like this.

The instruction iret will then read these values from the stack and fill in the appropriate registers. Before we can run iret, we must first navigate to the page directory we created for the user-mode process. It is critical to remember that after switching PDT, the kernel must be mapped in order for the code to continue running.

One approach is to create a separate PDT for the kernel that maps all data above 0xC0000000 and then combine it with the user PDT when switching. Keep in mind that the PDT’s physical address must be used when creating the register cr3.

For us, the interrupt enable (IF) flag is the most important. The assembly code instruction sti cannot be used to enable interrupts at privilege level 3. If interrupts are disabled when entering user mode, they cannot be activated once in user mode. Because the assembly code instruction iret sets the register eflags to the stack equivalent value, setting the IF flag in the eflags entry on the stack allows interrupts in user mode.

The stack value eip should point to the user code’s entry point, which in our case is 0x00000000. The stack’s esp value should be 0xBFFFFFFB, which is where the stack starts (0xC0000000–4).

On the stack, the segment selectors for the user code and user data segments should be cs and ss, respectively.

The RPL (Requested Privilege Level) is the segment selector’s bottom two bits. When using iret to enter PL3, the RPL of cs and ss should be 0x3.

The code below provides an example:

Creating User Mode Programs in C

Allowing user-mode programs to be written in C but compiling them to flat binaries rather than ELF binaries is one way to make user-mode applications easier to build. The resulting C code structure is more surprising, and the entry point, main, may not be at binary offset 0 in the binary. To do so, we’ll need to use the code “program start.s.”

This start_program.s file will be compiled to produce a start_program.o file. In addition to our main C file, we require a linker script that places these instructions first in the executable. Then, using the linker script below, these instructions are placed first in the executable.

Using this script, we can now build programs in C or assembly, and it’s simple to load and map for the kernel.

--

--

Sachithra_Manamperi

Undergraduate | Software Engineering | Dharmaraja College Kandy | Sri Lankan