之前已经做了一些理论上的铺垫,这次我们就可以看代码了。

一、代码清单

         ;代码清单11-1;文件名:c11_mbr.asm;文件说明:硬盘主引导扇区代码 ;创建日期:2011-5-16 19:54;设置堆栈段和栈指针 mov ax,cs      mov ss,axmov sp,0x7c00;计算GDT所在的逻辑段地址 mov ax,[cs:gdt_base+0x7c00]        ;低16位 mov dx,[cs:gdt_base+0x7c00+0x02]   ;高16位 mov bx,16        div bx            mov ds,ax                          ;令DS指向该段以进行操作mov bx,dx                          ;段内起始偏移地址 ;创建0#描述符,它是空描述符,这是处理器的要求mov dword [bx+0x00],0x00mov dword [bx+0x04],0x00  ;创建#1描述符,保护模式下的代码段描述符mov dword [bx+0x08],0x7c0001ff     mov dword [bx+0x0c],0x00409800     ;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区) mov dword [bx+0x10],0x8000ffff     mov dword [bx+0x14],0x0040920b     ;创建#3描述符,保护模式下的堆栈段描述符mov dword [bx+0x18],0x00007a00mov dword [bx+0x1c],0x00409600;初始化描述符表寄存器GDTRmov word [cs: gdt_size+0x7c00],31  ;描述符表的界限(总字节数减一)   lgdt [cs: gdt_size+0x7c00]in al,0x92                         ;南桥芯片内的端口 or al,0000_0010Bout 0x92,al                        ;打开A20cli                                ;保护模式下中断机制尚未建立,应 ;禁止中断 mov eax,cr0or eax,1mov cr0,eax                        ;设置PE位;以下进入保护模式... ...jmp dword 0x0008:flush             ;16位的描述符选择子:32位偏移;清流水线并串行化处理器 [bits 32] flush:mov cx,00000000000_10_000B         ;加载数据段选择子(0x10)mov ds,cx;以下在屏幕上显示"Protect mode OK." mov byte [0x00],'P'  mov byte [0x02],'r'mov byte [0x04],'o'mov byte [0x06],'t'mov byte [0x08],'e'mov byte [0x0a],'c'mov byte [0x0c],'t'mov byte [0x0e],' 'mov byte [0x10],'m'mov byte [0x12],'o'mov byte [0x14],'d'mov byte [0x16],'e'mov byte [0x18],' 'mov byte [0x1a],'O'mov byte [0x1c],'K';以下用简单的示例来帮助阐述32位保护模式下的堆栈操作 mov cx,00000000000_11_000B         ;加载堆栈段选择子mov ss,cxmov esp,0x7c00mov ebp,esp                        ;保存堆栈指针 push byte '.'                      ;压入立即数(字节)sub ebp,4cmp ebp,esp                        ;判断压入立即数时,ESP是否减4 jnz ghalt                          pop eaxmov [0x1e],al                      ;显示句点 ghalt:     hlt                                ;已经禁止中断,将不会被唤醒 ;-------------------------------------------------------------------------------
     gdt_size         dw 0gdt_base         dd 0x00007e00     ;GDT的物理地址
                             times 510-($-$$) db 0db 0x55,0xaa

上面就是配书源码。我们一点一点看。

二、源码分析

(一)设置堆栈和栈指针

;设置堆栈段和栈指针 mov ax,cs      mov ss,axmov sp,0x7c00

这个没有什么好说的,就是初始化栈。这三行执行后,SS=0; SP=0x7c00;

需要注意的是,这样设置后,栈的区域从0x0000_7c00向下扩展(不含0x0000_7c00这个字节),该区域包含了很多BIOS数据,包括实模式下的中断向量表,所以一定要小心。

(二)安装段描述符

;计算GDT所在的逻辑段地址 mov ax,[cs:gdt_base+0x7c00]        ;低16位 mov dx,[cs:gdt_base+0x7c00+0x02]   ;高16位 mov bx,16        div bx            mov ds,ax                          ;令DS指向该段以进行操作mov bx,dx                          ;段内起始偏移地址

怎么理解这段代码呢?

首先,在代码清单的95、96行,有

         gdt_size         dw 0gdt_base         dd 0x00007e00     ;GDT的物理地址

作者在这里声明了标号gdt_base,还初始化了一个双字——0x0000_7e00; 作者的意图是从这个地方开始建立全局描述符表GDT。我们的程序就是一个引导扇区,占用了512(=0x200)字节。程序加载的物理地址是0x7c00, 0x7c00+0x200 = 0x7e00. 可见,在物理地址的安排上,引导程序后面紧跟着就是GDT。

目前我们还是处在实模式下,所以要建立GDT,必须将GDT的线性地址(物理地址)转换成实模式下使用的“段地址:偏移地址”的形式。

mov ax,[cs:gdt_base+0x7c00] ;

这句使了段超越前缀“cs”,表明访问代码段中的数据;因为CS=0,所以就把物理地址(0x7c00+gdt_base)处的0x7e00传送给了ax; 同样地,将0x0000传送给dx; 为了把线性地址转换成逻辑地址,我们用DX:AX除以16,得到的商(AX)就是段地址,余数(DX)就是偏移地址。

mov bx,16       
        div bx           
        mov ds,ax                          ;令DS指向该段以进行操作
        mov bx,dx                          ;段内起始偏移地址
这几行执行之后,GDT的逻辑地址就是 DS:BX.

;创建0#描述符,它是空描述符,这是处理器的要求mov dword [bx+0x00],0x00mov dword [bx+0x04],0x00

处理器规定,GDT中的第一个描述符必须是空描述符。这是什么原因呢?因为很多时候,寄存器和内存单元的初始值都会为0,再加上程序设计有问题,就会在无意中用全0的索引来选择描述符,这当然是不好的。因此,处理器要求将第一个描述符定义成空描述符。所以,上面两行代码定义了一个空描述符。

;创建#1描述符,保护模式下的代码段描述符mov dword [bx+0x08],0x7c0001ff     mov dword [bx+0x0c],0x00409800

这两行用来创建第二个描述符。之前的博文我们已经掌握了数据段和代码段描述符的格式,所以对这个描述符就不难理解了。

还记得我上一篇博文中写了一个小程序吗?http://blog.csdn.net/longintchar/article/details/50507218

赶紧用它来分析一下吧:

线性基地址:0x0000_7c00

段界限为0x001FF,因为G=0,所以该段的长度是512(2的9次方)字节;

特权级:0

其他字段就不逐个说明了,相信你一定能懂。很明显,这个描述符定义的段,就是主引导程序所在的区域。

接着看代码。

;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区) mov dword [bx+0x10],0x8000ffff     mov dword [bx+0x14],0x0040920b

程序分析的结果是:

seg_base = 0XB8000
seg_limit = 0XFFFF
S = 1
DPL = 0
G = 0
D/B = 1
TYPE = 2
数据段: 可读可写

看来这个段是指向显存的。

;创建#3描述符,保护模式下的堆栈段描述符mov dword [bx+0x18],0x00007a00mov dword [bx+0x1c],0x00409600

这是创建栈段的描述符。程序分析的结果是:

-----------------------
seg_base = 0
seg_limit = 0X7A00
S = 1
DPL = 0
G = 0
D/B = 1
TYPE = 6
数据段: 向下扩展,可读可写
------------------------

正如作者所说:段界限的值0x7a00加上1(0x7a01),就是ESP寄存器所允许的最小值。当执行隐式的栈操作(如PUSH、CALL)时,处理器会检查ESP的值,一旦发现它小于0x7a01,就会引发异常中断。如果你还不理解,那么可以把书翻到215页。作者说在栈操作时,必须符合以下规则:

实际使用的段界限+1 <= (ESP的内容减操作数的长度) <= 0xFFFF_FFFF

就拿这个例子来说,因为G=0,所以段界限就是0x7a00. 假设现在ESP的内容是0x7a04,此时执行下面的指令:

push edx

因为压入的是双字,所以处理器会先将ESP的值减去4,于是ESP=0x7a00. 因为0x7a00小于0x7a01,因此会引发异常中断。

(三)LGDT指令

好了,现在描述符已经安装完毕,接下来的工作是加载描述符表的线性基地址和界限到GDTR寄存器。相关的指令是lgdt. 该指令的格式为:

lgdt m48

也就是说,该指令的操作数内存操作数。注意,该指令在实模式和保护模式下都可以执行,也不影响任何标志位。

这个内存操作数指向一个6字节的内存区域,要求低16位是GDT的界限值(表的总字节数减去1),高32位是GDT的线性基地址。

         gdt_size         dw 0gdt_base         dd 0x00007e00     ;GDT的物理地址

还记得吗,这是代码中事先定义了6字节的空间。前两个字节就是为了保存GDT的界限值。

;初始化描述符表寄存器GDTRmov word [cs: gdt_size+0x7c00],31  ;描述符表的界限(总字节数减一)   lgdt [cs: gdt_size+0x7c00]

第一句写入界限值,第二句把6字节加载到GDTR寄存器。

注意,到目前为止,我们依然在实模式下。

(四)关于A20

1.A20 GATE 起源[1]

在8086/8088中,只有20根地址线,所以可以访问的地址是2^20=1M。但由于8086/8088是16位地址模式,能够表示的地址范围是0-64K,所以为了访问1M内存,Intel采取了分段的模式。

即:物理地址=16位段地址*16 + 16位偏移

但这种方式引起了新的问题,通过上述分段模式,能够表示的最大内存为:FFFFh:FFFFh=FFFF0h+FFFFh=10FFEFh

但8086/8088只有20位地址线,所以当访问100000h~10FFEFh之间的内存时,系统并不认为访问越界而产生异常,而是自动从重新0开始计算,也就是说系统计算实际地址的时候是按照对1M求模的方式进行的,这种技术被称为wrap-around(回绕)。

到了80286,系统的地址总线发展为24根,这样能够访问的内存可以达到2^24=16M。为了兼容,Intel在设计80286时提出的目标是:在实模式下,系统所表现的行为应该和8086/8088所表现的完全一样。但最终,80286芯片却存在一个BUG:如果程序员访问100000H~10FFEFH之间的内存,系统将实际访问这块内存,而不是象过去一样重新从0开始。

为了解决上述问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址线(从0开始数是第20根),被称为A20Gate;如果A20 Gate打开,则当程序员给出100000H~10FFEFH之间的地址的时候,系统将真正访问这块内存区域;如果A20Gate被禁止,则当程序员给出100000H~10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式。绝大多数IBM PC兼容机默认的A20Gate是被禁止的。由于在当时没有更好的方法来解决这个问题,所以IBM使用了键盘控制器来操作A20 Gate,但是这种操作太麻烦了,要使用一大堆指令。

2.Alt_A20_GATE

Alt_A20_GATE ,又称Fast A20. 通过端口0x92的bit1来打开A20,具体方法是:先从端口读出原数据,接着将bit1置1,然后再写入该端口,这样就打开了A20.

正如代码所示

in al,0x92                         ;南桥芯片内的端口 or al,0000_0010Bout 0x92,al                        ;打开A20

一次学太多会不会觉得累呢?我们就说到这里,下次继续…

【参考资料】

[1] 如烟海的专栏. http://blog.csdn.net/ruyanhai/article/details/7181842

进入保护模式(一)——《x86汇编语言:从实模式到保护模式》读书笔记12相关推荐

  1. x86汇编语言从实模式百度云_Intel x86 CPU 32位保护模式杂谈之任务切换 上

    目录: 什么是任务 任务由什么组成 任务门描述符是什么东东?有了TSS描述符为什么要有任务门描述符? 参考文献 什么是任务 任务(task)是处理器可以分配.执行.挂起的工作单位,笔者认为和我们操作系 ...

  2. 【OS修炼指南目录】----《X86汇编语言-从实模式到保护模式》读书笔记目录表

    学习交流加(可免费帮忙下载CSDN资源): 个人微信: liu1126137994 学习交流资源分享qq群1(已满): 962535112 学习交流资源分享qq群2: 780902027 本文是将个人 ...

  3. x86汇编语言从实模式百度云_x86汇编语言:从实模式到保护模式

    x86汇编语言:从实模式到保护模式2013年1月由电子工业出版社出版发行,总共6000行的源代码,全方位地向读者展现汇编语言程序设计之美.尽管汇编语言也是一种计算机语言,但却是与众不同的,与它的同类们 ...

  4. X86汇编语言从实模式到保护模式13:保护模式程序的动态加载和执行

    目录 1. 引入保护模式对程序加载与执行的影响 1.1 对应用程序的影响 1.2 对操作系统的影响 1.3 本章程序总体结构 2. MBR加载内核过程分析 2.1 内核头部段分析 2.1.1 内核总长 ...

  5. ASM:《X86汇编语言-从实模式到保护模式》第10章:32位x86处理器的编程架构

    ★PART1:32位的x86处理器执行方式和架构 1. 寄存器的拓展(IA-32) 从80386开始,处理器内的寄存器从16位拓展到32位,命名其实就是在前面加上e(Extend)就好了,8个通用寄存 ...

  6. X86汇编语言从实模式到保护模式18:中断和异常的处理与抢占式多任务

    目录 1. 中断和异常概述 1.1 中断的分类 1.1.1 中断(Interrupt) 1.1.2 异常(Exception) 1.2 异常的分类 1.2.1 按异常的来源分类 1.2.2 按异常的性 ...

  7. X86汇编语言从实模式到保护模式16:特权级和特权级保护

    目录 1. 特权级保护机制 1.1 基础段保护机制的不足 1.2 特权级划分 1.3 特权级的表示 1.3.1 当前特权级CPL 1.3.2 描述符特权级DPL 1.3.3 请求特权级RPL 1.4 ...

  8. X86汇编语言从实模式到保护模式15:任务和任务的创建

    目录 1. 任务的概念与组成 1.1 任务的概念 1.2 多任务系统 1.3 任务的组成 1.3.1 局部描述符表LDT 1.3.2 任务状态段TSS 2. 任务控制块和TCB链表 2.1 为何需要任 ...

  9. X86汇编语言从实模式到保护模式10:进入保护模式

    目录 1. 全局描述符表GDT 1.1 段描述符与描述符表 1.2 全局描述符表的定义 1.3 全局描述符表寄存器GDTR 1.3.1 GDTR用途 1.3.2 GDTR构成 1.3.3 lgdt指令 ...

  10. X86汇编语言从实模式到保护模式09:32位x86处理器编程架构

    目录 1. IA-32架构的基本执行环境 1.1 寄存器的扩展 1.1.1 通用寄存器的扩展 1.1.2 IP寄存器的扩展 1.1.3 FLAGS寄存器的扩展 1.1.4 段寄存器的扩展 1.2 32 ...

最新文章

  1. ArcGIS Server 内存占用相关
  2. java语言仅支持单重继承_java语言程序设计基础篇习题_复习题_第十一章
  3. 2021北京智源大会圆满闭幕,现场8千人,参会7万人,发布「悟道2.0」全球最大万亿级模型...
  4. 在项目中使用react
  5. 动态规划--编辑距离问题
  6. 校园二手交易平台的开发和利用
  7. python基础学习中常见问题
  8. Oracle中一把梭获取对象DDL创建语句
  9. AS3学习笔记(一)基础知识
  10. C++从文件中查找特定的字符串,并提取该字符串
  11. js产生两个数字之间的随机数
  12. Java练习题2-基础(含解析)
  13. 鸟哥的Linux私房菜之Linux 的文件权限与目录管理(一)
  14. 新团队团队融合研讨会_新的网络研讨会:如何避免持续交付的隐性成本
  15. php ios表情包,php处理APP中emoji表情包的方法
  16. 计算机比赛小组名称和口号,取个小组队名和口号
  17. 解决CENTOS下There are no enabled repos.的问题
  18. Mixed Content: The page was loaded over HTTPS,blocked the content must be served over HTTPS.
  19. 走出“大唐” 穆穆-movno1 第一财经周刊
  20. oracle查询第三行,oracle层次化查询(行政区划三级级联)

热门文章

  1. JavaScript学习笔记:你必须要懂的原生JS(一)
  2. C#基础第七天-作业-利用面向对象的思想去实现名片-动态添加
  3. c# select标签绑定枚举,并以Description做Text显示
  4. android 调试
  5. Visual LISP 第5章 编辑源程序代码(1)文本编辑工具
  6. C++ vector容器简单解释
  7. 一起用C#做个五子棋的小游戏 增加了程序对战功能
  8. 在一个数组中找出和为目标值的那 两个 整数,并返回他们的数组下标python代码(Leetcode1)
  9. 机器学习(二)Logistic回归(Logistic regression)算法
  10. 吴恩达 coursera ML 第十二课总结+作业答案