Linux中断处理浅析

转载请注明出处:http://blog.youkuaiyun.com/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对中断的处理相关,暂且不分析了。*****







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值