Linux 中断处理

1: 通过调用 request_irq api来注册指定中断号上的 irq_handler,flags可选是否是共享或者其他
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);
}

2:requst_irq 进一步调用 request_threaded_irq
desc = irq_to_desc(irq); // 获取该irq号对应的 irq_desc结构,并check相关的字段,注意到desc结构和irq号是一一对应的(采用基数树结构实现irq_desc_tree)
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); // 创建一个irqaction对象
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;

action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;

retval = __setup_irq(irq, desc, action); // 调用__setup_irq 安装action

3:__setup_irq函数负责安装一个irq,应该是代码的核心所在
/*
* Internal function to register an irqaction - typically used to
* allocate special interrupts that are part of the architecture.
*/
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)

Step1: 如果触发类型没有指定,那么使用默认的配置
Step2:检查中断是否被嵌套到其他的中断线程中
Step3:如果该中断提供了一个线程函数并且中断不被其他线程嵌套,那么就为该中断创建一个线程
/*
* Create a handler thread when a thread function is supplied
* and the interrupt does not nest into another interrupt
* thread.
*/
if (new->thread_fn && !nested) {
ret = setup_irq_thread(new, irq, false);
if (ret)
goto out_mput;
if (new->secondary) {
ret = setup_irq_thread(new->secondary, irq, true);
if (ret)
goto out_thread;
}
}
Step4:如果该中断号被设置为共享,那么必须满足下面的条件
(1)同一个IRQ上被注册的其他irq_handler都设置了共享,即IRQF_SHARED
(2)中断触发类型必须相同(level, edge, polarity)
(3)是否是ONESHOT类型也必须保持一致
(4)all handlers must agree on per-cpuness

/* add new interrupt at end of irq queue */
do {
/*
* Or all existing action->thread_mask bits,
* so we can find the next zero bit for this
* new action.
*/
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
Step5:
Step6:设置和更新/proc/interrupts的显示
register_irq_proc(irq, desc);
new->dir = NULL;
register_handler_proc(irq, new);

4:内核提供了配置开关,可以开启强制irq线程
注意:部分中断是不能被线程化的,例如IPIs中断等,这些中断被标志为:IRQF_NO_THREAD,All interrupts marked IRQF_TIMER or IRQF_PER_CPU are automatically excluded from threading
#ifdef CONFIG_IRQ_FORCE_THREADING
__readmostly bool fore_threads

5:早期的中断和异常处理
https://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-2.html
arch/x86/kernel/head64.c,中断向量共256个(前32个为异常,后面的为中断编号:32~255)
#define NUM_EXCEPTION_VECTORS 32
在64位模式下我们IDT(中断描述符表)初始化

set_intr_gate(n, addr)
set_system_gate(n, addr)
set_system_intr_gate(n, addr)
set_trap_gate(n, addr)
set_task_gate(n, addr)

#ifdef CONFIG_X86_64
struct desc_ptr idt_descr __ro_after_init = {
.size = NR_VECTORS * 16 - 1,
.address = (unsigned long) idt_table,
};

asmlinkage __visible void __init x86_64_start_kernel(char * real_mode_data)
{

for (i = 0; i < NUM_EXCEPTION_VECTORS; i++)
set_intr_gate(i, early_idt_handler_array[i]); // 设置中断门,填充到idt_table里
load_idt((const struct desc_ptr *)&idt_descr);
}

static inline void _set_gate(int gate, unsigned type, void *addr,
unsigned dpl, unsigned ist, unsigned seg)
{
gate_desc s;

pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
/*
* does not need to be atomic because it is only done once at
* setup time
*/
write_idt_entry(idt_table, gate, &s);
write_trace_idt_entry(gate, &s);
}

static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
{
memcpy(&idt[entry], gate, sizeof(*gate));
}

如果是由中断门进入中断处理程序的,CPU会清除IF标志位,这样当当前中断处理程序执行时,CPU不会对其他的中断进行处理;
只有当当前的中断处理程序返回时,CPU才在iret指令执行时重新设置IF标志位。

默认情况下,内核栈(线程栈大小为)2个Page大小(32位体系结构上8KB,64位体系结构上16KB)
中断上下文的栈空间大小在禁用KASAN时大小是2个Page大小(和线程栈类似)
#define IRQ_STACK_ORDER (2 + KASAN_STACK_ORDER)
#define IRQ_STACK_SIZE (PAGE_SIZE << IRQ_STACK_ORDER)

#define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER)
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)

PerCPU的中断栈
#ifdef CONFIG_X86_64
per_cpu(irq_stack_ptr, cpu) =
per_cpu(irq_stack_union.irq_stack, cpu) +
IRQ_STACK_SIZE;
#endif

arch/x86/include/asm/irq.h:44:extern __visible unsigned int do_IRQ(struct pt_regs *regs);
arch/x86/kernel/irq_64.c:59: WARN_ONCE(1, "do_IRQ(): %s has overflown the kernel stack (cur:%Lx,sp:%lx,irq stk top-bottom:%Lx-%Lx,exception stk top-bottom:%Lx-%Lx)\n",
arch/x86/kernel/irq.c:208: * do_IRQ handles all normal device IRQ‘s (the special
arch/x86/kernel/irq.c:212:__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
arch/x86/entry/entry_64.S:518: interrupt do_IRQ
arch/x86/entry/entry_32.S:655: call do_IRQ

do_IRQ函数是所有普通设备中断的入口
/*
* do_IRQ handles all normal device IRQ‘s (the special
* SMP cross-CPU interrupts have their own specific
* handlers).
*/
__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc * desc;
/* high bit used in ret_from_ code */
unsigned vector = ~regs->orig_ax;

/*
* NB: Unlike exception entries, IRQ entries do not reliably
* handle context tracking in the low-level entry code. This is
* because syscall entries execute briefly with IRQs on before
* updating context tracking state, so we can take an IRQ from
* kernel mode with CONTEXT_USER. The low-level entry code only
* updates the context if we came from user mode, so we won‘t
* switch to CONTEXT_KERNEL. We‘ll fix that once the syscall
* code is cleaned up enough that we can cleanly defer enabling
* IRQs.
*/

entering_irq();

/* entering_irq() tells RCU that we‘re not quiescent. Check it. */
RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");

desc = __this_cpu_read(vector_irq[vector]);

if (!handle_irq(desc, regs)) {
ack_APIC_irq();

if (desc != VECTOR_RETRIGGERED) {
pr_emerg_ratelimited("%s: %d.%d No irq handler for vector\n",
__func__, smp_processor_id(),
vector);
} else {
__this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
}
}

exiting_irq();

set_irq_regs(old_regs);
return 1;
}

调用流程 do_IRQ --> handle_irq --> generic_handle_irq_desc --> desc->handle_irq(desc);

作业:
(1)irq_stack_union的含义是什么?
(2)desc数据结构是合适创建的?
答:
256个中断向量对应的irq_desc不是一次性初始化完的。
其中一条路径是:start_kernel --> early_irq_init --> alloc_descs --> irq_insert_desc
另外一条路径是: arch_setup_msi_irq --> irq_alloc_hwirq --> irq_alloc_hwirqs --> __irq_alloc_descs --> alloc_descs

时间: 2024-11-05 05:03:02

Linux 中断处理的相关文章

linux中断处理原理分析

首先需要了解一下中断的概念:一个"中断"仅仅是一个信号,当硬件需要获得处理器对它的关注时,就可以发送这个信号.内核维护了一个中断信号线的注册表,该注册表类似于I/O端口的注册表. 模块在使用中断前要先请求一个中断通道(或中断请求IRQ),然后在使用后释放该通道.用到的API就是request_irq()以及free_irq().注意在调用request_irq()和free_irq()的时机最好是在设备第一次打开和最后一次关闭之后. 对于中断处理例程来讲,它的一个典型的任务就是:如果中

Linux 中断处理浅析

最近在研究异步消息处理, 突然想起linux内核的中断处理, 里面由始至终都贯穿着"重要的事马上做, 不重要的事推后做"的异步处理思想. 于是整理一下~ 第一阶段--获取中断号 每个CPU都有响应中断的能力, 每个CPU响应中断时都走相同的流程. 这个流程就是内核提供的中断服务程序. 在进入中断服务程序时, CPU已经自动禁止了本CPU上的中断响应, 因为CPU不能假定中断服务程序是可重入的. 中断处理程序的第一步要做两件事情: 1. 将中断号压入栈中; (不同中断号的中断对应不同的中

Linux中断处理驱动程序编写

本章节我们一起来探讨一下Linux中的中断: http://blog.csdn.net/gotosola/article/details/7422072 中断处理 http://www.cnblogs.com/tianshuai11/archive/2012/04/20/2477168.html 里边有自动探测中断号: http://blog.csdn.net/en_wang/article/details/6726421 当目标设备有能力告知驱动它要使用的中断号时,自动探测中断号只是意味着探测

linux中断处理下文:软中断tasklet机制分析

参考: <Linux内核设计与实现> http://blog.csdn.net/fontlose/article/details/8279113 http://blog.chinaunix.net/uid-27212029-id-3386692.html tasklet是中断处理下半部分最常用的一种方法,驱动程序一般先申请中断,在中断处理函数内完成中断上半部分的工作后调用tasklet.tasklet有如下特点: 1.tasklet只可以在一个CPU上同步地执行,不同的tasklet可以在不同

Linux中断处理流程

1. 中断处理流程 当中断发生时,Linux系统会跳转到asm_do_IRQ()函数(所有中断程序的总入口函数),并且把终端号irq传进来.根据中断号,找到irq_desc结构(一个中断的描述结构),然后调用irq_desc中的handle_irq函数,即子中断入口函数.我们编写中断的驱动,即填充并注册irq_desc结构. 2. 中断处理数据结构:irq_desc Linux内核将所有的中断统一编号,使用一个irq_desc[NR_IRQS]的结构体数组来描述这些中断:每个数组项对应着一个中断

linux中断处理子系统小结

前面的博文中, 大致分散介绍了一些中断相关的东西, 但是对软中断的部分没有仔细介绍, 在这里统一总结一下. 中断上半部的处理,汇编到request_irq的handle之间的过程. http://blog.csdn.net/jackjones_008/article/details/42387241 MIPS平台的一点记录 http://blog.csdn.net/jackjones_008/article/details/41945909 tasklet/workqueue的介绍在下面这篇博文

linux中断处理程序

Linux进行中断处理的4个步骤: 1.当中断产生,跳到统一入口IRQ_SVC 2.获取中断号 3.根据中断号找到irq_desc结构 4.从irq_desc结构中取出事先注册好的中断处理函数 Linux中断处理程序设计: 1.注册中断 2.实现中断处理函数 3.注销处理 一.注册中断 使用request_irq函数 原型:int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags, const char

linux signal 处理

v/:* {behavior:url(#default#VML);} o/:* {behavior:url(#default#VML);} w/:* {behavior:url(#default#VML);} .shape {behavior:url(#default#VML);} Normal 0 7.8 pt 0 2 false false false EN-US ZH-CN X-NONE /* Style Definitions */ table.MsoNormalTable {mso-s

Linux中断机制

1.中断概念 中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序.Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常). 在实地址模式中,CPU把内存中从0开始的1KB空间作为一个中断向量表.表中的每一项占4个字节.但是在保护模式中,有这4个字节的表项构成的中断向量表不满足实际需求,于是根据反映模式切换的信息和偏移量的足够使得中断向量表