My Operating system Development Experience and understanding -Part 09

M.R.M Abdullah
6 min readSep 27, 2021

Hello developers…!

in this article we are going to understand about User mode in OS development. Before that if you don’t read my previous OS development Articles please go and check those from below links

  1. My Operating system Development Experience and understanding. Part 01 -Setting Up environments
  2. My Operating system Development Experience and understanding -Part 02- C instead of assembly
  3. My Operating system Development Experience and understanding -Part 03-implement Drivers
  4. My Operating system Development Experience and understanding -Part 04-Segmentation in OS development
  5. My Operating system Development Experience and understanding -Part 05-nterrupts and inputs
  6. My Operating system Development Experience and understanding -Part 06-user modes
  7. My Operating system Development Experience and understanding -Part 07-virtual memory paging
  8. My Operating system Development Experience and understanding -Part 08-Page Frame Allocation

let get into todays Topics

User mode

User mode is now nearly available to us, only a few more steps are needed to get there. While these procedures might appear easy to execute, they might be difficult to implement, because there are many places in which tiny problems are difficult to identify.

User Mode Segments
We need to add two more segments to the GDT in order to enable user mode. The segments of the kernel that we inserted are pretty similar to that of the GDT in the segmentation chapter:(check the link at stating of article)

The segment descriptors needed for user mode.

Index Offset Name Address range Type DPL
3 0x18 user code segment 0x00000000–0xFFFFFFFF RX PL3
4 0x20 user data segment 0x00000000–0xFFFFFFFF RW PL3

I hope you could see where this change is going to take place. PL3 is now the DPL. But the DPL had been set to PL0 before that (kernel mode). However, I want to remind you that in its address space, the user program cannot reach the Kernel space! The user software must not interfere with the kernel space required for the safety of our operating system. We can do this by using paging.

Setting Up For User Mode

Every user mode needs a number of things:

1.Page frames for code, data and stack. For the moment one page frame for the stack and sufficient page frames are sufficient to fit the code of the program. Don’t bother about establishing a stack, which can grow and decrease now, focus first on a fundamental implementation task.

2.The GRUB module binary is to be copied to the program code page frames.

3.To map page frames mentioned above into memory, a page directory and page tables are required. There must be at least two tables for the page since code and data should be mapped to a minimum of 0x00000000, and the stack should start at 0xBFFFFFFB immediately below the kernel, growing to a smaller number. To allow PL3 access, the U/S flag must be set.

Store this information in a structure that represents a process can be convenient. The malloc function of the kernel can assign this process structure dynamically.

Entering User Mode

Only the iret or lret instructions — Interrupt return or Long Return — can be used to run code with lower privilege levels than the current privilege level (CPL).

In order to enter user mode, we configured a stack as though the processor had interrupted an interrupt level. The stack should look the same:

[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

Then the instruction iret reads the stack contents and fills in the respective registers. We must go to the page directory for the user mode process before we execute iret. It’s vital to note that after we have switched PDT, the kernel has to be mapped to continue running kernel code. A separate PDT for the kernel can do this, by mapping any data on or above 0xC0000000 and merging the information with the PDT user (which only maps less than 0xC0000000 when the switch is being carried out). Note that when setting register cr3 you need to enter the actual address of the PDT.

The eflags registry contains a range of flags. The interrupt enable (IF) flag is very crucial to us. The instructions in the assembly code can’t be utilized for interrupting at privilege level 3. When interrupts are disabled, interrupts cannot be enabled when user mode is entered. When interrupts are disabled. Setting the IF flag on the stack of eflags will allow user interrupts, as the iret assembly code instruction sets the eflags on the stack to the appropriate value.

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

On the stack, the values cs and ss should be segment selectors for the user code and user data segments, respectively. The RPL — the Requested Privilege Level — is the lowest two bits of a segment selector, as we saw in the segmentation chapter. When entering PL3 with iret, the RPL of cs and ss should be 0x3. An example can be found in the following code:

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 segment selector for register ds and the other data segment registers should be the same as for ss. They can be set in the traditional manner, using the mov assembly code instruction.

We are now ready to carry out iret. If everything is in order, we should now have a kernel that can enter user mode.

Creating User Mode Programs in C

When using C as a programming language for user mode programs, it’s crucial to consider the file structure that will be created as a result of the compilation.

Because GRUB understands how to parse and interpret the ELF file format, we may utilize it as the file format for the kernel executable. We could compile user mode applications into ELF binaries if we created an ELF parser. We’ll leave it up to the reader to figure out what to do with it.

Allowing user mode programs to be written in C but compiling them to flat binaries rather than ELF binaries is one method for making user mode programming easier. The code layout produced in C is more unusual, because the entry point, main, may not be at binary offset 0 in the binary. One frequent technique is to include a few lines of assembly code at offset () that call main:

If you store this code in a file named start.s, the following code shows an example of a linker script that places these instructions first in the executable (remember that start.s gets compiled to start.o):

Note that start.o’s.text section will not be included in *(.text). (its preview of start.o)

We can develop programs in C or assembly (or any other language that compiles to object files that can be linked with .ld) with this script, and it’s simple to load and map for the kernel (.rodata will be mapped in as writeable, though).

To run user programs, we need to compile them with the following GCC flags:

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

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.

Now go ahead and congratulate yourself! Your operating system has the ability to transition between user and kernel modes. I hope you found this post helpful. Thank you so much for taking the time to read this! Be inspired until we meet again with another exciting article! Continue to learn!!!

Abdullah M.R.M

--

--

M.R.M Abdullah

Software Engineering undergraduate at University of Kelaniya.