操作系统:从实模式切换到保护模式
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 |
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一个操作系统的实现》
3、http://www.cnblogs.com/hongzg1982/articles/2111254.html
以上是关于操作系统:从实模式切换到保护模式的主要内容,如果未能解决你的问题,请参考以下文章