Implement Operating System #9

Tharushi Chamalsha
4 min readSep 27, 2021

--

We have already started the chapter about User mode. But there are a few more steps to complete the chapter. So this week we are going to complete the other part of User mode. And this will be the last episode of implementing Operating System article series.

Before starting this article it would be better if you can go through the last episode of user modes.

In any modern operating system, the CPU is actually spending time in two very distinct modes.

  1. Kernal Mode

In Kernel mode, the executing code has complete and 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. Crashes in kernel mode are catastrophic; they will halt the entire PC.

2. User Mode

In User Mode, the executing code has no ability to directly access hardware or reference memory. Code running in user mode must delegate to system APIs to access hardware or memory. Due to the protection afforded by this sort of isolation, crashes in user mode are always recoverable. Most of the code running on your computer will execute in user mode.

Our kernel, at this moment, is running with the processor in Kernel Mode.

Value of User mode

These two modes aren’t mere labels, they’re enforced by the CPU hardware. If code executing in User mode attempts to do something outside its purview like, say, accessing a privileged CPU instruction or modifying memory that it has no access to a trappable exception is thrown. Instead of your entire system crashing, only that particular application crashes. That’s the value of User mode.

CPU hardware actually provides four protection rings: 0, 1, 2, and 3. Only rings 0 (Kernel) and 3 (User) are typically used.

The outermost part is the least privileged and has the least access to the resources. This is usually all the User Applications. The next privilege level goes to the Device drivers. And lastly, the Kernel.

Now we have to add segmentations to user mode.

At the moment we have 3 entries in our GDT. We have to add two more entries to the GDT. Those are,

  1. User code segment
  2. User data segment

Now we have to update our memory segment file as follows.

Now we can switch to the user mode.

The only way to execute code with a lower privilege level than the current privilege level (CPL) is to execute an exception return instruction (IRET).

To enter user mode we set up the stack as if the processor had raised an inter-privilege level interrupt. The stack should look like the following:

[esp + 16]  ss      ; the stack segment selector we want for user mode
[esp + 12] esp ; the user mode stack pointer
[esp + 8] eflags ; the control flags we want to use in user mode
[esp + 4] cs ; the code segment selector
[esp + 0] eip ; the instruction pointer of user mode code to execute

For now, we should have interrupts disabled, as it requires a little more work to get inter-privilege level interrupts to work properly.

Let’s write a C program for User mode

Since we can enter user mode from the kernel, it’s time to write a program to see its functioning.

One thing we can do to make it easier to develop user-mode programs is to allow the programs to be written in C.

Let’s add a source file with a C program.

This user_mode_program.s will be compiled to user_mode_program.o.

Then the following code shows an example of a linker script that places these instructions first in executable.

OUTPUT_FORMAT("binary")    /* output flat binary */

SECTIONS
{
. = 0; /* relocate to address 0 */

.text ALIGN(4):
{
start.o(.text) /* include the .text section of start.o */
*(.text) /* include all other .text sections */
}

.data ALIGN(4):
{
*(.data)
}

.rodata ALIGN(4):
{
*(.rodata*)
}
}

When we compile user programs we want the following GCC flags:

-m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles
-nodefaultlibs

For linking, the followings flags should be used:

-T link.ld -melf_i386  # emulate 32 bits ELF, the binary output is specified
# in the linker script

Finally, now we can write programs written in C or assembly language.

Resources:

https://littleosbook.github.io/#entering-user-mode

--

--