MyOS 之 调度与特权级

区分出特权级才能区分出用户程序,否则都是内核程序有什么用。。。

现在定义超过4MB的进程都是用户级进程。

现在都是在4MB内核级内,没有用也没必要实现对吧,必须有用,比如左边命令行与右边的文件显示器,一开始就是初始化成这样的,当然,都是内核级进程。

文件管理,其实一开始就提到了,在ipl的时候,我们曾一个扇区一个扇区地往内存中拷贝,就是这样的,如果有文件管理功能,我们就要设计文件系统。

对于应用级程序来说,只能说内核初始化的时候一个init_app来进行部署,就是这样。

因此现在就是熟悉特权级,启动一大批应用级进程,然后再开始精细化调度,然后实现命令行来启动进程。

 操作系统的保护模式相对于实模式最大的改变之一就是安全性的提升,这是通过引入权限概念来实现的。操作系统内核作为直接控制硬件设备的底层软件,权限最高;而驱动程序、虚拟机等系统程序的权限则次于内核(通常也称为服务);用户程序的权限最低,位于特权级最外层。

TSS相当于一个任务的标识,相当于Task ID。其中涉及到三对栈指针,这是与特权级栈密切相关的东西。 一个任务的执行分为用户部分和内核部分,由于内核程序位于0特权级,而用户程序位于3特权级,这个是怎么运作的呢?

任务是由处理器执行的,任务在特权级变换时,本质上是处理器的当前特权级在变换。为了避免栈溢出和程序混乱,操作系统规定每个任务的每个特权级下都有且只有一个栈。而TSS中的三对栈指针分别代表了0~2的三个特权级栈(一对指针由段选择子和偏移量组成)。特权级在变换时,需要用到不同特权级的栈。

特权级转移分为两类,一类是由中断门、调用门等手段实现低特权级转向高特权级,另一类则是由调用返回指令从高特权级返回到低特权级(但注意高特权级数小,低特权级数大)。

关键就是在于低特权级要转移到高特权级,也就是用户态转移到内核态,就需要在TSS段的环栈添加这个内核栈,需要提前把目标栈的地址记录在TSS中,此外,不是每个任务都有4个栈,比如1特权级的程序,它可以提升一级,因此只需要0特权级的栈就够了。

TSS只会有比当前特权级高的特权级栈的地址信息。正常情况下,特权级由低转高在先,由高返回低在后,因此只有先向高特权级转移才谈得上从高特权级返回到低特权级。低特权级的栈信息被压到了高特权级的栈中,这是自动的,是硬件执行的。

  保护模式下的内存段的特权级由段描述符给出,段描述符的DPL字段就标识了该段的特权级,而段选择子的RPL(请求特权级)字段则标识了访问者的特权级。CPL(上一个段,也就是本段的8字节描述符上的DPL)表示处理器的当前特权级,其值为CS选择子中的RPL(起一个限制的作用,继承而来,只能更弱)字段值。

在不考虑RPL的情况下,对于受访者是数据段来说,访问者的权限必须大于等于受访者的DPL才能够访问,而对于受访者是代码段来说,访问者的权限必须等于受访者的DPL才能访问,即必须平级访问(除了中断处理程序会让处理器返回到用户态)。

在段描述符中有一致性代码段的标志位,用来实现从低特权级到高特权级的代码段转移。只有代码段才有一致性和非一致性之分,数据段没有。它的特点是转移后的特权级一定会更大(或者平等),但是特权级不变(还跟原来一样,意味着不能读取特权级更高的数据。)也就是说,当处理器遇到目标段为一致性代码段时,并不会将CPL的数值替换为目标段的DPL。低特权级的代码段可以在不切换进程特权级的情况下执行高特权级的代码,这看似很不安全,但由于访问权限还受制于RPL,因此低特权级的程序并不能为所欲为。

 操作系统提供了一致性代码段和门结构来实现从低特权级到高特权级的代码段转移,这会给恶意攻击者用低特权级的程序访问高特权级资源,使攻击者有诸如篡改内核之类的危险操作的机会,因此必须在CPL和DPL的基础上增加条件。这既是RPL。RPL代表了真正资源请求者的特权级,也就是第一个发起请求的特权级,也就是RPL是继承的。它被动态放在了cs寄存器里。这样,当一个低特权级的进程委托一个高特权级的进程去执行另一个高特权级的进程,就会原形毕露。毕竟一致代码段也算高特权级的进程不是?一致性代码段是可以访问非一致代码段的,毕竟同在内核态,CPL是一致的,但是RPL有可能不一致,因为RPL一直都是最初的进程的。

有了RPL后,访问内存段的特权检查规则如下(不通过调用门):

       ·如果目标为非一致性代码段,要求数值上:CPL=RPL=目标代码段DPL,这个很好理解,就是内核态自产自销。

       ·如果目标为一致性代码段,要求数值上:CPL>=目标代码段DPL && RPL≥目标代码段DPL,当RPL的值比CPL大的时候,RPL将起决定性作用。

       ·如果目标为数据段时,要求数值上:CPL≤目标数据段DPL && RPL ≤ 目标数据段DPL,就是特权级必须高。

       栈段的特权级检查比较特殊,因为在各个特权级下处理器都有对应的栈,所以往段寄存器中赋予选择子时,要求CPL等于栈段选择子对应的数据段的DPL,即数值上CPL=RPL=用作栈的目标数据段DPL。

因此,设定4个段,序号1,序号2是ring0的数据段,代码段,序号3,序号4是ring3的数据段,代码段。

进程的调度是通过时间中断实现的,实际上调度程序被写在一个时间中断函数里,这个中断函数自然是内核级的,因此当系统运行起来后,整个操作系统就会被简化为这个调度函数,所有的进程都异于这个调度进程。唯一不需要调度的就是这个调度进程。调度进程必然是内核级的,操作系统的五大核心功能也都是内核级的,因为它们需要内核级的汇编指令(也不一定哈)。

可能除了调度进程,其他进程都没必要呆在内核态。不是说不能实现,而是从实用主义上将没必要。

一般高特权级的例如内存管理(你不能让一个用户级进程去访问其他用户级进程),文件管理(你不能让一个用户级进程随便读写磁盘),网络功能,这些都应该是内核态。

用户级进程,去访问内核级代码,就是这样。这只是最最最基本的权限管理,仅仅这些也是不能够实现全部的权限功能。

因此你怎么做呢?就是在系统初始化之初,初始化一堆进程插入进程表,让它们自己调度着玩。

等到实现命令行,就实现新建一个进程插入到进程表让它们调度着玩。

等到实现界面,双击获取信息,插入到进程表让它们调度着玩。

在windows上,打开一个程序是从磁盘里复制到内存里然后开始执行的,我没有磁盘,因此我只是将代码插入到4mb内核里,只是不执行它,等到调用的时候,开辟内存,拷贝过去,就相当于从磁盘中读取了。当然,所有的代码我都调试好了。

例如一个记事本程序,可以初始化它的时候开辟一块内存区当作模拟磁盘,记事本本身也有编译按钮,编译就是将写的指令编译成i386代码,存放在虚拟硬盘里,然后记事本有执行这段指令的按钮,然后这段指令就会被存入内存,存入进程表,开始执行。

这注定是一项长久的工程啊。。。

关于调度,自然是用中断调度,每隔一段时间激发调度函数,这里有两种方法:

第一种是在wait_1s里面调用调度函数,这样调度函数就可以实现频率调用了。

第二种是在调度函数里调用wait_1s,如果一直没到点就一直循环,到点了就开始调度。好处是耦合性很小。

在时间中断里调用wait_1s,在wait_1s里调用进程调度函数,那么,主要的循环就变成了

fin:

hlt

jmp fin

就一直等待中断了,中断才是系统的本体。

进程表是自己创建的结构,是调度进程使用的,通过在进程表中使用调度算法实现程序的依次执行,现在就按照最简单的轮询就可以了。

TSS是被存到GDT中,是无法得知究竟有几个进程的,因此必须有自己的进程表,PT,一般来说进程表是一个链表,但是就与汇编来说,链表的实现出奇的难啊。。。还是说固定数组吧,固定最大进程数是GDT中可以有8192个描述项。除一些系统的开销(例如GDT中的第2项和第3项分别用于内核 的代码段和数据段,第4项和第5项永远用于当前进程的代码段和数据段,第1项永远是0,等等)以外,尚有8180个表项可供使用,所以理论上系统中最大的 进程数量是4090。为啥折半了呢?

这个表就是存储了 TSS段选择子,没了,干脆还不如直接遍历GDT表,有一位是可以表示是否使用的,当前用的哪一个TSS的选择子存起来,遍历就依次遍历即可。

首先申请一块4K内存,用于页目录表(初始化成0),存入TSS,然后规划页表,填入页目录表后,将代码移过去(一次移过去4K),然后再注册TSS,设置后注册进GDT,这时候就会自动调度了。

eax,程序地址。

call App_Init ;一次只开辟一个4K页

call Memory_Use_4k ;物理地址放在eax,这是页表

; 复制代码

call Memory_Use_4k ; 这是页目录表

填入页表

call 4k ; TSS

初始化TSS,填入TSS

填入GDT表

现在就可以调度了。

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值