Hand writing the operating system

xiaoxiao2021-03-06  99

Write the operating system yourself from:

http://www.pagoda-ooos.org/

Please contact this site

The Free Software Community is a place full of freedom and dreams, which created another miracle in more than 10 years. However, these miracles of these miracles are not just Stallman, nor is it Linus Torvalds, but is active in the world's countless developers. In the use of a variety of powerful free software, I will always be full of respect for their developers, and I hope that I can become one of them in one day. Many people who are full of love for the free community, although I want to strive to strive in it, but I don't know what to do. So, please start with us to write a simple operating system! What we have to do may worry that you have neither learn the principle of computer, and have not learned the principle of operating system. Don't understand the language of assembly. Do you know how to use the C language, can you write an operating system? The answer is no problem. I will take you step by step to complete your operating system. Of course, if you learn the above content, it is ok. The first thing to confirm the processor (that is, the CPU) is controlled. For PCs, when startup, the CPU is in real mode status, which is equivalent to just an Intel 8086 processor. That is, even if you have a Pentium processor now, its function can only be 8086 level. From this point of view, you can use some software to convert the processor to the famous protection mode. Only in this way can we make full use of the powerful function of the processor. Writing the operating system begins to control the BIOS and remove the program stored in the ROM. The BIOS is used to perform POST (Power On Self Test, self-test). Self-test is to check the integrity of the computer (such as whether the peripheter is working properly, the keyboard is connected, etc.). After all of this, you will hear the PC speaker to make a crisp sound. If everything is normal, the BIOS selects a boot device and read the first sector of the device (ie, the boot sector), and then the control process will be transferred to the specified location. The boot device may be a floppy disk, an optical disk, a hard disk, or other selected device. Here we put the floppy disk as a startup device. If we have written some code in the boot sector of the floppy disk, it is executed. Therefore, our purpose is clear, just write some programs to the boot sector of the floppy disk. First use the 8086 assembly to write a small program, then copy it to the boot sector of the floppy disk. In order to achieve a copy, you have to write a C program. Finally, start the computer using a floppy disk. Required tools AS86: This is a compilation program that converts the code to the target file. LD86: This is a connector, the target code generated by the AS86 is converted into a real machine language. The machine language is the form of 8086 can interpret. GCC: The famous C programmer. Because we need to write a C program to transfer your OS to the floppy disk.  A empty floppy disk: It is used to store the written operating system, but also the startup device.  A computer with Linux: This machine can be very old, 386, 486 can. There will be AS86 and LD86 in most standard Linux distributions. These two tools are included in the Red Hat 7.3 I am using, and in the default, it is already installed in the machine. If you don't use these two tools, you can download (http://www.cix.co.uk/~mayday/), both of which are included in a package called bin86. In addition, the relevant documents can also be available online (www.linux.org/docs/ldp/howto/assembly-howto/as86.html).

Beginning to use a editor you like to enter the following: Entry StartStart: MOV AX, # 0xb800mov es, axseg esmov [0], # 0x41seg esmov [1], # 0x1floop1: jmp loop1 This is a compilation that AS86 can read program. The first sentence indicates the entry point of the program and declares that the entire process begins at START. The second line indicates the location of the Start, indicating that the entire program begins to start from START. The 0xB800 is the start address of the memory. # Indicates that it is an immediate number. Execute a statement: MOV AX, # OXB800 AX register changes to 0xB800, which is the address of the memory. This value is moved to the ES register, and ES is an additional segment register. Remember that 8086 has a segmentation architecture. Its segment registers are code segments, data segments, stack segments, and additional sections, corresponding register names, CS, DS, SS, and ES, respectively. In fact, we sent the memory address into additional sections, so anything sent to the additional segment will be sent to the memory. To display characters on the screen, you need to write two bytes to your memory. The previous one is the ASCII value of the characters to display, and the second byte indicates the properties of the character. Attributes include the foreground color, background color and whether the background color is blinking, and so on. SEG ES indicates that the next command to be executed is to the ES segment. So, we send value 0x41 (a character represented in ASCII is a) to the first byte of the memory. Next, you want to send the properties of the character to the next byte. The 0x1f is entered here, which is a character that shows a white in a blue background. Therefore, if this program can be executed, you can get a white A on the blue bottom on the screen. Then it is a loop. Because after the task is executed, the program is either end, or use a loop to make it run forever. Name this file boot.s, then store. The concept of memory here is not very clear, it is necessary to further explain it. Assume that the screen consists of 80 columns × 25 lines, then the first line takes 160 bytes, one byte is used to represent characters, and one byte is used to represent the properties of the character. If you want to display a character in the third line, you want to skip the No. 0 and 1 bytes of the memory (they are used to display the first column), the second and 3 bytes (they are used to display the second Column), then put the ASCII code value of the display character into the 4th byte, write the properties of the characters to the 5th byte. Write the program to the start sector to write a C program, write my operating system into the first sector of the floppy disk. The program content is as follows: #include / * Unistd.h Requires this file * / # include / * contains read and write functions * / # include int Main ) {char boot_buf [512]; int FLOPPY_DESC, FILE_DESC; File_Desc = Open ("./ Boot", O_RDONLY); Read (file_desc, boot_buf, 510); close (file_desc); boot_buf [510] = 0x55; boot_buf [511 ] = 0xAA; FLOPPY_DESC = Open ("/ dev / fd0", o_rdwr); Lseek; Write (FLOPPY_DESC, BOOT_BUF, 512); Close (FLOPPY_DESC);} First, open Boot in read-only mode File, then copy the file descriptor to the file_desc variable when the file is opened. Read 510 characters from the file, or read until the end of the file. In this case, since the file is small, it is read to the end of the file. Then close the file.

The last 4 lines of code open the floppy disk drive device (generally / dev / fd0). Use LSeek to find the file start and write 512 bytes from the buffer. In the Help page of Read, Write, Open, and LSeek, you can see all related parameters and how the usage is used. There are two lines in the program. It is more difficult to understand: boot_buf [510] = 0x55; boot_buf [511] = 0xAA; this information is used for BIOS, if it identifies that the device is a bootable device, then in 510th and 511 The location, this value should be 0x55 and 0xAA. The program will read the file boot into buffer named boot_buf. It requires to change the 510 and 511 bytes, and then write boot_buf to the floppy disk. If the code is executed, the first 512 bytes on the floppy contain the startup code. Finally, save the file as Write.c. Compilation Run Use the command to turn the file to an executable: as86 boot.s -o boot.old86 -d boot.o -o bootcc write.c -o Write first compiles the Boot.s file into the target file Boot.o , Then connect the file into the final boot file. Finally, the C program compiles the executable WRITE file. Insert a blank floppy disk, run the following program: ./write restarts the computer, performs the BIOS interface setting, and set the floppy disk as the first device. Then insert the floppy disk, the computer starts from the floppy disk. After the startup is complete, you can see a letter A (Blue Bit) on the screen, the start speed is very fast, almost instantly completed. This means that the system has already started from the floppy disk we make, and executes programs just written to start sectors. Now it is in a state in an infinite loop. So, if you want to enter Linux, you must take your floppy disk and restart the machine. At this point, this operating system is completed, although it does not implement any function, but it can already start the machine. The previous issue, I tell how to write some code on the boot sector of the floppy disk, then start from the floppy disk. Making a start-up sector, we should know how to use the BIOS interrupt before switching to the protection mode. The BIOS interrupt is some low-level programs provided by the BIOS that makes the operating system's creation easier. In this article, we will learn to deal with BIOS interrupts. Why use the BIOS BIOS to copy the boot sector to the RAM and perform these code. In addition, BIOS will do a lot of other things. When an operating system is started, there is no graphics driver, floppy disk drive, etc. in the system. Therefore, it is impossible to include any driver in the start sector, and we have to take other ways. At this time, BIOS can help us. The BIOS contains various programs that can be used, including detecting installed devices, control printers, computing memory sizes, etc. for various purposes. These programs are what the BIOS is interrupted. How to call the BIOS interrupt in a general programming language, the call is a very easy thing. For example, in the C language, if there is a program called Display, it comes with two parameters, where parameter noofchar represents the number of characters displayed, the parameter Attr represents the properties of the display character. So to call it, just give the name of the program. For interrupt calls, we use int instruction in assembly language. For example, when you want to display something in the C language, the instructions used are as follows: Display (NOFCHAR, ATTR); when using the BIOS, the instructions to implement the same function are as follows: int 0x10 How to transfer parameters before calling the BIOS interrupt We need to send some specific values ​​in the registers.

Suppose it is to use the BIOS interrupt 13h, the interrupt function is to transfer the data from the floppy disk to the memory. Before calling the interrupt, you must first specify the segment address of the copy data, specify the driver number, the track number, the sector number, and the number of sectors to be transmitted. Then, send the corresponding register to the corresponding register. Before making the following steps, the reader must have a more clear understanding of this. In addition, a more important fact is that the same interrupt can often achieve a variety of different functions. The exact functionality implemented by the interrupt depends on the function number selected, and the function number is generally in the AH register. For example, the interrupt 13h can be used to read the disk, write disk, etc. Things we have to do this time our source code consists of two assembly language programs and a C program. The first assembler is the code that boots the sector. In the boot sector, the code we write is to copy the second sector in the floppy disk to the 0x500 of the memory segment (the address is 0x5000, that is, the offset address is 0). At this time we need to use the BIOS interrupt 13h. At this time, the code that starts the sector will transfer the control to 0x500. In the second assembly file, the code will use the BIOS interrupt 10h to display an information on the screen. The function of the C procedure is to copy the executable file 1 to the start sector, copy the executable file 2 to the second sector of the floppy disk. The start sector code uses an interrupt 13h, and the start sector loads the contents of the floppy disk in the second sector of the floppy disk to the memory 0x5000 (the segment address is 0x500). The following code is a code for implementing this, saving it to the file sbect.s. Loc1 = 0x500thry StartStart: MOV AX, # Loc1mov ES, AXMOV BX, # 0 MOV DL, # 0 MOV DH, # 0 MOV CH, # 0 MOV CL, # 2 MOV Al, # 1 MOV AH, # 2 Int 0x13jmpi 0 The # LOC1 The first line of the code is similar to a macro. The next two rows are loaded to the ES register to the ES register, which is where the second sector code on the floppy disk will be copied (the first sector is a start sector). At this time, the offset within the segment is set to 0. Next, the drive letter is sent to the DL register, where the head number is sent to the DL register, and the track number is sent to the CH register, and the sector number is sent to the CL register, and the sector number is sent into the Al register. The function we want to implement is to send the sector 2, the magnetic track number 0, the content of the drive number 0 is 0x500. All of these parameters correspond to the 1.44MB floppy disk. The 2 is sent to the AH register, which is selected from the corresponding functionality provided by the interrupt 13h, that is, the function of transferring data from the floppy drive drive. Finally, the interrupt 13h is called, and it is transferred to a segment address 0x500 of the offset zero. The code in the second sector in the second sector is shown below (saving these code into file sBect2.s): Entry StartStart: Mov Ah, # 0x03 xor Bh, Bhint 0x10 MOV CX, # 26 MOV BX, # 0x0007 MOV BP, # mymsgmov AX, # 0x1301 int 0x10 loop1: jmp loop1mymsg: .bete 13, 10.ascii "Operating System is loading ..." The above code will be loaded to the segment address of 0x500, and carried out. In this code, the interrupt 10h is used to obtain the current cursor position and then display information. From the third line to the 5th line to obtain the current cursor position, the interrupt 10h is selected is functional 3. Then, the contents of the BH register are cleared and the string is sent to the CH register. In BX, we sent the page and displayed properties. Here, we want to show white characters on a black background.

Then, in the address to display the character, the information consists of two bytes, and its value is 13, which respectively corresponds to the ASCI II value of the Enter and the LF (Renewal), respectively. Next is a string consisting of 29 characters; the functionality implemented below is the output string and move the cursor; finally call the interrupt, then enter the loop. The source code of the C program Code C program is stored as a Write.c file. #include / * unistd.h Needs this * / # include / * contains read / write * / # include int main () {char boot_buf [512] ; int FLOPPY_DESC, FILE_DESC; FILE_DESC = Open ("./ bsect", o_rdonly); read (file_desc, boot_buf, 510); close (file_desc); boot_buf [510] = 0x55; boot_buf [511] = 0xAA; Floppy_Desc = Open ("/ Dev / fd0", o_rdwr; lseek (Floppy_Desc, 0, seek_set); Write (Floppy_Desc, Boot_BUF, 512); File_Desc = Open ("./ SECT2", O_RDONLY); read (file_desc, boot_buf, 512) Close (File_Desc); Lseek (Floppy_Desc, 512, Seek_set); Write (FLOPPY_DESC, BOOT_BUF, 512); Close (FLOPPY_DESC);} In the previous period, I have introduced how to operate the floppy disk. Now this process is slightly different, first copy the executable BSECT compiled by bsect.s to the boot sector of the floppy disk. Then copy the executable file SECT2 generated by SECT2.s to the second sector of the floppy disk. Place the above files under the same directory, then compile it, the method is as follows: as86 bsect.s -o bsect.old86 -d bsect.o bsect repeats the above operations on the SECT2.s file, Out of executable SECT2. Compile Write.c, execute the Write file after inserting the floppy disk, the command is as follows: cc Write.c -o Write./write Next We have to do things After starting from the floppy disk, you can see the displayed string. This is done using the BIOS interrupt. The next issue to do is to achieve the conversion of the real mode to the protection mode in this operating system. Now, this operating system is getting closer to the Linux kernel of Linus Torvalds. Therefore, to switch this system immediately below the protection mode. What is protection mode has been continuously updated from 8086, 8088, 80286, from 8086, 8088, 80286, to 80386, 80486, Pentium, Pentium II, Pentium 4, etc. Also changing. After 80386, some new features have been provided to compensate for some of the 8086 defects. This includes memory protection, multitasking and memory using more than 640KB or more, and still maintains compatibility with 8086 families. That is to say that 80386 still has all the features of 8086 and 80286, but there is a lot of enhancements. Early processors were working under real mode, and 80286 was introduced in the protection mode, while 80386 was greatly improved. In 80386, the protection mode provides programmers with better protection and provides more memory.

In fact, the purpose of the protection mode is not to protect the program, but to protect all programs other than the program (including the operating system). Briefly, the protection mode is the most natural mode of the processor. In this mode, all features of the processor and all features of the architecture are available, and the highest performance can be achieved. Protection mode and real mode are seen from the surface, the protection mode and real mode do not have much difference, both use memory segments, interrupts, and device drivers to handle hardware, but there are many differences. We know that memory in real mode is divided into segments, each segment is 64kB, and such segment addresses can be represented by 16. The processing of memory segments is processed by internal mechanisms associated with segment registers, and content of these segments (CS, DS, SS, and ES) form part of the physical address. Specifically, the final physical address consists of 16-bit segment addresses and 16-bit segment offset addresses. Expressed with the formula as: physical address = segment address offset address of the left shift 4 bits. In protection mode, the segment is defined by a set of tables called "descriptor table". Segment registers are stored in pointers to these tables. There are two tables for defining the memory segments: Global Descriptive Table (GDT) and Local Descriptive Table (LDT). GDT is a segment descriptor array, which contains basic descriptors that all applications can be used. In the real mode, the length is fixed (64kb), and in the protection mode, the length is variable, and its maximum is 4GB. LDT is also an array of segment descriptors. Unlike GDT, the LDT is a segment, which is stored, and a segment descriptor that does not require global sharing. Each operating system must define a GDT, and each running task has a corresponding LDT. The length of each descriptor is 8 bytes, and the format is shown in Figure 3. When the segment register is loaded, the segment base address is obtained from the corresponding table entry. The contents of the descriptor are stored in a SHDOW Register in a programmer so that the next time the same segment can be used without each time to extract each time. The physical address is composed of the base address in the image register from a 16-bit or 32-bit offset. The difference in real mode and protection mode can be seen clearly from Figures 1 and 2. Figure 1 Real Mode Addressing Figure 2 Addressing Figure 3 In the form of the descriptor in the protection mode, there is an interrupt descriptor table (IDT). These interrupt descriptors tell the processor to find the interrupt handler. Like the real model, each interrupt has an entry, but the format of these entrance is completely different. Because I didn't use the IDT during the process of switching to the protection mode, there is not much introduction. Entering protection mode 80386 has 4 32-bit control registers, named CR0, CR1, CR2, and CR3, respectively. CR1 is reserved in future processors, and is not defined in 80386. The CR0 contains the control flag of the system for controlling the operation mode and status of the processor. CR2 and CR3 are used to control paging mechanisms. Here, we are concerned about the PE bit control of the CR0 register, which is responsible for switching between real mode and protection mode. When PE = 1, the processor is operated under the protection mode, which corresponds to the segment mechanism used to correspond to the corresponding content described above. If PE = 0, then the processor works below the real mode. Switch to the protection mode, actually put the PE position to 1. In order to switch the system to the protection mode, you have to do some other things. The program must initialize the segment register and control register of the system. After the PE bit 1, the jump instruction is also performed. The process is briefly described as follows: 1. Create a GDT table; 2. Enter the protection mode by setting the PE bit to 1; 3. Perform jump to clear any instructions read in the real mode. The following is used to implement this switching process.

Need to: ◆ A blank floppy disk ◆ NASM compiler The following is the source code for the entire program: org 0x07c00; start address is 0000: 7C00 JMP short begin_boot; skip other data, jump to the beginning of the boot program Bootmesis DB "OOS boot sector loading ..." pm_mesg db "switching to protected mode ...." DW 512; Number of bytes of each sector DB 1; each cluster number DW 1; reserved sector number DB 2DW 0x00e0 DW 0x0B40 DB 0x0F0 DW 9 DW 18 DW 2; Read the sector number DW 0; hidden fan area number print_mesg: MOV AH, 0x13; function 13 hidden 10h, write a string MOV Al, 0x00 on the screen; decision call After the function, the position MOV BX, 0x0007; set the display attribute MOV CX, 0x20; this string length is 32 MOV DX, 0x0000; the starting line of the cursor and the column INT 0x10; call the BIOS interrupt 10hret; return call Procedure get_key: MOV AH, 0x00 INT 0x16; Get_Key uses the function of the interrupt 16h, read the next character RETCLRSCR: MOV AX, 0x0600; function using the interrupt 10h, implement the volume screen, if Al = 0, clear screen MOV CX , 0x0000; clear screen MOV DX, 0x174F; volume screen to 23,79mov BH, 0; use color 0 to fill int 0x10; call 10h interrupt RetBegin_boot: Call Clrs; first clear screen MOV BP, bootmesg; provide string address Call Print_mesg; Output information CALL GET_KEY; Waiting for the user to press any key bits 16Call Clrs; clear screen MOV AX, 0xB800; make GS point to display memory MOV GS, AX; display a brown amov Word [GS: 0], 0x641; display Call get_key; call GET_KEY Wait for the user to press any key MOV BP, PM_MESG; set the string pointer call print_mesg; call the print_mesg subroutine call get_key; wait button Call ClrsCr; Cleanup CLI; Off Interrupt LGDT [GDTR]; Load GDT MOV EAX, CR0 OR Al, 0x01; Set the protection mode bit MOV CR0, EAX; send the changed words to the control register JMP CodeSel: Go_PMBITS 32GO_PM: MOV AX, Datasel MoV DS, AX; initialization DS and ES, point to data segment MOV ES, AX MOV AX, VIDEOSEL; initialize GS, point to display memory MOV GS, AX MoV Word [GS: 0], 0x741; in protection mode Show a white character aspin: JMP spin; loop bits 16GDTR: DW gdt_end-gdt-1; GDT length DD GDT; GDT physical address GDTNULLSEL EQU $ -Gdt; $ pointing to the current location, so nullsel = 0HGDT0; empty description DD 0 DD 0; all segment descriptors are 64-bit CodeSel EQU $ -GDT; this is 8h is also the second descriptor Code_GDT DW 0x0ffff; segment descriptor is 4GBDW 0x0000 DB 0x00 DB 0x09A DB 0x0CF DB 0x00 Datasel EQU $ -GDT Data_GDT DW 0x0fff DW 0x0000 DB 0x00 DB 0x092DB 0x0CFDB 0x00VIDEOL EQU $ -GDT DW 3999 DW 0x8000;

The base is 0xB8000DB 0x0BDB 0x92 DB 0x00 DB 0x00GDT_ENDTIMES 510 - ($ - $$) DB 0 DW 0X0AA55 There is a file named abc.asm, using the command NASM ABC.ASM, will be a name For the ABC file. Then insert the floppy disk, enter the command: DD if = ABC of = / dev / fd0. This command will write the file ABC into the first sector of the floppy disk. Then restart the system, you will see the following information: * OOS booting ................. * a (brown) * Switch to protected mode .... * A (white) The explanation of the code is given above. Code, here I explain some of the above code. ◆ The function used is below the code in the code: Print_mesg This subroutine uses the function of the BIOS interrupt 10h, which is written to the screen. Property control is achieved by sending different values ​​to some registers. Interrupt 10h is used in a variety of string operations, we send the subunit number 13H to the AH, which is used to specify a string to print. 0 in the Al register illustrates the starting position returned by the cursor, 0 indicates that the cursor returns to the next row after the call function. If Al is 1 indicates that the cursor is located at the last character. The memory is divided into a few pages, only one page can be displayed at the same time. BH indicated by the page; BL indicates the color of the character to display; CX indicates the length of the string; DX refers to the position of the cursor (ie the starting row and column). After all relevant registers are initialized, you can call the BIOS interrupt for 10h. Get_key uses the sub-function 00h of the interrupt 16h to get the next character from the screen. The CLRSCR This function uses another sub-function of the interrupt 10h for 06h, which is used to output the start-up screen. When it is initialized, it is sent to Al. Registers CX and DX indicate the screen range of the screen to clear, in this case the entire screen. The register BH indicates the color of the screen fill, in this case. ◆ Other content programs starting with a short jump instruction, jump to the begin_boot. In the real mode, you print a brown "A" here and set a GDT. Switch to the protection mode and print a white "A". Both models are used by their own addressing methods. In the real mode, use the segment register GS indicate the memory location, we use the CGA graphics card (the default base address is 0xB8000). Is it a 0 in the code? No, it will provide an additional 0 because of the real mode. This approach is also inherited by 80386. A ASCII is 0x41, 0x06 indicates a brown character. This display will continue until any key is pressed. Below you want to display a sentence on the screen, tell the user that the following is right away. Start to protection mode, do not want to have an interrupt in the time of switching, so close all interrupts (using the CLI). Then initialize GDT. During the entire switching process, 4 descriptors were initialized. These descriptors initialize the display segments for the code segment (Code_GDT), data, and stack segment, and to access the memory. In addition, an empty descriptor will be initialized. The base address of the GDT should be loaded into the GDTR system register. The first word of the GDTR segment is loaded is the size of the GDT, and the base address is loaded in the next double word. Then, the LGDT command loads the GDT segment into the GDTR register. Now there is all preparations for switching to the protection mode. The last thing is to put the PE bit of the CR0 register 1. However, even this is not under the state of protection mode. After setting the PE bit, you will also need to clear the processor instruction prefetch queue by performing JMP instructions. In 80386, it is always taken out from memory before using the instruction, and decoding and addressing it.

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

New Post(0)