文章目录
- 注册中断处理程序
- 释放中断处理程序
- 编写中断处理程序
- 共享的中断处理程序
- 中断例程实例
- 中断上下文
- /proc/interrupts
- 中断控制
- 禁止和激活中断
- 禁用指定中断线
- 中断系统的状态
注册中断处理程序
// 分配一条给定的 irq 中断线
request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev);
// 成功返回 0 ,否则非 0.
-
irq:要分配的中断号。该值对于某些设备来说是预先固定的,例如时钟、键盘。而对于大部分设备,可以通过探测获取或编程来确定。
-
handler:是个函数指针,指向处理这个中断的中断处理程序。
typedef irqreturn_t (*irq_handler_t)(int, void *);
irqreturn_t 类型的返回值。
- flag
- name:设备名称。被用于 /proc/irq 和 /proc/interrupts。
- dev:用于共享中断线。在共享中断线中,当一个中断处理程序需要释放时,内核会过该标识符进行删除。
request_irq() 函数可能会休眠,因此不能在中断上下文或其它不允许阻塞的代码中调用该函数。
在注册的过程中,内核需要在 /proc/irq
下创建一个与中断对应的项。由 proc_mkdir() 来负责创建这个 procfs 项。
初始化硬件和注册中断处理程序的顺序必须正确,以防中断处理程序在设备初始化之前就开始执行了。(先初始化,后注册)
释放中断处理程序
卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。
arch/sparc/kernel/irq_32.c:
void free_irq(unsigned int irq, void *dev_id) { ... }
若指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线。若中断线是共享的,则仅仅只是删除 dev 所对应的中断处理程序,而这条中断线只有当最后一个中断处理程序被删除时才会跟着被禁用。
编写中断处理程序
drivers/net/fealnx.c:
/* 中断处理程序完成所有 Rx 线程工作并在 Tx 线程之后进行清理。 */
static irqreturn_t intr_handler(int irq, void *dev_instance) {
...
return IRQ_RETVAL(handled);
}
- irq:中断号。
- dev_instance:
struct net_device
类型的指针,位于:include/linux/netdevice.h。
返回值:
/**
* enum irqreturn
* @IRQ_NONE 中断不是来自这个设备
* @IRQ_HANDLED 中断由该设备处理
* @IRQ_WAKE_THREAD 处理程序请求唤醒处理程序线程
*/
enum irqreturn {
IRQ_NONE,
IRQ_HANDLED,
IRQ_WAKE_THREAD,
};
typedef enum irqreturn irqreturn_t;
#define IRQ_RETVAL(x) ((x) != IRQ_NONE)
重入和中断处理程序:
同一个中断处理程序绝对不允许被同时调用,造成嵌套中断。即同一条中断线上只允许接收一个中断,而该中断线上的其它中断我们此时不接收,但其它中断线上的不受影响。
共享的中断处理程序
所有共享中断线的驱动程序都必须满足的条件:
- request_irq() 的 flags 参数必须设置为 IRQF_SHARED。
- 对于每个注册的中断处理程序,dev 参数必须唯一。
- 中断处理程序必须能够区分它的设备是否真的产生了中断。(需要硬件支持,也需要软件逻辑判断)
中断例程实例
位置:drivers/char/rtc.c
当 RTC 驱动程序转载时,rtc_init() 函数会被调用,对这个驱动程序进行初始化。它的职责之一便是注册中断处理程序。
/*
* XXX Interrupt pin #7 in Espresso is shared between RTC and
* PCI Slot 2 INTA# (and some INTx# in Slot 1).
*/
if (request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc",
(void *)&rtc_port)) {
rtc_has_irq = 0;
printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
return -EIO;
}
具体的中断处理程序 rtc_interrupt:
/*
* rtc_status is never changed by rtc_interrupt, and ioctl/open/close is
* protected by the spin lock rtc_lock. However, ioctl can still disable the
* timer in rtc_status and then with del_timer after the interrupt has read
* rtc_status but before mod_timer is called, which would then reenable the
* timer (but you would need to have an awful timing before you'd trip on it)
*/
static unsigned long rtc_status; /* bitmapped status byte. */
static unsigned long rtc_freq; /* Current periodic IRQ rate */
static unsigned long rtc_irq_data; /* our output to the world */
static unsigned long rtc_max_user_freq = 64; /* > this, need CAP_SYS_RESOURCE */
/*
* 一个非常小的中断处理程序。
* 它在设置了 IRQF_DISABLED 的情况下运行,但有可能与 set_rtc_mmss() 调用发生冲突(rtc irq 和定时器 irq 可以很容易地同时在两个不同的 CPU 中运行)。 因此,我们需要使用 rtc_lock 自旋锁串行化对芯片的访问,每个体系结构都应在定时器代码中实现。
*(有关 set_rtc_mmss() 函数,请参阅 ./arch/XXXX/kernel/time.c。)
*/
static irqreturn_t rtc_interrupt(int irq, void *dev_id)
{
/*
* 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;
if (is_hpet_enabled()) {
/*
* In this case it is HPET RTC interrupt handler
* calling us, with the interrupt information
* passed as arg1, instead of irq.
*/
rtc_irq_data |= (unsigned long)irq & 0xF0;
} else {
rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
}
if (rtc_status & RTC_TIMER_ON)
mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 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;
}
中断上下文
中断上下文具有较为严格的时间限制,因为它打断了其它代码。中断上下文中的代码应当迅速、简洁、尽量避免使用循环来处理工作。
牢记:中断处理程序打断了其它的代码(甚至可能是打断了在其它中断线上的另一个中断处理程序)
应该要把繁杂的工作从中断处理程序中分离出来,放到下半部执行。
Linux 2.6 后,中断处理程序拥有了自己的栈,每个处理器一个,在 32 位系统上,大小为一页,这个栈称为中断栈。
/proc/interrupts
这是个文件,该文件存放的是系统中与中断相关的统计信息。
以多核 CPU 为例子输出如下:
CPU0 CPU1 CPU2 CPU3
1: 64403 0 0 0 IR-IO-APIC 1-edge i8042
8: 0 0 0 0 IR-IO-APIC 8-edge rtc0
9: 170 81 0 0 IR-IO-APIC 9-fasteoi acpi
...
NMI: 129 529 524 526 Non-maskable interrupts
LOC: 13324301 12721094 12471117 12517149 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
PMI: 129 529 524 526 Performance monitoring interrupts
...
第一列是中断线。XT-PIC 对与标椎的 PC 可编程中断控制器。在具有 I/O APIC 的设备上,大多数中断会列出 IO-APIC-level 或 IO-APIC-edge,作为自己的中断控制器。最后一列是这个中断对应的设备名称。
Tips:我是 7 核!!!
这些输出依靠 show_interrupts()
函数,位于:arch/arm/kernel/irq.c
中断控制
- arch/alpha/include/asm/system.h
- arch/arm/kernel/irq.c
禁止和激活中断
这里的禁用和激活中断,指的是处理器上的所有中断。
- local_irq_disable():禁用中断
- local_irq_enable():激活中断
#define do { setipl(IPL_MAX); barrier(); } while(0)
#define do { barrier(); setipl(IPL_MIN); } while(0)
如果在 local_irq_disable() 之前就以及禁用了中断,那么再次中断可能会存在一些潜在的危险,local_irq_enable() 也同理。为了根据方便操作,Linux 有一个恢复和保存中断的方式:
unsigned long flags; // 中断状态
local_irq_save(flags); // 保存当前中断状态,并且禁用中断
// ...
local_irq_restore(flags); // 恢复先前的状态
禁用指定中断线
这里的禁用指的是处理室的某个中断线。
提供如下函数:
/**
* disable_irq
* 当调用该函数时,该函数会等待该中断线上的所有中断例程处理完毕才返回
*/
extern void disable_irq(unsigned int irq);
/**
* disable_irq_nosync
* 不用于 disable_irq,该函数不会等待当前中断线上的中断例程执行完毕,而是直接中断
*/
extern void disable_irq_nosync(unsigned int irq);
/**
* enable_irq
* 激活当前中断线
*/
extern void enable_irq(unsigned int irq);
这些函数的调用可以嵌套。但在一条指定的中断线上,对 disable_irq() 或 disable_irq_nosync() 的每次调用,都需要响应地调用一次 enable_irq()。只有在对 enable_irq() 完成最后一次调用后,才真正激活了中断线。例如,如果 disable_irq() 被调用了两次,那么直到第二次调用 enable_irq() 后,才能真正地激活了中断线。
禁用多个中断处理程序共享的中断线是不合适的。禁用中断线也就禁用了这条线上的所有设备的中断传递。
中断系统的状态
// 若本地处理器上的中断系统被禁用,则返回0,否则非0.
#define irqs_disabled() (getipl() == IPL_MAX)
include/linux/hardirq.h
/*
* Are we doing bottom half or hardware interrupt processing?
* Are we in a softirq context? Interrupt context?
*/
#define in_irq() (hardirq_count())
#define in_softirq() (softirq_count())
#define in_interrupt() (irq_count())
通常情况下,有时需要检查自己是否处于进程上下文中,因为有时代码需要做一些像休眠这样只能从进程上下文中做的事。