Lab_1:練習3——分析bootloader進入保護模式的過程


文章鏈接:https://www.cnblogs.com/cyx-b/p/11809742.html

作者:chuyaoxin

一、實驗內容

BIOS將通過讀取硬盤主引導扇區到內存,並轉跳到對應內存中的位置執行bootloader。請分析bootloader是如何完成從實模式進入保護模式的。

提示:需要閱讀小節“保護模式和分段機制”和lab1/boot/bootasm.S源碼,了解如何從實模式切換到保護模式,需要了解:

  • 為何開啟A20,以及如何開啟A20
  • 如何初始化GDT表
  • 如何使能和進入保護模式

二、實驗相關

(1)匯編

沒有學過匯編的我剛看到源碼時,有點懵逼,於是,我首先查了不少關於匯編的小資料。

Ucore中用到的是AT&T格式的匯編

在 AT&T 匯編格式中

 %

  寄存器名要加上 '%' 作為前綴;

 $

  用 '$' 前綴表示一個立即操作數; 

.set symbol, expression     

將symbol的值設為expression

 cli

屏蔽系統中斷

 .code16     

 由於代碼段在實模式下運行,所以要告訴編譯器使用16位的模式編譯

標號:

在x86匯編代碼中,標號有唯一的名字加冒號組成。它可以出現在匯編程序的任何地方,並與緊跟其后的哪行代碼具有相同的地址。

概括的說 ,當程序中要跳轉到另一位置時,需要有一個標識來指示新的位置,這就是標號,通過在目標地址的前面放上一個標號,可以在指令中使用標號來代替直接使用地址。

其他

目標操作數在源操作數的右邊; 

操作數的字長由操作符的最后一個字母決定,后綴'b'、'w'、'l'分別表示操作數為字節(byte,8 比特)、字(word,16 比特)和長字(long,32比特);

 

(2)bootloader的作用

1、關閉中斷

2、A20 使能

3、全局描述符表初始化

4、保護模式啟動

5、設置段寄存器(長跳轉更新CS,根據設置好的段選擇子更新其他段寄存器)

6、設置堆棧,esp 0x700 ebp 0

7、進入bootmain后讀取內核映像到內存,檢查是否合法,並啟動操作系統,控制權交給它

(3) 實模式

CPU復位(reset)或加電(power on)的時候以實模式啟動,處理器以實模式工作。在實模式下,內存尋址方式和8086相同,由16位段寄存器的內容乘以16(10H)當做段基地址,加上16位偏移地址形成20位的物理地址,最大尋址空間1MB,最大分段64KB。可以使用32位指令。32位的x86 CPU用做高速的8086。在實模式下,所有的段都是可以讀、寫和可執行的。

 

實模式將整個物理內存看成分段的區域,程序代碼和數據位於不同區域,操作系統和用戶程序並沒有區別對待,而且每一個指針都是指向實際的物理地址。這樣,用戶程序的一個指針如果指向了操作系統區域或其他用戶程序區域,並修改了內容,那么其后果就很可能是災難性的。通過修改A20地址線可以完成從實模式到保護模式的轉換。

(4)保護模式

實模式下,程序地址為真實的物理地址,可以訪問任意地址空間,這樣不同進程可能訪問到其它進程程序,造成嚴重錯誤。而保護模式下,程序地址為虛擬地址,然后由OS系統管理內存訪問權限,這樣每個進程只能訪問分配給自己的物理

內存空間,保證了程序的安全性。例如Linux系統地址訪問采用分頁機制,在加載程序時,由OS分配的進程可以訪問的物理頁空間,並設置了頁目錄項和頁表項,才能保證程序正常運行。這樣程序運行時地址間接地由OS進行管理,防止進程之間互相影響,全部由OS穩定性保證。

(5)CR0

CR0是控制寄存器,其中包含了6個預定義標志,0位是保護允許位PE(Protedted Enable),用於啟動保護模式。如果PE位置1,則保護模式啟動,如果PE=0,則在實模式下運行。

關於CR0及其他控制寄存器的詳細內容可以參考以下鏈接:https://blog.csdn.net/wyt4455/article/details/8691500

三、實驗步驟

(一)代碼分析

1、bootasm.S的代碼

#include <asm.h>

# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

.set PROT_MODE_CSEG, 0x8 # kernel code segment selector
.set PROT_MODE_DSEG, 0x10 # kernel data segment selector
.set CR0_PE_ON, 0x1 # protected mode enable flag

# start address should be 0:7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16 # Assemble for 16-bit mode
cli # Disable interrupts
cld # String operations increment

# Set up the important data segment registers (DS, ES, SS).
xorw %ax, %ax # Segment number zero
movw %ax, %ds # -> Data Segment
movw %ax, %es # -> Extra Segment
movw %ax, %ss # -> Stack Segment

# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
seta20.1:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.1

movb $0xd1, %al # 0xd1 -> port 0x64
outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port

seta20.2:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.2

movb $0xdf, %al # 0xdf -> port 0x60
outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1

# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0

# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
ljmp $PROT_MODE_CSEG, $protcseg

.code32 # Assemble for 32-bit mode
protcseg:
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment

# Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
movl $0x0, %ebp
movl $start, %esp
call bootmain

# If bootmain returns (it shouldn't), loop.
spin:
jmp spin

# Bootstrap GDT
.p2align 2 # force 4 byte alignment
gdt:
SEG_NULLASM # null seg
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel

gdtdesc:
.word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt
View Code
#注釋:
#include <asm.h> asm.h頭文件中包含了一些宏定義,用於定義gdt,gdt是保護模式使用的全局段描述符表,其中存儲着段描述符。 3 # Start the CPU: switch to 32-bit protected mode, jump into C. # The BIOS loads this code from the first sector of the hard disk into # memory at physical address 0x7c00 and starts executing in real mode # with %cs=0 %ip=7c00. 此段注釋說明了要完成的目的:啟動保護模式,轉入C函數。 這里正好說了一下bootasm.S文件的作用。計算機加電后,由BIOS將bootasm.S生成的可執行代碼從硬盤的第一個扇區復制到內存中的物理地址0x7c00處,並開始執行。 此時系統處於實模式。可用內存不多於1M。 .set PROT_MODE_CSEG, 0x8 # kernel code segment selector .set PROT_MODE_DSEG, 0x10 # kernel data segment selector 這兩個段選擇子的作用其實是提供了gdt中代碼段和數據段的索引 .set CR0_PE_ON, 0x1 # protected mode enable flag 這個變量是開啟A20地址線的標志,為1是開啟保護模式 # start address should be 0:7c00, in real mode, the beginning address of the running bootloader .globl start start: 這兩行代碼相當於定義了C語言中的main函數,start就相當於main,BIOS調用程序時,從這里開始執行 .code16 # Assemble for 16-bit mode 因為以下代碼是在實模式下執行,所以要告訴編譯器使用16位模式編譯。 cli # Disable interrupts cld # String operations increment 關中斷,設置字符串操作是遞增方向。cld的作用是將direct flag標志位清零,
這意味着自動增加源索引和目標索引的指令(如MOVS)將同時增加它們。 # Set up the important data segment registers (DS, ES, SS). xorw %ax, %ax # Segment number zero ax寄存器就是eax寄存器的低十六位,使用xorw清零ax,效果相當於movw $0, %ax。 但是好像xorw性能好一些,google了一下沒有得到好答案 movw %ax, %ds # -> Data Segment movw %ax, %es # -> Extra Segment movw %ax, %ss # -> Stack Segment 將段選擇子清零 # Enable A20: # For backwards compatibility with the earliest PCs, physical # address line 20 is tied low, so that addresses higher than # 1MB wrap around to zero by default. This code undoes this. 准備工作就緒,下面開始動真格的了,激活A20地址位。先翻譯注釋:由於需要兼容早期pc,物理地址的第20位綁定為0,所以高於1MB的地址又回到了0x00000. 好了,激活A20后,就可以訪問所有4G內存了,就可以使用保護模式了。 怎么激活呢,由於歷史原因A20地址位由鍵盤控制器芯片8042管理。所以要給8042發命令激活A20 8042有兩個IO端口:0x60和0x64, 激活流程位: 發送0xd1命令到0x64端口 --> 發送0xdf到0x60,done! seta20.1: inb $0x64, %al # Wait for not busy(8042 input buffer empty). testb $0x2, %al jnz seta20.1 #發送命令之前,要等待鍵盤輸入緩沖區為空,這通過8042的狀態寄存器的第2bit來觀察,而狀態寄存器的值可以讀0x64端口得到。 #上面的指令的意思就是,如果狀態寄存器的第2位為1,就跳到seta20.1符號處執行,知道第2位為0,代表緩沖區為空 movb $0xd1, %al # 0xd1 -> port 0x64 outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port 發送0xd1到0x64端口 seta20.2: inb $0x64, %al # Wait for not busy(8042 input buffer empty). testb $0x2, %al jnz seta20.2 movb $0xdf, %al # 0xdf -> port 0x60 outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1 到此,A20激活完成。 # Switch from real to protected mode, using a bootstrap GDT # and segment translation that makes virtual addresses # identical to physical addresses, so that the # effective memory map does not change during the switch. 轉入保護模式,這里需要指定一個臨時的GDT,來翻譯邏輯地址。這里使用的GDT通過gdtdesc段定義。
它翻譯得到的物理地址和虛擬地址相同,所以轉換過程中內存映射不會改變 lgdt gdtdesc 載入gdt movl %cr0, %eax orl $CR0_PE_ON, %eax movl %eax, %cr0 打開保護模式標志位,相當於按下了保護模式的開關。cr0寄存器的第0位就是這個開關,通過CR0_PE_ON或cr0寄存器,將第0位置1 # Jump to next instruction, but in 32-bit code segment. # Switches processor into 32-bit mode. ljmp $PROT_MODE_CSEG, $protcseg 由於上面的代碼已經打開了保護模式了,所以這里要使用邏輯地址,而不是之前實模式的地址了。 這里用到了PROT_MODE_CSEG, 他的值是0x8。根據段選擇子的格式定義,0x8就翻譯成:         INDEX         TI CPL 0000 0000 1 00 0 INDEX代表GDT中的索引,TI代表使用GDTR中的GDT, CPL代表處於特權級。 PROT_MODE_CSEG選擇子選擇了GDT中的第1個段描述符。這里使用的gdt就是變量gdt。
下面可以看到gdt的第1個段描述符的基地址是0x0000,所以經過映射后和轉換前的內存映射的物理地址一樣。
.code32 # Assemble for 32-bit mode protcseg: # Set up the protected-mode data segment registers movw $PROT_MODE_DSEG, %ax # Our data segment selector movw %ax, %ds # -> DS: Data Segment movw %ax, %es # -> ES: Extra Segment movw %ax, %fs # -> FS movw %ax, %gs # -> GS movw %ax, %ss # -> SS: Stack Segment 重新初始化各個段寄存器。 # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) movl $0x0, %ebp movl $start, %esp call bootmain 棧頂設定在start處,也就是地址0x7c00處,call函數將返回地址入棧,將控制權交給bootmain # If bootmain returns (it shouldn't), loop. spin: jmp spin # Bootstrap GDT .p2align 2 # force 4 byte alignment gdt: SEG_NULLASM # null seg SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel gdtdesc: .word 0x17 # sizeof(gdt) - 1 .long gdt # address gdt

2、<asm.h>的內容

// 用來定義段描述符的宏 
#ifndef __BOOT_ASM_H__
#define __BOOT_ASM_H__
// assembler macros to create x86 segments
// 定義了一個空段描述符 
#define SEG_NULLASM                                            \
        .word 0, 0;                                             \
        .byte 0, 0, 0, 0
//  以type,base,lim為參數定義一個段描述符, 其中的0xC0=(1100)2, 其
//  中的第一個1對應於段描述符中的G位,置1表示段界限以4KB為單位
//  第二個1對應於段描述符的D位,置1表示這是一個保護模式下的段描述符
//  具體的關於段描述符的格式定義在mmu.h中 
// The 0xC0 means the limit is in 4096-byte units
// and (for executable segments) 32-bit mode.
#define SEG_ASM(type,base,lim)                                  \
        .word (((lim) >> 12) & 0xffff), ((base) & 0xffff);      \
        .byte (((base) >> 16) & 0xff), (0x90 | (type)),         \
                (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
//  可執行段 
#define STA_X     0x8       // Executable segment
//  非可執行段 
#define STA_E     0x4       // Expand down (non-executable segments)
//  只能執行的段 
#define STA_C     0x4       // Conforming code segment (executable only)
//  可寫段但是不能執行的段 
#define STA_W     0x2       // Writeable (non-executable segments)
//  可讀可執行的段 
#define STA_R     0x2       // Readable (executable segments)
//  表明描述符是否已被訪問;把選擇字裝入段寄存器時,該位被標記為1 
#define STA_A     0x1       // Accessed

(二)問題解答

1.1為何開啟A20,以及如何開啟A20

首先說明一點,這是一個歷史遺留問題。

1981年8月,IBM公司最初推出的個人計算機IBM PC使用的CPU是Inter 8088.在該微機中地址線只有20根。在當時內存RAM只有幾百KB或不到1MB時,20根地址線已經足夠用來尋址這些 內存。其所能尋址的最高地址是0xffff,

也就是0x10ffef。對於超出0x100000(1MB)的尋址地址將默認地環繞到0xffef。當IBM公司與1985年引入AT機時,使用的是Inter 80286 CPU,具有24根地址線,最高可尋址16MB,並且有一個與8088那樣實現地址尋址的環繞。

但是當時已經有一些程序是利用這種環繞機制進行工作的。為了實現完全的兼容性,IBM公司發明了使用一個開關來開啟或禁止0x100000地址比特位。由於當時的8042鍵盤控制器上恰好有空閑的端口引腳(輸出端口P2,引腳P21),

於是便使用了該引腳來作為與門控制這個地址比特位。該信號即被稱為A20。如果它為零,則比特20及以上地址都被清除。從而實現了兼容性。

當A20地址線控制禁止時,程序就像運行在8086上,1MB以上的地址是不可訪問的,只能訪問奇數MB的不連續的地址。為了使能所有地址位的尋址能力,必須向鍵盤控制器8082發送一個命令,鍵盤控制器8042會將A20線置於高電位,使全部32條地址線可用,實現訪問4GB內存。

 1.2打開A20 Gate的具體步驟(參考bootasm.S)

控制 A20 gate 的方法有 3 種:

  1.804x 鍵盤控制器法

  2.Fast A20 法

  3.BIOS 中斷法

ucore實驗中用了第一種 804x 鍵盤控制器法,這也是最古老且效率最慢的一種。

由於在機器啟動時,默認條件下,A20地址線是禁止的,所以操作系統必須使用適當的方法來開啟它。

  1. 等待8042 Input buffer為空;
  2. 發送Write 8042 Output Port (P2)命令到8042 Input buffer;
  3. 等待8042 Input buffer為空;
  4. 將8042 Output Port(P2)得到字節的第2位置1,然后寫入8042 Input buffer

打開A20 Gate的代碼為:

seta20.1:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    #從0x64端口讀入一個字節的數據到al中 
    testb $0x2, %al
    #如果上面的測試中發現al的第2位為0,就不執行該指令 
    jnz seta20.1
   #循環檢查                            
    movb $0xd1, %al                                 # 0xd1 -> port 0x64
    outb %al, $0x64                                 # 0xd1 means: write data to 8042's P2 port
    #將al中的數據寫入到端口0x64中 
seta20.2:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.2

    movb $0xdf, %al                                 # 0xdf -> port 0x60
    outb %al, $0x60                                 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1

第一步是向 804x 鍵盤控制器的 0x64 端口發送命令。這里傳送的命令是 0xd1,這個命令的意思是要向鍵盤控制器的 P2 寫入數據。這就是 seta20.1 代碼段所做的工作。

第二步就是向鍵盤控制器的 P2 端口寫數據了。寫數據的方法是把數據通過鍵盤控制器的 0x60 端口寫進去。寫入的數據是 0xdf,因為 A20 gate 就包含在鍵盤控制器的 P2 端口中,隨着 0xdf 的寫入,A20 gate 就被打開了。

接下來要做的就是進入“保護模式”了。

 2.1什么是GDT表

GDT全稱是Global Descriptor Table,中文名稱叫“全局描述符表”,想要在“保護模式”下對內存進行尋址就先要有 GDT。GDT 表里的每一項叫做“段描述符”,用來記錄每個內存分段的一些屬性信息,每個“段描述符”占 8 字節。

在保護模式下,我們通過設置GDT將內存空間被分割為了一個又一個的段(這些段是可以重疊的),這樣我們就能實現不同的程序訪問不同的內存空間。這和實模式下的尋址方式是不同的, 在實模式下我們只能使用address = segment << 4 | offset的方式進行尋址(雖然也是segment + offset的,但在實模式下我們並不會真正的進行分段)。在這種情況下,任何程序都能訪問整個1MB的空間。而在保護模式下,通過分段的方式,程序並不能訪問整個內存空間

 2.2初始化GDT表

為了使分段存儲管理機制正常運行,需要建立好段描述符和段描述符表,全局描述符表是一個保存多個段描述符的“數組”,其起始地址保存在全局描述符表寄存器GDTR中。GDTR長48位,其中高32位為基地址,低16位為段界限。這里只需要載入已經靜態存儲在引導區的GDT表和其描述符到GDTR寄存器:

lgdt gdtdesc
#CPU 單獨為我們准備了一個寄存器叫做 GDTR 用來保存我們 GDT 在內存中的位置和我們 GDT 的長度。
#GDTR 寄存器一共 48 位,其中高 32 位用來存儲我們的 GDT 在內存中的位置,其余的低 16 位用來存我們的 GDT 有多少個段描述符。
#16 位最大可以表示 65536 個數,這里我們把單位換成字節,而一個段描述符是 8 字節,所以 GDT 最多可以有 8192 個段描述符。
#CPU 不僅用了一個單獨的寄存器 GDTR 來存儲我們的 GDT,而且還專門提供了一個指令用來讓我們把 GDT 的地址和長度傳給 GDTR 寄存器:
lgdt gdtdesc
 
        

gdtdesc 和 gdt 一起放在了 bootasm.S 文件的最底部

# Bootstrap GDT
.p2align 2                                          # force 4 byte alignment
gdt:
    SEG_NULLASM                                     # null seg
    SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)           # code seg for bootloader and kernel
    SEG_ASM(STA_W, 0x0, 0xffffffff)                 # data seg for bootloader and kernel
 
gdtdesc:
    .word 0x17                                      # sizeof(gdt) - 1 # 16 位的 gdt 大小sizeof(gdt) - 1 
   .long gdt                        # address gdt(# 32 位的 gdt 所在物理地址

48 位傳給了 GDTR 寄存器,到此 GDT 就准備好了

  3.1如何使能和進入保護模式

  3.1.1修改CR0寄存器的PE值

如同 A20 gate 這個開關負責打開 1MB 以上內存尋址一樣,想要進入“保護模式”我們也需要打開一個開關,這個開關叫“控制寄存器”,x86 的控制寄存器一共有 4 個分別是 CR0、CR1、CR2、CR3(這四個寄存器都是 32 位的),而控制進入“保護模式”的開關在 CR0 上。 

CR0中包含了6個預定義標志,0位是保護允許位PE(Protedted Enable),用於啟動保護模式,如果PE位置1,則保護模式啟動,如果PE=0,則在實模式下運行。

CR0 上和保護模式有關的位,如圖所示:

 

打開保護模式的代碼為:

    movl %cr0, %eax
    orl $CR0_PE_ON, %eax
    movl %eax, %cr0

 

因為我們無法直接操作 CR0,所以我們首先要用一個通用寄存器來保存當前 CR0 寄存器的值,這里第一行就是用通用寄存器 eax 來保存 cr0 寄存器的值;

然后 CR0_PE 這個宏的定義在 mmu.h 文件中,是個數值 0x00000001,將這個數值與 eax 中的 cr0 寄存器的值做“或”運算后,就保證將 cr0 的第 0 位設置成了 1 即 PE = 1 保證打開了保護模式的開關。

而 cr0 的第 31 位 PG = 0 表示我們只使用分段式,不使用分頁,這時再將新的計算后的 eax 寄存器中的值寫回到 cr0 寄存器中就完成了到保護模式的切換。

  3.1.2通過長跳轉,更新CS寄存器的基地址 

ljmp $PROT_MODE_CSEG, $protcseg

其中protcseg是一個標號(標號的用途在本文中的實驗相關部分已說明)

由於已經使能了保護模式,所以這里要使用邏輯地址,而不是之前實模式的地址了
這里還要注意PROT_MODE_CSEG和PROT_MODE_DSEG,這兩者分別定義為0x8和0x10,表示代碼段和數據段的選擇子。

根據段選擇子的格式定義,0x8就翻譯成:

        INDEX TI   CPL
        0000    0000    1000
INDEX代表GDT中的索引,TI代表使用GDTR中的GDT, CPL代表處於特權級。

 

  3.1.3設置段寄存器,並建立堆棧

注意這里建立堆棧,ebp寄存器按理來說是棧幀的,但是這里並不需要把它設置為0x7c00,因為這里0x7c00是棧的最高地址,它上面沒有有效內容,而之后因為調用,ebp會被設置為被調用的那個函數的棧的起始地址,這里就不用管它了。

 

1         movw $PROT_MODE_DSEG, %ax 2         movw %ax, %ds 3         movw %ax, %es 4         movw %ax, %fs 5         movw %ax, %gs 6         movw %ax, %ss 7         movl $0x0, %ebp 8         movl $start, %esp

 

 

 

  3.1.4轉到保護模式完成,進入boot主方法

          call bootmain    

 

四、總結

Bootload的啟動過程可以概括如下:

首先,BIOS將第一塊扇區(存着bootloader)讀到內存中物理地址為0x7c00的位置,同時段寄存器CS值為0x0000,IP值為0x7c00,之后開始執行bootloader程序。CLI屏蔽中斷(屏蔽所有的中斷:為中斷提供服務通常是操作系統設備驅動程序的責任,因此在bootloader的執行全過程中可以不必響應任何中斷,中斷屏蔽是通過寫CPU提供的中斷屏蔽寄存器來完成的);CLD使DF復位,即DF=0,通過執行cld指令可以控制方向標志DF,決定內存地址是增大(DF=0,向高地址增加)還是減小(DF=1,向地地址減小)。設置寄存器 ax,ds,es,ss寄存器值為0;A20門被關閉,高於1MB的地址都默認回卷到0,所以要激活A20,給8042發命令激活A20,8042有兩個IO端口:0x60和0x64, 激活流程: 發送0xd1命令到0x64端口 --> 發送0xdf到0x60,打開A20門。從實模式轉換到保護模式(實模式將整個物理內存看成一塊區域,程序代碼和數據位於不同區域,操作系統和用戶程序並沒有區別對待,而且每一個指針都是指向實際的物理地址,地址就是IP值。這樣,用戶程序的一個指針如果指向了操作系統區域或其他用戶程序區域,並修改了內容,那么其后果就很可能是災難性的),所以就初始化全局描述符表使得虛擬地址和物理地址匹配可以相互轉換;lgdt匯編指令把通過gdt處理后的(asm.h頭文件中處理函數)描述符表的起始位置和大小存入gdtr寄存器中;將CR0的第0號位設置為1,進入保護模式;指令跳轉由代碼段跳到protcseg的起始位置。設置保護模式下數據段寄存器設置堆棧寄存器並調用bootmain函數

五、參考鏈接

匯編基本語法簡介

清華大學教學內核ucore學習系列(1) bootloader

ucore-lab1-練習3report

學習xv6從實模式到保護模式

ucore練習三

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM