之前做了那么多铺垫,我们终于可以看看第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相关推荐

  1. 访问数据段时的特权级检查,修改SS时的特权级检查——《x86汇编语言:从实模式到保护模式》读书笔记30

    1. 访问数据段时的特权级检查 为了访问数据段,数据段的选择子必须被加载进段寄存器(ES,ES,FS,GS,SS).在把一个段选择子加载进段寄存器之前,处理器会进行特权级检查(如下图所示). 在数值上 ...

  2. X86汇编语言从实模式到保护模式16:特权级和特权级保护

    目录 1. 特权级保护机制 1.1 基础段保护机制的不足 1.2 特权级划分 1.3 特权级的表示 1.3.1 当前特权级CPL 1.3.2 描述符特权级DPL 1.3.3 请求特权级RPL 1.4 ...

  3. x86汇编语言从实模式百度云_Intel x86 CPU 32位保护模式杂谈之任务切换 上

    目录: 什么是任务 任务由什么组成 任务门描述符是什么东东?有了TSS描述符为什么要有任务门描述符? 参考文献 什么是任务 任务(task)是处理器可以分配.执行.挂起的工作单位,笔者认为和我们操作系 ...

  4. 【OS修炼指南目录】----《X86汇编语言-从实模式到保护模式》读书笔记目录表

    学习交流加(可免费帮忙下载CSDN资源): 个人微信: liu1126137994 学习交流资源分享qq群1(已满): 962535112 学习交流资源分享qq群2: 780902027 本文是将个人 ...

  5. x86汇编语言从实模式百度云_x86汇编语言:从实模式到保护模式

    x86汇编语言:从实模式到保护模式2013年1月由电子工业出版社出版发行,总共6000行的源代码,全方位地向读者展现汇编语言程序设计之美.尽管汇编语言也是一种计算机语言,但却是与众不同的,与它的同类们 ...

  6. [书]x86汇编语言:从实模式到保护模式 -- 第14章 任务和特权级保护,调用门、LDT、TSS、TCB

    # 加载用户程序 Part 1.TCB, Task Control Block, 任务控制块 分配内存作为该任务的TCB,并插入至TCB链表. Part 2.LDT, Locak Descriptor ...

  7. ASM:《X86汇编语言-从实模式到保护模式》第14章:保护模式下的特权保护和任务概述...

    ★PART1:32位保护模式下任务的隔离和特权级保护  这一章是全书的重点之一,这一张必须要理解特权级(包括CPL,RPL和DPL的含义)是什么,调用门的使用,还有LDT和TSS的工作原理(15章着重 ...

  8. X86汇编语言从实模式到保护模式20:平坦模型

    1 引入平坦模型(Flat Model)的原因 1.1 内存管理模型变迁 1.1.1 分段模型 1.1.1.1 基本特点 1. 在程序中按结构组织为多个段 2. 在加载程序时,为程序中的每个段创建段描 ...

  9. 李忠 X86汇编语言 从实模式到保护模式-初学

    学习资料: 教学视频 网易云课堂 哔哩哔哩 原书网站 原书相关源码附件下载 网友帖子 除了后面没有图片之外很不错的笔记总结,写者很用心 留存待看,一片文章写了特点 很有特色总结的笔记 学习目标: 15 ...

最新文章

  1. android 布局中 layout_gravity、gravity、orientation、layout_weight
  2. javaScript Code 用javascript确定每月第二个星期五
  3. BootStrap 效果展示
  4. elasticsearch api中的Buckets(桶)及Metrics(指标)
  5. GM6 pageset - Cache get scenario /ui2/cl_pfcg_utils
  6. OpenCV与图像处理学习十二——图像形状特征之HOG特征
  7. 冲上热搜!快手宣布取消大小周
  8. 轻松掌握Redux-Action使用方法
  9. java 二叉树 遍历_JAVA实现二叉树(简易版--实现了二叉树的各种遍历)
  10. XPath解析网页学习笔记
  11. Java图书管理系统
  12. Maven实战(高清完整带书签)
  13. 企业级即时通讯通信平台的实现
  14. springboot Basic Auth 暴露API 访问认证
  15. Mockito使用简介
  16. 华为应用市场名称问题
  17. 2004古墓丽影黄金关卡——Lara在电影中:一号门
  18. 10月12日 阅读数异常公告
  19. 从键盘输入接收多个整数,直到输入quit的时结束输入,把输入过的整数倒序排序输出。
  20. uniapp-map基本使用

热门文章

  1. 《机器学习实战》chapter04 使用Python进行文本分类
  2. 在CentOS上安装和配置Guacamole
  3. centos 6 防火墙开启端口无效问题
  4. sublime使用技巧总结
  5. JavaScript编码encode和decode escape和unescape
  6. 各类最新Asp .Net Core 项目和示例源码
  7. 40岁后学编程(1)
  8. Go 语言web 框架 Gin 练习6
  9. [云炬python3玩转机器学习]6-3线性回归中的梯度下降法
  10. 科大星云诗社动态20210214