WIN32程序设计-必须了解的基础知识(1)
大纲:
(1)80x86处理器的互作模式
1 | -实模式 |
(2)Windows的内存管理机制
1 | -Dos操作系统的内存安排情况 |
(3)Windows的特权保护机制
1 | -80386的中断和异常 |
80x86处理器的互作模式
- 80386以后的处理器有3种互作模式
1 | -实模式 |
- 其实,实模式和虚拟8086模式都是为了向下兼容而设置的,保护模式才是我们的主角,是我们现代系统实际上的互作模式
- 只有在保护模式下,32位CPU的寻址才能达到4GB的地址空间,同时,也能实现多任务,内存分页管理和优先级保护等先进的机制
补:Windows 7的启动过程
1.开启电源
1 | 计算机系统将进行加电自检[POST].如果通过之后BIOS会读取主引导记录[MBR]---被标记位启动设备的硬盘的首扇区,并传送被Windows 7 建立的控制编码给MBR |
- 引导型病毒
1 | '鬼影病毒' |
- MBR
1 | 是一个存储区,位于硬盘的第一个扇区. |
- 注意:
1 | 硬盘的0磁通的第一个扇区称为MBR,它的大小是512字节,而这个区域可以分为2个部分.第一个部分是pre-boot[预启动区],占446字节,第二个部分是Partition table区[分区表],占64个字节,该区相当于一个小程序,作用是判断哪个分区被标记为活动分区,然后去读取那个分区的启动区,并运行该分区中的代码. |
2.启动菜单生成
1 | Windows启动管理器读取"启动配置数据存储[Boot Confi guration Data store]"中的信息.此信息包含已被安装在计算机上的所有操作系统的配置信息,并且用以生成启动菜单 |
当你启动菜单中选择下列动作的时候
- 如果你选择的是:Windows 7
1 | Windows启动管理器[Windows Boot Manager]运的行%SystemRoot%\System32文件夹中的OS loader---Winload.exe |
- 如果你选择的是:自休眠状态恢复Windows 7
1 | 那么启动管理器将装载Winresume.exe并恢复您先前的使用环境 |
- 如果你选择的是:早期Windows版本
1 | 启动管理器将定位系统安装所在的卷,并且加载Windows NT风格的早期OS loader(Ntldr.exe)---生成一个由boot.ini内容决定的启动菜单 |
3.核心文件加载和登录
- windows7启动时,加载其核心文件Ntoskrnl.exe和hal.dll—从注册表中读取设置并加载驱动程序
- 接下来将运行Windows会话管理器(smss.exe)并且启动Windows启动程序(Wininit.exe)本地安全验证(Lsass.exe)和服务(services.exe)进程,完成后就可以登录系统了
1.实模式
处理器被复位或者加电的时候以实模式启动
这时候处理器中各寄存器以实模式的初始化值互作
80386处理器在实模式下的存储器寻址方式和8086是一样的,由段寄存器的内容乘以16当作基地址,加上段内的偏移地址形成最终的物理地址,这时候它的32位地址只用了低20位
在实模式下,80386处理器不能对内存进行分页管理,所以指令寻址的地址就是内存中实际的物理地址,在实模式下,所有的段都是可以读.写和执行的
实模式下80386不支持优先级,所有的指令相当于互作在特权级,所以它可以执行所有特权指令,包括读写控制寄存器CR0等
实际上,80386就是通过在实模式下初始化控制寄存器,GDTR . LDTR . IDTR和TR等管理寄存器以及页表,然后再通过加载CR0使其中的保护模式使能位置位而进入保护模式的。当然实模式下不支持硬件上的多任务切换
实模式下的中断处理方式和8086处理器相同,也用中断向量表来定位中断服务程序地址
中断向量表的结构也和8086处理器一样:每4个字节组成一个中断向量,其中包括2个字节的段地址和2个字节的偏移地址
从编程的角度看,除了可以访问80386新增的一些寄存器外,实模式的80386处理器和8086有什么进步呢?
1 | 最大的好处是可以使用80386的32位寄存器,用32位寄存器进行编程可以使计算机更加快速 |
- 80386中增加的两个辅助段寄存器FS和GS再实模式下也可以使用,这样,同时可以访问的段到达了6个而不必考虑从小装入的问题;最后,很多80386的新增指令也使一些原来不方便的操作得以简化
2.保护模式
- 当80386互作再保护模式下的时候,它的所有功能都是可用的,这个时候80386所有的32根地址线都可供寻址,物理寻址空间高达4GB
1 | 这个时候就不需要和8086一样有什么段地址什么偏移地址了,直接偏移地址就能索引到4GB的空间 |
再保护模式下,支持内存分页机制,提供了对虚拟内存的良好支持。虽然和8086可寻址的1MB物理地址空间相比,80386可寻址的物理地址很大,但实际的微机系统不可能安装如此大的物理内存。【这是在以前】所以,为了运行大型程序和真正实现多任务,虚拟内存【再硬盘上开辟一段空间,来当内存使用】是一种必要的技术
保护模式下80386支持多任务,可以依靠硬件仅再一条指令中实现任务切换。任务环境的保护互作是由处理器自动完成的
再保护模式下,80386处理器还支持优先级机制,不同的程序可以运行再不同的优先级上
1 | 优先级一共分为0~3一共4个级别,操作系统运行再最高的优先级0上,应用程序则运行再比较低的级别上 |
- 配合良好的检查机制后,既可以再任务间实现数据的安全共享也可以很好地隔离各个任务。从实模式切换到保护模式是通过修改控制寄存器CR0的控制位PE(位0)来实现的。再这之间还需要建立保护模式必须的一些数据表,如全局描述符表GDT和中断描述符表IDT等
- ODS操作系统运行再实模式下,而Windows操作系统运行于保护模式下
3.虚拟8086模式
虽说实模式兼容以前的系统,但是设想一下,如果Windows或80386处理器推出的时候,不能运行MS-DOS程序,那么就等于放弃了一个巨大的软件库,,,这是不可能的
由于这种特殊需求的普遍性,虚拟8086就来啦
虚拟86模式是以任务形式再保护模式上执行的,再80386上可以同时支持由多个真正的80386任务和虚拟86模式构成的任务
再虚拟86模式下,80386支持任务切换和内存分页,再Windows 操作系统中,有一部分程序专门用来管理虚拟86模式的任务,称为虚拟86管理程序
既然虚拟86模式以保护模式位基础,它的互作方式实际上是实模式和保护模式的混合
为了和8086程序的寻址方式兼容,虚拟86模式采用和8086一样的寻址方式
但显然多个虚拟86模式不能同时使用同一位置的1MB地址空间,否则会有冲突。操作系统利用分页机制将不同虚拟86任务的地址空间映射到不同的物理地址上去,这样每个虚拟86任务看起来都任务自己在使用0~1MB的地址空间
8086代码中有相当一部分再保护模式下属于特权指令,如屏蔽中断的cli和中断返回指令iret等
这些指令再8086程序中是合法的。如果不让这些指令执行,8086代码就无法互作。为了解决这个问题,虚拟86管理程序采用模拟的方法来完成这些指令
虚拟86管理程序再异常处理程序中检查产生异常的指令,如果是中断指令,则从虚拟86任务的中断向量表中取出中断处理程序的入口地址,并将控制转移过去
如果是危机操作系统的指令,比如cli等,则简单地忽略这些指令,再异常处理程序返回的时候直接返回到下一条指令
通过这些措施,8086程序既可以正常的运行下去,再执行这些指令的时候又觉察不到已经被虚拟86管理程序做了手脚。MS-DOS应用程序再Windows操作系统中就是这样互作的
Windows的内存管理机制
- 在这一节中,我们需要通过学习解决两大疑问:
1 | 1.win32汇编中,每个程序都可以用4GB的内存吗? |
- DOS操作系统的内存安排
1 | Win32编程相对于DOS编程最大的区别之一就是内存的使用问题 |
1.DOS操作系统的内存安排
我们看到,由于8086处理器的寻址范围只有可怜的1MB大小,当时系统硬件使用的存储器地址被安排在高端
地址是从A0000H(即640KB)开始的384KB中,其中有用于显示的视频缓冲区和BIOS的地址空间
而在内存低端,安排了中断向量表和BIOS数据区;剩下从500h开始到A0000H总共不到640KB的内存是操作系统和应用程序所能够使用的;应用程序不可能使用这640KB以外的内存。这就是著名的“640KB限制”
而即使在这640KB中,DOS操作系统又占领了低端的一部分内存,最后剩下600KB左右的内存才是应用程序真正可以用的
如果系统中有内存驻留程序存在,那么应用程序还要和这些TSR程序共同分享这段内存空间
1 | TSR:[Terminate and Stay Resident]指:内存驻留程序 |
2.80386的内存寻址机制
- Windows的内存管理和DOS的内存管理有很大的不同,在了解Windows的内存管理模式之前,需要对80386保护模式下内存分页机制有所了解
- 为了做个对比,先来看实模式下的内存寻址方式:在实模式下,一个完整的地址由段地址和偏移地址俩部分组成【因为在实模式下和8086一样】
- 现在我们谈回到80386处理器的互作模式:
1 | 1.当80386处理器互作在保护模式和虚拟8086模式的时候,可以使用全部32根地址访问4GB大的内存。段地址加偏移地址的计算方法显然无法覆盖这么大的范围 |
- 这是不是说,在保护模式下,段寄存器就不在有用了呢?
1 | 1.错!实际上,段寄存器就更有用了,虽然在寻址上不在有分段的限制问题,但是在保护模式下,一个地址空间是否可以被写入,可以被多久优先级的代码写入。 |
但是问题来了,涉及的属性和保护模式下的段的其他参数,要表示的信息需要用64位的数据才能表示。我们把64位的属性叫做段描述(Segment Descriptor)
但是!!80386的段寄存器任然是16位的,怎么办?
1 | 把所有段的段描述符顺序放在内存中的指定位置,组成一个段描述符表 |
而段寄存器中的16位用来做索引信息,指定这个段的属性用段描述符表中的第几个描述符来表示
这时,段寄存器中的信息不在是段地址了,而是段选择器(segment selector)。可以通过它在段描述符表中“选择”一个项目以得到段的全部信息
段描述符表放在哪里呢?
1 | 80386中引入了2个全新的寄存器来管理描述符表 |
GDTR指向的描述符为全局描述符表GDT【Global Descriptor Table】它包含系统中所有任务都可用的段描述符,通常包含描述操作系统所使用的代码段,数据段和堆栈段和描述符及各任务的LDT段等;全局描述符表只有一个【也可以把这里理解成共有的属性】
LDTR则指向局部描述符表LDT(Local Descriptor Table)80386处理器设计成每个任务都有一个独立的LDT。它包含有每个任务私有的代码段,数据段,堆栈段的描述符,也包含该任务所使用的一些门描述符,比如:任务门和调用门描述符【这个和中断差差不多】
不同任务的局部描述符表分别组成不同的内存段,描述这些内存段的描述符当作系统描述符放在全局描述符表中
和GDTR直接指向内存地址不同,LDTR和CS,DS等段选择器一样只放在索引值,指向局部描述符表内存段对应的描述符在全局描述符表中的位置
随着任务的切换,只要改变LDTR的值,系统当前的局部描述符表LDT也随之切换,这样便于各任务之间数据的隔离,但GDT并不会随着任务的切换而切换
既然有全局描述符表和局部描述符表,那么段选择器【也就是段寄存器】中的索引值对应那个表中的描述符呢?
1 | 1.实际上,16位的段选择器只有高13位表示索引值 |
- 图说寻址路线:
1 | 1.在保护模式下,同样在xxxx:yyyyyyyy格式表示一个虚拟地址 |
3.80386的内存分页机制
- 在 实模式下寻址的时候,”段寄存器+偏移地址“经过转换计算后得到的地址是”物理地址“,也就是在物理内存中的实际地址
- 而在保护模式下,”段选择器+偏移地址“转化后的地址被称为”线性地址“而不是”物理地址“。那么线性地址就是物理地址吗?
1 | 很显然,名字都不一样,肯定不一样啦 |
- 为什么会有内存分页机制?
1 | 就是为了解决下面的不足【内存碎片化】 |
我们先来回顾一下:在单任务的DOS系统中,一个应用程序可以使用所有的空闲内存,程序退出后,操作系统回收所有的碎片内存并且合并成一个大块内存继续供下一个程序使用【也就是单线程】
内存合并过程中的一个极端情况是当系统中有多个TSR程序时,早装入内存的TSR被卸载后,后装入的TSR会留在内存的中间部位,把空闲内存隔离成两个区域
这时应用程序使用的最大内存块只能是这两块内存中较大的一块,无法将它们合并使用
对于一个多任务的操作系统,内存的碎片化是不行的,否则经过一段时间后,即使空闲内存的总和很大,也可能出现任何一篇内存都小到无法装入执行程序的地步
1 | TSR差不多就是说,会把内存给隔开,不能连续起来一起使用咯 |
所以对于多任务操作系统中碎片内存的合并是一个很重要的问题
如何解决?
1 | 80386处理器的分页机制可以很好的解决这个问题,80386处理器把4KB大小的一块内存当作“一页”内存,每页物理内存可以根据“页目录”和“页表”,随意映射到不同的线性地址上 |
- 在80386处理器中,除了和CR3寄存器(指定当前页目录的地址)相关的指令使用的是物理地址外,其他所有指令都是线性寻址的
- 什么是CR3?
1 | CR3用于保存页目录表页面的物理地址,因此被称为PDBR。由于目录是页对齐的,所以仅高20位有效,低12位保留。 |
- 为什么是20位?
1 | 因为:在80386CPU中能索引的内存地址是4GB,然后一页是4KB |
- 还有是否启用内存分页机制是由80386处理器新增的CR0寄存器中的位31(PG位)决定的
1 | 如果PG=0,则分页机制不启用,这个时候指令的寻址地址(线性地址)页就是系统中的实际的物理地址 |
- 一个xxxx:yyyyyyyy格式的虚拟地址,经过段地址转换不走后得到一个32位的线性地址zzzzzzzz(步骤1)
1 | 1.当禁用分页机制时,线性地址就是物理地址,处理器直接从物理内存存取数据(步骤2) |
内存分页管理只能在保护模式下才可以实现,实模式不支持分页机制。但是不管在那种模式下,所有寻址指令使用的都是线性地址,程序不用关系数据最后究竟存放在物理内存的哪个地方
页表规定的不仅是地址的映射,同时还规定了页访问属性。如:是否可写,可读和可执行等。比如把代码所在的内存页属性设置位可读和可执行,那么权限不够的代码向他写数据就会发生保护异常。用这个机制可以在硬件层次上直接虚拟内存的实现
页表可以指定一个页面并不真正映射到物理内存中。这样,访问这个页的指令会引发页异常错误。这时,处理器会自动转移到页异常处理程序中去
操作系统可以在异常处理程序中将硬盘上的虚拟内存读到内存中并修改表重新映射,然后重新执行引发异常的指令
这样就可以正常的执行下去
4.Windows的内存安排
- 这节课有3个概念
1 | 1.每个应用程序都有自己的4GB的寻址空间,就算这个程序只占有1KB的内存 |
- 虚拟内存安排
1 | 1.windows系统一般在硬盘上建立大小为物理内存2倍左右的交换文件用作虚拟内存 |
- 众所周知,Windows是一个分时的多任务操作系统,CPU时间【就是CPU运行的过程】被分为一个个的时间片后分配给不同程序轮流使用
- 在A程序的时间片中,和这个程序执行无关的部分【B和C和其他程序的代码和数据】并不需要映射到线 性地址中
- 附加解析:
1 | 1.内存中,所有的程序都搞在一起,关系十分混乱 |
WIN32编程中几个很重要的概念
- 第一个
1 | 1.每个应用程序都有自己的4GB的寻址空间【假的】 |
- 第二个
1 | 1.不同应用程序的线性地址空间是隔离的 |
- 第三点
1 | 1.DLL程序没有自己“私有”的空间 |
5.从WIN32汇编的角度看内存寻址
- WIN32汇编中的内存访问远比DOS下的分段寻址方式简单,why?
1 | because windows是一个多任务的操作系统,最首要的宗旨就是“稳定压倒一切”如果把描述符表以及页表等内容交给用户程序管理是很不安全的 |
- 任何权限开放引发的安全问题都是很严重的
1 | 如,windows 9x中的中断描述符表是可写的,CIH病毒可利用它将自己的权限提高到优先级0 |
而windows NT 下的中断描述符表就不能写了哦
windows操作系统帮用户“安排好了一切”
用户程序的代码段,数据段和堆栈段全部预定义好了段描述符。这些段的起始地址为0,限长ffffffff,所以用它们可以直接寻址全部的4GB地址空间
程序在开始执行的时候,CS,DS,ES,SS都已经指向了正确的描述符,在整个程序的生命周期内,程序员不必改动这些段寄存器,也不必关心它们的值是多少【因为想改也改不了】
windows的特权保护
- windows的特权保护和处理器硬件的支持是分布来的
- 优先级的划分,指令的权限和超出权限访问的异常处理等是构成特权保护的基础
- 有2个问题需要解决
1 | 1.win32汇编中为什么找不到中断指令? |
1.80386的中断和异常
- 什么是中断?
1 | - 中断指当程序执行过程中有更重要的事情需要实时处理的时候【如串口中有数据到达,不及时处理数据会丢失,串行控制器就提交了一个中断信号给处理器要求处理】,硬件通过中断控制器通知处理器 |
- 什么是异常?
1 | - 异常指令执行中发生不可忽略的错误的时候【比如遇到无效的指令编码,除法指令除0之类的】处理器用和中断处理相同的操作方法挂起当前运行的程序转移到异常处理程序中 |
- 实模式下的中断或者异常处理
1 | 1.实模式下的中断和异常服务程序地址放在中断向量表中 |
保护模式下,中断或异常处理往往从用户代码切换到操作系统代码中执行
由于保护模式下的代码分优先级,因此出现了从低到高……
为了使高优先级的代码能够安全的被低优先级代码调用,在保护模式下增加了“门”的概念
“门”指向某个优先级高的程序所规定的入口点,所有优先级低的程序调用优先级高的程序只能通过门重定向,进入门所规定的入口点
这样可以避免低级别的程序代码从任意位置进入优先级高的程序的问题
保护模式下的中断和异常等服务程序也要从“门”进入,80386的门分为中断门,自陷门,和任务门
在保护模式下把所有的中断描述符放在一起组成‘’中断描述符表IDT“为此80386处理器引入了一个新的48位寄存器IDTR。IDTR的高32位指定了IDT在内存中的基址【线性地址】,低16位指定了IDT的长度,相当于指定了可以支持的中断数量
保护模式下发生异常或中断时,处理器先根据IDTR寄存器得到中断描述符的地址,然后取出n号中断/异常的门描述符,再从描述符中得到中断服务程序的地址:xxxx:yyyyyyyy,经过段地址转换后得到服务程序的32位线性地址并转移后执行
在windows中,操作系统使用动态链接库来代替中断服务程序提供系统功能,所以在win32汇编中int指令也就失去了存在的意义。这就是在win32汇编代码中看不到int指令的原因。其实那些调用api的指令原本就是用int指令实现的
windows会在处理程序中经常系统会用”蓝屏“来通知用户程序视图访问不存在的内存页