5.1. 中断初始化与注册

5.1.1. 中断与异常

中断可分为同步(synchronous)中断和异步(asynchronous)中断: (1)同步中断又称为异常,是当指令执行时由CPU控制单元产生,之所以称为同步,是因为只有在一条指令执行完成后CPU才会发出中断。典型的异常有缺页和除0。 (2)异步中断是指由其他硬件设备依照CPU时钟信号随机产生。

5.1.1.1 ARM异常与向量表

当一个异常或中断发生时,处理器会把pc设置为一个特定的存储器地址。这个地址放在一个被称为向量表(vector table)的特定的地址范围内。向量表的入口是一些跳转指令,跳转到专门处理某个异常或中断的子程序。 存储器映射地址0x00000000是为向量表保留的。在有些处理器中,向量表可以选择定位在存储空间的更高地址(从偏移量0xffff0000开始)。操作系统,如Linux和Microsoft的嵌入式操作系统,就可以利用这一特性。

当一个异常或中断发生时,处理器挂起正常的执行转而从向量表(见表1-1)装载指令。每一个向量表入口包含一条指向一个特定子程序的跳转指令。

  • 复位向量是处理器上电后执行的第一条指令的位置。这条指令使处理器转到初始化代码处。
  • 未定义指令向量是在处理器不能对一条指令译码时使用的。
  • 软件中断向量是执行SWI指令时被调用的。SWI指令经常被用作调用一个操作系统例程的机制。
  • 预取指中止向量是发生在处理器试图从一个未获得正确访问权限的地址去取指时,实际上中止发生在“译码”阶段。
  • 数据中止向量是与预取指中止类似,发生在一条指令试图访问未获得正确访问权限的数据存储器时。
  • 中断请求向量是用于外部硬件中断处理器的正常执行流。只有当cpsr中的IRQ位未被屏蔽时才能发生。
  • 快速中断请求向量类似于中断请求,是为要求更短的中断响应时间的硬件保留的。只有当cpsr中的FIQ位未被屏蔽时才能发生。

    (注意:本章代码为linux-v4.4)

    5.1.2. 相关数据结构

    与中断相关的主要数据结构有三个,分别是:irq_desc、irq_chip和irqaction。下面将介绍一下这三个数据结构的实现以及它们之间的关系。

内核通过irq_desc来描述一个中断,irq_desc结构体在include/linux/irqdesc.h中定义:

 46 struct irq_desc {
 47         struct irq_common_data  irq_common_data;
 48         struct irq_data         irq_data;
 49         unsigned int __percpu   *kstat_irqs;
 50         irq_flow_handler_t      handle_irq;
 51 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
 52         irq_preflow_handler_t   preflow_handler;
 53 #endif
 54         struct irqaction        *action;        /* IRQ action list */
 55         unsigned int            status_use_accessors;
 56         unsigned int            core_internal_state__do_not_mess_with_it;
 57         unsigned int            depth;          /* nested irq disables */
 58         unsigned int            wake_depth;     /* nested wake enables */
 59         unsigned int            irq_count;      /* For detecting broken IRQs */
 60         unsigned long           last_unhandled; /* Aging timer for unhandled count */
 61         unsigned int            irqs_unhandled;
 62         atomic_t                threads_handled;
 63         int                     threads_handled_last;
 64         raw_spinlock_t          lock;
 65         struct cpumask          *percpu_enabled;
 66 #ifdef CONFIG_SMP
 67         const struct cpumask    *affinity_hint;
 68         struct irq_affinity_notify *affinity_notify;
 69 #ifdef CONFIG_GENERIC_PENDING_IRQ
 70         cpumask_var_t           pending_mask;
 71 #endif
 72 #endif
 73         unsigned long           threads_oneshot;
 74         atomic_t                threads_active;
 75         wait_queue_head_t       wait_for_threads;
 76 #ifdef CONFIG_PM_SLEEP
 77         unsigned int            nr_actions;
 78         unsigned int            no_suspend_depth;
 79         unsigned int            cond_suspend_depth;
 80         unsigned int            force_resume_depth;
 81 #endif
 82 #ifdef CONFIG_PROC_FS
 83         struct proc_dir_entry   *dir;
 84 #endif
 85         int                     parent_irq;
 86         struct module           *owner;
 87         const char              *name;
 88 } ____cacheline_internodealigned_in_smp;

其中,handle_irq指向中断处理函数,每当发生中断时,体系结构相关的代码都会调用handle_irq,该函数负责使用chip中提供的处理器相关的方法,来完成处理中断所必需的一些底层操作。用于不同中断类型的默认函数由内核提供,例如handle_level_irq和handle_edge_irq等。

中断控制器相关操作被封装到irq_chip,该结构体在include/linux/irq.h中定义:

346 struct irq_chip {
347         const char      *name;
348         unsigned int    (*irq_startup)(struct irq_data *data);
349         void            (*irq_shutdown)(struct irq_data *data);
350         void            (*irq_enable)(struct irq_data *data);
351         void            (*irq_disable)(struct irq_data *data);
352 
353         void            (*irq_ack)(struct irq_data *data);
354         void            (*irq_mask)(struct irq_data *data);
355         void            (*irq_mask_ack)(struct irq_data *data);
356         void            (*irq_unmask)(struct irq_data *data);
357         void            (*irq_eoi)(struct irq_data *data);
358 
359         int             (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
360         int             (*irq_retrigger)(struct irq_data *data);
361         int             (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
362         int             (*irq_set_wake)(struct irq_data *data, unsigned int on);
363 
364         void            (*irq_bus_lock)(struct irq_data *data);
365         void            (*irq_bus_sync_unlock)(struct irq_data *data);
366 
367         void            (*irq_cpu_online)(struct irq_data *data);
368         void            (*irq_cpu_offline)(struct irq_data *data);
369 
370         void            (*irq_suspend)(struct irq_data *data);
371         void            (*irq_resume)(struct irq_data *data);
372         void            (*irq_pm_shutdown)(struct irq_data *data);
373 
374         void            (*irq_calc_mask)(struct irq_data *data);
375 
376         void            (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
377         int             (*irq_request_resources)(struct irq_data *data);
378         void            (*irq_release_resources)(struct irq_data *data);
379 
380         void            (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
381         void            (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);
382 
383         int             (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
384         int             (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);
385 
386         int             (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);
387 
388         unsigned long   flags;
389 };

action指向一个操作链表,在中断发生时执行。由中断通知设备驱动程序,可以将与之相关的处理函数放置在此处。irqaction结构体在include/linux/interrupt.h中定义:

110 struct irqaction {
111         irq_handler_t           handler;
112         void                    *dev_id;
113         void __percpu           *percpu_dev_id;
114         struct irqaction        *next;
115         irq_handler_t           thread_fn;
116         struct task_struct      *thread;
117         struct irqaction        *secondary;
118         unsigned int            irq;
119         unsigned int            flags;
120         unsigned long           thread_flags;
121         unsigned long           thread_mask;
122         const char              *name;
123         struct proc_dir_entry   *dir;
124 } ____cacheline_internodealigned_in_smp;

每个处理函数都对应该结构体的一个实例irqaction,该结构体中最重要的成员是handler,这是个函数指针。该函数指针就是通过request_irq来初始化的,在后续小节中将详细介绍。当系统产生中断,内核将调用该函数指针所指向的处理函数。name和dev_id唯一地标识一个irqaction实例,name用于标识设备名,dev_id用于指向设备的数据结构实例。

请注意irq_desc中的handle_irq不同于irqaction中的handler,这两个函数指针分别定义为:

typedef void (*irq_flow_handler_t)(struct irq_desc *desc);
typedef irqreturn_t (*irq_handler_t)(int, void *);

内核通过维护一个irq_desc的全局数组来管理所有的中断。该数组在kernel/irq/irqdesc.c中定义:

259 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
260         [0 ... NR_IRQS-1] = {
261                 .handle_irq     = handle_bad_irq,
262                 .depth          = 1,
263                 .lock           = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
264         }
265 };

其中NR_IRQS表示支持中断的最大数,在arch/alpha/include/asm/irq.h文件中定义。

IRQ相关数据结构关系如图:

5.1.3. 中断初始化

ARM Linux中断初始化主要通过三个函数完成:early_trap_init 、early_irq_init和init_IRQ。

初始化以处理异常——early_trap_init函数定义在arch/arm/kernel/traps.c文件中,在setup_arch函数最后调用的,主要完成ARM异常向量表和异常处理程序的重定位。

791 void __init early_trap_init(void *vectors_base)
792 {
793 #ifndef CONFIG_CPU_V7M
794         unsigned long vectors = (unsigned long)vectors_base;
795         extern char __stubs_start[], __stubs_end[];
796         extern char __vectors_start[], __vectors_end[];
797         unsigned i;
798 
799         vectors_page = vectors_base;
800 
801         /*
802          * Poison the vectors page with an undefined instruction.  This
803          * instruction is chosen to be undefined for both ARM and Thumb
804          * ISAs.  The Thumb version is an undefined instruction with a
805          * branch back to the undefined instruction.
806          */
807         for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
808                 ((u32 *)vectors_base)[i] = 0xe7fddef1;
809 
810         /*
811          * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
812          * into the vector page, mapped at 0xffff0000, and ensure these
813          * are visible to the instruction stream.
814          */
815         memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
816         memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
817 
818         kuser_init(vectors_base);
819 
820         flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
821 #else /* ifndef CONFIG_CPU_V7M */
822         /*
823          * on V7-M there is no need to copy the vector table to a dedicated
824          * memory area. The address is configurable and so a table in the kernel
825          * image can be used.
826          */
827 #endif
828 } 

为了调用异常代码,在early_trap_init()函数中将处理器及辅助器代码复制到异常向量表基址,并设置CPU域。

early_irq_init函数定义在kernel/irq/irqdesc.c文件中:

259 int __init early_irq_init(void)
260 {
261         int i, initcnt, node = first_online_node;
262         struct irq_desc *desc;
263 
264         init_irq_default_affinity();
265 
266         /* Let arch update nr_irqs and return the nr of preallocated irqs */
267         initcnt = arch_probe_nr_irqs();
268         printk(KERN_INFO "NR_IRQS:%d nr_irqs:%d %d\n", NR_IRQS, nr_irqs, initcnt);
269 
270         if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))
271                 nr_irqs = IRQ_BITMAP_BITS;
272 
273         if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
274                 initcnt = IRQ_BITMAP_BITS;
275 
276         if (initcnt > nr_irqs)
277                 nr_irqs = initcnt;
278 
279         for (i = 0; i < initcnt; i++) {
280                 desc = alloc_desc(i, node, NULL);
281                 set_bit(i, allocated_irqs);
282                 irq_insert_desc(i, desc);
283         }
284         return arch_early_irq_init();
285 }

init_IRQ函数定义在arch/arm/kernel/irq.c文件中:

 83 void __init init_IRQ(void)
 84 {
 85         int ret;
 86 
 87         if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
 88                 irqchip_init();
 89         else
 90                 machine_desc->init_irq();
 91 
 92         if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
 93             (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
 94                 if (!outer_cache.write_sec)
 95                         outer_cache.write_sec = machine_desc->l2c_write_sec;
 96                 ret = l2x0_of_init(machine_desc->l2c_aux_val,
 97                                    machine_desc->l2c_aux_mask);
 98                 if (ret)
 99                         pr_err("L2C: failed to init: %d\n", ret);
100         }
101 
102         uniphier_cache_init();
103 }

5.1.4 注册中断处理程序

本节将主要讲述如何注册中断处理程序。Linux中用于注册中断处理程序的函数是request_irq,该函数定义在include/linux/interrupt.h:

133 static inline int __must_check
134 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
135             const char *name, void *dev)
136 {
137         return request_threaded_irq(irq, handler, NULL, flags, name, dev);
138 }

    request_thread_irq()函数定义在kernel/irq/manage.c

1604 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
1605                          irq_handler_t thread_fn, unsigned long irqflags,
1606                          const char *devname, void *dev_id)
1607 {
1608         struct irqaction *action;
1609         struct irq_desc *desc;
1610         int retval;
1611 
1612         /*
1613          * Sanity-check: shared interrupts must pass in a real dev-ID,
1614          * otherwise we'll have trouble later trying to figure out
1615          * which interrupt is which (messes up the interrupt freeing
1616          * logic etc).
1617          *
1618          * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
1619          * it cannot be set along with IRQF_NO_SUSPEND.
1620          */
1621         if (((irqflags & IRQF_SHARED) && !dev_id) ||
1622             (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
1623             ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
1624                 return -EINVAL;
1625 
1626         desc = irq_to_desc(irq);
1627         if (!desc)
1628                 return -EINVAL;
1629 
1630         if (!irq_settings_can_request(desc) ||
1631             WARN_ON(irq_settings_is_per_cpu_devid(desc)))
1632                 return -EINVAL;
1633 
1634         if (!handler) {
1635                 if (!thread_fn)
1636                         return -EINVAL;
1637                 handler = irq_default_primary_handler;
1638         }
1639 
1640         action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
1641         if (!action)
1642                 return -ENOMEM;
1643 
1644         action->handler = handler;
1645         action->thread_fn = thread_fn;
1646         action->flags = irqflags;
1647         action->name = devname;
1648         action->dev_id = dev_id;
1649 
1650         chip_bus_lock(desc);
1651         retval = __setup_irq(irq, desc, action);
1652         chip_bus_sync_unlock(desc);
1653 
1654         if (retval) {
1655                 kfree(action->secondary);
1656                 kfree(action);
1657         }
1658 
1659 #ifdef CONFIG_DEBUG_SHIRQ_FIXME
1660         if (!retval && (irqflags & IRQF_SHARED)) {
1661                 /*
1662                  * It's a shared IRQ -- the driver ought to be prepared for it
1663                  * to happen immediately, so let's make sure....
1664                  * We disable the irq to make sure that a 'real' IRQ doesn't
1665                  * run in parallel with our fake.
1666                  */
1667                 unsigned long flags;
1668 
1669                 disable_irq(irq);
1670                 local_irq_save(flags);
1671 
1672                 handler(irq, dev_id);
1673 
1674                 local_irq_restore(flags);
1675                 enable_irq(irq);
1676         }
1677 #endif
1678         return retval;
1679 }

5.2 中断处理流程

当一个异常或中断发生时,处理器会将PC设置为特定地址,从而跳转到已经初始化好的异常向量表。因此,要理清中断处理流程,先从异常向量表开始。对于ARM Linux而言,异常向量表和异常处理程序都存在arch/arm/kernel/entry_armv.S汇编文件中。

异常向量表的代码实现:

1209 __vectors_start:
1210         W(b)    vector_rst
1211         W(b)    vector_und
1212         W(ldr)  pc, __vectors_start + 0x1000
1213         W(b)    vector_pabt
1214         W(b)    vector_dabt
1215         W(b)    vector_addrexcptn
1216         W(b)    vector_irq
1217         W(b)    vector_fiq

vector_stub是一个宏(在arch/arm/kernel/entry_armv.S中定义),为了分析更直观,我们将vector_stub宏展开如下:

1077 /*
1078  * Interrupt dispatcher
1079  */
1080         vector_stub     irq, IRQ_MODE, 4
1081 
1082         .long   __irq_usr                       @  0  (USR_26 / USR_32)
1083         .long   __irq_invalid                   @  1  (FIQ_26 / FIQ_32)
1084         .long   __irq_invalid                   @  2  (IRQ_26 / IRQ_32)
1085         .long   __irq_svc                       @  3  (SVC_26 / SVC_32)
1086         .long   __irq_invalid                   @  4
1087         .long   __irq_invalid                   @  5
1088         .long   __irq_invalid                   @  6
1089         .long   __irq_invalid                   @  7
1090         .long   __irq_invalid                   @  8
1091         .long   __irq_invalid                   @  9
1092         .long   __irq_invalid                   @  a
1093         .long   __irq_invalid                   @  b
1094         .long   __irq_invalid                   @  c
1095         .long   __irq_invalid                   @  d
1096         .long   __irq_invalid                   @  e
1097         .long   __irq_invalid                   @  f

如果发生中断前处于用户态则进入__irq_usr,其定义如下(arch/arm/kernel/entry_armv.S):

454         .align  5
455 __irq_usr:
456         usr_entry //保存中断上下文
457         kuser_cmpxchg_check
458         irq_handler
459         get_thread_info tsk //获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk
460         mov     why, #0
461         b       ret_to_user_from_irq //中断处理完成,恢复中断上下文并返回中断产生的位置
462  UNWIND(.fnend          )
463 ENDPROC(__irq_usr)
464 
465         .ltorg

上面代码中的usr_entry是一个宏定义,主要用于保护上下文到栈中:

376         .macro  usr_entry, trace=1, uaccess=1
377  UNWIND(.fnstart        )
378  UNWIND(.cantunwind     )       @ do not unwind the user space
379         sub     sp, sp, #S_FRAME_SIZE  //堆栈被定义为递减式满堆栈,所以首先让sp向下移动#S_FRAME_SIZE,
准备向栈中存放数据。
380  ARM(   stmib   sp, {r1 - r12}  )
381  THUMB( stmia   sp, {r0 - r12}  )
382 
383  ATRAP( mrc     p15, 0, r7, c1, c0, 0)
384  ATRAP( ldr     r8, .LCcralign)
385 
386         ldmia   r0, {r3 - r5}
387         add     r0, sp, #S_PC           @ here for interlock avoidance
388         mov     r6, #-1                 @  ""  ""     ""        ""
389 
390         str     r3, [sp]                @ save the "real" r0 copied
391                                         @ from the exception stack
392 
393  ATRAP( ldr     r8, [r8, #0])
394 
395         @
396         @ We are now ready to fill in the remaining blanks on the stack:
397         @
398         @  r4 - lr_<exception>, already fixed up for correct return/restart
399         @  r5 - spsr_<exception>
400         @  r6 - orig_r0 (see pt_regs definition in ptrace.h)
401         @
402         @ Also, separately save sp_usr and lr_usr
403         @
404         stmia   r0, {r4 - r6}
405  ARM(   stmdb   r0, {sp, lr}^                   )
406  THUMB( store_user_sp_lr r0, r1, S_SP - S_PC    )
407 
408         .if \uaccess
409         uaccess_disable ip
410         .endif
411 
412         @ Enable the alignment trap while in kernel mode
413  ATRAP( teq     r8, r7)
414  ATRAP( mcrne   p15, 0, r8, c1, c0, 0)
415 
416         @
417         @ Clear FP to mark the first stack frame
418         @
419         zero_fp
420 
421         .if     \trace
422 #ifdef CONFIG_TRACE_IRQFLAGS
423         bl      trace_hardirqs_off
424 #endif
425         ct_user_exit save = 0
426         .endif
427         .endm

上面的这段代码主要是填充结构体pt_regs ,在arch/arm/include/asm/ptrace.h中定义:

 16 struct pt_regs {
 17         unsigned long uregs[18];
 18 };

保存中断上下文后则进入中断处理程序—irq_handler,定义在arch/arm/kernel/entry_armv.S文件中:

 38 /*
 39  * Interrupt handling.
 40  */
 41         .macro  irq_handler
 42 #ifdef CONFIG_MULTI_IRQ_HANDLER
 43         ldr     r1, =handle_arch_irq
 44         mov     r0, sp
 45         badr    lr, 9997f
 46         ldr     pc, [r1]
 47 #else
 48         arch_irq_handler_default
 49 #endif
 50 9997:
 51         .endm

回看__irq_usr在完成了irq_handler中断处理函数后,要完成从中断异常处理程序返回到中断点的工作。如果中断产生于用户空间,则调用ret_to_user_from_irq来恢复中断现场并返回用户空间继续运行:

 Linux/arch/arm/kernel/entry-common.S
 95 ENTRY(ret_to_user_from_irq)
 96         ldr     r1, [tsk, #TI_FLAGS] //获取thread_info->flags
 97         tst     r1, #_TIF_WORK_MASK //判断是否有待处理的work
 98         bne     slow_work_pending //如果有,则进入work_pending进一步处理,主要是完成用户进程抢占相关处理。
 99 no_work_pending: //如果没有work待处理,则准备恢复中断现场,返回用户空间。
100         asm_trace_hardirqs_on save = 0
101 
102         /* perform architecture specific actions before user return */
103         arch_ret_to_user r1, lr //调用体系结构相关的代码
104         ct_user_enter save = 0
105 
106         restore_user_regs fast = 0, offset = 0 //用restore_user_regs
107 ENDPROC(ret_to_user_from_irq)

如果中断产生于内核空间,则调用svc_exit来恢复中断现场:

arch/arm/kernel/entry-header.S
200         .macro  svc_exit, rpsr, irq = 0
201         .if     \irq != 0
202         @ IRQs already off
203 #ifdef CONFIG_TRACE_IRQFLAGS
204         @ The parent context IRQs must have been enabled to get here in
205         @ the first place, so there is no point checking the PSR I bit.
206         bl      trace_hardirqs_on
207 #endif
208         .else
209         @ IRQs off again before pulling preserved data off the stack
210         disable_irq_notrace
211 #ifdef CONFIG_TRACE_IRQFLAGS
212         tst     \rpsr, #PSR_I_BIT
213         bleq    trace_hardirqs_on
214         tst     \rpsr, #PSR_I_BIT
215         blne    trace_hardirqs_off
216 #endif
217         .endif
218         uaccess_restore
219 
220 #ifndef CONFIG_THUMB2_KERNEL
221         @ ARM mode SVC restore
222         msr     spsr_cxsf, \rpsr
223 #if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_32v6K)
224         @ We must avoid clrex due to Cortex-A15 erratum #830321
225         sub     r0, sp, #4                      @ uninhabited address
226         strex   r1, r2, [r0]                    @ clear the exclusive monitor
227 #endif
228         ldmia   sp, {r0 - pc}^                  @ load r0 - pc, cpsr
229 #else
230         @ Thumb mode SVC restore
231         ldr     lr, [sp, #S_SP]                 @ top of the stack
232         ldrd    r0, r1, [sp, #S_LR]             @ calling lr and pc
233 
234         @ We must avoid clrex due to Cortex-A15 erratum #830321
235         strex   r2, r1, [sp, #S_LR]             @ clear the exclusive monitor
236 
237         stmdb   lr!, {r0, r1, \rpsr}            @ calling lr and rfe context
238         ldmia   sp, {r0 - r12}
239         mov     sp, lr
240         ldr     lr, [sp], #4
241         rfeia   sp!
242 #endif
243         .endm

在arch/arm/kernel/irq.c文件中找到asm_do_IRQ函数定义:

 74 /*
 75  * asm_do_IRQ is the interface to be used from assembly code.
 76  */
 77 asmlinkage void __exception_irq_entry
 78 asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
 79 {
 80         handle_IRQ(irq, regs);
 81 }


    arch/arm/kernel/irq.c
 63 /*
 64  * handle_IRQ handles all hardware IRQ's.  Decoded IRQs should
 65  * not come via this function.  Instead, they should provide their
 66  * own 'handler'.  Used by platform code implementing C-based 1st
 67  * level decoding.
 68  */
 69 void handle_IRQ(unsigned int irq, struct pt_regs *regs)
 70 {
 71         __handle_domain_irq(NULL, irq, false, regs);
 72 }

    kernel/irq/irqdesc.c
356 /**
357  * __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
358  * @domain:     The domain where to perform the lookup
359  * @hwirq:      The HW irq number to convert to a logical one
360  * @lookup:     Whether to perform the domain lookup or not
361  * @regs:       Register file coming from the low-level handling code
362  *
363  * Returns:     0 on success, or -EINVAL if conversion has failed
364  */
365 int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
366                         bool lookup, struct pt_regs *regs)
367 {
368         struct pt_regs *old_regs = set_irq_regs(regs); // 保存新的寄存器集合指针到全局cpu变量,
                                                           //方便后续处理程序访问寄存器集合。
369         unsigned int irq = hwirq;
370         int ret = 0;
371 
372         irq_enter();
373 
374 #ifdef CONFIG_IRQ_DOMAIN
375         if (lookup)
376                 irq = irq_find_mapping(domain, hwirq);
377 #endif
378 
379         /*
380          * Some hardware gives randomly wrong interrupts.  Rather
381          * than crashing, do something sensible.
382          */
383         if (unlikely(!irq || irq >= nr_irqs)) {                // 判断中断号
384                 ack_bad_irq(irq);
385                 ret = -EINVAL;
386         } else {
387                 generic_handle_irq(irq); // 调用中断处理函数
388         }
389 
390         irq_exit();
391         set_irq_regs(old_regs);
392         return ret;
393 }

generic_handle_irq是体系结构无关函数,用来调用desc->handle_irq,该函数指针在中断初始化时指向了处理函数。 其中,set_irq_regs将指向寄存器结构体的指针保存在一个全局的CPU变量中,后续的程序可以通过该变量访问寄存器结构体。所以在进入中断处理前,先将全局CPU变量中保存的旧指针保留下来,等到中断处理结束后再将其恢复。irq_enter负责更新一些统计量:

     kernel/softirq.c
322 /*
323  * Enter an interrupt context.
324  */
325 void irq_enter(void)
326 {
327         rcu_irq_enter();
328         if (is_idle_task(current) && !in_interrupt()) {
329                 /*
330                  * Prevent raise_softirq from needlessly waking up ksoftirqd
331                  * here, as softirq will be serviced on return from interrupt.
332                  */
333                 local_bh_disable();
334                 tick_irq_enter();
335                 _local_bh_enable();
336         }
337 
338         __irq_enter();
339 }

宏__irq_enter()定义如下:

 29 /*
 30  * It is safe to do non-atomic ops on ->hardirq_context,
 31  * because NMI handlers may not preempt and the ops are
 32  * always balanced, so the interrupted value of ->hardirq_context
 33  * will always be restored.
 34  */
 35 #define __irq_enter()                                   \
 36         do {                                            \
 37                 account_irq_enter_time(current);        \
 38                 preempt_count_add(HARDIRQ_OFFSET);      \
 39                 trace_hardirq_enter();                  \
 40         } while (0)

preempt_count_add(HARDIRQ_OFFSET)使表示中断处理程序嵌套层次的计数器加1.

完成中断处理函数调用后,返回到asm_do_IRQ继续执行,其中最重要的是执行中断退出函数irq_exit:

377 /*
378  * Exit an interrupt context. Process softirqs if needed and possible:
379  */
380 void irq_exit(void)
381 {
382 #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
383         local_irq_disable();
384 #else
385         WARN_ON_ONCE(!irqs_disabled());
386 #endif
387 
388         account_irq_exit_time(current);
389         preempt_count_sub(HARDIRQ_OFFSET);
390         if (!in_interrupt() && local_softirq_pending())
391                 invoke_softirq();
392 
393         tick_irq_exit();
394         rcu_irq_exit();
395         trace_hardirq_exit(); /* must be last! */
396 }

irq_exit函数首先将preempt_count_sub计数器减去HARDIRQ_OFFSET,用来标识退出硬中断,这与irq_enter函数中的preempt_count_add相对应。

之后irq_exit会通过宏in_interrupt()判断当前是否处在中断(interrupt)中。

处理完软中断后,返回asm_do_IRQ,该函数最后通过set_irq_regs(old_regs)将寄存器集合指针恢复到发生中断之前的值。asm_do_IRQ结束后会返回入口点,再次回到汇编语言代码。

5.3 软中断softirq

内核出于性能等方面的考虑,将中断分为两部分:上半部处理中断中需要及时响应且处理时间较短的部分,下半部用于处理对响应时间要求不高且处理时间可能较长的部分。本节将主要介绍中断下半部的三种实现方式之一:软中断。

软中断是在编译期间静态分配的,由数据结构softirq_action表示,定义在include/linux/interrupt.h:

436 struct softirq_action
437 {
438         void    (*action)(struct softirq_action *);
439 };

softirq_action结构体非常简单,仅包含一个指向软中断处理函数的指针。为了管理软中断,内核维护着一个softirq_action结构体数组softirq_vec[NR_SOFTIRQS],称之为软中断向量表:

 56 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

其中NR_SOFTIRQS是一个枚举类型内核支持的最大软中断数。

408 enum
409 {
410         HI_SOFTIRQ=0,
411         TIMER_SOFTIRQ,
412         NET_TX_SOFTIRQ,
413         NET_RX_SOFTIRQ,
414         BLOCK_SOFTIRQ,
415         BLOCK_IOPOLL_SOFTIRQ,
416         TASKLET_SOFTIRQ,
417         SCHED_SOFTIRQ,
418         HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the
419                             numbering. Sigh! */
420         RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
421 
422         NR_SOFTIRQS
423 };

通过上面的枚举类型可知当前内核版本支持10种软中断,其中HI_SOFTIRQ和TASKLET_SOFTIRQ用于实现tasklet,TIMER_SOFTIRQ和HRTIMER_SOFTIRQ用于实现定时器,NET_TX_SOFTIRQ和NET_RX_SOFTIRQ用于实现网络设备的发送和接受操作,BLOCK_SOFTIRQ和BLOCK_IOPOLL_SOFTIRQ用于实现块设备操作,SCHED_SOFTIRQ用于调度器。

内核中通过调用open_softirq函数注册软中断处理程序,该函数接收两个参数:软中断索引号(枚举类型)和处理程序。根据索引号寻址到软中断向量表softirq_vec的指定元素,使其action成员指向处理程序。

433 void open_softirq(int nr, void (*action)(struct softirq_action *))
434 {
435         softirq_vec[nr].action = action;
436 }

例如,网络系统中注册接收和发送数据包的软中断: net/core/dev.c 7794 open_softirq(NET_TX_SOFTIRQ, net_tx_action); 7795 open_softirq(NET_RX_SOFTIRQ, net_rx_action);

一个已经注册的软中断必须“触发”后才能在“适当的时机”被执行,raise_softirq函数和__raise_softirq_irqoff宏用于触发软中断,将一个软中断设置为挂起状态(pending)。raisesoftirq最终也是通过调用\_raise_softirq_irqoff来实现的:

398 /*
399  * This function must run with irqs disabled!
400  */
401 inline void raise_softirq_irqoff(unsigned int nr)
402 {
403         __raise_softirq_irqoff(nr);
404 
405         /*
406          * If we're in an interrupt or softirq, we're done
407          * (this also catches softirq-disabled code). We will
408          * actually run the softirq once we return from
409          * the irq or softirq.
410          *
411          * Otherwise we wake up ksoftirqd to make sure we
412          * schedule the softirq soon.
413          */
414         if (!in_interrupt())
415                 wakeup_softirqd();
416 }

418 void raise_softirq(unsigned int nr)
419 {
420         unsigned long flags;
421 
422         local_irq_save(flags); //禁止中断
423         raise_softirq_irqoff(nr); //触发软中断之前先要禁止中断
424         local_irq_restore(flags); //开启中断
425 }

__raise_softirq_irqoff被定义为宏,以上述的枚举类型作为参数:

        arch/arm/include/asm/hardirq.h
 10 typedef struct {
 11         unsigned int __softirq_pending; //软中断挂起位图,每bit代表一种软中断
 12 #ifdef CONFIG_SMP
 13         unsigned int ipi_irqs[NR_IPI];
 14 #endif
 15 } ____cacheline_aligned irq_cpustat_t;

  kernel/softirq.c
 51 #ifndef __ARCH_IRQ_STAT
 52 irq_cpustat_t irq_stat[NR_CPUS]  ____cacheline_aligned;  //cpu拥有一个软中断状态结构 体,
                                    //所以即使是相同类型的软中断也可以在其他cpu上同时执行。
 53 EXPORT_SYMBOL(irq_stat);
 54 #endif

  include/linux/irq_cpustat.h
 19 #ifndef __ARCH_IRQ_STAT
 20 extern irq_cpustat_t irq_stat[];                /* defined in asm/hardirq.h */
 21 #define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
 22 #endif
 23 
 24   /* arch independent irq_stat fields */
 25 #define local_softirq_pending() \
 26         __IRQ_STAT(smp_processor_id(), __softirq_pending)

软中断并不是一旦触发就被执行,而是在之后的某个“适当的时机”执行,在Linux内核中共有三个“时机”:

1)从一个硬中断返回时,即在irq_exit中有机会执行软中断。

2)在ksoftirqd内核线程中被执行,稍后分析。

3)在那些显式检查或执行待处理软中断的代码中。如网络系统中的netif_rx_ni函数,local_bh_enable以及local_bh_enable_ip等函数。

最终软中断都是通过do_softirq或__do_softirq函数被执行的,其中do_softirq也是通过调用__do_softirq来实现的。在内核需要执行软中断时,先通过local_softirq_pending函数检查是否有软中断挂起,如果有则调用do_softirq来执行软中断。

304 asmlinkage __visible void do_softirq(void)
305 {
306         __u32 pending;
307         unsigned long flags;
308 
    /*如果in_interrupt()返回值为非0,
     *则表示在中断上下文中调用了do_softirq,或者是在禁止了软中断后调用了do_softirq。
     *这两种情况都不允许执行软中断,所以直接返回。
     */
309         if (in_interrupt())
310                 return;
311 
312         local_irq_save(flags); //禁止本地CPU中断,并保存中断状态到flags临时变量中
313 
314         pending = local_softirq_pending(); //将本地软中断挂起状态位图存入局部变量pending
315 
316         if (pending) //如果有软中断挂起等待处理
317                 do_softirq_own_stack();
318 
319         local_irq_restore(flags);
320 }

158 void do_softirq_own_stack(void)
159 {
160         struct thread_info *curctx;
161         union irq_ctx *irqctx;
162         u32 *isp;
163 
164         curctx = current_thread_info();
165         irqctx = softirq_ctx[smp_processor_id()];
166         irqctx->tinfo.task = curctx->task;
167 
168         /* build the stack frame on the softirq stack */
169         isp = (u32 *) ((char *)irqctx + sizeof(struct thread_info));
170 
171         asm volatile (
172                 "MOV   D0.5,%0\n"
173                 "SWAP  A0StP,D0.5\n"
174                 "CALLR D1RtP,___do_softirq\n"
175                 "MOV   A0StP,D0.5\n"
176                 :
177                 : "r" (isp)
178                 : "memory", "cc", "D1Ar1", "D0Ar2", "D1Ar3", "D0Ar4",
179                   "D1Ar5", "D0Ar6", "D0Re0", "D1Re0", "D0.4", "D1RtP",
180                   "D0.5"
181                 );
182 }

230 asmlinkage __visible void __do_softirq(void)
231 {
232         unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
233         unsigned long old_flags = current->flags;
234         int max_restart = MAX_SOFTIRQ_RESTART;
235         struct softirq_action *h;
236         bool in_hardirq;
237         __u32 pending;
238         int softirq_bit;
239 
240         /*
241          * Mask out PF_MEMALLOC s current task context is borrowed for the
242          * softirq. A softirq handled such as network RX might set PF_MEMALLOC
243          * again if the socket is related to swap
244          */
245         current->flags &= ~PF_MEMALLOC;
246 
247         pending = local_softirq_pending();
248         account_irq_enter_time(current);
249 
250         __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
251         in_hardirq = lockdep_softirq_start();
252 
253 restart:
254         /* Reset the pending bitmask before enabling irqs */
255         set_softirq_pending(0);
256 
257         local_irq_enable(); //开启本地CPU中断
258 
259         h = softirq_vec; //指向软中断向量表
260 
261         while ((softirq_bit = ffs(pending))) {  //遍历所有类型的软中断
262                 unsigned int vec_nr;
263                 int prev_count;
264 
265                 h += softirq_bit - 1;
266 
267                 vec_nr = h - softirq_vec;
268                 prev_count = preempt_count();
269 
270                 kstat_incr_softirqs_this_cpu(vec_nr);
271 
272                 trace_softirq_entry(vec_nr);
273                 h->action(h);
274                 trace_softirq_exit(vec_nr);
275                 if (unlikely(prev_count != preempt_count())) {
276                         pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
277                                vec_nr, softirq_to_name[vec_nr], h->action,
278                                prev_count, preempt_count());
279                         preempt_count_set(prev_count);
280                 }
281                 h++; //指向软中断向量表的下一项
282                 pending >>= softirq_bit;
283         }
284 
285         rcu_bh_qs();
286         local_irq_disable(); //禁止本地CPU中断
287 
288         pending = local_softirq_pending(); //再次获取软中断挂起状态位图存入pending

      /*如果pending不为0,表示有软中断等待处理,并且循环计数器max_restart不为0时,
    *回到restart 标号处运行,遍历并处理挂起的软中断.
    */
289         if (pending) {
290                 if (time_before(jiffies, end) && !need_resched() &&
291                     --max_restart)
292                         goto restart;
    /*如果完成了max_restart次循环处理软中断后,仍有待处理的软中断(pending为非0),
    *调用wakeup_softirqd唤醒软中断处理内核线程处理本地CPU上的软中断。
    */
293 
294                 wakeup_softirqd();
295         }
296 
297         lockdep_softirq_end(in_hardirq);
298         account_irq_exit_time(current);
299         __local_bh_enable(SOFTIRQ_OFFSET); //最后开启中断下半部
300         WARN_ON_ONCE(in_interrupt());
301         tsk_restore_flags(current, old_flags, PF_MEMALLOC);
302 }

最后,软中断的内核线程(ksoftirqd)。这实际上是内核为了解决大量软中断重复触发“霸占”CPU导致用户进程处于饥饿状态的一种折中方案。借助内核线程,可以保证在软中断负担很重的时候用户进程不会因为得不到处理时间而处于饥饿状态;同时也保证过量的软中断终究会得到处理。即使在空闲系统上,这种方案也表现良好,软中断处理得非常迅速,因为仅存的内核线程肯定会马上调度。

内核中有三个地方调用wakeup_softirqd唤醒ksoftirqd内核线程:

1)在__do_softirq函数中

2)在raise_softirq_irqoff函数中

3)在invoke_softirq函数中

5.4 tasklet

判断一个类型的软中断处理函数是否可以执行的条件是软中断挂起位图__softirq_pending的相应bit是否被置1。在多处理器系统中,每个处理器拥有各自的软中断挂起位图__softirq_pending变量。由于执行软中断过程中是开启本地处理器的硬件中断并禁止本地处理器的软中断。因此,如果同一个软中断在执行过程中又被触发,那么另外一个处理器就有机会同时执行该软中断处理程序,这有助于提高软中断效率,但对于软中断处理函数的设计必须是完全可重入且线程安全的,对临界区做必要的加锁保护,从而增加了代码设计复杂度。

tasklet是另一种中断下半部的实现方式。tasklet是通过软中断实现的,其本质上就是软中断,不过它的代码设计复杂度要比软中断低,接口更简单。因此除非对性能要求很高像网络系统那样,否则实现中断下半部的方式一般选择tasklet。

tasklet_struct结构体用于描述tasklet,其定义在include/linux/interrupt.h如下:

487 struct tasklet_struct
488 {
489         struct tasklet_struct *next; //指向链表中下一个tasklet结构体
490         unsigned long state;  //tasklet状态
491         atomic_t count; //引用计数器
492         void (*func)(unsigned long); //tasklet处理函数
493         unsigned long data; //tasklet处理函数的参数
494 };

其中state有2种可能的状态,分别是TASKLET_STATE_SCHED和TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示tasklet已被调度等待执行;TASKLET_STATE_RUN表示该tasklet正在执行,该状态仅在多处理器系统中有效。

count是原子的引用计数器,用于禁止已调度的tasklet。如果count值不为0,则tasklet被禁止,不允许执行;只有当它为0时,tasklet才被激活,并且被设置为TASKLET_STATE_SCHED状态时,该tasklet才能够执行。

func函数指针指向tasklet的处理函数,data是给处理函数传递的参数。

内核中有两种创建tasklet的方式:静态创建和动态创建。内核提供了如下两个宏用于静态创建tasklet:

496 #define DECLARE_TASKLET(name, func, data) \
497 struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
498 
499 #define DECLARE_TASKLET_DISABLED(name, func, data) \
500 struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

这两个宏的唯一区别就是count引用计数的初值设置。前一个宏把count设置为0,表示该tasklet处于激活状态;后一个宏把count设置为1,表示该tasklet处于禁止状态。

在内核中也可以通过一个tasklet指针动态创建一个tasklet结构,并使用如下接口来初始化一个tasklet:

557 void tasklet_init(struct tasklet_struct *t,
558                   void (*func)(unsigned long), unsigned long data)
559 {
560         t->next = NULL;
561         t->state = 0;
562         atomic_set(&t->count, 0);
563         t->func = func;
564         t->data = data;
565 }

开发人员在定义并初始化一个tasklet实例后,必须将其注册到系统后才有机会被执行。内核中维护了两个per-CPU型的tasklet管理链表tasklet_vec和tasklet_hi_vec,分别定义如下:

441 struct tasklet_head {
442         struct tasklet_struct *head;
443         struct tasklet_struct **tail;
444 };
445 
446 static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
447 static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

软中断时提到的枚举类型,其中HI_SOFTIRQ和TASKLET_SOFTIRQ用于实现tasklet,所以tasklet_hi_vec与tasklet_vec的唯一区别就在于tasklet_hi_vec对应HI_SOFTIRQ而tasklet_vec对应TASKLET_SOFTIRQ。在执行软中断时,HI_SOFTIRQ优先于TASKLET_SOFTIRQ执行。所以开发人员可以根据优先级的需求来决定自己创建的tasklet注册到tasklet_hi_vec还是tasklet_vec中。

由于tasklet_hi_vec和tasklet_vec非常类似,所以接下来仅分析与tasklet_vec相关的内容。内核中提供了注册tasklet的接口:

533 static inline void tasklet_schedule(struct tasklet_struct *t)
534 {
535         if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
536                 __tasklet_schedule(t);
537 }

449 void __tasklet_schedule(struct tasklet_struct *t)
450 {
451         unsigned long flags;
         // 为了避免多个内核控制路径共同修改tasklet_vec,必须禁止本地cpu中断
452   
453         local_irq_save(flags);
454         t->next = NULL;
455         *__this_cpu_read(tasklet_vec.tail) = t;
456         __this_cpu_write(tasklet_vec.tail, &(t->next));
         //激活TASKLET_SOFTIRQ软中断,确保下次调用do_softirq时执行该tasklet
457         raise_softirq_irqoff(TASKLET_SOFTIRQ);
458         local_irq_restore(flags); //开启本地cpu中断
459 }

tasklet_schedule首先检查待注册tasklet的状态字段是否已经被设置为TASKLET_STATE_SCHED。如果是,则表示该tasklet已经被注册,不需要重新注册;如果不是,则将该tasklet的状态字段设置为TASKLET_STATE_SCHED并调用__tasklet_schedule将tasklet结构体实例添加到tasklet_vec管理链表中。

由于tasklet是基于软中断实现的,因此要执行tasklet需要一个软中断处理函数来调用tasklet的处理函数。在内核启动过程中调用了softirq_init函数来完成一些软中断相关的初始化工作:

634 void __init softirq_init(void)
635 {
636         int cpu;
637 
638         for_each_possible_cpu(cpu) {
639                 per_cpu(tasklet_vec, cpu).tail =
640                         &per_cpu(tasklet_vec, cpu).head; //初始化tasklet_vec链表
641                 per_cpu(tasklet_hi_vec, cpu).tail =
642                         &per_cpu(tasklet_hi_vec, cpu).head; //初始化tasklet_hi_vec链表
643         }
644 
645         open_softirq(TASKLET_SOFTIRQ, tasklet_action); //注册tasklet的软中断处理函数
646         open_softirq(HI_SOFTIRQ, tasklet_hi_action); //注册高优先级tasklet的软中断处理函数
647 }

以tasklet_action为例,分析如何执行已注册到tasklet_vec中的tasklet。

485 static void tasklet_action(struct softirq_action *a)
486 {
487         struct tasklet_struct *list;
     /*由于软中断处理函数是在开中断环境下执行的,所以为了避免多个内核控制路径修改
      *tasklet_vec,此处要禁止本地cpu中断。
      */
488 
489         local_irq_disable();
490         list = __this_cpu_read(tasklet_vec.head); //使用局部变量list保存tasklet_vec链表表头指针  
491         __this_cpu_write(tasklet_vec.head, NULL); //将tasklet_vec链表清空 
492         __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
493         local_irq_enable(); //开启本地cpu中断
494 
495         while (list) {      //遍历tasklet_vec链表
496                 struct tasklet_struct *t = list;
497 
498                 list = list->next;
499 
500                 if (tasklet_trylock(t)) {  //判断count是否为0,如果为0,则表示该tasklet没有被禁止可以执行。
501                         if (!atomic_read(&t->count)) {
502                                 if (!test_and_clear_bit(TASKLET_STATE_SCHED,
503                                                         &t->state))
504                                         BUG();
505                                 t->func(t->data); //执行tasklet处理函数
506                                 tasklet_unlock(t); //清除tasklet的TASKLET_STATE_RUN状态
507                                 continue; //跳到循环开始处继续执行下一个tasklet
508                         }
        //如果count不为0,则表示该tasklet被禁止,执行下面的代码
509                         tasklet_unlock(t); 
510                 }
511 
512                 local_irq_disable(); //禁止本地cpu中断,原因同上。

       /*将已被注册但没有得到执行的tasklet重新放到已清空的tasklet_vec链表中,以
        *免造成tasklet丢失。
        */
513                 t->next = NULL;
514                 *__this_cpu_read(tasklet_vec.tail) = t;
515                 __this_cpu_write(tasklet_vec.tail, &(t->next));
516                 __raise_softirq_irqoff(TASKLET_SOFTIRQ); //触发TASKLET_SOFTIRQ软中断
517                 local_irq_enable(); //开始本地cpu中断
518         }
519 }

5.5 工作队列

工作队列是另一种中断下半部实现方式,它与软中断和tasklet最主要区别在于其不在中断上下文执行而是在进程上下文执行。因此它拥有在进程上下文执行的所有优势,例如允许被重新调度以及睡眠等。一般的,如果推后执行的任务需要睡眠则选择工作队列来实现,否则选择软中断或tasklet。

工作队列子系统可以让你的驱动程序创建一个专门的工作者线程来处理需要推后的工作。不过,工作队列子系统提供了一个默认的工作者线程来处理这些工作。因此,工作队列最基本的表现形式就转变成了一个把需要推后执行的任务交给特定的通用线程这样一种接口。

5.5.1 相关数据结构

首先介绍一下与工作队列相关的几个数据结构。内核将工作队列抽象成数据结构workqueue_struct,定义如下:

237 struct workqueue_struct {
238         struct list_head        pwqs;           /* WR: all pwqs of this wq */
239         struct list_head        list;           /* PR: list of all workqueues */
240 
241         struct mutex            mutex;          /* protects this wq */
242         int                     work_color;     /* WQ: current work color */
243         int                     flush_color;    /* WQ: current flush color */
244         atomic_t                nr_pwqs_to_flush; /* flush in progress */
245         struct wq_flusher       *first_flusher; /* WQ: first flusher */
246         struct list_head        flusher_queue;  /* WQ: flush waiters */
247         struct list_head        flusher_overflow; /* WQ: flush overflow list */
248 
249         struct list_head        maydays;        /* MD: pwqs requesting rescue */
250         struct worker           *rescuer;       /* I: rescue worker */
251 
252         int                     nr_drainers;    /* WQ: drain in progress */
253         int                     saved_max_active; /* WQ: saved pwq max_active */
254 
255         struct workqueue_attrs  *unbound_attrs; /* PW: only for unbound wqs */
256         struct pool_workqueue   *dfl_pwq;       /* PW: only for unbound wqs */
257 
258 #ifdef CONFIG_SYSFS
259         struct wq_device        *wq_dev;        /* I: for sysfs interface */
260 #endif
261 #ifdef CONFIG_LOCKDEP
262         struct lockdep_map      lockdep_map;
263 #endif
264         char                    name[WQ_NAME_LEN]; /* I: workqueue name */
265 
266         /*
267          * Destruction of workqueue_struct is sched-RCU protected to allow
268          * walking the workqueues list without grabbing wq_pool_mutex.
269          * This is used to dump all workqueues from sysrq.
270          */
271         struct rcu_head         rcu;
272 
273         /* hot fields used during command issue, aligned to cacheline */
274         unsigned int            flags ____cacheline_aligned; /* WQ: WQ_* flags */
275         struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
276         struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */
277 };

每一个work对应的数据结构为work_struct,定义在include/linux/workqueue.h如下:

100 struct work_struct {
101         atomic_long_t data; /*传递给处理函数的参数*/
102         struct list_head entry; /*将多个待处理工作节点形成链表*/
103         work_func_t func; /*处理函数*/
104 #ifdef CONFIG_LOCKDEP
105         struct lockdep_map lockdep_map;
106 #endif
107 };

5.5.2 使用工作队列

内核对外提供了3种创建工作队列的接口(宏定义):

413 #define create_workqueue(name)                                          \
414         alloc_workqueue("%s", WQ_MEM_RECLAIM, 1, (name))
415 #define create_freezable_workqueue(name)                                \
416         alloc_workqueue("%s", WQ_FREEZABLE | WQ_UNBOUND | WQ_MEM_RECLAIM, \
417                         1, (name))
418 #define create_singlethread_workqueue(name)                             \
419         alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, name)

通过对宏展开发现,这3个接口最终都是调用__alloc_workqueue_key函数,其定义在kernel/workqueue.c如下:

3787 struct workqueue_struct *__alloc_workqueue_key(const char *fmt,
3788                                                unsigned int flags,
3789                                                int max_active,
3790                                                struct lock_class_key *key,
3791                                                const char *lock_name, ...)
3792 {
3793         size_t tbl_size = 0;
3794         va_list args;
3795         struct workqueue_struct *wq;
3796         struct pool_workqueue *pwq;
3797 
3798         /* see the comment above the definition of WQ_POWER_EFFICIENT */
3799         if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
3800                 flags |= WQ_UNBOUND;
3801 
3802         /* allocate wq and format name */
3803         if (flags & WQ_UNBOUND)
3804                 tbl_size = nr_node_ids * sizeof(wq->numa_pwq_tbl[0]);
3805 
3806         wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL); /*处理函数*/
3807         if (!wq)
3808                 return NULL;
3809 
3810         if (flags & WQ_UNBOUND) {
3811                 wq->unbound_attrs = alloc_workqueue_attrs(GFP_KERNEL); 
3812                 if (!wq->unbound_attrs)
3813                         goto err_free_wq;
3814         }
3815 
3816         va_start(args, lock_name);
3817         vsnprintf(wq->name, sizeof(wq->name), fmt, args);
3818         va_end(args);
3819 
3820         max_active = max_active ?: WQ_DFL_ACTIVE;
3821         max_active = wq_clamp_max_active(max_active, flags, wq->name);
3822 
3823         /* init wq */
3824         wq->flags = flags;
3825         wq->saved_max_active = max_active;
3826         mutex_init(&wq->mutex);
3827         atomic_set(&wq->nr_pwqs_to_flush, 0);
3828         INIT_LIST_HEAD(&wq->pwqs);
3829         INIT_LIST_HEAD(&wq->flusher_queue);
3830         INIT_LIST_HEAD(&wq->flusher_overflow);
3831         INIT_LIST_HEAD(&wq->maydays);
3832 
3833         lockdep_init_map(&wq->lockdep_map, lock_name, key, 0);
3834         INIT_LIST_HEAD(&wq->list);
3835 
3836         if (alloc_and_link_pwqs(wq) < 0)
3837                 goto err_free_wq;
3838 
3839         /*
3840          * Workqueues which may be used during memory reclaim should
3841          * have a rescuer to guarantee forward progress.
3842          */
3843         if (flags & WQ_MEM_RECLAIM) {
3844                 struct worker *rescuer;
3845 
3846                 rescuer = alloc_worker(NUMA_NO_NODE);
3847                 if (!rescuer)
3848                         goto err_destroy;
3849 
3850                 rescuer->rescue_wq = wq;
3851                 rescuer->task = kthread_create(rescuer_thread, rescuer, "%s",
3852                                                wq->name);
3853                 if (IS_ERR(rescuer->task)) {
3854                         kfree(rescuer);
3855                         goto err_destroy;
3856                 }
3857 
3858                 wq->rescuer = rescuer;
3859                 kthread_bind_mask(rescuer->task, cpu_possible_mask);
3860                 wake_up_process(rescuer->task);
3861         }
3862 
3863         if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
3864                 goto err_destroy;
3865 
3866         /*
3867          * wq_pool_mutex protects global freeze state and workqueues list.
3868          * Grab it, adjust max_active and add the new @wq to workqueues
3869          * list.
3870          */
3871         mutex_lock(&wq_pool_mutex);
3872 
3873         mutex_lock(&wq->mutex);
3874         for_each_pwq(pwq, wq)
3875                 pwq_adjust_max_active(pwq);
3876         mutex_unlock(&wq->mutex);
3877 
3878         list_add_tail_rcu(&wq->list, &workqueues);
3879 
3880         mutex_unlock(&wq_pool_mutex);
3881 
3882         return wq;
3883 
3884 err_free_wq:
3885         free_workqueue_attrs(wq->unbound_attrs);
3886         kfree(wq);
3887         return NULL;
3888 err_destroy:
3889         destroy_workqueue(wq);
3890         return NULL;
3891 }

5.5.3 销毁工作队列

当驱动程序不再需要先前创建的工作队列时,就需要使用destroy_workqueue函数来销毁工作队列,其主要工作是释放在__alloc_workqueue_key函数中分配的资源

3900 void destroy_workqueue(struct workqueue_struct *wq)
3901 {
3902         struct pool_workqueue *pwq;
3903         int node;
3904 
3905         /* drain it before proceeding with destruction */
3906         drain_workqueue(wq);
3907 
3908         /* sanity checks */
3909         mutex_lock(&wq->mutex);
3910         for_each_pwq(pwq, wq) {
3911                 int i;
3912 
3913                 for (i = 0; i < WORK_NR_COLORS; i++) {
3914                         if (WARN_ON(pwq->nr_in_flight[i])) {
3915                                 mutex_unlock(&wq->mutex);
3916                                 return;
3917                         }
3918                 }
3919 
3920                 if (WARN_ON((pwq != wq->dfl_pwq) && (pwq->refcnt > 1)) ||
3921                     WARN_ON(pwq->nr_active) ||
3922                     WARN_ON(!list_empty(&pwq->delayed_works))) {
3923                         mutex_unlock(&wq->mutex);
3924                         return;
3925                 }
3926         }
3927         mutex_unlock(&wq->mutex);
3928 
3929         /*
3930          * wq list is used to freeze wq, remove from list after
3931          * flushing is complete in case freeze races us.
3932          */
3933         mutex_lock(&wq_pool_mutex);
3934         list_del_rcu(&wq->list); //将工作队列从链表中删除
3935         mutex_unlock(&wq_pool_mutex);
3936 
3937         workqueue_sysfs_unregister(wq);
3938 
3939         if (wq->rescuer)
3940                 kthread_stop(wq->rescuer->task);
3941 
3942         if (!(wq->flags & WQ_UNBOUND)) {
3943                 /*
3944                  * The base ref is never dropped on per-cpu pwqs.  Directly
3945                  * schedule RCU free.
3946                  */
3947                 call_rcu_sched(&wq->rcu, rcu_free_wq);
3948         } else {
3949                 /*
3950                  * We're the sole accessor of @wq at this point.  Directly
3951                  * access numa_pwq_tbl[] and dfl_pwq to put the base refs.
3952                  * @wq will be freed when the last pwq is released.
3953                  */
3954                 for_each_node(node) { //遍历每个node
3955                         pwq = rcu_access_pointer(wq->numa_pwq_tbl[node]);
3956                         RCU_INIT_POINTER(wq->numa_pwq_tbl[node], NULL);
3957                         put_pwq_unlocked(pwq);
3958                 }
3959 
3960                 /*
3961                  * Put dfl_pwq.  @wq may be freed any time after dfl_pwq is
3962                  * put.  Don't access it afterwards.
3963                  */
3964                 pwq = wq->dfl_pwq;
3965                 wq->dfl_pwq = NULL;
3966                 put_pwq_unlocked(pwq);
3967         }
3968 }