随想录(386cpu保护模式)

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】

 

    写过操作系统的同学都知道,编写os除了基础的操作系统理论之外,最大的工作就是需要阅读cpu手册。注意,这里提到的是cpu手册,不是soc手册。比如说,s3c2416是三星的芯片,大家拿到的一般是这款芯片的soc手册,但是如果需要查找arm的相关信息,还是应该去arm的官网看arm7、arm9的相关手册。学习linux也是一样,最早期的os都是在386完成的,所以对于pc底层开发的人员来说,386怎么学习都不过。

 

1、386工作模式

386有两种工作模式,一种是实模式,访问地址空间只有1M;另外一种是保护模式,访问空间有4G。

 

2、访问地址的方法有什么不同

实模式下,地址就是段寄存器 << 4 + 偏移地址

保护模式下,地址就是GDT+ 段寄存器基地址 +  Page分页 + MMU表

一般GDT表中的前四个描述符write后不能修改

 

3、GDT、LDT、IDT是什么

GDT是一张表,每个选项8个字节,一般用lgdt载入,输入为一个基地址,它存储了GDT表的基地址和长度,共48位

LDT就是GDT里面的一个选项,一般用lldt载入,输入为一个段寄存器索引

IDT也是一张表,每个表也是8个字节,一般用lidt载入,输入为一个基地址,它存储了IDT表的基地址和长度,也是48位

注意:如果是实模式,那么中断向量表是在0x0的位置

void init()
{
	unsigned char gdt_base[6];
	unsigned char idt_base[6];

	__asm {
		lgdt gdt_base
		lidt idt_base
		mov ax, 0x10
		lldt ax
		sgdt gdt_base
		sidt idt_base
	}
}

 

4、段寄存器索引

在保护模式下,cs、ds、ss、es、fs、gs退化成一个索引。但是由于索引低3位保存了属性,所以如果需要检查段描述符的数值,可以直接GDT基地址 + 段寄存器来完成。同时,段寄存器也记录了当前访问的是LDT,还是GDT。

 

5、段寄存器的修改

cs外的寄存器都可以通过类似mov es/gs/ds/fs/ss ax这种方式来赋值

而cs寄存器只能通过jmp来完成,比如可以用es赋值给cs,start赋值给ip

	__asm {
	start:
		jmp es:start
	}

 

6、其他寄存器

386下面有dr0、dr1、dr2、dr3、dr4等调试寄存器,对于理解软件调试很有帮助

除此之外,还有mmx、sse等多媒体指令,主要用于性能加速

	__asm {
		mov eax, dr0
		mov eax, dr1
		mov eax, dr2
		mov eax, dr3
	}

 

7、CALL门

作为GDT表的一个段描述符存入,用段寄存器访问,但是里面的属性、内容不同

CALL门会使用到两个段描述符,这是稍微一点不同的地方

CALL门和jump使用广泛,不同的优先级别会导致不同的压栈方法,需要注意下

这种门描述符,一般不涉及具体的地址,它需要和一个段选择符绑定在一起。

 

8、TSS和任务门

用一块内存记录TSS下的寄存器布局内容(全局变量就可以),接着用一个GDT段描述符记录这块空间,最后用ltr指令 + 段寄存器进行加载。TSS和多任务绑定在一起,任务的切换,会导致GDT/LDT和TR寄存器的更新。一般GDT负责整个系统的布局,LDT负责线程空间的分布,而TR就是每个线程的上下文。TSS表示一段空间保存寄存器数据,而任务门表示一个任务状态。每个任务可以都有一个属于自己的TSS。

	__asm {
		mov eax, 0x10
		ltr eax
	}

jump到LDT中的任务门或者GDT中的某个TSS都会产生上下文切换。TSS中的地址也会保存在TCB信息头当中。所有的操作都是硬件帮忙完成的,软件做的其实就是切换了一下tr寄存器。

 

9、CPL、RPL、DPL

CPL表示当前段寄存器的优先级,一般特指cs中的优先级

RPL表示拟使用的优先级,表示cs外其他段寄存器的优先级

DPL表示段描述符的优先级,表示GDT表中描述符的优先级

 

10、如何进入保护模式

开启A20 + cr0 pe位置起

 

11、如何分页

开启cr0 pg位 + 设置cr3寄存器,即MMU的目录首地址

在此之前,应该准备好表目录和页目录,可以用GDT的描述符生成,一个段描述符对应一个目录

初始化好就可以

MMU里面的表目录、页目录属性特别多,用到的时候学就可以了。

保护模式默认是分段模式,分页模式开启后,cpu会再做一次MMU的映射工作,这就是它的基本原理。

	__asm {
		mov eax, cr0
		mov cr0, eax
		mov eax, cr3
		mov cr3, eax
	}

 

12、如何eflags置位

	__asm {
		pushfd
		pop eax
		or eax,0x1
		push eax
		popfd
	}

 

13、输入输出

in、out指令

	__asm {
		mov dx, 0x1234
		in ax, dx
		out dx, ax
	}

 

14、系统调用

int 15/ int 21等等,通过系统调用可以让cpu从用户模式跳入内核模式

	__asm {
		int 10
		int 15
		int 21
	}

15、tlb和cache问题

查找对应汇编指令解决,参考IA32 Architecture Developer‘s Manual II

还有一种方法就是参考linux 386 cpu抑制部分的代码

 

16、参考资料有哪些

IA32 Architecture Developer‘s Manual I,II, III

<自己动手写操作系统>中第二章

<linux 0.11完全解析>

<X86 汇编语言 从实模式到保护模式>

 

17、主要的分析工具

qemu + gcc + binutils 等工具

 

PS:

    从上面的描述也知道,数据空间 + 段描述符 + 段寄存器是保护模式学习的重中之重。理解了这个,其他概念都可以迎刃而解。当然,有了概念的理解还是不够的,最好利用qemu实践一下,或者找一些开源代码,比如ucore,实际操作一把,效果更好。

 

    对于操作系统来说,OS不一定会使用到全部cpu特性。即使像linux这样大的系统,他们使用到的也只是一个通用的cpu特性,只要满足指令集、寄存器、输入输出、异常、中断、MMU、cache、多 core通信这些基本功能就行了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章