转载请注明出处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官网下载压缩包后安装。
1、准备GDT
2、用lgdt指令加载gdtr
3、打开A20地址线
4、将cr0寄存器的最后一位(PE位)置1:
PE位为0时,CPU运行在实模式下;
PE位为1时,CPU运行在保护模式下.
5、跳转,进入保护模式
GDT全称为Global Descriptor Table,即全局描述符表。实模式下,物理地址=段值*16+偏移值,寻址能力仅仅达到1MB。这显然不能满足人们的需求,所以后来引入了保护模式。在保护模式下,虽然段值仍然由原来16位的cs、ds等寄存器表示,但此时它仅仅变成了一个索引,这个索引指向一个数据结构的一个表项,表项中详细定义了段的起始地址、界限、属性等。这个数据结构就是GDT。GDT的作用就是用来提供段式存储机制,现在我们来看看描述符的结构:
类型(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 |
代码 |
一致性段,执行/可读,已访问 |
类型(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位陷阱门 |
BYTE15——————BYTE3 |
BYTE2 |
BYTE1——BYTE0 |
描述符索引 |
TI |
RPL |
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