Series catalog
- Preface
- Preparation work
- BIOS boot to real mode
- GDT and protection mode
- A preliminary
- loads and enters the kernel
- Display and print
- Global Descriptor Table GDT
- Interrupt handling
- virtual memory perfect
- implements heap and malloc
- First kernel thread
- multi-thread switching
- lock and multi-thread synchronization
- Enter user mode
- process
- system call
- Simple file system
- Load executable program
- keyboard driver
- run shell
the world of the kernel
loading the 160fecda961463 and entering the kernel , we finally came to the door of the kernel. This article will officially start the kernel phase. The good news is that we can finally start programming in C language. It seems that we can say goodbye to the sea of assembly, but assembly will still be used later. They all appear on a small scale, but they are all at a very important point. Node.
In general, the main tasks of the kernel will include the following parts:
- Establish a complete memory management mechanism, which mainly includes the
virtual memory
andheap / kmalloc
; - Establish a multi-task management system, namely the operation and management of
thread / process
- Implement simple hardware drivers, mainly
disk
andkeyboard
; - Realize loading and running of user-mode programs, and provide system calls (
system call
);
But before we start, we need to do some preparatory work, one of which is the screen display, after all, there must be something tangible and tangible, so that we can continue to get some positive feedback, and the print is related The function is also very important for subsequent development and debugging. Therefore, the main content of this article is the control of the screen display and the development of functions such as printing string. It is relatively easy and pleasant.
For the realization of this article, I mainly refer to the previously recommended JamesM's kernel development tutorials . It is still very clear, and you can also refer to it.
VGA display
By convention, the code of this article is given first, mainly in the src/monitor/ directory.
What we use is VGA text mode , an ancient display mode, its principle is simply to use 32KB
memory to control a 25-row * 80-column screen terminal. Where is this 32KB memory mapped?
The answer is the 0xB800 ~ 0xBFFF
segment with 1MB of memory. We can control the screen display by accessing and modifying the value of this segment of memory.
Of course, we have opened paging
and entered the kernel. The lower 1MB of memory has been mapped to 0xC0000000
above, so we can use 0xC000B800 ~ 0xC000BFFF
to access, which is the dark blue part in the figure.
We define the address of the display memory in the code:
// The VGA framebuffer starts at 0xB8000.
uint16* video_memory = (uint16*)0xC00B8000;
As mentioned above, there are 25 * 80 = 2000 characters on the screen, and each character needs to be controlled by 2 bytes, so a screen is 4000 bytes, so 32 KB can hold about 8 screens of content. However, although there are 8 screens of data, we only control the data of the first screen for the sake of simplicity, and the excess part will not be displayed, and functions such as up and down scrolling are not supported.
To print a character somewhere on the screen, just modify the memory at the position 0xC00B8000
Character display
On the screen, a character is controlled by 2 bytes. I directly posted the picture on Wikipedia:
The low byte stores the ASCII value of the character, and the high byte controls the color (including foreground and background colors) and blinking, which is very simple.
3 bits can display 8 colors:
#define COLOR_BLACK 0
#define COLOR_BLUE 1
#define COLOR_GREEN 2
#define COLOR_CYAN 3
#define COLOR_RED 4
#define COLOR_FUCHSINE 5
#define COLOR_BROWN 6
#define COLOR_WHITE 7
Add a bit in front to control highlight or normal. Note that only 4-bit foreground color can support this:
#define COLOR_LIGHT_BLACK 8
#define COLOR_LIGHT_BLUE 9
#define COLOR_LIGHT_GREEN 10
#define COLOR_LIGHT_CYAN 11
#define COLOR_LIGHT_RED 12
#define COLOR_LIGHT_FUCHSINE 13
#define COLOR_LIGHT_BROWN 14
#define COLOR_LIGHT_WHITE 15
Cursor control
In addition to characters, another important role on the screen is the cursor, which is generally used to mark the current position. But in fact, the cursor position has nothing to do with the position of the printed character. As long as you specify the coordinates, you can print the character anywhere, and let the cursor look lonely in the distance. However, as usual, we always make the cursor blink at the next printing position.
So the cursor position is defined in the code:
// Stores the cursor position.
int16 cursor_x = 0;
int16 cursor_y = 0;
To update the cursor position, several hardware ports need to be operated:
static void move_cursor_position() {
// The screen is 80 characters wide.
uint16 cursorLocation = cursor_y * 80 + cursor_x;
// Tell the VGA board we are setting the high cursor byte.
outb(0x3D4, 14);
// Send the high cursor byte.
outb(0x3D5, cursorLocation >> 8);
// Tell the VGA board we are setting the low cursor byte.
outb(0x3D4, 15);
// Send the low cursor byte.
outb(0x3D5, cursorLocation);
}
outb
function, and its corresponding inb
function, are defined in src/common/io.c , which are functions for operating ports.
Print characters
Below we need to define several print function functions, the most basic of course is to print a character:
void monitor_write_char_with_color(char c, uint8 color);
I will not post the detailed code, the main steps are:
- Spell out the 2-bytes representation of the printed character;
- Printing this character at the current cursor position is actually assigning 2-bytes to the display memory at the corresponding position;
- Scroll the screen, if necessary (overflowing the last line);
- Move the cursor to the next position;
With the most basic function of printing a character, then you can realize the printing of strings, decimals, hexadecimal integers, etc., so that the print-related functions are more abundant, which can meet many of our needs, but among them I think the most important function has not yet been implemented, and that is printf
.
Implementation of printf
printf
C standard library, it needs to be able to support multiple template parameters:
void printf(char* str, ...);
How should such a function be implemented?
In fact, I am not quite sure what the correct approach should be. Here is just to introduce my personal implementation. The key here is to be able to get the variable parameters of the ellipsis part, but they are actually pressed onto the stack when the printf function is called:
Therefore, the starting position of the following variable parameter is at the position of ebp + 12.
void monitor_printf(char* str, ...) {
void* ebp = get_ebp();
void* arg_ptr = ebp + 12;
monitor_printf_args(str, arg_ptr);
}
The function get_ebp is defined in src/common/util.S , which is very simple:
[GLOBAL get_ebp]
get_ebp:
mov eax, ebp
ret
In fact, there is a simpler way to char* str
, you can also get the address of the following parameter.
Of course, this method of obtaining parameters is not rigorous, it depends entirely on the architecture and the behavior of the compiler. At present, this solution is only suitable for 32-bit x86 architecture, and only works under the currently given compilation options. If you want to support more platforms and compilers, you need to do some extensions. But for our project, it should be completely enough. After all, this is just a system for teaching practice, so there is no need to be too demanding.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。