可在内核态和用户态之间进行切换的ucore

在操作系统原理中,一直强调操作系统运行在内核态(特权态),应用程序运行在用户态(非特权态)。但为什么说处于用户态的应用程序就不能访问内核态的数据,而内核态的操作系统可以访问用户态的数据?我们有没有一个project来体验内核态和用户态的区别是什么?更进一步体验如何在内核态和用户态之间进行切换呢?project4.1.1为此进行了尝试。

实验目标

通过学习和实践,读者可以了解如何CPU处不同特权态下的执行特点和限制,理解如何从内核态切换到用户态,以及如何从用户态切换到内核态。

proj4.1.1概述

实现描述

proj4.1.1建立在proj4.1(当然是基于proj4)基础之上,主要完成了用户态(非特权态)与内核态相互切换的过程。相对于proj4,主要增加了两部分工作,一部分是从用户态返回内核态的准备工作,即建立任务段(Task Segment)和任务段描述符(SEG_TSS),设置陷阱中断号(T_SWITCH_TOK)和对应的中断处理例程。另外一部分是对内核栈进行各种特殊处理,使得能够完成内核态切换到用户态或用户态切换到内核态的工作。

项目组成

这里我们通过proj4.1.1来完成此事。proj4.1.1整体目录结构如下所示:

  1. proj4.1.1
  2. |-- kern
  3. | |-- init
  4. | | `-- init.c
  5. | |-- mm
  6. | | |-- memlayout.h
  7. | | |-- mmu.h
  8. | | |-- pmm.c
  9. | | `-- pmm.h
  10. | `-- trap
  11. | |-- trap.c
  12. | |-- trapentry.S
  13. | |-- trap.h
  14. | `-- vectors.S
  15. ……

相对于proj4,改动不多,主要修改和增加的文件如下:

  • memlayout.h:定义了全局描述符的索引值和一些段描述符的属性。
  • pmm.[ch]:为了能够使CPU从用户态转换到内核态,在ucore初始化时,设置任务段和任务段描述符,重新加载任务段和段描述符表;
  • trap.c:设置自定义的陷阱中断T_SWITCH_TOK(用于用户态切换到内核态)和实现对自定义的陷阱中断T_SWITCH_TOK/T_SWITCH_TOU的中断处理例程,使得CPU能够在内核态和用户态之间切换。

编译运行

编译并运行proj4.1.1的命令如下:

  1. make
  2. make qemu

则可以得到如下显示界面

3.5.2.1

通过上图,我们可以看到ucore在切换到用户态之前,先显示了当前CPU的特权级(CS的最低两位),CS/DS/ES/SS的值(即对应的段描述符表的索引值),可以看到特权级为0。根据lgdt函数(位于kern/mm/pmm.c中)的处理,CS的值是内核代码段描述符的索引下标,DS/ES/SS的值是内核数据段描述符的索引下标;而在切换到用户态后,又显示了一下,当前CPU的特权级为3, CS的值为1b,DS/ES/SS的值为23,把这四个寄存器的值&0xfc,则分别为0x18(SEG_UTEXT)和0x20(SEG_UDATA),说明确实运行在用户态了。在执行了系统调用T_SWITCH_TOK后,又回到了内核态。下面我们将分析到底发生了什么事情。