没有合适的资源?快使用搜索试试~ 我知道了~
写在前面在本节中,同学们将会学习到如下知识。学习保护模式特权级的基本概念。学习系统调用的实现。学习用户进程的实现。学习实现特权级转移。浅谈特权级在本章中,我们将
资源详情
资源评论
资源推荐
第6章 从内核态到用户态
仰之弥高,钻之弥坚,瞻之在前,忽焉在后。
第6章 从内核态到用户态
写在前面
浅谈特权级
系统调用的实现
Assignment 1 第一个系统调用
进程的实现
进程创建前的准备
初始化TSS和用户段描述符
进程的创建
进程的调度
Assignment 2 第一个进程
Assignment 3 printf的改造
练习
bonus
写在前面
在本节中,同学们将会学习到如下知识。
学习保护模式特权级的基本概念。
学习系统调用的实现。
学习TSS。
学习用户进程的实现。
学习实现特权级转移。
浅谈特权级
在本章中,我们将会实现从内核态到用户态和用户态到内核态的转移。实际上,内核态和用户态只
是两种特权级0和3的别名而已。在CPU中,CPU可以识别的特权级只有4个,0,1,2,3。其中,从0
到3,特权级依次降低。在我们的实验中只会用到两个特权级,0和3,因此我们把CPU处在特权级0的状
态称为内核态,把CPU处在特权级3的状态称为用户态。
为什么要区分内核态和用户态呢?因为我们的操作系统是支持多任务的,每一个任务都是一个独立
的程序。如果我们任由这些程序能够使用特权代码,如访问硬件,修改其他任务的页目录表和页表,那
么就会对系统的运行环境造成巨大的影响。因此,我们必须区分程序在什么环境下执行什么样的代码,
例如只有当CPU处在内核态时才可以执行特权代码。
因此,当用户程序运行在用户态后,CPU无法执行特权代码。如果程序需要访问磁盘,那么程序首
先需要进入内核态,然后CPU才能访问磁盘读写端口来访问磁盘,访问完毕后,CPU再从内核态返回到
用户态。
实际上,内核态和用户态的划分是对程序访问资源的限制。代码是一种资源,数据和栈等也是一种
资源。因此,内核态和用户态的划分也会对程序访问数据和栈造成限制。那么CPU是如何得知自己处于
用户态还是内核态的呢?CPU又是如何知道哪些代码、数据和栈等资源是特权代码,不允许用户态程序
访问,哪些资源又是允许用户访问的呢?这就涉及到CPL,RPL,DPL和特权级检查的内容了,如下所
示。
RPL,Request Privilege Level,段选择子的低2位。
CPL,Current Privilege Level,也就是放入了CS寄存器的段选择子的RPL,CPL标识了CPU
处在内核态还是用户态。
DPL,Descriptor Privilege Level,位于每一个段描述符中。
一致性代码段,简单理解,就是操作系统拿出来被共享的代码段,是可以被低特权级的用户直
接调用访问的代码。在访问前后,特权级不会改变,用户态还是用户态,所以被称为一致的。
非一致代码段,为了避免低特权级的访问而被操作系统保护起来的系统代码,只允许同级间访
问,绝对禁止不同级访问,核心态不用用户态的资源,用户态也不使用核心态的资源。
因此,特权级检查如下。
对于数据段和栈段,访问前进行特权级检查,要求 。
对于代码段,如果是一致性代码段,要求 ;对于非一致性代码段,要求
, 。
当我们想要从内核态进入用户态,或从用户态进入内核态时,我们就要进行特权级转移。特权级转
移有两种方式,如下所示。
从低特权级向高特权级转移。从低特权级向高特权级转移是通过中断,调用等方式来实现的,
但这里我们只使用中断的方式来实现特权级转移。程序通过使用 int 指令来调用指定的中断,
然后中断描述符中的代码段选择子会被加载到CS寄存器中,从而改变了CPL,实现特权级转
移。
从高特权级向低特权级转移。从高特权级转移向低特权级转移只能通过中断返回和调用返回来
实现。
CPU在不同特权级下会使用不同的栈,当CPU使用中断从低特权级向高特权级转移的时候,CPU首
先会将高特权级栈的段选择子和栈指针送入SS,ESP,然后将中断发生前的SS,ESP,EFLAGS、CS、
EIP依次压入高特权级栈。不同特权级栈的选择子和段指针是保存在TSS(Task State Segment)中的,如
下所示。
注意,TSS中只会保存特权级0,1,2的段选择子和栈指针,因为没有比特权级3更低的特权级向其
转移。TSS的作用不只是用于保存不同特权级栈的信息外,实际上CPU提供了原生的多任务切换机制,
TSS是使用来保存任务切换时任务的状态的,也就是说,每一个任务都有一个TSS。但我们并不使用这个
原生的机制,进程的状态被保存在PCB中。
但是,当高特权级向低特权级转移时,我们并不需要给出低特权级栈的信息。因为CPU是禁止高特
权级向低特权级转移的,只有一种情况除外,就是中断返回或任务返回, iret 和 retf 。因此,CPU默
认高特权级向低特权级转移的情况是中断或调用处理完成,然后返回,那么低特权级栈的信息在进入中
断前被保存在高特权级栈中,因此返回后就可以恢复低特权级栈的SS和ESP。
关于特权级的事情我们就点到为止,但也足够我们去实现用户进程和进程切换了。
系统调用的实现
操作系统为了限制程序访问特权资源,于是将程序的执行限制在特权级3下,称为用户态。但是,操
作系统会向程序提供一系列服务的约定,如访问磁盘、内存分配和输入输出等。此时,为了使用这些服
务,程序需要通过中断、调用等方式从用户态进入内核态,由操作系统内核来完成这些服务,然后再从
内核态返回用户态,最后得到服务的结果。整个过程被称为系统调用,system call。
在第2章中,在代码段、数据段和栈段的段描述符中,我们将DPL设置为0,因此这几个段也就是特
权资源所在的段了。注意我们将这几个段设置为非一致性代码段。而非一致代码段要求CPL=DPL才能访
问代码段。因此,当我们需要访问非一致代码段时,要求CPL=0。注意到我们的进程运行时的CPL=3,
因此当我们从用户态进入内核态时,需要进行特权级转移,将RPL=0的段选择子送入CS。这里,我们使
用中断来实现特权级转移。此时,在我们的系统中,系统调用就是调用软中断,中断处理和中断返回的
过程。
我们现在来看看如何使用中断来实现系统调用。
Assignment 1 第一个系统调用
首先,系统调用的函数声明如下。
系统调用是操作系统向用户程序提供的一系列服务的约定,实际上这些服务就是一个个的能够处理
用户程序不同系统调用的函数。这些函数的地址被统一放在系统调用表(system call table)中,函数参
数 index 是用户程序希望调用的系统调用号,系统调用号是由操作系统向用户程序提供的。实际上,系
统调用号就是处理系统调用的函数在系统调用表中的位置。
我们后面会知道,我们使用中断来进行系统调用时,系统调用的参数是放在5个寄存器当中的,因此
系统调用的参数不可以超过5个。但是,为什么不放在栈上面呢?这是因为用户程序使用系统调用时会进
行特权级转移,此时进入系统调用后,CPU使用的栈是高特权级的栈,而我们在放置参数的栈是低特权
级的栈,因此CPU可能会找不到函数的参数。为了简便起见,我们就将系统调用的参数通过寄存器来传
递。
这个函数是通过汇编来实现的,如下所示。
extern "C" int asm_system_call(int index, int first = 0, int second = 0, int third = 0, int forth
= 0, int fifth = 0);
1
asm_system_call:
push ebp
mov ebp, esp
pushad
push ds
push es
push fs
push gs
mov eax, [ebp + 2 * 4]
mov ebx, [ebp + 3 * 4]
mov ecx, [ebp + 4 * 4]
mov edx, [ebp + 5 * 4]
mov esi, [ebp + 6 * 4]
mov edi, [ebp + 7 * 4]
int 0x80
mov [ASM_TEMP], eax
pop gs
pop fs
pop es
pop ds
popad
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
系统调用的中断向量号是 0x80 。在调用中断前,我们先保护现场,将系统调用的参数放到5个寄存
器ebx, ecx, edx, esi, edi中,将系统调用号放到eax中,然后使用指令 int 0x80 调用 0x80 中断。中断
返回后,根据C语言函数调用规则,函数的返回值是放在eax中的,由于后面的 popad 指令会影响eax,
因此我们先将eax保存在 dd 类型的变量 ASM_TEMP 中,如下所示。
恢复现场后再恢复eax,最后返回即可。
然后,我们创建一个管理系统调用的类 SystemService ,如下所示,代码放在 syscall.h 中。
其中, syscall_0 是我们第一个系统调用,其作用是打印输入的5个参数,然后将它们加起来返
回。同样地,我们在 os_modules.h 中创建它的实例。
pop ebp
mov eax, [ASM_TEMP]
ret
27
28
29
30
ASM_TEMP dd 0
1
#ifndef SYSCALL_H
#define SYSCALL_H
#include "os_constant.h"
class SystemService
{
public:
SystemService();
void initialize();
// 设置系统调用,index=系统调用号,function=处理第index个系统调用函数的地址
bool setSystemCall(int index, int function);
};
// 第0个系统调用
int syscall_0(int first, int second, int third, int forth, int fifth);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef OS_MODULES_H
#define OS_MODULES_H
#include "interrupt.h"
#include "stdio.h"
1
2
3
4
5
剩余25页未读,继续阅读
学习呀三木
- 粉丝: 22
- 资源: 303
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
评论0