📈
ucore-analysis
  • Introduction
  • lab1
    • boot
      • bootasm
      • bootmain
    • kern
      • debug
        • kmonitor
        • panic
      • init
        • init
      • libs
        • readline
      • mm
        • pmm
      • trap
        • trap
        • trapentry
        • vectors
    • libs
    • tools
  • lab解析
    • lab1
      • 练习1
      • 练习2
      • 练习3
      • 练习4
      • 练习6
      • 扩展练习
      • Piazza优质问题/笔记收集
    • lab2
      • 练习1
      • 练习2
      • 练习3
    • lab3
    • lab4
    • lab5
    • lab6
    • lab7
    • lab8
  • uCore代码
    • boot
      • asm.h
      • bootasm.S
      • bootmain.c
      • (lab1) bootasm.S
    • kern
      • debug
        • assert.h
        • kdebug.c
        • kdebug.h
        • kmonitor.c
        • kmonitor.h
        • panic.c
        • stab.h
        • (lab1) kdebug.c
      • driver
        • clock.c
        • clock.h
        • console.c
        • console.h
        • ide.c
        • ide.h
        • intr.c
        • intr.h
        • kbdreg.h
        • picirq.c
        • picirq.h
      • fs
        • devs
          • dev.c
          • dev_disk0.c
          • dev.h
          • dev_stdin.c
          • dev_stdout.c
        • sfs
          • bitmap.c
          • bitmap.h
          • sfs.c
          • sfs_fs.c
          • sfs.h
          • sfs_inode.c
          • sfs_io.c
          • sfs_lock.c
        • swap
          • swapfs.c
          • swapfs.h
        • vfs
          • inode.c
          • inode.h
          • README.md
          • vfs.c
          • vfsdev.c
          • vfsfile.c
          • vfs.h
          • vfslookup.c
          • vfspath.c
        • file.c
        • file.h
        • fs.c
        • fs.h
        • iobuf.c
        • iobuf.h
        • sysfile.c
        • sysfile.h
      • init
        • entry.S
        • init.c
        • (lab1) init.c
      • libs
        • readline.c
        • stdio.c
        • string.c
      • mm
        • default_pmm.c
        • default_pmm.h
        • kmalloc.c
        • kmalloc.h
        • memlayout.h
        • mmu.h
        • pmm.c
        • pmm.h
        • swap.c
        • swap_fifo.c
        • swap_fifo.h
        • swap.h
        • vmm.c
        • vmm.h
        • (lab2) pmm.c
        • (lab3) vmm.c
      • process
        • entry.S
        • proc.c
        • proc.h
        • switch.S
        • (lab4) proc.c
        • (lab5) proc.c
      • schedule
        • default_sched.c
        • default_sched.h
        • default_sched_stride.c
        • sched.c
        • sched.h
      • sync
        • check_sync.c
        • monitor.c
        • monitor.h
        • sem.c
        • sem.h
        • sync.h
        • wait.c
        • wait.h
      • syscall
        • syscall.c
        • syscall.h
      • trap
        • trap.c
        • trapentry.S
        • trap.h
        • vectors.S
        • (lab1) trap.c
    • libs
      • atomic.h
      • defs.h
      • dirent.h
      • elf.h
      • error.h
      • hash.c
      • list.h
      • printfmt.c
      • rand.c
      • skew_heap.h
      • stat.h
      • stdarg.h
      • stdio.h
      • stdlib.h
      • string.c
      • string.h
      • unistd.h
      • x86.h
    • tools
      • boot.ld
      • function.mk
      • gdbinit
      • grade.sh
      • kernel.ld
      • mksfs.c
      • sign.c
      • user.ld
      • vector.c
    • user
      • libs
        • dir.c
        • dir.h
        • file.c
        • file.h
        • initcode.S
        • lock.h
        • panic.c
        • stdio.c
        • syscall.c
        • syscall.h
        • ulib.c
        • ulib.h
        • umain.c
      • badarg.c
      • badsegment.c
      • divzero.c
      • exit.c
      • faultread.c
      • faultreadkernel.c
      • forktest.c
      • forktree.c
      • hello.c
      • ls.c
      • matrix.c
      • pgdir.c
      • priority.c
      • sfs_filetest1.c
      • sh.c
      • sleep.c
      • sleepkill.c
      • softint.c
      • spin.c
      • testbss.c
      • waitkill.c
      • yield.c
    • Makefile
    • (lab1) Makefile
  • 附录:工具使用
    • 如何编辑该文档
    • 讨论区的维护方法
    • 使用Travis CI自动化更新gitbook
Powered by GitBook
On this page
  • 练习3: 分析bootloader进入保护模式的过程
  • 相关文件
  • 为何开启A20,如何开启A20
  • 背景知识
  • 问题分析
  • 参考文献
  • 如何初始化GDT表
  • 背景知识
  • 问题分析
  • 参考文献
  • 如何使能和进入保护模式
  • 背景知识
  • 问题分析
  • 参考文献

Was this helpful?

  1. lab解析
  2. lab1

练习3

Previous练习2Next练习4

Last updated 4 years ago

Was this helpful?

练习3: 分析bootloader进入保护模式的过程

  • 为何开启A20,以及如何开启A20

  • 如何初始化GDT表

  • 如何使能和进入保护模式

相关文件

为何开启A20,如何开启A20

背景知识

  1. A20是什么?

    A20为处理器的第21根地址线。A即Address。A0是第一根地址线,以此类推,A19是第20根地址线,A20是第21根地址线。

  2. 开启A20是什么?

    开启A20指的是开启A20 Gate。

    A20 Gate是80286处理器为了兼容8086处理器的程序而设置的,一个控制第21根地址线(A20 Line)的与门。

  3. 为什么要有A20 Gate?

    8086处理器有20根地址线(A0 ~ A19),可以寻址1MB内存。当物理地址达到0xFFFFF (11111111111111111111B)时,再加1,由于只能维持20位地址,进位会丢失导致地址变为0x00000 (00000000000000000000B)。

    而80286处理器有24根地址线(A0 ~ A23),可以寻址16M内存。当物理地址达到0x0FFFFF (000011111111111111111111B),再加1,得到的地址为0x100000 (000100000000000000000000)。若运行8086处理器的程序,会导致寻址出现错误。

    为了让80286处理器兼容8086处理器的程序,IBM在主板上添加了A20 Gate,当A20 Gate关闭(值为0)时,在与门的操作下,A20 Line的值始终为0,所以当0x(0)FFFFF [(0000)11111111111111111111B]增加1时80286处理器可以得到跟8086处理器一样的结果0x(0)00000 [(0000)00000000000000000000]。

  4. 如何开启A20 Gate?

    A20 Gate的控制阀门在8042键盘控制器(8042芯片)内,为输出端口P2的第二个bit(P21引脚)。

    为开启A20 Gate,只需向P2端口写入第二个bit为1的一个字节即可。

问题分析

  1. 为何开启A20?

    为了使CPU可以正常访问高于16位的地址,为进入可寻址32位的保护模式做准备。

  2. 如何开启A20?

    以下为开启A20的代码:

        #    File:    bootasm.S
        #    Line:    25-43
    
        # 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

    8042芯片的I/O端口为0x60~0x6f,实际上IBM PC/AT使用的只有0x60和0x64两个端口。

    8042芯片有四个8 bit长的寄存器:Input Buffer, Output Buffer, Status Register, Control Register。

    为向输出端口P2写入数据,需要先将要写入的数据传入8042芯片的Output Buffer,然后让8042芯片会将Output Buffer的内容写入输出端口P2。

    向Output Buffer写入数据的流程为:向0x64端口发送0xd1,表示写入,然后再将要写入的数据传入0x60端口。

    在向Output Buffer写入数据前,需要先读取Status Register的数据已获取芯片状态,得到的结果第二个bit表示0x60和0x64端口是否有数据,若无数据才可正常写入。

    以下为具体步骤:

        #    File:    bootasm.S
        #    Line:    29-32
    
    seta20.1:
        inb $0x64, %al
        testb $0x2, %al
        jnz seta20.1

    首先,从0x64端口读入数据存入al,将al的值和0x02(00000010B)进行与操作,若得到结果不为0,即0x60和0x64端口内有数据,则跳转回标记seta20.1并重复这一操作,直到端口内无数据,则进行下一步。

        #    File:    bootasm.S
        #    Line:    34-35
    
        movb $0xd1, %al
        outb %al, $0x64

    然后将0xd1经al写入0x64端口,表示将要向0x60端口传入要写进输出端口P2的数据。

        #    File:    bootasm.S
        #    Line:    37-43
    
    seta20.2:
        inb $0x64, %al
        testb $0x2, %al
        jnz seta20.2
    
        movb $0xdf, %al
        outb %al, $0x60

    这里与seta20.1部分类似,从0x64端口获取状态,直到0x60和0x64端口内没有数据,即可执行下一部分。

    然后经al向0x60端口写入数据以传入Output Buffer,然后8042芯片自动将Output Buffer中的数据写入输出端口P2。

    在这段代码中写入0x60端口的数据为0xdf (11011111B),其第二个bit为1,即表示开启A20 Gate。另外7个bit表示其它参数,这里不再做了解。

参考文献

  • 《x86汇编语言:从实模式到保护模式》,电子工业出版社;

如何初始化GDT表

背景知识

  1. 什么是GDT表

    Global Descriptor Table(全局描述符表)是IA32架构CPU的一种数据结构,它保存多个段描述符,其起始地址保存在全局描述符表寄存器(GDTR)中。GDTR长48位,其中高32位为基地址,低16位为段界限。

    其中,全局描述符表中第一个段描述符设定为空段描述符。GDTR中的段界限以字节为单位。对于含有N个描述符的描述符表的段界限通常可设为8*N-1。

    GDT表由汇编指令lgdt加载,该指令将GDT表地址加载到GDTR中。

  2. GDT表的作用

    在保护模式下,大多数内存管理和中断服务例程都是通过GDT表控制的。每个描述符(Descriptor)都存储着CPU可能需要的对象信息。

  3. GDT表存储的内容

    • 空描述符(不会被加载)

    • 一个代码段描述符(在内核中,其类型应设为0x9A)

    • 一个数据段描述符(其类型应设为0x92)

    • 一个TSS(Task State Segment 任务段描述符)(uCore中没有使用)

    • ...

问题分析

  1. 如何初始化GDT表

        #    File:    bootasm.S
        #    Line:    49
    
        lgdt gdtdesc

    gdtdesc即GDT表的地址(见下文),使用lgdt指令加载即可。

  2. uCore中GDT表的格式

        #    File:    bootasm.S
        #    Line:    77-86
    
    # 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

    gdt为存储的两个段描述符(以及之前的一个空的段描述符),gdtdesc为GDT表的段界限及段描述符(gdt)的起始地址。两个段描述符分别为:代码段描述符,数据段描述符。

    以下为代码中创建段描述符的宏以及其参数。

     //    File:    asm.h
     //    Line:    4-23
    
     /* Assembler macros to create x86 segments */
    
     /* Normal segment */
     #define SEG_NULLASM                                             \
         .word 0, 0;                                                 \
         .byte 0, 0, 0, 0
    
     #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)
    
     /* Application segment type bits */
     #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)
     #define STA_A       0x1     // Accessed

    由以上代码可知,代码描述符的类型(type)值为(0x90 | (STA_X|STA_R))即(0x90 | 0x0A) = 0x9A,数据段描述符的类型(type)值为(0x90 | STA_W)即(0x90 | 0x02) = 0x92,与文档描述一致。

参考文献

如何使能和进入保护模式

背景知识

  1. 什么是保护模式

    保护模式与实模式相对应,是在80286处理器之后现代Intel处理器主要的操作模式。在80386处理器或更高版本上,32位保护模式允许使用多个虚拟空间,每个虚拟空间最多具有4GB的可寻址内存,并能通过Rings限制可用的指令集使系统能够执行严格的内存和硬件I/O保护。

问题分析

  1. 如何进入保护模式

    在进入保护模式前,需要进行一系列操作:

    1. 禁用所有中断(执行cli命令)

    2. 启用A20 Line

    3. 使用适用于代码、数据和堆栈的段描述符加载GDT表

    做完准备工作后,就可以切换到保护模式了。

    控制实模式与保护模式切换的开关位于CR0寄存器。CR0是处理器内部的控制寄存器(Control Register, CR),它是32位的寄存器,包含了一系列用于控制处理器操作模式和运行状态的标志位,它的第1位是保护模式允许位,把该位置设为1后处理器就会进入保护模式。

     #    File:    bootasm.S
     #    Line:    50-52
    
     movl %cr0, %eax
     orl $CR0_PE_ON, %eax
     movl %eax, %cr0

    由于CR0是32位寄存器,所以我们需要先将其值存入EAX寄存器。EAX寄存器是一个32位寄存器,常用的AX寄存器是EAX的低16位。

    在存入数据后,对EAX的值与常量CR0_PE_ON进行位或操作,常量CR0_PE_ON的值为0x1,进行位或操作后可使EAX内数值的第1位变为1,即 将CR0状态标志位的保护模式允许设为打开。

    然后将EAX寄存器内的值传入CR0,这时CR0内第1位的值变成了1,其它位的值也没有发生变化,这就标志着保护模式的正常启动。

参考文献

  • 《x86汇编语言:从实模式到保护模式》,电子工业出版社;

;

;

练习3文档
相关阅读:保护模式和分段机制
bootasm.S
关于A20 Gate
A20 Line
保护模式和分段机制
Global Descriptor Table
Protected Mode