Write An OS In A Week(3)

萝卜甲@ZJU 2004 yujiazi@gmail.com

转载请保留以上信息

 前面搞了两天,连个OS的影子都没见着。不要着急,今天我们就开始进入正题了,我们要来看看一些跟OS运作密切相关的一些硬件机制以及过程:中断,时钟以及进程切换。

 先来看看OS的一般结构吧,如图:

       -----------------------------------------------------
       |                  User application                 |
       -----------------------------------------------------
       -----------------------------------------------------
       |                   User library                    |
       -----------------------------------------------------
       -----------------------------------------------------
       |                    OS Kernel                      |
       -----------------------------------------------------
       -----------------------------------------------------
       |                    Hardware                       |
       -----------------------------------------------------

 一般把User application和User library都看成User application,这样就是一个三层的结构: User application, OS kernel, hardware。User application通过系统调用(System call,一般是一个中断调用)来调用Os kernel的功能,比如打开一个文件,通过网络发送数据等等。从上面结构可以看到,真正的软件部分只有两层:OS Kernel和User Application。所以CPU一般被设定为两个执行模式:Kernel Mode和User Mode。Kernel mode是特权级别,在Intel的PC下一般为特权级0,就是Ring0,而User Mode一般为特权级3,Ring3。在Kernel mode下,系统具有所有的权利,可以访问一切内存以及其他硬件资源而不受限制,而User mode下只能访问由Kernel授权的内存空间以及硬件资源,这样就可以做到资源分配和管理。一般Kernel对User采取的"不信任"策略,所谓"不信任"策略就是Kernel认为所有的User application都是恶意的,不管这个恶意是有意的(病毒)或是无意的(应用程序Bug)。之所以采取"不信任"策略是因为当无法判断对方是好是坏时我们宁可认为他都是坏的(宁可错杀一千,也不可放过一人啊...)。

 当CPU执行到系统调用中断指令的时候,就会跳转到特定的位置,这个位置放的就是OS kernel的代码,然后OS kernel就取得了系统的控制权,执行一系列的操作,完了以后就返回到User Mode继续执行原来的进程。当然每次返回都不一定返回到原来中断时在运行的进程,而是有可能返回到另外一个应用程序,这就是所谓的进程切换。这样原来被中断的进程的执行状态被保存下来,新的进程执行状态被恢复。CPU就是在不同的进程以及Kernel之间不停地忙碌。

 说到底了多进程就是CPU在不同的进程间快速切换造成所有进程都在同时运行的假象。这里就涉及到进程状态的保存问题,进程的状态是个很抽象的东西,到底包含了哪些东西呢?我把它总结一下,首先是CPU的执行状态,包括所有通用寄存器,段寄存器,状态寄存器等,再就是进程的堆栈,还有就是进程所拥有的系统资源,比如打开的文件等。CPU执行状态需要保存,堆栈只需要保存堆栈的指针,系统资源的具体内容保存在Kernel的数据结构中,所以不需要保存。

 说了这么多抽象的,其实整个原理都是靠中断机制来维持的。我们来看看IA-32下的中断切换机制吧。学过8086汇编知道,8086的有一个叫中断向量表IVT(Interrupt Vector Table),每当产生中断时,CPU从IVT中取得中断服务程序(ISR)的位置。IA-32的基本原理也差不多,只不过多了一些附加的功能而已。IA-32下也有一个类似于IVT的东西,叫做IDT(Interrupt Descriptor Talbe),这个IDT就是类似于前面的GDT和LDT,只不过他里面存放的是很特殊的Descriptor,叫Interrupt Descriptor,每个Descriptor对应一个中断(IA-32最多支持256个中断),每个Descriptor描述了该中断的位置(相当于IVT里面的CS和OFFSET),调用该中断的权限以及一些其他的信息。IDT的位置由IDTR指向。

 这里插播一个附带的内容就是IA-32提供的硬件支持的进程切换的机制。IA-32从硬件上支持进程切换,但是由于这个IA-32特有的,所以考虑到移植性和通用性我们不使用它提供的方法,而是使用自己的办法来实现进程切换。但是由于该机制无法被忽略,所以我们是用了对付段机制一样的办法,设定一个结构对付他一下便完事。不过我们的进程切换还是会用到他的一部分功能,就是我们的中断切换堆栈时需要从这个机制中取得内核堆栈的段寄存器SS和内核堆栈指针ESP。仅此而已。在此我们忽略过,到具体代码实现的时候再来看一下。

 好了,我们来看一下一个中断的具体过程。当一个中断发生时(软件中断,或者硬件中断:包括CPU内部中断硬件外部中断),CPU根据IDTR的内容取得IDT的位置(只是一个线性地址),然后取得该中断的Descriptor(比如该中断为10号中断,则取IDT中的第10个Descriptor)。该Descriptor中包含了中断服务程序(ISR)的段地址和偏移地址,CPU在进行一些列权限以及合法性检查之后就跳转到ISR,而ISR的地址就是我们先前设置好的,在内核的代码,这样内核就取得了控制权。中断发生时,堆栈的情况如下:

 如果中断发生在Ring0,就是说不需要权限切换,所以不会切换堆栈,如下:

 Interrupted Procedure's
 and Handler's stack 

        |         |
        |         |
        -----------
        |         | <- ESP before interrupt
        -----------
        |  EFLAGS |
        -----------
        |   CS    |
        -----------
        |   EIP   |
        -----------
        |ErrorCode| <- ESP after interrupt
        -----------
        |         |
        |         |

 如果中断发生在别的权限,则会切换堆栈,如下:

 Interrupted Procedure's                     Handler's Stack
 Stack

        |         |                                 |         |
        |         |                                 |         |
        -----------                                 -----------
        |         | <- ESP before interrupt         |         |
        -----------                                 -----------
        |         |                                 |         |
        -----------                                 -----------
        |         |                                 |   SS    |
        -----------                                 -----------
        |         |                                 |   ESP   |
        -----------                                 -----------
        |         |                                 |  EFLAGS |
        -----------                                 -----------
        |         |                                 |   CS    |
        -----------                                 -----------
        |         |                                 |   EIP   |
        -----------                                 -----------
        |         |         ESP after  interrupt -> |ErrorCode|
        -----------                                 -----------
        |         |                                 |         |
        |         |                                 |         |


 我们可以看到,堆栈切换时,需要制定一个新的堆栈位置,这个位置就是由刚才提到的硬件进程切换机制提供的SS和ESP,我们只需要设置好这两个就行了,而不需要估计硬件进程切换机制的其他东西。这里堆栈里面的内容都是被中断的程序的状态,ErrorCode是由CPU产生的,用来描述一些CPU内部中断时的状态。

 IA-32支持最多256个中断,前32个为Intel保留的,留给CPU作为内部中断。32-255作为用户中断,可以由用户指定,外部中断也可被指定到32-255之间。


 接下来就是时钟机制啦,时钟机制是整个OS运作的动力,OS在时钟不停地激励下运行。时钟中断在PC下由一块硬件的芯片产生,这块芯片叫Programmable Interval Timer(PIT),一般使用Intel 8253/8254芯片,这个应该是我们熟悉的,它可以被设定为每隔一段时间产生一个外部中断。进程调度就是在这个时钟的推动下不停的进行的。这个时钟芯片很好搞,找个资料把它设定为100Hz产生中断的频率就OK了。做个微机接口实验应该还记得有个PIC( Programmable Interrupt Controller )吧,把外部中断设置到32-47,也就是0x20-0x2F。我们选定0x30为系统调用中断。这样的话我们的中断分布如下: 0x00 - 0x1F为CPU保留中断,0x20-0x2F为外部中断,0x30为Systemcall。

 OK,就让我们来看一段核心代码,来实现中断处理和进程切换吧。

 AllInterrupt:
 ; push all the registers including segment registers
 pusha
 
 ; load kernel segment registers
 mov ds, kernel segment
 mov es, kernel segment
 mov ss, kernel segment

 call SysInt   ; 调用C写的中断处理函数
 
 cmp [OldProcess],[CurrentNewProcesss]
 je NoProcessSwitch
 
 ; here switch process
 mov [OldProcessESP],esp  ; 保存旧的 ESP
 mov esp,[NewProcessESP]  ; 装入新的 ESP

 NoProcessSwitch:

 ; pop all the registers including segment registers
 popa
 iret

 这个就是核心切换机制的代码,每当一个中断发生时,CPU都最终跳转到AllInterrupt,然后保存所有的寄存器,接着跳转到SysInt,这个就是用C写的一个函数,里面就发挥你的想象了,Kernel该做些什么都在这里了。最后判断一下是否切换进程,如果切换的话则切换堆栈,iret到相应的进程。

 当然实际过程比这个要复杂,涉及到一些细节问题,我们这里就不考虑了,抓住关键嘛!

 至于SysInt里面怎么样,怎么样调度,什么样的算法等等都是OS理论里面的事情了,抓本书看看就行了。

 hoho,怎么样?是不是OS也不是很神秘的东西?OK,让我们保持精力,等待第四天的到来!!(汗啊,我已经累死了,打那么多字555)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值