操作系统开发:编写开机引导

news2024/11/15 10:29:11

操作系统是用来管理与协调硬件工作的,开发一款操作系统有利于理解底层的运转逻辑,本篇内容主要用来理解操作系统是如何启动的,又是如何加载磁盘中的内核的,该系列文章参考各类底层书籍,通过自己的理解并加以叙述,让内容变得更加简单,一目了然,即可学到知识又能提高自己的表述能力。

注释: 该系列笔记是在学习《操作系统真相还原》时通过阅读后简化并适当描述整理的学习笔记,首先,致敬作者郑刚博士,在读本书时能深刻的感觉到作者写书时一丝不苟的态度,书很厚写的,讲解细致幽默,很能让人愿意继续读下去,同时也不得不佩服作者计算机底层功力的深厚,转载本文请一并附带郑刚版权信息。

1. BOIS 是如何苏醒的

BIOS 基本输入输出系统,BIOS代码所做的工作是一成不变的,所以他是被固化到ROM中的一块只读区域中,在开机时此ROM会被映射到低端1MB内存的顶部,原因是系统在开启时默认是实地址模式(该模式最大寻址范围0-fffff),所以其寻址范围也就被限制在了0xF0000-x0xFFFFF区域中,这64KB的内存就是BIOS的执行代码.

在开机的一瞬间,CPU的CS:IP寄存器会被强制初始化为0xF000:0xFFF0,在实地址模式下该地址需要乘以16也就是左移四位加上偏移地址得到,于是0xF000:0xFFF0就等效于0xFFFF0此处的地址距离0xFFFFF只有16个字节的空间,里面存放着一条jmp far f000:e05b = fe05b的汇编指令,该指令将跳转到真正的BIOS开始的位置.

接着BIOS将会通过自身的代码对硬件进行自检测,在初始化硬件后,则开始向内存0x000-0x3ff中初始化数据结构以及拷贝中断向量表,紧接着BIOS将会通过调用int 19h中断,此中断用以检测计算机中的硬盘,如果检测到0盘0道1扇区末尾的两个字节是0x55,0xaa则认为此扇区确实存在,于是就会将此区域中的内容,加载到内存0x7c00的位置,并通过一条jmp far 0:0x7c00h的指令跳转到该位置执行,这样BIOS就将CPU控制权交给了MBR了,而BIOS将会再次睡去.

2. MBR 继续执行引导

如上提到过的0x7c00就是MBR代码的开始位置,之所以是0x7C00是因为,DOS中要求最小内存是32KB而MBR大小必须是512字节,所以选择32kB中的最后1KB的位置最为合适(32KB(0x8000)-1KB(0x400)=>0x7c00),这就是0x7C00的由来,同时还需要保证第510-511字节必须为0x55,0xaa才可以,这就需要在末尾部分自动补齐两字节的填充.

简单的引导MBR的代码如下,首先我们需要先初始化每个段寄存器DS,ES,SS,FS,SP然后通过调用两次int 0x10中断对命令行进行置空操作,最后通过mov ax,01301h也就是13号中断,打印出字符串.

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
        mov ax,cs
        mov ds,ax
        mov es,ax
        mov ss,ax
        mov fs,ax
        mov sp,0x7c00

        mov ax,0x600      ; 清屏范围,也就是宽度
        mov bx,0x0
        mov cx,0x0        ; 清屏 左上角(0,0)
        mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)

        int 0x10
        mov dh,0x0        ; 设置光标列号
        mov dl,0x0        ; 设置光标行号
        mov bh,0x0        ; 页码
        int 0x10

        mov ax,Message
        mov bp,ax         ; 保存字符串地址
        mov cx,15         ; 保存字符串长度
        mov ax,01301h     ; 子功能号13是显示字符及属性
        mov bx,000ch      ; 页号位0,使用黑色为背景色,红色为字体颜色
        mov dl,0
        int 10h           ; 调用10h号中断,用来显示字符
        hlt
        ret

Message: db "hello lyshark !"
times 510-($-$$) db 0  ; 填充剩余的510字节的空间为0
db 0x55,0xaa           ; mbr的结束标志

我们直接将其保存为mbr.asm文件,通过Nasm汇编器编译为二进制文件,然后再通过dd命令写入到一个镜像文件中,具体编译流程如下,这里需要下载好Windows版本的dd命令.

# 首先编译为二进制文件
nasm mbr.asm -o mbr.bin

# 将镜像写入到kernel.img镜像中,写入长度512字节,循环1次
dd if=mbr.bin of=kernel.img bs=512 count=1 conv=notrunc

# 通过seek跳过第一个扇区,然后向后填充4096字节的0
dd if=/dev/zero of=kernel.img seek=1 bs=512 count=4096

由于我们使用的模拟器是Bochs x86所以,在制作好镜像后,需要在编写一个虚拟机配置文件,该配置文件命名为mbr.src其内部需要定义好虚拟机的类型,启动方式,镜像位置等基本参数,一个简易版定义语句如下.

megs:32
romimage:file=./BIOS-bochs-latest
vgaromimage:file=./VGABIOS-lgpl-latest
boot:disk
mouse:enabled=0
ata0-master: type=disk, path="kernel.img", mode=flat, status=inserted
keyboard: keymap=./x11-pc-de.map

启动时可以直接调用bochsdbg -q -f mbr.src命令,使用调试模式运行,并通过语句vb sp:0x7c00在开头下断点,使用c命令可运行到MBR代码处,单步n执行,即可输出一段话,标志着MBR已经成功被加载.

3. 让MBR直接驱动显卡

如上代码,我们通过调用BIOS提供的int 0x10中断来实现打印字符操作,但我们在后期必须要借助显卡来输出图像,而显卡是外部设备,必须通过总线来操作。

由于CPU使用的信号是TTL电平,而外部设备都是机械设备,故他们不会使用该电平驱动,这就导致CPU与硬件设备没有办法实现沟通,硬件工程师们提供的方法是,在这两者之间架起一座桥,也就是在CPU和外设之间加上一层IO接口,该接口的作用就是实现CPU和外设之间相互做协调转换。

其次外部设备的种类也是多种多样的,其输出的信号可能是数字信号,也可能是模拟信号,而我们的CPU只能处理数字信号,数字信号需要经过数模转换器<D/A>成模拟量才能送到外设来驱动硬件工作,模拟量也同样需要经过模数转换器<A/D>转换成数字量才能被CPU直接处理,所以接口电路中需要包括A/D转换器和D/A转换器。

转换后的数字信号,会经过总线进行传递,总线的别名是BUS,之所以叫做BUS是因为其是公共线路,所有硬件设备都会走此线路,但同一时刻,CPU只能和一个IO接口(寄存器/端口)通信,当有多个IO接口同时想和CPU通信时,那么IO仲裁模块会对其进行竞争与选优,仲裁模块固化到,输入输出控制中心(ICH)也就是南桥芯片上的。

多数情况下,南桥和北桥是成对出现的,南桥主要负责连接PCI,PCI-Express,AGP等低速设备,而北桥则用于链接高速设备,如内存等。

IO接口都是串行口,其在设计之初就是负责与CPU进行通信的,我们想要与CPU通信,其实是向这些接口中写入数据,同时为了区别CPU中的寄存器,所以把IO接口叫做端口,某些外设可以通过内存映射来访问,即把某些端口映射到指定内存中,访问某个内存区域就相当于访问了指定的端口。

由于显卡的起始地址为0xb800向该地址写入数据即可回显在显示器上,如下代码是一个简单的填充过程。

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
        mov ax,cs
        mov sp,0x7c00
        mov ax,0xb800
        mov gs,ax

        mov ax,0x600      ; 清屏范围,也就是宽度
        mov bx,0x0
        mov cx,0x0        ; 清屏 左上角(0,0)
        mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)
        int 0x10

        mov dh,0x0        ; 设置光标列号
        mov dl,0x0        ; 设置光标行号
        mov bh,0x0        ; 页码
        int 0x10

        mov byte [gs:0x00],'L'
        mov byte [gs:0x01],0xa4 ; 显示A=绿色闪烁 4=红色

        mov byte [gs:0x02],'y'
        mov byte [gs:0x03],0xa5

        mov byte [gs:0x04],'S'
        mov byte [gs:0x05],0xa6

        mov byte [gs:0x6],'h'
        mov byte [gs:0x7],0xa7

        mov byte[gs:0x8],'a'
        mov byte [gs:0x9],0xa6

        mov byte[gs:0xa],'r'
        mov byte [gs:0xb],0xa5

        mov byte[gs:0xc],'k'
        mov byte [gs:0xd],0xa4
        hlt
        ret

times 510-($-$$) db 0  ; 填充剩余的510字节的空间为0
db 0x55,0xaa           ; mbr的结束标志

编译并运行这段代码,由于使用的是显卡输出,所以在输出色彩上,我们的选择余地更多了。

如上代码中需要注意,偶数行gs:0x04代表的是输出数据,奇数行gs:0x05则代表颜色背景色,如果需要实现循环输出,那么我们除需要考虑循环条件外,还应把基数偶数行也考虑进来。

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
	; 清屏和设置光标位置
	mov ax,0x600      ; 清屏范围,也就是宽度
	mov bx,0x0
	mov cx,0x0        ; 清屏 左上角(0,0)
	mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)
	int 0x10

	mov dh,0x0        ; 设置光标列号 左上角(0,0)
	mov dl,0x0        ; 设置光标行号 右下角(0,0)
	mov bh,0x0        ; 页码
	int 0x10

	; 初始化,使SP寄存器指向段基址0X7C0处,GS指向显存基地址
	mov ax,cs
	mov sp,0x7c00
	mov ax,0xb800
	mov gs,ax              ; 设置显存地址

	; 设置字符串长度与字符串基地址
	mov cx, msglen        ; 获取字符串长度
	mov si, message       ; 设置字符串基址
	xor di, di            ; 每次清空di寄存器

loop_str:
	mov al, [si]            ; 每次取出一个字符
	mov [gs:di], al         ; 将字符逐一赋值到显存中
	inc si
	inc di
	
	mov byte [gs:di], 000ch ; 设置字体颜色
	inc di
	loop loop_str

	hlt      ; 程序在此处终止

;message db "Loading MBR...",0ah,0dh
message db "Loading MBR..."
msglen  equ $ - message

times 510-($-$$) db 0  ; 填充剩余的510字节的空间为0
db 0x55,0xaa           ; mbr的结束标志

4. 让MBR直接驱动硬盘

既然显卡中存在端口可以被操作,那么硬盘也同样存在,硬盘控制器属于IO接口,如果想让硬盘工作,我们需要通过读写硬盘控制器上的端口,此处的端口指的就是硬盘控制器上的寄存器组。

  • 硬盘控制器中的端口可被分为两种,最主要的是 Command Block registers 组中的寄存器
  • Command Block registers 用于向硬盘驱动器写入命令字或者从硬盘控制器获得硬盘状态
  • Control Block registers 用于控制硬盘工作状态

一般硬盘中的一个通道包括两片硬盘,其中0为主盘,1为从盘,硬盘控制器中的主要寄存器如下,其中主盘所对应的通道为Primary,后面的那个Secondary则是从盘通道,主从盘调用中断号完全不同:

  • DATA 寄存器主要负责管理数据,相当于数据的门,作用是读取或写入数据

  • 读硬盘时: 硬盘准备好数据后,硬盘控制器将其放在内部的缓冲区中,不断读此寄存器便是读出缓冲区中的全部数据。

  • 写硬盘时: 把数据源源不断地输送到此端口,数据便被存入缓冲区里,硬盘控制器发现这个缓冲区中有数据了,便将此处的数据写入相应的扇区中。

  • ERROR/FEATURES 由于环境不同用途不同,所以两个寄存器名字指的是同一个

  • 读硬盘时: 端口0x171或0x1F1的寄存器名字叫Error寄存器,若读取失败,里面存储的是失败状态信息,并且0x1F2端口中存储未读的扇区数。

  • 写硬盘时: 就变成feauture寄存器,用于写命令的参数,有些命令需要指定额外参数,这些参数就写在 Feature 寄存器中。

  • SectorCount 寄存器用来指定待读取或待写入的扇区数

  • 硬盘每完成1个扇区,就会将此寄存器的值减1,如果中间失败了,此寄存器中的值便是尚未完成的扇区。

  • LBA 逻辑块地址,解决了磁盘在柱面磁头扇区上寻址的麻烦(CHS),寻址时不用再考虑扇区所在的物理结构,当今LBA有两种,一种是LBA28最大支持128GB的寻址,另外一种是LBA48,最大支持128PB寻址.

  • LBA寄存器,有三种不同的形式: LBA low、LBA mid、LBA high

  • LBA low 寄存器用来存储28位地址的第0-7位

  • LBA mid 寄存器用来存储第8-15位

  • LBA high 寄存器存储第16-23位

  • Device 寄存器是个杂项,宽度8位,此寄存器的低4位用来存储LBA地址的第24-27位

  • 第4位用来指定通道上的主盘或从盘

  • 第6位用来设置是否启用LBA方式

  • 第5位和第7位是固定为1的,称为MBS位

  • Status 状态寄存器,控制端口0x1F7或0x177,它是8位宽度寄存器,用来给出硬盘的状态信息

  • 第0位是ERR位,为1表示命令出错,具体原因可见error寄存器。

  • 第3位是data request位,为1表示硬盘已经把数据准备好了机现在可以把数据读出来。

  • 第6位是DRDY,表示硬盘就绪表示硬盘检测正常,可以继续执行一些其他命令。

  • 第7位是BSY位,表示硬盘是否繁忙,为1表示硬盘正忙。

  • 注释: 状态位与Error寄存器一样,在写硬盘时寄存器就变成Command,此寄存器用来存储让硬盘执行的命令,只要把命令写进此寄存器,硬盘就开始工作。

由于MBR受制于只能容纳512字节大小的数据,没法为内核准备好环境,更没法将内核成功加载到内存并运行,此时我们需要让MBR实现从硬盘加载Loader程序到内存,加载完成后再将接力棒交给Loader继续运行,MBR加载磁盘代码如下。

%include "boot.inc"
SECTION MBR vstart=0x7c00         
   mov ax,cs      
   mov ds,ax
   mov es,ax
   mov ss,ax
   mov fs,ax
   mov sp,0x7c00
   mov ax,0xb800
   mov gs,ax

; 清屏
   mov ax, 0600h
   mov bx, 0700h
   mov cx, 0
   mov dx, 184fh
   int 10h

   mov eax,LOADER_START_SECTOR	 ; 起始扇区lba地址
   mov bx,LOADER_BASE_ADDR       ; 写入的地址
   mov cx,1			             ; 待读入的扇区数
   call rd_disk_m_16		     ; 以下读取程序的起始部分(一个扇区)
  
   jmp LOADER_BASE_ADDR

;功能:读取硬盘n个扇区
rd_disk_m_16:	   
      mov esi,eax	  ;备份eax
      mov di,cx		  ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数
      mov dx,0x1f2
      mov al,cl
      out dx,al            ;读取的扇区数

      mov eax,esi	   ;恢复ax

;第2步:将LBA地址存入0x1f3 ~ 0x1f6

      ;LBA地址7~0位写入端口0x1f3
      mov dx,0x1f3                       
      out dx,al                          

      ;LBA地址15~8位写入端口0x1f4
      mov cl,8
      shr eax,cl
      mov dx,0x1f4
      out dx,al

      ;LBA地址23~16位写入端口0x1f5
      shr eax,cl
      mov dx,0x1f5
      out dx,al

      shr eax,cl
      and al,0x0f	   ;lba第24~27位
      or al,0xe0	   ; 设置7~4位为1110,表示lba模式
      mov dx,0x1f6
      out dx,al

;第3步:向0x1f7端口写入读命令,0x20 
      mov dx,0x1f7
      mov al,0x20                        
      out dx,al

;第4步:检测硬盘状态
  .not_ready:
      ;同一端口,写时表示写入命令字,读时表示读入硬盘状态
      nop
      in al,dx
      and al,0x88	   ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
      cmp al,0x08
      jnz .not_ready	   ;若未准备好,继续等。

;第5步:从0x1f0端口读数据
      mov ax, di
      mov dx, 256
      mul dx
      mov cx, ax	   ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字,
			   ; 共需di*512/2次,所以di*256
      mov dx, 0x1f0
  .go_on_read:
      in ax,dx
      mov [bx],ax
      add bx,2		  
      loop .go_on_read
      ret

   times 510-($-$$) db 0
   db 0x55,0xaa

由于MBR是占据了硬盘的第0扇区不可再使用,从第1扇区之后的扇区均可使用,此处我们把loader放到第2扇区,MBR从第2扇区中把它读出来,并将loader的加载地址选为0x900的位置,编译镜像需要注意扇区位置。

# 编译并连接include目录
nasm -I include/ mbr.asm -o mbr.bin

# 第一个扇区中写入512字节的mbr引导
dd if=mbr.bin of=kernel.img bs=512 count=1 conv=notrunc
dd if=/dev/zero of=kernel.img seek=1 bs=512 count=4096

# 第二个loader加载器,seek跳过两个扇区并写入
nasm -I include/ loader.asm -o loader.bin
dd if=loader.bin of=kernel.img bs=512 count=1 seek=2 conv=notrunc

至此虽然输出效果与在MBR中直接操作显卡输出结果一致,但本质是不同的,此处代码中MBR主要负责从硬盘中的第3个扇区中读入Loader加载器到内存,并将CPU指针指向它,后期的输出纯粹是Loader加载器所为。

5. 实模式切入保护模式

保护模式最早出现在80286系列处理器中,之所以会出现保护模式是因为实地址模式中存在以下问题:

1.实模式下操作系统与用户程序属于同一特权级R0,无法区分系统程序与用户程序。
2.用户程序引用的地址都是指向真实的物理地址,所以逻辑地址就等于物理地址。
3.用户程序可以直接修改段基址,当访问超过64KB的内存区域时需要手动切换段基址。
4.共20条地址线最大可用1MB内存,且一次只能运行1个程序,无法充分利用计算资源。

为了克服内存访问限制,CPU厂商则开发出保护模式,在保护模式下物理地址不能被程序直接访问,在访问时需要将虚拟地址转换为物理地址再去访问,而对于程序而言这一系列操作都是透明的。

这个地址转换过程是由操作系统与处理器共同协作完成的,处理器在硬件上提供地址转换部件,操作系统提供转换过程中所需要的页表。

实模式与保护模式

相对于实地址模式,保护模式对寄存器进行了一定的扩展,CPU扩展为32位后,其地址总线和数据总线也变为32位,寻址空间达到了4GB,为了让一个寄存器可以访问到4GB空间,需要将寄存器宽度提升到32位。

除段寄存器外,通用寄存器、指令指针寄存器、标志寄存器都由原来的16位扩展到了32位,段寄存器16位就够用了。

相对于实地址模式,保护模式大大提高了对内存段的保护能力,GDT全局描述符就是对特定内存段属性进行描述的数据结构,该数据结构中的每一个表项称为段描述符,大小为64字节,用来描述各个内存段的起始地址、大小、权限等信息。

由于全局描述符表GDT很大,所以默认将其放在了内存中,由GDTR寄存器指向它,GDTR是个48位的寄存器,通常使用lgdtr指令操作,控制该寄存器。

这样,段寄存器中保存的再也不是段基址了,里面保存的内容叫 段选择子(selector) 该选择子其实就是个数,用这个数来索引全局描述符表中的段描述符,如果把全局描述符表当成数组,那么选择子就是数组的下标。

GDT 全局描述符表

全局描述符表GDT是保护模式下内存段的登记表,这是不同于实地址模式下的显著特征。
局部描述符表LDT是CPU厂商为了在硬件层面支持多任务的一个表,当今操作系统不使用。

在实地址模式下,寻址是按照[段基址+段内偏移]的形式进行,而在保护模式下为了保证兼容性,其也必须遵循这一规范。

在实地址模式下,访问内存时只要将段基址加载到段寄存器中,再结合偏移地址就行,段寄存器太小了,只能存储 16 位的信息,甚至连 20 位地址都要借助左移 4 位来实现。

而进入到保护模式,各个寄存器都提升到了32位,且还需对特定的内存段增加一些额外的安全属性,那么将这些属性放在内存中是最好的选择。

  • 之所以需要增加全局描述符表,并为每个段增加段描述符,是因为实模式下存在以下问题。
  • 实模式下的用户程序可以破坏存储代码的内存区域,所以要添加个内存段类型属性来阻止这种行为。
  • 实模式下的用户程序和操作系统是同一级别的,所以要添加个特权级属性来区分用户程序和系统。
  • 内存段是一片内存区域,访问内存就要提供段基址,所以要有段基址属性。
  • 为了限制程序访问内存的范围,还要对段大小进行约束,所以要有段界限属性。

段描述符: 一个段描述符只用来描述一个内存段的属性,这些描述符被依次排列在GDT中,GDT全局描述符表相当于描述符数组,数组中每个元素都是一个描述符,每个描述符大小是8字节,分为高32位与低32位,即两个四字节,GDT中最多可容纳的描述符数量是65536/8=8192个,即 GDT 中可容纳 8192 个段或门。

如下,段描述符结构示意参考,以及每个字段的大体含义。

  • 段界限

    • 第0-15位与16-19位共同构成段界限,表示段的边界,大小,范围,段界限用20个二进制位来表示。
  • 段基址

    • 第0-7是段基址的16-23位,第24-31位是段基址的高32位,加上段描述符低32位中的段基址0-15位,就构成了32位的基地址。
  • Type字段

    • 第8-11位是type字段,共占用4位,用于表示内存段或调用门的子类型。
  • S字段

    • 第12位是S字段,用于指示是否为系统段,为0是系统段,为1是数据段,通常与Type字段配合使用。
  • DPL字段

    • 第13-14位是DPL,即描述符特权级,通常是指所代表的内存段的特权级。
  • Present字段

    • 第15位是P字段,标志着指定段是否存在,如果段存在于内存中,P为1否则为0。
  • AVaiLable字段

    • 第20位是AVL字段,无用途,可随意使用此位。
  • L字段

    • 第21位是L字段,用于设置是否是64位代码段,L为1表示64位代码段,为0则为32位。
  • D/B字段

    • 第22位是DB字段,用来指示有效地址(段内偏移地址)及操作数的大小。
  • Granularity字段

    • 第23位是G字段,用来指定段界限的单位大小,若G为0表示段界限的单位是1字节,若G为1表示段界限的单位是4KB。

段选择子: 保护模式下段寄存器中存储的就是段选择子,选择子是一个索引值,用此索引值在段描述符表中索引相应的段描述符,这样,便可以在段描述符中得到了内存段的起始地址和段界限值等相关信息。

如下,段选择子结构示意参考,以及每个字段的大体含义。

由于段寄存器是16位,所以选择子也是16位,每一个选择子都会被分为3块。

  • RPL字段

    • 第0-1位,用来存储RPL(请求特权级) 通常为0、1、2、3四种特权级。
  • TI字段

    • 第2位,用来指示选择子是在GDT还是LDT中索引描述符,为0在GDT中,为1在LDT中。
  • 描述符索引

    • 第3-15位是描述符的索引值,此值主要用于在GDT中索引符合条件的段描述符。

选择子的作用主要是确定段描述符,确定描述符的目的,一是为了特权级、界限等安全考虑,最主要的还是要确定段的基地址。

由于保护模式下段寄存器中已经默认是选择子了,在寻址时直接用选择子对应的[段描述符中的段基址+段内偏移地址]就是要访问的内存地址。

A20Gate 地址回绕线

地址回绕线是为了兼容8086实模式而增加的,在实模式下地址线只有20条,寻址空间只能是1MB(0x00000 - 0xFFFFF)如果超出1MB的寻址范围,那么在默认开启地址回绕的CPU上,会自动将超出1MB的部分回绕到0地址处,继续从0地址处开始映射,地址回绕如下图。

对于只有20位地址线的8086系列CPU而言,A20地址线默认是开启的,不需要任何操作即可实现地址回绕,但80286有24 条地址线,即A0-A23,也就是说A20 地址线是开启的,如果访问0x100000-0x10FFEF之间的内存,系统将直接访问这块物理内存,并不会像8086那样回绕到0,反之如果是关闭的,则访问超出0x00000 - 0xfffff的地址范围后会自动回绕到0处。

如果A20Gate被打开,当访问到0x100000-0x10FFEF之间的地址时,CPU将真正访问这块物理内存。
如果A20Gate被禁止,当访问0x100000-0x10FFEF之间的地址时,CPU将采用8086的地址回绕。

我们想进入保护模式,就需要突破第20条地址线(A20)去访问更大的内存空间。而这一切,只有关闭了地址回绕才能实现,而关闭地址回绕,就需要打开A20Gate,打开A20Gate地址线只需要将端口0x92的第1位置1即可。

CR0 控制寄存器

想要进入保护模式还差最后一步,通过控制CR系列寄存器切换CPU模式,CR寄存器是CPU的控制窗口,即可用来查询CPU的内部状态,也可用于直接控制CPU的运行机制,切入保护模式最需要关注的是CR0寄存器中的PE位。

如下图是完整的CR0寄存器,以及重要寄存器解释:

  • 保护允许位PE (Protedted Enable)

    • 0位用于启动保护模式,如果PE置1则保护模式启动,PE置0则实模式启用。
  • 监控协处理位MP (Moniter coprocessor)

    • 1位与3位配合,当TS=1时操作码WAIT是否产生一个协处理器不能使用的出错信号。
  • 任务转换位TS (Task Switch)

    • 3位当一个任务转换完成之后,自动将它置1,随着TS=1就不能使用协处理器。
  • 模拟协处理器位EM (Emulate coprocessor)

  • 2位如果EM=1则不能使用协处理器,如果EM=0则允许使用协处理器。

  • 微处理器扩展类型位ET (Processor Extension Type)

    • 4位保存着处理器扩展类型的信息,如果ET=0使用287协处理器,ET=1使用387浮点协处理器。
  • 写保护位WP

    • 16位这一位置0就可以禁用写保护,置1则可恢复写保护。
  • 分页允许位PE (Paging Enable)

    • 31位表示芯片上的分页部件是否允许工作。

正式切入保护模式

在保护模式中,内存段都是平坦模式,也就是整个内存都在一个段内,进入保护模式之前我们需要手动在内存中构建出GDT及其内部的描述符,GDT只是一片内存区域,里面每隔8字节即是一个段描述符,GDT结构如下。

   GDT_BASE:   dd    0x00000000 
			   dd    0x00000000

   CODE_DESC:  dd    0x0000FFFF 
			   dd    DESC_CODE_HIGH4

   DATA_STACK_DESC:  dd    0x0000FFFF
					 dd    DESC_DATA_HIGH4

   VIDEO_DESC: dd    0x80000007	                  ; limit=(0xbffff-0xb8000)/4k=0x7
			   dd    DESC_VIDEO_HIGH4             ; 此时dpl已改为0

   GDT_SIZE    equ   $ - GDT_BASE
   GDT_LIMIT   equ   GDT_SIZE -	1 

   times 60 dq 0					          ; 预留60个描述符的slot

   SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0         ; 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
   SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	     ; 同上
   SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	     ; 同上 

   ;以下是定义gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址
   gdt_ptr  dw  GDT_LIMIT 
	        dd  GDT_BASE

GDT中的每个描述符简单介绍.

  • GDT_BASE

    • 构建GDT的起始地址(此位是0位,不可用,所以直接填充为全0即可)
  • CODE_DESC/DATA_STACK_DESC/VIDEO_DESC

    • 代码段描述符,数据段和栈段描述符,显存描述符
  • GDT_SIZE/GDT_LIMIT

    • 计算出GDT大小,GDT_LIMIT得到段界限,为后续加载GDT做准备.
  • SELECTOR_CODE/SELECTOR_DATA/SELECTOR_VIDEO

    • 分别构建代码段,数据段,显存段的选择子.
  • gdt_ptr

    • 定义GDT指针,此指针是lgdt加载GDT到gdtr寄存器用的.

接下来就是进入保护模式,进入保护模式需要三步,1.打开A20,2.加载GDT,3.CR0第0位置1,代码如下。

   in al,0x92
   or al,0000_0010B
   out 0x92,al

   lgdt [gdt_ptr]

   mov eax, cr0
   or eax, 0x00000001
   mov cr0, eax

最后还需要使用jmp SELECTOR_CODE:p_mode_start指令来实现刷新流水线。

   jmp  SELECTOR_CODE:p_mode_start

[bits 32]
p_mode_start:
   mov ax, SELECTOR_DATA
   jmp $

流水线是CPU为了提高执行效率而发展起来的加速技术,通常执行指令需要经过取指令,译码,执行指令,等操作,而运用流水线技术则将当前指令及其后面的几条指令同时放在流水线中重叠执行。

由于实模式是16位的,而保护模式是32位,在切换时必须要清空当前流水线上面所有的16位指令集,以及错误的段属性,只有这样才能保证后面的32位指令能够被正确的执行。

此时我们既要改变代码段描述符缓冲寄存器的值,又要清空以前的流水线,使用JMP指令则可以达到这两种效果,JMP指令在执行无条件跳转时会自动的将所有段寄存器初始化并清空当前流水线上的指令集。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/343167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

彻底理解 cookie、session、token (一)

发展史 1、很久很久以前&#xff0c;Web 基本上就是文档的浏览而已&#xff0c; 既然是浏览&#xff0c;作为服务器&#xff0c; 不需要记录谁在某一段时间里都浏览了什么文档&#xff0c;每次请求都是一个新的HTTP协议&#xff0c; 就是请求加响应&#xff0c; 尤其是我不用记…

阿里巴巴在开源压测工具 JMeter 上的实践和优化

Apache JMeter [1] 是 Apach 旗下的开源压测工具&#xff0c;创建于 1999 年初&#xff0c;迄今已有超过 20 年历史。JMeter 功能丰富&#xff0c;社区&#xff08;用户群体&#xff09;庞大&#xff0c;是主流开源压测工具之一。 性能测试通常集中在新系统上线或大型活动前&…

l1和l2接口如何进行编写?一定要掌握这几个元素

在这个大数据时代&#xff0c;很多地方都需要用到l1和l2接口&#xff0c;l1和l2接口在应用程序与数据库之间起着桥梁的作用&#xff0c;是实现数据的整合与共享的重要帮手。 l1和l2接口适用于各行各业&#xff0c;应用场景的不断拓展&#xff0c;l1和l2接口的发展也兴起&#…

浏览器广告拦截插件| 浏览器搜索广告横飞怎么办

文章目录浏览器广告拦截插件| 浏览器搜索广告横飞怎么办一、效果二、安装浏览器广告拦截插件| 浏览器搜索广告横飞怎么办 浏览器广告横飞怎么办&#xff1f;今天教你一招解决&#xff01;很多小伙伴说自己用的浏览器总是有广告。 今天咱们就针对这个问题分享一个浏览器插件&a…

【面试题】JavaScript中递归的理解

大厂面试题分享 面试题库后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库递归 RecursionTo iterate is human, to recurse, divine. 理解迭代&#xff0c;神理解递归。本文会以 JavaScript为主、有部分 Rust 举例说明。…

【python--networkx】函数说明+代码讲解

【Python–NetworkX】函数说明代码讲解 文章目录【Python--NetworkX】函数说明代码讲解1. 介绍1.1 前言1.2 图的类型&#xff08;Graph Types&#xff09;1.3 常用方法2. 代码示例1. 介绍 1.1 前言 NetworkX是复杂网络研究领域中的常用Python包。 1.2 图的类型&#xff08;G…

Linux高级命令之文件权限命令

文件权限命令学习目标能够使用chmod命令完成文件权限的修改1. chmod命令的介绍命令说明chmod修改文件权限chmod修改文件权限有两种方式:字母法数字法2. chmod 字母法的使用角色说明:角色说明uuser, 表示该文件的所有者ggroup, 表示用户组oother, 表示其他用户aall, 表示所有用户…

[carla]关于odometry坐标中的角度坐标系 以及 到地图的映射问题

1.获取车辆的Odometry原始信息 在carla中&#xff0c;通过订阅/carla/ego_vecle/odometry 可以查看车辆的全局位置信息,例如&#xff1a; > header: seq: 118872stamp: secs: 5946nsecs: 5720187frame_id: "map" child_frame_id: "ego_vehicle" pos…

替换子串得到平衡字符串[map计数+滑动窗口]

滑动窗口前言一、替换子串得到平衡字符串二、map计数滑动窗口总结参考文献前言 对于子串问题&#xff0c;确定左边界和有边界&#xff0c;就能确定一个子串&#xff0c;暴力取子串&#xff0c;时间复杂度O(n2)。有时挖掘内在规律的限定&#xff0c;或者问题所限定&#xff0c;…

Vue笔记(1)——数据代理与绑定

一、初始Vue 1.想让Vue工作&#xff0c;就必须创建一个Vue实例&#xff0c;且要传入一个配置对象&#xff1b; 2.root容器里的代码依然符合html规范&#xff0c;只不过混入了一些特殊的Vue语法&#xff1b; 3.root容器里的代码被称为【Vue模板】&#xff1b; 4.Vue实例和容器是…

教育行业需要什么样的客服系统?

某教育公司拥有素质教育、成人教育、智慧教育等多个业务板块&#xff0c;日常通过电商、线上媒体、线上线下授课等方式进行业务开展和品牌宣传&#xff0c;取得了非常不错的成绩&#xff0c;受到了很多人的好评反馈。 对于这样一个教育公司&#xff0c;客户来源广泛&#xff0…

SpringBoot 使用 Spark

文章目录读取 txt 文件读取 csv 文件读取 MySQL 数据库表读取 Json 文件中文输出乱码前提&#xff1a; 可以参考文章 SpringBoot 接入 Spark SpringBoot 已经接入 Spark已配置 JavaSparkContext已配置 SparkSession Resource private SparkSession sparkSession;Resource pr…

机器学习算法:随机森林

在经典机器学习中&#xff0c;随机森林一直是一种灵丹妙药类型的模型。 该模型很棒有几个原因&#xff1a; 与许多其他算法相比&#xff0c;需要较少的数据预处理&#xff0c;因此易于设置充当分类或回归模型不太容易过度拟合可以轻松计算特征重要性在本文[1]中&#xff0c;我想…

【docker知识】从容器中如何访问到宿主机

一、说明 使用 Docker 能实现服务的容器化&#xff0c;并使用容器间网络在它们之间进行通信。有时您可能需要一个容器来与宿主机上非容器化的服务通信。以下是如何从 Docker 容器中访问本地主机或 127.0.0.1的具体方法。 二、方法1&#xff1a;简单的选择 适用于 Windows 和 Ma…

2023/2/13总结

今天主要学习了哈夫曼树。 哈夫曼树 哈夫曼树是二叉树的一种&#xff0c;它是一种WPL最优二叉树。 叶子结点&#xff08;也称叶节点&#xff09;&#xff1a;指的是自己下面不再连接有节点的节点&#xff08;即末端&#xff09;&#xff0c;称为叶子节点&#xff08;又称为终…

PDF内容提取器:ByteScout PDF Extractor SDK Crack

ByteScout PDF Extractor SDK – 用于 PDF 到 JSON、PDF 到 Excel、CSV、XML、从 .NET 和 ASP.NET 从 PDF 中提取文本的 PDF 提取器库 ByteScout PDF Extractor SDK – 用于 PDF 到 JSON、PDF 到 Excel、CSV、XML、从 .NET 和 ASP.NET 从 PDF 中提取文本的 PDF 提取器库​ ​ ​…

test4

网络层故障分析 一、 路由器故障 a.主要用途简述 b.故障 & 故障原因 & 解决方案 1&#xff09;路由器的部分功能无法实现 故障故障原因解决方案路由器配置完全正确&#xff0c;但是有些功能却不能实现。路由器的软件系统出现问题升级软件系统 2&#xff09;网络频繁…

ABC 289 G - Shopping in AtCoder store 数学推导+凸包

大意&#xff1a; n个顾客&#xff0c;每个人有一个购买的欲望bi,m件物品&#xff0c;每一件物品有一个价值ci,每一个顾客会买商品当且仅当bici>定价. 现在要求对每一个商品定价&#xff0c;求出它的最大销售值&#xff08;数量*定价&#xff09; n,m<2e5 思路&#x…

工程监测多通道振弦模拟信号采集仪VTN常规操作

工程监测多通道振弦模拟信号采集仪VTN常规操作 一、开关机 1、开机 VTN4XX 有四个开机途径&#xff0c;手动开机、自动定时开机和上电开机、信号触发开机。 上电开机&#xff1a;当“工作模式拨码开关” 第 4 位为 ON 时&#xff0c;直接连接外部电源即可开机。 自动开机&…

JAVA文件上传多方式

1.文件上传接收文件接口 通过post接口&#xff0c;上传文件 PostMapping(value "/uploadFile")ApiOperation(value "文件上传", notes "文件上传")public Result uploadFile(RequestParam (name "file") MultipartFile file) thr…