中断
硬件在需要的时候向内核发出信号的机制,称为中断机制。
中断机制用于设备与内核通信。
中断本质是一种特殊的电信号,由硬件设备发向处理器。
处理器接受到中断后,会马上向操作系统反映此信号的到来,然后由操作系统负责处理这些新到来的数据。
中断随时可以产生,因此内核随时可能因为新到来的中断而被打断,所以必须保证中断处理程序能够快速执行。
中断是一种电信号,由硬件设备产生,并直接送入中断控制器的输入引脚。
中断控制器是简单的电子芯片,其作用是将多路中断管线,采用复用技术只通过一个和处理器相连接的管线与处理器通信。
当接收到信号之后,中断控制器会给处理器发送一个电信号。
处理器检测到该信号,就中断自己当前的工作转而处理中断。
不同的设备对应不同的中断,而每个中断都通过一个唯一的数字来标识。
这个数字称为中断请求(IRQ)号。
中断请求号在经典PC上是固定的,比如IRQ0是时钟中断,IRQ1是键盘中断;也可以是动态分配的,比如在PCI总线上的设备的中断。
异常
异常也称为同步中断。(相对的,前面讲的中断被称为异步中断)。
它由处理器本身产生。
对中断的处理与跟异常的处理基本一致。(前面的系统调用章节就说过,系统调用可以通过中断触发)
中断处理程序
内核执行中断处理程序或者中断服务例程来响应特定的中断。
中断处理程序针对中断,而不是针对产生中断的设备。
如果一个设备会产生多种中断,则对应有多个中断处理程序。
设备的中断处理程序由它的设备驱动提供。
在Linux中,中断处理程序就是普通的C函数,但是有特定的类型声明。
上半部和下半部
中断处理程序需要快速执行,但是中断处理程序又可能需要处理较多的工作量 。
为了解决这个问题,Linux提供了中断处理程序的上下半部:
上半部:接受到中断之后立即执行,但只做严格时限的工作;
下半部:在合适的时机会被开中断执行。
注册中断处理程序
中断处理程序是驱动程序的一部分。
驱动程序会调用request_irq()来注册中断处理程序(include\linux\interrupt.h):
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
irq:表示要分配的中断号。对于传统PC设备,它的值是确定的;而对于大部分设备,这个值要么通过探测获取,要么可以通过编程动态确定。
handler:中断处理程序:
typedef irqreturn_t (*irq_handler_t)(int, void *);
flags:中断标志,有不同的作用:
/*
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
*/
#define IRQF_DISABLED 0x00000020
#define IRQF_SAMPLE_RANDOM 0x00000040
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
name:与中断相关的设备的ASCII文本表示。
dev:用于共享中断。
- 当一个中断处理程序需要释放时,dev将提供唯一的标志信息,以便从共享中断线的诸多中断处理程序中删除指定的那一个。
- 此外,内核每次调用中断处理程序时,都会把这个指针传递个它,以便标识是哪个设备需要执行中断处理函数。实践中它往往指向驱动程序的设备结构。
- 如果无需共享中断线,那么它的值设置为空。
request_irq()函数可能会睡眠,因此,不能再中断上下文或其它不予许阻塞的代码中调用该函数。
卸载驱动程序时,需要注销相应的中断处理函数:
extern void free_irq(unsigned int, void *);
中断号如果不共享,则删除中断处理程序之后会被禁用。
共享中断处理程序
需要满足以下几点:
- request_irq()的参数flag必须设置IRQF_SHARED标志;
- dev参数必须唯一;
- 中断处理程序中必须能够区分它的设备是否真的产生了中断,这需要硬件的支持,也需要处理程序中有相关的处理逻辑;
内核接收一个中断后,它将依次调用在该中断线上注册的每一个处理程序。这也是上面第3条的原因。
中断处理函数
一个例子:
/*
* Handler for RTC timer interrupt
*/
static irqreturn_t
timer_interrupt(int irq, void *dev_id)
{
struct pt_regs *regs = get_irq_regs();
do_timer(1);
#ifndef CONFIG_SMP
update_process_times(user_mode(regs));
#endif
do_profile(regs);
RTC_RTCC = 0; /* Clear interrupt */
return IRQ_HANDLED;
}
中断处理程序通常是static的,因为它不会被其它文件中的代码直接调用。
中断处理程序返回特定的值(include\linux\irqreturn.h):
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE,
IRQ_HANDLED,
IRQ_WAKE_THREAD,
};
Linux中的中断处理程序是无须重入的,因为当中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽。
中断上下文
中断上下文不可以睡眠,所以不能在中断处理函数中使用可能睡眠的函数。
中断上下文具有较为严格的时间限制,
中断处理程序拥有自己的栈,每个处理器一个,大小为一页,称为中断栈。
中断处理程序不需要关心栈如何设置,或者内核栈的大小是多少,但是应该尽量节约内核栈空间。
内核处理中断程序的入口
中断开始于特定平台预定义的入口点(arch\x86\kernel\entry_32.S):
.section .init.rodata,"a"
ENTRY(interrupt)
.text
.p2align 5
.p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)
RING0_INT_FRAME
vector=FIRST_EXTERNAL_VECTOR
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
.balign 32
.rept 7
.if vector < NR_VECTORS
.if vector <> FIRST_EXTERNAL_VECTOR
CFI_ADJUST_CFA_OFFSET -4
.endif
1: pushl $(~vector+0x80) /* Note: always in signed byte range */
CFI_ADJUST_CFA_OFFSET 4
.if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
jmp 2f
.endif
.previous
.long 1b
.text
vector=vector+1
.endif
.endr
2: jmp common_interrupt
.endr
END(irq_entries_start)
.previous
END(interrupt)
.previous
/*
* the CPU automatically disables interrupts when executing an IRQ vector,
* so IRQ-flags tracing has to follow that:
*/
.p2align CONFIG_X86_L1_CACHE_SHIFT
common_interrupt:
addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range */
SAVE_ALL
TRACE_IRQS_OFF
movl %esp,%eax
call do_IRQ
jmp ret_from_intr
ENDPROC(common_interrupt)
CFI_ENDPROC
这里的do_IRQ对应到了C代码。下面以arch\x86\kernel\irq.c为例:
unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
/* high bit used in ret_from_ code */
unsigned vector = ~regs->orig_ax;
unsigned irq;
exit_idle();
irq_enter();
irq = __get_cpu_var(vector_irq)[vector];
if (!handle_irq(irq, regs)) {
ack_APIC_irq();
if (printk_ratelimit())
pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
__func__, smp_processor_id(), vector, irq);
}
irq_exit();
set_irq_regs(old_regs);
return 1;
}
中断处理程序执行过程中会屏蔽中断(部分或者所有),但是这些中断并不会被放弃,中断处理芯片会存放这些中断,直到CPU空出来之后再送入。
/proc/interrupts
procfs是一个虚拟文件系统,它只存在于内核内存,一般安装与/proc目录。
在procfs中读写文件都要调用内核函数,这些函数模拟从真实文件中读或写。
/proc/interrunpts文件是一个例子,该文件存放系统中与中断相关的统计信息:
中断控制操作
Linux内核提供了一组接口用于操作机器上的中断状态。