Win32基础知识5
让编程改变世界
Change the world by program
Windows的内存安排
这节课我们需要理解三个概念:
每个应用程序都有自己的4 GB的寻址空间,就算这个程序只暂 1KB的内存;
不同应用程序的线性地址空间是隔离的,尽管他们在内存中是搞在一起;
时刻要记住,DLL程序是“小三”,因此它们没有自己“私有”的空间。
本节课我们将图文并茂地来进行原理层面的分析!
虚拟内存安排:
Windows 系统一般在硬盘上建立大小为物理内存两倍左右的交换文件用作虚拟内存。
利用 80386处理器的内存分页机制,交换文件的寻址上可以很方便地作为物理内存使用。
(只需要在真正调用的时候将其读入物理内存并同时修改线性地址映射到这块内存即可)。
同样道理,反正是映射一个地址而已,所以被执行的程序也可以不必装入内存,只需要在页表中建立映射关系,真正运行到这段代码才调入内存。
众所周知,Windows 是一个分时的多任务操作系统,CPU时间(就是CPU运行的过程)被分成一个个的时间片后分配给不同程序轮流使用。
在A程序的时间片中,和这个程序执行无关的部分(B和C等其他程序的代码和数据)并不需要映射到线性地址中。
附加解析:
内存中,所有的程序都搞在一起,关系十分混乱;
CPU只能看到线性地址(假的),每个程序拥有自己的线性地址(小三除外)。
总结:WIN32编程中几个很重要的概念
第一点:
每个应用程序都有自己的4 GB的寻址空间。
该空间可存放操作系统、系统DLL和用户DLL的代码,它们之中有各种函数供应用程序调用。
再除去其他的一些空间,余下的是应用程序的代码、数据和可以分配的地址空间。
第二点:
不同应用程序的线性地址空间是隔离的。
虽然它们在物理内存中同时存在,但在某个程序所属的时间片中,其他应用程序的代码和数据没有被映射到可寻址的线性地址中,所以是不可访问的。
从编程的角度看,程序可以使用4 GB的寻址空间,而且这个空间是“私有”的。
第三点:
DLL程序没有自己“私有”的空间。
它们总是被映射到其他应用程序的地址空间中,当做其他应用程序的一部分运行。
原因很简单,如果它不和其他程序同属一个地址空间,应用程序该如何调用它呢?
从WIN32汇编的角度看内存寻址
如果坚持不到这句话出现的同学就因为前边Windows原理太复杂而放弃的童鞋,小甲鱼觉得很可惜……
Win32汇编中的内存访问远比DOS下的分段寻址方式简单,这是为什么呢?
因为Windows是一个多任务的操作系统,最首要的宗旨就是“稳定压倒一切”。
如果把描述符表以及页表等内容交给用户程序管理是很不安全的。
任何权限上开放引发的安全问题都是很严重的,如Windows 9x中的中断描述符表是可写的,CIH病毒可利用它将自己的权限提高到优先级0;
而Windows NT下的中断描述符表是不可写的,CIH病毒在Windows NT下就无法使用同样的方法进驻内存。
关于CIH病毒可以到鱼C论坛“资源分享”版块下载^_^
正因为如此,Windows操作系统干脆为用户程序“安排好了一切”。
具体表现在为用户程序的代码段、数据段和堆栈段全部预定义好了段描述符。这些段的起始地址为0,限长为ffffffff,所以用它们可以直接寻址全部的4 GB地址空间。
程序开始执行的时候,CS,DS,ES和SS都已经指向了正确的描述符,在整个程序的生命周期内,程序员不必改动这些段寄存器,也不必关心它们的值究竟是多少(实际上是想改也改不了)。
所以对Win32汇编程序来说,整个源程序中竟然可以不用出现段寄存器的身影。
这在DOS汇编编程中是不可想像的。
回顾之前提出的问题:
“为什么在WIN32汇编源代码中看不到CS,DS,ES,SS等段寄存器的使用?”
答案是:并不是Win32汇编源代码用不到段寄存器,而是用户在使用中不必去关心段寄存器!
Win32基础知识6
让编程改变世界
Change the world by program
Windows的特权保护
Windows 的特权保护和处理器硬件的支持是分不开的。
优先级的划分、指令的权限检查和超出权限访问的异常处理等是构成特权保护的基础。
这一讲我们将试图讲过讲解为大家解决两大问题:
Win32汇编中为什么找不到中断指令的应用?
Windows错误的“蓝屏”是从哪里来的?
小甲鱼解释中断和异常
鱼C故事会:
假设某一天你正在兴致勃勃、兴高采烈地看一部爱情动作片,但是突然妈妈在外边猛敲你的房门,因为她发觉家里的酱油没了……
这时候没办法,还是老妈的命令重要,因此,我们暂停了视频,然后去打酱油……
打完酱油回来,你又接着往下看,意兴珊阑处,停电了……
过了N久,好不容易来电了,一开机,它蓝屏了!
官方解释 — 什么是中断
中断指当程序执行过程中有更重要的事情需要实时处理时(如串口中有数据到达,不及时处理数据会丢失,串行控制器就提交一个中断信号给处理器要求处理),硬件通过中断控制器通知处理器。
接到命令后,处理器暂时挂起当前运行的程序,转移到中断处理程序中。
当中断处理程序处理完毕后,通过iret指令回到原先被打断的程序中继续执行。
官方解释 — 什么是异常
异常指指令执行中发生不可忽略的错误时(如遇到无效的指令编码,除法指令除零等),处理器用和中断处理相同的操作方法挂起当前运行的程序转移到异常处理程序中。
异常处理程序决定在修正错误后是否回到原来的地方继续执行。
注意:中断和异常处理的方式是相同的!!
实模式下的中断或异常处理:
实模式下的中断和异常服务程序地址存放在中断向量表中。
中断向量表位于物理内存中,每个中断向量是一个xxxx:yyyy格式的地址,占用4字节。
当发生n号异常或n号中断,或者执行到int n指令的时候,CPU首先到内存n×4的地方取出服务程序的地址aaaa:bbbb
然后将标志寄存器、中断时的CS和IP压入堆栈,接着转移到aaaa:bbbb处执行
保护模式下的中断或异常处理
保护模式下,中断或异常处理往往从用户代码切换到操作系统代码中执行。
由于保护模式下的代码有优先级之分,因此出现了从优先级低的应用程序转移到优先级高的系统代码中的问题。
如果优先级低的代码能够任意调用优先级高的代码,就相当于拥有了高优先级代码的权限。
为了使高优先级的代码能够安全地被低优先级的代码调用,保护模式下增加了“门”的概念。
“门”指向某个优先级高的程序所规定的入口点,所有优先级低的程序调用优先级高的程序只能通过门重定向,进入门所规定的入口点。
这样可以避免低级别的程序代码从任意位置进入优先级高的程序的问题。
保护模式下的中断和异常等服务程序也要从“门”进入,80386的门分为中断门、自陷门和任务门几种。
保护模式下中断或异常示意图
保护模式下把所有的中断描述符放在一起组成”中断描述符表IDT”。
为此80386处理器引入了一个新的48位寄存器IDTR。
IDTR的高32位指定了IDT在内存中的基址(线性地址),低16位指定了IDT的长度,相当于指定了可以支持的中断数量。
保护模式下发生异常或中断时,处理器先根据IDTR寄存器得到中断描述符的地址,然后取出n号中断/异常的门描述符,再从描述符中得到中断服务程序的地址xxxx:yyyyyyyy,经过段地址转换后得到服务程序的32位线性地址并转移后执行。
在Windows中,操作系统使用动态链接库来代替中断服务程序提供系统功能,所以 Win32汇编中int指令也就失去了存在的意义。
这就是在Win32汇编源代码中看不到int指令的原因。
其实那些调用API的指令原本是用int指令实现的。
80386的保护机制
80286之前的处理器只支持单任务,操作系统并没有什么安全性可言,计算机的全部资源包括操作系统的内部资源都可以任凭程序员调用。
但对于多任务的操作系统,某个想捣乱的程序为所欲为令使所有程序都无法运行。
所以80286及以上的处理器引入了优先级的概念。80386处理器共设置4个优先级(0~3):
0级是最高级(特权级)3级是最低级(用户级)
保护机制主要由下列几方面组成
段的类型检查
段的类型是由段描述符指定的,主要属性有是否可执行,是否可读和是否可写等。
CS,DS和SS等段选择器是否能装入某种类型的段描述符是有限制的。
如不可执行的段不能装入CS;不可读的段不能装入DS与ES等数据段寄存器;不可写的段不能装入SS等。
如果段类型检查通不过,则处理器会产生一般性保护异常或堆栈异常。
页的类型检查
除了可以在段级别上指定整个段是否可读写外,在页表中也可以为每个页指定是否可写。
对于特权级下的执行代码,所有的页都是可写的。
但对于1,2和3级的代码,还要根据页表中的R/W项决定是否可写,企图对只读的页进行写操作会产生页异常。
访问数据时的级别检查
优先级低的代码不能访问优先级高的数据段。80386的段描述符中有一个DPL域(描述符优先级),表示这个段可以被访问的最低优先级。
而段选择器中含有RPL域(请求优先级),表示当前执行代码的优先级。
只有DPL在数值上大于或等于RPL值的时候,该段才是可以访问的,否则会产生一般性保护异常。
控制转移的检查
在处理器中,有很多指令可以实现控制转移,如jmp,call,ret,int和iret等指令。
但优先级低的代码不能随意转移到优先级高的代码中,所以遇到这些指令的时候,处理器要检查转移的目的位置是否合法。
指令集的检查
有两类指令可以影响保护机制。
第一类是改变GDT,LDT,IDT以及控制寄存器等关键寄存器的指令,称为特权指令
第二类是操作I/O端口的指令以及cli和sti等改变中断允许的指令,称为敏感指令。
特权指令只能在优先级0上才能运行,而敏感指令取决于eflags寄存器中的IOPL位。
只有IOPL位表示的优先级高于等于当前代码段的优先级时,指令才能执行。
I/O操作的保护
I/O地址也是受保护的对象。因为通过I/O操作可以绕过系统对很多硬件进行控制。
80386可以单独为I/O空间提供保护,每个任务有个TSS(任务状态段)来记录任务切换的信息。
TSS中有个I/O允许位图,用来表示对应的I/O端口是否可以操作。
Windows的保护机制
以上是Windows规定的“保护条例”,如果某个程序违反了,那么会引发保护异常,处理器会毫不犹豫地把控制权转移到对应的异常处理程序中去。
Windows会在处理程序中用一个很酷的“非法操作”对话框把用户的程序判死刑,没有一点回旋的余地!
经常时候系统会用一个“蓝屏”来通知用户程序试图访问不存在的内存页。
小结
至此,本章节的讲解到终于到一段落。
小甲鱼此时此刻的心情出了哀怨作者外剩下的就是释然。
因为本章节可以说是全书最难懂、最让人抓狂的一章节,相信从第二章开始,我们的学习会变得格外轻松和愉快!
加油!!鱼C教学 — 让编程学习充满欢乐!