3

Series catalog

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 and heap / kmalloc ;
  • Establish a multi-task management system, namely the operation and management of thread / process
  • Implement simple hardware drivers, mainly disk and keyboard ;
  • 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.


navi
612 声望191 粉丝

naive