When using a variety of powerful free software, I always have reverence for its developers and hope that they will become one of them one day. Many people who are eager for a free community want to embrace it, but do not know how to do it. Then, start with writing a simple operating system!
What we want to do
Some people may worry that they have neither learned computer principles nor operating system principles, but do not understand assembly languages or C languages. Can they write the operating system? The answer is no problem. I will take you through your operating system step by step. Of course, it would be better to learn the above content.
First, we need to clarify that the processor (that is, the CPU) controls the computer. For a PC, the CPU is in the real mode when it is started, which is equivalent to an Intel 8086 processor. That is to say, even if you have a Pentium processor, its function can only be 8086 levels. In this regard, some software can be used to convert the processor to the famous protection mode. Only in this way can we make full use of the powerful features of the processor.
At the beginning of writing the operating system, the system controls the bios and extracts the programs stored in the Rom. BIOS is used to execute post (power on self test, self-check. Self-check is to check the integrity of the Computer (for example, whether the peripherals work normally, whether the keyboard is connected ). After all this is done, you will hear the PC horn sound clear. If everything works properly, the BIOS selects a boot device and reads the first sector (that is, the start sector) of the device. Then, the control process is transferred to the specified position. The boot device may be a floppy disk, a CD, a hard disk, or another device. Here, we use a floppy disk as the boot device. If we have written some code in the boot sector of the floppy disk, It will be executed. Therefore, our goal is to write programs to the boot sector of a floppy disk. First, use 8086 assembly to write a small program, and then copy it to the Boot Sector of the floppy disk. To implement
Copy now. Write a C program. Finally, start the computer with a floppy disk. Required tools
● As86: This is an assembly program that converts written code into a target file.
● Ld86: This is a connector. The target code generated by as86 is converted into a real machine language. Machine language is a form that can be interpreted by 8086.
● GCC: the famous C programmer. Because we need to write a C program to transfer our OS to a floppy disk.
● An empty floppy disk: it is used to store the written operating system and is also a Startup Device.
● A computer with Linux installed: This machine can be very old, 386 or 486.
In most standard Linux releases, as86 and ld86 are included. The two tools are included in my Red Hat 7.3 and installed on the machine by default. If you are using Linux without these tools, you can download from the Internet (http://www.cix.co.uk /~ Mayday/). Both tools are included in a software package named bin86. You can also obtain related documents online (www.linux.org/docs/ldp/howto/assembly-howto/as86.html ).
Start work
Enter the following content in your favorite editor:
Entry start
Start:
MoV ax, #0xb800
MoV es, ax
SEG es
MoV [0], #0x41
SEG es
MoV [1], # 0x1f
Loop1: JMP loop1
This is an assembly program that as86 can understand. The first sentence specifies the entry point of the program and declares that the entire process starts from start. The second line specifies the start position, indicating that the entire program should be executed from start. 0xb800 is the starting address of the video memory. # Indicates that it is an immediate number. Execution statement
:
MoV ax, # oxb800
The value of the ax register is changed to 0xb800, which is the memory address. Next, move the value to the es register, which is an additional segment register. Remember that 8086 has a segmented architecture. Each register includes the code segment, data segment, stack segment, and additional segment. The corresponding Register names are CS, DS, SS, and es. In fact, we send the video memory address to the append segment. Therefore, anything sent to the append segment will be sent to the video memory.
To display characters on the screen, you need to write two bytes to the video memory. The first one is the ASC ⅱ value of the character to be displayed, and the second byte represents the attribute of the character. Attributes include the foreground color, background color, and whether to flash. SEG es indicates that the next command to be executed directs to the es segment. Therefore, the value 0x41 (the character in ASC ⅱ is a) is sent to the first byte of the video. Next, we will send the character attribute to the next byte. Enter 0x1f here, which indicates that the white characters are displayed in the blue background. Therefore, if you execute this program, you can get a white a displayed on the screen on the blue background. Next is a loop. Because after the task that displays characters is executed
Let the program end, or use a loop to make it run forever. Name the file boot. s and save the disk.
The concept of memory is not very clear here, so it is necessary to explain it further. Assume that the screen consists of 80 columns × 25 rows, the first line requires 160 bytes, one of which is used to represent characters, and the other is used to represent the character attributes. If you want to display a character in the third row, you must skip the 0th and 1 bytes of the display (they are used to display the 1st columns ), 2nd and 3 bytes (they are used to display the 2nd columns), and then the ASC ⅱ code value to display the characters into 4th bytes, write the properties of the characters to 5th bytes.
Write the program to the startup sector and write a C program to write my operating system to the first sector of the floppy disk. The program content is as follows:
# Include/* unistd. h need 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 (floppy_desc, 0, seek_cur );
Write (floppy_desc, boot_buf, 512 );
Close (floppy_desc );
}
First, open the boot file in read-only mode, and 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 example, because the file is small, it is read to the end of the file. Close the file. The last four lines of code open the floppy drive device (generally/dev/fd0 ). Use lseek to locate the start of the file, and then write 512 bytes to the floppy disk from the buffer.
On the read, write, open, and lseek help pages, you can see all the parameters related to the function and their usage. Two lines in the program are hard to understand:
Boot_buf [510] = 0x55;
Boot_buf [511] = 0xaa;
This information is used in bios. If it identifies a device that can be started, the value should be 0x55 and 0xaa at the positions of 510th and 511. The program will read the file boot to the buffer named boot_buf. It requires 510th and 511st bytes to be changed, and then the boot_buf is written to the floppy disk. If the code is executed, the first 512 bytes on the floppy disk contain the startup code. Finally, save the file as write. C.
Compile and run
Run the following command to change the file to an executable file:
As86 boot. S-O boot. o
Ld86-D boot. O-o boot
CC write. C-o write
First, compile the boot. s file into the target file boot. O, and then connect the file to the final boot file. Finally, the C program is compiled into an executable Write File. Insert a blank floppy disk and run the following program:
./Write
Restart the computer, set the BIOS interface, and set the floppy disk as the first boot device. Then insert a floppy disk and start the computer from the floppy disk. After the startup is complete, a letter A (blue-white) is displayed on the screen, and the startup speed is very fast, almost instantly completed. This means that the system has started from the floppy disk we created and executed the program that just writes the Boot Sector. Now, it is in an infinite loop. Therefore, if you want to enter Linux, you must remove the floppy disk and restart the machine. At this point, the operating system is complete. Although it does not implement any functions, it can start the machine.
In the next phase, I will add some code in this startup sector program so that it can do some complicated things (such as BIOS interruption and protective mode switching ). Write the operating system by yourself (2)
By Emy
In the last issue, I talked about how to write code in the boot sector of a floppy disk and then start it from a floppy disk. Make a boot sector and know how to use BIOS interruption before switching to the protection mode. BiOS interruption is a low-level program provided by the BIOS to make it easier to create the operating system. In this article, we will learn how to handle BIOS interruptions.
Why use bios?
The BIOS copies the Boot Sector to Ram and runs the code. In addition, the BIOS has to do many other things. When an operating system is started, there are no drivers, such as the graphics card driver and the floppy disk driver, in the system. Therefore, the startup sector cannot contain any driver. We need to take other approaches. At this time, BIOS can help us. BiOS contains a variety of programs available, including detecting installed devices, controlling printers, calculating memory size, and other programs for various purposes. These programs are called BIOS interruptions.
How to call BIOS interruption
In general programming languages, function calling is very easy. For example, in C, if a program named "display" has two parameters, noofchar indicates the number of characters displayed, and ATTR indicates the attribute of the displayed characters. To call it, you only need to give the program name. For interrupted calls, we use int commands in assembly languages.
For example, to display something in C, the command is as follows:
Display (nofchar, ATTR );
When using bios, the command to implement the same function is as follows:
Int 0x10
How to pass Parameters
Before calling BIOS interruption, we need to first send some specific values to the Register. Assume that the BIOS interrupt is used for 13 H. The interrupt function is to transfer data from a floppy disk to the memory. Before calling this interrupt, you must specify the data copy segment address, drive letter, track number, sector number, and number of sectors to be transferred. Then, the corresponding value is sent to the corresponding register. Before proceeding to the following steps, you must have a clear understanding of this point.
In addition, it is important that different functions can be implemented for the same interrupt. The exact function implemented by the interrupt depends on the selected function number, which is generally included in the ah register. For example, the interruption of 13 h can be used to read disks, write disks, and other functions. If 3 is sent to the ah register, the interruption option is to write the disk. If 2 is sent to the ah register, the selected function is to read the disk.
What we want to do
This time, our source code consists of two assembly language programs and one C program. The first assembly file is the code of the boot sector. In the Boot Sector, the code we write is to copy the second sector of the floppy disk to 0x500 of the memory segment (the address is 0x5000, that is, the offset address is 0 ). At this time, we need to use BIOS interruption for 13 H. At this time, the code of the Start sector will transfer the control to 0x500. In the Second Assembly file, the code will display a message on the screen after 10 hours of BIOS interruption. The function implemented by the C program is to Copy Executable File 1 to the startup sector, and Copy Executable File 2 to the second sector of the floppy disk.
Start Sector Code
When 13 hours of interruption is used, start the sector to load the content in the second sector of the floppy disk to 0x5000 of the memory (segment address: 0x500 ). The following code is used to achieve this purpose and save it to the file sbect. S.
Loc1 = 0x500
Entry start
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 above Code is similar to a macro. The next two lines load the value 0 X to the es register, where the second sector code on the floppy disk will be copied (the first sector is the start sector ). In this case, the offset in 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, the track number is sent to the CH register, the sector number is sent to the CL register, and the number of sectors is sent to the Al register. The function we want to implement is to send the content of Sector 2, channel number 0, and drive number 0 to segment address 0x500. All of these parameters correspond to a 1. 44 MB Floppy disk.
Sending 2 to the ah register selects the corresponding function provided by the interruption of 13 H, that is, the function of transferring data from the soft drive.
At last, the call was interrupted for 13 H and transferred to the segment address 0x500 with the offset of 0.
Code for the second sector
The code in the second sector is as follows (Save the code to the 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:
. Byte 13, 10
. ASCII "Operating System is loading ......"
The code above will be loaded to the segment address 0x500 and executed. In this Code, an interruption of 10 h is used to obtain the current cursor position and then display information.
From row 3rd to row 5th is used to obtain the current cursor position. In this case, function 3 is selected for 10 h interruption. Then, the content of the BH register is cleared and the string is sent to the CH register. In BX, we sent the page number and display attributes. Here, we want to display white characters in the black background. Then, the address of the character to be displayed is sent to BP. The information is composed of two bytes with 10 values of 13, which correspond to the ASC ⅱ values of carriage return and LF (line feed) respectively. Next is a string consisting of 29 characters. The following function is to output the string and move the cursor. Finally, the call is interrupted and the loop is entered.
C program code
The source code of the C program is as follows and stored as the 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 last phase, I introduced how to start a floppy disk. This process is a little different. First, copy the executable file bsect compiled by bsect. s to the start 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 and compile them separately. The method is as follows:
As86 bsect. S-O bsect. o
Ld86-D bsect. O-o bsect
Repeat the preceding operations on the sect2.s file to obtain the executable file sect2. Compile write. C, insert a floppy disk, and execute the Write File. The command is as follows:
CC write. C-o write
./Write
What we will do next
After starting from a floppy disk, you can see the displayed string. This is accomplished by BIOS interruption. The next step is to convert the real-world mode to the protection mode in this operating system.
Write the operating system by yourself (3)
By Emy
In the last two phases (write the Operating System 1 and 2 by yourself), I told you how to use the development tools provided by Linux to write code in the boot sector of a floppy disk and how to call the BIOS. Now, the operating system is getting closer and closer to the Linux kernel with "historical significance" of Linus Torvalds. Therefore, you need to switch the system to the protection mode immediately. Www.xker.com)
What is protection mode?
Since the launch of the first microprocessor in 1969, Intel processor has been constantly updated, from 8086, 8088, 80286, to 80386, 80486, Pentium, Pentium II, Pentium 4, etc, its architecture is also changing. Later than 80386, some new functions were provided to make up for some defects of 8086. This includes memory protection, multitasking, and memory usage of more than 8086 kb, and still maintains compatibility with the family. That is to say, 80386 still has all the functions of 8086 and 80286, but has greatly improved its functionality. In the early stages, the processor was working in the real mode. After 80286, the protection mode was introduced, and after 80386, the protection mode was greatly improved. In 80386, the protection mode provides better protection for programmers and more memory. In fact, the purpose of protection mode is not to protect programs, but to protect all programs (including operating systems) other than programs ).
In short, the protection mode is the most natural mode of the processor. In this mode, all the instructions of the processor and all the features of the architecture are available and can achieve the highest performance.
Protection mode and real mode
On the surface, there is no big difference between the protection mode and the actual mode. Both use the memory segment, interrupt, and device driver to process hardware, but there are many differences between the two. We know that in real mode, the memory is divided into segments, and the size of each segment is 64 KB, and such a segment address can be expressed in 16 bits. Memory segments are processed through an internal mechanism associated with the segments register. The content of these segments registers (CS, DS, SS, and ES) forms part of the physical address. Specifically, the final physical address consists of a 16-bit segment address and a 16-bit intra-segment offset address. Expressed:
Physical address = four shifts left segment address + offset address.
In protection mode, segments are defined through a series of tables called "descriptor tables. Segment registers store pointers to these tables. Two types of tables are used to define memory segments: Global Descriptor Table (gdt) and Local Descriptor Table (LDT ). Gdt is a segment descriptor array that contains the basic descriptors that can be used by all applications. In the real mode, the segment length is fixed (64 KB), while in the protection mode, the segment length is variable, and the maximum length is 4 GB. LDT is also an array of segment descriptors. Unlike gdt, LDT is a segment, which stores partial segment descriptors that do 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 in the format of 3. When the segment register is loaded, the segment base address is obtained from the corresponding table entry. The descriptor content is stored in an image register invisible to the programmer (shadow
In register), so that the information can be used in the same segment next time instead of being extracted from the table every time. The physical address consists of a 16-bit or 32-bit offset plus the base address in the image register. The differences between the real mode and the protection mode can be clearly seen in Figure 1 and figure 2.
Figure 1 addressing in real mode
Figure 2 addressing in protection mode
Figure 3 description format
In addition, there is an Interrupt Descriptor Table (IDT ). These interrupt Descriptors will tell the processor where to find the interrupt handler. Like the actual mode, each interrupt has an entry, but the format of these entries is completely different. Because IDT is not used in the process of switching to the protection mode, we will not discuss it here.
Enter protection mode
80386 has four 32-bit control registers named Cr0, CR1, CR2, and C3. CR1 is reserved for use in future processors and is not defined in 80386. Cr0 contains the control mark of the system, which is used to control the operating mode and status of the processor. Cr2 AND Cr 3 are used to control the paging mechanism. Here, we are concerned with the PE bit control of the Cr0 register, which is responsible for switching between the real mode and the protection mode. When Pe = 1, it indicates that the processor is running in the protection mode, and the segment mechanism is corresponding to the content described above. If PE = 0, the processor will work in the real mode.
To switch to the protection mode, the PE location is actually 1. To switch the system to the protection mode, you need to do other things. The program must initialize the system's segment registers and control registers. Run the jump command after the PE position is 1. The process is described as follows:
1. Create a gdt table;
2. Set the PE bit to 1 to enter the protection mode;
3. Execute the redirection to clear any commands read in real mode.
The following code is used to implement the switchover process.
What you need
◆ A blank floppy disk
◆ NASM Compiler
The source code of the entire program is as follows:
Org 0x07c00; start address: 0000: 7c00
JMP short begin_boot; Skip other data and jump to the start of the boot program
Bootmesg DB "our OS Boot Sector loading ......"
Pm_mesg DB "tching to protected mode ...."
DW 512; number of bytes per sector
DB 1; number of sectors in each cluster
DW 1; reserved fan area number
DB 2
DW 0x00e0
DW 0x0b40
DB 0x0f0
DW 9
DW 18
DW 2; read/write fan area number
DW 0; hide the fan area number
Print_mesg:
MoV ah, 0x13; Use the function of interrupting 10 H 13, write a string on the screen
MoV Al, 0x00; determines the cursor position after the function is called
MoV BX, 0x0007; set the display attribute
MoV CX, 0x20; the string length is 32
MoV dx, 0x0000; Start row and column of the cursor
Int 0x10; 10 h of BIOS interruption
RET; return the caller
Get_key:
MoV ah, 0x00
Int 0x16; get_key uses the function 0 that interrupts 16 h to read the next character
RET
Clrscr:
MoV ax, 0x0600; Use the function 6 that interrupts 10 h to implement screen scrolling. If Al = 0, clear the screen.
MoV CX, 0x0000; clear screen
MoV dx, 0x174f; scroll screen
MoV BH, 0; fill with color 0
Int 0x10; 10 hours interrupted
RET
Begin_boot:
Call clrscr; clear screen first
MoV bp, bootmesg; Provide string address
Call print_mesg; Output Information
Call get_key; wait for the user to press any one key
Bits 16
Call clrscr; clear screen
MoV ax, 0xb800; point gs to Display memory
MoV Gs, ax; a brown A is displayed in real mode.
MoV word [GS: 0], 0x641; displayed
Call get_key; call get_key to wait for the user to press any one key
MoV bp, pm_mesg; Set string pointer
Call print_mesg; call the print_mesg subroutine
Call get_key; wait for the button
Call clrscr; clear screen
CLI; disconnection
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_pm
Bits 32
Go_pm:
MoV ax, datasel
MoV ds, ax; initialize DS and es to point them to data segments
MoV es, ax
MoV ax, videosel; initialize gs to point to Display memory
MoV Gs, ax
MoV word [GS: 0], 0x741; displays a white character a in Protected Mode
Spin: JMP spin; Loop
Bits 16
GDTR:
DW gdt_end-gdt-1; gdt Length
Dd gdt; physical address of gdt
Gdt
Nullsel equ $-gdt; $ points to the current position, so nullsel = 0 h
Gdt0; empty Descriptor
Dd 0
Dd 0; all segment descriptors are 64-bit
Codesel equ $-gdt; this is the second descriptor of 8 h, that is, gdt.
Code_gdt
DW 0x0ffff; the range descriptor is 4 GB
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
Videosel 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
Store the above Code in a file named ABC. ASM. Run the nasm abc. ASM command to generate a file named ABC. Insert a floppy disk and enter the command dd If = ABC of =/dev/fd0. This command writes the ABC file to the first sector of the floppy disk. Then restart the system and you will see the following information:
* Our OS booting ................
* A (brown)
* Tching to protected mode ....
* A (white)
Www.xker.com)
Code explanation
All the code is provided above. I will explain the above Code below.
◆ Functions used
The following describes some functions in the Code:
Print_mesg This subroutine uses the BIOS to interrupt the 10 h function for 13 H, that is, to write a string to the screen. Attribute control is implemented by sending different values to some registers. 10 h of interruption is used for various string operations. We send the sub-function number 13 h to Ah to indicate that a string is to be printed. 0 in the Al Register indicates the starting position returned by the cursor. 0 indicates that the cursor is returned to the first row of the next row after the function is called. If Al is 1, the cursor is located at the last character.
The video memory is divided into several pages. Only one page can be displayed at a time. BH indicates the page number; BL indicates the color of the character to be displayed; CX indicates the length of the string to be displayed; DX indicates the cursor position (that is, the starting row and column ). After initialization of all related registers is complete, you can call BIOS to interrupt for 10 h.
Get_key uses the sub-function 00 H that interrupts 16 h to get the next character from the screen.
Clrscr this function uses another sub-function that interrupts 10 h 06 h, used to clear the screen before the output starts. Input 0 to Al during initialization. Registers CX and DX indicate the screen range to be cleared. In this example, the entire screen is used. The register BH specifies the screen fill color, which is black in this example.
◆ Other content
The program is a short jump command at the beginning and jumps to begin_boot. In real mode, print a brown "A" here and set a gdt. Switch to protection mode and print a white "". Both modes use their own addressing methods.
In real mode, use the block register gs to indicate the video memory location. We use a CGA video card (the default base address is 0xb8000 ). Is a 0 missing in the code? No, because an additional 0 is provided in real mode. This method is also inherited by 80386. ASC ⅱ of A is 0x41, and 0x06 indicates that a brown character is required. The display continues until any key is pressed. Next we will display a sentence on the screen, telling the user that the protection mode is about to enter.
When switching to the protection mode, you do not want the impact of interruption to this time. Therefore, you need to disable all interruptions (implemented using CLI ). Then initialize the gdt. During the entire switching process, four descriptors are initialized. These descriptors initialize code segments (code_gdt), data and stack segments (data_gdt), and display segments to access the video memory. In addition, an empty descriptor is initialized.
The base address of gdt must be loaded into the GDTR system register. The first word in the GDTR segment is the size of 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 you are ready to switch to the protection mode. The last thing is to set the PE position of the Cr0 register to 1. However, even if it is not in protection mode.
After the PE bit is set, you also need to clear the processor command prefetch queue by executing the JMP command. In 80386, commands are always extracted from the memory before being used, and decoded and addressable. However, when the protection mode is enabled, the prefetch command information (which is still in the real address mode) is invalid. The purpose of using the JMP command is to force the processor to discard invalid information.
Now, it is in protection mode. So, how can we detect that the protection mode is under? Let's take a look at the white letter A on the screen. Here, datase1 is used to initialize the Data Segment and the additional segment, and videose1 is used to initialize the Gs. The asc ii value and attribute of the character "a" of the notice are located at [GS: 0000], that is, b8000: 0000. The loop statement keeps the character on the screen until the system is restarted.
What to do next
Now, the operating system is working in protection mode, but it does not actually implement any specific functions. You can add functions of various operating systems on this basis. This is when we write the operating system by ourselves.