linux 中断子系统

  • 1,异常和中断
    • 1.1 中断引入的必要性
    • 1.2 同步异常
      • 1.2.1 同步异常
      • 1.2.2 同步异常的种类
    • 1.3 异步异常
      • 1.3.1 异步异常
      • 1.3.2 异步异常的种类
    • 1.4 中断和异常的关系
  • 2 arm64中断处理
    • 2.1 异常等级( Exception level)
      • 2.1.1 异常等级
      • 2.1.2 在正常和安全世界的异常等级
    • 2.2 异常入口和异常返回
      • 2.2.1 异常发生时处理器所作的事情
      • 2.2.2 异常发生时操作系统所作的事情
      • 2.2.3 异常返回
    • 2.3 异常向量表
      • 2.3.1 armv8定义的异常向量表
        • 2.3.1.1 异常向量表
        • 2.3.1.2 向量基地址寄存器
      • 2.3.2 linux定义的异常向量表
        • 2.3.2.1 linux定义的异常向量表
        • 2.3.2.2 kernel_ventry
    • 2.4 异常/中断处理流程(以el1异常等级下发生irq为例介绍)
      • 2.4.1 el1_irq
      • 2.4.2 中断现场的保存和恢复
        • 2.4.2.1 struct pt_regs
        • 2.4.2.2 和pt_regs所对应的各个宏定义
        • 2.4.2.3 kernel_entry中断现场保存
        • 2.4.2.4 kernel_exit中断现场恢复
    • 2.5 中断处理函数irq_handler
      • 2.5.1 irq_handler
        • 2.5.1.1 irq_handler
        • 2.5.1.2 内核中断栈的初始化
        • 2.5.1.3 handle_arch_irq设置流程
    • 2.6 中断控制器GIC
      • 2.6.1 arm中断控制器的演进
      • 2.6.2 中断控制器支持的中断类型
      • 2.6.3 中断处理状态机
        • 2.6.3.1 Transition A1 or A2, add pending state
        • 2.6.3.2 Transition B1 or B2, remove pending state
        • 2.6.3.3 Transition C, pending to active
        • 2.6.3.4 Transition D, pending to active and pending
        • 2.6.3.5 Transition E1 or E2, remove active state
      • 2.6.4 GIC-V2中断控制器
        • 2.6.4.1 GIC-V2中断控制器的抽象模型
        • 2.6.4.2 GIC-V2的逻辑分区
        • 2.6.4.3 中断路由寄存器
        • 2.6.4.4 GIC-400 handles two physical interrupts of different priority
    • 2.7 GIC中断控制器程序分析
      • 2.7.1 中断控制器代码的声明和初始化
        • 2.7.1.1 中断控制器的声明
        • 2.7.1.2 IRQCHIP_DECLARE
        • 2.7.1.3 中断控制器代码的初始化
        • 2.7.1.4 of_irq_init
        • 2.7.1.5 gic_of_init
        • 2.7.1.6 __gic_init_bases
        • 2.7.1.7 gic_init_chip
          • 2.7.1.7.1 irq_chip的定义
          • 2.7.1.7.2 gic_init_chip的定义
        • 2.7.1.8 gic_init_bases
          • 2.7.1.8.1 gic_init_bases
          • 2.7.1.8.2 gic_irq_domain_hierarchy_ops
          • 2.7.1.8.3 gic_irq_domain_translate
          • 2.7.1.8.4 gic_irq_domain_alloc和gic_irq_domain_map函数
            • 2.7.1.8.4.1 gic_irq_domain_alloc
            • 2.7.1.8.4.2 gic_irq_domain_map
      • 2.7.2 GIC中断处理
        • 2.7.2.1 gic_handle_irq
        • 2.7.2.2 __handle_domain_irq
        • 2.7.2.3 GIC 中断处理函数
        • 2.7.2.4 irq_enter进入到gic中断处理的上下文
            • 2.7.2.4.1 irq_enter
          • 2.7.2.4.2 __irq_enter
        • 2.7.2.5 irq_exit退出中断上下文
      • 2.7.3 gic下级中断的处理
        • 2.7.3.1 handle_fasteoi_irq
        • 2.7.3.2 handle_irq_event
    • 2.8 中断下半部
      • 2.8.1 软中断
        • 2.8.1.1 软中断的概念以及类型
          • 2.8.1.1.1 软中断的概念
          • 2.8.1.1.2 软中断的类型
        • 2.8.1.2 软中断执行的时机
        • 2.8.1.3 irq_exit
        • 2.8.1.4 invoke_softirq
        • 2.8.1.5 __do_softirq
        • 2.8.1.6 注册软中断
          • 2.8.1.6.1 软中断注册函数定义
          • 2.8.1.6.2 软中断注册样例
        • 2.8.1.7 触发软中断
          • 2.8.1.7.1 触发软中断处理的接口
          • 2.8.1.7.2 触发软中断处理的示例
      • 2.8.2 tasklet
        • 2.8.2.1 tasklet数据结构
        • 2.8.2.2 声明tasklet
          • 2.8.2.2.1 静态定义
          • 2.8.2.2.2 动态定义接口
          • 2.8.2.2.3 tasklet使用样例
          • 2.8.2.2.4 调度一个tasklet
      • 2.8.3 工作队列workqueue
        • 2.8.3.1 workqueue数据结构
        • 2.8.3.2 创建workqueue以及使用样例:
        • 2.8.3.3 调度workqueue以及使用样例:
  • 参考文献:
    • 书籍:
    • 博文:

1,异常和中断

1.1 中断引入的必要性

引进中断主要是为了应对系统侧一些紧急的请求而需要把当前正在运行的任务暂停下来的一种做法,这样可以保证紧急的请求得到响应。

1.2 同步异常

1.2.1 同步异常

An exception is described as synchronous if all of the following apply:
如果以下所有情况都适用,一个异常被描述为同步的。
• The exception is generated as a result of direct execution or attempted execution of an instruction.

  • 异常是由于直接执行或试图执行一条指令而产生的
    • The return address presented to the exception handler is guaranteed to indicate the instruction that caused the exception.
  • 呈现给异常处理程序的返回地址保证表明引起异常的指令。
    • The exception is precise.
  • 异常是精确的

1.2.2 同步异常的种类

  • Instruction aborts from the MMU. For example, by reading an instruction from a memory
  • location marked as Execute Never.
  • Data Aborts from the MMU. For example, Permission failure or alignment checking.
  • SP and PC alignment checking.
  • Synchronous external aborts. For example, an abort when reading translation table.
  • Unallocated instructions.
  • Debug exceptions.
  • 系统调用:svc, hvc, SMC
  • pagefault等MMC引起的异常
  • SP和PC对齐异常
  • 未定义指令执行异常

1.3 异步异常

1.3.1 异步异常

An exception is described as asynchronous if any of the following apply:
如果以下所有情况都适用,一个异常被描述为异步的:
• The exception is not generated as a result of direct execution or attempted execution of the instruction stream.

  • 异常是并非由于直接执行或试图执行一条指令而产生的
    • The return address presented to the exception handler is not guaranteed to indicate the instruction that caused the exception.
  • 呈现给异常处理程序的返回地址不能保证是引起异常的指令。
    • The exception is imprecise.
  • 异常是非精确的

1.3.2 异步异常的种类

  • IRQ中断
  • FIQ中断
  • SError

1.4 中断和异常的关系

中断是一种特殊的异常,中断是异步异常。

2 arm64中断处理

2.1 异常等级( Exception level)

2.1.1 异常等级

EL0 : Normal user applications.
EL1 : Operating system kernel typically described as privileged.
EL2 : Hypervisor.
EL3 : Low-level firmware, including the Secure Monitor.

2.1.2 在正常和安全世界的异常等级

2.2 异常入口和异常返回

2.2.1 异常发生时处理器所作的事情

  • PSTATE寄存器的值保存到SPSR_ELx
  • 将PC 保存到ELR_ELx
  • 将PSTATE寄存器的DAIF域均设置为1,关闭调试异常,系统错误,IRQ以及FIQ
  • 更新异常原因寄存器ESR_ELx
  • SP切换到SP_ELx
  • 切换到对应的异常等级ELx,跳转到异常向量表去执行

2.2.2 异常发生时操作系统所作的事情

操作系统会根据发生的异常向量表跳转到对应的异常向量入口。
异常向量表的每一个入口代表的是一个异常处理的跳转函数,跳转到对应的异常处理函数去处理异常。

2.2.3 异常返回

当操作系统执行eret执行时,会触发从异常返回的处理
处理器所做的事情:

  • ELR_ELx恢复到PC
  • 从SPSR_ELx恢复PSTATE寄存器
    注:x30和ELR_ELx两个寄存器分别的含义:x30作为lr是子函数的返回地址,使用的是ret指令。ELR_ELx作为lr是异常返回的地址,使用eret指令来返回。

2.3 异常向量表

2.3.1 armv8定义的异常向量表

2.3.1.1 异常向量表

2.3.1.2 向量基地址寄存器

Holds the vector base address for any exception that is taken to ELx
保存ELx的异常向量基地址

2.3.2 linux定义的异常向量表

2.3.2.1 linux定义的异常向量表

  • .align 11表示要对下面的代码段按照2^12对齐的方式放置
  • kernel_ventry是一个宏,用于跳转到对应的异常处理函数中
/** Exception vectors.*/.pushsection ".entry.text", "ax".align  11
ENTRY(vectors)kernel_ventry   1, sync_invalid                 // Synchronous EL1tkernel_ventry   1, irq_invalid                  // IRQ EL1tkernel_ventry   1, fiq_invalid                  // FIQ EL1tkernel_ventry   1, error_invalid                // Error EL1tkernel_ventry   1, sync                         // Synchronous EL1hkernel_ventry   1, irq                          // IRQ EL1hkernel_ventry   1, fiq_invalid                  // FIQ EL1hkernel_ventry   1, error                        // Error EL1hkernel_ventry   0, sync                         // Synchronous 64-bit EL0kernel_ventry   0, irq                          // IRQ 64-bit EL0kernel_ventry   0, fiq_invalid                  // FIQ 64-bit EL0kernel_ventry   0, error                        // Error 64-bit EL0#ifdef CONFIG_COMPATkernel_ventry   0, sync_compat, 32              // Synchronous 32-bit EL0kernel_ventry   0, irq_compat, 32               // IRQ 32-bit EL0kernel_ventry   0, fiq_invalid_compat, 32       // FIQ 32-bit EL0kernel_ventry   0, error_compat, 32             // Error 32-bit EL0
#elsekernel_ventry   0, sync_invalid, 32             // Synchronous 32-bit EL0kernel_ventry   0, irq_invalid, 32              // IRQ 32-bit EL0kernel_ventry   0, fiq_invalid, 32              // FIQ 32-bit EL0kernel_ventry   0, error_invalid, 32            // Error 32-bit EL0
#endif
END(vectors)

2.3.2.2 kernel_ventry

  • sub sp, sp, #S_FRAME_SIZE /* 分配栈框 */
  • b el()\el()_\label /* 对于kernel_ventry 1, irq则表示为b el1_irq */
        .macro kernel_ventry, el, label, regsize = 64.align 7    /* 每个entry有128Byte, 可以容纳32条指令 */
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
alternative_if ARM64_UNMAP_KERNEL_AT_EL0.if     \el == 0    /* 如果发生异常的异常等级是el0 */.if     \regsize == 64mrs     x30, tpidrro_el0msr     tpidrro_el0, xzr.elsemov     x30, xzr.endif.endif
alternative_else_nop_endif
#endifsub     sp, sp, #S_FRAME_SIZE
#ifdef CONFIG_VMAP_STACK/** Test whether the SP has overflowed, without corrupting a GPR.* Task and IRQ stacks are aligned to (1 << THREAD_SHIFT).*/add     sp, sp, x0                      // sp' = sp + x0sub     x0, sp, x0                      // x0' = sp' - x0 = (sp + x0) - x0 = sptbnz    x0, #THREAD_SHIFT, 0fsub     x0, sp, x0                      // x0'' = sp' - x0' = (sp + x0) - sp = x0sub     sp, sp, x0                      // sp'' = sp' - x0 = (sp + x0) - x0 = spb       el\()\el\()_\label0:/** Either we've just detected an overflow, or we've taken an exception* while on the overflow stack. Either way, we won't return to* userspace, and can clobber EL0 registers to free up GPRs.*//* Stash the original SP (minus S_FRAME_SIZE) in tpidr_el0. */msr     tpidr_el0, x0/* Recover the original x0 value and stash it in tpidrro_el0 */sub     x0, sp, x0msr     tpidrro_el0, x0/* Switch to the overflow stack */adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0/** Check whether we were already on the overflow stack. This may happen* after panic() re-enables interrupts.*/mrs     x0, tpidr_el0                   // sp of interrupted contextsub     x0, sp, x0                      // delta with top of overflow stacktst     x0, #~(OVERFLOW_STACK_SIZE - 1) // within range?b.ne    __bad_stack                     // no? -> bad stack pointer/* We were already on the overflow stack. Restore sp/x0 and carry on. */sub     sp, sp, x0mrs     x0, tpidrro_el0
#endifb       el\()\el\()_\label  /* 假如是在el1下发生的中断,该条指令和b el1_irq一致*/.endm

2.4 异常/中断处理流程(以el1异常等级下发生irq为例介绍)

2.4.1 el1_irq

  • kernel_entry 1 /中断现场保存/
  • irq_handler /中断处理函数/
  • kernel_exit 1 /中断现场恢复/
        .align  6
el1_irq:kernel_entry 1    /*中断现场保存*/enable_da_f
#ifdef CONFIG_TRACE_IRQFLAGSbl      trace_hardirqs_off
#endifirq_handler    /*中断处理函数*/#ifdef CONFIG_PREEMPTldr     x24, [tsk, #TSK_TI_PREEMPT]     // get preempt countcbnz    x24, 1f                         // preempt count != 0bl      el1_preempt
1:
#endif
#ifdef CONFIG_TRACE_IRQFLAGSbl      trace_hardirqs_on
#endifkernel_exit 1    /*中断现场恢复*/
ENDPROC(el1_irq)

2.4.2 中断现场的保存和恢复

kernel_entry和kernel_exit是对pt_regs的处理,用于保存中断发生时的现场。

2.4.2.1 struct pt_regs

/** This struct defines the way the registers are stored on the stack during an* exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for* stack alignment). struct user_pt_regs must form a prefix of struct pt_regs.*/
struct pt_regs {union {struct user_pt_regs user_regs;struct {u64 regs[31];        /* 31个通用寄存器 x0 ~ x31 */u64 sp;        /* 栈寄存器 sp */u64 pc;        /* 程序计数寄存器PC */u64 pstate;        /* 当前处理器状态寄存器 */};};u64 orig_x0;
#ifdef __AARCH64EB__u32 unused2;s32 syscallno;
#elses32 syscallno;u32 unused2;
#endifu64 orig_addr_limit;u64 unused;     // maintain 16 byte alignmentu64 stackframe[2];
};

2.4.2.2 和pt_regs所对应的各个宏定义

#define S_X0 0 /* offsetof(struct pt_regs, regs[0]) */
#define S_X1 8 /* offsetof(struct pt_regs, regs[1]) */
#define S_X2 16 /* offsetof(struct pt_regs, regs[2]) */
#define S_X3 24 /* offsetof(struct pt_regs, regs[3]) */
#define S_X4 32 /* offsetof(struct pt_regs, regs[4]) */
#define S_X5 40 /* offsetof(struct pt_regs, regs[5]) */
#define S_X6 48 /* offsetof(struct pt_regs, regs[6]) */
#define S_X7 56 /* offsetof(struct pt_regs, regs[7]) */
#define S_X8 64 /* offsetof(struct pt_regs, regs[8]) */
#define S_X10 80 /* offsetof(struct pt_regs, regs[10]) */
#define S_X12 96 /* offsetof(struct pt_regs, regs[12]) */
#define S_X14 112 /* offsetof(struct pt_regs, regs[14]) */
#define S_X16 128 /* offsetof(struct pt_regs, regs[16]) */
#define S_X18 144 /* offsetof(struct pt_regs, regs[18]) */
#define S_X20 160 /* offsetof(struct pt_regs, regs[20]) */
#define S_X22 176 /* offsetof(struct pt_regs, regs[22]) */
#define S_X24 192 /* offsetof(struct pt_regs, regs[24]) */
#define S_X26 208 /* offsetof(struct pt_regs, regs[26]) */
#define S_X28 224 /* offsetof(struct pt_regs, regs[28]) */
#define S_LR 240 /* offsetof(struct pt_regs, regs[30]) */
#define S_SP 248 /* offsetof(struct pt_regs, sp) */
#define S_COMPAT_SP 104 /* offsetof(struct pt_regs, compat_sp) */
#define S_PSTATE 264 /* offsetof(struct pt_regs, pstate) */
#define S_PC 256 /* offsetof(struct pt_regs, pc) */
#define S_ORIG_X0 272 /* offsetof(struct pt_regs, orig_x0) */
#define S_SYSCALLNO 280 /* offsetof(struct pt_regs, syscallno) */
#define S_ORIG_ADDR_LIMIT 288 /* offsetof(struct pt_regs, orig_addr_limit) */
#define S_STACKFRAME 304 /* offsetof(struct pt_regs, stackframe) */
#define S_FRAME_SIZE 320 /* sizeof(struct pt_regs) */

2.4.2.3 kernel_entry中断现场保存

        .macro  kernel_entry, el, regsize = 64.if     \regsize == 32mov     w0, w0                          // zero upper 32 bits of x0.endifstp     x0, x1, [sp, #16 * 0]stp     x2, x3, [sp, #16 * 1]stp     x4, x5, [sp, #16 * 2]stp     x6, x7, [sp, #16 * 3]stp     x8, x9, [sp, #16 * 4]stp     x10, x11, [sp, #16 * 5]stp     x12, x13, [sp, #16 * 6]stp     x14, x15, [sp, #16 * 7]stp     x16, x17, [sp, #16 * 8]stp     x18, x19, [sp, #16 * 9]stp     x20, x21, [sp, #16 * 10]stp     x22, x23, [sp, #16 * 11]stp     x24, x25, [sp, #16 * 12]stp     x26, x27, [sp, #16 * 13]stp     x28, x29, [sp, #16 * 14].if     \el == 0        /* if (el == 0)*/clear_gp_regsmrs     x21, sp_el0ldr_this_cpu    tsk, __entry_task, x20  // Ensure MDSCR_EL1.SS is clear,ldr     x19, [tsk, #TSK_TI_FLAGS]       // since we can unmask debugdisable_step_tsk x19, x20               // exceptions when scheduling.apply_ssbd 1, x22, x23.else        /* el != 0*/add     x21, sp, #S_FRAME_SIZEget_thread_info tsk/* Save the task's original addr_limit and set USER_DS */ldr     x20, [tsk, #TSK_TI_ADDR_LIMIT]str     x20, [sp, #S_ORIG_ADDR_LIMIT]        /* S_ORIG_ADDR_LIMIT 288 offsetof(struct pt_regs, orig_addr_limit) */mov     x20, #USER_DSstr     x20, [tsk, #TSK_TI_ADDR_LIMIT]        /* TSK_TI_ADDR_LIMIT 8 offsetof(struct task_struct, thread_info.addr_limit) *//* No need to reset PSTATE.UAO, hardware's already set it to 0 for us */.endif /* \el == 0 */mrs     x22, elr_el1        /* 把elr_el1寄存器保存到x22寄存器中*/mrs     x23, spsr_el1        /* 把spsr_el1寄存器的值保存到x23中 */stp     lr, x21, [sp, #S_LR]        /* lr(x30) and sp + #S_FRAME_SIZE save to stack*//** In order to be able to dump the contents of struct pt_regs at the* time the exception was taken (in case we attempt to walk the call* stack later), chain it together with the stack frames.*/.if \el == 0stp     xzr, xzr, [sp, #S_STACKFRAME].elsestp     x29, x22, [sp, #S_STACKFRAME]        /* 把FP 和elr_el1寄存器的值保存到sp + S_STACKFRAME处 */.endifadd     x29, sp, #S_STACKFRAME        /* fp = sp + S_STACKFRAME */#ifdef CONFIG_ARM64_SW_TTBR0_PAN/** Set the TTBR0 PAN bit in SPSR. When the exception is taken from* EL0, there is no need to check the state of TTBR0_EL1 since* accesses are always enabled.* Note that the meaning of this bit differs from the ARMv8.1 PAN* feature as all TTBR0_EL1 accesses are disabled, not just those to* user mappings.*/
alternative_if ARM64_HAS_PANb       1f                              // skip TTBR0 PAN
alternative_else_nop_endif.if     \el != 0        /* 如果是在el0发生的异常 */mrs     x21, ttbr0_el1        /* 保存ttbr0_el1的值到x21寄存器中 */tst     x21, #TTBR_ASID_MASK            // Check for the reserved ASIDorr     x23, x23, #PSR_PAN_BIT          // Set the emulated PAN in the saved SPSRb.eq    1f                              // TTBR0 access already disabledand     x23, x23, #~PSR_PAN_BIT         // Clear the emulated PAN in the saved SPSR.endif__uaccess_ttbr0_disable x21
1:
#endifstp     x22, x23, [sp, #S_PC]    /*将elr_elx和spsr_el1的值保存到栈的sp+S_PC位置*//* Not in a syscall by default (el0_svc overwrites for real syscall) */.if     \el == 0mov     w21, #NO_SYSCALLstr     w21, [sp, #S_SYSCALLNO]    /*保存系统调用号*/.endif/** Set sp_el0 to current thread_info.*/.if     \el == 0msr     sp_el0, tsk    /*如果是在el0发生的中断,则将tsk保存到sp_el0寄存器中*/.endif/** Registers that may be useful after this macro is invoked:** x21 - aborted SP* x22 - aborted PC* x23 - aborted PSTATE*/.endm

2.4.2.4 kernel_exit中断现场恢复

  • ct_user_enter /如果是在el0发生的中断,则中断现场的恢复调用该函数进行处理/
  • ldr lr, [sp, #S_LR] // restore lr
  • add sp, sp, #S_FRAME_SIZE // restore sp
  • eret /* eret指令会让处理器从硬中断上下文(中断上半部)返回 */
        .macro  kernel_exit, el.if     \el != 0disable_daif/* Restore the task's original addr_limit. */ldr     x20, [sp, #S_ORIG_ADDR_LIMIT]str     x20, [tsk, #TSK_TI_ADDR_LIMIT]/* No need to restore UAO, it will be restored from SPSR_EL1 */.endifldp     x21, x22, [sp, #S_PC]           // load ELR, SPSR.if     \el == 0ct_user_enter    /*如果是在el0发生的中断,则中断现场的恢复调用该函数进行处理*/.endif#ifdef CONFIG_ARM64_SW_TTBR0_PAN/** Restore access to TTBR0_EL1. If returning to EL0, no need for SPSR* PAN bit checking.*/
alternative_if ARM64_HAS_PANb       2f                              // skip TTBR0 PAN
alternative_else_nop_endif.if     \el != 0tbnz    x22, #22, 1f                    // Skip re-enabling TTBR0 access if the PSR_PAN_BIT is set.endif__uaccess_ttbr0_enable x0, x1.if     \el == 0/** Enable errata workarounds only if returning to user. The only* workaround currently required for TTBR0_EL1 changes are for the* Cavium erratum 27456 (broadcast TLBI instructions may cause I-cache* corruption).*/bl      post_ttbr_update_workaround.endif
1:.if     \el != 0and     x22, x22, #~PSR_PAN_BIT         // ARMv8.0 CPUs do not understand this bit.endif
2:
#endif.if     \el == 0ldr     x23, [sp, #S_SP]                // load return stack pointermsr     sp_el0, x23tst     x22, #PSR_MODE32_BIT            // native task?b.eq    3f#ifdef CONFIG_ARM64_ERRATUM_845719
alternative_if ARM64_WORKAROUND_845719
#ifdef CONFIG_PID_IN_CONTEXTIDRmrs     x29, contextidr_el1msr     contextidr_el1, x29
#elsemsr contextidr_el1, xzr
#endif
alternative_else_nop_endif
#endif
3:apply_ssbd 0, x0, x1.endifmsr     elr_el1, x21                    // set up the return datamsr     spsr_el1, x22ldp     x0, x1, [sp, #16 * 0]ldp     x2, x3, [sp, #16 * 1]ldp     x4, x5, [sp, #16 * 2]ldp     x6, x7, [sp, #16 * 3]ldp     x8, x9, [sp, #16 * 4]ldp     x10, x11, [sp, #16 * 5]ldp     x12, x13, [sp, #16 * 6]ldp     x14, x15, [sp, #16 * 7]ldp     x16, x17, [sp, #16 * 8]ldp     x18, x19, [sp, #16 * 9]ldp     x20, x21, [sp, #16 * 10]ldp     x22, x23, [sp, #16 * 11]ldp     x24, x25, [sp, #16 * 12]ldp     x26, x27, [sp, #16 * 13]ldp     x28, x29, [sp, #16 * 14]ldr     lr, [sp, #S_LR]add     sp, sp, #S_FRAME_SIZE           // restore sp.if     \el == 0
alternative_insn eret, nop, ARM64_UNMAP_KERNEL_AT_EL0
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0bne     4fmsr     far_el1, x30tramp_alias     x30, tramp_exit_nativebr      x30
4:tramp_alias     x30, tramp_exit_compatbr      x30
#endif.elseeret        /* 从硬中断上下文返回 */.endifsb.endm

2.5 中断处理函数irq_handler

2.5.1 irq_handler

2.5.1.1 irq_handler

  • irq_stack_entry中断栈的保存
  • blr handle_arch_irq执行handle_arch_irq中断处理函数,该函数由中断控制器初始化的时候注册
  • irq_stack_exit中断栈的恢复
/** Interrupt handling.*/.macro  irq_handlerldr_l   x1, handle_arch_irqmov     x0, spirq_stack_entryblr     x1irq_stack_exit.endm

2.5.1.2 内核中断栈的初始化

中断栈的创建过程:

|-start_kernel|- init_IRQ|- init_irq_stacks

2.5.1.3 handle_arch_irq设置流程

针对arm v8所使用的gic-v2和gic-v3中断控制器,handle_arch_irq函数被设置为gic_handle_irq函数。

handle_arch_irq设置流程:
|- start_kernel|- init_IRQ|- irqchip_init|- of_irq_init|- gic_of_init|- __gic_init_bases|- set_handle_irq(gic_handle_irq);handle_arch_irq = handle_irq;

2.6 中断控制器GIC

2.6.1 arm中断控制器的演进

2.6.2 中断控制器支持的中断类型

中断可以有多种不同的类型:
软件触发中断(SGI,Software Generated Interrupt)这是由软件通过写入专用仲裁单元的寄存器
即软件触发中断寄存器(ICDSGIR)显式生成的。它最常用于CPU核间通信(IPI)。
SGI既可以发给所有的核,也可以发送给系统中选定的一组核心。中断号0-15保留用于SGI的中断号。
用于通信的确切中断号由软件决定。

私有外设中断(PPI,Private Peripheral Interrupt)这是由单个CPU核私有的外设生成的。PPI的
中断号为16-31。它们标识CPU核私有的中断源,并且独立于另一个内核上的相同中断源,
比如,每个核的计时器。

共享外设中断(SPI,Shared Peripheral Interrupt)这是由外设生成的,中断控制器可以将其路由
到多个核。中断号为32-1020。SPI用于从整个系统可访问的各种外围设备发出中断信号。

本地特殊外设中断(LPI, Locality-specific peripheral interrupt),GIC-V3新增加的中断类型,
基于消息传递的中断。

2.6.3 中断处理状态机

2.6.3.1 Transition A1 or A2, add pending state

For an SGI, occurs if either:
• Software writes to a GICD_SGIR that specifies the processor as a target.
• Software on the target processor writes to the GICD_SPENDSGIRn bit that corresponds to
the required source processor and interrupt ID
Note
If the GIC implements the GIC Security Extensions and the write to the GICD_SGIR is Secure, the
transition occurs only if the security configuration of the specified SGI, for the appropriate CPU
interface, corresponds to the GICD_SGIR.NSATT bit value.
For an SPI or PPI, occurs if either:
• a peripheral asserts an interrupt request signal
• software writes to an GICD_ISPENDRn.

2.6.3.2 Transition B1 or B2, remove pending state

For an SGI, occurs if software on the target processor writes to the relevant bit of the
GICD_CPENDSGIRn.
For an SPI or PPI, occurs if either:
• the level-sensitive interrupt is pending only because of the assertion of an input signal, and
that signal is deasserted
• the interrupt is pending only because of the assertion of an edge-triggered interrupt signal, or
a write to an GICD_ISPENDRn, and software writes to the corresponding GICD_ICPENDRn.

2.6.3.3 Transition C, pending to active

If the interrupt is enabled and of Sufficient priority to be signaled to the processor, occurs when
software reads from the GICC_IAR.

2.6.3.4 Transition D, pending to active and pending

For an SGI, this transition occurs in either of the following circumstances:
• If a write to set the SGI state to pending occurs at approximately the same time as a read of
GICC_IAR.
• When two or more pending SGIs with the same interrupt ID originate from the same source
processor and target the same processor. If one of the SGIs follows transition C, the other
SGIs follow transition D
For an SPI or PPI this transition occurs if all the following apply:
• The interrupt is enabled.
• Software reads from the GICC_IAR. This read adds the active state to the interrupt.
• In addition, one of the following conditions applies:
— For a level-sensitive interrupt, the interrupt signal remains asserted. This is usually the
case, because the peripheral does not deassert the interrupt until the processor has
serviced the interrupt.
— For an edge-triggered interrupt, whether this transition occurs depends on the timing
of the read of the GICC_IAR relative to the detection of the reassertion of the interrupt.
Otherwise the read of the GICC_IAR causes transition C, possibly followed by
transition A2.

2.6.3.5 Transition E1 or E2, remove active state

Occurs when software deactivates an interrupt by writing to either GICC_EOIR or GICC_DIR. In a GIC implementation the includes the Virtualization Extensions, also occurs if the virtual CPU interface signals that the corresponding physical interrupt has been deactivated.

2.6.4 GIC-V2中断控制器

2.6.4.1 GIC-V2中断控制器的抽象模型

或者

2.6.4.2 GIC-V2的逻辑分区

  • SGI是用作核间通信
  • PPI用来作为CPU核的私有中断
  • SGI核PPI是针对CPU的,每个CPU有与其对应的中断控制模块
  • SPI可以发送到所有的CPU

2.6.4.3 中断路由寄存器

  • 8bit表示一个中断源,每个bit表示能路由到的CPU核
  • 如果某个bit被置1,则表明中断可以路由到该寄存器
  • 前32个中断源的路由时由硬件设置好的,是RO的。
  • 第33 ~ 1019号中断可以由软件来设置其路由。

2.6.4.4 GIC-400 handles two physical interrupts of different priority



2.7 GIC中断控制器程序分析

2.7.1 中断控制器代码的声明和初始化

2.7.1.1 中断控制器的声明

IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
IRQCHIP_DECLARE(pl390, "arm,pl390", gic_of_init);

2.7.1.2 IRQCHIP_DECLARE

// include\linux\irqchip.h
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)#define OF_DECLARE_2(table, name, compat, fn) \_OF_DECLARE(table, name, compat, fn, of_init_fn_2)#define _OF_DECLARE(table, name, compat, fn, fn_type)                        \static const struct of_device_id __of_table_##name                \__used __section(__##table##_of_table)                        \= { .compatible = compat,                                \.data = (fn == (fn_type)NULL) ? fn : fn  }

最终中断控制器的声明都会被设置到__irqchip_of_table,

2.7.1.3 中断控制器代码的初始化

初始化流程
|- start_kernel|- init_IRQ|- irqchip_init|- of_irq_init|- gic_of_init
/** This special of_device_id is the sentinel at the end of the* of_device_id[] array of all irqchips. It is automatically placed at* the end of the array by the linker, thanks to being part of a* special section.*/
static const struct of_device_id
irqchip_of_match_end __used __section(__irqchip_of_table_end);extern struct of_device_id __irqchip_of_table[];void __init irqchip_init(void)
{of_irq_init(__irqchip_of_table);acpi_probe_device_table(irqchip);
}

2.7.1.4 of_irq_init

  • desc->irq_init_cb = match->data;
  • desc->interrupt_parent = of_irq_find_parent(np);
  • ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent); 最终会通过desc->irq_init_cb调用到所有的中断控制器的初始化函数
/*** of_irq_init - Scan and init matching interrupt controllers in DT* @matches: 0 terminated array of nodes to match and init function to call** This function scans the device tree for matching interrupt controller nodes,* and calls their initialization functions in order with parents first.*/
void __init of_irq_init(const struct of_device_id *matches)
{const struct of_device_id *match;struct device_node *np, *parent = NULL;struct of_intc_desc *desc, *temp_desc;struct list_head intc_desc_list, intc_parent_list;INIT_LIST_HEAD(&intc_desc_list);INIT_LIST_HEAD(&intc_parent_list);for_each_matching_node_and_match(np, matches, &match) {if (!of_property_read_bool(np, "interrupt-controller") ||!of_device_is_available(np))continue;if (WARN(!match->data, "of_irq_init: no init function for %s\n",match->compatible))continue;/** Here, we allocate and populate an of_intc_desc with the node* pointer, interrupt-parent device_node etc.*/desc = kzalloc(sizeof(*desc), GFP_KERNEL);if (WARN_ON(!desc)) {of_node_put(np);goto err;}desc->irq_init_cb = match->data;desc->dev = of_node_get(np);desc->interrupt_parent = of_irq_find_parent(np);if (desc->interrupt_parent == np)desc->interrupt_parent = NULL;list_add_tail(&desc->list, &intc_desc_list);}/** The root irq controller is the one without an interrupt-parent.* That one goes first, followed by the controllers that reference it,* followed by the ones that reference the 2nd level controllers, etc.*/while (!list_empty(&intc_desc_list)) {/** Process all controllers with the current 'parent'.* First pass will be looking for NULL as the parent.* The assumption is that NULL parent means a root controller.*/list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {int ret;if (desc->interrupt_parent != parent)continue;list_del(&desc->list);of_node_set_flag(desc->dev, OF_POPULATED);pr_debug("of_irq_init: init %pOF (%p), parent %p\n",desc->dev,desc->dev, desc->interrupt_parent);ret = desc->irq_init_cb(desc->dev,desc->interrupt_parent);if (ret) {of_node_clear_flag(desc->dev, OF_POPULATED);kfree(desc);continue;}/** This one is now set up; add it to the parent list so* its children can get processed in a subsequent pass.*/list_add_tail(&desc->list, &intc_parent_list);}/* Get the next pending parent that might have children */desc = list_first_entry_or_null(&intc_parent_list,typeof(*desc), list);if (!desc) {pr_err("of_irq_init: children remain, but no parents\n");break;}list_del(&desc->list);parent = desc->dev;kfree(desc);}list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {list_del(&desc->list);kfree(desc);}
err:list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {list_del(&desc->list);of_node_put(desc->dev);kfree(desc);}
}

2.7.1.5 gic_of_init

  • gic_of_setup(gic, node); 中断控制器distributer和cpu interface地址获取
  • __gic_init_bases(gic, -1, &node->fwnode) gic的初始化相关处理
int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{struct gic_chip_data *gic;int irq, ret;if (WARN_ON(!node))return -ENODEV;if (WARN_ON(gic_cnt >= CONFIG_ARM_GIC_MAX_NR))return -EINVAL;gic = &gic_data[gic_cnt];ret = gic_of_setup(gic, node);if (ret)return ret;/** Disable split EOI/Deactivate if either HYP is not available* or the CPU interface is too small.*/if (gic_cnt == 0 && !gic_check_eoimode(node, &gic->raw_cpu_base))static_branch_disable(&supports_deactivate_key);ret = __gic_init_bases(gic, -1, &node->fwnode);if (ret) {gic_teardown(gic);return ret;}if (!gic_cnt) {gic_init_physaddr(node);gic_of_setup_kvm_info(node);}if (parent) {irq = irq_of_parse_and_map(node, 0);gic_cascade_irq(gic_cnt, irq);}if (IS_ENABLED(CONFIG_ARM_GIC_V2M))gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain);gic_cnt++;return 0;
}

2.7.1.6 __gic_init_bases

  • set_handle_irq(gic_handle_irq); 用来设置handle_arch_irq = handle_irq
  • gic_init_chip(gic, NULL, name, true); 用来设置中断控制器的irq_chip
  • gic_init_bases(gic, irq_start, handle); 用来设置gic的处理函数
static int __init __gic_init_bases(struct gic_chip_data *gic,int irq_start,struct fwnode_handle *handle)
{char *name;int i, ret;if (WARN_ON(!gic || gic->domain))return -EINVAL;if (gic == &gic_data[0]) {/** Initialize the CPU interface map to all CPUs.* It will be refined as each CPU probes its ID.* This is only necessary for the primary GIC.*/for (i = 0; i < NR_GIC_CPU_IF; i++)gic_cpu_map[i] = 0xff;
#ifdef CONFIG_SMPset_smp_cross_call(gic_raise_softirq);
#endifcpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,"irqchip/arm/gic:starting",gic_starting_cpu, NULL);set_handle_irq(gic_handle_irq);if (static_branch_likely(&supports_deactivate_key))pr_info("GIC: Using split EOI/Deactivate mode\n");}if (static_branch_likely(&supports_deactivate_key) && gic == &gic_data[0]) {name = kasprintf(GFP_KERNEL, "GICv2");gic_init_chip(gic, NULL, name, true);} else {name = kasprintf(GFP_KERNEL, "GIC-%d", (int)(gic-&gic_data[0]));gic_init_chip(gic, NULL, name, false);}ret = gic_init_bases(gic, irq_start, handle);if (ret)kfree(name);return ret;
}

2.7.1.7 gic_init_chip

gic_init_chip用来初始化gic的irq_chip:

2.7.1.7.1 irq_chip的定义

对于gic v2 irq_chip的定义如下:

static const struct irq_chip gic_chip = {.irq_mask               = gic_mask_irq,.irq_unmask             = gic_unmask_irq,.irq_eoi                = gic_eoi_irq,.irq_set_type           = gic_set_type,.irq_get_irqchip_state  = gic_irq_get_irqchip_state,.irq_set_irqchip_state  = gic_irq_set_irqchip_state,.flags                  = IRQCHIP_SET_TYPE_MASKED |IRQCHIP_SKIP_SET_WAKE |IRQCHIP_MASK_ON_SUSPEND,
};

对于gic v3 irq_chip的定义如下:

static struct irq_chip gic_chip = {.name                   = "GICv3",.irq_mask               = gic_mask_irq,.irq_unmask             = gic_unmask_irq,.irq_eoi                = gic_eoi_irq,.irq_set_type           = gic_set_type,.irq_set_affinity       = gic_set_affinity,.irq_get_irqchip_state  = gic_irq_get_irqchip_state,.irq_set_irqchip_state  = gic_irq_set_irqchip_state,.flags                  = IRQCHIP_SET_TYPE_MASKED |IRQCHIP_SKIP_SET_WAKE |IRQCHIP_MASK_ON_SUSPEND,
};
2.7.1.7.2 gic_init_chip的定义
static void gic_init_chip(struct gic_chip_data *gic, struct device *dev,const char *name, bool use_eoimode1)
{/* Initialize irq_chip */gic->chip = gic_chip;gic->chip.name = name;gic->chip.parent_device = dev;if (use_eoimode1) {gic->chip.irq_mask = gic_eoimode1_mask_irq;gic->chip.irq_eoi = gic_eoimode1_eoi_irq;gic->chip.irq_set_vcpu_affinity = gic_irq_set_vcpu_affinity;}#ifdef CONFIG_SMPif (gic == &gic_data[0])gic->chip.irq_set_affinity = gic_set_affinity;
#endif
}

2.7.1.8 gic_init_bases

2.7.1.8.1 gic_init_bases

irq_domain的申请方式有三种,在gic_init_bases中用到了两种,irq_domain_add_legacy和irq_domain_add_linear,采用这两个接口处理的中断控制器一般是下级中断控制器和它是N:1的中断映射结构。
irq_domain_add_legacy和irq_domain_add_linear的区别为:irq_domain_add_legacy一次性分配完,而irq_domain_add_linear是等用到的时候再分配irq_desc。

static int gic_init_bases(struct gic_chip_data *gic, int irq_start,struct fwnode_handle *handle)
{irq_hw_number_t hwirq_base;int gic_irqs, irq_base, ret;if (IS_ENABLED(CONFIG_GIC_NON_BANKED) && gic->percpu_offset) {/* Frankein-GIC without banked registers... */unsigned int cpu;gic->dist_base.percpu_base = alloc_percpu(void __iomem *);gic->cpu_base.percpu_base = alloc_percpu(void __iomem *);if (WARN_ON(!gic->dist_base.percpu_base ||!gic->cpu_base.percpu_base)) {ret = -ENOMEM;goto error;}for_each_possible_cpu(cpu) {u32 mpidr = cpu_logical_map(cpu);u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);unsigned long offset = gic->percpu_offset * core_id;*per_cpu_ptr(gic->dist_base.percpu_base, cpu) =gic->raw_dist_base + offset;*per_cpu_ptr(gic->cpu_base.percpu_base, cpu) =gic->raw_cpu_base + offset;}gic_set_base_accessor(gic, gic_get_percpu_base);} else {/* Normal, sane GIC... */WARN(gic->percpu_offset,"GIC_NON_BANKED not enabled, ignoring %08x offset!",gic->percpu_offset);gic->dist_base.common_base = gic->raw_dist_base;gic->cpu_base.common_base = gic->raw_cpu_base;gic_set_base_accessor(gic, gic_get_common_base);}/** Find out how many interrupts are supported.* The GIC only supports up to 1020 interrupt sources.*/gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;gic_irqs = (gic_irqs + 1) * 32;if (gic_irqs > 1020)gic_irqs = 1020;gic->gic_irqs = gic_irqs;if (handle) {           /* DT/ACPI */gic->domain = irq_domain_create_linear(handle, gic_irqs,&gic_irq_domain_hierarchy_ops,gic);} else {                /* Legacy support *//** For primary GICs, skip over SGIs.* For secondary GICs, skip over PPIs, too.*/if (gic == &gic_data[0] && (irq_start & 31) > 0) {hwirq_base = 16;if (irq_start != -1)irq_start = (irq_start & ~31) + 16;} else {hwirq_base = 32;}gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,numa_node_id());if (irq_base < 0) {WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",irq_start);irq_base = irq_start;}gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base,hwirq_base, &gic_irq_domain_ops, gic);}if (WARN_ON(!gic->domain)) {ret = -ENODEV;goto error;}gic_dist_init(gic);ret = gic_cpu_init(gic);if (ret)goto error;ret = gic_pm_init(gic);if (ret)goto error;return 0;error:if (IS_ENABLED(CONFIG_GIC_NON_BANKED) && gic->percpu_offset) {free_percpu(gic->dist_base.percpu_base);free_percpu(gic->cpu_base.percpu_base);}return ret;
}
2.7.1.8.2 gic_irq_domain_hierarchy_ops
  • translate 用来解析设备树
  • alloc 用来设置irq_desc的irq_handler
static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {.translate = gic_irq_domain_translate,.alloc = gic_irq_domain_alloc,.free = irq_domain_free_irqs_top,
};
2.7.1.8.3 gic_irq_domain_translate
static int gic_irq_domain_translate(struct irq_domain *d,struct irq_fwspec *fwspec,unsigned long *hwirq,unsigned int *type)
{if (is_of_node(fwspec->fwnode)) {if (fwspec->param_count < 3)return -EINVAL;/* Get the interrupt number and add 16 to skip over SGIs */*hwirq = fwspec->param[1] + 16; /* 对于GIC_PPI中断类型来说需要在GIC_PPI中断号的基础上加16才是它的实际gic中断号*//** For SPIs, we need to add 16 more to get the GIC irq* ID number*/if (!fwspec->param[0])/*对于GIC_SPI中断来说,需要在GIC_PPI的基础上再加16才是它真是的gic中断号*/*hwirq += 16;*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;/* Make it clear that broken DTs are... broken */WARN_ON(*type == IRQ_TYPE_NONE);return 0;}if (is_fwnode_irqchip(fwspec->fwnode)) {if(fwspec->param_count != 2)return -EINVAL;*hwirq = fwspec->param[0];*type = fwspec->param[1];WARN_ON(*type == IRQ_TYPE_NONE);return 0;}return -EINVAL;
}
2.7.1.8.4 gic_irq_domain_alloc和gic_irq_domain_map函数
2.7.1.8.4.1 gic_irq_domain_alloc
static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,unsigned int nr_irqs, void *arg)
{int i, ret;irq_hw_number_t hwirq;unsigned int type = IRQ_TYPE_NONE;struct irq_fwspec *fwspec = arg;ret = gic_irq_domain_translate(domain, fwspec, &hwirq, &type);if (ret)return ret;for (i = 0; i < nr_irqs; i++) {ret = gic_irq_domain_map(domain, virq + i, hwirq + i);if (ret)return ret;}return 0;
}
2.7.1.8.4.2 gic_irq_domain_map

对于GIC_SPI中断设置irq_desc->handle_irq为handle_fasteoi_irq函数

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,irq_hw_number_t hw)
{struct gic_chip_data *gic = d->host_data;if (hw < 32) {irq_set_percpu_devid(irq);irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,handle_percpu_devid_irq, NULL, NULL);irq_set_status_flags(irq, IRQ_NOAUTOEN);} else {irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,handle_fasteoi_irq, NULL, NULL);irq_set_probe(irq);irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));}return 0;
}

2.7.2 GIC中断处理

2.7.2.1 gic_handle_irq

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{u32 irqstat, irqnr;struct gic_chip_data *gic = &gic_data[0];void __iomem *cpu_base = gic_data_cpu_base(gic);do {irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);irqnr = irqstat & GICC_IAR_INT_ID_MASK;if (likely(irqnr > 15 && irqnr < 1020)) {if (static_branch_likely(&supports_deactivate_key))writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);isb();handle_domain_irq(gic->domain, irqnr, regs);continue;}if (irqnr < 16) {writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);if (static_branch_likely(&supports_deactivate_key))writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP/** Ensure any shared data written by the CPU sending* the IPI is read after we've read the ACK register* on the GIC.** Pairs with the write barrier in gic_raise_softirq*/smp_rmb();handle_IPI(irqnr, regs);
#endifcontinue;}break;} while (1);
}

2.7.2.2 __handle_domain_irq

  • irq_enter(); 进入中断上下文
  • generic_handle_irq(irq); 处理中断
  • irq_exit(); 退出中断上下文,开始执行中断下半部相关的处理
/** Convert a HW interrupt number to a logical one using a IRQ domain,* and handle the result interrupt number. Return -EINVAL if* conversion failed. Providing a NULL domain indicates that the* conversion has already been done.*/
static inline int handle_domain_irq(struct irq_domain *domain,unsigned int hwirq, struct pt_regs *regs)
{return __handle_domain_irq(domain, hwirq, true, regs);
}
/*** __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain* @domain:     The domain where to perform the lookup* @hwirq:      The HW irq number to convert to a logical one* @lookup:     Whether to perform the domain lookup or not* @regs:       Register file coming from the low-level handling code** Returns:     0 on success, or -EINVAL if conversion has failed*/
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,bool lookup, struct pt_regs *regs)
{struct pt_regs *old_regs = set_irq_regs(regs);unsigned int irq = hwirq;int ret = 0;irq_enter();#ifdef CONFIG_IRQ_DOMAINif (lookup)irq = irq_find_mapping(domain, hwirq);
#endif/** Some hardware gives randomly wrong interrupts.  Rather* than crashing, do something sensible.*/if (unlikely(!irq || irq >= nr_irqs)) {ack_bad_irq(irq);ret = -EINVAL;} else {generic_handle_irq(irq);}irq_exit();set_irq_regs(old_regs);return ret;
}

2.7.2.3 GIC 中断处理函数

  • desc = irq_to_desc(irq)根据irq获取到对应的irq_desc
  • generic_handle_irq_desc(desc)根据获取到的对应中断的irq_desc去调用对应的handle_irqdesc->handle_irq(desc)
  • desc->handle_irq是在gic_irq_domain_map函数里面通过irq_domain_set_info函数注册的handle_fasteoi_irq 函数。
/*** generic_handle_irq - Invoke the handler for a particular irq* @irq:        The irq number to handle**/
int generic_handle_irq(unsigned int irq)
{struct irq_desc *desc = irq_to_desc(irq);if (!desc)return -EINVAL;generic_handle_irq_desc(desc);return 0;
}

2.7.2.4 irq_enter进入到gic中断处理的上下文

2.7.2.4.1 irq_enter
/** Enter an interrupt context.*/
void irq_enter(void)
{rcu_irq_enter();if (is_idle_task(current) && !in_interrupt()) {/** Prevent raise_softirq from needlessly waking up ksoftirqd* here, as softirq will be serviced on return from interrupt.*/local_bh_disable();tick_irq_enter();_local_bh_enable();}__irq_enter();
}
2.7.2.4.2 __irq_enter
/** It is safe to do non-atomic ops on ->hardirq_context,* because NMI handlers may not preempt and the ops are* always balanced, so the interrupted value of ->hardirq_context* will always be restored.*/
#define __irq_enter()                                   \do {                                            \account_irq_enter_time(current);        \preempt_count_add(HARDIRQ_OFFSET);      \trace_hardirq_enter();                  \} while (0)

2.7.2.5 irq_exit退出中断上下文

irq_exit会退出中断上半部的处理,转而去处理待处理的软中断。

  • preempt_count_sub(HARDIRQ_OFFSET);减少硬中断的计数
  • !in_interrupt() 表示当前不处于中断上下文中
  • local_softirq_pending()为真则表示当前有待处理的软中断
/** Exit an interrupt context. Process softirqs if needed and possible:*/
void irq_exit(void)
{#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLEDlocal_irq_disable();
#elselockdep_assert_irqs_disabled();
#endifaccount_irq_exit_time(current);preempt_count_sub(HARDIRQ_OFFSET);if (!in_interrupt() && local_softirq_pending())invoke_softirq();tick_irq_exit();rcu_irq_exit();trace_hardirq_exit(); /* must be last! */
}

2.7.3 gic下级中断的处理

2.7.3.1 handle_fasteoi_irq

  • mask_irq(desc);会调用在gic_init_chip函数中注册的irq_chip里的irq_mask函数
  • cond_unmask_eoi_irq(desc, chip);会调用在gic_init_chip函数中注册的irq_chip里的irq_eoi函数
  • handle_irq_event(desc)会去处理通过request_irq所注册的所有中断,既irq_desc->action
/***      handle_fasteoi_irq - irq handler for transparent controllers*      @desc:  the interrupt description structure for this irq**      Only a single callback will be issued to the chip: an ->eoi()*      call when the interrupt has been serviced. This enables support*      for modern forms of interrupt handlers, which handle the flow*      details in hardware, transparently.*/
void handle_fasteoi_irq(struct irq_desc *desc)
{struct irq_chip *chip = desc->irq_data.chip;raw_spin_lock(&desc->lock);if (!irq_may_run(desc))goto out;desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);/** If its disabled or no action available* then mask it and get out of here:*/if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {desc->istate |= IRQS_PENDING;mask_irq(desc);goto out;}kstat_incr_irqs_this_cpu(desc);if (desc->istate & IRQS_ONESHOT)mask_irq(desc);preflow_handler(desc);handle_irq_event(desc);cond_unmask_eoi_irq(desc, chip);raw_spin_unlock(&desc->lock);return;
out:if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))chip->irq_eoi(&desc->irq_data);raw_spin_unlock(&desc->lock);
}

2.7.3.2 handle_irq_event

handle_irq_event处理的都是通过request_irq所注册的中断处理函数

调用关系:
|- handle_fasteoi_irq|- handle_irq_event|- handle_irq_event_percpu|- __handle_irq_event_percpu|- action->handler(irq, action->dev_id);

2.8 中断下半部

2.8.1 软中断

2.8.1.1 软中断的概念以及类型

2.8.1.1.1 软中断的概念

软中断是一种软件实现的机制,而非硬件实现的中断。软中断属于中断上下文,当软中断在执行时,task无法打断软中断执行。

  • 软中断的类型是静态定义的,内核不建议新增加软中断类型
  • 软中断的回调函数是在开中断的情况下执行的
  • 软中断的执行点:在硬中断处理函数返回之前irq_exit()函数中会去检查是否有软中断需要处理
  • 软中断属于中断上下文,软中断可以抢占进程上下文
  • 同一类型的软中断可以在多个处理器上并行执行
  • tasklet属于一种特殊的软中断,相同的tasklet在整个系统上只有一个可以执行,但是不同的tasklet可以同时在不同的处理器上运行(tasklet is running only on one CPU simultaneously,different tasklets may be run simultaneously on different CPUs.)
2.8.1.1.2 软中断的类型
/* PLEASE, avoid to allocate new softirqs, if you need not _really_ highfrequency threaded job scheduling. For almost all the purposestasklets are more than enough. F.e. all serial device BHs etal. should be converted to tasklets, not to softirqs.*/enum
{HI_SOFTIRQ=0,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,BLOCK_IOPOLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on thenumbering. Sigh! */RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */NR_SOFTIRQS
};

2.8.1.2 软中断执行的时机

中断处理函数执行完成,返回中断中断现场之前(irq_exit)会去检查:

  • 检查当前是否处于中断上下文in_interrupt()
  • 检查系统是否有待处理的软中断local_softirq_pending()
  • 调用invoke_softirq()去处理软中断
  • 如果当前不处于中断上下文并且有待处理的软中断,则会调用invoke_softirq()去处理软中断。

2.8.1.3 irq_exit

irq_exit会退出中断上半部的处理,转而去处理待处理的软中断。

  • preempt_count_sub(HARDIRQ_OFFSET);减少硬中断的计数
  • !in_interrupt() 表示当前不处于中断上下文中
  • local_softirq_pending()为真则表示当前有待处理的软中断
/** Exit an interrupt context. Process softirqs if needed and possible:*/
void irq_exit(void)
{#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLEDlocal_irq_disable();
#elselockdep_assert_irqs_disabled();
#endifaccount_irq_exit_time(current);preempt_count_sub(HARDIRQ_OFFSET);if (!in_interrupt() && local_softirq_pending())invoke_softirq();tick_irq_exit();rcu_irq_exit();trace_hardirq_exit(); /* must be last! */
}

2.8.1.4 invoke_softirq

static inline void invoke_softirq(void)
{if (!force_irqthreads) {#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK/** We can safely execute softirq on the current stack if* it is the irq stack, because it should be near empty* at this stage.*/__do_softirq();
#else/** Otherwise, irq_exit() is called on the task stack that can* be potentially deep already. So call softirq in its own stack* to prevent from any overrun.*/do_softirq_own_stack();
#endif} else {wakeup_softirqd();}
}

2.8.1.5 __do_softirq

__do_softirq函数需要重点关注一下几个部分:

  • pending = local_softirq_pending();获取有哪些软中断被置位
  • h = softirq_vec;
  • h += softirq_bit - 1;
  • h->action(h);
  • 当中断不满足跳出条件时,会一直执行goto restart去执行软中断处理函数;软中断退出的条件如下所示:
    • time_before(jiffies, end) /* end = jiffies + MAX_SOFTIRQ_TIME 软中断允许的最长占用时间为2s */
    • !need_resched() /* 检查TIF_NEED_RESCHED*/
    • –max_restart /* max_restart = MAX_SOFTIRQ_RESTART; 调度次数最多为MAX_SOFTIRQ_RESTART */
  • 当退出软中断时还有需要处理的软中断则会通过调用wakeup_softirqd()函数去唤醒softirqd线程去处理剩余的软中断。
asmlinkage __visible void __softirq_entry __do_softirq(void)
{unsigned long end = jiffies + MAX_SOFTIRQ_TIME;unsigned long old_flags = current->flags;int max_restart = MAX_SOFTIRQ_RESTART;struct softirq_action *h;bool in_hardirq;__u32 pending;int softirq_bit;/** Mask out PF_MEMALLOC s current task context is borrowed for the* softirq. A softirq handled such as network RX might set PF_MEMALLOC* again if the socket is related to swap*/current->flags &= ~PF_MEMALLOC;pending = local_softirq_pending();account_irq_enter_time(current);__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);in_hardirq = lockdep_softirq_start();restart:/* Reset the pending bitmask before enabling irqs */set_softirq_pending(0);local_irq_enable();h = softirq_vec;while ((softirq_bit = ffs(pending))) {unsigned int vec_nr;int prev_count;h += softirq_bit - 1;vec_nr = h - softirq_vec;prev_count = preempt_count();kstat_incr_softirqs_this_cpu(vec_nr);trace_softirq_entry(vec_nr);h->action(h);trace_softirq_exit(vec_nr);if (unlikely(prev_count != preempt_count())) {pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",vec_nr, softirq_to_name[vec_nr], h->action,prev_count, preempt_count());preempt_count_set(prev_count);}h++;pending >>= softirq_bit;}rcu_bh_qs();local_irq_disable();pending = local_softirq_pending();if (pending) {        /* 存在软中断需要处理 */if (time_before(jiffies, end) && !need_resched() &&        /* 软中断调度时间最长为2s, 当前系统不需要调度并且最大执行次数没有超过MAX_SOFTIRQ_RESTART的限制 */--max_restart)goto restart;        /* 继续处理剩余的软中断 */wakeup_softirqd();        /* 剩余尚未来的及处理的软中断通过唤醒softirqd线程去处理 */}lockdep_softirq_end(in_hardirq);account_irq_exit_time(current);__local_bh_enable(SOFTIRQ_OFFSET);WARN_ON_ONCE(in_interrupt());tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}

2.8.1.6 注册软中断

代码路径:kernel/softirq.c

2.8.1.6.1 软中断注册函数定义
void open_softirq(int nr, void (*action)(struct softirq_action *))
{softirq_vec[nr].action = action;
}
2.8.1.6.2 软中断注册样例

示例代码路径:net/core/dev.c

/**      Initialize the DEV module. At boot time this walks the device list and*      unhooks any devices that fail to initialise (normally hardware not*      present) and leaves us with a valid list of present and active devices.**/
/**       This is called single threaded during boot, so no need*       to take the rtnl semaphore.*/
static int __init net_dev_init(void)
{...open_softirq(NET_TX_SOFTIRQ, net_tx_action);open_softirq(NET_RX_SOFTIRQ, net_rx_action);...
}

2.8.1.7 触发软中断

触发软中断其实是在中断中通过调用raise_softirq_irqoff或者raise_softirq设置_softirq_pending位图,当从异常返回执行到irq_exit()函数时,会检查_softirq_pending是否有设置的软中断待处理

2.8.1.7.1 触发软中断处理的接口
/** This function must run with irqs disabled!*/
inline void raise_softirq_irqoff(unsigned int nr)
{__raise_softirq_irqoff(nr);/** If we're in an interrupt or softirq, we're done* (this also catches softirq-disabled code). We will* actually run the softirq once we return from* the irq or softirq.** Otherwise we wake up ksoftirqd to make sure we* schedule the softirq soon.*/if (!in_interrupt())wakeup_softirqd();
}void raise_softirq(unsigned int nr)
{unsigned long flags;local_irq_save(flags);raise_softirq_irqoff(nr);local_irq_restore(flags);
}
2.8.1.7.2 触发软中断处理的示例

在触发软中断处理时,通常会使用raise_softirq_irqoff和raise_softirq函数

   6     51  block/blk-softirq.c <<<unknown>>>raise_softirq_irqoff(BLOCK_SOFTIRQ);7     94  block/blk-softirq.c <<<unknown>>>raise_softirq_irqoff(BLOCK_SOFTIRQ);8    148  block/blk-softirq.c <<<unknown>>>raise_softirq_irqoff(BLOCK_SOFTIRQ);12    784  drivers/irqchip/irq-gic.c <<<unknown>>>static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)13   1157  drivers/irqchip/irq-gic.c <<<unknown>>>set_smp_cross_call(gic_raise_softirq);14    279  drivers/irqchip/irq-hip04.c <<<unknown>>>static void hip04_raise_softirq(const struct cpumask *mask, unsigned int irq)16   2288  drivers/net/wireless/rockchip_wlan/cywdhd/bcmdhd/dhd_linux.c <<<unknown>>>* This function will essentially invoke __raise_softirq_irqoff(NET_RX_SOFTIRQ)17    131  drivers/net/wireless/rockchip_wlan/cywdhd/bcmdhd/include/linuxver.h <<<unknown>>>cpu_raise_softirq(smp_processor_id(), NET_RX_SOFTIRQ)18    123  drivers/net/wireless/rockchip_wlan/cywdhd/bcmdhd/wl_iw.c <<<unknown>>>cpu_raise_softirq(smp_processor_id(), NET_RX_SOFTIRQ)19   1025  drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd/dhd_linux_lb.c <<<unknown>>>* This function will essentially invoke __raise_softirq_irqoff(NET_RX_SOFTIRQ)20   1042  drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd/dhd_linux_lb.c <<<unknown>>>raise_softirq(NET_RX_SOFTIRQ);

2.8.2 tasklet

tasklet是一种特殊的软中断TASKLET_SOFTIRQ和HI_SOFTIRQ

2.8.2.1 tasklet数据结构

/* Tasklets --- multithreaded analogue of BHs.Main feature differing them of generic softirqs: taskletis running only on one CPU simultaneously.Main feature differing them of BHs: different taskletsmay be run simultaneously on different CPUs.Properties:* If tasklet_schedule() is called, then tasklet is guaranteedto be executed on some cpu at least once after this.* If the tasklet is already scheduled, but its execution is still notstarted, it will be executed only once.* If this tasklet is already running on another CPU (or schedule is calledfrom tasklet itself), it is rescheduled for later.* Tasklet is strictly serialized wrt itself, but notwrt another tasklets. If client needs some intertask synchronization,he makes it with spinlocks.*/struct tasklet_struct
{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};

2.8.2.2 声明tasklet

2.8.2.2.1 静态定义
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
2.8.2.2.2 动态定义接口
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
{t->next = NULL;t->state = 0;atomic_set(&t->count, 0);t->func = func;t->data = data;
}
2.8.2.2.3 tasklet使用样例
   1   1843  drivers/atm/eni.c <<<unknown>>>tasklet_init(&eni_dev->task,eni_tasklet,(unsigned long) dev);2   2043  drivers/atm/fore200e.c <<<unknown>>>tasklet_init(&fore200e->tx_tasklet, fore200e_tx_tasklet, (unsigned long)fore200e);3   2044  drivers/atm/fore200e.c <<<unknown>>>tasklet_init(&fore200e->rx_tasklet, fore200e_rx_tasklet, (unsigned long)fore200e);4    386  drivers/atm/he.c <<<unknown>>>tasklet_init(&he_dev->tasklet, he_tasklet, (unsigned long) he_dev);5   1304  drivers/atm/solos-pci.c <<<unknown>>>tasklet_init(&card->tlet, solos_bh, (unsigned long)card);6    903  drivers/block/umem.c <<<unknown>>>tasklet_init(&card->tasklet, process_page, (unsigned long)card);7    986  drivers/block/xsysace.c <<<unknown>>>tasklet_init(&ace->fsm_tasklet, ace_fsm_tasklet, (unsigned long)ace);8   2820  drivers/char/ipmi/ipmi_msghandler.c <<<unknown>>>tasklet_init(&intf->recv_tasklet,9    838  drivers/char/mmtimer.c <<<unknown>>>tasklet_init(&timers[node].tasklet, mmtimer_tasklet,10   1198  drivers/crypto/amcc/crypto4xx_core.c <<<unknown>>>tasklet_init(&core_dev->tasklet, crypto4xx_bh_tasklet_cb,11   1357  drivers/crypto/atmel-aes.c <<<unknown>>>tasklet_init(&aes_dd->done_task, atmel_aes_done_task,12   1359  drivers/crypto/atmel-aes.c <<<unknown>>>tasklet_init(&aes_dd->queue_task, atmel_aes_queue_task,13   1370  drivers/crypto/atmel-sha.c <<<unknown>>>tasklet_init(&sha_dd->done_task, atmel_sha_done_task,14   1378  drivers/crypto/atmel-tdes.c <<<unknown>>>tasklet_init(&tdes_dd->done_task, atmel_tdes_done_task,15   1380  drivers/crypto/atmel-tdes.c <<<unknown>>>tasklet_init(&tdes_dd->queue_task, atmel_tdes_queue_task,16    587  drivers/crypto/bfin_crc.c <<<unknown>>>tasklet_init(&crc->done_task, bfin_crypto_crc_done_task, (unsigned long)crc);
2.8.2.2.4 调度一个tasklet

__tasklet_schedule会通过调用raise_softirq_irqoff(TASKLET_SOFTIRQ);或者raise_softirq_irqoff(HI_SOFTIRQ);去设置_softirq_pending,以表示有tasklet待处理

void __tasklet_schedule(struct tasklet_struct *t)
{unsigned long flags;local_irq_save(flags);t->next = NULL;*__this_cpu_read(tasklet_vec.tail) = t;__this_cpu_write(tasklet_vec.tail, &(t->next));raise_softirq_irqoff(TASKLET_SOFTIRQ);local_irq_restore(flags);
}
EXPORT_SYMBOL(__tasklet_schedule);

2.8.3 工作队列workqueue

工作队列是和软中断或者tasklet不同的一种下半部机制。
工作队列将工作推迟,交给内核线程执行(所以工作队列总是运行在进程上下文中)。
工作队列的这种实现可以很好的利用进程上下文的优势,最重要的就是可以睡眠也可以被调度。

2.8.3.1 workqueue数据结构

struct work_struct {atomic_long_t data;struct list_head entry;work_func_t func;
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};

2.8.3.2 创建workqueue以及使用样例:

#define DECLARE_WORK(n, f)                                              \struct work_struct n = __WORK_INITIALIZER(n, f)
样例:
static DECLARE_WORK(aer_recover_work, aer_recover_work_func);
INIT_WORK(&hu->init_ready, hci_uart_init_work);
INIT_WORK(&hu->write_work, hci_uart_write_work);

2.8.3.3 调度workqueue以及使用样例:

schedule_work(&work);
schedule_delayed_work(&work, delay);
样例:
schedule_work(&aer_recover_work);
schedule_work(&hu->write_work);
schedule_work(&hu->init_ready);

参考文献:

书籍:

  • ARM_v8_architecture_Programmer Guide v1.0.pdf
  • armv8_arm_v8.6.pdf
  • gic400_r0p1_trm.pdf

博文:

  • 中断子系统
  • linux AArch64中断下半部之软中断softirq
  • linux ARM64 中断底层处理代码分析

linux 中断子系统相关推荐

  1. Linux中断子系统-通用框架处理

    背景 Kernel版本:4.14 ARM64处理器,Contex-A53,双核 使用工具:Source Insight 3.5, Visio 1. 概述 <Linux中断子系统(一)-中断控制器 ...

  2. 漫画-Linux中断子系统综述

    1.中断引发的面试教训 2.什么是中断? 中断: (英语:Interrupt)指当出现需要时,CPU暂时停止当前程序的执行转而执行处理新情况的程序和执行过程. 即在程序运行过程中,系统出现了一个必须由 ...

  3. Linux中断子系统

    首先感谢原文作者 LoyenWang 的分享,可以点击章节阅读原作者原文,或者查看本文的转载地址,再次感谢原作者分享,已经在公众号上征得作者同意. 说明: Kernel版本:4.14 ARM64处理器 ...

  4. linux中断子系统(基于imx6ul arm32分析)

    0.说明 本文主要针对linux内核中断整个框架进行梳理,针对的是armv7架构,硬件平台是imx6ul,基于arm GIC控制器来分析. GIC是arm公司设计使用的中断控制器,全称Global I ...

  5. Linux中断子系统(一)中断控制器GIC架构

    Linux中断子系统(一)中断控制器GIC架构 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客: Linux中断子系统(一)中 ...

  6. Linux中断子系统(二)中断控制器GIC驱动分析

    Linux中断子系统(二)中断控制器GIC驱动分析 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客: Linux中断子系统(一 ...

  7. Linux中断子系统---中断申请request_irq()与中断线程化request_threaded_irq()

    一.申请中断request_irq() Linux中使用中断需要先进行申请,申请中断的API函数如下: int request_irq(unsigned int irq,irq_handler_t h ...

  8. 浅入浅出linux中断子系统

    浅入浅出linux中断子系统,如需深入,直接跳转重要参考章节. 什么是中断? 当CPU被某些信号触发,CPU暂停当前工作,转而处理信号的事件,简单的称它为中断,这个信号可以是系统外设的信号,也可能是芯 ...

  9. linux - 中断子系统分析(1) -- GICv3硬件架构

    目录 1. 参考文档 2. GIC Version History 3. Interrup Types 4. Interrupt State Machine 5. Programmer's Model ...

最新文章

  1. 重新启动C++Builder
  2. 【必看】新手妹子一键删库,老司机机智救场
  3. windows常用技巧
  4. Hive - HWI 简单使用
  5. java valueof null,String.valueOf(null) 遇到的坑
  6. 给C#的oracle绿色版
  7. lucene 分词相关的类
  8. Java Socket实战之六 使用NIO包实现Socket通信
  9. Spring的IoC容器实现原理(一)#loadBeanDefinition
  10. 设计模式(Design Patterns)总结归纳
  11. 【重识云原生】第三章云存储第一节——分布式云存储总述
  12. python pymysql multiprocessing.dummy多线程 读写数据库报错
  13. Android性能优化的问题
  14. 计算机编程专业的民办大学排名,法国计算机编程专业大学排名(2020年USNEWS)_快飞留学...
  15. python快速开发app_python 使用Airtest超快速开发App爬虫
  16. 【从本人QQ空间迁移】重构“依恋情结”(以黑名单的新增编辑为例)
  17. 微信小程序 18 播放记录和video页面初步搭建
  18. Android-四大天王
  19. 导出word如何默认打开为页面视图
  20. QuantLib 金融计算——基本组件之天数计算规则详解

热门文章

  1. ArcGIS Pro简介
  2. C# chart1 添加滚动条的缩放 鼠标滚轮控制缩放
  3. iOS开发——keychain的使用
  4. Matlab一个错误引发的血案:??? Error using == str2num Requires string or character array input....
  5. 打印机定影膜引起的诡异故障
  6. 沉浮70年,人工智能2018年将走向何方?
  7. html输入密码访问指定页面,三种方法使HTML单页面输入密码才能访问
  8. (二)WebService之调用soap服务
  9. SDN:简述对各类SDN控制器的认识
  10. word2vec模型原理(附python实现代码)