---------------------------------------------------------------------------------------------
JongXie OS 任务调度的实现
By 姜江 <jznsmail@tom.com>
http://blog.csdn.net/jznsmail
一、概述
=========
JongXie OS Project并没有采用调度算法的比较,择其最优,而是采用了最简单的时间轮片算
法。之所以采用该算法是因为我并不想研究各种算法的效率,而是研究任务是如何来调度的。如果
明白了基本的任务调度原理之后,采用其他调度算法也并非难事了。
二、任务调度概述
=================
目前的处理器速度可以说是越来越快了,但是外部设备的速度并没有伴随着处理器速度的提高
而提高,相反外部设备的速度于处理器相比起来是十分慢的。比如说,如果一个系统只支持一个任
务,当该进程需要从外部设备读取一些数据,那么,该进程首先会向CPU发送请求信号,CPU通知外
部设备准备,这时CPU什么都没干了,直到外部设备准备好请求的数据之后发送给CPU一个准备就绪
的回应信号时,CPU才通知该进程读取外部设备的数据。在这段外部设备的数据准备时间段里处理器
停滞了。处理器的低效率使得我们会想如何采用一种更好的策略来提高处理器的利用率,这样就引
入了任务调度的概念。当一个进程请求外部设备时,可以将该进程先调出休眠,而调度等待队列里
的另外一个新进程。在外部设备准备时间段里,处理器无须等待,而是利用该段时间来执行新进程。
当原进程所需要的数据准备好之后,处理器将当前执行的进程调出,放入等待队列里,然后重新唤
醒原进程取走数据。这样处理器的利用率随着空闲等待时间的减少而大大的提高了。随着任务调度
概念的引入,同样也引入了多任务操作系统的概念,即处理器可以同时起动多个进程,在宏观上来
看是同时执行多个进程,但是在处理器微观角度来看,一个时刻处理器只能执行一个进程,而其他
的进程则放入等待队列等待调度。
三、时间轮片算法
=================
时间轮片算法是由操作系统给每个进程分配一定的时间片段,这个时间片段是任意给的,但是
如果给他大也会造成处理器资源的浪费,如果给太小则会不断的进行任务的换进换出操作,也大大
降低了处理器的效率。该时间片段对于一个进程来说是处理器调度一次可执行的时间滴答数,当该
进程的时间滴答数到达最大允许值时,操作系统就会将起调出放入等待队列,然后重新从等待队列
里唤醒一个新的进程进行调度。因为时间片通常都很小,一般是几十毫秒,因此一秒内多个进程可
能会被交替切换几此,因此人们感觉多个进程是同时执行的。
四、任务调度的现场保护
========================
因为每个进程对于其他进程来说都是独立的,它有它单独的数据段、代码段、堆栈段等,因此
当时间片轮转完后,将换入新进程,而被换出进程的数据应该进行现场保护,以便下次唤醒时继续
执行。这跟中断处理程序的保护现场操作有点类似。任务调度的现场保护通常是保存当前即将换出
进程的一些处理器状态信息,而对于操作系统的设计来说,这些状态信息的集合就构成了我们通常
所说的任务状态结构(TSS)。
在设计JongXie OS的时候,我参看了Linux的源代码,在JongXie OS项目开发中,采用了类Linux
的任务状态结构。下面是TSS的结构图:
31 15 0
+--------------------------------------------+---------------------------------------------+
| Reserved(NULL) | Link(previous task segment descriptor) |
+--------------------------------------------+---------------------------------------------+
| *ESP0(Super privilege ESP pointer) |
+--------------------------------------------+---------------------------------------------+
| Reserved(NULL) | *SS0(Super privilege SS pointer) |
+--------------------------------------------+---------------------------------------------+
| *ESP1(Super privilege ESP pointer) |
+------------------------------------------------------------------------------------------+
| Reserved(NULL) | *SS1(Super privilege SS pointer) |
+--------------------------------------------+---------------------------------------------+
| *ESP2(Super privilege ESP pointer) |
+------------------------------------------------------------------------------------------+
| Reserved(NULL) | *SS2(Super privilege SS pointer) |
+--------------------------------------------+---------------------------------------------+
| *CR3(Page Directory Base Rgiseter) |
+--------------------------------------------+---------------------------------------------+
| EIP(Program pointer regiseter) |
+--------------------------------------------+---------------------------------------------+
| EFLAGS(CPU status register) |
+--------------------------------------------+---------------------------------------------+
| EAX |
+--------------------------------------------+---------------------------------------------+
| ECX |
+--------------------------------------------+---------------------------------------------+
| EDX |
+--------------------------------------------+---------------------------------------------+
| EBX |
+--------------------------------------------+---------------------------------------------+
| ESP |
+--------------------------------------------+---------------------------------------------+
| EBP |
+--------------------------------------------+---------------------------------------------+
| ESI |
+--------------------------------------------+---------------------------------------------+
| EDI |
+--------------------------------------------+---------------------------------------------+
| Reserved(NULL) | ES |
+--------------------------------------------+---------------------------------------------+
| Reserved(NULL) | CS |
+--------------------------------------------+---------------------------------------------+
| Reserved(NULL) | SS |
+--------------------------------------------+---------------------------------------------+
| Reserved(NULL) | DS |
+--------------------------------------------+---------------------------------------------+
| Reserved(NULL) | FS |
+--------------------------------------------+---------------------------------------------+
| Reserved(NULL) | GS |
+--------------------------------------------+---------------------------------------------+
| Reserved(NULL) | *LDT Selector Descriptor |
+--------------------------------------------+---------------------------------------------+
| *I/O bit map | Reserved(NULL) |
+--------------------------------------------+---------------------------------------------+
31 15 0
上面的结构中带有'*'号的不分是处理器只读的,而其他部分是处理器来填写的,当然要除了
Reserved部分,Reserved部分是不会使用的。处理器在进行任务切换的过程中,会自动的填写没有
'*'号的内容,这样一个TSS结构就保存了当前任务的所有必须的相关信息了。只读部分是说处理器
在任务切换时会从中读取相关信息,但是并不会在任务切换出去的时候保存它们。它们是在系统创
建任务的时候就已经指定好了的。在JongXie OS项目中,任务的切换是通过System_Timer中断处理
程序来处理的,它完成了处理器信息的保存设置工作和任务的切换工作。相关代码可以查看int.asm
文件。
五、TSS描述负和任务门
=======================
TSS结构是我们通过编成语言来构造的一个逻辑结构,但是处理器并不能直接保存该结构,需要
用到TSS描述负和任务门来保存指向TSS结构的一个指针。
TSS描述符结构:
15 7 0
+--------------------------------+--------------------------------+
| TSS Limit 0 ~ 15 bits |
+--------------------------------+--------------------------------+
| TSS Base Address 0 ~ 15 bits |
+--------------------------------+--------------------------------+
| P | DPL | 0 | 1 | 0 | B | 1|TSS Base Address 16 - 23 bits|
+--------------------------------+--------------------------------+
|TSS Base Address 24 - 31 bits| G | 0 | 0 |AVL|Limit16-19bits|
+--------------------------------+--------------------------------+
B:标志任务是否忙
DPL:特权级(2bits)
P:标志TSS是否存在在内存中
G:粒度位,在32位保护模式下应该为1
从TSS结构图中我们可以看出,TSS描述符指代了一个TSS结构,对于一般任务来说这个描述符已
经够了,但是INTEL公司在设计处理器的时候允许在中断过程中进行任务切换,因此我们可以将中断
处理程序作为一个专门的任务。但是中断描述符表中并不允许存放TSS描述符,而只能是门描述符,
因此TSS描述符不能保存在中断描述符,INTEL为此定义了一个任务门。任务门指向的是一个TSS描述
负,当中断产生时处理器通过中断号查询中断描述符表,获取相应的门描述符,如果获取的是一个任
务门,那么将通过它找到相应的TSS描述符,然后再通过相应的TSS描述符寻找到对应的TSS结构。下
图就是任务门的结构图:
15 7 0
+------------------------------+-----------------------------+
| Reserved(NULL) |
+------------------------------+-----------------------------+
| TSS Selector |
+------------------------------+-----------------------------+
| P|DPL |0 | 0 | 1 | 0 | 1 | Reserved(NULL) |
+------------------------------+-----------------------------+
| Reserved(NULL) |
+------------------------------+-----------------------------+
DPL:特权级(2bits)
P:标志TSS是否存在在内存中
操作系统中,TSS描述符放在全局描述符表中,因为它将会被处理器、中断服务程序、其他进程
访问。TSS选择符的作用就是在全局描述符表中索引找到相应的TSS描述符。
六、任务切换
=============
系统的GDT全局描述符表保存着TSS描述符和任务门,任务门是一个指向TSS描述符在GDT表中的索
引值,而TSS描述符指向的是一个TSS结构。同样在中断描述符表中也存在一个指向TSS描述符的任务
门。这样,有3种情况可能会引起任务切换:1.当使用了JMP或者CALL指令,并且指令的目标地址是指
向一个TSS描述符,或者指向一个任务门描述符,而该任务门描述符指向了一个TSS描述符时会引起任
务切换。2.产生中断调用,中断向量指向了中断向量表中的一个任务描述符时也会引起任务切换。3.
使用了IRET指令,并且在执行该指令的时候EFLAGS寄存器NT位被置位1时,同样也会引起任务切换。
下面我分这几种情况来讨论任务切换:
1.当操作系统通过一个JMP跳转指令跳转到一个TSS描述符,处理器将检查该描述符的特权级,和
B位是否为0,如果为0,那么处理器将目标任务的B位置1表示忙,然后把当前任务的B位置0。之后处理器
会进行任务切换的现场保护,将处理器状态信息放入相应的TSS结构中,并且从目标任务的TSS结构中
取出相应的信息,完成一次任务切换。
2.当操作系统通过CALL指令或者中断来切换任务,处理器将检查特权位和B位,然后还会将目标任
务的EFLAGS的NT位置1,并且将当前任务的TSS描述符放入目标任务Link字段中,之后,处理器把当前任
务的状态信息放入相应的TSS结构中来保护现场,并且从目标任务的TSS结构中取出需要的信息,完成
任务切换。
3.如果当前任务切换是由于IRET指令引起的,并且当前任务的EFLAGS的NT位是置1的,那么处理器
会依次完成特权检查,B位检查后,将当前任务的B位清0,并且将当前任务的EFLAGS的NT位清0,然后处理
器将当前任务信息保护起来,放入TSS结构中,并且取出目标任务TSS结构中所需要的信息,完成任务
切换。
七、参考文档
1.Intel 80386 Programmer's Reference
2.Linux source code