How To Build An Operating System: User Mode - Part 09

Pubudu Wickramathunge
5 min readSep 27, 2021

Hello everyone!. This is the 9th article of this building an own operating system article series. Please read the first seven articles in this article series if you didn’t read those. It will help you to understand this article more. Let's jump into today's chapter.

In this article, we are going to discuss user mode. We are only a few steps away from our destination but these steps are hard and tricky. So I hope you will follow these steps very carefully with me. We talked about user-mode briefly in previous articles. You can refer to that article here. Here we will discuss that more deeply.

To enable user mode, we should add two more segments to the GDT and they are very similar to the kernel segments we added in previous articles.

The segment descriptors needed for user mode as following:

Index       Offset Name             Address range          type DPL
3 0x18user code segment 0x00000000 - 0xFFFFFFFFRX PL3
4 0x20user data segment 0x00000000 - 0xFFFFFFFFRW PL3

DPL now allows code to execute in PL3. We need paging to protect the kernel because just using these segments won’t protect it.

Setting Up For User Mode

  • Page frames for code, data, and stack. It suffices to allocate a one-page frame for the stack and enough page frames to fit the program’s code. Get basic implementation work first.
  • The binary from the GRUB module should be copied to the page frames used for the program's code.
  • Above mentioned page frames need to be mapped using a page dictionary and page tables. For that, we need at least two-page tables since code and data must be mapped in at 0x00000000 and increasing from there, and the stack should start from below the kernel, at 0xBFFFFFFB, growing towards lower addresses. The U/S flag needs set to allow PL3 access.

Entering User Mode

The only way to execute code with a lower privilege level than the current privilege level is executing an iret or lret instruction — interrupt return or long return.

After setting up the stack as if the processor had raised an inter-privilege level interrupt to enter user mode the stack should look like this:

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

The instruction iret will read these values from the stack. And then it will fill in the corresponding registers. We need to change the page directory we set up for the user-mode process before we execute iret. You need to continue executing kernel code after switched PDT. And the kernel needs to be mapped in. To accomplish this we need to have a separate PDT for the kernel, which maps all data at 0xC0000000 and above and merges it with the user PDT which only maps below 0xC0000000 when performing the switch. The physical address of the PDT has to be used when setting the register cr3.

The eflags register contains a set of different flags. The interrupt enables (IF) flag is the most important for this. In privilege level 3, the assembly code instruction sti cannot be used to enable interrupts. Interrupts cannot be enabled once user mode is entered if interrupts are disabled before entering user mode. The assembly code instruction iret sets the register eflags to the matching value on the stack, setting the IF flag in the eflags register entry on the stack will enable interrupts during the user-mode. We should have interrupts disabled for now. It requires some work to get inter-privilege level interrupts to work correctly. The value eip on the stack should be pointing to the entry point for the user code - 0x00000000 . The value esp on the stack should be where the stack starts - 0xBFFFFFFB (0xC0000000 - 4).

Respectively the values cs and ss on the stack should be the segment selectors for the user code and user data segments. The lowest two bits of a segment selector is the Requested Privilege Level. The RPL of cs and ss should be 0x3when using iret to enter PL3.

Here is an example:

    USER_MODE_CODE_SEGMENT_SELECTOR equ 0x18
USER_MODE_DATA_SEGMENT_SELECTOR equ 0x20
mov cs, USER_MODE_CODE_SEGMENT_SELECTOR | 0x3
mov ss, USER_MODE_DATA_SEGMENT_SELECTOR | 0x3

The other data segment registers, and the register ds should be set to the same segment selector as ss. With the mov assembly code instruction, they can be set the ordinary way. Now we can execute iret. Now the kernel should be able to enter user mode.

Using C for User Mode Programs

We write user-mode programs in C and compile them to flat binaries. In this way, the generated code is more unpredictable. And the entry point, main, may not be at offset 0 in the binary. Adding few assembly code lines placed at offset 0 which calls mainwill solve this as below.

    extern main    section .text
; push argv
; push argc
call main
; main has returned, eax is return value
jmp $ ; loop forever

save this code in a file called start.s.Here is an example for 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*)
}
}

Now we can write programs in C or assembler or any other language that compiles to object files linkable with ld. As well as it is easy to load and map for the kernel.

We want the following GCC flags when we compiling user programs:

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

The followings flags should be used for linking:

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

# in the linker script

The option -T instructs the linker to use the linker script link.ld.

This is all for this article. Hope you get the idea and see you with the next article and there, we will be discussing File Systems as the next part.

Thanks for reading!.

References,

--

--

Pubudu Wickramathunge

Software Engineering Undergraduate at University of Kelaniya