WVB Operating System(Sixth Stage)
Welcome back to my sixth article on operating system implementation. In the last articles, we learned about “interrupts” and “inputs”. Now we know how the kernel boots, prints to screen, and reads from the keyboard. This article will discuss how user programs are executed.
Normally, a kernel is not designed to handle application logic; instead, applications are supposed to accomplish it. The kernel provides the necessary abstractions to make application development easier, as well as performing tasks on behalf of applications and scheduling processes. In contrast to kernel mode, user mode is the environment in which the user’s programs run. Because this environment has fewer privileges than the kernel, user programs will be unable to interfere with other programs or the kernel. Kernels that are poorly written are free to do whatever they want.
What is the location of the external program? The code we want to run must be loaded into memory in some way. Drivers and file systems for loading software from a CD-ROM drive, a hard disk, or other persistent media are frequently included with more feature-rich operating systems. Rather than building all of these drivers and file systems from scratch, we’ll use GRUB’s modules functionality to load them.
The ISO image can be used by GRUB to load arbitrary files into memory, which are referred to as modules. As a first step, Add the module /modules/program
at the end of the file “iso/boot/grub/menu.lst” to make GRUB load a module. Next, we need to create a folder called modules in the iso folder using this mkdir -p iso/modules
command.
In order to instruct GRUB how to load our modules, the “multiboot header” the first bytes of the kernel must be updated. The below code shows the updated “loader.s” file.
Next, we are going to create a small program. At this point, a program can only do a few actions. As a result, a test program consisting of a very simple program that writes a value to a register suffices. Stopping Bochs after a while and checking the register for the proper number in the Bochs log will confirm that the program has been completed. The below code shows a simple program like that.
Next, we need to compile the code into a flat binary. NASM can do this with the flag -f. Run the below command to compile it.
nasm -f bin program.s -o program
Then we have to move both “program.s” and “program.o” files into the modules folder.
Before jumping to the program, we must find its location in memory. Assuming that the content of “ebx” is passed as a parameter to “kmain”, we can do this entirely from C. Since the pointer in “ebx” points to a multiboot structure, we have to create “multiboot.h” file like below.
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”. However, before just blindly following the pointer, you should check that the module got loaded correctly by GRUB. This can be done by checking the flags field of the “multiboot_info_t” structure. We need to check the field “mods_count” to make sure it is exactly 1.
All that remains is to jump to the code loaded by GRUB. Since it is easier to parse the multiboot structure in C than the assembler code. The below code shows the Complete “kmain.c” file.
If we start the kernel, wait until it finishes executing, and enter an infinite loop in the program, and then stop Bochs, we should see “0xDEADBEEF” in the “eax” log through the Bochs log. We have successfully launched a program in our operating system.
Thank You for reading!