cr0,cr1,cr2,cr3
控制寄存器(CR0~CR3)用于控制和确定处理器的操作模式以及当前执行任务的特性,如图4-3所示。CR0中含有控制处理器操作模式和状态的系统控制标志;CR1保留不用;CR2含有导致页错误的线性地址;CR3中含有页目录表物理内存基地址,因此该寄存器也被称为页目录基地址寄存器PDBR(Page-Directory Base address Register)。
(点击查看大图)图4-3 控制寄存器CR0~CR3 |
1.CR0中协处理器控制位
CR0的4个位:扩展类型位ET、任务切换位TS、仿真位EM和数学存在位MP用于控制80x86浮点(数学)协处理器的操作。有关协处理器的详细说明请参见第11章内容。CR0的ET位(标志)用于选择与协处理器进行通信所使用的协议,即指明系统中使用的是80387还是80287协处理器。TS、MP和EM位用于确定浮点指令或WAIT指令是否应该产生一个设备不存在(Device Not Available,DNA)异常。这个异常可用来仅为使用浮点运算的任务保存和恢复浮点寄存器。对于没有使用浮点运算的任务,这样做可以加快它们之间的切换操作。
(1)ET:CR0的位4是扩展类型(Extension Type)标志。当该标志为1时,表示指明系统中有80387协处理器,并使用32位协处理器协议。ET=0指明使用80287协处理器。如果仿真位EM=1,则该位将被忽略。在处理器复位操作时,ET位会被初始化指明系统中使用的协处理器类型。如果系统中有80387,则ET被设置成1,否则若有一个80287或者没有协处理器,则ET被设置成0。
(2)TS:CR0的位3是任务已切换(Task Switched)标志。该标志用于推迟保存任务切换时的协处理器内容,直到新任务开始实际执行协处理器指令。处理器在每次任务切换时都会设置该标志,并且在执行协处理器指令时测试该标志。
如果设置了TS标志并且CR0的EM标志为0,那么在执行任何协处理器指令之前会产生一个设备不存在异常。如果设置了TS标志但没有设置CR0的MP和EM标志,那么在执行协处理器指令WAIT/FWAIT之前不会产生设备不存在异常。如果设置了EM标志,那么TS标志对协处理器指令的执行无影响,见表4-1。
表4-1 CR0中标志EM、MP和TS的不同组合对协处理器指令动作的影响
CR0中的标志 |
指令类型 |
|||
EM |
MP |
TS |
浮点 |
WAIT/FWAIT |
0 |
0 |
0 |
执行 |
执行 |
0 |
0 |
1 |
设备不存在(DNA)异常 |
执行 |
0 |
1 |
0 |
执行 |
执行 |
0 |
1 |
1 |
DNA异常 |
DNA异常 |
1 |
0 |
0 |
DNA异常 |
执行 |
1 |
0 |
1 |
DNA异常 |
执行 |
1 |
1 |
0 |
DNA异常 |
执行 |
1 |
1 |
1 |
DNA异常 |
DNA异常 |
在任务切换时,处理器并不自动保存协处理器的上下文,而是会设置TS标志。这个标志会使得处理器在执行新任务指令流的任何时候遇到一条协处理器指令时产生设备不存在异常。设备不存在异常的处理程序可使用CLTS指令清除TS标志,并且保存协处理器的上下文。如果任务从没有使用过协处理器,那么相应协处理器上下文就不用保存。
(3)EM:CR0的位2是仿真(EMulation)标志。当该位设置时,表示处理器没有内部或外部协处理器,执行协处理器指令时会引起设备不存在异常;当清除时,表示系统有协处理器。设置这个标志可以迫使所有浮点指令使用软件来模拟。
(4)MP:CR0的位1是监控协处理器(Monitor coProcessor或Math Present)标志。用于控制WAIT/FWAIT指令与TS标志的交互作用。如果MP=1、TS=1,那么执行WAIT指令将产生一个设备不存在异常;如果MP=0,则TS标志不会影响WAIT的执行。
2.CR0中保护控制位
(1)PE:CR0的位0是启用保护(Protection Enable)标志。当设置该位时即开启了保护模式;当复位时即进入实地址模式。这个标志仅开启段级保护,而并没有启用分页机制。若要启用分页机制,那么PE和PG标志都要置位。
(2)PG:CR0的位31是分页(Paging)标志。当设置该位时即开启了分页机制;当复位时则禁止分页机制,此时所有线性地址等同于物理地址。在开启这个标志之前必须已经或者同时开启PE标志。即若要启用分页机制,那么PE和PG标志都要置位。
(3)WP:对于Intel 80486或以上的CPU,CR0的位16是写保护(Write Proctect)标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作;当该位复位时则反之。该标志有利于UNIX类操作系统在创建进程时实现写时复制(Copy on Write)技术。
(4)NE:对于Intel 80486或以上的CPU,CR0的位5是协处理器错误(Numeric Error)标志。当设置该标志时,就启用了x87协处理器错误的内部报告机制;若复位该标志,那么就使用PC形式的x87协处理器错误报告机制。当NE为复位状态并且CPU的IGNNE输入引脚有信号时,那么数学协处理器x87错误将被忽略。当NE为复位状态并且CPU的IGNNE输入引脚无信号时,那么非屏蔽的数学协处理器x87错误将导致处理器通过FERR引脚在外部产生一个中断,并且在执行下一个等待形式浮点指令或WAIT/FWAIT指令之前立刻停止指令执行。CPU的FERR引脚用于仿真外部协处理器80387的ERROR引脚,因此通常连接到中断控制器输入请求引脚上。NE标志、IGNNE引脚和FERR引脚用于利用外部逻辑来实现PC形式的外部错误报告机制。
启用保护模式PE(Protected Enable)位(位0)和开启分页PG(Paging)位(位31)分别用于控制分段和分页机制。PE用于控制分段机制。如果PE=1,处理器就工作在开启分段机制环境下,即运行在保护模式下。如果PE=0,则处理器关闭了分段机制,并如同8086工作于实地址模式下。PG用于控制分页机制。如果PG=1,则开启了分页机制。如果PG=0,分页机制被禁止,此时线性地址被直接作为物理地址使用。
如果PE=0、PG=0,处理器工作在实地址模式下;如果PG=0、PE=1,处理器工作在没有开启分页机制的保护模式下;如果PG=1、PE=0,此时由于不在保护模式下不能启用分页机制,因此处理器会产生一个一般保护异常,即这种标志组合无效;如果PG=1、PE=1,则处理器工作在开启了分页机制的保护模式下。
当改变PE和PG位时,必须小心。只有当执行程序至少有部分代码和数据在线性地址空间和物理地址空间中具有相同地址时,我们才能改变PG位的设置。此时这部分具有相同地址的代码在分页和未分页世界之间起着桥梁的作用。无论是否开启分页机制,这部分代码都具有相同的地址。另外,在开启分页(PG=1)之前必须先刷新页高速缓冲TLB。
在修改该了PE位之后程序必须立刻使用一条跳转指令,以刷新处理器执行管道中已经获取的不同模式下的任何指令。在设置PE位之前,程序必须初始化几个系统段和控制寄存器。在系统刚上电时,处理器被复位成PE=0和PG=0(即实模式状态),以允许引导代码在启用分段和分页机制之前能够初始化这些寄存器和数据结构。
3.CR2和CR3
CR2和CR3用于分页机制。CR3含有存放页目录表页面的物理地址,因此CR3也被称为PDBR。因为页目录表页面是页对齐的,所以该寄存器只有高20位是有效的。而低12位保留供更高级处理器使用,因此在往CR3中加载一个新值时低12位必须设置为0。
使用MOV指令加载CR3时具有让页高速缓冲无效的副作用。为了减少地址转换所要求的总线周期数量,最近访问的页目录和页表会被存放在处理器的页高速缓冲器件中,该缓冲器件被称为转换查找缓冲区(Translation Lookaside Buffer,TLB)。只有当TLB中不包含要求的页表项时才会使用额外的总线周期从内存中读取页表项。
即使CR0中的PG位处于复位状态(PG=0),我们也能先加载CR3。以允许对分页机制进行初始化。当切换任务时,CR3的内容也会随之改变。但是如果新任务的CR3值与原任务的一样,处理器就无需刷新页高速缓冲。这样共享页表的任务可以执行得更快。
CR2用于出现页异常时报告出错信息。在报告页异常时,处理器会把引起异常的线性地址存放在CR2中。因此操作系统中的页异常处理程序可以通过检查CR2的内容来确定线性地址空间中哪一个页面引发了异常。
特权级转移总结(转)
特权级转移比较复杂,但可以归纳为两大类.
1.对于代码段,只能从低到高访问.
2.对于数据段,只能从高到低访问.
然后再分解:
代码段从低向高(一致,目标特权级转换为访问者特权级)或相同(非一致).
数据段总是非一致.
描述符本身是数据段.比如调用门,TTS本身都是数据段,所以必须从高特权级
向低特权级访问,即访问者特权级<=门,TTS的DPL.(值小于等于表示特权级高).
而通过门来访问代码段,无论是一致还是非一致代码段,原则上是从低向高访问.
在非一致代码段上,只有jmp指令要求访问者特权级和通过门访问的目标代码段相等,
其它的都可以从低向高访问.
所以要通过门调用条件是:1.先要有权访问门本身(从高到低),然后就可以从低到高.
最后加上RPL.当RPL比CPL低时,RPL覆盖CPL.这样即使访问者的段有足够的特权级但RPL
不够时也访问不了高特权级的数据.防止低特权级构造高特权级的选择子来访问高特权
级的数据(当前RPL会被放置到选择子的RPL中).
如果目标代码段特权级低,则无法通过call和jmp转移进去.只能通过retf指令跳转.
为什么通过retf可以降低特权级呢?
既然call,jmp是从调用者的低特权级向被调用者的高特权级访问的(非一致代码段相等),
那么在ret/retf时,是从被调用者的高特权级向调用者的低特权级返回,所以处理器的判断
逻辑是要返回目标代码(调用者)的RPL,DPL都应该比当前代码段(被调用者)低.当调用者
的代码段的RPL比当前的CPL低时,就应该切换到更低的RPL特权级运行.
当然原来调用者的cs,eip都是处理器在调用之前压栈保存的,但是如果当前代码段在
高特级下,就可以访问到要跳转的低特权级选择子和描述符,堆栈段,那么我们就可以把
要进入的低特权级ss,esp,cs,eip象从它调用高特权级之前被CPU保存那样手工构造出来.
然后调用retf来模拟从高特权级的被调用环境中返回调用者低特权级环境中,从而实现从
高特权级向低特权级的跳转.
这里的跳转一定要使用retf,因为ret虽然可以实现长跳转,但它不返回cs,而切换特权级
就是根据cs的RPL决定的.
ret:
(ip)=((ss)*16+(sp))
(sp)=(sp)+2
->pop ip
retf:
(ip)=((ss)*16+(sp))
(sp)=(sp)+2
(cs)=((ss)*16+(sp))
(sp)=(sp)+2
->pop ip
->pop cs
不同特权级的代码段使用不同的堆栈段,为了防止低特级权的代码段大量使用高特权级的
栈而使内核崩溃,所以在不同特权级之间切换要进行栈转移,TTS就是为了从低到高访问时
指示目标代码段的ss和esp,同时将原来的ss和esp压入新栈中以便返回时调用.
直接跳转(jmp,call)对一致代码段只能从低到高,而且CPL不能改变,非一致代码段只能在
相同的特权级之间.
使用调用门在一致和非一致代码段都也可以实现自由跳转.
堆排序
一个简单的堆排序,搞了半天,汗~~
/* * ===================================================================================== * * Filename: heap_sort.c * * Description: 堆排序 * * Version: 1.0 * Created: 2011年09月28日 20时09分46秒 CST * Revision: none * Compiler: gcc * * Author: nestor * Company: * * ===================================================================================== */ #include "stdio.h" #define length 10 int heap_size; void max_heappify(int a[],int i); void build_max_heap(int a[]); void heap_sort(int a[]); int left(int i); int right(int i); int main(void) { int a[length+1]={0,45,324,4,54,324,4,54,65,67,43},i; printf("please input 10 numbers to sort\n"); // for(i=1;i<length+1;i++) // scanf("%d",&a[i]); heap_sort(a); for(i=1;i<length+1;i++) { printf("%d ",a[i]); } printf("\n"); return 0; } void max_heappify(int a[],int i) { int l, r, largest, temp; l=left(i); r=right(i); if(l<=heap_size && a[l]>a[i]) { largest=l; } else { largest=i; } if(r<=heap_size && a[r]>a[largest]) { largest=r; } if(largest!=i) { temp=a[i]; a[i]=a[largest]; a[largest]=temp; max_heappify(a, largest); } } void build_max_heap(int a[]) { int i; heap_size=length; for(i=heap_size/2;i>=1;i--) { max_heappify(a, i); } } void heap_sort(int a[]) { int i,temp; build_max_heap(a); for(i=length;i>=1;i--) { temp=a[i]; a[i]=a[1]; a[1]=temp; heap_size--; max_heappify(a,1); } } int left(int i) { return 2*i; } int right(int i) { return 2*i+1; }
“__stack_chk_fail”错误
ld -s -Ttext 0x30400 -o kernel.bin kernel/kernel.o kernel/start.o kernel/i8259.o kernel/global.o kernel/protect.o lib/klib.o lib/kliba.o lib/string.o lib/klib.o: In function `disp_int': klib.c:(.text+0xe5): undefined reference to `__stack_chk_fail' make: *** [kernel.bin] 错误 1
CFLAGS = -I include/ -c -fno-builtin -fno-stack-protector
2.在Makefile的cflags后加入-nostdlib
CFLAGS = -I include/ -c -fno-builtin -nostdlib
但是!%@……#&%*¥&%……#%@依旧错误....-_-#
问题不再Makefile,在于修改后,没有rm klib.o,make命令没有执行链接,所以还是出错!@#¥%%Z……&下面显示成功
问题解释的地址:http://forum.osdev.org/viewtopic.php?f=1&t=19434
nestor@nestor-Ideapad-Z460:~/oranges/chapter5/i$ rm -R -f *.o nestor@nestor-Ideapad-Z460:~/oranges/chapter5/i$ make nasm -I include/ -f elf -o kernel/kernel.o kernel/kernel.asm gcc -I include/ -c -fno-stack-protector -fno-builtin -o kernel/start.o kernel/start.c gcc -I include/ -c -fno-stack-protector -fno-builtin -o kernel/i8259.o kernel/i8259.c gcc -I include/ -c -fno-stack-protector -fno-builtin -o kernel/global.o kernel/global.c gcc -I include/ -c -fno-stack-protector -fno-builtin -o kernel/protect.o kernel/protect.c ld -s -Ttext 0x30400 -o kernel.bin kernel/kernel.o kernel/start.o kernel/i8259.o kernel/global.o kernel/protect.o lib/klib.o lib/kliba.o lib/string.o
段选择子与描述符的结构及其含义(一)(转)
段描述符:代码段描述符、数据段描述符、系统描述符
系统描述符:系统段描述符、门描述符
部分段限长、基地址被存放在2个或3个位置,将由CPU按照值的高位存放在内存高地址处的规则进行整合。
1.
15...3 |
2 |
1...0 |
索引值 |
TI |
RPL |
(1)
(2)
(3)
2.
31...24 |
23 |
22 |
21 |
20 |
19...16 |
15 |
14...13 |
12 |
11...8 |
7...0 |
基地址 |
G |
D/B |
保留位 |
AVL |
段限长 |
P |
DPL |
S |
TYPE |
基地址 |
31...16 |
15...0 |
基地址 |
段限长 |
3.
31...24 |
23 |
22 |
21 |
20 |
19...16 |
15 |
14...13 |
12 |
11...8 |
7...0 |
|||
基地址 |
G |
B |
0 |
AVL |
段限长 |
P |
DPL |
1 |
0 |
E |
W |
A |
基地址 |
31...16 |
15...0 |
基地址 |
段限长 |
4.
31...24 |
23 |
22 |
21 |
20 |
19...16 |
15 |
14...13 |
12 |
11...8 |
7...0 |
|||
基地址 |
G |
D |
0 |
AVL |
段限长 |
P |
DPL |
1 |
1 |
C |
R |
A |
基地址 |
31...16 |
15...0 |
基地址 |
段限长 |
5.
31...24 |
23 |
22 |
21 |
20 |
19...16 |
15 |
14...13 |
12 |
11...8 |
7...0 |
基地址 |
G |
|
0 |
|
段限长 |
P |
DPL |
0 |
TYPE |
基地址 |
31...16 |
15...0 |
基地址 |
段限长 |
6.
31...24 |
23 |
22 |
21 |
20 |
19...16 |
15 |
14...13 |
12 |
11...8 |
7...0 |
|||
基地址 |
G |
0 |
0(保留) |
AVL |
段限长 |
P |
DPL |
0 |
1 |
0 |
B |
1 |
基地址 |
31...16 |
15...0 |
基地址 |
段限长 |
7.
31...16 |
15 |
14...13 |
12 |
11...8 |
7...5 |
4...0 |
段中偏移值 |
P |
DPL |
0(S) |
1100(TYPE) |
000 |
参数个数 |
31...16 |
15...0 |
段选择符 |
段中偏移值 |
8.
31...16 |
15 |
14...13 |
12 |
11...8 |
7...5 |
4...0 |
过程入口点偏移值 |
P |
DPL |
|
(TYPE) |
|
|
31...16 |
15...0 |
段选择符 |
过程入口点偏移值 |
9.
31...16 |
15 |
14...13 |
12 |
11...8 |
7...5 |
4...0 |
过程入口点偏移值 |
P |
DPL |
0(S) |
1111(TYPE) |
000 |
|
10.
31...16 |
15 |
14...13 |
12 |
11...8 |
7...0 |
|
P |
DPL |
|
|
|
31...16 |
15...0 |
段选择符 |
|
由于发布的文章长度有限制,将各位的含义放入(二)中: