Write their own operating system (a) Author: Trevor
Transfer from:
http://www.upsoft.com.cn/bbs/read.asp?summary=1&Forum=982025305&id=29&ccess=1&status=1 Free Software Community is a place full of freedom and dreams, created in more than 10 years of time. One another miracle. 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 assembler 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 and 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 use Linux without these two tools, you can download (http://www.cix.co.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).
Start working using an editor you like to enter the following: Entry Start Start: Mov Ax, # 0xb800 MOV ES, AX Seg Es Mov [0], # 0x41 seg es MOV [1], # 0x1f loop1: jmp loop1 this is A assembler can be read by AS86. The first sentence indicates the entry point of the program and declares that the entire process starts 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 function * / #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 (Floppy_Desc, 0, Seek_cur); Write (Flose_Desc, Boot_buf, 512); Close (FLOPPY_DESC);} First, open the boot file in read-only mode, and 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. Compile Run Use the command to turn the file to an executable: as86 boot.s -o boot.o ld86 -d boot.o -o boot cc Write.c -o Write first compiles the Boot.s file into the target file Boot , 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 next issue I will add some code in this launch sector program, so that it can do some more complicated things (such as using BIOS interrupts, protect mode switches, etc.). Write their own operating system (b) of: Trevor on one, I described how to write some code in the boot sector of floppy disks, and then start the process 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 = 0x500 Entry Start: Mov Ax, # Loc1 Mov Es, Ax Mov BX, # 0 MOV DL, # 0 MOV DH, # 0 MOV Ch, # 0 MOV CL, # 2 MOV Al, # 1 MOV AH, # 2 INT 0X13 JMPI 0, # LOC1 The first line of the code is similar to one 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 Start Start: MOV AH, # 0x03 xor Bh, BH INT 0x10 MOV CX, # 26 MOV BX, # 0x0007 MOV BP, # Mymsg Mov AX, # 0x1301 int 0x10 loop1: jmp loop1 mymsg: .bete 13 ,10 .ascii "Operating System is loading ..." The above code will be loaded to The segment site is 0x500 and is executed. 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 that can be started. 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.o ld86 -d bsect.o -o bsect repeats the above operations on the SECT2.s file, Device SECT2 is obtained. Compiling 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 what we have to start, 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. Write their own operating system (C) Author: Trevor on two issues (write their own operating system 1, 2), I told you about how to use Linux development tools offered to write some code in the boot sector of floppy disks, and How to call the BIOS problem. Now, this operating system is getting closer to the Linux kernel of Linus Torvalds that is "Historical Significance". 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 Infographic Figure 3 in the Protection Mode Description Drawing Format In addition, 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 be 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 Bootmesg 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; retained Sector number DB 2 DW 0x00E0 DW 0x0B40 DB 0x0F0 DW 9 DW 18 DW 2; readout sector number DW 0; hidden fan area number print_mesg: MOV AH, 0X13; function 13 hidden 10h, write a string MOV on the screen Al, 0x00; Decide to call the position MOV BX, 0x0007 after the cursor, set the display attribute MOV CX, 0x20; this string length is 32 MOV DX, 0x0000; cursor start line and column INT 0x10; call BIOS Interrupt 10h return; return to the calling program get_key: MOV AH, 0x00 INT 0x16; Get_Key uses the function of 16h, read the next character RET CLRSCR: MOV AX, 0x0600; function using the interrupt 10h, realize the volume screen, if Al = 0, clear screen MOV CX, 0x0000; clear screen MOV DX, 0x174F; volume screen to 23,79 MOV BH, 0; use color 0 to fill int 0x10; call 10h interrupt RET Begin_Boot: Call Clrs; first clear screen MOV BP, bootmesg; provide string address call print_mesg; output information call get_key; wait for users to press any key bits 16 Call Clrs; clear screen MOV AX, 0xB800; make GS points to display memory MOV GS, AX; display a brown in real mode A MOV Word [GS: 0], 0x641; Display Call get_key; call get_key Waiting for user to press any key MOV BP, PM_MESG; set string pointer Call print_mesg; call Pr INT_MESG subroutine call get_key; wait button CALL CLRSCR; clear screen CLI; turn off LGDT [GDTR]; load GDT MOV EAX, CR0 OR Al, 0x01; set the protection mode bit MOV CR0, EAX; send changes to the changed words to control JMP CodeSel: Go_PM Bits 32 Go_PM: MOV AX, Datasel Mov DS, AX; Initialization DS and ES, to point to Data Segment MOV ES, AX MOV AX, VIDEOSEL; initialize GS, make it point to display memory MOV GS, AX MOV Word [GS: 0], 0x741; Show a white character a spin: jmp spin; cycle BITS 16 GDTR: DW gdt_end-gdt-1; GDT length DD GDT; GDT physical address GDT NULLSEL EQU $ -GDT; $ pointing to the current location, so nullsel = 0H gdt0; air descriptor DD 0 DD 0; all segment descriptors are 64-bit CodeSel EQU $ -GDT; this is 8h is the second description of GDT Code_GDT DW 0x0fff;
The boundary of the descriptor is 4GB DW 0x0000 DB 0x00 DB 0x09A DB 0x0cf db 0x00 Datasel EQU $ -GDT DATA_GDT DW 0x0FFFF DW 0x0000 DB 0x00 DB 0x092 DB 0x0CF DB 0x00 VIDEOL EQU $ -GDT DW 3999 DW 0x8000; base address 0xB8000 DB 0x0B DB 0x92 DB 0x00 DB 0x00 GDT_END TIMES 510 - ($ - $$) DB 0 DW 0x0AA55 There is a file named abc.asm, using the command NASM ABC.ASM, will be named 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) gives all the code on the explanation of the code, and I explain 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.