码迷,mamicode.com
首页 > 编程语言 > 详细

任务和特权级保护(二)——《x86汇编语言:从实模式到保护模式》读书笔记32

时间:2016-05-22 12:31:35      阅读:393      评论:0      收藏:0      [点我收藏+]

标签:

之前做了那么多铺垫,我们终于可以看看第14章的代码了。
对于引导代码和用户程序,依然采用第13章的;对于内核程序(c14_core.asm),编译的时候有几行报错了,只要加上dword即可解决。

1. 为什么要用调用门

在第13章,为了能使用内核提供的例程,用户程序是用call far指令直接转移到内核例程(非一致代码段)。因为CPL=目标代码段描述符的DPL=RPL=0,符合下面表格的条件,所以转移是没有问题的。
技术分享

但是在本章,用户程序工作在3特权级,而非0特权级,所以是无法直接转移的。不过也不用悲观,我们还是有办法的,可以通过调用门来转移。

2. 什么是调用门

关于调用门的知识,可以参考我的博文:调用门详解

调用门的格式如下图:
技术分享

3. 调用门的安装

811         ;以下开始安装为整个系统服务的调用门。特权级之间的控制转移必须使用门
812         mov edi,salt                       ;C-SALT表的起始位置 
813         mov ecx,salt_items                 ;C-SALT表的条目数量 
814   .b3:
815         push ecx   
816         mov eax,[edi+256]                  ;该条目入口点的32位偏移地址 
817         mov bx,[edi+260]                   ;该条目入口点的段选择子 
818         mov cx,1_11_0_1100_000_00000B      ;特权级3的调用门(3以上的特权级才
819                                            ;允许访问),0个参数(因为用寄存器
820                                            ;传递参数,而没有用栈) 
821         call sys_routine_seg_sel:make_gate_descriptor
822         call sys_routine_seg_sel:set_up_gdt_descriptor
823         mov [edi+260],cx                   ;将返回的门描述符选择子回填
824         add edi,salt_item_len              ;指向下一个C-SALT条目 
825         pop ecx
826         loop .b3

先复习一下过程make_gate_descriptor.

331    make_gate_descriptor:                   ;构造门的描述符(调用门等)
332                                            ;输入:EAX=门代码在段内偏移地址
333                                            ;    BX=门代码所在段的选择子 
334                                            ;    CX=段类型及属性等(各属
335                                            ;    性位都在原始位置)
336                                            ;返回:EDX:EAX=完整的描述符

816~821:调用过程make_gate_descriptor构造调用门(请参考调用门的格式),P=1,DPL=3,参数个数=0;
822:调用过程set_up_gdt_descriptor把构造好的调用门安装到GDT中,返回对应的选择子(TI=0,RPL=0);

264  set_up_gdt_descriptor:                    ;在GDT内安装一个新的描述符
265                                            ;输入:EDX:EAX=描述符 
266                                            ;输出:CX=描述符的选择子

823:将返回的调用门选择子回填,覆盖原先的段选择子。如下图(下图是内核符号表中一个表项的示意图)所示:

技术分享

4. 调用门的测试

828         ;对门进行测试 
829         mov ebx,message_2
830         call far [salt_1+256]              ;通过门显示信息(偏移量将被忽略) 

表面上,这是一个间接绝对远调用,通过指令中的内存地址,可以间接取得32位偏移量和16位的代码段选择子;但是,处理器在执行这条指令的时候,会用选择子访问GDT,结果发现是一个调用门,所以忽略32位的偏移量(上图中的绿色部分)。

调用门安装完成后,GDT的示意图如下:

技术分享

不仅间接绝对远调用是这样,直接绝对远调用也是这样,如果选择子指向的是调用门,偏移量也会被忽略。例如

    call 0x0040:0x00001234

结合上图,因为0x40处是调用门,所以偏移0x00001234被忽略。

5. 加载用户程序与创建用户任务

5.1 任务控制块(Task Control Block,TCB)

5.1.1 TCB的格式

加载程序并创建一个任务,需要用到很多数据,比如程序大小、加载位置等等。内核应当为每一个任务创建一个内存区域,来记录任务的信息和状态,这个内存区域就称为任务控制块(Task Control Block,TCB)。
需要说明的是:TCB不是处理器的要求,而是我们为了自己方便而发明的。
关于TCB的结构,如原书图14-12(P264)。为了读者方便,我在这里把图再绘制一遍。

技术分享

请注意,这个格式是作者发明的,并不是说TCB就必须是这种格式。

5.1.2. TCB链表

为了能够追踪所有的任务,可以把每个TCB串起来,形成一个链表。
在代码的核心数据段中,声明了标号tcb_chain,初始化了一个双字,值为0.

413         ;任务控制块链
414         tcb_chain        dd  0

其实,这相当于一个指针,用来指向第一个任务的TCB。当它为0时,表示没有任务。所有任务都按照被创建的先后顺序链接在一起形成一个无头单向非循环链表。

835         ;创建任务控制块。这不是处理器的要求,而是我们自己为了方便而设立的
836         mov ecx,0x46
837         call sys_routine_seg_sel:allocate_memory
838         call append_to_tcb_link            ;将任务控制块追加到TCB链表 

以上三行用于分配TCB的空间(0x46字节),然后把这个TCB挂到链表上(尾插法)。

5.2. 加载用户任务

840         push dword 50                      ;用户程序位于逻辑50扇区
841         push ecx                           ;压入任务控制块起始线性地址 
842       
843         call load_relocate_program

以上三行用于加载和重定位用户程序。

5.2.1. 使用栈传递参数

464   load_relocate_program:                   ;加载并重定位用户程序
465                                            ;输入: PUSH 逻辑扇区号
466                                            ;      PUSH 任务控制块基地址
467                                            ;输出:无 
468         pushad
469      
470         push ds
471         push es
472      
473         mov ebp,esp                        ;为访问通过堆栈传递的参数做准备

这是过程load_relocate_program开头的几行,执行完第473行后,栈的状态如下图所示:
技术分享

这里主要是复习如何用栈传递参数。需要说明的是:
1. 用EBP寄存器来寻址的时候,默认使用段寄存器SS;
2. 在32位模式下,栈操作的默认操作数大小是双字;
3. 处理器执行压栈指令的时候,如果发现操作数是段寄存器,则将段寄存器的16位值扩展为32位(高16位全0),然后执行压栈操作;出栈指令执行相反的操作,将32位的值截断,仅保留低16位,并传送到相应的段寄存器;
4. 由于load_relocate_program是通过32位近调用进入的(第843行),所以只压入EIP的内容,没有压入CS;

475         mov ecx,mem_0_4_gb_seg_sel
476         mov es,ecx
477      
478         mov esi,[ebp+11*4]                 ;从堆栈中取得TCB的基地址

以上三行执行完后,ES指向0-4GB数据段;ESI指向TCB的基地址。

5.2.2. 在TCB中填写LDT的基地址和初始界限值

GDT一般用于存放全局空间的段描述符。对于任务私有的段描述符,也可以放在GDT中,但是最好放在自己私有的LDT中。

480         ;以下申请创建LDT所需要的内存
481         mov ecx,160                        ;允许安装20个LDT描述符
482         call sys_routine_seg_sel:allocate_memory
483         mov [es:esi+0x0c],ecx              ;登记LDT基地址到TCB中
484         mov word [es:esi+0x0a],0xffff      ;登记LDT初始的界限到TCB中 

5.2.3. 从硬盘加载用户程序到内存

  1. ES指向0-4GB数据段;ESI指向TCB基地址;
  2. 根据头部信息确定用户程序需要多大的内存;
  3. 分配内存,并登记用户程序在内存中的基地址到TCB;
  4. 循环调用read_hard_disk_0加载用户程序;

5.2.4. 在LDT中安装描述符

  1. 安装头部段、代码段、数据段以及固有栈栈段的描述符(DPL=3);
  2. 这些描述符对应的选择子(令其RPL=3)要回填到用户程序头部;在LDT中安装的描述符,通常只由用户程序自己使用,所以请求者是用户程序自己,所以其选择子的RPL=用户程序的CPL=3;
  3. 头部段的选择子(令其RPL=3)还要登记到TCB中。

用户程序的头部的格式和第13章完全相同。
技术分享

5.2.5. 重定位符号表

这个过程和第13章的基本相同。注意,用户符号表中的调用门选择子,其RPL=3;
技术分享

5.2.6 创建0、1、2特权级的栈

通过调用门的控制转移,有可能会改变CPL。如果通过调用门把控制转移到了更高特权级的非一致代码段中,那么CPL就会被设置为目标代码段的DPL值,并且会引起堆栈切换。

为此,必须为每个任务定义额外的栈。对于我们的用户任务,需要为它创建特权级0、1、2的栈。而且,这些栈应当在LDT中有对应的段描述符。

这些栈是内核为用户程序动态创建的,而且需要登记在TSS中,以便处理器固件能够自动访问到它们。不过目前我们还没有创建TSS,所以,有必要先将这些栈的信息登记在TCB中暂时保存(如下图)。
技术分享

创建x(x=0,1,2)特权级的栈的步骤如下:
1. 申请内存,为栈分配空间;
2. 在LDT中创建栈段描述符(DPL=x);
3. 在TCB中登记栈的信息,包括栈的大小、基地址、选择子(RPL=x)以及ESPx的初始值(=0);
我觉得栈的大小和基地址的登记是没有必要的,因为TSS中不需要填写这些字段。

囿于篇幅,本文就到这里。劳逸结合,休息一下…

【未完待续】

任务和特权级保护(二)——《x86汇编语言:从实模式到保护模式》读书笔记32

标签:

原文地址:http://blog.csdn.net/longintchar/article/details/51472284

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!