In the
previous article, the second in a row, we developed the necessary functions for working with strings from the C library. In this lesson, we implement a full debugging output to the screen - the kernel system log.
Table of contents
- Build system (make, gcc, gas). Initial boot (multiboot). Launch (qemu). C library (strcpy, memcpy, strext).
- C library (sprintf, strcpy, strcmp, strtok, va_list ...). Building the library in kernel mode and user application mode.
- The kernel system log. Video memory Output to the terminal (kprintf, kpanic, kassert).
- Dynamic memory, heap (kmalloc, kfree).
- Organization of memory and interrupt handling (GDT, IDT, PIC, syscall). Exceptions
- Virtual memory (page directory and page table).
- Process. Scheduler. Multitasking. System calls (kill, exit, ps).
- The file system of the kernel (initrd), elf, and its internals. System calls (exec).
- Character device drivers. System calls (ioctl, fopen, fread, fwrite). C library (fopen, fclose, fprintf, fscanf).
- Shell as a complete program for the kernel.
- User protection mode (ring3). Task Status Segment (tss).
Kernel system log
Before starting, we need to introduce some useful functions for working with input / output ports. I / O ports for a programmer are no different from ordinary cells in memory, except that there are separate commands for operating them. Devices that operate on these ports are connected to the memory bus. There is also a dedicated address space for them. We need two assembler functions to work with input / output ports, because as you already remember, I do not tolerate assembler inserts.
extern u_char asm_read_port(u_char port); extern void asm_write_port(u_int port, u_char data);
Similarly, two commands for controlling masked processor interrupts.
extern void asm_lock(); extern void asm_unlock();
Well, to save energy after fatal errors, you need a processor stop command.
extern void asm_hlt();
As you remember, video memory starts at 0xB8000, but I suggest writing messages to the buffer first in plain text format. And then just copy this buffer into the video memory taking into account the color attributes. To do this, I implemented several utilities for working with these buffers. One buffer will be for the kernel syslog, and the rest for virtual terminals. Scrolling the screen will also be performed on the buffer. And only the video_flush function will copy the buffer to the video memory, expanding it with attributes.
extern void video_init(); extern void video_disable_cursor(); extern void* video_scroll(char const* video_buff, char* pos); extern char* video_clear(char const* video_buff); extern void video_flush(char const* video_buff);
Now is the time to introduce the most commonly used features. The last two will be used to debug the kernel, when laziness is debugged by a debugger. Believe me, I have never used a debugger when I wrote this kernel.
extern void kpanic(char* message, ...); extern void kassert(const char* file, u_int line, bool expr); extern void kunreachable(const char* file, u_int line);
Well, actually, functions for working with the kernel system log. In order to control what is displayed on the screen, I entered the kmode function in the system log or user console. And to read the system log into the buffer, you will need the klog function, because user processes will not have access to the kernel except through system calls.
extern void kclear(); extern void kprintf(const char* format, ...); extern void kvprintf(const char* format, va_list list); extern void kmode(bool is_early); extern void klog(char* buf, u_int n);
I give the most interesting functions here:
extern void* video_scroll(char const* video_buff, char* pos) { char* ptr = (void*)video_buff; for (int i = 1; i < VIDEO_SCREEN_HEIGHT; ++i) { for (int j = 0; j < VIDEO_SCREEN_WIDTH; ++j) { ptr[(i - 1) * VIDEO_SCREEN_WIDTH + j] = ptr[i * VIDEO_SCREEN_WIDTH + j]; } } for (int j = 0; j < VIDEO_SCREEN_WIDTH; ++j) { ptr[(VIDEO_SCREEN_HEIGHT - 1) * VIDEO_SCREEN_WIDTH + j] = ' '; } pos -= VIDEO_SCREEN_WIDTH; return pos; }
extern void kvprintf(const char* format, va_list list) { char buff[VIDEO_SCREEN_WIDTH]; int len = vsprintf(buff, format, list); for (int i = 0; i < len; ++i) { if (buff[i] != '\n') { kputc(buff[i]); } else { int line_pos = (syslog_pos - syslog) % VIDEO_SCREEN_WIDTH; for (int j = 0; j < VIDEO_SCREEN_WIDTH - line_pos; ++j) { kputc(' '); } } } kflush(); }
static void kputc(char ch) { if ((size_t)syslog_pos - (size_t)syslog + 1 < VIDEO_SCREEN_SIZE) { *syslog_pos++ = ch; } else { syslog_pos = video_scroll(syslog, syslog_pos); kputc(ch); } }
See the detailed tutorial in the video tutorial.
References
→
Video tutorial for this article
→
Source code (you need a lesson3 branch)
Bibliography
- James Molloy. Roll your own toy UNIX-clone OS.
- Zubkov. Assembler for DOS, Windows, Unix
- Kalashnikov. Assembler is easy!
- Tanenbaum. Operating Systems. Implementation and development.
- Robert Love. Linux kernel Description of the development process.