分类: LINUX
2008-11-28 00:58:40
从体系结构看计算机系统包括处理器,存储器,i/o设备。内核的一个主要任务是管理硬件或者说设备,因此需要一种机制实现内核与设备的通信,轮询是一种效率低下的方式。中断是由硬件通常指I/O设备向处理器发出的电子信号,处理器接收到中断后就调用相应的中断句柄进行处理。中断句柄是设备驱动的一部分从而也是操作系统内核的一部分。中断可以在任何时候发生,因此内核就有可能在任何时候被中断以处理中断(好吧,这里的两个中断不是一个意思,下面还会有这种连续出现不同意思的“中断”的句子,就不再说明了)。
实际上,中断信号发向中断控制器的某个针脚(虽然在某些体系中中断控制器是处理器的一部分,不过既然内存控制器可以是也可以不是处理器的一部分,中断控制器也可以不是,这里为了明确责任,姑且把中断控制器跟处理器区分开来),中断控制器再向处理器发出信号指明发生中断,中断控制器并不需要告知处理器发生的是什么中断,或者说中断由哪个设备发出,处理器检测到这个信号之后就要中断当前执行(的指令,或者指令集,原子指令当然无法中断,不过现代体系中中断一条非原子指令是允许的)以处理中断,最终对中断的处理自然由操作系统进行,中断句柄由内核调用,并运行在中断上下文中。
(先插一段,不然下面不好安排位置)异常(exception)是与处理器时钟同步的,通常是处理器执行的指令出错比如除0或者发生了某些异常情况如页错误。既然大多数处理器架构对异常的处理与中断类似,内核自然也照此办理,只是异常无需中断控制器向处理器发出,它本来就是处理器自己的问题。在逻辑上中断、异常以及系统调用是类似的。(插入完毕)
中断需要迅速处理,至少中断句柄要告诉设备接收到了中断。由于技术进步,处理中断可能需要大量的工作。这是一对矛盾,为此把对中断的处理分成了两部分,一部分是一收到中断就需要马上处理的工作,比方说确认收到中断或者重置设备,这部分称为top half(不知道怎么翻译,以前的操作系统教材没有看到相应的内容),实际上中断句柄指的是这一部分,相应的另一部分称为bottom half,这是可以随后再处理的工作。
以网卡为例,当网卡收到数据就会发出中断,内核调用注册的中断句柄处理这个中断,包括确认中断,把收到的网络包复制到内存中,然后让网卡继续接收。这一部分就是top half的工作,剩下的处理则是bottom half的范围。
每个设备都有一个驱动,如果这个设备需要用到中断(当然大多数设备都是要的,剩下的极少数是啥我暂时想不起来),则驱动需要注册一个句柄,所谓注册,就是告诉内核,如果发生了啥啥中断就来找我。怎么找呢?当处理器收到中断请求,做一些处理后就要跳转到相应的中断入口,从这里开始执行中断句柄。把这个中断入口和中断句柄联系起来就是内核的工作。
看下这个函数int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long irqflags, const char *devname, void *dev_id)
显然,irq这个参数是中断号,对于一些设备来说,如系统时间和键盘,这个中断号是硬编码的,也就是说早就确定并且不可更改,如果要改也的处理器生产厂商来改。当然也有一些是可以动态确定的,比如说pci设备。
第二个参数handle是个函数指针,这里作为一个回调函数的形式出现,不过回调这个函数要在中断发生的时候而不是在注册的时候。
第三个参数 iraflags,如同许多其他类似名字的参数,可以是0,也可以是一些标志的组合,一般来说这种组合是用"|"串在一起的,一共有3个标志。
SA_INTERRUPT——所谓的快中断标志,从名字看不出来啥意义,不过如果说这是个要求关闭中断的标志就很好理解了,实际上现在linux中快中断的唯一意义就是要把所有的中断关掉,当然指的是一个local处理器,不是全部,别的处理器管不着。不过除了这个标志,关中断也不是唯一由这个标志决定,处理器也可以屏蔽某些中断信号线来达到关中断的目的,两者并不矛盾。大部分中断并不需要这个标志。
SA_SAMPLE_RANDOM——这个标志表示发生这个中断的设备要为内核熵池(kernel entropy pool)做贡献,其实就是要帮助系统产生真正的随机数,产生真正的随机数是内核熵池的工作之一。因此会事先知道发生时间的(如系统定时器)或者由外界控制发生率(如网络设备)的中断就不要设置这个标志,设置这个标志的中断应该是会发生在任何时候的。
SA_SHIRQ——共享中断信号线的标志。
第4个参数,devname,设备名字,一串ascii码字符,可以在/proc/interrupts 和/proc/irq中看到。
第5个参数,dev_id,主要针对共享中断信号线的设备,并且主要在移除中断时使用。一般传入的是驱动程序的设备结构。
Request_irq()成功返回0。
这个函数可能会睡眠,因此不能在中断上下文或者其他无法堵塞的场合调用。睡眠的原因在于在注册时需要在/proc/irq中创建一个条目,创建这个条目有一系列的函数调用,最终需要分配内存,而分配内存的函数会进入睡眠。
这是个例子,不在解说
If(request_irq(irqn,my_interrupt,SA_SHIRQ,"my_device",dev)){
Printk(KEEN_ERR "my_device:cannot register IRQ%d\n",irqn);
Return -EIO;
}
有注册函数相应的也有释放函数:free_irq(unsigned int irq, void *dev_id),除了移除中断句柄,还会禁止中断信号线,当然对于共享信号线的中断句柄来说操作跟其他所有共享的资源的处理一样。
释放函数只能在进程上下文中调用,实际上如前所说,注册函数也只能在进程上下文中调用,不过释放函数并不会睡眠。
接下来看这个函数static irqreturn_t intr_handler(int irq,void *dev_id,struct pt_regs *regs)
这个函数就是上面注册函数里handler的一个例子。
Irq这个参数现在只用在打印日志的时候,如果需要的话,因为现在已经提供了dev_id这个参数,以前(2.0之前,很古老了)是没有dev_id这个参数的,因此irq就用来区分使用同一个设备驱动进而同一个中断句柄的不同设备,比方说你的电脑上安装了好几个同一型号的硬盘控制器。
Dev_id这个参数跟上面注册函数的dev_id指向同一个结构体,一般是device结构,因为这个结构体通常对每个设备来说是唯一的,因此可以用来区分设备。
Regs指向的结构保存了处理中断前处理器的寄存器和状态,现在已经很少使用了,除非用来debug。
如果一个中断句柄返回IRQ_NONE,则表示这个句柄发现中断不是由它所控制的设备发出的,否则就返回IRQ_HANDLED,表示句柄被正确的调用,并且设备就是那个设备。
也可以用IRQ_RETVAL(val),如果val非零,返回IRQ_NONE,否则IRQ_HANDLED。
通过这些值内核就可以判断设备是否发出的是虚假中断(大概就是莫名其妙的中断,啥也不干的中断)。
Irqreturn_t在2.6中是个Int,而在之前则是个void,只要把irqreturn_t typedef成void就可以简单的实现向后兼容。
定义成static是中断句柄不会在别的文件中直接调用。
中断句柄是不需要考虑可重入的,因为发生一个中断时这条中断信号线就被禁止了,不可能再发出同样的一个中断,也就不可能在调用同一个句柄。
可共享的中断句柄跟不可共享的中断句柄有3个不同点:
1.设置SA_SHIRQ;
2.Dev_id必须是唯一的,并且不可为NULL
3.中断句柄必须能区分设备是否确实发出了中断。这个是需要硬件支持的。
所谓的共享中断指的是共享中断信号线的中断,这里再提醒次,免得忘了。
比较下进程上下文和中断上下文,这一段可能将来会剪切粘贴到别的地方。
进程上下文是指内核executing on behalf of a process(不好翻译,硬译为了一个进程而执行,显然不专业,于是保留),比方说执行系统调用或者运行一个系统线程。这时候的current宏指向相关联的任务,这时候是可以睡眠并且会调用调度程序的。
中断上下文是指执行中断句柄或者bottom half的时候,不与任何进程关联,current宏虽然指向的是被中断的进程,但与中断本身没有关系,因此中断上下文是不能睡眠的,不然调度程序如何调度中断重新执行。所以在中断上下文中是不能调用可能会睡眠的函数的。
中断上下文是时间紧急的,必须要快,越快越好。
历史上,中断句柄是没有自己的栈的,他们可以共享被中断的进程的栈,包括idle进程。因为内核栈不大,所以要小心。
在2.6内核中,可以为中断分配一个叫中断栈的栈,这是为了适应某些配置中内核栈被裁减了一半的情况。
中断句柄并不需要关心用的是啥栈,只要记住永远使用最小栈空间。
本章结束,剩下的需要对照书看源码了,不再多说。