操作系统:从实模式切换到保护模式

Posted 爱搬砖的摄影师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统:从实模式切换到保护模式相关的知识,希望对你有一定的参考价值。

转载请注明出处http://blog.csdn.net/sx154893743/article/details/12378053


        最近一直在学着自己动手制作操作系统,昨天总算是有了点成效,现作博客一篇整理下思路。

        本次提供的代码是从实模式到保护模式的切换,并在保护模式下打印"Hello, world!"。

实验环境

        lenovo G470

        ubuntu11.10

        bochs-2.4.6

        环境搭建请参考http://blog.csdn.net/sx154893743/article/details/9341621,这里只说一点需要注意的地方:如果bochs是直接用apt-get install以命令行形式下载安装的话,将没有调试功能;若需要调试功能,请到bochs官网下载压缩包后安装。

相关知识

从实模式切换到保护模式,主要有以下5个步骤:

        1、准备GDT
        2、用lgdt指令加载gdtr
        3、打开A20地址线
        4、将cr0寄存器的最后一位(PE位)置1:

               PE位为0时,CPU运行在实模式下;

               PE位为1时,CPU运行在保护模式下.
        5、跳转,进入保护模式

GDT

        GDT全称为Global Descriptor Table,即全局描述符表。实模式下,物理地址=段值*16+偏移值,寻址能力仅仅达到1MB。这显然不能满足人们的需求,所以后来引入了保护模式。在保护模式下,虽然段值仍然由原来16位的cs、ds等寄存器表示,但此时它仅仅变成了一个索引,这个索引指向一个数据结构的一个表项,表项中详细定义了段的起始地址、界限、属性等。这个数据结构就是GDT。GDT的作用就是用来提供段式存储机制,现在我们来看看描述符的结构:

一个段描述符中各字段和标志的含义可参考 http://baike.baidu.com/link?url=6fCMHjAh8rkkxK5PQo7HzqzOeK5sdm6Y5tSVdK0LQOlftf4AXQ36MvEA4fnvqh-xF7275mJkzwrgmjuELZW3YK
这里各个字段、标志的含义看起来很复杂,实际上需要我们了解的只有段界限、段基址、D/B位、S位以及TYPE。在本次实验代码中,我们可通过宏设置好各描述符的段界限和段属性:
%macro    Descriptor    3
                    dw        %2 & 0FFFFh                                                ;段界限1
                    dw        %1 & 0FFFFh                                                ;段基址1
                    db        (%1 >> 16) & 0FFh                                        ;段基址2
                    dw        ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)    ;属性1+段界限+属性2
                    db        (%1 >> 24) & 0FFh                                        ;段基址3
%endmacro
至于段基址,则可由具体代码填充进去。
段属性如下:
DA_32        equ    4000h                ;32位段(D/B位为1,其他为0)
DA_C            equ    98h                    ;存在的只执行代码段(1001:P=1存在,
                                                            ;ring0级,s=1数据段/代码段描述符;
                                                            ;1000:只执行)
DA_DRW    equ    92h                    ;存在的可读写数据段(1001;
                                                            ;0010:读/写)
这里只列出了很少一部分属性,其他属性可依据需求自由设置,下面是TYPE描述符类型:
数据段和代码段描述符:(S=1)

类型(TYPE)字段

描述符类型

说明

十进制

位11

位10

位9

位8

E

W

A

0

0

0

0

0

数据

只读

1

0

0

0

1

数据

只读,已访问

2

0

0

1

0

数据

可读/写

3

0

0

1

1

数据

可读/写,已访问

4

0

1

0

0

数据

向下扩展,只读

5

0

1

0

1

数据

向下扩展,只读,已访问

6

0

1

1

0

数据

向下扩展,可读/写

7

0

1

1

1

数据

向下扩展,可读/写,已访问

C

R

A

8

1

0

0

0

代码

仅执行

9

1

0

0

1

代码

仅执行,已访问

10

1

0

1

0

代码

执行/可读

11

1

0

1

1

代码

执行/可读,已访问

12

1

1

0

0

代码

一致性段,仅执行

13

1

1

0

1

代码

一致性段,仅执行,已访问

14

1

1

1

0

代码

一致性段,执行/可读

15

1

1

1

1

代码

一致性段,执行/可读,已访问


系统段和门描述符:(S=0)

类型(TYPE)字段

说明

十进制

位11

位10

位9

位8

0

0

0

0

0

Reserved

保留

1

0

0

0

1

16-Bit TSS (Available)

16位 TSS(可用)

2

0

0

1

0

LDT

LDT

3

0

0

1

1

16-Bit TSS (Busy)

16位 TSS(忙)

4

0

1

0

0

16-Bit Call Gate

16位调用门

5

0

1

0

1

Task Gate

任务门

6

0

1

1

0

16-Bit Interrupt Gate

16位中断门

7

0

1

1

1

16-Bit Trap Gate

16位陷阱门

8

1

0

0

0

Reserved

保留

9

1

0

0

1

32-Bit TSS (Available)

32位TSS(可用)

10

1

0

1

0

Reserved

保留

11

1

0

1

1

32-Bit TSS (Busy)

32位TSS(忙)

12

1

1

0

0

32-Bit Call gate

32位调用门

13

1

1

0

1

Reserved

保留

14

1

1

1

0

32-Bit Interrupt Gate

32位中断门

15

1

1

1

1

32-Bit Trap Gate

32位陷阱门


选择子

       我们已经大致了解了GDT,那么计算机如何找到GDT的表项(描述符)呢?这里我们就需要用到选择子了。在保护模式下,CS等段寄存器会载入相应段的选择子,计算机会通过选择子来判断我们需要的是哪个描述符。段选择子结构如下

          BYTE15——————BYTE3

                                   BYTE2

                               BYTE1——BYTE0

                       描述符索引

                                      TI

                                        RPL

        TI标识是全局描述符表索引还是局部描述符表索引:为0时表示GDT,为1时表示LDT;
        RPL为特权级,表示ring0~ring3。

寄存器gdtr和cr0

         我们在用GDT前,需要先找到GDT,这里就需要用到一个特殊的寄存器:gdtr。gdtr示意图如下:

                                           |                      32位基地址                   |     16位界限    |

         cr0寄存器最后一位为PE位:PE=0时,CPU运行在实模式下;PE=1时,CPU运行在保护模式下。

实验步骤

1、进入自己的工作目录,也就是你准备编写代码的那个目录,在终端输入bximage命令生成一张虚拟软盘。如下图:

2、编写代码,保存文件名boot_lv.asm。

; ====================================================
; 保护模式
; nasm boot_lv.asm -o boot_lv.bin
; dd if=boot_lv.bin of=a.img bs=512 count=1 conv=notrunc
; 主要过程:
; 1、准备GDT
; 2、用lgdt指令加载gdtr
; 3、打开A20地址线
; 4、将cr0寄存器的最后一位(PE位)置1:
;          PE位为0时,CPU运行在实模式下;
;         PE位为1时,CPU运行在保护模式下.
; 5、跳转,进入保护模式
;
; Made by Lv
; 2013.10.06    
; 完成基本功能:实模式到保护模式,并打印字符串
; ====================================================

;描述符
;Descriptor    base, limit, attr
;base:    dd                                                    ;段基址
;limit:    dd                                                    ;段界限    
;attr:        dw
;[SECTION    .macro]    
%macro    Descriptor    3
                    dw        %2 & 0FFFFh                                                ;段界限1
                    dw        %1 & 0FFFFh                                                ;段基址1
                    db        (%1 >> 16) & 0FFh                                        ;段基址2
                    dw        ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)    ;属性1+段界限+属性2
                    db        (%1 >> 24) & 0FFh                                        ;段基址3
%endmacro
DA_32        equ    4000h                ;32位段(D/B位为1,其他为0)
DA_C            equ    98h                    ;存在的只执行代码段(1001:P=1存在,
                                                            ;ring0级,s=1数据段/代码段描述符;
                                                            ;1000:只执行)
DA_DRW    equ    92h                    ;存在的可读写数据段(1001;
                                                            ;0010:读/写)

org        07c00h
                jmp    LABEL_BEGIN

[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
LABEL_DESC_DATA:            Descriptor    0,    DataLen - 1,    DA_DRW

GdtLen        equ    $ - LABEL_GDT            ;GDT长度
GdtPtr        dw        GdtLen - 1                    ;GDT界限
                    dd        0                                        ;GDT基址
                    
SelectorCode32        equ    LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo        equ    LABEL_DESC_VIDEO - LABEL_GDT
SelectorData            equ    LABEL_DESC_DATA - LABEL_GDT

[SECTION    .data]
ALIGN    32
[BITS    32]
LABEL_SEG_DATA:
Message                    db    "Hello, world!", 0
OffsetMessage        equ    Message - $$
DataLen                    equ    $ - LABEL_SEG_DATA
SegDataAddress    equ    $$
                    
[SECTION    .s16]
[BITS    16]
LABEL_BEGIN:
                mov    ax,    cs
                mov    ds,    ax
                mov    es,    ax
                mov    ss,    ax
                mov    sp,    0100h
                
                ;填充代码段描述符的段基址部分
                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
                
                ;填充数据段描述符的段基址部分
                xor        eax,    eax
                mov    ax,        ds
                shl        eax,    4
                add    eax,     LABEL_SEG_DATA
                mov    word    [LABEL_DESC_DATA + 2],    ax
                shr        eax,    16
                mov    byte        [LABEL_DESC_DATA + 4],    al
                mov    byte        [LABEL_DESC_DATA + 7],    ah
                
                ;填充GDTR的GDT基址部分
                xor        eax,    eax
                mov    ax,        ds
                shl        eax,    4
                add    eax,    LABEL_GDT
                mov    dword    [GdtPtr + 2],    eax
                
                ;lgdt指令加载GDTR
                lgdt    [GdtPtr]
                
                ;此处注意关中断,
                ;因为保护模式下中断处理方式与实模式不同,不关掉中断会出错
                cli
                
                ;打开A20地址线
                in        al,        92h
                or        al,        00000010b
                out        92h,    al
                
                ;cr0的PE位置为1,准备金如保护模式
                mov    eax,    cr0
                or        eax,        1
                mov    cr0,        eax
                
                ;进入保护模式
                ;保护模式下,原来的段寄存器存储变为选择子
                jmp    dword    SelectorCode32:0
                
SegCode16Len        equ        $ - LABEL_BEGIN

[SECTION    .s32]
[BITS    32]
LABEL_SEG_CODE32:
                mov    ax,        SelectorVideo
                mov    gs,        ax
                mov    ax,        SelectorData
                mov    ds,        ax
                
                mov    esi,        OffsetMessage
                mov    edi,        (80 * 0 + 0) * 2
                mov    ah,        0Ch                        ;0000:黑底    1010:红字
                cld
.loop:
                lodsb                                                ;(AL)<-[ds:esi]
                test    al,    al
                jz        .break
                mov    [gs:edi],    ax
                add    edi,        2
                jmp    .loop

.break:
                jmp    $
                
SegCode32Len        equ        $ - LABEL_SEG_CODE32

times        510 - (SegCode32Len + SegCode16Len + DataLen + GdtLen + 36)     db         0
dw             0xaa55


3、生成bin文件

       nasm boot_lv.asm -o boot_lv.bin

4、将bin文件写入虚拟软盘

       dd if=boot_lv.bin of=a.img bs=512 count=1 conv=notrunc

5、运行bochs,如图:

注意,这里是带调试功能的bochs,输入c,表示执行代码,直到遇到断点;q表示退出。

这里是输入不带参数的bochs,bochs会在当前目录下顺序寻找以下文件作为默认配置文件:

.bochsrc

bochsrc

bochsrc.txt

bochsrc.bxrc(仅对windows有效)


实验结果如图:

当然,在代码出现错误,无法得到想要结果的情况下,就需要用到bochs强大的调试功能了,这里只简单列出bochs部分调试指令:

行为

指令

举例

在某物理地址设置断点

b addr

b 0x30400

显示当前所有断点信息

info break

info break

继续执行,直到遇上断点

c

c

单步执行

s

s

单步执行(遇到函数则跳过)

n

n

查看寄存器信息

info cpu

r

fp

sreg

creg

info cpu

r

fp

sreg

creg

查看堆栈

print-stack

print-stack

查看内存物理地址内容

xp /nuf addr

xp /40bx 0x9013e

查看线性地址内容

x /nuf addr

x /40bx 0x13e

反汇编一段内存

u start end

u 0x30400 0x3040D

反汇编执行的每一条指令

trace-on

trace-on

每执行一条指令就打印CPU信息

trace-reg

trace-reg on


参考资料

1、《Orange's一个操作系统的实现》

2、http://baike.baidu.com/link?url=6fCMHjAh8rkkxK5PQo7HzqzOeK5sdm6Y5tSVdK0LQOlftf4AXQ36MvEA4fnvqh-xF7275mJkzwrgmjuELZW3YK

3、http://www.cnblogs.com/hongzg1982/articles/2111254.html

以上是关于操作系统:从实模式切换到保护模式的主要内容,如果未能解决你的问题,请参考以下文章

自制操作系统04从实模式到保护模式

第十一课 实模式到保护模式 中

《x86汇编语言:从实模式到保护模式》课后答案

《x86汇编语言:从实模式到保护模式》配书文件包下载

Linux从头学08:Linux 是如何保护内核代码的?从实模式到保护模式

《x86汇编语言:从实模式到保护模式》课后答案