Developing a Monolithic Unix-Like OS - Getting Started (1)

In connection with the negative reviews of the trial article “Developing a Microkernel Unix-like OC - Scheduler”, I decided to restart the series of articles, taking into account some comments. Now, having realized my target audience, I was able to shift the focus from my own kind to those who really need it.



Responses to comments to the previous article.



Remark 1. The scheduler is too simple.



Since the publication of the previous article, the code has changed a lot. A full-fledged core was formed, which ceased to be micronuclear. There is support for initial ram disk (kernel file system), support for virtual memory (MMU). It has become possible for the kernel to write custom programs. There were system calls and the clib library (stdio, string). So, the /initrd/sh.elf shell is a separate program that is parsed by the elf bootloader and launched as a process.



List of shell commands: exec <file.elf>, ps, kill, exit. In this case, the shell can be launched from the shell itself (demonstrating multitasking).



Note 2. Too much magic behind the scenes in the video tutorials.



I decided to highlight the text that I am currently explaining. Also headed for beginners who need clarification and the simplest things. Honestly, I was surprised that beginners who have no experience in programming are interested in such a complex topic, although it’s a sin to start, I started from that. I shifted the bias from explaining my OS to explaining how to write your OC.



Remark 3. Where is the link to github ?



Now she is. In the description of the video on YouTube (yes, I want you not to pass by my video tutorials, at least take a look at one eye).



Remark 4. You should start with the assembly first.



That is what we will do.



Remark 5. You do not write anything, but simply comment.



Yes, it was a mistake to talk about something big right away. Now we will move step by step, gradually, starting with the simplest Hello world kernel. Video tutorials will allow you to create a holistic picture of the world, and the source code on the github will plunge you into details.



Table of contents.



1. Build system (make, gcc, gas). Initial boot (multiboot). Launch (qemu). C library (strcpy, memcpy, strext).

2. C library (sprintf, strcpy, strcmp, strtok, va_list ...). Building the library in kernel mode and user application mode.

3. The kernel system log. Video memory Output to the terminal (kprintf, kpanic, kassert).

4. Dynamic memory, a bunch (kmalloc, kfree).

6. Organization of memory and interrupt handling (GDT, IDT, PIC, syscall). Exceptions

5. Virtual memory (page directory and page table).

6. The process. Scheduler. Multitasking. System calls (kill, exit, ps).

7. The file system of the kernel (initrd), elf and its internals. System calls (exec).

8. Character device drivers. System calls (ioctl, fopen, fread, fwrite). C library (fopen, fclose, fprintf, fscanf).

9. The shell as a complete program for the kernel.

10. User protection mode (ring3). Task Status Segment (tss).



Go. Part 1. Build system and launch



In the article I will list only the key steps . For a detailed explanation, refer to the video tutorial for this article.



You need Linux. We will collect good old make. In order to compile the kernel executable file, the following flags are needed:



CC_FLAGS=-g -m32 -isystem $(IDIR) -I include -DKERNEL=1 -fno-stack-protector -Wall -Werror -fno-pie AS_FLAGS=-g --32 LD_FLAGS=-m elf_i386
      
      





As a hardware emulator, install the qemu emulator on Linux. We will run our kernel like this:



 qemu-system-i386 -kernel ./bin/kernel.elf
      
      





It will also require a small script for the linker. It is needed in order to load sections at the correct address and in the correct order. Also in it we will indicate the entry point:



 OUTPUT_FORMAT(elf32-i386) ENTRY(start) SECTIONS { . = 0x100000; .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss) } }
      
      





Since the kernel will be loaded according to the multiboot specification, a header is required at the beginning of the code section:



 .code32 .text # multiboot spec .align 4 multiboot: .long 0x1BADB002 # magic .long 0x00 # flags .long -(0x1BADB002 + 0x00) # checksum. m+f+c should be zero
      
      





It is recommended that you immediately switch to your own stack and set up a global descriptor table. Define our own stack:



 .bss .fill 8192,1 # 8Kb stack:
      
      





Let's write an entry point. You may find the gnu assembler syntax unusual. I once also preferred the Intel syntax, then having tasted it, delving into the Linux source code, I completely preferred the AT & T syntax for myself. The main thing to remember is that they have the opposite operands. The rest will be intuitive.



 start: cli movl $stack,%esp push %esp push %ebx /* address of struct multiboot_t */ call kernel_start /* should never return */ hlt
      
      





This completes the boilerplate code. The fun begins. Now we can write the code in C. And first of all, we will define the greeting message.



 char *hello = "Hello world!"; int screen_size = 80 * 25;
      
      





Next, we write the entry point itself to which the assembler code will transfer control:



 /* * Api - Kernel entry point */ extern void kernel_start(struct multiboot_t* multiboot, void* kstack) { char *video = (char*)0xB8000; char buff[screen_size + 1]; video[screen_size] = '\0'; memset(buff, ' ', screen_size); strext(video, buff, 0x7); strext(video, hello, 0x7); }
      
      





Here we simply display the message on the screen. In principle, you can completely copy the code of the first lesson, since it is template and will never be changed. In order to display something on the screen, you just need to write it directly into the video memory, supplementing each character with an attribute symbol. To do this, you will need your own C language library, which we will write ourselves for our needs. So it will be easier to control the process. So, for example, today we have at our disposal 2 familiar functions (strcpy, memcpy) and one of our own strext in order to insert the attribute byte after each character.



Conclusion



That's all for today. Watch the video tutorial and try to do the same on your own. If it doesn’t work out, you can peek into the source code for the lesson on the github. Link to github in the video tutorial description:



Literature





1. James Molloy. Roll your own toy UNIX-clone OS.

2. Teeth. Assembler for DOS, Windows, Unix

3. Kalashnikov. Assembler is easy!

4. Tanenbaum. Operating Systems. Implementation and development.

5. Robert Love. Linux kernel Description of the development process.



All Articles