Operating system boot exploration [transfer] Preface this article does not aim to discuss how a multi-boot system program will guide different operating systems, but only intend to start from the perspective of writing operating systems, talk about how computers start from power Since there is nothing, run the operating system, which will be as detailed as possible to describe the transition from the real mode to the protection mode. The purpose is only to be more enjoyable to the wide enthusiasts. Friends who want to develop operating systems A little information is also left for yourself. This article will take the PYOS system boot program in development as an example. PYOS is an experimental operating system under development. It does not intend to model the operating system in any of the current operations, but only want to write itself. A operating system from head to tail to learn knowledge, accumulate technology, if you are interested, welcome you to join! This is a little experience in the learning process. If you find that there is a mistake or improper, I hope you will tell you. First, what did the computer have done from power? When the power button of the machine computer is pressed, the electrical signal line associated with this button will send an electric signal to the motherboard. The motherboard transmits this electrical signal to the power supply system. The power supply system starts working, and the entire system is powered. And send an electrical signal to the BIOS, notifying the BIOS power supply system ready to complete. Subsequent BIOS launched a program, hosting the host self-test, the main job of host self-test is to ensure that each part of the system has received power support, internal memory, other chips, keyboard, mouse, disk controller and some I / The O port is normal. Since then, the self-test program will control the control to the BIOS. Next, BIOS read the BIOS settings, get the order of the boot driver, then check until the drive that can be used to boot (or can be used to boot, including floppy disk, hard disk, disc, etc.), then call this drive Guide the magnetic disk guidance sector. How does BIOS know or tell which disk can be used to boot? Second, the Know the Bios BIOS loads the first sector (512b) of the inspection disk into memory, placed at 0x0000: 0x7C00 (see Figure 3), if the last two bytes of a sector are "55 aa" Then this is a guiding sector, which is a bootable disk. Usually this program of this size is 512B is called a boot program. If the last two bytes are not "55 aa", then BIOS checks the next disk drive. Through the above expression I can summarize the characteristics of the following three-point boot procedure: 1. Its size is 512B, and it cannot be one byte by one byte because the BIOS reads only 512b to memory. 2. Its ends must be "55 AA", which is the sign of the boot sector. 3. It is always placed on the first sector of the disk (0 head, 0 track, 1 sector) because the BIOS reads only the first sector. (Figure 1) Therefore, when we write boot procedures, we must also pay attention to the above three principles, which meets the above three principles can be seen as a boot process, at least BIOS is thinking so, although it may It is a paragraph that you will be free and there is no practical code. Because BIOS reads only one sector, it is 512 bytes of data to memory, this is obviously not enough, and now the operating system is large, so we must in the boot sector will have the core of the operating system on disk. Read into memory, then jump to the core part of the operating system to execute.
Third, through the BIOS read disk sector from above description we can know that the boot program needs to read the operating system existing on the disk into memory, so we have to talk about it, how do you not pass the operating system (because now There is no operating system) to read the disk magnetic zone. Generally speaking, there are two methods to be implemented, one is the I / O port of directly read the disk, one is implemented by the BIOS interrupt. The former method is the lowest layer method (the latter method is also implemented on the basis of it), with extremely flexibility, can read the contents of the disk to anywhere in memory, but the programming is complex. The second method is a slight higher level of the previous method, sacrificing a flexibility, for example, it cannot read the contents of the disk to 0x0000: 0x0000 ~ 0x0000: 0x03FF. Why can't you read this? Here we will have to describe the interrupt processing mechanism of the CPU after power-on. 3.1 What is the interrupt of BIOS, I believe that people who have learned the computer will not be strange. If you don't understand the interruption, I don't know what it is recommended to look at "Computer Composition Principles" (Higher Education Press Tang Yufei), there is very detailed Description, and general assembly materials also talk, so it is only intended to talk about BIOS to interrupt. (Figure 2) Be clearly seen by the above figure, when the interrupt signal is generated, the interrupt signal generates an interrupt vector address via "Interrupt Address Forming Part", which is actually pointing to a pointer to an actual memory address. This actual memory address is often enrolled in the interrupt service program that actually handles this interrupt in this actual memory address (JMP). This block is specifically used to process the memory that interrupts the jump is called an interrupt vector table. Where is this interrupt vector table in memory? What is the actual interrupt handler? 3.2 System Memory Arrangement (1M) To answer the above two questions, we need to see how memory in the system is arranged. When the CPU is added, the initial 1M memory is arranged by the BIOS for us, and each byte has a special place. (Figure 3) By the figure above, we can now very convenient question and answer the two issues raised above. Since 0x00000 ~ 0x003FF is the interrupt vector table, the disk from the operating system cannot be read here because this will overwrite the interrupt vector table, and it is no longer able to read the disk content through the BIOS interrupt. You may say: I am calling first, read again. But the facts will call other interrupt assistances multiple times during the BIOS in the process of reading. 3.3 Using the BIOS 13 interrupt reading disk sector has the previous description as the basis, the following we can formally describe how to read the disk sector through the BIOS interrupt. To read the disk sector, we need to use the BIOS 13 interrupt, and the 13 interrupt will make the value of several registers as its parameters, so we need to set the register first during the call 13 interrupt.
So how do you set registers? What registers will be used? Please see: AH register: When the function number is stored, when 2 is 2, it means to use the read disk function DL register: store the drive letter, indicate which driver CH register wants to read, indicate which head number, indicating which one of the head CL registers : The fan area number is indicated by the start sector Al register that wants to read: the count value, indicating that the number of sectors wants to read After setting these registers, we can use the INT 13 instruction to call BIOS 13 The interrupt reads the specified disk sector, which reads the disk sector into the ES: BX, so before calling it, we actually need to set the ES and BX registers to indicate the location where the data stores stored in the memory. Fourth, the protection mode The next mode memory address access writer is inseparable from the memory of memory, however the access to memory in the protection mode is completely different in the real mode, here we will describe in detail the memory of the protection mode. Access method. Of course, this is not intended to introduce all the memory access methods and mechanisms in the protection mode, only the transformations you need to go to the protection mode from the real mode, and complete memory access, please refer to "Intel User Manual", Of course, as Pyos's experiments, I will gradually describe in the later experiment report and experience. Now I don't describe the main reason is that I also experiment with PYOS, I'm not experimentally verified. I don't dare to discuss the conclusion, because in the preface, this article is just some of my experience. If I have not experiment, I have no experience, and I have not experienced it. The words returned, we still first take a look at the memory access method in the real mode. 4.1 Memory Accessing Computer in Real Mode When powering, in "real mode", there is a CR0 register in the computer, also known as the No. 0 control register, in which the lowest bit is 0th, called For the PM (Protected Modle: Protection Mode), when it is cleared, the CPU works under "real mode", when it is set, it means that the CPU works under "protection mode". When the computer is powered up, it is cleared, and the computer is at this time is in "real mode". Memory Accesss under "Real Mode" are composed of segment registers and offset, such as 0x: 0000: 0x0001, which often appears in the previous description, is a memory address in a real mode. The value in the value table time period in front of the semicolon, the value table behind the semicolon is the offset, the formation of the actual physical address is shown below: (Figure 4) However, in the protection mode, the memory address is not as follows. The method shown is formed. So how do it formed when it? 4.2 The memory address in the protection mode is complicated in the memory address, and we must first divide three concepts: logical address, linear address and physical address. The physical address is very understandable, the logical address is also understood, the address used by the program. So what is linear address? In fact, if you do not use the paging mechanism, the linear address is the physical address, which corresponds to the physical address, linear address 0, which is physical address 0. But we know, 32-bit CPU has 32 address lines, which is accessible: = 4G memory space, this is another too space! There are very few physical memory energy that now.
How do you use 4GB space in a limited physical space? People divide the physical memory into many pages. When some pages are used, some pages are not used, and the page that is not used can be used to load 4GB space, which is called from linear addresses. Mapping of physical addresses, this is a multi-to-one mapping, which means pages in multiple linear spaces corresponding to a page in a physical space, I hope the following figure will help you understand such a paging mechanism. (Figure 5) The above is the simplest mapping method, the term is called "direct connection" map, which can only be used to explain the problem, and in an actual operating system is usually "full interconnected" mapping, That is to say, the page in the linear address can be in any page mapped to the physical address, as long as the physical address space is now free. However, the problem can be explained by the above figure. When the page 5 in the linear address needs to be accessed, the CPU converts it to the physical address through the address mapping mechanism and discovers the page 1 in the physical address. The CPU is then placed in the physical address page 1 in a place (virtual memory) on the hard disk, and then loads page 5 in the linear address into the physical memory page 1. Here, when you can distinguish what is linear address, what is a physical address. However, when the paging mechanism is not used, the linear address will be used by the CPU as a physical address, and the linear address will be placed directly on the address signal line of the CPU, and when we write the application, we usually use another An address-logical address also has a mapping mechanism similar to the above mechanism from logical address to linear addresses, but this mechanism is often referred to as "segment mode", which is completed by the operating system and CPU hardware. The task of the operating system is the assignment mapping table, and the task of the CPU hardware is to map in the mapping table. Such a mapping table is also known as "Descriptive Table" in the operating system writing, there are two important descriptors, one is "Global Descriptor Table (GDT)", the other is "local descriptor Table (LDT) ", the use of these two tables is different, but their usage is approximate, and we will describe the global descriptor table. Speaking of the table, people who have learned the data structure know that it is a data structure. The global descriptor table is also a data structure. When this structure is placed in a continuous memory, it is called a table. . The table consists of the entry, the global descriptor table consists of its historical descriptor, and the simple term is called "descriptor", just because it is placed in the global descriptor table, it has become a full-term descriptor. This descriptor consists of 8 bytes. Let's take a look at its structure: (Figure 6) TYPE: Indicates the type of this paragraph, the highest bit in 4 bits is set by 1, it is a data segment, The three digits from left to right are e, W, A, that is, the TYPE of the data segment from left to right is: 0EWA. Where E represents the downward growth bit, set the downward growth, W represents a writable, and sets 1 surface of the ocean, which represents the access bit (if the CPU has access it, this bit will be set).
S: Time indicating that it is a code or data segment, indicating that the system segment DPL is 0, indicating the property right, from 00 ~ 11, total 0, 1, 2, 3 privileges P: 0 is expressed This profick is invalid, cannot be used with AVL: Removed D / B: When 0 is used, it means that it is a 16-bit segment, indicating that it is a 32-bit segment G: 0 When the unit indicates the unit of the segment is 1 byte, the unit indicating the segment limit is 4KB, and the minimum 12 bits of the segment offset will not be detected in the segment limit. (This may now be unbearable, but I will explain now). There are two parties in this, more interesting, one is "base address", one is "segment". The base address should be better understood that it gives an address in physical memory, for "segment", as the name suggests, is the limit of segment size. However, it is a bit special, and the maximum accessible address of a segment is calculated by the following formula: segment base address segment limited value * segment unit = the maximum accessible address of this paragraph If an offset address is greater than this If the maximum interview address, the CPU will generate an error interruption, so that you can access a program illegally access another program's memory space, which plays a protective role in memory, so "protection mode" is named . So, if the limit is 0, then the maximum possible access address of this section is the base address, so when the unit limit unit is one byte, the segment size of this segment is 1 byte; when the unit limit unit is 4KB Because the CPU will not detect the maximum left 12 bits of the offset, the 12-bit maximum may be 0xFFF, so this, this segment is 4KB, so: (segment limit 1) * segment Limit unit = This size Now we can start to describe how to access memory in the protection mode down mode. The reason here is to emphasize "Section Mode" is because there is a previous memory access mode-page mode in the protection mode, which is responsible for converting the linear address to the physical address. "Page Mode" is also its segment mode. When it is not used, the linear address will be placed directly on the address line as a physical address. "Segment mode" is inevitable, so-called "pure page mode" is just a whole linear address as a whole, and there is no way to truly bypass "segment mode" because it is specified by the CPU memory access mechanism. This article only describes the paragraph mode, which is as mentioned earlier, I haven't done it yet. We already know that the logical address from the program to the linear address is completed by "descriptor", and "descriptor" is placed in the descriptor table, then there are many descriptors in a descriptor table, in the end Which descriptor is selected? This is determined by a claim, which will point out that the first few descriptors in the table have a dedicated term to describe, often referred to as "segment selector". "Section Select Son" consists of 2 bytes, below, let's take a look at what information it provides: Figure 7): RPL: Indicates privilege level, 00 ~ 11, total 0, 1, 2 , 3 four privilege levels, like foregoing. Ti: 0 When this is an selected subsection of the global descriptor table, indicating a partial descriptor table when 1 is 1. The index value: used to indicate the second descriptor in the table.
The index value has a total of 13 bits, so each descriptor table can have 8K entries, and a group item is as previously described, therefore a descriptor table is up to 64K. I don't know if you pay attention to such a fact. If you select the last 3 position bit of "Segment Selection], this whole segment selector is actually a descriptor in the descriptor table! Here we can find that Intel's engineer is really very delicate, so arrangement, you can make the speed of choosing a descriptor, because the last 3 digits of the segment is clear and the descriptor table The base is added, and the physical address of a descriptor can be obtained immediately. You can get a descriptor directly through this address. So where is the base address of this descriptor table? The base address for the descriptor table is the start address of this descriptor table in memory, that is, the memory address in which the first descriptor in the table is located, and the system is stored in two special registers, one for Store the base address of the global descriptor table, called "Global Descriptor Table Register (GDTR)", another base address for depositing a local descriptor table, called "local descriptor table register (LDTR)", Their structure is shown below: (Figure 8) where the specification is limited to the size limit of the table, its use is similar to the species described above, and therefore, it is not described herein. In the protection mode, the segment register in the previous mode is still useful, but it is not in the base address used to store the segment, but is used to store "segment selection sub-", its name also become "segment selector Register ", when accessing memory, we need to give" Segment Select Subs ", not a section base address. For example, I now want to use the second entry in the global descriptor table, that is, the second "segment descriptor", this "segment selector" needs to be constructed as follows: RPL: 00, because we now Is in writing the operating system, working in 0 privilege level Ti: 0, we use the global descriptor index value: 01, we use the second global descriptor, the first global descriptor number is 0, the second is 1, The binary is expressed in 01. Therefore, our "segment selection" is: 0000 0000 0000 10000, i.e. 0x0008, so for the 0x0008: 0x0000 such a logical address, in the protection mode, it should be regarded as the second description in the global descriptor segment. The segment described in the context and the memory address of 0 is 0. How is the linear address of this logical address? Please see the following picture: (Figure 9) I believe that from the above figure, you can clearly see how a logical address is converted to a 32-bit linear address. V. PYOS boot program Write PYOS is a system in writing, is an item in an experiment, and I have talked about the purpose and motivation of the written, here, here, just tell the content described in this order. Talking about the writing of the PYOS boot program, writing a list of Linux 0.11 kernel boot programs, but PYOS is not Linux, there are many different places in their boot programs.
Let's first take a look at the memory arrangement of the entire boot area of PYOS: (Figure 10) The above picture is PYOS memory schema, which is also a bootstrap flowchart, PYOS is a two-stage boot system, the first line is BOOT BiOS In addition, Boot reads setup, setup reads the System program to the temporary area, then move the System program to the top of the memory, and establish a segment descriptor pointing to the segment where the System program is located, and establish GDT, then switch the CPU to the protection mode. Then jump to the System program, and the SYSTEM program will be the true system kernel of the PYOS. The data storage area in the figure is used to store parameters that need to be passed between boot, setup, and PYOS three programs. The reason why the secondary guidance is mainly considering the convenience of future expansion, and the various prices are almost independent, and Boot or Setup can be rewritten to provide more boot mode. The SYSTEM program is because, as mentioned, the data cannot be read directly to the original interrupt vector change in the interrupt vector table. After the data is read, the interrupt is not called, and the program moves to the internal interrupt. Vector table, for the interrupt vector table in the protection mode, will be established by the System program and handed over to System, will be a complete memory. For the PYOS process memory schedule, ready to refer to Linux 0.11, the memory arrangement is as follows: (Figure 11, the source "Linux 0.11 Kernel Code full note") A process has 64M space, 4GB / 64M = 64, that is, the maximum number of processes in the system 64. Therefore, the segment of a segment is: 64MB, each process takes up two of the global descriptors, one is a data segment descriptor, one is a code segment descriptor, and the segment is 64MB. Six, PYOS boot program source code will provide all source code for the PYOS boot program, because System has not completely completed, so it is just a simple printing of a character to show the boot work, and there is more detailed notes in the code. If there is still a unclear place, you can go :: url :: http://purec.binghua.com (Pure C Forum) Operation System Experiment Zone, check the experiment report before PYOS, have a very detailed annotation and related The principle description and describes how to compile and experiment.
Boot.asm 0.04; for pyos4; xieyubo@126.com;; this is the fourth version of Boot.asm, this version has a large change, refer to Linux 0.11 design; first point out that the version identity change, for It is convenient for future modification, and each file is set in the future, and it is pointed out for what system for use, this version is used for the PYOS4 system; the memory allocation of this version is as follows; the memory is 0x90000; the maximum end address is 0x9FFF; maximum end address is 0x9fffff; maximum A total of 64KB; all startup code is convenient to call; the startup code is divided into two parts, one is boot, one is setup, this is the design of Linux 0.11; but it is different, Boot will not move yourself to 0x90000, and directly jump to 0x90100 running; 0x90000 ~ 0x900FF (256B) system reservation to store key data taken from BIO; 0x90100 ~ 0x904ff (1KB): Start storage of Setup, setup size is 1KB [BITS 16 ]; Compile into 16-bit instructions [org 0x7c00]; ------------------------------------- -------------------------------------------------- ------- jmp main; ------------------------------------------------------ -------------------------------------------------- ---- Data Definition MSG DB "Loading Pyos ..."; output information DB 13, 10, 0; 13 Represents Enter, 10 indicates the wrap, and 0 denotes the string end bootseg EQU 0x0000; BOOT Subgrade Site setupseg EQU 0x9000; SETUP location STUPOFFSET EQU 0x0100; SETUP The offset setupsize EQU 1024; SETUP size, must be the multiple of 512 BootDriver DB 0; save the start-up drive letter; ----- -------------------------------------------------- --------------------------------------- SHOWME Ssage:; The following program behavior displays the output information MOV AH, 0x0E; sets the display mode MOV BH, 0x00; set the page number MOV BL, 0x07; set font properties. Nextchar: Lodsb or Al, Al Jz .Return Int 0x10 jmp.nextchar .return : Ret; ------------------------------------------------ ---------------------------------- Main: MOV [BootDriver], DL; get the start-up drive letter; the following program sets the data segment MOV AX, BootSEG MOV DS, AX MOV SI, MSG Call showMessage; display information; read setup; read from the second sector from the disk 0x90100 .readfloopy: MOV AX, Setupseg Mov ES, AX MOV BX, Setupoffset MOV AH, 2 MOV DL, [Bootdriver] MOV CH, 0 MOV CL, 2 MOV Al, Setupsize / 512; Read Send Sector (2 A total of 1kb int 0x13 jc .readfloopy;
Save the boot driver number at 0x90000 MOV Al, [Bootdriver] MOV [0], Al; Jump JMP Setupseg: setupoffset; -------------------------------------------------------------------------------------------------------------- -------------------------------------------------- --- Times 510 - ($ - $$) DB 0 DB 0x55 DB 0xAA; setup.asm 0.04; for pyos4; xieyubo@126.com; This setup program completes Boot unfinished startup work, including reading from BIOS The system information is stored in the specified position; initializes the GDT, the LDT table, completes the conversion from the protection mode to real mode; the code of the real mode is also read [BITS 16] [org 0x0100]; ------- -------------------------------------------------- ------------------------------------------- -------------------------------------------------- ---------------- Setupseg EQU 0x9000 SetupSize EQU 0x0100 SetupSize EQU 1024; SETUP size 1kb,; must be 512 multiplestemseg EQU 0x0000 SystemOffset EQU 0x0000 SystemSize EQU 1024; SYSTEM size 1kb,; this value must be a multiple of 512, the actual value can be incomprehensible; the descriptor of the temporary GDT table is defined below; a total of three segments, one empty segment is reserved by Intel, a code segment, a data segment GDT_ADDR: DW 0x7FFF; GDT table size DW gdt; GDT table location DW 0x0009 GDT: GDT_NULL: DW 0x0000 dw 0x0000 dw 0x0000 dw 0x0000 gdt_system_code: DW 0x3FFF; Different (16K * 64KB = 64MB DW 0x0000 DW 0x9a00 dw 0x00c0 gdt_system_data: dw 0x3 FFF dw 0x0000 dw 0x9200 dw 0x00c0; ------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------; Waiting for the keyboard controller free Subprogram EMPTY_8042: IN AL, 0X64 TEST Al, 0x2 JNZ EMPTY_8042 RET; ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -------------------------------------------------- - Main:; Initialization Register, because the BIOS interrupt and call will use the stack or SS register; when the CPU is started or reset, it is initialized by the BIOS, and now the segment transfer, you need to reset MOV AX, Setupseg Mov DS, AX MOV ES, AX MOV SS, AX MOV SP, 0xFFF; ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -------------------------------------; what useful information should be read from the BIOS, now Not sure, therefore temporarily skip this function block; -------------------------------------- ---------; 0x90000 (1B): Save the boot driver number, deposited by the boot program;
-------------------------------------------------- ------------------------; After reading the SYSTEM to the setup program; because 0x00000 is now placing the BIOS interrupt, there is no direct SYSTEM reads 0x00000, otherwise the BIOS interrupts will not be called. ReadFload: Mov Ax, Setupseg Mov ES, AX MOV BX, Setupoffset Setupsize MOV AH, 2 MOV DL, [0] MOV CH, 0 MOV CL, 1 1 setupsize / 512; start sector in SYSTEM; (first 1 means the number starting from 1, the second 1 is the number of sector occupied by boot) MOV Al, SystemSize / 512; read Number of sectors (2 sectors a total of 1kb INT 0x13 jc .readfloopy; the read SYSTEM is moved to 0x00000 Location CLD MOV SI, SETUPOFFSET SETUPSIZE MOV AX, SystemSeg Mov ES, AX MOV DI, Systemoffset MOV CX, SystemSize / 4 Rep Movsd; Next start to initialize the work CLI; turn off LGDT [GDT_ADDR]; loaded into a descriptor of GDT; turn on the A20 address cable Call Empty_8042 MOV Al, 0xD1 OUT 0x64, Al Call Empty_8042 MOV Al, 0xDF OUT 0X60, Al Call Empty_8042; The following settings Enter 32-bit protection mode Run MOV EAX, Cr0 or Eax, 1 MOV CR0, EAX JMP DWORD 0x8: 0x0; ---------------------------------------------------------------------- -------------------------------------------------- ------------------- Times 1024 - ($ - $$) DB 0; system.asm 0.04; for pyos4; xieyubo@126.com; this program will be fully used 32-bit assembly code is the system Core module [BITS 32] [org 0x0]; -------------------------------------- -------------------------------------------------- ------ JMP main; ----------------------------------------- -------------------------------------------------- --- Main:; Sets Register MOV AX, 0x10 MOV DS, AX MOV CL, '1' MOV [0xB8000], CL MOV CL, 0x04 MOV [0xB8001], CL JMP $; --------- -------------------------------------------------- ----------------------------------- The above program has a place in this program and the previous experimental report In order to mention, this is the problem of the A20 address line. For the issue of the A20 address line, there is a very detailed description in "Linux 0.11 kernel source code full note", the author also lists several other ways to open the A20 address line. And analyze problems that may exist. This is a very good book, recommend everyone to read.