Skip to content

Latest commit

 

History

History
144 lines (104 loc) · 9.83 KB

PA3.1 Talk.md

File metadata and controls

144 lines (104 loc) · 9.83 KB

授课内容 PA3.1

作者:南京航空航天大学 计算机科学与技术学院 金航 未经作者允许,禁止转载

操作系统:更方便的运行环境

回想我们之前的运行环境由谁来提供?AM,但是AM并不是操作系统,其没有中断和文件的概念。

OS:提供文件管理服务,我们的程序都是在OS下运行的。现在我们要实现一个简单的操作系统支持dummy的运行。

什么是NANOS-LITE?

可以认为是我们运行在NEMU/AM之上的一个OS

现在的NANOS-LITE有什么功能?

  1. 输出两条log
  2. 初始化ramdisk:即内存模拟磁盘
  3. 初始化设备:调用AM的初始化函数
  4. 加载用户程序:通过loader将用户程序加载到指定位置(第一个任务)
  5. 跳转到用户程序入口点:即调用entry函数

NANOS-LITE运行用户程序的机理

  1. 先将待运行的用户程序放于ramdisk的映像中(编译阶段完成)
  2. 开始运行项目,首先加载ramdisk
  3. ramdisk中将程序读到指定内存处(运行NANOS-LITE后由loader完成)
  4. 调用放置好的程序

编译用户程序

  1. 按讲义要求编译出dummy-x86
  2. 执行make update更新ramdisk

这样以后,可执行文件就进入了ramdisk.img这个映像文件中。

注意:每需要更换ramdisk的内容都需要更新ramdisk

任务:实现loader

  1. 目前,我们只需要直接将程序读到0x4000000(即框架自带的DEFAULT_ENTRY宏定义)这个位置就可以了。使用ramdisk.c中提供的函数。
  2. 可执行程序在哪里?ramdisk偏移为0的地方。
  3. 可执行程序有多大?整个ramdisk那么大
  4. 在哪里实现loader?理所当然:loader.c
  5. 很简单,1行代码就能实现。
  6. 正常实现后,NEMU会报出未实现指令的错误(0xcd),这就是INT指令。请见后文。

注意:虽然说整个ramdisk中只有dummy-x86一个可执行文件,但是所有的库都已经被编译到这个可执行文件中了,这和我们平时写的程序的库函数调用过程不太一样(真正x86平台的程序通过动态链接库调用)。


系统调用

保护机制

一般来说,用户程序运行在第3级,而操作系统运行在第0级。保证了一般用户程序拿不到操作系统的部分特权。

如果用户程序试图去执行一条他的权限不够的特权级指令,CPU就会抛出一个异常。因此我们本章的内容就是异常控制流

这个特权级的检查本来是由分段机制负责控制的,但是我们的NEMU为了简单,不包含分段的保护机制。(理论课上,分段是个考试重点,这里我们不讲,需要的时候简单讲)

最典型的特权保护机制:银行取钱

注意:我们PA中所有的程序相当于直接运行在和操作系统位于同一级的第0级上。

系统调用

我们的操作系统有义务将部分特权安全地给客户程序使用,这就有了系统调用的存在。虽然我们的程序有权利能够执行这些特权指令,但是为了后续实验,我们仍要实现系统调用这个模块。

系统调用的接口

系统调用的传参

提问:回想我们C语言程序调用一个函数如何传参?传参的过程在机器级层面上是个什么样的过程?(答案:我们在C语言中函数的传参通常先将各个参数压栈,然后通过CALL指令跳转到函数体的入口点。这只是参数传递的方式之一,最简单的参数传递是寄存器传递,如果后面学微机原理,就会知道用寄存器传参是最好写的)

那么,我们系统调用不使用CALL指令,因为CALL指令只能调用用户态的函数,并不能完成特权级转换。这里我们引入了INT中断指令。再特殊一点,我们这里用到的是int 0x80的系统调用来陷入内核态

通过寄存器传参。系统调用参数的顺序一定要记住:EAX, EBX, ECX, EDX

系统调用的返回值

C语言函数的返回值是如何返回的?(答案:EAX寄存器带回)

那么,系统调用和函数调用一样,也是将返回值放入EAX之中带回。

系统调用的接口

Navy-app中,nanos.c_syscall_()函数。通过调用这个函数,就可以便利地帮我们编译出上述的五条指令(把参数值依次放入对应寄存器中并进行系统调用)以及带回返回值。

中断门

如何知道系统调用的代码入口点?

通过门描述符来找,门描述符的格式见讲义。这里特别指出:我们只需要关心两个用来被拼接在一起的地址字段P位

中断描述符表IDT与IDTR寄存器

将每个中断门描述符看作一个结构体,那么内存中有一段区域,这个区域存放着门描述符数组,这个数组就是IDT,即中断描述符表

这个表中存放着每一个中断入口地址,我们通过中断号作为索引在IDT中找到其对应的描述符,然后将该描述符中的地址域组合,得到中断服务程序的入口地址。

我们认为,中断的服务程序都是已经加载到内存中了,需要中断调用的时候直接经过一定手续,让EIP指向中断服务程序的入口点(中断服务程序的第一个指令)即可

我们如何能够找到IDT在内存中的哪个位置?那就是IDTR寄存器,它始终指向IDT表的首地址。

中断与系统调用的关系

我们说,执行一次指令INT 0x??就是一次中断调用,而当

0x?? = 0x80

时,这个中断为系统调用。因此,触发系统调用就是触发一个特殊的异常

重点:系统调用的过程(完成本部分代码的依据)

第一步:中断调用

我们说系统调用是一个特殊的中断调用,所以系统调用肯定需要经过中断调用的流程。详细流程见i386手册和讲义。这里简述如下:

  1. 保护现场(将EFLAGS, EIP, CS压栈)。为什么要保护现场?为了让程序返回调用点后仍然能保持原先的状态。
  2. 从IDTR中得到IDT首地址,并通过中断号IDT中寻址,找到一个门描述符。
  3. 得到门描述符后,首先检验门描述符的P位,若这个门描述符无效,请直接结束程序的运行并进行调试。
  4. 将门描述符的地址字段相拼接,成为跳转地址。然后设置跳转即可。

任务1:实现中断调用

  1. 在CPU结构体中添加IDTR寄存器(自行参考IDTR的结构),并实现LIDT指令(2行代码能够实现),定义宏HAS_ASYE。实现以后,项目就会在启动后对IDT进行初始化,初始化内容包括填IDT表注册处理函数。(这一步是加入AM为我们提供的ASYE,有兴趣自己看一下,很简单)
  2. 实现INT指令(1行代码能够实现),但是我们不要把处理过程写在INT里面,而是用INT去调用raise_intr函数。这个函数实现的内容即上述中断调用的过程(20行代码以内可以实现)。其中,门描述符GateDesc结构体框架已为我们写好,直接用就行。注意:这里讲义上有几个注意事项,不要忘记实现。
  3. 重新运行dummy程序,若出现了讲义说的未实现指令(0x60,PUSHA),说明正常实现。
  4. 实现PUSHA指令,用于将所有一般寄存器压栈,以保存现场,即程序在进行中断调用前的状态。
  5. 注意重新组织_RegSet的顺序,使得其内的寄存器指针或错误号等项目和陷阱帧一致。
  6. 重新运行dummy程序,在irq_handle中出现了HIT BAD TRAP,从提示消息知是8号事件(注意:一定是8号事件)未被处理,则说明正常实现。

第二步:系统调用

中断调用经过其自己通用的流程过后,将异常封装成了一个个事件,现在开始针对不同的中断类型进行处理,这就是事件分发,即对应我们的do_event函数。注意:我们并不是直接调用do_event函数,而是在初始化的时候把do_event函数注册为了事件分发函数,所以此时会调用这个函数。

我们前文提到的未处理的8号事件,可以去am.h中查一下,其实这就是一个_EVENT_SYSCALL,即系统调用事件。因此我们需要调用系统调用事件的处理函数去处理这个事件。

而我们的系统调用处理函数就是do_syscall,目前,我们这个阶段只需要处理2个类型的系统调用即可,即SYS_noneSYS_exit。具体来说,这个函数中做的事情就是得到四个参数(有可能有没用上的),然后根据中断调用类型选择合适的后续处理函数(本阶段暂不涉及,下阶段文件调用时会涉及)。处理完毕后,设置返回值到约定的地方(忘了吗?请看前文)即可。

任务2:实现系统调用

  1. do_event中识别系统调用并调用do_syscall
  2. 正确实现arch.h中的几个可以从现场寄存器中获取参数的寄存器的宏
  3. 完成并实现SYS_noneSYS_exit这两个系统调用
  4. 设置返回值到寄存器中
  5. 实现POPA(0x61)和IRET(0xcf)指令(调用的时候保护现场,调用完成后当然要恢复现场啦)
  6. 成功运行dummy项目并HIT GOOD TRAP

小结:系统调用涉及到的所有函数的顺序

注意:省去了初始化时候的部分调用过程以及NEMU中部分调用过程,仅写出和系统调用相关的

次序 函数名 所在项目 所在子项目 所在代码文件
1 main navy-apps dummy dummy.c
2 _syscall_ navy-apps libos nanos.c
3 make_EHelper(int) nemu - system.c
4 raise_intr nemu - intr.c
5 vecsys am x86-nemu tarp.S
6 asm_trap am x86-nemu tarp.S
7 irq_handle am x86-nemu asye.c
8 H(即do_event) nanos-lite - irq.c
9 do_syscall nanos-lite - syscall.c