您的位置:首页 > 其它

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

2013-10-07 14:20 330 查看
转载请注明出处/article/11358629.html

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

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

实验环境

lenovo G470

ubuntu11.10

bochs-2.4.6

环境搭建请参考/article/11358627.html,这里只说一点需要注意的地方:如果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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: