WHEN Do We Write Our Chinese OS? (3)

zhaozj2021-02-16  52

In the previous one, we implemented a true boot program that turns into a 32-bit protection mode when the computer is started from the start-up state, and a true kernel written in C language The memory execution is loaded, the operation of the boot program has been completed, the next job is to write a good operating system's kernel. This is a very huge project. We need step by step. Similarly, we only complete very much today. Very small step. In the previous one, the kernel only shows a prompt, marking the kernel has been launched, and it does not have the interaction function with the user. In this article, we will complete a kernel that can accept user keyboard, this It is the first step in the kernel with interactive function - accepts user input. The kernel implemented by this article mainly uses the C language, and the interrupt processing section uses a small section of assembly code. Therefore, from this article you will also know how to implement mixed programming of C and assembly language, this kernel open source, use it The open structure of the C class mode, you can easily modify it, so that it is more functional, and performance is good. Here, I want to first introduce the program structure of this kernel so that you will be at a glance at the time of reading the source code.

This kernel defines a TVideo class, encapsulates the processing of VGA graphics, which is as follows: Class TVideo {public: static tpos getpos (); static void setpos (TPOS & POS); static void setpos (unsigned short x, unsigned short Y); static void ClearScreen (); static void Print (const char * msg, eColor FrontColor = clWhite, eColor BackColor = clBlack); static void Print (const char msg, eColor FrontColor = clWhite, eColor BackColor = clBlack); eColor BackColor Ecolor FrontColor; protected: tvideo () {}}; where TPOS is also a class, (with T start, the class name, with E-opening enumeration type), its statement is as follows Class TPOS {public: unsigned Short X; unsigned short Y;}; eColor which contains a color variable of enumerated types: enum eColor {clBlack, clBlue, clGreen, clCyan, clRed, clMagenta, clBrown, clLightGray, clDarkGray, clLightBlue, clLightGreen, clLightCyan, clLightRed, CllightMagenta, CLYELLOW, CLWHITE}; due to the C language package mechanism, this makes it easy to output on the screen in the program, let's take a look at the example of the main program to call them: char * msg0 = "! Welcome To HIT Operation System Version 0.0003 by xyb"; char * msg1 = "Please input:"; EColor color [] = {clLightBlue, clLightGreen, clLightCyan, clLightRed, clLightMagenta, clYellow, clWhite, clLightBlue}; int i = 0; while (msg0 [i]! = '/ 0') {TVIDEO :: Print (msg0 [i ], color [i% 8]);} TVideo :: setPos (0, 2); i = 0; While (MSG1 [I]! = '/ 0') {tvideo :: Print (MSG1 [i ], CLWHITE, Color [I% 8]);} This code is very simple, it is not explained, it uses various Color printing prompt information, below is its execution effect below is the effect after accepting the user input,

We will see how this is achieved. Read this article, it is best to have a little compilation foundation. In addition, it is best to read the top two, because many Dongdong is the first two, especially the second related ~~~ Ok, start to turn Question, first, let's introduce how to handle the graphics card. In the previous one, we already know that by writing the data directly to the memory, you can display it on the screen. Here we will introduce more in-depth introductions. Most of the current graphics cards. VGA standard compatible card, its score and graphics mode, this article only describes characters mode. In character mode, it has 25 lines, each line has 80 columns, the displayed address is 0xB8000, for each character you need to display, use two bytes to describe this, the first byte is to display this The ASCII code of the character, the second byte is the color properties of this character to display, where the high 4 bits are used to represent the background color, the low 4 bits are used to represent the foreground color, that is, the color, color of the character itself, color comparison table The following: 0 Black Black 1 Blue Blue 2 Green Green 3 Cyan Cyan 4 Red Red 5 Magenta Magenta 6 Brown Brown 7 Light Gray Giant Gray 8 Dark Gray Dark Gray 9 Light Blue Gales Highlight Blue A Light Green Green B Light CYAN GL bright blue C Light Red Highlight Red D Light Magenta Highlight Eyellow Yellow F White White So, you can combine the font color, how to combine? See the source program related code. We know that the 0xB8000 is the start address of the memory, which is the address where the characters at 0,0 are located. Where is the character position at X, Y? Because a line shows 80 characters, there are 25 lines, so we can calculate the following characters in memory in memory with the following formula: 0xB8000 Y * 80 x So if you want to display one at X, Y Red 'a' You can do this char * video = 0xB8000; video = y * 80 x; * video = 'a'; video ; * video = 0x04; Let's see how to handle the cursor first, we have to know We have two ports, and the port numbers are 0x3d4, 0x3d5, respectively. This first port is used to provide an index, and the second port is used to provide data. The position of the cursor is stored in two port registers of the index value at 14, 15. Each port register is only 8 digits, and the 14th register is low. Quantity: Offset = Y * 8 X The low 8 bits are put in the No. 14 register, and the high 8 bits are placed in the No. 15 register, just like this: OUT 0x3d4, 14; // Specify access 14 register OUT 0x3D5, Offset >> 8; OUT OX3D4, 15; OUT 0X3D5, OFFSET; (Note, this is not the final executable assembly code, just a schematic code, actual code Please refer to the source program) To get the cursor location to read these two registers Value, get the offset, then convert it into X, Y, see the source program. Below, we will enter the topic and will tell how to process keyboard inputs.

There are two ways to handle the keyboard input, one is to pass the loop, the constant query 0x60 port in the main program has data, if there is a data indicating a keyboard input, and this data is the keyboard of the input key, will scan The code is converted into a corresponding ASCII code and then display it. This situation is very simple, we will not be described in detail, you can change your original program to do this. Here, we often use a new way, which is made through interrupts. To use an interrupt mode, we must write your own interrupt handler, and let the CPU know where the interrupt handler of this interrupt is completed, which is completed by the IDT (interrupt descriptor table). Each of this table corresponds to an interrupt, and each entry indicates where the interrupt handler is located. Therefore, the primary task is to construct an interrupt descriptor table. The interrupt descriptor table can have 256 items, ie 256 interrupts.

Thirty-two, it is from 0 to 31, it has been occupied by CPU and hardware, so we need to construct our own interrupt and interrupt service program interrupt descriptor from the 33th interruption. Items account for 8 bytes (64-bit), so we define the following structure to handle it: typedef struct {unsigned long dword0; unsigned long dword1;} segment_desc; the following is the program we define interrupt descriptum: segment_desc IDT [ 256]; / * Interrupt No. 0 ~ 255 * / unsigned long idt_desc [2]; unsigned long keyboard_addr; unsigned long idt_offset = 0x8; / * IDT in the GDT location, this program is also the code segment in GDT The location * / / / 4 ICW TOPORT (0x20, 0x11); TOPORT (0xA0, 0x11); TOPORT (0x21, 0x20); TOPORT (0xA1, 0x28); TOPORT (0x21, 0x4); // In Inter In the hardware of the produced hardware, the connection between the PIC is TOPORT (0xA1, 0x2); // Connect the PIC1 IRQ2 to PIC2's IRQ1 TOPORT (0x21, 0x1); TOPORT (0xA1, 0x1); // The following setting interrupt Shield word, only license keyboard interrupt TOPORT (0x21, 0xfd); TOPORT (0xA1, 0xFF); keyboard_addr = (unsigned long) keyboard_interrupt; / * Keyboard interrupt handler location * / idt [0x21] .dword0 = (keyboard_addr & 0xfffff ) | IDT_OFFSET << 16); IDT [0x2 1] .dword1 = (keyboard_addr & 0xffff00) | 0x8e00; / * Get the location of the entire IDT * / idt_addr = (unsigned long) idt; idt_desc [0] = 0x800 (IDT_ADDR & 0xFFFFF) << 16); IDT_DESC [1] = IDT_ADDR >> 16; __ASM __ ("LIDT% 0 / N" "STI": "= m" (IDT_DESC)); / * Load the IDT table with the LIDT instruction, and open the interrupt with the STI command * / below To explain that this program TOPORT is a function defined by the program. For details, please see the source program. Here you need to know the data specified by the second parameter, sent in the port specified by the first parameter.

Let's take a look at the two lines ID [0x21] .dword0 = (keyboard_addr & 0xfff) | (IDT_OFFSET << 16); IDT [0x21] .dword1 = (keyboard_addr & 0xffff00) | 0x8e00; one IDT table item has 64 long 0 to 15 bits are the low 16 bits of the interrupt handler address, and 16 to 31 are the positions where the interrupt handler is located in the GDT (see the second article). The highest 16 bits are 16 digits of the interrupt handler address, and the 16 digits left in the middle are used to show that this is a interrupt gate or a trap door or a task door, and is 16 interrupt or 32-bit interrupts, etc. , Very complicated, want to learn more, please see the Intel CPU Developer Manual (online, you can find me if you find it). Fortunately, we don't need too much, just remember that it is 0x8E00 in normal case. Below we, let's explain the "// Send 4 ICW" parts in the code. We now know that we need to set up an interrupt, we need to fill the IDT (interrupt descriptor table), it needs to be pointed out when interrupt occurs Be executed. To make the interrupt system work, we need to program a PIC (programmable interrupt controller), PIC is a programmable interrupt controller, which can handle hardware interrupt requests (IRQ0, IRQ1 .., etc.), if there is no PIC We have to query which hardware is interrupted according to the rules, with the PIC, when the hardware is interrupted, the PIC sends the interrupt signal to the CPU, the CPU processing interrupt. We actually have two PICs, the first PIC1 (port number 0x20 ~ 0x21) handles the request for IRQ0 ~ IRQ7, the second PIC2 (port number 0xA0 ~ 0xA1) handles IRQ8 ~ IRQ15 request CPU only knows the logical sense Interrupt, does not distinguish whether physically software interrupt or hardware interrupts, so we must map the physical interruption of the CPU to the logical sense of the CPU known. In real mode, this work is made by BIOS, and we need us in protective mode.

So we need to initiate Pics, which enables control of PICS by sending some ICW (initialization command words), which must be accurately sent in order, because they are interdependent 1. Send ICW1 to PIC1 ( 20H) During PIC2 (A0H) 2. Send ICW2 to PIC1 (21H) and PIC2 (A1H) 3. Send ICW3 to PIC1 (21H) and PIC2 (A1H) 4. Send ICW4 to PIC1 (21H) and PIC2 ( The ICW1 is used in A1H) to tell PIC, there is ICW4, (when two PICs are working in series, this is required) ICW2 is used to tell PIC, map IRQ0 and IRQ7 to where (each PIC has eight pin processing Interrupt (IRQ0 ~ IRQ7) ICW3 is used to tell PIC, and they should use the ICW4 to tell PIC between the few IRQ (Senior Pins) to tell PIC. We work in the protection mode, and is the software to handle or automatically Treatment Interrupt ICW1 Structure 7 6 5 4 3 2 1 00 0 0 1 m 0 C II: If ICW 4 behind ICW3, set C: If it is not set, it indicates that the two PIC works in series M: indicate The lines of IR0 to IR7 are horizontally triggered. In the PC, this should be 0 (edge ​​trigger) ICW2 indicates the address of IRQ0 in the interrupt vector table, in protection mode, you should change it 7 6 5 4 3 2 1 0A7 A6 A5 A4 A3 0 0 0IRQ1 The address of the interrupt vector table is 1, IRQ2 ~ IRQ7 uses this type of ICW3 only in these two PICs to be transmitted in series (ICW1 C position 0 It is the purpose of establishing a connection between two PICs about PIC1 on PIC1. 7 6 5 4 3 2 1 0IR7 IR6 IR5 IR4 IR3 IR2 IR1 IR0 If IR0 is 0, it indicates that the root line links to an peripheral device. If IR0 is set 1, it indicates that the rest of this root line and the PIC2 are associated with this similar ICW3 structure 7 6 5 4 3 2 1 00 0 0 0 I r q last 3 is PIC1, coupled with PIC2 IRQ ICW4 structure 7 6 5 4 3 2 1 00 0 0 0 0 0 EOI 80x86eoi indicates that the interrupt is the ultimate automatic processing or by software assistance. This bit is usually 0 in the PC, indicating that the software must handle the last sweeping of the interrupt 80x86 indicates whether the PIC has the above basic knowledge under the architecture of 80x86, you should now understand. Let's take a look at: Interrupt Shield PIC 1 Processing Interrupt There is 0 system clock 1 keyboard 2 redirect to IRQ9 (IRQ1 of PIC2), if this bit is set 1, the screen off all from PIC2 interrupt 3 serial port 1 ( COM2 / 4) 4 serial port 2 (COM1 / 3) 5 Sound card 6 floppy drive 7 parallel port PIC 2 processing interrupt with 0 real time clock 1 from IRQ2 (PIC1) 2 Reserved 3 Reserved 4 Mouse 5 Mathematical Cooperation 3 Hard Disk 7 Reserved A location bit 0 indicates that it allows it to issue an interrupt request, set to 1 means that the shield is broken, there is such two lines of code: TOPORT (0x21, 0xFD); TOPORT (0xA1, 0xFF); where 0xFD is 11111101; Allow the second interrupt request of PIC1, ie the keyboard interrupt request.

Finish! This mission has been successfully completed ~~~ ^ _ ^, all source code can be downloaded in the following address ftp://202.118.239.46/incoming/other/btc/temp/pyos/pyos3.ziptw: during the process of this experiment In BBS, I have been encouraged by many teachers' students. It is because of this presence of support forces, so I got the power of this experiment, I am deeply grateful! At the same time, there are countless mistakes in this, I hope that teachers, classmates, big cows: P ~~, criticism! Still a mail: xieyubo@126.com Original: http://purec.binghua.com/Article/showArticle.asp? ArticleId = 5

转载请注明原文地址:https://www.9cbs.com/read-20489.html

New Post(0)