操作系统学习记录II——从实模式到保护模式

1.概述

本次博客对应orange's书中3.1节的内容,针对实模式跳转到保护模式的汇编代码。本章实验内容较少且步骤简单,因此我们把重心放在汇编代码的分析与理解上,同时对GDT和LDT进行简单介绍。水平有限,欢迎指正。


2.实模式和保护模式简介

2.1 实模式

这里说的实模式和保护模式是指在IA32下CPU的两种工作模式,他们最大的区别就在于寻址的方式上。为何会有实模式呢?因为早期的Intel8086CPU是16位的,所以只有16位的寄存器、数据总线,以及20位的地址总线。这时候内存是裸露给处理器的,只要获得段地址和偏移地址,就可以经过cs:eip的方式进行寻址。大家熟悉的BIOS程序就是运行在CPU的实模式之下的,其中cs:ip=0000:7c00的引导程序段跳转正是用的实模式寻址方式。显然这种模式已经不适用于当今32位以及64位的CPU了,但是为了向前兼容,PC开机时一段时间内依旧会处于实模式下,而后才跳转到保护模式。


2.2 保护模式

由于一系列的启动程序并不需要很高的寻址要求,所以开机时进入的实模式也就被保存下来了。然而对于32位的CPU来说,拥有了32位地址线,寻址空间达到了4GB,因此实模式的寻址显得十分不够用了。这时候我们需要重新考虑寻址规则,这也是保护模式和实模式最大的不同。在保护模式下,依旧采用段:偏移的形式来进行寻址。只不过这时候的段值并非真实的地址了,而是一个索引,这个索引指向一个表,表里定义了起始地址、属性等信息。换句话说,对内存进行操作的时候,需要找到操作对象所对应的一个索引,通过索引找到操作的起始地址,再加上偏移量找到具体地址。这种寻址方式安全的地方就在于,对内存的操作必须通过一段规范的索引表单,从而避免了对内存的随意访问修改。再者,由于索引的存在,在每次使用索引的时候会有相应的使用记录,这样的记录确保了访问的安全性,这也是保护模式中“保护”二字的重要体现。后续在解析汇编代码的时候会深入了解这种索引的机制,也就是GDT(LDT)。


3.汇编代码解析

3.1 简述

这里代码解析主要针对原书中chapter3/a/pmtest1.sam的汇编代码解析,笔者会从头到尾地分析这段代码如何从实模式跳转到保护模式。

3.2 描述符

所谓的描述符就是以下代码宏定义的Descriptor,这段代码在pm.inc中:

; 描述符
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
    dw  %2 & 0FFFFh             ; 段界限1
    dw  %1 & 0FFFFh             ; 段基址1
    db  (%1 >> 16) & 0FFh           ; 段基址2
    dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2
    db  (%1 >> 24) & 0FFh           ; 段基址3
%endmacro ; 共 8 字节
;

看上去似乎比较难以理解,先不用管这段代码具体指的是什么,我们大致可以感觉出来这是对描述符结构的定义。那么这个定义里有什么呢,根据注释我们知道有段界限、段基址、属性这三个基本结构,也就是描述符指定的对象在哪里、有多大、有什么特征三个问题。所以对此我们先了解这么多,现在正式进入pmtest文件里。

3.3GDT段

[SECTION .gdt]
; GDT
;                              段基址,       段界限     , 属性
LABEL_GDT:         Descriptor       0,                0, 0           ; 空描述符
LABEL_DESC_CODE32: Descriptor       0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段
LABEL_DESC_VIDEO:  Descriptor 0B8000h,           0ffffh, DA_DRW      ; 显存首地址
; GDT 结束

GdtLen  equ $ - LABEL_GDT   ; GDT长度
GdtPtr      dw  GdtLen - 1  ; GDT界限
              dd    0       ; GDT基地址

; GDT 选择子
SelectorCode32      equ LABEL_DESC_CODE32   - LABEL_GDT
SelectorVideo       equ LABEL_DESC_VIDEO    - LABEL_GDT
; END of [SECTION .gdt]
3.3.1 GDT

我们很容易就在代码中发现了Descriptor这个词,也就是描述符,说明描述符是重点。先看前三行的Descriptor描述符,看上去组成了一个数组,这其实就是我们说的GDT。所谓GDT,就是Gobal Descriptor Table,用于提供段式存储机制。前面我们提到,保护模式的寻址方式依旧是段:偏移量这种结构,只不过这时候的段并非具体的地址了,而是一个个对应的描述符,这样说其实不严谨,但可以简单先这样认为。还记得上文我们分析的描述符结构吗,基址、界限、属性,你一定猜到了这就是为了寻址而设定的。后面的GdtLen顾名思义就是GDT的长度,GdtPtr用于存储GDT的界限和基地址。所以GDT就是一个由一堆描述符组成的表,而每个描述符中又记录了地址信息,因此这个表也就是一张查找索引的表,这些代码的作用就是描绘了这样的一个有索引信息的表。

3.3.2 GDT选择子

接下来是两个GDT选择子,从代码直观上来看,是某个描述符相对于GDT基址的偏移。而事实上,选择子只是稍微比地址偏移复杂一些,多了一位的TI值和RPL特权级,这与访问的对象和权限有关。在这里我们可以简单地认为这个选择子通过地址偏移找到描述符表基址中的具体描述符,通过描述符的段基址和相应的偏移找到具体的线性地址。

3.3.3 为何要有选择子(个人推测)

这时候一定有人要问了,我之前不是定义了一堆描述符的基地址了吗,为什么我不可以直接调用描述符从而进入里面的地址?为何还要算出某个描述符和GDT基地址的偏移从而多出选择子这一步?这个问题困扰了我很久,原书中也并没有详细说明,我只能谈谈个人的看法,可能不够严谨。的确,我们在GDT段定义了一堆的描述符以及他们的各种信息,从而得到了一个表。但是问题是,我们后续的程序没办法通过描述符的名字本身去访问这个描述符的信息。比如我们需要找到显存,我们当然希望直接通过DESC_VIDEO找到对应的地址。但是别忘了,能够被我们访问的只有GDT这张表,而DECS_VEDIO这个名字在这张表里是没有存储的。所以只能通过一个偏移来找到它,就像数组的下标一样,这个偏移或者说下标就是选择子。

3.4 16位代码段

[SECTION .s16]
[BITS   16]
LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h

    ; 初始化 32 位代码段描述符
    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_SEG_CODE32
    mov word [LABEL_DESC_CODE32 + 2], ax
    shr eax, 16
    mov byte [LABEL_DESC_CODE32 + 4], al
    mov byte [LABEL_DESC_CODE32 + 7], ah

    ; 为加载 GDTR 作准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT      ; eax <- gdt 基地址
    mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址

    ; 加载 GDTR
    lgdt    [GdtPtr]

    ; 关中断
    cli

    ; 打开地址线A20
    in  al, 92h
    or  al, 00000010b
    out 92h, al

    ; 准备切换到保护模式
    mov eax, cr0
    or  eax, 1
    mov cr0, eax

    ; 真正进入保护模式
    jmp dword SelectorCode32:0  ; 执行这一句会把 SelectorCode32 装入 cs,
                    ; 并跳转到 Code32Selector:0  处
; END of [SECTION .s16]

16位代码段看上去十分复杂,其实按照功能区划分就十分明朗。首先是将32位代码段描述符初始化,这里具体汇编代码我也没搞清楚,总之就是这个作用了。接着的代码的作用就是把之前定义的GDT存储下来,这里存储GDT的结构叫做GDTR,R就是寄存器的意思。然后把中端关闭避免产生错误。接着打开A20地址线,这个作用是什么呢?要知道在实模式下只有20位地址线,编号从0到19。而切换到保护模式后有32位地址线,这时候如果不把后面的地址线打开的话就会产生错误,所以我们这时候要打开第21个地址线也就是A20。下面的代码就是把cr0设为1,表示系统进入了保护模式。紧接着跟上jmp指令,这里的jmp看上去有些不同,多了dword,具体原因可以参考原书。就这样,我们就实现了从16位实模式跳转到32位保护模式了。

3.5 32位代码段

[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS   32]

LABEL_SEG_CODE32:
    mov ax, SelectorVideo
    mov gs, ax          ; 视频段选择子(目的)

    mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
    mov ah, 0Ch         ; 0000: 黑底    1100: 红字
    mov al, 'P'
    mov [gs:edi], ax

    ; 到此停止
    jmp $

SegCode32Len    equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]

当系统从实模式跳转进入保护模式的时候,开始执行32位代码段。一开始以ax为中介,把SelectVedio也就是显存写到gs中。然后往edi中写入地址偏移量,接着分别在ah和al里写入显存需要写入的东西。最后将ax(低8位为al,高8位为ah)写入显存,就可以实现对显存的操作了。注意这里的[gs:edi]寻址方式就是先找到显存的基址,然后通过偏移量寻找目标。


4.实验总结

操作上本次实验没有任何难度,主要挑战在于描述符的理解和GDT结构的设计,以及汇编代码究竟是如何一步步从实模式跳转到保护模式的。总而言之就是寻址方式中加进了一种类似于索引的方式,使处理器对内存的访问需要通过GDT来获取描述符对应的实际地址,从而间接完成寻址。本节实验成功后bochs会显示出以下画面,可以在最右侧看见我们写入显存的P字符:


bochs页面

这里要注意一下,只把跳转的pmtest代码写进软盘是无效的,需要把第一章的简单引导程序也一起写入软盘,或者自己写一份引导程序也是可以的。


5.附录

本节内容中GDT,GDTR以及LDT的内容易分辨不清,这里提供一下对这些名词的解释GDT,LDT,GDTR,LDTR 详解,有需要的读者可以自行查阅加深印象(其实是我自己),同时感谢作者的分享。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335