现在的位置: 首页 > 综合 > 正文

保护模式下的8259A芯片编程及中断处理探究(下)(Version 0.02)

2012年08月16日 ⁄ 综合 ⁄ 共 4608字 ⁄ 字号 评论关闭
保护模式下的8259A芯片编程及中断处理探究(下)(Version 0.02)
作者:谢煜波 文章来源:《纯C论坛·电子杂志》 点击数:

964 更新时间:2004-12-11

此文原刊发于:《纯C论坛·电子杂志》2004.11(总第二期)


保护模式下

8259A芯片编程及中断处理探究(下)

Version 0.02

哈尔滨工业大学 并行计算实验室 谢煜波

简介

在上篇中,我们详细讲述了保护模式下中断处理的基本原理以及对可编程中断控制器8259A的编程方法。如果说上一篇更偏重于原理及特定的硬件编程方法,那么本篇就会偏软一点,将详细描述怎样编写操作系统中的中断处理程序,并将通过pyos进行验证。在此篇中,你将会详细了解到操作系统是怎样处理中断的,中断处理程序是怎样编写的,操作系统又是怎样调用中断处理程序的。希望本篇可以使你对上述问题有个比较清晰的认识。

本篇是独立的,当然,如果你阅读了上篇,那么对于理解本篇中所描述的内容无疑是有巨大帮助的。

pyos是一个实验性的操作系统,阅读本篇之后,你可以尝试着改动pyos中的中断处理部份,这样你将更可以详细而深入的理解多重中断、现场保护等内容,本篇在最后也将对于怎样进行这样的自我实验做些许描述。如果你在学习“操作系统”或“组成原理”的过程中,对于书中描述的内容感到不太直观,你可以试试用pyos去验证你所学习的知识。

再次声明:此文只是我在进行操作系统实验过程中的一点心得体会,记下来,避免自己忘记。对于其中可能出现的错误,欢迎你来信指证。

 

一、操作系统中断服务概述

现代计算机如果从纯硬件角度,我个人更倾向于将它理解为是利用的一种所谓的“中断驱动”机制,就相当于我们常常津津乐道的Windows的“消息驱动”机制一样。CPU在正常情况下按顺序执行程序,一旦有外部中断到来,CPU将会中断现行程序的运行,转到中断服务程序进行中断处理,当中断处理完成之后,CPU再回到原来执行程序被中断的地方继续执行,并等待下一个中断的来临。

CPU需要响应的中断有很多种,比如键盘中断、磁盘中断、CPU时钟中断等等,每种中断的功能都是不同的,而所需要的中断服务程序也是不同的,CPU又是怎么识别这种种不同的中断的呢?

 

二、中断描述符表及中断描述符

在上一篇中,我们知道了CPU是通过给这些不同的中断分配不同的中断号来识别的。一种中断就对应一个中断号,而一个中断号就对应一个中断服务程序,这样,当有中断到来的时候,CPU就会识别出这个中断的中断号,并将这个中断号作为一个索引,在一张表中查找此索引号对应的一个入口地址,而这个入口地址就是中断服务程序的入口地址,CPU取得入口地址后,就跳转到这个地址所指示的程序处运行中断服务程序。

这张存放不同中断服务程序的表在系统中常常称为“中断向量表”。在保护模式下也常常称为“中断描述符表”(IDT,这个表中的每一项就是一个中断描述符,每一个中断描述符都包含一个中断服务程序的地址,CPU通过将中断号作为索引值取得的就是这样一个“中断描述符”,通过“中断描述符”,CPU就可以得到中断服务程序的地址了,下面,我们就来看看中断描述符的结构:



(
图一)

上图就是一个“中断描述符”的结构,其中P位是存在位,置1的时候就是指这个描符述可以被使用;DPL是特权级,可以指定为0~3中的一级;保留位是留给Inter将来用的,在现阶段,我们只需要简单的将其置零就可以了;偏移量总共是32位,它表示一个中断服务程序在内存中的位置。由于保护模式下,内存的寻址是由段选择符与偏移量指定的,所以在中断描述符中也分别设定了段选择符与偏移量位,他们共同决定了一个中断服务程序在内存中的位置。有关保护模式下内存的寻址方面的描述,可以参看本系列的另一篇文章:《操作系统引导探究》(编者注:此文在本刊第一期上刊载)

在前面我们描述了,一个中断描述符是放在一张中断描述符表中的,而中断号就是中断描述符在中断描述符表中的索引或说下标。那么系统又怎么知道中断描述符表是放在什么地方的呢?这在系统中是通过一个称之为“中断描述符表寄存器”(IDTR实现的,这个寄存器中就存放了“中断描述符表”在内存中的地址。下面,我们也来看看这个寄存器的结构:


(
图二)

下面,我们可以比较完整的来欣赏一下CPU处理中断的流程:


(
图三)

 

三、pyos中的中断系统实验

下面,我们将以pyos作为例子,用它来实验操作系统中中断系统的实现。当然,在实际的操作系统中这也许是非常复杂的,但我们现在通过pyos也完全可以进行这样的实验。在实验正式开始之前,你或许需要下载本实验所用到的源代码,这可以在我们的网站“纯C论坛”(http://purec.binghua.com)“操作系统实验专区”中下载(编者注:此源代码已经收录到本刊本期的资源包中),也可以来信向作者索要。对于实验环境的搭建你也许需要看看“操作系统实验专区”中《When Do We Write Our Chinese Os1)》对此的描述。

在实验正式开始之前,我们将详细描述一下pyos的文件组织形式。

 

3.1 pyos 的文件组织形式

本实验用到的pyos目前的文件组织如下:“boot.asm”、“setup.asm”这两个文件是操作系统引导文件,他们负责把pyos的内核读入内存,然后转到内核执行(这方面的内容可以参见《操作系统引导探究》一文)。“kernel.cpp”就是pyos的内核,pyos目前是用c++开发的,因此它的内核看起来非常简单,也比较有结构,下面就是“kernel.cpp”中的内容:

#include "system.h"

#include "video.h"

 

extern "C" void Pyos_Main()

{

  /* 系统初始化 */

  class_pyos_System::Init() ;

  class_pyos_Video::ClearScreen() ;

  class_pyos_Video::PrintMessage( "Welcome to Pyos :)" ) ;

  for(;;);

}

我想,这样的程序也许不用我做什么注释了。“system.h”中定义了一个名为“class_pyos_System”的系统类,用来做系统初始化的工作,“video.h”中定义了一个名为“class_pyos_Video”的显卡类,它封装了对VGA显卡的操作,从上面的代码中我们可以看见,系统先通过class_pyos_System类的Init()完成系统初始化,然后调用class_pyos_Video类中的ClearScreen()进行清屏,最后用PrintMessage()输出了一条欢迎信息。下面,我就一步一步的来剥开上面看上去很神秘的Init()——系统初始化函数。对于显卡类,你可以参看源代码,本篇中将不进行描述,也许以后我会用专门的一篇来详细描述它。当然,你如果看过《When Do We Write Our Chinese OS3)》的话,对于显卡类的理解就易容反掌了。

 

3.2 pyos 的系统初始化

下面,我们来看看 pyos 的系统初始化函数:

#include "interrupt.h"

/* 系统初始化 */

void class_pyos_System::Init() 

{

  /* 初始化Gdt */

  InitGdt() ;

  /* 初始化段寄存器 */

  InitSegRegister() ;

  /* 初始化中断 */

  class_pyos_Interrupt::Init() ;

}

是的,他就是这么简单,由于InitGdtInitSegRegister的内容在《操作系统引导探究》中已经描述过了,这里我们就专注于我们本篇的核心内容——对于中断系统的初始化。

从上面的代码中我们可以看出,系统首先在“interrupt.h”中定义了一个名为“class_pyos_Interrup”的中断类,专门来处理系统的中断部份。然后,系统调用中断类的Init()函数,来进行初始化。呵呵,我们马上就去看看这个中断类的初始化函数到底做了些什么:

/* 初始化中断服务 */

void class_pyos_Interrupt::Init()

{

  /* 初始化中断可编程控件器 8259A */

  Init8259A() ;

  /* 初始化中断向量表 */

  InitInterruptTable() ;

  /* 许可键盘中断 */

  class_pyos_System::ToPort( 0x21 , 0xfd ) ;

  /* 汇编指令,开中断 */

  __asm__( "sti" ) ;

}

我想,对于这样自说明式的代码,解释是多余的,我们还是抓紧时间来看看它首先是怎样初始化 8259A的吧:

/* 初始化中断控制器 8259A */

void class_pyos_Interrupt::Init8259A()

{

  // 给中断寄存器编程

  // 发送 ICW1 : 使用 ICW4,级联工作

  class_pyos_System::ToPort( 0x20 , 0x11 ) ;

  class_pyos_System::ToPort( 0xa0 , 0x11 ) ;

 

  // 发送 ICW2,中断起始号从 0x20 开始(第一片)及 0x28开始(第二片)

  class_pyos_System::ToPort( 0x21 , 0x20 ) ;

  class_pyos_System::ToPort( 0xa1 , 0x28 ) ;

 

  // 发送 ICW3

  class_pyos_System::ToPort( 0x21 , 0x4 ) ;

  class_pyos_System::ToPort( 0xa1 , 0x2 ) ;

 

  // 发送 ICW4

  class_pyos_System::ToPort( 0x21 , 0x1 ) ;

  class_pyos_System::ToPort( 0xa1 , 0x1 ) ;

 

  // 设置中断屏蔽位 OCW1 ,屏蔽所有中断请求

  class_pyos_System::ToPort( 0x21 , 0xff ) ;

  class_pyos_System::ToPort( 0xa1 , 0xff ) ;

}

从上面的代码可以看出程序是通过向 8259A发送4ICW8259A进行初始化的。其中ToPortclass_pyos_System类中定义的一个成员函数,它的声明如下:

/* 写端口 */

void class_pyos_System::ToPort( unsigned short port , unsigned char data )

OK,好像又不需要我多解释了,当然,你也许会问,为什么会发送这几个值的数据,而不是其它值的数据。对于这个问题,因为在“上篇”中已经详细描述了,这里就不再浪费大家的时间了。 :)

 

3.3 初始化 pyos 的中断向量表

从中断初始化的代码中我们可以清楚的看见,pyos在进行完8259A的初始化后,调用InitInterruptTable()对中断向量表进行了初始化,这可是本篇的核心内容,我们这就来看看这个核心函数:

/* 中断描述符结构 */

struct struct_pyos_InterruptItem{

  unsigned short Offset_0_15 ; // 偏移量的0~15

  unsigned short SegSelector ; // 段选择符

  unsigned char UnUsed ; // 未使用,须设为全零

  unsigned char Saved_1_1_0 : 3 ; // 保留,需设为 110

  unsigned char D : 1 ; // D

抱歉!评论已关闭.