前面的段的保护是类似阻止写可执行的代码段,访问超过段的界限.这些段的保护是处理器提供的基本保护功能.
正常的程序只访问自己的段,但恶意的程序可以修改自己的段寄存器,指向操作系统的私有数据.进行访问.
其次,在(单核)多任务系统中,两个以上的任务需要可以交替执行,快速的处理器加上高效的任务切换,在外界看来,多个任务都在同时运行.
多任务系统对任务之间的隔离和保护,以及任务和操作系统之间的隔离和保护都有要求.可以看作是段保护机制的进一步强化.
程序是指令和数据,是为了完成特定的工作,正在执行的一个副本叫做任务.
以前是把所有的段描述符都放在gdt中,不管它属于内核还是用户程序,为了有效的在任务之间隔离,每个任务都应该有自己的描述符表.ldt(与gdt对应),并且把属于自己的段放在gdt中.gdt的第0个槽位也是有效的.局部描述符表寄存器(ldtr r:register)是用来追踪访问这些ldt的.
段选择子的第二位是表指示器(tl=0表示从gdt中加载描述符,tl=1表示从当前任务加载描述符)
在一个多任务运行环境中,当前任务发生切换是,要保护旧任务运行的状态,或说是保护现场.保护的内容包括所有的寄存器.用任务状态段(tss task state segment)来保护这些信息,处理器能识别tss中的每个元素,并在任务切换的时候读取其中的信息.处理器用tr指向当前任务的tss.(tr在内存中只有一个)
操作系统肩负着任务的创建以及在任务之间的调度和切换工作,不过更繁重的工作是对处理器,设备及存储器的管理.提供大量的例程和数据供应用程序调用.可以简化编程,并能够在访问设备时消除潜在的竞争和冲突.(设备访问的排除和调度.)
任务包含两个部分,全局部分(操作系统的软件和库程序)和私有部分.任务是在内存中的,全局地址空间和局部地址空间.全局地址空间用全局描述符gdt来指定的,局部地址空间是由每个任务的ldt来定义的.
从程序员来看,gdt包含操作系统的段,是别人写的,他可以用.通常任务在自己的局部空间运行,当它需要调用系统服务时,转入全局空间执行.
引入ldt和tss,提供了构建可靠的硬件设施.
特权级(privilege level),是存在于描述符及选择子中的一个值.4个特权级,0~3,0是最高级,操作系统主体部分特权级是0,位于中心.用户程序是3.
描述符有一个dlp字段,可以取值为00~11,dpl是每个描述符都有的字段,称为描述符特权级(descriptor),描述符总是指向它所描述的目标对象,代表着该对象,因此该字段实际上是目标对象的特权级.
当处理器在一个代码段中取指令和执行指令时,那个代码段的特权级就叫做当前特权级.cpl.正在执行的代段,选择子位于cs中.其中最低的两位就是当前特权级.
特权指令:lgdt,ltr 加载任务寄存器,lldt ,eflags 第12,13位是iopl位,也就是输入输出特权级(I/O),它代表着当前任务的Io特权级别.处理器不限制0特权级别程序的IO访问,它总是允许的.
代码段的特权级检查是严格的,一般来说,只允许发生在两个特权级相同的代码段.为了让特权级低的调用特权级高的,提供了两种办法:
1.将代段定位依从 c=1为依从代码段,可以从特权级比它低的程序调用并进入.(例依从特权级为1,则有1,2,3可以调用.),cpl不变.
2:使用门,门是另一种形式的描述符,门描述符描述可执行的代码段,比如一段程序,一个过程或者一个任务.不同特权级之间的调用可以使用调用门,中断门作为中断处理过程使用,任务门对应着单个任务,用来执行任务的切换.(注:以前我切换是通过jmp,call 偏移地址/绝对地址来做的,但是不能实现不同特权级的切换.就是说不能从用户态到内核态.现在就弄了一个门. )
jmp far 转移到比当前特权级高的代码段,不改变当前的特权级别,call far 当前特权级别会提升到目标代码段的特权级别.
特权级在保护模式下才能用,刚进入保护模式CPL=0BN
RPL请求者的特权级别,绝大多数时候,请求者的都是当前程序自己.cpl=rpl
使用调用门调系统的时候,cpl=0,如果说传入的选择子是操作系统的,就会有问题.再加一个pl,rpl.
原因是处理器在遇到一条将选择子传送到段寄存器的指令时,无法区分真正的请求者是谁,操作系统必须保证rpl的值和请求者的身份相符(要想使用硬件的功能,操作系统就要按硬件说的做),将rpl字段设为请求者的特权级可以用(arpl命令),剩下的工作就交给处理器了.
引用rpl的意图是确保特权代码不会替代应用程序访问一个段.
0特权级是最高的特权级,当一个系统的各个部分都位于0特权级时,各种特权级检查总能够通过.如果不需要使用特权级机制的话,可以将所有的特权级都设为0.
调用门(call gate )用于在不同特权级程序之间进行控制转移,本质上它只是一个描述符,一个不同于代码段和数据段的描述符,可以安装在gdt和ldt中.调用门描述符给出了例程所在代码的选择子,而不是32位的线性地址.
这里是用寄存器传递参数的,所以参数的个数为0
特权级为3的程序除了自己的3特权级工作栈,还要有0,1,2工作栈,防止栈空间不足和栈数据交差引用.这些栈的创建是由操作系统来进行的.这些栈的描述符位于ldt中,还要在tss中登记,栈的切换是由处理器固件完成
通过调用门使用高级特权的例程服务时,是通过栈进行的.栈要切换,需要将旧栈的参数复制到新栈中,参数的复制工作是由处理器进行的,
但是必须知道参数的个数,共5b,也就是说最多传递31个参数.
所以调用门的描述符是3.调用门是比它的dpl高的才能访问,设为3,都能访问了
make_gate_descriptor: ;构造门的描述符(调用门等)
;输入:EAX=门代码在段内偏移地址
; BX=门代码所在段的选择子
; CX=段类型及属性等(各属
; 性位都在原始位置)
;返回:EDX:EAX=完整的描述符
push ebx
push ecx
mov edx,eax
and edx,0xffff0000 ;得到偏移地址高16位
or dx,cx ;组装属性部分到EDX
and eax,0x0000ffff ;得到偏移地址低16位
shl ebx,16
or eax,ebx ;组装段选择子部分
pop ecx
pop ebx
retf
sys_routine_end:
//使用方法
push ecx
mov eax,[edi+256] ;该条目入口点的32位偏移地址
mov bx,[edi+260] ;该条目入口点的段选择子
mov cx,1_11_0_1100_000_00000B ;特权级3的调用门(3以上的特权级才
;允许访问),0个参数(因为用寄存器
;传递参数,而没有用栈)
call sys_routine_seg_sel:make_gate_descriptor
//最后把表中的段选择子换成了调用门,再把用户头部的表中的选择子也换成这个调用门,用户就可以通过调用门调用程序了.
内核加载完成后,接下来的工作就是加载和重定位用户程序,要使一个程序成为一个任务,并且能够参与任务的切换与调度,要有ldt和tss,
为了在任务之间切换和轮转,必须能追踪到所有正在运行的任务,记录它们的状态,或者根据它们的状态来采用适当的操作.为了满足上面的要求,内核应该为每个任务创建一个内存区域,来记录任务的信息和状态.任务控制块(task control block tcb),任务控制块不是处理器要求的,是我们自己为了方便发明的
tbc_chain在内核的数据段
1.创建用户程序的tcb
2.创建ldt
3.创建用户程序的局部描述符
4.重定位U-sala表(这里把选择子的rpl改为3.用的还是gdt)
5.创建0,1,2特权级栈
6.安装ldt到gdt中,并回写到tcb中相应的位置
7.任务状态段tss
在创建任务时,操作系统需要赶写eip,eflags,esp,cs,ss,ds,es,fs,gs,当该任务第一次执行时,处理器从这里加载初始化的执行环境,并从cs:eip处开始执行任务的第一条指令,在此之后任务运行期间,该区域的内容由处理器固件进行更改.
申请一个104字节用于创建tss,把tss基地址和界限登记到tcb中.将来创建tss描述符的时候用得着.
和局部描述符ldt一样,也必须在gdt中安装tss的描述符,这样做一方面是为了对tss进行段和特权级的检查,另一方面也是切换任务的需要.当call far 和Jmp far指令的操作数是tss描述符选择子时,处理器执行任务切换操作.
我们创建的是一个3特权级的任务,这是一个从0特权级到3特权级的转移.](这个栈在tr指向的tss中)
call far指令通过调用门转移时,如果改变了特权级别,则必须切换栈,栈的切换是由处理器进行的.
插入 注意部分
调用门
依从段
调用门依从段
调用门非依从段
调用门返回时也要做特权检查,若段描述符<返回后新的cpl,将0送到该段,如果访问就会引发异常.(全0 一定会指向gdt,访问gdt的第0个就会出现问题.)
在多任务环境下,随着任务的切换,每当一个任务成为前台活动任务时,tr和ldt寄存器中的内容都会更新,以指向新的当前任务.
要从任务0特权级转移到3特权级. 先使tr和ldtr寄存器指向这个任务,然后假装从调用门返回
ltr [ecx+0x18] ;加载任务状态段
lldt [ecx+0x10] ;加载LDT
mov eax,[ecx+0x44]
mov ds,eax ;切换到用户程序头部段
;以下假装是从调用门返回。摹仿处理器压入返回参数
push dword [0x08] ;调用前的堆栈段选择子
push dword 0 ;调用前的esp
push dword [0x14] ;调用前的代码段选择子
push dword [0x10] ;调用前的eip
retf
//这里使用的0特权级栈并非来自于用户.
引入rpl,需要操作系统来保护rpl的值是正确的.
操作系统指令arpl.
表里面的是各种描述符
ldt和gdt都用来存放各种描述符,但是它们是内存段,但是因为他们用于系统的管理,所以称为系统的段或系统段.gdt是唯一的,gdtr存放基地地和界限就可以了.ldt每个任务都有,要在gdt中安装ldt的描述符,当要用这些ldt的时候,可以用它们的选择子来访问gdt.(先创建了ldt,把里面的描述符建好了,然后再来把它安装到gdt中)
必须要在gdt中创建tss描述符.
通过调用门发生转移:
如果发生了栈切换:
先保存ss ,esp,复制参数,cs,esp
通过调用门,一定是远转移,所以一定要压入cs:eip
返回时根据栈中cs的rpl(所以用户的代码段的选择子rpl是3)