前面的教程已经说了怎么由机器启动然后转到执行我们自己的代码,现在已经可以显示出一个粗糙的图形界面了,我们的程序可以先启动
loader
,然后再由
loader
里面的代码把放在磁盘里面的
kernel
文件载入到一个指定的位置,再转到
kernel
里面执行,这样一来,代码就可以突破
512
个字节的限制,任意由你爱写多长就写多长了,但是如果所有的代码都用汇编来写的话,我想你首先尝到的是身体上病痛的折磨,而不是完成一个自己操作系统雏形的喜悦。
那么,我们就需要一个好用的工具来代替汇编。
C
语言是一个非常好的工具,全世界使用者都有很多,虽然也可以用
basic
来写,但是它的面好像没有
C
那么广泛,既然流行,我们也就随波逐流吧。据说
UNIX
后来就是用
C
写的。
要用
C
的话,那么就要看看它和汇编混合起来的情况。
错了,在使用
C
之前,我们应该把系统转换成
32
位的模式(人家
intel
都出了
64
位的
cpu
现在讲
32
位不知道还有没有前途),因为在系统启动的时候就是
16
位的模式
为什么?
“在
8086/8088
时代,处理器只存在一种操作模式,而到了
286
、
386
的时代处理器增加了两种操作模式――保护模式
(Protected Mode)
和系统管理模式
(System Management Mode)
,而以前的模式就被称为实地址模式
(Real address Mode),
保护模式可以使处理器支持所有的指令和所有的体系结构,提供最高的性能和兼容性,而当主机被启动
(
开机或者重启动
)
的时候处理器处于
RM
状态下
”
为什么用小字体,因为看着会头晕,干脆就不要看了,说的是计算机在启动的时候是处于
16
位的RM
状态下的,而
C
编译出来的代码是
32
位的,你想在一个
16
位系统里面运行
32
位的程序似乎是行不通的,所以,我们先要把系统置为
32
位的PM
状态。好处先不说了一大堆的,先看看怎么进去。(因为我也不懂,大家一起学习)
文章说只要设置一个位就可以进入保护模式了:
;
准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
这里的
CR0
要说一下:它是
cpu
的控制寄存器
包括工作方式的控制位,
包含分页管理机制的控制位,
包含浮点协处理器的控制位
CR0
|
PG
|
0000000000000000
|
ET
|
TS
|
EM
|
MP
|
PE
|
位
|
31
|
5
~
30
|
4
|
3
|
2
|
1
|
0
|
保护控制位
PG/PE
控制分段和分页管理机制的操作
PG
|
PE
|
处理器工作方式
|
0
|
0
|
实模式
|
0
|
1
|
保护模式,禁用分页机制
|
1
|
0
|
非法组合
|
1
|
1
|
保护方式,启用分页机制
|
控制浮点协处理器的操作:
MP(
算术存在位
)
EM(
模拟位
)
TS(
任务切换位
)
ET(
扩展类型位
)
。
…
or eax, 1
这句话的意思就是把
pe
置
1
,应该就是处在保护模式下面了,但是是不是分页先不管了。
我们原来
16
位的时候使用地址是这样的:
Segment:Offset
Segment
的最大可以表示的值就是
64k,16
位嘛
(0x0000~0xffff)
Offset
也一样
64k
得到的线性地址就是
Segment * 0x10 + Offset = 0x00000 ~0xfffff
算来就是
20
位有效长度的地址
1MB
的内存可以访问
下来到了
32
位的
PM
模式,顾名思义保护模式就要有保护模式的样子,靠什么东西保护呢?就需要为它设置一个
这个内存地方的访问权限
Access
因为是
32
位的所以
Segment
(
0xffffffff
)
是
32
位的
还有一个界限
Limit
限制所使用的内存大小,不知道是干什么用的,好像是不让你占用过多的内存
三个最主要的东西构成了一个数据结构
GDT
(
Global Descriptor Table
)全局描述表
64
位长度据说这个表可以放在内存的任何位置只要用
GDTR
指令让
cpu
知道在哪里找
GDT
就可以了。
怎么操作这样的地址呢?
在段寄存器里装入
Segment Selector(段选择器)
通过这个选择器里面的内容在全局描述表里面找到相应的段描述内容,实际上段选择器相当于一个索引目录,比如说我有一本书,开头一页是总纲,就有目录(
GDT
),你想要找哪一章,就打开总纲找,然后在目录下面有章,每一章能指示从书的哪一页开始(
Base address
)
,
看到第几页
(Offset)
先看一个
GDT
的例子
gdtr :
dw gdtend - gdt - 1 ; gdt的长度
dd gdt ; gdt的物理地址
gdt:
gdt0:
dw 0,0,0,0 ; 据说是一定要为0否则会有错
codesel_gdt:
dw 0xffff ; 界限Limit值 = 0x100000 * 0x1000 = 4GB
dw 0 ; 基地址 = 0
dw 0x9A00 ; 表示代码段可读可执行
dw 0x00CF ; 粒度(不知道是什么意思)
datasel_gdt:
dw 0xffff ; 4GB
dw 0x0 ; 基地址
dw 0x9200 ; 数据段可读可写
dw 0x00CF ; 粒度
gdtend:
codesel equ codesel_gdt - gdt
datasel equ datasel_gdt – gdt
先试一下,完整的程序如下:
;======================
; 操作系统入门(四) -痛并学习中
; 进入保护模式的测试
; 2006年4月19日
; http://blog.csdn.net/flyback
; fly-back@163.com
;======================
org 0x7c00
entry:
jmp short begin
begin:
cli ; 关中断,防止意外中断打断程序执行
mov ax,cs ;
mov ds, ax ; 设置数据段
mov es, ax ;
lgdt [cs:gdtr]
mov eax, cr0
or eax,1
mov cr0, eax
jmp codesel:pmnow
bits 32
pmnow:
mov ax, cs
mov ds, ax
mov ax, datasel
mov es, ax
mov byte [es:0xb8000], 'p'
jmp $
gdtr :
dw gdtend - gdt - 1 ; gdt的长度
dd gdt ; gdt的物理地址
gdt:
gdt0:
dw 0,0,0,0 ; 据说是一定要为0否则会有错
codesel_gdt:
dw 0xffff ; 界限Limit值 = 0x100000 * 0x1000 = 4GB
dw 0 ; 基地址 = 0
dw 0x9A00 ; 表示代码段可读可执行
dw 0x00CF ; 粒度(不知道是什么意思)
datasel_gdt:
dw 0xffff ; 4GB
dw 0x0 ; 基地址
dw 0x9200 ; 数据段可读可写
dw 0x00CF ; 粒度
gdtend:
codesel equ codesel_gdt - gdt
datasel equ datasel_gdt - gdt
times 510-($-$$) db 0
dw 0x0aa55
试一下,启动后屏幕上出现了一个白色的
P
字,不相信的话可以把
lgdt
…
mov cr0,eax
删除再试一下,应该什么反映都没有了。完了之后我们就可以在
loader
或者
kernel
里面加上进入保护模式的代码了,以后才能用
C
来做工作。