内核第二讲,内存保护的实现,以及知识简介,局部描述符,全局描述符.
一丶了解80386的各种模式
80386,也就是32位系统下,有三种模式需要了解一下.
实模式,保护模式.虚拟86模式
实模式: 指的是操作系统在启动的是否,这时候访问的内存都是实际的物理内存.而在这个是否,操作系统会填写内核中的内中表格.比如今天讲的表(全局描述符表 GDT)
保护模式: 当各种表填写好了,那么我们的内存也被保护了.这个是否我们的进程就不会直接访问物理地址了.进而产生了保护行为,我们的内存就有了 可读 可写,可执行一说了.
虚拟86模式: 操作系统启动的是否,运行的都是实际的16位汇编.那么现在我们假设有一个16位程序要启动.那么修改了我们物理地址的内存,那么保护模式不就没用了.所以为了防止这一情况的产生,操作系统做了一个虚拟86模式,也就是说可以运行16位汇编程序.
关于更详细的介绍,请下载当讲课堂资料的 "8086处理器的工作模式.doc"文件进行查看.
二丶保护模式如何保护内存的.
我们通过上面模式的介绍,知道的操作系统启动的是否会从实模式启动,然后会切换到保护模式.
那么是如何保护内存的.
比如我们要保护 100地址的内存,让其支持可以读,不可以写.那么在 ring3下有汇编代码
mov [100],10
如何保证它不被改写.
思路一:
操作系统作者进行各种判断,判断这个地址不可以写.
思路二:
留下接口,比如我们编写SDK程序的是否,当鼠标点击的是否,会产生一个回调函数,那么这个回调函数是用户写的.因为当鼠标按下,操作系统只能通知你,但是不知道你要怎么做.
思路三:
做表,通过查表,进而判断内存是否可以访问或者读写.
这里关于前两种思路,第一种思路,对于操作系统肯定不现实的.每次访问内存都要判断,校验,不说你能不能完成,就算真的完成了,那么你的操作系统也会奇卡无比.
思路二,思路二放在我们自己编写的是否倒是可以.但是对于操作系统来说也是不现实的.
思路三,这个可以了.我们可以通过查表的方法,进行判断.而且只在访问内存的是否才进行判断内存是否可读写.
对于上面来说,我们就会产生一个新的疑问.这个表怎么做才合理.
在做表之前,我们要熟悉汇编中的段寄存器. 我们知道,在16位汇编中,我们可以通过段 偏移的方式来寻找内存.管理内存.那么我们现在要对内存做管理.那么就要分段了.
三丶分段管理概要
进行分段管理,来管理内存.那么我们应该怎么分.在分段之前我们要理解几个基本的概念.
1.逻辑地址.什么是逻辑地址
2.线性地址.什么是线性地址.
3.物理地址.
4.虚拟地址(暂时不讲)
逻辑地址: 逻辑地址指的是 段 偏移的方式,我们程序中每一行代码都是逻辑地址.
物理地址: 物理地址就是内存条的地址,也就是我们说的实际地址.
线性地址: 线性地址我们可以理解为逻辑上连续的物理地址.
段 偏移 查表,会查到线性地址.(物理地址),每个段 偏移都会查到一块物理地址.
比如我们的逻辑地址: 00401000~00402000,在逻辑上我们看的是连续的,但是通过查表转换为物理地址的是否则不是连续的.
看图:
有可能进过查表,得出的物理地址不是连续的.但是逻辑地址是连续的.
线性地址,如果我们没有虚拟内存.那么查到的线性地址就是物理地址.如果有虚拟内存,那么可能还要查表才能转化为物理内存.
为什么要查表得到物理地址?
原因是,我们要对进程做隔离,对内存做保护.所以我们查到对应的物理地址其实是对它做一个保护.
四丶表格式怎么做.怎么做表?
既然上面我们明白了,要对内存做保护,那么首先要分段,对每一段内存做保护.那么该怎么设计.
1.段起始地址
2.大小
3.结束地址.
4.当前内存的保护属性.
我们会这样设计表格.其实我们想到这样设计,那么inter等等CPU也不是神,也会这样设计.
看下Inter设计的表格.
每错,第一次看到就会晕.那么仔细讲解这个存储段描述符表
首先我们理解一下 Base Address,也就是段.
段是实现虚拟地址到线性地址(物理地址)的转化机制的基础,也就是查表需要用到段.
那么在保护模式下.每个段有三个参数. 段基地址(Base Address) 段界限(limit),可以理解为区域
段属性(Attributes) 这三个字段则是inter定义的表格.和我们的差不多.
Base Address: 段基地址,也就是我们分段的是否,在32位下,段基地址长32位.也就代表的我们可以分配4G个段.这些了解即可.
段界限(Limit): 段界限,指的就是当前内存起始位置加上当前段界限的大小,是属于什么属性的.这个在段属性中有表明.段界限用20位来表示,注意,是位.段界限可以是以字节为单位,也可以以4K为单位.什么意思那? 也就是说.这个段界限最大是20位表示 那么界限就是 2^20次方-1*4096字节大小
计算得出:
2^20-1 * 4K = FFFFF000,然而在32为系统中不是4G大小的界限.所以进而加上0FFFH大小.
公式: LIMIT = limit(2^20) * 4k 0FFFh. 也相当于 左移<<12位, 0FFFh.
而是否*4K则看属性中的G位,如果G位为1,那么就*4K,如果为0,则*8位,也就是按照一个字节的界限去做.
4.1 线性地址的范围怎么计算.
base Address limit 则可以形成一块内存区域.
假设段A的基地址是 00012345h,段界限为5678h,并且是以字节为单位(G = 0),那么
段基地址 段界限 = 000179BDh, 那么在00012345h ~ 000179BDH则是段A的线性地址
计算就是 段基地址 段界限 (也可以理解为,起始地址 一块区域.则这块区域就是线性地址)
如果属性中 G位 = 1,那么就是按照4K去计算.
0012345h (5678 *4K) 0FFFh = 0568b344h
那么段A的线性地址就是从 0012345h ~ 0568b344h
我们上面计算线性地址,提供了一个表格.这个表格相信大家现在也能看懂了.
其中,我们知道了 段基地址占4个字节(32位)而段界限占20位,那么还属性则占12位,而一种表的大小是8个字节.
看上面的图,我们发现
M 7 存储的是段地址
M 4,M 3,M 2 这三个字节也是存储的段的地址.这是为什么? 因为inter为了兼容80286(24)位,那么只能添加新的东西来了,而在80286上面,是没有多加的.
M 1 M 0 这两个字节是16位,存储的是段界限,而M 6的8位也是存储的段界限的.
而M 5,M 6中.M 5全部都是段的属性,M 6中,也有段属性的存在.
M 6,和M 5展开查看.
其中 BLT3,BLT2,BLT1,BLT0这4位,是段界限的4位.剩余的是段属性
4.2段属性介绍.
根据上面的表展开得知,段属性分别有
G D 0 AVL P DPL DT1 TYPE
那么分别是什么意思?
G : 历史位, 当G = 1的是否,段界限需要 * 4K(0-4095),G = 0的是否,那么就是按照8位来计算,也就是一个字节.
D: D位方向位.我们的堆栈保护内存的时候,堆栈和我们正常的数据结构是相反的.段界限我们应该 那么堆栈就需要减,所以需要个方向位调整
BLT5, 保留位
AVL: 应用软件可使用位.
P: 这个段这个内存是否有效
DPL: 特权位,判断你是0环还是3环
DT位: 描述我们这个表的类型是用户还是系统.DT = 1描述系统.
TYPE:位,这个则是表明了内存属性是可读,可写,还是可执行.
TYPE位展开.
如果想要一块内存可读可执行,可写.那么需要建立两个表.分别让TYPE为 = 2,然后 = 8即可.
因为段可以重叠.所以可以这样操作,所以就有了修改内存保护属性的API,inter官方承认的.
五丶什么是描述符,以及全局描述符表,局部描述符表.
描述符:
也就是我们说的三个字段.我们这三个字段合在一起成为了一张表.这个表则成为存储段描述符. 存储段信息的表.
局部描述符表:
我们知道针对我们一个进程我们可以建立多个存储段描述符表.来保护我们的内存,那么CPU访问的是否则会进行校验.
那么这个则是局部描述符表.简称 LDT表.
那么如果多个进程我们就需要多个描述符表.各自放在自己的低2G空间,高2G空间的描述符表是一样.
那么此时就产生了冗余问题.高2G的描述符表都是一样的,所以建立一个全局的描述符表. 简称GDT
LDT表只需要操作系统给表的地址即可.这些都是操作系统在实模式启动的是否进行填写的.
其中LDT = A进程,那么就执行A进程的操作.切换进程,并且保存进程的各种信息.以及各种表. 如果我们换成了B进程,那么就会切换到B进程.
如果我们手动切换,则是进行内核操作了,也就是所说的ROOTKIT,(内核补丁)的技术了.(我猜大部分学习内核的都是学习这种)
六丶段选择子,进行查表.
什么是段选择子.段选择子是翻译的.我们在32为的段选择子是我们的段寄存器. 这些段寄存器都用来保护内存了.
段当下表进行查表动作.其中后2为是RPL,也是特权指令. 如果查表的是否RPL和描述符表的DPL一至则可以进行查表.不会出错. BLT2是表示查询那个表, TL = 1表示是LDT表,TL = 0 表示GDT表.
3环下有读取GDT和LDT描述符表的指令
SGDT [内存] 读取SGDT表的起始位置到你指定的内存. 3环下可以读不可以写. LGDT[内存] 写,ring3不能执行.
其中如果在ring3下执行SGDT那么操作系统给你的是一个错误的值.
学习内核知识建议读书 视频. 资料只能简单看看