学习笔记--保护模式进阶 访问大内存

我们虽然成功进入了保护模式,但是并没有体验到保护模式给我们带来的便利,上一个例子中打印了一个红色的p,这在实模式中也很容易做到,但是保护模式能做的远不止如此,上文我们提到在保护模式下的寻址空间可以到达4G,首先实验一下读大地址内存,在前面程序的基础上,新建一个段,这个段是以5MB为基址,远远超过了实模式下1MB的限制,我们先读出开始处8字节的内容,然后写入一个字符串,再从中读出8个字节的内容,如果读写成功的话,两次读出的结果是不一样的,而且第二次读出的结果应该是我们写的字符串,下面我们先来看一下源代码,然后在来慢慢分析。
  1. ; ==========================================  
  2. ; pmtest2.asm  
  3. ; 编译方法:nasm pmtest2.asm -o pmtest2.com  
  4. ; ==========================================  
  5.   
  6. DA_32       EQU 4000h   ; 32 位段  
  7. DA_C        EQU 98h ; 存在的只执行代码段属性值  
  8. DA_DRW      EQU 92h ; 存在的可读写数据段属性值  
  9. DA_DRWA     EQU 93h ; 存在的已访问可读写数据段类型值  
  10. ATCE32      EQU 4098h   ;存在的只执行32代码段属性值  
  11.   
  12. ;下面是宏定义  
  13. ; 有三个参数:段界限,段基址,段属性其中宏定义中的%1代表参数1,%2代表参数2,%3代表参数3  
  14. %macro Descriptor 3    
  15.   
  16.     dw  %2 & 0FFFFh             ; 段界限1(参数2的低16位)  
  17.     dw  %1 & 0FFFFh             ; 段基址1(参数1的低16位)  
  18.     db  (%1 >> 16) & 0FFh         ; 段基址2(参数1的16-23位)  
  19.     dw  ((%2 >> 8) & 0F00h) | (%3 & 0F000h)| (%3 & 000FFh) ; 属性1(高4位) + 段界限2(高4位) + 属性2(低8位)  
  20.     db  (%1 >> 24) & 0FFh         ; 段基址3(参数1的24-31位)  
  21. %endmacro ; 共 8 字节  
  22. ;段界限共20位,段基地址30位,段属性共16位(含段界限高4位)  
  23. org 0100h  
  24.     jmp LABEL_BEGIN  
  25.   
  26. [SECTION .gdt]  
  27. ; GDT                                                         关于一致非一致后面文章会介绍  
  28. ;                            段基址,        段界限 , 属性  
  29. LABEL_GDT:         Descriptor    0,              0, 0         ; 空描述符  
  30. LABEL_DESC_NORMAL: Descriptor    0,         0ffffh, DA_DRW    ; Normal 描述符  
  31. LABEL_DESC_CODE32: Descriptor    0, SegCode32Len-1, DA_C+DA_32; 非一致代码段, 32  
  32. LABEL_DESC_CODE16: Descriptor    0,         0ffffh, DA_C      ; 非一致代码段, 16  
  33. LABEL_DESC_DATA:   Descriptor    0,      DataLen-1, DA_DRW    ; Data  
  34. LABEL_DESC_STACK:  Descriptor    0,     TopOfStack, DA_DRWA+DA_32; Stack, 32 位  
  35. LABEL_DESC_TEST:   Descriptor 0500000h,     0ffffh, DA_DRW  
  36. LABEL_DESC_VIDEO:  Descriptor  0B8000h,     0ffffh, DA_DRW    ; 显存首地址  
  37. ; GDT 结束  
  38.   
  39. GdtLen      equ $ - LABEL_GDT   ; GDT长度  
  40. GdtPtr      dw  GdtLen - 1  ; GDT界限  
  41.         dd  0       ; GDT基地址  
  42.   
  43. ; GDT 选择子  
  44. SelectorNormal      equ LABEL_DESC_NORMAL   - LABEL_GDT  
  45. SelectorCode32      equ LABEL_DESC_CODE32   - LABEL_GDT  
  46. SelectorCode16      equ LABEL_DESC_CODE16   - LABEL_GDT  
  47. SelectorData        equ LABEL_DESC_DATA     - LABEL_GDT  
  48. SelectorStack       equ LABEL_DESC_STACK    - LABEL_GDT  
  49. SelectorTest        equ LABEL_DESC_TEST     - LABEL_GDT  
  50. SelectorVideo       equ LABEL_DESC_VIDEO    - LABEL_GDT  
  51. ; END of [SECTION .gdt]  
  52.   
  53. [SECTION .data1]     ; 数据段  
  54. ALIGN   32  
  55. [BITS   32]  
  56. LABEL_DATA:  
  57. SPValueInRealMode   dw  0  
  58. ; 字符串  
  59. PMMessage:      db  "In Protect Mode now. ^-^", 0   ; 在保护模式中显示(以0结尾)  
  60. OffsetPMMessage     equ PMMessage - $$                  ;PMMessage起始地址偏移  
  61. StrTest:        db  "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0 ;写入的字符串  
  62. OffsetStrTest       equ StrTest - $$            ;写入字符串的偏移  
  63. DataLen         equ $ - LABEL_DATA          ;字符串长度  
  64. ; END of [SECTION .data1]  
  65.   
  66.   
  67. ; 全局堆栈段  
  68. [SECTION .gs]  
  69. ALIGN   32  
  70. [BITS   32]  
  71. LABEL_STACK:  
  72.     times 512 db 0  
  73.   
  74. TopOfStack  equ $ - LABEL_STACK - 1 ;栈底  
  75.   
  76. ; END of [SECTION .gs]  
  77.   
  78.   
  79. [SECTION .s16]  
  80. [BITS   16]  
  81. LABEL_BEGIN:  
  82.     mov ax, cs  
  83.     mov ds, ax  
  84.     mov es, ax  
  85.     mov ss, ax  
  86.     mov sp, 0100h                       ;设置栈顶指针(指向栈底)  
  87.   
  88.     mov [LABEL_GO_BACK_TO_REAL+3], ax   ;后面解释  
  89.     mov [SPValueInRealMode], sp     ;保存实模式下的栈顶指针  
  90.   
  91.     ; 初始化 16 位代码段描述符  
  92.     mov ax, cs  
  93.     movzx   eax, ax  
  94.     shl eax, 4   ;段值*16  
  95.     add eax, LABEL_SEG_CODE16 ;段值*16+偏移=16位代码段基地址  
  96.     mov word [LABEL_DESC_CODE16 + 2], ax  ;基地址1  
  97.     shr eax, 16  
  98.     mov byte [LABEL_DESC_CODE16 + 4], al  ;基地址2  
  99.     mov byte [LABEL_DESC_CODE16 + 7], ah  ;基地址3  
  100.   
  101.     ; 初始化 32 位代码段描述符  
  102.     xor eax, eax  
  103.     mov ax, cs  
  104.     shl eax, 4    ;段值*16  
  105.     add eax, LABEL_SEG_CODE32  ;段值*16+偏移=32位代码段基地址  
  106.     mov word [LABEL_DESC_CODE32 + 2], ax  ;基地址1  
  107.     shr eax, 16  
  108.     mov byte [LABEL_DESC_CODE32 + 4], al  ;基地址2  
  109.     mov byte [LABEL_DESC_CODE32 + 7], ah  ;基地址3  
  110.   
  111.     ; 初始化数据段描述符  
  112.     xor eax, eax  
  113.     mov ax, ds  
  114.     shl eax, 4    ;段值*16  
  115.     add eax, LABEL_DATA        ;段值*16+偏移=数据段基地址  
  116.     mov word [LABEL_DESC_DATA + 2], ax    ;基地址1  
  117.     shr eax, 16  
  118.     mov byte [LABEL_DESC_DATA + 4], al    ;基地址2  
  119.     mov byte [LABEL_DESC_DATA + 7], ah    ;基地址3  
  120.   
  121.     ; 初始化堆栈段描述符  
  122.     xor eax, eax  
  123.     mov ax, ds  
  124.     shl eax, 4    ;段值*16  
  125.     add eax, LABEL_STACK        ;段值*16+偏移=堆栈段基地址  
  126.     mov word [LABEL_DESC_STACK + 2], ax   ;基地址1  
  127.     shr eax, 16  
  128.     mov byte [LABEL_DESC_STACK + 4], al   ;基地址2  
  129.     mov byte [LABEL_DESC_STACK + 7], ah   ;基地址3  
  130.   
  131.     ; 为加载 GDTR 作准备  
  132.     xor eax, eax  
  133.     mov ax, ds  
  134.     shl eax, 4    ;段值*16  
  135.     add eax, LABEL_GDT      ; eax <- gdt 基地址  
  136.     mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址  
  137.   
  138.     ; 加载 GDTR  
  139.     lgdt    [GdtPtr]  
  140.   
  141.     ; 关中断  
  142.     cli  
  143.   
  144.     ; 打开地址线A20  
  145.     in  al, 92h  
  146.     or  al, 00000010b  
  147.     out 92h, al  
  148.   
  149.     ; 准备切换到保护模式  
  150.     mov eax, cr0  
  151.     or  eax, 1    ;设置cr0的0位(PE位,PE=1准备进入保护模式)  
  152.     mov cr0, eax  ;更新cr0  
  153.   
  154.     ; 真正进入保护模式  
  155.     jmp dword SelectorCode32:0  ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处  
  156.   
  157. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;  
  158.   
  159. LABEL_REAL_ENTRY:       ; 从保护模式跳回到实模式就到了这里  
  160.     mov ax, cs  
  161.     mov ds, ax      ;重新设置数据段  
  162.     mov es, ax        
  163.     mov ss, ax      ;重新设置堆栈段  
  164.   
  165.     mov sp, [SPValueInRealMode] ;恢复栈顶指针  
  166.   
  167.     in  al, 92h     ; `.  
  168.     and al, 11111101b   ;  | 关闭 A20 地址线  
  169.     out 92h, al     ; /  
  170.   
  171.     sti         ; 开中断  
  172.   
  173.     mov ax, 4c00h   ; `.  
  174.     int 21h     ; /  回到 DOS(调用dos中断号,结束程序,返回到DOS)  
  175. ; END of [SECTION .s16]  
  176.   
  177.   
  178. [SECTION .s32]; 32 位代码段. 由实模式跳入.  
  179. [BITS   32]  
  180.   
  181. LABEL_SEG_CODE32:  
  182.     mov ax, SelectorData  
  183.     mov ds, ax          ; 数据段选择子  
  184.     mov ax, SelectorTest  
  185.     mov es, ax          ; 测试段选择子  
  186.     mov ax, SelectorVideo  
  187.     mov gs, ax          ; 视频段选择子  
  188.   
  189.     mov ax, SelectorStack  
  190.     mov ss, ax          ; 堆栈段选择子  
  191.   
  192.     mov esp, TopOfStack         ;设置栈顶指针  
  193.   
  194.   
  195.     ; 下面显示一个字符串  
  196.     mov ah, 0Ch         ; 0000: 黑底    1100: 红字  
  197.     xor esi, esi  
  198.     xor edi, edi  
  199.     mov esi, OffsetPMMessage    ; 源数据偏移  
  200.     mov edi, (80 * 10 + 0) * 2  ; 目的数据偏移。屏幕第 10 行, 第 0 列。  
  201.     cld  
  202. .1:  
  203.     lodsb                           ;从[ds:esi]中去一个字符送入al  
  204.     test    al, al                  ;判断是否到字符串末尾(以0结尾)  
  205.     jz  .2          ;是->跳转  
  206.     mov [gs:edi], ax            ;否->输入到显存  
  207.     add edi, 2          ;指针加2(一个字符占两个字节)  
  208.     jmp .1          ;循环  
  209. .2: ; 显示完毕  
  210.   
  211.     call    DispReturn      ;换行  
  212.   
  213.     call    TestRead  
  214.     call    TestWrite  
  215.     call    TestRead  
  216.   
  217.     ; 到此停止  
  218.     jmp SelectorCode16:0  
  219.   
  220. ; ------------------------------------------------------------------------  
  221. TestRead:  
  222.     xor esi, esi  
  223.     mov ecx, 8          ;读的次数  
  224. .loop:  
  225.     mov al, [es:esi]        ;从指定内存中都一个字符送到al中  
  226.     call    DispAL2         ;显示该字符  
  227.     inc esi         ;准备读下一个字符  
  228.     loop    .loop           ;循环  
  229.   
  230.     call    DispReturn      ;换行  
  231.   
  232.     ret  
  233. ; TestRead 结束-----------------------------------------------------------  
  234.   
  235.   
  236. ; ------------------------------------------------------------------------  
  237. TestWrite:  
  238.     push    esi  
  239.     push    edi  
  240.     xor esi, esi  
  241.     xor edi, edi  
  242.     mov esi, OffsetStrTest  ; 源数据偏移  
  243.     cld  
  244. .1:  
  245.     lodsb               ;从[ds:esi]中读一个字符  
  246.     test    al, al          ;判断是否结束  
  247.     jz  .2          ;是->跳转  
  248.     mov [es:edi], al        ;否->送入指定内存  
  249.     inc edi           
  250.     jmp .1  
  251. .2:  
  252.   
  253.     pop edi  
  254.     pop esi  
  255.   
  256.     ret  
  257. ; TestWrite 结束----------------------------------------------------------  
  258.   
  259.   
  260. ; ------------------------------------------------------------------------  
  261. ; 显示 AL 中的数字  
  262. ; 默认地:  
  263. ;   数字已经存在 AL 中  
  264. ;   edi 始终指向要显示的下一个字符的位置  
  265. ; 被改变的寄存器:  
  266. ;   ax, edi  
  267. ; ------------------------------------------------------------------------  
  268. DispAL:  
  269.     push    ecx  
  270.     push    edx  
  271.   
  272.     mov ah, 0Ch         ; 0000: 黑底    1100: 红字  
  273.     mov dl, al          ;暂存al  
  274.     shr al, 4           ;取数字的高4位(十位数字)  
  275.     mov ecx, 2                  ;十位和个位共两位需要输出  
  276. .begin:  
  277.     and al, 01111b              ;保留低4位(十位或个位)  
  278.     cmp al, 9           ;大于数字9,则转换成相应的16进制数对应的ASCII  
  279.     ja  .1  
  280.     add al, '0'         ;+30转换成ASCII  
  281.     jmp .2  
  282. .1:  
  283.     sub al, 0Ah           
  284.     add al, 'A'           
  285. .2:  
  286.     mov byte[gs:edi], al        ;改变了书上的写法  
  287.     add edi, 2          ;移动指针  
  288.   
  289.     mov al, dl  
  290.     loop    .begin  
  291.     add edi, 2          ;空格  
  292.   
  293.     pop edx  
  294.     pop ecx  
  295.   
  296.     ret  
  297. ; DispAL 结束-------------------------------------------------------------  
  298.   
  299. DispAL2:mov [gs:edi], al   ;显示字符  
  300.     add edi, 2  
  301.     ret  
  302.       
  303.   
  304. ; ------------------------------------------------------------------------  
  305. ;换行函数  
  306. DispReturn:  
  307.     push    eax  
  308.     push    ebx  
  309.     mov eax, edi ;获取当前光标的显存偏移  
  310.     mov bl, 160  ;每行80字符,每个字符占两个字节(字符ASCII码+字符属性),所以一行共80*2=160个字节  
  311.     div bl       ;获取当前行数(在显存中行数从0开始编号的)  
  312.     and eax, 0FFh;取低8位(在当前页,否则有可能输出到其它页了,显示器不会显示的)  
  313.     inc eax      ;下一行  
  314.     mov bl, 160    
  315.     mul bl       ;确定下一行开始的字节数  
  316.     mov edi, eax ;更新edi寄存器  
  317.     pop ebx  
  318.     pop eax  
  319.   
  320.     ret  
  321. ; DispReturn 结束---------------------------------------------------------  
  322.   
  323. SegCode32Len    equ $ - LABEL_SEG_CODE32  
  324. ; END of [SECTION .s32]  
  325.   
  326.   
  327. ; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式  
  328. [SECTION .s16code]  
  329. ALIGN   32  
  330. [BITS   16]  
  331. LABEL_SEG_CODE16:  
  332.     ; 跳回实模式:  
  333.     mov ax, SelectorNormal  
  334.     mov ds, ax  
  335.     mov es, ax  
  336.     mov fs, ax  
  337.     mov gs, ax  
  338.     mov ss, ax  
  339.   
  340.     mov eax, cr0  
  341.     and al, 11111110b  ;设置cr0的0位(PE位,PE=0准备进入实模式)  
  342.     mov cr0, eax       ;更新cr0  
  343.   
  344. LABEL_GO_BACK_TO_REAL:  
  345.     jmp 0:LABEL_REAL_ENTRY  ; 段地址会在程序开始处被设置成正确的值  
  346.   
  347. Code16Len   equ $ - LABEL_SEG_CODE16  
  348.   
  349. ; END of [SECTION .s16code]  

看这个之前必须得明白上一节的代码,在这里对重复的内容不多解释,如果不清楚,可以参考:

《Orange's 一个操作系统的实现》学习笔记--实践认识保护模式 

代码中很多我都补充了注释,如果有汇编基础的,应该没有什么难度了,下面是书中的话:

段[SECTION.s32]在开始初始化了ds,es,gs,让ds指向了新增的数据段,es指向了新增的5MB内存的段,gs指向显存,DisAL将al中的字节用十六进制形式显示出来,DispReturn模拟一个回车的显示,实际上让下一个字符显示在下一行的开头处,在TestWrite中用到了一个常量offsetStrTest,注意:我们用到这个字符串的时候并没有用直接标号,StrTest,而是

又定义了一个符号offsetStrTest,它等于StrTest -

,StrTest
表示字符串StrTest相对于本节的开始处(即LABEL_DATA处)的偏移,看看初始化数据段描述符的代码,容易发现数据段的基地址就是便是LABEL_DATA的物理地址,于是offsetStrTest既是字符串相对于LABEL_DATA的偏移,也是其在数据段中的偏移,我们在保护模式下需要用的正是这个偏移,而不再是实模式下的地址,另外,由于在保护模式下用到了堆栈,我们也建立了堆栈段,现在我们来解释一下回到实模式的代码,我们从实模式进入保护模式直接用一个跳转就可以了但是返回的时候却要稍微复杂一些,因为在准备结束保护模式回到实模式之前,需要加载一个合适的描述符选择子到相关段寄存器,以使对应段描述符高速缓存寄存器中含有合适的段界限和属性。而且,我们不能从32位代码段返回实模式,只能从16位代码段中返回。这是因为无法实现从32位代码段返回时cs高速缓冲寄存器中的属性符合实模式的要求(实模式不能改变段属性)。

所以,在这里,我们新增一个Normal描述符,在返回实模式之前把对应选择子SelectorNormal加载到ds,es,和ss,就是上面所说的这个原因。

  1. ; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式  
  2. [SECTION .s16code]  
  3. ALIGN   32  
  4. [BITS   16]  
  5. LABEL_SEG_CODE16:  
  6.     ; 跳回实模式:  
  7.     mov ax, SelectorNormal  
  8.     mov ds, ax  
  9.     mov es, ax  
  10.     mov fs, ax  
  11.     mov gs, ax  
  12.     mov ss, ax  
  13.   
  14.     mov eax, cr0  
  15.     and al, 11111110b  ;设置cr0的0位(PE位,PE=0准备进入实模式)  
  16.     mov cr0, eax       ;更新cr0  
  17.   
  18. LABEL_GO_BACK_TO_REAL:  
  19.     jmp 0:LABEL_REAL_ENTRY  ; 段地址会在程序开始处被设置成正确的值  
  20.   
  21. Code16Len   equ $ - LABEL_SEG_CODE16  
  22.   
  23. ; END of [SECTION .s16code]  
这个段由[SECTION.s32]中的jmp SelectorCode16:0跳进来的,在后面的跳转中看上去好像不太对,因为段地址为0,其实这里只是暂时这样写罢了,在程序的一开始处就可以看到下面的代码:
  1. mov ax, cs  
  2. mov ds, ax  
  3. mov es, ax  
  4. mov ss, ax  
  5. mov sp, 0100h                       ;设置栈顶指针(指向栈底)  
  6.   
  7. mov [LABEL_GO_BACK_TO_REAL+3], ax   ;后面解释  
  8. mov [SPValueInRealMode], sp     ;保存实模式下的栈顶指针  

mov[LABEL_GO_BACK_TO_REAL+3], ax 的作用就是为回到实模式的这个跳转指令指定正确的段地址,这条指令的机器码如下


这个图告诉我们,LABEL_GO_BACK_TO_REAL+3恰好就是Segment的地址,而在这条指令mov[LABEL_GO_BACK_TO_REAL+3], ax执行之前ax已经是实模式下的cs,(我们记做cs_real_model)了,所以它将把cs保存到Segment的位置,等到jmp指令执行时,它已经

不再是jmp  0:LABEL_REAL_ENTRY,而变成了:

           jmp  cs_real_model :LABEL_REAL_ENTRY它将跳转到标号LABEL_REAL_ENTRY处。

在跳回实模式之后,程序重新设置各个段寄存器的值,恢复sp的值,然后关闭A20,打开中断,重新回到原来的样子。

下面是书上代码运行图:


下面是我修改了程序的图调用的是DisAL2直接输出字符


但是怎么读出的数据没有呢,其实呀显示的直接是ASCII,而ASCII为0的就是什么也没有,可以再次运行,这样上下就都是相同的字符了

再次运行后的图:


注:因为是.com文件,我没有按照书上的做法了弄,这些弄了一个dos虚拟机,上面是在里面运行的结果,当然还可以调用debug,方便调用了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值