Developing a monolithic Unix-like OS - C Library (2)

In a previous article, we learned how to run the Hello World kernel and wrote a couple of functions for working with strings. Now it's time to expand the C library so that you can implement kprintf and other necessary functions. Go!



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, heap (kmalloc, kfree).
  5. Organization of memory and interrupt handling (GDT, IDT, PIC, syscall). Exceptions
  6. Virtual memory (page directory and page table).
  7. Process. Scheduler. Multitasking. System calls (kill, exit, ps).
  8. The file system of the kernel (initrd), elf, and its internals. System calls (exec).
  9. Character device drivers. System calls (ioctl, fopen, fread, fwrite). C library (fopen, fclose, fprintf, fscanf).
  10. Shell as a complete program for the kernel.
  11. User protection mode (ring3). Task Status Segment (tss).


Library C



First you need to implement types with explicit dimensions.



Since we will collect under one platform, until our definitions and implementations are correct. It is not universal, but that is why it is readable and simple. I take the approach that sometimes itโ€™s better to do something thatโ€™s not universal, but that meets current requirements, because supporting universal solutions is extremely difficult.



typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned char u_char; typedef unsigned short u_short; typedef unsigned int u_int; typedef unsigned int u_long;
      
      





It does not hurt to introduce the Boolean type.



 #pragma once /* types */ typedef int bool; #define true 1 #define false 0 #define null 0
      
      





Also, a couple of macros for working with bytes will not hurt us.



 typedef unsigned long size_t; #define HIGH_WORD(addr) ((addr & 0xffff0000) >> 16) #define LOW_WORD(addr) ((addr & 0xffff)) #define LOW_BYTE(addr) ((addr & 0x00ff)) #define HIGH_BYTE(addr) ((addr & 0xff00) >> 8)
      
      





The following macros are needed to work with a variable number of arguments. This approach only works if the function follows the C language calling convention, in which the function arguments are passed through the stack starting from the last.



 typedef size_t* va_list; #define va_start(l, a) (l = (void*)((size_t)&a) + sizeof(a)) #define va_end(l) (l = (void*)0) #define va_arg(l, s) (*(s*)(l++))
      
      





Of course, one could go the other way. Instead of defining your own functions, try to use the built-in library and replace functions that will access the kernel through LD_PRELOAD. But I like to control the process completely, so let's leave this option as an idea for those who start to write their OS on this tutorial.



Further, in the video tutorial, we will consider the implementation of the following library functions. The implementation does not claim to be optimality and completeness, but I think it claims to be simplicity and readability. I just note that we are using a thread-safe implementation of the strtok function, which is called strtok_r. And we came up with the strinv and strext functions ourselves in the last lesson. If you are familiar with the C language, I think you will be familiar with almost all of the functions listed below.



 extern int strlen(const char* s); extern char* strcpy(char* s1, const char* s2); extern char* strncpy(char* s1, const char* s2, u_int n); extern void* memcpy(void* buf1, const void* buf2, u_int bytes); extern void* memset(void* buf1, u8 value, u_int bytes); extern int strcmp(const char* s1, const char* s2); extern int strncmp(const char* s1, const char* s2, u_int n); extern char* strcat(char* s1, const char* s2); extern char* strext(char* buf, const char* str, char sym); extern int strspn(char* str, const char* accept); extern int strcspn(char* str, const char* rejected); char* strchr(const char* str, char ch); extern char* strtok_r(char* str, const char* delims, char** save_ptr); extern char* memext(void* buff_dst, u_int n, const void* buff_src, char sym); extern char* itoa(unsigned int value, char* str, unsigned int base); extern unsigned int atou(char* str); extern char* strinv(char* str); extern unsigned int sprintf(char* s1, const char* s2, ...); extern unsigned int snprintf(char* s1, u_int n, const char* s2, ...); extern unsigned int vsprintf(char* s1, const char* s2, va_list list); extern unsigned int vsnprintf(char* s1, unsigned int n, const char* s2, va_list list);
      
      





The routine is done away with. The end of the boilerplate code. The next lesson will be much more complicated and interesting. If you want to sponsor a project, you can offer your optimal implementations of library functions.



References



Developing a Monolithic Unix-Like OS - Getting Started

Video tutorial for this article

Source code (you need a lesson2 branch)



Bibliography



  1. James Molloy. Roll your own toy UNIX-clone OS.
  2. Zubkov. 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