1. 任务隔离以及LDT的概念:
1) 任务的概念:程序是记录在载体(也就是硬盘等外存)的指令和数据,而任务则是指其加载在内存中的副本,该副本不是等待执行就是正在执行,一个程序可以有多个任务副本;
2) 任务的隔离:把所有任务,不管是OS内核还是APP都放在GDT中管理显然是不合理的,容易造成APP对内核数据的破坏等问题,这就需要从两个层面上将它们隔离开来;
i. 第一个层面就是特权级层面(给予内核高特权级,给予APP低特权级,不同特权级之间相互访问将会受到很多限制,从而提高安全性);
ii. 第二个层面就是LDT,即Local Descriptor Table,局部描述符表,它是相对于GDT的,GDT是全局的,逻辑上其只有一个并且是由OS管理的,而每个APP都必须拥有一个自己的LDT来管理自己拥有的段,也就是说GDT是共有的(OS、APP都能用) 而LDT是任务私有的,一般只有任务自己使用;
3) LDT格式:和GDT格式一模一样,LDT中的段描述符也GDT的段描述符格式也完全一样,只不过LDT的0号槽位也可以用来存放描述符;
!注意:一般LDT中描述符的DPL为3(GDT中描述符的DPL一般为0,OS拥有最高特权级),并且指示LDT中描述符的选择子的TI位为1(Table Indicator,表示选择的描述符位于LDT中);
2. 用GDT和LDTR来管理LDT:
1) GDT和LDT虽说是描述符表,但这也掩盖不了它们是内存段的事实,由于GDT是全局唯一的,所以处理器要求将所有的LDT都要做出一个GDT描述符放在GDT中统一管理也是合情合理的;
2) CPU要求,为了对所有的LDT统一管理,必须在GDT中安装每一个LDT的描述符(就是把LDT看做一个段,然后做出该段的描述符安装到GDT中);
3) 考虑到GDT和LDT的特殊性(适用于系统管理的),因此它们也被称为系统的内存段,简称系统段;
4) 系统段的特征:段描述符中S位为0,这就表示这是一个系统段或者是一个门,但是系统段有两种(一种是LDT段另一种是GDT),而门也有多种(调用门、中断门、陷阱门等),那该如何确定其是哪一种角色呢?那就得看TYPE字段了,当S=0,TYPE=0010时就表示LDT段,当S=0,TYPE=1100时就表示调用门,就把S+TYPE看做一个纯5位类型编码即可;
!其它特征:
i. G、AVL、P、DPL位和GDT描述符中相应位的意义一样,只不过GDT和LDT的最大范围只能是64KB,因此不管是4KB粒度还是1B粒度,最终大小都不能超过64KB!
ii. D/B和L位对系统段描述符来说没意义,因此恒为0;
iii. 一般LDT都是由GDT管理的,因此LDT段的特权级一般为0;
!综上所述:LDT描述符的属性一般为0x00408200(即描述符的高32位,属性位保留在原始位置,其余无关位清零);
5) 和GDTR一样同样也存在LDTR来指示LDT的位置和大小,只不过LDT在GDT中存在描述符,因此也可以“像访问其它普通GDT段的形式”来访问LDT,之所以打引号是因为指示形式相似而已(毕竟是系统段,不能随意访问的),即LDTR和其它段寄存器一样,也只是一个16位的选择子,但它拥有一个64位的高速缓冲器保存完整的LDT描述符以加速对LDT的访问,因此在加载LDTR的形式和加载GDTR有所不同:
i. 加载GDTR的指令是:lgdt m48
ii. 而加载LDTR的指令时:lldt r16/m16,即只需要加载一个LDT在GDT中的选择子即可;
6) LDTR的唯一性:不是说LDT可以有多个吗?是啊,但是LDTR是只有一个的,毕竟,一个OS是无法预知会有多少个任务会运行(当然任务数量可能非常之多),因此在一个多任务系统中,唯一的LDTR只指向当前正在运行的任务的LDT,因此单核CPU上实现多任务就是通过不停切换LDT指向的方法来模拟”宏观并行微观串行的“;
3. 任务状态段——TSS:
1) 对于一个多任务系统,在多任务之间切换的时候必定需要保存被切换走的任务当时的处理器信息,以便在切换回来的时候能正确地”继续“刚刚未完成的作业;
2) 所谓的处理器信息就是指在发生任务切换时通用寄存器、段寄存器、标志寄存器等部件上的数据信息,总结起来至少有104个字节量,而这些数据的总和就是任务状态段(Task Status Segment,简称TSS);
3) 每个任务都有唯一属于自己的TSS,需要自己开一片额外的内存空间来存放TSS(这些工作都属于OS),对!你猜对了,TSS和LDT一样,也属于系统段,需要在GDT中注册它的描述符!
4) TSS格式固定,一般最小是104个字节,CPU固件能自动识别它以支持多任务之间的切换,其格式如图:
!可以看到,TSS中有LDT选择子,通过该选择子找到并加载LDTR,再多LDT进行访问;
5) 在GDT中登记TSS描述符:
i. TSS也属于系统段,因此S=0,当描述符是TSS描述符时TYPE=10B1,这个B就表示Busy位,如果为0就表示TSS指示的任务不忙(即准备就绪,可以开始执行),如果为1就表示该任务正在执行或者挂起;
ii. DPL一般为00,P表示存在位,当任务没被换入硬盘时为1,其余设置和LDT描述符一样;
iii. 因此TSS描述符一般为0x00408900;
iv. 同样和GDTR、LDTR一样,也有一个TR来指示TSS的基址和大小:TR就是Task Register,和LDTR一样,也是一个16位的选择子(指示TSS的GDT选择子),同样也有64位的高速缓存器存放完整的TSS描述符;
v. 加载TR的指令:ltr r16/m16
6) 使用TSS选择子进行任务切换:当call far或jmp far指令的操作数是TSS选择子时就可以实现任务之间的切换(将TSS中的寄存器数据块填入相应寄存器中,并根据LDT选择子加载LDTR等);
4. LDT和TSS不是万能的,还需要——TCB:
1) 虽然TSS中有LDT选择子,但是在多任务中你肯定要记录所有任务的TSS选择子才能实现多任务之间的切换,一般的做法是使用链表,链表节点中的数据域就是tr,而指针域就是next指针了,但在现实情况中仅仅只有一个tr域是远远不够的,还需要更多的信息,比如tr指向的任务的权限、所属成员、所属组等种种信息,只不过tr是核心而已;
2) 因此为了管理方便,就需要为每个任务创建一个任务控制块(Task Control Block,简称TCB),每个TCB中则包含了tr等丰富的信息,将所有TCB串成一条链表就形成了任务控制表,这就现代操作系统任务管理的核心,这部分是软件实现部分,也是操作系统内核的关键模块;
3) 在本次的程序演示中设计的TCB格式如下图所示:
4) 动态管理TCB内存:由于任务数量是无法预知的,但是只要加载一个任务就需要为该任务开一个TCB,因此任务的TCB只能动态加载(和C语言中malloc的概念一样),所以TCB不能传统的一个固定大小的段来存放,因此必须使用一个能访问4G平坦空间的全局描述符来灵活管理,即哪里有合适的空位就忘哪儿塞一个TCB,也就是说TCB存放是灵活的,可以不要求连续存放,但是在本次的程序演示还是使用连续存放的方式来模拟的;
5. 小结——任务控制模块搭建的步骤:
1) 为任务创建一个TCB;
2) 再为该任务创建一张LDT;
3) 将LDT描述符加入GDT中,再将LDT的有关信息填入TCB中(包括LDT选择子,所以要先添加LDT的GDT描述符);
4) 创建TSS;
5) 将TSS描述符假如GDT中,再将LDT选择子填入TSS中(所以要先创建LDT才能再执行这步),并把TSS相关信息填入TCB中;
6) lldt、ltr使LDT和TSS生效并切换到所指向的任务;
6. 特权级保护之当前特权级——CPL:
1) DPL:再回顾一下,就是指描述符特权级,由于描述符指向对象,因此也就是目标对象的特权级(目标对象就是指相应的段),该特权级指示了能访问目标所必须拥有的最低特权级,比如要访问特权级为1的段,自己本身拥有的特权级至少为1,再高的话就是0,但是不能为2和3(数字越小特权级越高);
!因此一般来说OS内核DPL为0,驱动、数据库等系统软件的DPL一般为1或2,而APP的DPL最低,必为3;
2) CPL的概念:即Current Privilege Level,即当前特权级,就是指 当 前 正 在 执 行 的 代 码 段 的 DPL,注意几个关键词,首先必须是代码段才能执行