保护模式_2

 

[原创] 操作系统DIY - 进入保护模式

 

预备知识:

  1. 熟悉 i386 CPU寄存器,了解实模式及保护模式模式;
  2. 了解A20门
  3. 文本模式下直接显存操作

涉及工具:
  NASM,一个文本编辑器(我用的是ConText + NASM语法高亮),QEMU/VMWARE虚拟机

前言:

  近期确实很忙,论坛里有一位朋友写的代码进入不了保护模式;我最初也是对保护模式相当敬畏,因为32位比16位要“复杂”的多;当时一直不敢下手,偶尔的尝试有如蜻蜓点水,但最终以失败告终。在学校的图书馆里几乎找不到386保护模式汇编的资料,更不用说CPU相关的书了;不知道看了多少可怜的教材后,终于凑出了一点起眼的代码,不过还是失败了。我最终是通过一个Bona Fide 的实例教程解决了问题:实例程序在我的开发过程中起到了很重要的作用。

  由于时间原因,这篇文章将主要以代码来说明,因为我的确没太多的时间再去介绍“实模式”,“保护模式”,GDT,IDT,A20等等等等相关的名词、概念及规范;这些东西在我的网站里已经收罗了:http://www.xemean.net/resource/ ,其中有中文也有英文的,有的甚至是图文并茂,网络上也有不少的例子,但在这里强烈建议的一本电子教程是:《80X86保护模式教程》 ,这本书详细地介绍了如何对 80X86 CPU进行编程,包括进入保护模式,保护模式的中断,多任务等等。另外,值得一提的是:由杨季文等人编著的,清华大学出版社出版的《80x86汇编语言程序设计教程》也是一本不错的书。

  此文适合于有一定基础,但又不能实现保护模式切换的朋友。

  本文将以我上次写的“启动你的计算机”的代码为基础,演示进入保护模式,但程序还是在引导区内工作。

  姑且不理会保护模式下“复杂”的内存管理,多任务,中断,实际上进入保护只要:
mov  eax,cr0  ; 控制寄存器CR0 -> EAX
or  eax,1        ; 最低位置1,即PE位
mov  cr0,eax  ; 写回CR0

I386兼容CPU使用CR0这个寄存器来“控制”或者说决定CPU的工作状态,命名为“控制寄存器中”,其中CR0的最低位叫PE位,中文翻译应该是“保护模式允许”位,如果对CR0的PE位置1,则CPU就工作在保护模式下。不幸的是,我们并不能直接对CR0进行操作,但是却可以通过通用寄存器对其修改,上面便是开启保护模式大门的实例。当然,只有上面的代码你可能永远也进不了保护模式。

  保护模式与实模式有一个区别在于,段寄存器不再保存实际的内存地址,CPU已经有32位寻址的能力,也就是能访问4G的内存,似乎用32位的EIP就可以访问4G了,但Intel并没有想得那么简单,段寄存器在内存管理方面还有很大的作用。另外,之所以叫保护模式,是因为CPU还能不同应用层的代码进行保护,这在16位实模式是做不到的。因此引入了GDT,及描述符的概念。(这就得请各位看官参看一些资料了)
  CPU中有一个高速的寄存器用来保存GDT表在内存中的位置以及GDT表的大小:GDT的大小用16位来表示,GDT的物理地址用32位来表示(以保证GDT能在4G内存的任意位置),因此GDT高速寄存器(GDTR)占48位,已经不能用一个32位的寄存器来表示了,因此要在内存中表示出GDTR内容,书上说这叫“伪描述符”,GDTR由下面的指令装载:
lgdt [__GDTR]

其中__GDTR是GDT伪描述符的地址,一口气,我们作如下数据定义:

ALIGN 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GDT 伪描述符
; 参考保护模式相关文档以获得关于GDT伪描述
; 符更详细的资料
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
__GDTR:
       dw GDT_END - GDT-1      ; GDT表的长度,由编译器计算
       dd GDT                           ; GDT物理地址,由编译器计算
;<- END OF __GDTR
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GDT entry
; 参考保护模式相关文档以获得关于GDT的更
; 详细的资料
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ALIGN 8 ; 对齐,以保护CPU访问GDT的速度
GDT:
; 第一个GDT作为保留项,以0填充
; reserved GDT
    dd 0
    dd 0

osCodeSel equ   $-GDT ; 内核用的代码段选择子
oscode:
          dw    0xffff
          dw    0
          db    0
          db    10011010b ; 0x9A ,可读/可执行 代码段
          db    11011111b ;
          db    0

osDataSel equ   $-GDT ; 内核用的数据段选择子
osdata:
          dw    0xffff
          dw    0
          db    0
          db    10010010b ; 0x92 ,可读/写 数据段
          db    11011111b ;
          db    0
GDT_END:  ;<- END OF GDT

完成如上的定义之后,就可以着手进入保护模式了,过程大致为:

  1. 禁止所有中断
  2. 打开A20门
  3. 加载GDTR
  4. 置PE位
  5. 初始化保护模式下的寄存器
  6. 一个远跳转到32位代码以清除当前(实模式)的CS及EIP

如果跳转成功,则CPU就可以工作在保护模式下。保护模式并不像实模式下有很多BIOS中断可用,这就意味着我们必须自己写键盘、显卡等等驱动,计算机的几乎所有资源都由内核来管理,当然,也由你来实现各种设备的驱动。

为了显示我们的程序已经成功地工作在保护模式下,我们必须在32位模式时在屏幕上写点什么东西,直接写显存吧!演示程序对显存进行操作,结果是屏幕的第三行第1列显示了一个洋红色的字母P。

源码编译:nasmw -f bin boot.asm -o boot.bin

用WinImage写入软盘镜像,然后用Qemu或VMware启动。注:不知道什么原因,这段代码并不能在Bochs下工作。

拍照以示留念:

Image1.jpg

程序源码如下:

 

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; boot.asm
; A demo to show how bootsect works
; Last modified:2005-4-3 10:57:45 
; Copyright (c) 2005,E-mean X.
;
; This program is released under GPL,See document for details
; You can use this code anywhere you want in condition keep autor's info
; original
;
; Author: E-mean X.
; Contact: xemean@sina.com
; Website: http://www.xemean.net/
; April,02,2005
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;**************************************************************************
; 16位代码
bits 16          ; 伪指令,告诉编译器这是 16位代码
org 0x7C00       ; 伪指令,告诉编译器这段代码由0x0:0x7C00开始
;===========================================================================
; 程序执行的第一条指令必须是跳转(如果你想使用FAT12这类文件系统的磁盘)
; 必须占用3字节
;===========================================================================
jmp  SHORT main  ; 2 bits,跳转到主程序执行
nop             ; 1 bit
;===========================================================================
; FAT12 文件系统头,从NYAOS 借过来的,可以参考相关的文档以获得更多细节
; 这个块会让 Winimage 认出编译后的二进制文件为有效的引导文件
; 如果不使用这个块,Winimage将不会将其作为引导程序处理
; 但我们可以借助其它方法和工具处理,比如DEBUG
;===========================================================================
bsOEM        db  "ExOS0.02"                ; OEM String,任意你喜欢的8字节ASCII码
bsSectSize   dw 512                       ; Bytes per sector
bsClustSize  db 1                         ; Sectors per cluster
bsRessect    dw 1                         ; # of reserved sectors
bsFatCnt     db 2                         ; # of fat copies
bsRootSize   dw 224                       ; size of root directory
bsTotalSect  dw 2880                      ; total # of sectors if < 32 meg
bsMedia      db 0xF0                      ; Media Descriptor
bsFatSize    dw 9                         ; Size of each FAT
bsTrackSect  dw 18                        ; Sectors per track
bsHeadCnt    dw 2                         ; number of read-write heads
bsHidenSect  dd 0                         ; number of hidden sectors
bsHugeSect   dd 0                         ; if bsTotalSect is 0 this value is
                                         ; the number of sectors
bsBootDrv    db 0                         ; holds drive that the bs came from
bsReserv     db 0                         ; not used for anything
bsBootSign   db 29h                       ; boot signature 29h
bsVolID      dd 0                         ; Disk volume ID also used for temp
                                         ; sector # / # sectors to load
bsVoLabel    db  "NO NAME    "             ; Volume Label
bsFSType     db  "FAT12   "                ; File System type <- FAT 12文件系统
;===========================================================================
; Main start here
;===========================================================================
main:
      cli   ; 关闭可屏蔽中断,以备我们接下来初始化寄存器的工作
      mov   ax, cs  ; 将代码段传给ax,实模式下,代码段与数据段没什么分别
      mov   ds, ax  ; 数据段寄存器,实际上都是0
      mov   es, ax  ; 附加段
      mov   ax, ss  ; 堆栈段
      mov   sp,0x7C00-1    ; 堆栈指针,指向0x7BFF
      sti   ; 基本工作已经完成,开放中断

      ;
      mov   ax,0x0003
      int  0x10

      mov   si,msgHello  ; 使 si指向 "Hello World!"字符串
      call printStr     ; 调用显示子程序
     
      mov   si,fixLine   ; 回车换行
      call printStr
     
      mov   si,msgMore   ; 显示swithing to protect mode
      call printStr
     
      ; 打开A20门,几乎所有想进入保护模式的程序都通过A20门来实现
      ; 当然,也有其它办法,比如 int 0x15,不过并不推荐,因为可能不兼容
      ; 参考a20门以获得更多细节

       cli
       call kbdwait
       mov  al,0xD1
       out 0x64, al
       call kbdwait
       mov  al,0xDF
       out 0x60, al
       call kbdwait

       lgdt [__GDTR]            ; 加载伪描述符到GDT高速寄存器
       mov   eax, cr0             ; 将控制寄存器CR0的值放到eax中
       or    eax,1               ; 置PE位
       mov   cr0, eax             ; 写回CR0,这时候PE已经被置位了
                               ; 进入保护模式的工作完成了一大半:
                               ; 另一小半是:我们当前的寄存器还在
                               ; 16位模式下工作

       mov   eax,osDataSel       ; 初始化所有段寄存器
       mov   ds, ax
       mov   es, ax
       mov   ss, ax
       mov   fs, ax
       mov   gs, ax
       mov   esp,0x7C00-1        ; 新的堆栈
       jmp  osCodeSel:code32    ; 一个远跳转以"冲"掉当前实模式的代码段CS及指令指针EIP
                               ; 以使其使用保护模式的CS(注意:是选择子),及EIP

;------- END OF MAIN ----------------

;===========================================================================
; printStr
; sub function for print a string to screen by INT 10H
; 入口:es:si = 指向目标字符串
; 返回:无
;===========================================================================
printStr:
      push  si   ; 保护寄存器
      push  ax
      push  bx

           cld     ; 清除进位标志位,这个标志位会影响 si 的递增方向
           mov   ah,0x0E     ; int 0x10 子功能号,显示字符,参看相关资料以获得细节
           mov   bx,0x0007   ; 页号0,字符前景色 7,浅灰色,试着改变这个数值
                           ; 会给你的文字增添色彩
     .nextChar:
           lodsb            ; [si] -> al,取一个字节码
           or   al, al        ; 如果取得的字节是0,则表示字符串结束
           jz  .OK          ; 退出
           int 0x10         ; 调用BIOS int 10h 中断
           jmp .nextChar    ; 继续下一个字符,直到遇到0
     .OK:
      pop   bx  ; 恢复寄存器
      pop   ax
      pop   si
      ret      ; 返回调用程序
;------- END OF printStr --------------
;=========================================================================
; 等待键盘缓冲区清空
kbdw0:
       jmp  short $+2
       in  al,0x60
kbdwait:
       jmp  short $+2
       in  al,0x64
       test  al,1
       jnz kbdw0
       test  al,2
       jnz kbdwait
       ret
;------ END OF kbdwait -----------------
; data area

msgHello  db 'Hello World!',0           ; 以物理 0结束
msgMore   db 'Swithing to protect mode ...',0
fixLine   db 13,10,0    ; 回车,换行的ASCII码

ALIGN 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GDT 伪描述符
; 参考保护模式相关文档以获得关于GDT伪描述
; 符更详细的资料
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
__GDTR:
        dw GDT_END - GDT-1                ; GDT表长度
        dd GDT                            ; GDT物理地址
;<- END OF __GDTR
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GDT entry
; 参考保护模式相关文档以获得关于GDT的更
; 详细的资料
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ALIGN 8  ; 对齐
GDT:
; 第一个GDT作为保留项,以0填充
; reserved GDT
     dd 0
     dd 0

osCodeSel  equ   $-GDT  ; 内核用的代码段选择子
oscode:
           dw    0xffff
           dw    0
           db    0
           db    10011010b  ; 0x9A ,可读/可执行 代码段
           db    11011111b  ;
           db    0

osDataSel  equ   $-GDT  ; 内核用的数据段选择子
osdata:
           dw    0xffff
           dw    0
           db    0
           db    10010010b  ; 0x92 ,可读/写 数据段
           db    11011111b  ;
           db    0
GDT_END:   ;<- END OF GDT
;**************************************************************************
; 32位代码
bits 32    ; 告诉编译器这段代码在32位模式下工作
code32:
; take a break
      nop  ; 我不知道这三个NOP会不会起作用
      nop
      nop
      ; 接下来让我们直接向显存写数据 
      mov   [0xB8000+80*2*2], BYTE 'P'   ; 在屏幕的第三行第一列写字母'P'
      mov   [0xB8000+80*2*2+1], BYTE 13  ; 字母P的颜色为洋红色
      jmp   $
     

bits 16
; 引导程序必须为512字节,不用的地方以0填充
  times 510-($-$$)  db 0             ; $表示程序当前位置,$$表示程序开始位置,由编译器自动计算

BOOT_SIGN      DW 0xAA55             ; 最后两个字节为引导标志55AA
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值