Fork Me on GitHub

源码开放学ARM

LASO - Learn ARM with Source Open

首页         目录索引         资料下载         代码下载         联系作者        
下载PDF打印版本

中断处理程序

Linux 注册函数

设备驱动要将回调函数 handler 注册到 irq_desc 的表项中。这就是我们要讲的 request_irq 。

request_irq()/free_irq() 的声明在 linux/interrupt.h 中,实现在 kernel/irq/manager.c 中,如下:

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
            const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

extern void free_irq(unsigned int, void *);

request_irq 函数的参数如下:

unsigned int irq 		// 请求的中断号
irqreturn_t (*handler) 		// 安装的处理函数指针. 我们在本章后面讨论给这个函数的参数以及它的返回值.	
unsigned long flags 		// 与中断管理相关的选项的位掩码(后面描述).
const char *dev_name 		//这个传递给 request_irq 的字串用在 /proc/interrupts 来显示中断的拥有者(下一节看到)
void *dev_id 			// 用作共享中断线的指针. 它是一个独特的标识, 用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区(来标识哪个设备在中断). 如果中断没有被共享, dev_id 可以设置为 NULL, 但是使用这个项指向设备结构不管如何是个好主意. 我们将在"实现一个处理"一节中看到 dev_id 的一个实际应用.

flags 中可以设置的位如下:

SA_INTERRUPT 
	当置位了, 这表示一个"快速"中断处理. 快速处理在当前处理器上禁止中断来执行(这个主题在"快速和慢速处理"一节涉及).
SA_SHIRQ 
	这个位表示中断可以在设备间共享. 共享的概念在"中断共享"一节中略述.
SA_SAMPLE_RANDOM 
	这个位表示产生的中断能够有贡献给 /dev/random 和 /dev/urandom 使用的加密池. 这些设备在读取时返回真正的随机数并且设计来帮助应用程序软件为加密选择安全钥. 这样的随机数从一个由各种随机事件贡献的加密池中提取的. 如果你的设备以真正随机的时间产生中断, 你应当设置这个标志. 如果, 另一方面, 你的中断是可预测的( 例如, 一个帧抓取器的场消隐), 这个标志不值得设置 -- 它无论如何不会对系统加密有贡献. 可能被攻击者影响的设备不应当设置这个标志; 例如, 网络驱动易遭受从外部计时的可预测报文并且不应当对加密池有贡献. 更多信息看 drivers/char/random.c 的注释. 

设备通过函数request_irq()注册一个IRQ号,并提供相应的处理函数。

从 request_irq 返回给请求函数的返回值或者是 0 指示成功, 或者是一个负的错误码, 如同平常. 函数返回 -EBUSY 来指示另一个驱动已经使用请求的中断线是不寻常的.

request_threaded_irq() 的实现在 kernel/irq/manage.c 文件中,如下:

__setup_irq(irq, desc, action);

下面是键盘驱动程中注册中断的代码:

request_irq(IRQ_EINT0, key1_irq_isr, SA_INTERRUPT, "key2345irq", NULL);

IRQ_EINT0是IRQ号, key1_irq_isr()是处理函数。如果该函数成功的话,irq_descIRQ_EINT0的action成员将会指向一个新分配的irqaction结构,该结构的handler指向key1_irq_isr()。

/proc 接口

无论何时一个硬件中断到达处理器, 一个内部的计数器递增, 提供了一个方法来检查设备是否如希望地工作. 报告的中断显示在 /proc/interrupts. 下面的快照取自一个双处理器 Pentium 系统:

root@montalcino:/bike/corbet/write/ldd3/src/short# cat /proc/interrupts
	CPU0     CPU1 
 0:  4848108       34   IO-APIC-edge  timer 
 2:        0        0         XT-PIC  cascade 
 8:        3        1   IO-APIC-edge  rtc 
 10:    4335        1  IO-APIC-level  aic7xxx 
 11:    8903        0  IO-APIC-level  uhci_hcd 
 12:      49        1   IO-APIC-edge  i8042  
NMI:       0        0  
LOC: 4848187  4848186  
ERR:       0  
MIS:       0  

第一项是中断的号,第二项是中断的次数,第三项是中断的触发方式,第四项是中断名称。

中断的处理过程

  • 中断信号由外部设备发送到中断控制器,中断控制器根据IRQ号转换成相应的中断向量号传给CPU 。
  • CPU接收中断后,保存现场,根据中断向量号到IDT中查找相应的处理函数。对于IRQ n的中断,它的处理函数IRQn_interrutp()。
  • 调用do_IRQ()函数。该函数完成对中断控制器确认、设置中断源状态等动作, 接着它会根据IRQ号找到描述中断具体动作的irqaction结构变量action,执行如下代码:

处理 irq 事件的函数,主要实现在 kernel/irq/handle.c 中,其中 res = action->handler(irq, action->dev_id);
执行了调用用户注册的 handler() 函数的功能。

182 irqreturn_t handle_irq_event(struct irq_desc *desc)
183 {
184         struct irqaction *action = desc->action;
185         irqreturn_t ret;
186 
187         desc->istate &= ~IRQS_PENDING;
188         irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
189         raw_spin_unlock(&desc->lock);
190 
191         ret = handle_irq_event_percpu(desc, action);
192 
193         raw_spin_lock(&desc->lock);
194         irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
195         return ret;
196 }

132 irqreturn_t
133 handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
134 {
135         irqreturn_t retval = IRQ_NONE;
136         unsigned int flags = 0, irq = desc->irq_data.irq;
137 
138         do {
139                 irqreturn_t res;
140 
141                 trace_irq_handler_entry(irq, action);
142                 res = action->handler(irq, action->dev_id);
143                 trace_irq_handler_exit(irq, action, res);
144 
145                 if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
146                               irq, action->handler))
147                         local_irq_disable();
148 
149                 switch (res) {
150                 case IRQ_WAKE_THREAD:
151                         /*
152                          * Catch drivers which return WAKE_THREAD but
153                          * did not set up a thread function
154                          */
155                         if (unlikely(!action->thread_fn)) {
156                                 warn_no_thread(irq, action);
157                                 break;
158                         }
159 
160                         irq_wake_thread(desc, action);
161 
162                         /* Fall through to add to randomness */
163                 case IRQ_HANDLED:
164                         flags |= action->flags;
165                         break;
166 
167                 default:
168                         break;
169                 }
170 
171                 retval |= res;
172                 action = action->next;
173         } while (action);
174 
175         add_interrupt_randomness(irq, flags);
176 
177         if (!noirqdebug)
178                 note_interrupt(irq, desc, retval);
179         return retval;
180 }
  • 最后do_IRQ()函数要检查是否有软中断,如有则调用do_softirq()执行软中断。

  • 跳转到 ret_from_intr退出,恢复中断前的现场。

    Examples

    include
    REXML could not parse this XML/HTML: 
    <linux/module.h>

    include
    REXML could not parse this XML/HTML: 
    <linux/kernel.h>

    include
    REXML could not parse this XML/HTML: 
    <linux/interrupt.h>

    include
    REXML could not parse this XML/HTML: 
    <linux/irqreturn.h>

    //#include

    REXML could not parse this XML/HTML: 
    <linux/irq.h>

    MODULE_LICENSE(“GPL”);

    define DPRINT(x) printk(“<%s> ” #x ” = 0x%x\n”, func, (int)x);

    irqreturn_t myhandler(int irq, void * p) { // printk(“irqmyhandler called!\n”); DPRINT(irq);

    return IRQ_HANDLED;	

    }

    static init int interrupt_init(void) { int ri; int btn_irq;

    DPRINT(interrupt_init);
    
    btn_irq = 160;

    // ri = request_irq(17, myhandler, 0, “my test interrupt”, NULL); // ri = request_irq(17, 1, 0, “my test interrupt”, NULL); ri = request_irq(btn_irq, myhandler, IRQF_TRIGGER_FALLING, “btn K1 interrupt”, NULL); ri = request_irq(btn_irq+1, myhandler, IRQF_TRIGGER_FALLING, “btn K2 interrupt”, NULL);

    DPRINT(ri);
    
    return 0;

    }

    static exit void interrupt_exit(void) { DPRINT(interrupt_exit);

    free_irq(160, NULL);
    free_irq(161, NULL);
    
    return;

    }

    module_init(interrupt_init); module_exit(interrupt_exit);

实际使用过程中,中断的信号常常是用来作为唤醒等待队列里面睡眠的进程的,这就需要了解有关等待队列的知识。

上一节 | 目录索引 | 下一节

blog comments powered by Disqus