Linux中断处理浅析

时间:2022-10-30 14:35:54

转载请注明出处:http://blog.csdn.net/hxcpp

目录:

1 Intel提供的硬件及机制 (没有总结)
2 Linux对中断的处理
    2.1 关键数据结构
        2.1.1 irq_chip
        2.1.2 irq_desc
    2.2 中断初始化
        2.2.1 local_irq_disable (没有总结)
        2.2.2 setup_arch    (没有总结)
        2.2.3 trap_init        (没有总结)
        2.2.4 early_irq_init()    (没有总结)
        2.2.5 init_IRQ()
            2.2.5.1 native_init_IRQ()
                2.2.5.1.1 init_ISA_irqs()
                2.2.5.1.2 interrupt数组
        2.2.6 local_irq_enable()(没有总结)
    2.3 中断处理
        2.3.1 CPU对中断的响应
        2.3.2 中断处理程序
            2.3.2.1 do_IRQ()
            2.3.2.2 handle_irq()
            2.3.2.3 irq_exit()
    2.4 中断请求服务 request_irq()函数


1 Intel提供的硬件及机制

2 Linux对中断的处理

2.1 关键数据结构

2.1.1 irq_chip
Linux中定义一个irq_chip结构用于描述中断控制器,因为不同的硬件系统可能会使用不同的中断控制器,如之前的8259A以及多处理器系统中使用的IOAPIC和LAPIC等。这个结构就是一些函数指针,这样做的好处是使得内核无需关注硬件差异,仅仅需要提供相应的操作函数。该结构定义如下:

277 "include/linux/irq.h"
278 /**
279 * struct irq_chip - hardware interrupt chip descriptor
280 *
281 * @name: name for /proc/interrupts
282 * @irq_startup: start up the interrupt (defaults to ->enable if NULL)
283 * @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
284 * @irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
285 * @irq_disable: disable the interrupt
286 * @irq_ack: start of a new interrupt
287 * @irq_mask: mask an interrupt source
288 * @irq_mask_ack: ack and mask an interrupt source
289 * @irq_unmask: unmask an interrupt source
290 * @irq_eoi: end of interrupt
291 * @irq_set_affinity: set the CPU affinity on SMP machines
292 * @irq_retrigger: resend an IRQ to the CPU
293 * @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
294 * @irq_set_wake: enable/disable power-management wake-on of an IRQ
295 * @irq_bus_lock: function to lock access to slow bus (i2c) chips
296 * @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
297 * @irq_cpu_online: configure an interrupt source for a secondary CPU
298 * @irq_cpu_offline: un-configure an interrupt source for a secondary CPU
299 * @irq_suspend: function called from core code on suspend once per chip
300 * @irq_resume: function called from core code on resume once per chip
301 * @irq_pm_shutdown: function called from core code on shutdown once per chip
302 * @irq_print_chip: optional to print special chip info in show_interrupts
303 * @flags: chip specific flags
304 *
305 * @release: release function solely used by UML
306 */
307 struct irq_chip {
308 const char *name;
309 unsigned int (*irq_startup)(struct irq_data *data);
310 void (*irq_shutdown)(struct irq_data *data);
311 void (*irq_enable)(struct irq_data *data);
312 void (*irq_disable)(struct irq_data *data);
313
314 void (*irq_ack)(struct irq_data *data);
315 void (*irq_mask)(struct irq_data *data);
316 void (*irq_mask_ack)(struct irq_data *data);
317 void (*irq_unmask)(struct irq_data *data);
318 void (*irq_eoi)(struct irq_data *data);
319
320 int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
321 int (*irq_retrigger)(struct irq_data *data);
322 int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
323 int (*irq_set_wake)(struct irq_data *data, unsigned int on);
324
325 void (*irq_bus_lock)(struct irq_data *data);
326 void (*irq_bus_sync_unlock)(struct irq_data *data);
327
328 void (*irq_cpu_online)(struct irq_data *data);
329 void (*irq_cpu_offline)(struct irq_data *data);
330
331 void (*irq_suspend)(struct irq_data *data);
332 void (*irq_resume)(struct irq_data *data);
333 void (*irq_pm_shutdown)(struct irq_data *data);
334
335 void (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
336
337 unsigned long flags;
338
339 /* Currently used only by UML, might disappear one day.*/
340 #ifdef CONFIG_IRQ_RELEASE_METHOD
341 void (*release)(unsigned int irq, void *dev_id);
342 #endif
343 };

其中8259A的中断控制器对象如下,这几个函数分别用于操作8259A中断控制器:

221 "arch/x86/kernel/i8259.c"
222 struct irq_chip i8259A_chip = {
223 .name = "XT-PIC",
224 .irq_mask = disable_8259A_irq,
225 .irq_disable = disable_8259A_irq,
226 .irq_unmask = enable_8259A_irq,
227 .irq_mask_ack = mask_and_ack_8259A,
228 };

2.1.2 irq_desc
由于同一个外部中断可能被多个外部设备共享,而每个不同的外部设备都可以动态地注册和撤销自己的中断处理例程,为此,内核定义了一个irq_desc结构,每一个中断向量都有自己的irq_desc,暂且称之为中断请求描述符。

"include/linux/irqdesc.h"
15 /**
16 * struct irq_desc - interrupt descriptor
17 * @irq_data: per irq and chip data passed down to chip functions
18 * @timer_rand_state: pointer to timer rand state struct
19 * @kstat_irqs: irq stats per cpu
20 * @handle_irq: highlevel irq-events handler
21 * @preflow_handler: handler called before the flow handler (currently used by sparc)
22 * @action: the irq action chain
23 * @status: status information
24 * @core_internal_state__do_not_mess_with_it: core internal status information
25 * @depth: disable-depth, for nested irq_disable() calls
26 * @wake_depth: enable depth, for multiple irq_set_irq_wake() callers
27 * @irq_count: stats field to detect stalled irqs
28 * @last_unhandled: aging timer for unhandled count
29 * @irqs_unhandled: stats field for spurious unhandled interrupts
30 * @lock: locking for SMP
31 * @affinity_hint: hint to user space for preferred irq affinity
32 * @affinity_notify: context for notification of affinity changes
33 * @pending_mask: pending rebalanced interrupts
34 * @threads_oneshot: bitfield to handle shared oneshot threads
35 * @threads_active: number of irqaction threads currently running
36 * @wait_for_threads: wait queue for sync_irq to wait for threaded handlers
37 * @dir: /proc/irq/ procfs entry
38 * @name: flow handler name for /proc/interrupts output
39 */
40 struct irq_desc {
41 struct irq_data irq_data;
42 unsigned int __percpu *kstat_irqs;
43 irq_flow_handler_t handle_irq;
44 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
45 irq_preflow_handler_t preflow_handler;
46 #endif
47 struct irqaction *action; /* IRQ action list */
48 unsigned int status_use_accessors;
49 unsigned int core_internal_state__do_not_mess_with_it;
50 unsigned int depth; /* nested irq disables */
51 unsigned int wake_depth; /* nested wake enables */
52 unsigned int irq_count; /* For detecting broken IRQs */
53 unsigned long last_unhandled; /* Aging timer for unhandled count */
54 unsigned int irqs_unhandled;
55 raw_spinlock_t lock;
56 struct cpumask *percpu_enabled;
57 #ifdef CONFIG_SMP
58 const struct cpumask *affinity_hint;
59 struct irq_affinity_notify *affinity_notify;
60 #ifdef CONFIG_GENERIC_PENDING_IRQ
61 cpumask_var_t pending_mask;
62 #endif
63 #endif
64 unsigned long threads_oneshot;
65 atomic_t threads_active;
66 wait_queue_head_t wait_for_threads;
67 #ifdef CONFIG_PROC_FS
68 struct proc_dir_entry *dir;
69 #endif
70 struct module *owner;
71 const char *name;
72 } ____cacheline_internodealigned_in_smp;

目前仅关注handle_irq和action两个成员。

handle_irq 类型为irq_flow_handler_t,该结构定义如下:
35 typedef void (*irq_flow_handler_t)(unsigned int irq,
36 struct irq_desc *desc);

也即handle_irq为一个函数指针。

如前所述,多个设备能共享一个单独的IRQ。因此内核要维护多个irqaction描述符,其中每个描述符涉及一个特定的硬件设备和一个特定的中断,该结构定义如下:

 
92 "include/linux/interrupt.h"
93 /**
94 * struct irqaction - per interrupt action descriptor
95 * @handler: interrupt handler function
96 * @flags: flags (see IRQF_* above)
97 * @name: name of the device
98 * @dev_id: cookie to identify the device
99 * @percpu_dev_id: cookie to identify the device
100 * @next: pointer to the next irqaction for shared interrupts
101 * @irq: interrupt number
102 * @dir: pointer to the proc/irq/NN/name entry
103 * @thread_fn: interrupt handler function for threaded interrupts
104 * @thread: thread pointer for threaded interrupts
105 * @thread_flags: flags related to @thread
106 * @thread_mask: bitmask for keeping track of @thread activity
107 */
108 struct irqaction {
109 irq_handler_t handler;
110 unsigned long flags;
111 void *dev_id;
112 void __percpu *percpu_dev_id;
113 struct irqaction *next;
114 int irq;
115 irq_handler_t thread_fn;
116 struct task_struct *thread;
117 unsigned long thread_flags;
118 unsigned long thread_mask;
119 const char *name;
120 struct proc_dir_entry *dir;
121 } ____cacheline_internodealigned_in_smp;

91 typedef irqreturn_t (*irq_handler_t)(int, void *);



X86一共定义了256个中断,前32个用于异常处理函数或Intel保留,剩下224个中断,内核定义了irq_desc数组共224项(可能并非线性的数组结构如radix_tree,暂且这么理解,简单起见)。当发生n号中断时,中断处理函数会利用n索引到irq_desc数组的第n个irq_desc成员,然后调用irq_desc结构中的handle_irq(),随后又调用action中的handler函数,该函数则与具体的中断相关,从而进行实质性的处理。next成员反映出irq_desc中可以形成一个irqaction的链式结构,从而满足了前面提到的多个设备共享irq的要求。

设备驱动程序调用request_irq()注册中断时,该函数的实质就是构造一个irq_action结构,并将其挂接到action链表中。

*******后续的内容会分析request_irq()函数的具体内容以及handle_irq()函数的设置*******

这里存在一个问题,当多个设备共享一个中断号时,当该中断发生时,则每个中断处理函数都将会被调用一次,这显然是不合理的。实际上,每个外部设备都有中断状态寄存器,它们的中断处理程序会读取设备的中断状态,并依次进行下一步的处理。

当然,系统在处理中断时,何时关中断,何时重新允许中断,时机需要进一步分析,否则上述过程可能存在问题。下面分析外部中断初始化:

2.2 中断初始化

中断的初始化主要有一下几个方面,首先是设置中断向量表,同时需要初始化中断控制器,以及相关的管理结构。

在系统启动进入保护模式前,内核设置了中断向量表idt_table,其中的中断处理函数都是ignore_init()。

"arch/x86/kernel/head_32.S"
510 setup_idt:
511 lea ignore_int,%edx
512 movl $(__KERNEL_CS << 16),%eax
513 movw %dx,%ax /* selector = 0x0010 = cs */
514 movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
515
516 lea idt_table,%edi
517 mov $256,%ecx
518 rp_sidt:
519 movl %eax,(%edi)
520 movl %edx,4(%edi)
521 addl $8,%edi
522 dec %ecx
523 jne rp_sidt



随后,内核将在start_kernel()中再次对IDT进行初始化,用有意义的陷阱和中断处理程序替换这个空处理程序。之后,对于控制单元确认的每个不同异常,IDT都有一个专门的陷阱或系统门,而对于可编程中断控制器确认的每一个IRQ,IDT都将包含一个专门的中断门。

start_kernel()中与中断相关的函数主要由以下几个:
    local_irq_disable();
    setup_arch(&command_line);  
    trap_init();  
    early_irq_init();  
    init_IRQ();  
    local_irq_enable();

2.2.1 local_irq_disable
2.2.2 setup_arch
2.2.3 trap_init
2.2.4 early_irq_init()

2.2.5 init_IRQ()

117 "arch/x86/kernel/irqinit.c"
118 void __init init_IRQ(void)
119 {
120 int i;
121
122 /*
123 * We probably need a better place for this, but it works for
124 * now ...
125 */
126 x86_add_irq_domains();
127
128 /*
129 * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR's to IRQ 0..15.
130 * If these IRQ's are handled by legacy interrupt-controllers like PIC,
131 * then this configuration will likely be static after the boot. If
132 * these IRQ's are handled by more mordern controllers like IO-APIC,
133 * then this vector space can be freed and re-used dynamically as the
134 * irq's migrate etc.
135 */
136 for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)
137 per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i;
138
139 x86_init.irqs.intr_init();
140 }

line 126 对于不支持IOAPIC的计算机,x86_add_irq_domains(),暂且不分析该函数。
line 136 legacy_pic为legacy_pic类型指针,指向default_legacy_pic变量,该变量同中断控制器8259A有关,同时定义了该中断控制器的一些操作函数,具体定义如下:

 54 "arch/x86/include/asm/i8259.h"        
55 struct legacy_pic {
56 int nr_legacy_irqs;
57 struct irq_chip *chip;
58 void (*mask)(unsigned int irq);
59 void (*unmask)(unsigned int irq);
60 void (*mask_all)(void);
61 void (*restore_mask)(void);
62 void (*init)(int auto_eoi);
63 int (*irq_pending)(unsigned int irq);
64 void (*make_irq)(unsigned int irq);
65 };

378 "arch/x86/kernel/i8259.c"
379 struct legacy_pic default_legacy_pic = {
380 .nr_legacy_irqs = NR_IRQS_LEGACY,
381 .chip = &i8259A_chip,
382 .mask = mask_8259A_irq,
383 .unmask = unmask_8259A_irq,
384 .mask_all = mask_8259A,
385 .restore_mask = unmask_8259A,
386 .init = init_8259A,
387 .irq_pending = i8259A_irq_pending,
388 .make_irq = make_8259A_irq,
389 };
390
391 struct legacy_pic *legacy_pic = &default_legacy_pic;

分量nr_legacy_irqs为NR_IRQS_LEGACY(16),应该指的是PIC为8259A时的16条IRQ线。

line 137 per_cpu(var,cpu)表示获取编号cpu的处理器上面的变量var的副本;vector_irq是一个长度为NR_VECTORS (256) 的整形数组,该数组的具体作用目前还不清楚;IRQ0_VECTOR定义值为0x30,表示8259A IRQ0对应的中断向量为0x30。
因此,for循环的作用是将vector_irq数组的第0x30~0x3f项的值设置为0~15,目前不清楚这样设置的具体原因。

line 139 x86_init.irqs.intr_init()
x86_init为x86_init_ops类型变量,其中定义了x86体系结构专用的初始化函数。这里仅关注其中的irqs分量。

128 "arch/x86/include/asm/x86_init.h"
129 /**
130 * struct x86_init_ops - functions for platform specific setup
131 *
132 */
133 struct x86_init_ops {
134 struct x86_init_resources resources;
135 struct x86_init_mpparse mpparse;
136 struct x86_init_irqs irqs;
137 struct x86_init_oem oem;
138 struct x86_init_mapping mapping;
139 struct x86_init_paging paging;
140 struct x86_init_timers timers;
141 struct x86_init_iommu iommu;
142 struct x86_init_pci pci;
143 };

"arch/x86/include/asm/x86_init.h"
48 /**
49 * struct x86_init_irqs - platform specific interrupt setup
50 * @pre_vector_init: init code to run before interrupt vectors
51 * are set up.
52 * @intr_init: interrupt init code
53 * @trap_init: platform specific trap setup
54 */
55 struct x86_init_irqs {
56 void (*pre_vector_init)(void);
57 void (*intr_init)(void);
58 void (*trap_init)(void);
59 };
 
 定义x86_init时给出了该变量的初始化,其中,irqs分量如下设置:

 
32 "arch/x86/kernel/x86_init.c"
33 /*
34 * The platform setup functions are preset with the default functions
35 * for standard PC hardware.
36 */
37 struct x86_init_ops x86_init __initdata = {
54 ....
55 .irqs = {
56 .pre_vector_init = init_ISA_irqs,
57 .intr_init = native_init_IRQ,
58 .trap_init = x86_init_noop,
59 },
60 ....
91 };

;
 
 因此,line 139实际执行native_init_IRQ函数,下面对其进行分析。

2.2.5.1 native_init_IRQ()

293 "arch/x86/kernel/irqinit.c"
294 void __init native_init_IRQ(void)
295 {
296 int i;
297
298 /* Execute any quirks before the call gates are initialised: */
299 x86_init.irqs.pre_vector_init();
300
301 apic_intr_init();
302
303 /*
304 * Cover the whole vector space, no vector can escape
305 * us. (some of these will be overridden and become
306 * 'special' SMP interrupts)
307 */
308 i = FIRST_EXTERNAL_VECTOR;
309 for_each_clear_bit_from(i, used_vectors, NR_VECTORS) {
310 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
311 set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]);
312 }
313
314 if (!acpi_ioapic && !of_ioapic)
315 setup_irq(2, &irq2);
316
317 #ifdef CONFIG_X86_32
318 /*
319 * External FPU? Set up irq13 if so, for
320 * original braindamaged IBM FERR coupling.
321 */
322 if (boot_cpu_data.hard_math && !cpu_has_fpu)
323 setup_irq(FPU_IRQ, &fpu_irq);
324
325 irq_ctx_init(smp_processor_id());
326 #endif
327 }

line 299 实际执行的是init_ISA_irqs()函数,该函数主要是对8259A以及与之相关的中断进行初始化设置,2.2.5.1.1节对其进行分析。

line 301 apic_intr_init();显然是针对apic进行相应的中断初始化设置,APIC允许SMP结构中各处理器间发送中断(处理器间中断IPI),同时APIC中引入一些新的中断,如本地APIC时钟中断,温度中断等,该函数为这些中断在IDT中设置相应的中断门描述符。以后可能会对APIC进行总结,到时详细讨论,此处略过。

line 308~312 个人认为是初始化IDT表的最核心环节,下面详细分析:

for_each_clear_bit_from为一个宏,定义如下:

 39 "include/linux/bitops.h"         
40 /* same as for_each_clear_bit() but use bit as value to start with */
41 #define for_each_clear_bit_from(bit, addr, size) \
42 for ((bit) = find_next_zero_bit((addr), (size), (bit)); \
43 (bit) < (size); \
44 (bit) = find_next_zero_bit((addr), (size), (bit) + 1))
45  

展开后为一个for循环,其功能是在位图used_vectors中遍历为0的位,位图used_vectors反映了中断向量的使用情况,若该中断向量已使用,则对应位图中的位置1。
对于这些为0的位置(区间是FIRST_EXTERNAL_VECTOR 0x20到NR_VECTORS 256),设置中断门描述符:set_intr_gate()。该函数最终调用_set_gate()来完成在IDT中设置中断门描述符。

line 311 set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]); 第二个实参是interrupt数组中的一个元素,是值传递;而set_intr_gate()第二个参数为void *addr,指针类型,则说明数组interrupt中的元素是指针,最后可能是一个函数指针,存放中断处理程序入口的地址。后面分析interrupt数组时也验证了这一猜想。

"arch/x86/include/asm/desc.h"
341 static inline void set_intr_gate(unsigned int n, void *addr)
342 {
343 BUG_ON((unsigned)n > 0xFF);
344 _set_gate(n, GATE_INTERRUPT, addr, 0, 0, __KERNEL_CS);
345 }


321 "arch/x86/include/asm/desc.h"
322 static inline void _set_gate(int gate, unsigned type, void *addr,
323 unsigned dpl, unsigned ist, unsigned seg)
324 {
325 gate_desc s;
326
327 pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
328 /*
329 * does not need to be atomic because it is only done once at
330 * setup time
331 */
332 write_idt_entry(idt_table, gate, &s);
333 }

pack_gate()函数会依据中断门描述符的格式构造出一个gate_desc结构,将其加入到idt_table中,具体过程可以对照intel文档中中断门描述符格式逐步分析。

IDT表中存放的是中断处理程序的入口地址,从上述过程中可以发现在这些中断门描述符中存放的是interrupt数组元素的地址,2.2.5.1.2节中将分析interrupt数组的具体内容。

line 315 此处setup_irq(2, &irq2);验证了2.2.5.1.1节中关于级联IRQ2处理时的疑惑,此处设置了irqaction,对于setup_irq()的处理,在request_irq中进行分析。****
这里 irq2定义如下:

 
77 "arch/x86/kernel/irqinit.c"
78 /*
79 * IRQ2 is cascade interrupt to second interrupt controller
80 */
81 static struct irqaction irq2 = {
82 .handler = no_action,
83 .name = "cascade",
84 .flags = IRQF_NO_THREAD,
85 };

 line 323 为协处理器中断设置irqaction,不过多关注
 line 325 irq_ctx_init()初始化中断栈,包括软中断栈。内核在处理中断时,可以使用当前进程的内核栈,也可以单独使用中断栈。这里暂且不展开吧****。
 
 至此,整个中断初始化过程就结束了!总结一下主要是IDT,中断控制器,irq_desc中断请求描述符的设置!。

2.2.5.1.1 init_ISA_irqs()

102 "arch/x86/kernel/irqinit.c"
103 void __init init_ISA_irqs(void)
104 {
105 struct irq_chip *chip = legacy_pic->chip;
106 const char *name = chip->name;
107 int i;
108
109 #if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
110 init_bsp_APIC();
111 #endif
112 legacy_pic->init(0);
113
114 for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)
115 irq_set_chip_and_handler_name(i, chip, handle_level_irq, name);
116 }

line 105 该函数首先定义了一个irq_chip类型指针chip指向legacy_pic->chip,也即8259A_chip(2.1.1节),芯片名称为"XT-PIC"。
line 106 暂且忽略条件汇编中的内容,在分析APIC时再分析该函数。
line 112 legacy_pic->init(0)即为函数init_8259A(),顾名思义初始化8259A,微机原理课程中详细介绍了8259A的使用方法和初始化步骤,在此不赘述。看一下该函数的实现:

298 "arch/x86/kernel/i8259.c"
299 static void init_8259A(int auto_eoi)
300 {
301 unsigned long flags;
302
303 i8259A_auto_eoi = auto_eoi;
304
305 raw_spin_lock_irqsave(&i8259A_lock, flags);
306
307 outb(0xff, PIC_MASTER_IMR); /* mask all of 8259A-1 */
308 outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */
309
310 /*
311 * outb_pic - this has to work on a wide range of PC hardware.
312 */
313 outb_pic(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */
314
315 /* ICW2: 8259A-1 IR0-7 mapped to 0x30-0x37 on x86-64,
316 to 0x20-0x27 on i386 */
/*此处将中断向量IRQ0_VECTOR写入8259A,使得后期发生中断时
*CPU可以读取中断向量号0x30~0x3f
* 从而在IDT中找到对应的入口,执行中断处理程序
*/
317 outb_pic(IRQ0_VECTOR, PIC_MASTER_IMR);
318
319 /* 8259A-1 (the master) has a slave on IR2 */
320 outb_pic(1U << PIC_CASCADE_IR, PIC_MASTER_IMR);
321
322 if (auto_eoi) /* master does Auto EOI */
323 outb_pic(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR);
324 else /* master expects normal EOI */
325 outb_pic(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR);
326
327 outb_pic(0x11, PIC_SLAVE_CMD); /* ICW1: select 8259A-2 init */
328
329 /* ICW2: 8259A-2 IR0-7 mapped to IRQ8_VECTOR */
330 outb_pic(IRQ8_VECTOR, PIC_SLAVE_IMR);
331 /* 8259A-2 is a slave on master's IR2 */
332 outb_pic(PIC_CASCADE_IR, PIC_SLAVE_IMR);
333 /* (slave's support for AEOI in flat mode is to be investigated) */
334 outb_pic(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR);
335
336 if (auto_eoi)
337 /*
338 * In AEOI mode we just have to mask the interrupt
339 * when acking.
340 */
341 i8259A_chip.irq_mask_ack = disable_8259A_irq;
342 else
343 i8259A_chip.irq_mask_ack = mask_and_ack_8259A;
344
345 udelay(100); /* wait for 8259A to initialize */
346
347 outb(cached_master_mask, PIC_MASTER_IMR); /* restore master IRQ mask */
348 outb(cached_slave_mask, PIC_SLAVE_IMR); /* restore slave IRQ mask */
349
350 raw_spin_unlock_irqrestore(&i8259A_lock, flags);
351 }
352

line 114 通常系统中有两片8259A,主片的IRQ2线用于级联,接上从8259A。此处for循环进行16次,irq_set_chip_and_handler_name很显然是为中断请求描述符irq_desc设置处理芯片以及中断处理程序handle_irq。值得注意的是由于主片IRQ2用于级联,直观上相应的中断处理函数应该略有不同,但此处应该统一化处理,需要关注后期是否有修改***。

660 "kernel/irq/chip.c"    
661 void
662 irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
663 irq_flow_handler_t handle, const char *name)
664 {
665 irq_set_chip(irq, chip);
666 __irq_set_handler(irq, handle, 0, name);
667 }

"kernel/irq/chip.c"
23 /**
24 * irq_set_chip - set the irq chip for an irq
25 * @irq: irq number
26 * @chip: pointer to irq chip description structure
27 */
28 int irq_set_chip(unsigned int irq, struct irq_chip *chip)
29 {
30 unsigned long flags;
31 struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);
32
33 if (!desc)
34 return -EINVAL;
35
36 if (!chip)
37 chip = &no_irq_chip;
38
39 desc->irq_data.chip = chip;
40 irq_put_desc_unlock(desc, flags);
41 /*
42 * For !CONFIG_SPARSE_IRQ make the irq show up in
43 * allocated_irqs. For the CONFIG_SPARSE_IRQ case, it is
44 * already marked, and this call is harmless.
45 */
46 irq_reserve_irq(irq);
47 return 0;
48 }

622 "kernel/irq/chip.c"
623 void
624 __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
625 const char *name)
626 {
627 unsigned long flags;
628 struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
629
630 if (!desc)
631 return;
632
633 if (!handle) {
634 handle = handle_bad_irq;
635 } else {
636 if (WARN_ON(desc->irq_data.chip == &no_irq_chip))
637 goto out;
638 }
639
640 /* Uninstall? */
641 if (handle == handle_bad_irq) {
642 if (desc->irq_data.chip != &no_irq_chip)
643 mask_ack_irq(desc);
644 irq_state_set_disabled(desc);
645 desc->depth = 1;
646 }
647 desc->handle_irq = handle;
648 desc->name = name;
649
650 if (handle != handle_bad_irq && is_chained) {
651 irq_settings_set_noprobe(desc);
652 irq_settings_set_norequest(desc);
653 irq_settings_set_nothread(desc);
654 irq_startup(desc, true);
655 }
656 out:
657 irq_put_desc_busunlock(desc, flags);
658 }

需要注意,这两个函数里面的参数irq即为for循环中的i,从0开始,也就是说在获取对应中断请求描述符irq_desc时下标从0开始。若irq_desc为数组,则应该为(&irq_desc[irq])这种形式,那么也就是说,8259A中的中断在irq_desc数组中是占据0~15项的,而根据前面分析,8259A中中断对应中断向量起始为0x30,Linux可用中断向量从0x20开始。因此,中断向量同irq_desc之间的对应关系并非y=x这样的一一对应。回想起前面对vector_irq数组的设置(init_IRQ()中),可以猜想,该数组中应该就是存放从中断向量到中断请求描述符irq的映射!

设置的中断处理函数为handle_level_irq(),level的含义为中断是该中断是电平触发方式的,同理,handle_edge_irq()则是处理边沿触发的中断。handle_level_irq()即是irq_desc中的handle_irq成员,将在分析中断处理过程时详细分析。  

对于irq_desc的结构形式,是数组,还是radix_tree。值得分析,此处忽略吧。****    

2.2.5.1.2 interrupt数组

内核在hw_irq.h文件中声明了一个interrupt数组,从该声明中可以看出,该数组是一个函数指针类型的数组。
"arch/x86/include/asm/hw_irq.h"
164 extern void (*__initconst interrupt[NR_VECTORS-FIRST_EXTERNAL_VECTOR])(void);

也就是说,interrupt数组中保存的是前面所初始化的(依据used_vectors位图)中断服务程序的入口地址,它的定义是在entry_32.S中,因此需要很好的理解它!
我会按照自己理解注释代码。

 
789 "arch/x86/kernel/entry_32.S"
790 /*
791 * Build the entry stubs and pointer table with some assembler magic.
792 * We pack 7 stubs into a single 32-byte chunk, which will fit in a
793 * single cache line on all modern x86 implementations.
794 */
795 .section .init.rodata,"a"
796 ENTRY(interrupt)
797 .section .entry.text, "ax"
798 .p2align 5
799 .p2align CONFIG_X86_L1_CACHE_SHIFT
800 ENTRY(irq_entries_start)
801 RING0_INT_FRAME
802 vector=FIRST_EXTERNAL_VECTOR
803 .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
804 .balign 32
805 .rept 7
806 .if vector < NR_VECTORS
807 .if vector <> FIRST_EXTERNAL_VECTOR
808 CFI_ADJUST_CFA_OFFSET -4
809 .endif
810 1: pushl_cfi $(~vector+0x80) /* Note: always in signed byte range */
811 .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
812 jmp 2f
813 .endif
814 .previous
815 .long 1b
816 .section .entry.text, "ax"
817 vector=vector+1
818 .endif
819 .endr
820 2: jmp common_interrupt
821 .endr
822 END(irq_entries_start)
823
824 .previous
825 END(interrupt)
826 .previous

line 795 .section定义了一个段 .init.rodata ,该段是只读的,“a”表示section is allocatable,需要为该段分配内存
line 796 ENTRY(interrupt) 为数据段入口,即interrupt起始地址
line 797 .section .entry.text, "ax" 为可执行代码段 “x”表示executable,表示该段可执行

line 798,799
.p2align 5
.p2align CONFIG_X86_L1_CACHE_SHIFT
 //advances the location counter until it a multiple of 32. If the location counter is already a multiple of 32, no change is needed.
设置对齐方式,32字节吧,目的是便于cache,不管太多。

line 800 ENTRY(irq_entries_start) 设置代码段的入口地址为irq_entries_start

line 801 RING0_INT_FRAME,为宏定义如下:

 

261 .macro RING0_INT_FRAME
262 CFI_STARTPROC simple // #define CFI_STARTPROC .cfi_startproc
263 CFI_SIGNAL_FRAME // #define CFI_SIGNAL_FRAME .cfi_signal_frame
264 CFI_DEF_CFA esp, 3*4 //#define CFI_DEF_CFA .cfi_def_cfa
265 /*CFI_OFFSET cs, -2*4;*/
266 CFI_OFFSET eip, -3*4 //#define CFI_OFFSET .cfi_offset
267 .endm
 

不管了吧,cfi通用Flash存储器接口什么的。


line 802 vector=FIRST_EXTERNAL_VECTOR 0x20,外部中断向量从这开始
line 803 .rept循环,(256-32+6)/7 =32 次
line 804 .balign 32 按32字节对齐
.balign 增加位置计数器(在当前子段),使它指向规定的存储边界,并且使用nop指令填充空白区
line 805 .rept 两层循环 32*7=224次
line 806 .if vector < NR_VECTORS ,NR_VECTORS=256 应该是控制循环退出条件吧
line 808 CFI_ADJUST_CFA_OFFSET -4 //达到按4字节对齐,差不多这个意思吧
line 810 pushl_cfi $(~vector+0x80)
    宏pushl_cfi 定义如下:
    .macro pushl_cfi reg
         pushl \reg     //压栈
         CFI_ADJUST_CFA_OFFSET 4 //4字节对齐
        .endm
line 811 当vector=38 45 52... if 为假,不执行jmp 2f,不知道原因******
line 814 .previous表示在最近的段之间进行切换,当汇编器处理上面这段代码时 .init.rodata进入数据段 .entry.text进入代码段,然后遇到.previous又进入数据段
line 815 表示在数据段interrupt中放置上面标号为1的指令地址。
line 816 .section .entry.text, "ax" 回到 .entry.text代码段
line 817 vector+1
line 821 结束224次循环
line 822 结束代码段
line 824 返回数据段,结束数据段的interrupt
line 826 返回定义数据段之前定义的那个段
 
因此,上面这段代码告诉汇编器,在数据段定义一个interrupt作为起始,然后进入代码段,放置push $(~vector+0x80)和jmp common_interrupt两条指令(jmp 2f不管吧,那段代码没有弄透),指令要求在4的边界上对齐,不足的用nop补齐。然后用.previous切换到数据段中,并在数据段中放置标号为1(.long 1b)的指令的地址。最终汇编出来的代码如下:

<irq_entries_start>
#数据段中的interrupt[0]指向这里
pushl $(~0x20+0x80)
jmp common_interrupt
nop
#数据段中的interrupt[1]指向这里
pushl $(~0x21+0x80)
jmp common_interrupt
nop
#数据段中的interrupt[2]指向这里
pushl $(~0x22+0x80)
jmp common_interrupt
nop
......

因此,发生中断后,中断向量取反在加上0x80(why加上)入栈,再跳转到common_interrupt处继续执行。

2.2.6 local_irq_enable()

2.3 中断处理

2.3.1 CPU对中断的响应

当执行一条指令后,cs和eip会包含下一条将要执行的指令的逻辑地址。在执行下一条指令之前,控制单元会检查是否有中断或异常产生,若有,则执行下列操作:

在下列步骤处理之前应该关中断吧,个人觉得,cpu自动处理。
(INTEL 解释 6.12.1.2
The only difference between an interrupt gate and a trap gate is the way the processor handles the IF flag in the EFLAGS register. When accessing an exception- or interrupt-handling procedure through an interrupt gate, the processor clears the IF flag to prevent other interrupts from interfering with the current interrupt handler. A subse-quent IRET instruction restores the IF flag to its value in the saved contents of the EFLAGS register on the stack. Accessing a handler procedure through a trap gate does not affect the IF flag.


(1) 确定与中断或异常相关联的中断向量i(0~255),对于中断,可以读取中断控制器的相关寄存器获取;对于异常,控制单元逻辑会获取到。

(2) 读由idtr寄存器指向的IDT表中的第i项,IDT表中包含的是一个中断门或一个陷阱门(两者的区别会在Intel提供的硬件及机制中总结)

(3) 从gdtr寄存器中获得GDT的基地址,并在GDT中查找,以读取IDT表项中选择符所标识的段描述符。这个段描述符指定中断或异常处理程序所在段的基地址。

(4) 确定中断是由授权的(中断)发生源产生的。首先将当前特权级CPL(cs寄存器)与段描述符(存放在GDT中)的描述符特权级DPL比较,如果CPL小于DPL,则产生一个"General protection"异常,因为中断处理程序的特权不能低于引起中断的程序的特权。(The processor does not permit transfer of execution to an exception- or interrupt-handler procedure in a less privileged code segment (numerically greater privilege level) than the CPL.)
此外,还需:
a) Because interrupt and exception vectors have no RPL, the RPL is not checked on implicit calls to exception and interrupt handlers.
b) The processor checks the DPL of the interrupt or trap gate only if an exception or interrupt is generated with an INT n, INT 3, or INTO instruction. Here, the CPL must be less than or equal to the DPL of the gate. This restriction prevents application programs or procedures running at privilege level 3 from using a software interrupt to access critical exception handlers, such as the page-fault handler, providing that those handlers are placed in more privileged code segments (numerically lower privilege level). For hardware-generated
interrupts and processor-detected exceptions, the processor ignores the DPL of interrupt and trap gates.

(5) 检查是否发生特权级的变化(用户态程序执行时发生中断,需要切换到内核态执行),也就是说,CPL是否不同于所选择的段描述符的DPL。如果是,控制单元必须开始使用与新的特权级相关的栈。通过执行一下步骤实现:
a) 读tr寄存器来访问当前运行进程的TSS段。
b) 用与新特权级相关的栈段和栈指针的正确值装载ss和esp寄存器,这些值可以在TSS中找到。
c) 在新的栈中保存ss和esp以前的值,这些值定义了与旧特权级相关的栈的逻辑地址。

(6) 如果发生的是一个fault(page fault) ,用引起异常的指令地址装载cs和eip寄存器,从而使得这条指令能再次被执行。

(7) 在栈中保存eflags,cs以及eip的内容

(8) 如果异常产生一个硬件出错码,则将它保存在栈中

(9) 装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。这些值给出了中断或者异常处理程序的第一条指令。这是控制单元所执行的最后一步。

当中断或异常被处理完成之后,相应的处理程序必须产生一条iret指令,把控制权转交给被中断的进程,此时,控制单元执行:

(1) 用保存在栈中的值装载cs,eip,eflags寄存器。如果一个硬件出错码曾被压入栈中,并且在eip内容上面,那么执行iret指令前必须将其弹出,这一点貌似是程序员自己实现的。
(2) 检查处理程序的cpl是否等于cs中最低两位值,(即发生中断时是否导致特权级变化),如果在同一特权级,则iret结束;否则,下一步。
(3) 从栈中装载ss和esp寄存器,返回到与旧特权级相关的栈。
(4) 检查ds,es,fs及gs段寄存器的内容,如果其中一个寄存器包含的选择符是个段描述符,且其DPL值小于CPL,那么将相应段寄存器清空。控制单元这么做是防止用户态程序(CPL=3)利用内核以前所用的段寄存器(DPL=0),这样,使得用户态程序能够利用他们来访问内核地址空间。

2.3.2 中断处理程序

上一节中提到,发生中断后,控制单元将通过IDT将程序流指向中断处理程序的第一条指令,从前面中断初始化的分析中知道,程序流将转向
<irq_entries_start>
.....
#数据段中的interrupt[i]指向这里
pushl $(~(0x20+i)+0x80)
jmp common_interrupt
nop
.....

即首先将中断向量+0x80压入堆栈,然后跳转到common_interrupt处开始执行:

 827 "arch/x86/kernel/entry_32.S"
828 /*
829 * the CPU automatically disables interrupts when executing an IRQ vector,
830 * so IRQ-flags tracing has to follow that:
831 */
832 .p2align CONFIG_X86_L1_CACHE_SHIFT
833 common_interrupt:
834 addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range */
835 SAVE_ALL
836 TRACE_IRQS_OFF //可能和调试有关吧,忽略
837 movl %esp,%eax
838 call do_IRQ
839 jmp ret_from_intr
840 ENDPROC(common_interrupt)
841 CFI_ENDPROC

宏SAVE_ALL的作用就是保护现场,如下:
187 "arch/x86/kernel/entry_32.S"
188 .macro SAVE_ALL
189 cld
190 PUSH_GS
191 pushl_cfi %fs
192 /*CFI_REL_OFFSET fs, 0;*/
193 pushl_cfi %es
194 /*CFI_REL_OFFSET es, 0;*/
195 pushl_cfi %ds
196 /*CFI_REL_OFFSET ds, 0;*/
197 pushl_cfi %eax
198 CFI_REL_OFFSET eax, 0
199 pushl_cfi %ebp
200 CFI_REL_OFFSET ebp, 0
201 pushl_cfi %edi
202 CFI_REL_OFFSET edi, 0
203 pushl_cfi %esi
204 CFI_REL_OFFSET esi, 0
205 pushl_cfi %edx
206 CFI_REL_OFFSET edx, 0
207 pushl_cfi %ecx
208 CFI_REL_OFFSET ecx, 0
209 pushl_cfi %ebx
210 CFI_REL_OFFSET ebx, 0
211 movl $(__USER_DS), %edx
212 movl %edx, %ds
213 movl %edx, %es
214 movl $(__KERNEL_PERCPU), %edx
215 movl %edx, %fs
216 SET_KERNEL_GS %edx
217 .endm

line 189 cld指令用来清eflags的方向标志DF,以确保调用字符串指令时会自动增加edi和esi寄存器的值;注意,不要看错成cli,此时已经处于关中断状态,是硬件自动执行的,不需要cli。

接着调用do_IRQ()函数,下面详细分析之。

2.3.2.1 do_IRQ()

171 "arch/x86/kernel/irq.c"
172 /*
173 * do_IRQ handles all normal device IRQ's (the special
174 * SMP cross-CPU interrupts have their own specific
175 * handlers).
176 */
177 unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
178 {
179 struct pt_regs *old_regs = set_irq_regs(regs);
180
181 /* high bit used in ret_from_ code */
182 unsigned vector = ~regs->orig_ax;
183 unsigned irq;
184
185 irq_enter();
186 exit_idle();
187
188 irq = __this_cpu_read(vector_irq[vector]);
189
190 if (!handle_irq(irq, regs)) {
191 ack_APIC_irq();
192
193 if (printk_ratelimit())
194 pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
195 __func__, smp_processor_id(), vector, irq);
196 }
197
198 irq_exit();
199
200 set_irq_regs(old_regs);
201 return 1;
202 }

line 177 前面common_interrupt函数中有movl %esp,%eax,个人认为do_IRQ是通过寄存器eax传递参数的,struct pt_regs定义如下:
 
 
20 "arch/x86/include/asm/ptrace.h"
21 struct pt_regs {
22 long ebx;
23 long ecx;
24 long edx;
25 long esi;
26 long edi;
27 long ebp;
28 long eax;
29 int xds;
30 int xes;
31 int xfs;
32 int xgs;
33 long orig_eax;
34 long eip;
35 int xcs;
36 long eflags;
37 long esp;
38 int xss;
39 };        

通过对比前面SAVE_ALL宏以及硬件自动压栈,发现其分量与栈中一致,但是就是没有找到通过寄存器传递参数的证据,不管了吧*****

line 179 应该是将中断现场保存到old_regs中吧,应该是这样的,old_regs为指向per_cpu变量irq_regs的指针,随后将regs值写入该per_cpu变量,不管太多细节。
首先调用set_irq_regs将一个per-cpu型的指针变量irq_regs保存到old_regs, 然后将irq_regs赋值为regs, 这样在中断处理过程中, 系统中的每一个cpu都可以通过irq_regs来访问系统保存的中断现场. 函数结束时, 调用set_irq_regs(old_regs)来恢复irq_regs. irq_regs一般用来调试或者诊断时打印当前栈信息, 也可以通过这些保存的中断现场寄存器判断出被中断的进程当时运行在用户态还是内核态.

line 182 对照上面pt_regs变量以及前面压栈顺序,分量orig_eax处便是中断向量的位置,取反后得到vector。

line 185 接下来irq_enter会更新系统中的统计量, 同时把当前栈中的preempt_count变量加上HARDIRQ_OFFSET来标识一个HARDIRQ中断上下文:preempt_count() += HARDIRQ_OFFSET, HARDIRQ是linux下对中断处理上半部分的称谓,与之对应的是中断处理的下半部分SOFTIRQ, 此处irq_enter告诉系统现在进入了中断处理的上半部分. 与irq_enter配对的是irq_exit, 在中断处理完成退出时调用, 除了更新一些系统统计量和清除中断上下文标识外,它还有一个重要的功能是处理软中断, 也就是中断处理的下半部分.

line 188 通过中断向量获取其在irq_desc数组(或者radix_tree)中的下标,vector_irq记录了中断向量到中断请求描述符之间的映射。

line 190 handle_irq函数根据中断号,查找相应的desc结构,调用其handle_irq,见2.3.2.2节

line 198 irq_exit() 同irq_entry配对,见2.3.2.3节

2.3.2.2 handle_irq()

182 "arch/x86/kernel/irq_32.c"
183 bool handle_irq(unsigned irq, struct pt_regs *regs)
184 {
185 struct irq_desc *desc;
186 int overflow;
187
188 overflow = check_stack_overflow();
189
190 desc = irq_to_desc(irq);
191 if (unlikely(!desc))
192 return false;
193
194 if (user_mode_vm(regs) || !execute_on_irq_stack(overflow, desc, irq)) {
195 if (unlikely(overflow))
196 print_stack_overflow();
197 desc->handle_irq(irq, desc);
198 }
199
200 return true;
201 }

line 188 首先,确定内核栈是否还有1KB空间,如果可用空间不足 1KB,可能会引发栈溢出,输出内核错误信息。check_stack_overflow().
line 190 随后,获取该中断的中断请求描述符desc
line 194 user_mode_vm determines whether a register set came from user mode.后面execute_on_irq_stack()将当前内核栈切换到中断栈。当然,内核栈到中断栈的切换仅仅是内核的实现方法,对于中断处理的原理没有多大影响,暂且不考虑吧。
上面的判断条件表明,从用户态发生的中断直接使用当前进程内核栈中执行,
!execute_on_irq_stack表明当前已经在中断栈上,这表现为中断的嵌套执行。
关于中断栈,其初始化发生在native_init_IRQ() (2.2.5.1)中的irq_ctx_init()函数,今后可能会对中断栈做一个相关总结。******
line 197 desc->handle_irq,依据中断初始化时设置的handle_irq,(如,init_ISA_irq中设置的hanle_level_irq),进行处理,这里,对不同类型的中断,其相应的处理函数不同,但最终都是通过调用action链表中的处理函数进行的。这里不再分析。
没有分析的主要原因是没有找到显示的开中断的操作,即sti。这样不知道什么时候才会允许中断嵌套。以后再分析吧,中断处理流程差不多就分析到这了。******

2.3.2.3 irq_exit()

328 "kernel/softirq.c"
329 /*
330 * Exit an interrupt context. Process softirqs if needed and possible:
331 */
332 void irq_exit(void)
333 {
334 account_system_vtime(current);
335 trace_hardirq_exit();
336 sub_preempt_count(IRQ_EXIT_OFFSET);
337 if (!in_interrupt() && local_softirq_pending())
338 invoke_softirq();
339
340 #ifdef CONFIG_NO_HZ
341 /* Make sure that timer wheel updates are propagated */
342 if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
343 tick_nohz_irq_exit();
344 #endif
345 rcu_irq_exit();
346 sched_preempt_enable_no_resched();
347 }

该函数最重要的功能就是开启中断下半部分的执行,也就是软中断。关于软中断,我目前还没有分析,对自己目前的工作意义不大。等以后分析时再总结吧。*****
还有,从中断上下文中

2.3.3 中断返回

从do_IRQ返回到common_interrupt后,内核跳转到ret_from_intr函数,注意,使用的是jmp指令。该函数执行一系列处理(没有看****),最终会恢复保存在栈中的寄存器,通过iret返回。


2.4 中断请求服务 request_irq()函数

对于外部中断来说,设备驱动可以调用request_irq()把一个中断处理程序挂接到中断请求队列中来。

130 "include/linux/interrupt.h"
131 static inline int __must_check
132 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
133 const char *name, void *dev)
134 {
135 return request_threaded_irq(irq, handler, NULL, flags, name, dev);
136 }

1305 "kernel/irq/manage.c"
1306 /**
1307 * request_threaded_irq - allocate an interrupt line
1308 * @irq: Interrupt line to allocate
1309 * @handler: Function to be called when the IRQ occurs.
1310 * Primary handler for threaded interrupts
1311 * If NULL and thread_fn != NULL the default
1312 * primary handler is installed
1313 * @thread_fn: Function called from the irq handler thread
1314 * If NULL, no irq thread is created
1315 * @irqflags: Interrupt type flags
1316 * @devname: An ascii name for the claiming device
1317 * @dev_id: A cookie passed back to the handler function
1318 *
1319 * This call allocates interrupt resources and enables the
1320 * interrupt line and IRQ handling. From the point this
1321 * call is made your handler function may be invoked. Since
1322 * your handler function must clear any interrupt the board
1323 * raises, you must take care both to initialise your hardware
1324 * and to set up the interrupt handler in the right order.
1325 *
1326 * If you want to set up a threaded irq handler for your device
1327 * then you need to supply @handler and @thread_fn. @handler is
1328 * still called in hard interrupt context and has to check
1329 * whether the interrupt originates from the device. If yes it
1330 * needs to disable the interrupt on the device and return
1331 * IRQ_WAKE_THREAD which will wake up the handler thread and run
1332 * @thread_fn. This split handler design is necessary to support
1333 * shared interrupts.
1334 *
1335 * Dev_id must be globally unique. Normally the address of the
1336 * device data structure is used as the cookie. Since the handler
1337 * receives this value it makes sense to use it.
1338 *
1339 * If your interrupt is shared you must pass a non NULL dev_id
1340 * as this is required when freeing the interrupt.
1341 *
1342 * Flags:
1343 *
1344 * IRQF_SHARED Interrupt is shared
1345 * IRQF_TRIGGER_* Specify active edge(s) or level
1346 *
1347 */
1348 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
1349 irq_handler_t thread_fn, unsigned long irqflags,
1350 const char *devname, void *dev_id)
1351 {
1352 struct irqaction *action;
1353 struct irq_desc *desc;
1354 int retval;
1355
1356 /*
1357 * Sanity-check: shared interrupts must pass in a real dev-ID,
1358 * otherwise we'll have trouble later trying to figure out
1359 * which interrupt is which (messes up the interrupt freeing
1360 * logic etc).
1361 */
1362 if ((irqflags & IRQF_SHARED) && !dev_id)
1363 return -EINVAL;
1364
1365 desc = irq_to_desc(irq);
1366 if (!desc)
1367 return -EINVAL;
1368
1369 if (!irq_settings_can_request(desc) ||
1370 WARN_ON(irq_settings_is_per_cpu_devid(desc)))
1371 return -EINVAL;
1372
1373 if (!handler) {
1374 if (!thread_fn)
1375 return -EINVAL;
1376 handler = irq_default_primary_handler;
1377 }
1378
1379 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
1380 if (!action)
1381 return -ENOMEM;
1382
1383 action->handler = handler;
1384 action->thread_fn = thread_fn;
1385 action->flags = irqflags;
1386 action->name = devname;
1387 action->dev_id = dev_id;
1388
1389 chip_bus_lock(desc);
1390 retval = __setup_irq(irq, desc, action);
1391 chip_bus_sync_unlock(desc);
1392
1393 if (retval)
1394 kfree(action);
1395
1396 #ifdef CONFIG_DEBUG_SHIRQ_FIXME
1397 if (!retval && (irqflags & IRQF_SHARED)) {
1398 /*
1399 * It's a shared IRQ -- the driver ought to be prepared for it
1400 * to happen immediately, so let's make sure....
1401 * We disable the irq to make sure that a 'real' IRQ doesn't
1402 * run in parallel with our fake.
1403 */
1404 unsigned long flags;
1405
1406 disable_irq(irq);
1407 local_irq_save(flags);
1408
1409 handler(irq, dev_id);
1410
1411 local_irq_restore(flags);
1412 enable_irq(irq);
1413 }
1414 #endif
1415 return retval;
1416 }
1417 EXPORT_SYMBOL(request_threaded_irq);

函数request_threaded_irq( )首先对传入的参数进行正确性检查,根据传入的irq号获得数组irq_desc中以irq为下标的元素,然后动态的创建一个irqaction描述符,根据传入的参数初始化新生成的irqaction描述符,最后调用函数__setup_irq( )把该描述符加入到IRQ链表中,完成中断的动态申请及注册。

如果返回值是0则说明申请成功,如果申请不成功,则返回的值非零,一般为负数,可能的取值-16、-38。例如,如果返回值是-16,则说明申请的中断号在内核中已被占用。

line 1390 调用__setup_irq()函数将产生的irqaction结构挂接到对应的irq_desc链表中。前面提到的setup_irq()函数实际上最终也是调用__setup_irq()进行处理。同时注意到request_irq()中irqaction结构是通过kzalloc动态产生的,而setup_irq()中通过参数传递的irqaction是静态定义的,如前面提到的setup_irq(2, &irq2)。通过kzalloc分配产生的结构会利用到内核的内存管理相关功能,而静态定义则在编译链接时已经确定,这应该是两个函数的最大不同之处吧。

关于__setup_irq()里面涉及的情况比较多,不同的设置也同Linux对中断的处理相关,暂且不分析了。*****