实例一——为自己的操作系统中加入中断
中断机制的实现
在这个部分,我将为大家详细介绍SagaLinux_irq中是如何处理中断的。为了更好的演示软硬件交互实现中断机制的过程,我将在前期实现的SagaLinux上加入对一个新中断­——定时中断——的支持。
首先,让我介绍一下SagaLinux_irq中涉及中断的各部分代码。这些代码主要包含在kernel目录下,包括idt.c,irq.c,i8259.s,boot目录下的setup.s也和中断相关,下面将对他们进行讨论。
1、boot/setup.s
setup.s中相关于中断的部分主要集中在pic_init小结,该部分完成了对中断控制器的初始化。对8259A的编程是通过向其相应的端口发送一系列的ICW(初始化命令字)完成的。总共需要发送四个ICW,它们都分别有自己独特的格式,而且必须按次序发送,并且必须发送到相应的端口,具体细节请查阅相关资料。
pic_init:
cli
mov al, 0x11 ; initialize PICs
; 给中断寄存器编程
; 发送ICW1:使用ICW4,级联工作
out 0x20, al ; 8259_MASTER
out 0xA0, al ; 8259_SLAVE
; 发送 ICW2,中断起始号从 0x20 开始(第一片)及 0x28开始(第二片)
mov al, 0x20 ; interrupt start 32
out 0x21, al
mov al, 0x28 ; interrupt start 40
out 0xA1, al
; 发送 ICW3
mov al, 0x04 ; IRQ 2 of 8259_MASTER
out 0x21, al
; 发送 ICW4
mov al, 0x02 ; to 8259_SLAVE
out 0xA1, al
; 工作在80x86架构下
mov al, 0x01 ; 8086 Mode
out 0x21, al
out 0xA1, al
; 设置中断屏蔽位 OCW1 ,屏蔽所有中断请求
mov al, 0xFF ; mask all
out 0x21, al
out 0xA1, al
sti
这个request_irq函数显然要比SagaLinux中同名函数复杂很多,光看看参数的个数就知道了。不过头两个参数两者却没有区别,依稀可以推断出:它们的主要功能都是完成中断号与中断服务程序的绑定。
关于Linux提供给系统程序员的、与中断相关的函数,很多书籍都给出了详细描述,如“Linux Kernel Development”。我这里就不做重复劳动了,现在集中注意力在中断服务程序本身上。
static irqreturn_t rtc_interrupt(int irq, void *dev_id,
struct pt_regs *regs)
{
BMP位图的编码和解码
* Can be an alarm interrupt, update complete interrupt,
* or a periodic interrupt. We store the status in the
* low byte and the number of interrupts received since
* the last read in the remainder of rtc_irq_data.
*/
spin_lock (&rtc_lock);
rtc_irq_data += 0x100;
rtc_irq_data &= ~0xff;
rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
if (rtc_status & RTC_TIMER_ON)
mod_timer(&rtc_irq_timer,
jiffies + HZ/rtc_fre
q
+ 2*HZ/100);
spin_unlock (&rtc_lock);
/* Now do the rest of the actions */
spin_lock(&rtc_task_lock);
if (rtc_callback)
rtc_callback->func(rtc_callback->private_data);
spin_unlock(&rtc_task_lock);
wake_up_interruptible(&rtc_wait);
kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
这里先提醒读者注意一个细节:中断服务程序是static类型的,也就是说,该函数是本地函数,只能在rtc.c文件中调用。这怎么可能呢?根据我们从SagaLinux中得出的经验,中断到来的时候,操作系统的中断核心代码一定会调用此函数的,否则该函数还有什么意义?实际上, request_irq函数会把指向该函数的指针注册到相应的查找表格中(还记得SagaLinux中的irq_handler[]吗?)。static 只能保证rtc.c文件以外的代码不能通过函数名字显式的调用函数,而对于指针,它就无法画地为牢了。
程序用到了spin_lock函数,它是Linux提供的自旋锁相关函数,关于自旋锁的详细情况,我们会在以后的文章中详细介绍。你先记住,自旋锁是用来防止SMP结构中的其他CPU并发访问数据的,在这里被保护的数据就是rtc_irq_data。rtc_irq_data存放有关RTC的信息,每次中断时都会更新以反映中断的状态。
接下来,如果设置了RTC周期性定时器,就要通过函数mod_timer()对其更新。定时器是Linux操作系统中非常重要的概念,我们会在以后的文章中详加解释。
代码的最后一部分要通过设置自旋锁进行保护,它会执行一个可能被预先设置好的回调函数。RTC驱动程序允许注册一个回调函数,并在每个RTC中断到来时执行。
wake_up_interruptible是个非常重要的调用,在它执行后,系统会唤醒睡眠的进程,它们等待的RTC中断到来了。这部分内容涉及等待队列,我们也会在以后的文章中详加解释。
感受RTC——最简单的改动
我们来更进一步感受中断,非常简单,我们要在RTC的中断服务程序中加入一条printk语句,打印什么呢?“I’m coming, interrupt!”。
下面,我们把它加进去:
… …
spin_unlock(&rtc_task_lock);
printk(“I’m coming , interrupt!\n”);
wake_up_interruptible(&rtc_wait);
… …
没错,就先做这些,请你找到代码树的drivers\char\rtc.c文件,在其中irqreturn_t rtc_interrupt函数中加入这条printk语句。然后重新编译内核模块(当然,你要在配置内核编译选项时包含RTC,并且以模块形式)现在,当我们插入编译好的rtc.o模块,执行前面实时钟部分介绍的用户空间程序,你就会看到屏幕上打印的“I’m coming , interrupt!”信息了。
这是一次实实在在的中断服务过程,如果我们通过ioctl改变RTC设备的运行方式,设置周期性到来的中断的话,假设我们将频率定位8HZ,你就会发现屏幕上每秒打印8次该信息。
动手修改RTC实际上是对中断理解最直观的一种办法,我建议你不但注意中断服务程序,还可以看一下RTC驱动中ioctl的实现,这样你会更加了解外部设备和驱动程序、中断服务程序之间实际的互动情况。
不仅如此,通过修改RTC驱动程序,我完成了不少稀奇古怪的工作,比如说,在高速数据采集过程中,我就是利用高频率的RTC中断检查高速AD采样板硬件缓冲区使用情况,配合DMA共同完成数据采集工作的。当然,在有非常严格时限要求的情况下,这样不一定适用。但是,在两块12位20兆采样率的 AD卡交替工作,对每秒1KHz的雷达视频数据连续采样的情况下,我的RTC跑得相当好。
当然,这可能不是一种美观和标准的做法,但是,我只是一名程序员而不是艺术家,只是了解了这么一点点中断知识,我就完成了工作,我想或许您也希望从系统底层的秘密中获得收益吧,让我们在以后的文章中再见。
dreamice兄说的太好了,不错!是个好版主! 作者: zhj1011 时间: 2008-11-24 11:31 标题: 回复 #1 dreamice 的帖子 精华文章啊 深入浅出 语言通俗易懂 佩服版主作者: foxclever 时间: 2008-11-24 12:42
很不错,收藏!向楼主学习!作者: whoisliang 时间: 2008-12-30 21:58
LZ既给我解析了一个具体的中断问题,又在此看到你的好文章,我大声歌唱:GO GO GO OU LE LE LE
GO GO GO OU LE LE LE
GO GO GO OU LE LE LE
GO GO GO OU LE LE LE
GO GO GO OU LE LE LE作者: ni没情调 时间: 2012-04-27 22:50
果断顶起,好帖作者: phicomm 时间: 2012-04-28 16:31
好贴,学习了作者: Reallsc 时间: 2012-05-07 18:11
帖子里提到有图片,图片怎么都丢了呢?