前言

最近计划学习一下cpu虚拟化相关内容,其中一个重要内容就是中断虚拟化。

在学习中断虚拟化之前,首先需要对中断有基本的了解认识,虽然之前在linux内核硬中断分析中有了解,但比较片面,这里对中断会进行更深入的学习

中断硬件

本质上,内核是操作系统控制硬件的接口,其逻辑实现紧密遵循硬件规范。这种硬件强相关的特性,使得研究cpu的中断硬件机制成为理解内核中断子系统的关键点。

一切的核心是CPU的IDT(Interrupt Descriptor Table)机制。其规定了每个中断需要有一个对应的中断向量号。CPU内部有一个专用的系统寄存器IDTR(Interrupt Descriptor Table Register),记录了IDT在内存中的地址。IDT最多有256个条目,每个条目描述了一个中断处理逻辑的地址,中断向量号就是IDT的索引。

因此CPU收到中断控制器发送的中断后,根据中断的中断向量号根据IDTR从IDT中找到该中断的回调函数的地址并执行,如下所示

中断处理流程

而中断控制器则是将中断源触发的中断按照CPU的格式要求投递给CPU,从而触发上述IDT机制进行处理。下面介绍一下cpu的中断控制器的发展

pic(Programmable Interrupt Controller)

pic,也就是8259A,其是最早的cpu的中断管理芯片,其实物图和结构图如下所示
8259A实物图
8259A结构图

中断设置

按照8259A的手册,一般通过pio的方式完成ICW(Initialization Command Words)和OCW(Operation Command Words)寄存器的读取和设置,从而完成中断触发方式、中断向量基址、中断状态等8259A工作状态的查询和设置,这里不详细介绍了,感兴趣可以查看8259A的手册

中断投递

根据8259A的手册,一个典型的中断投递流程如下所示

  1. 一个或多个IR引脚发送中断信号(触发方式由ICW1配置),对应的IRR(Interrupt Request Register)中的相关bit位锁存请求状态(直到后续被清除)
  2. PR(Priority Resolver)根据中断优先级(OCW2配置)和IMR(Interrupt Mask Register)评估所有的中断请求。如果存在有效请求,并通过INT引脚向CPU发送中断请求信号投递中断
  3. CPU检测到INT信号后,在当前指令执行完毕后发送第一个INTA脉冲(8259A手册规定的电信号)
  4. 在第一个INTA周期内,8259A会在ISR(Interrupt Service Register)中将当前IRR中最高优先级的IR对应的位进行标记,并清除IRR中相应的bit位标志
  5. 在第二个INTA周期内,8259A会通过数据总线向CPU发送8位中断类型码(ICW2配置基地址+IRQ编号)
  6. 最后,根据ICW4配置的模式结束此次中断处理(AEOI模式会自动清除ISR对应的位;否则等到CPU发送EOI命令再清除ISR对应的位)

apic(Advanced Programmable Interrupt Controller)

前面提到的pic只适用于单处理器的中断投递,而对于当前的多CPU,则需要新的中断控制器,即apic。

apic由lapic(local apic)和ioapic(I/O apic)构成,其整体结构图如下所示
apic整体结构图

整体上,lapic类似于每个CPU的8259A,用于处理其所在的CPU的中断投递;而ioapic则是用来负责统一接受外部设备的中断请求,并重新分配给给不同CPU的lapic

lapic

lapic的主要功能就是接受中断消息或是自身/其他lapic产生的中断,然后投递到对应的CPU,其整体结构图如下所示
lapic结构图

可以看到,其基本和8259A很相似,包括ISR、IRR等,但又新增了很多结构用来实现新增的功能,因此其设置和中断投递流程大体相似,但又有区别

中断设置

按照apic的手册,lapic的寄存器被默认被映射到起始地址为0xfee00000的连续4KB的物理内存中,可通过IA32_APIC_BASE MSR(Model Specific Register)更改基址。寄存器通过mmio方式进行读取和设置,具体的寄存器布局如下所示
lapic寄存器布局

中断处理

lapic可以投递三种中断:本地中断(诸如apic timer generated interrupts等)、IPI(Inter-Processor Interrupts)和ioapic发送的中断。lapic投递这些中断遵循下述流程

  1. 过滤中断目的地是自己的中断(本地中断目的地始终是自己,IPI和ioapic发送的中断需要根据中断的destination字段判断)
  2. 如果中断的delivery Mode是NMI(Non-Maskable Interrupt)、SMI(System Management Interrupt)、INIT、ExtINT和SIPI(Start-up IPI),则直接通过相关引脚将中断发送给CPU,完成此次中断投递
  3. 对于其他delivery Mode的中断,根据中断号将IRR对应的位进行标记(直到后续被清除)
  4. lapic基于PPR(Processor Priority Register)评估IRR中所有的中断请求。如果存在有效请求,清除IRR中相应的bit位,标记ISR中相应的bit位,并通过相关引脚将中断发送给CPU
  5. CPU在处理完中断后,向EOIR(End-Of-Interrupt Register)写入
  6. lapic清除ISR中相应的bit位,并根据SVR(Spurious Vector Register)内存可能会通过总线向所有ioapic广播EOI

ioapic

ioapic负责接收外部I/O设备的硬件中断,并将其转换成一定格式的消息,通过总线发送给一个或多个lapic,其相关示意图如下所示

ioapic引脚图
ioapic结构图

中断设置

和lapic一样,ioapic有大量用于配置/读取工作状态的寄存器。但不同的是,ioapic没有将所有的寄存器直接映射到物理内存上;其仅仅映射了两个寄存器:位于0xfec0xy00(x和y可通过APICBASE寄存器配置)的IOREGSEL(I/O Register Select),用于要访问的ioapic的寄存器编号;位于0xfec0xy10的IOWIN(I/O Window Register),用于读写选择的寄存器内容

其中最重要的是IOREDTBL(I/O Redirection Table Registers),其是一张24项的中断重定向表,每项是一个64位的寄存器,对应着ioapic的一个中断引脚的中断重定向配置信息,包含中断向量、中断的destination、中断的delivery mode等内容

中断处理

ioapic的中断处理很简单,每当其收到外部I/O设备通过中断引脚发送的中断信号后,其会根据中断引脚对应的IOREDTBL中的重定向表项格式化出一条消息,并通过总线发送给destination字段指定的lapic

msi(Message Signaled Interrupt)

在早期的计算机架构中,外设硬件中断主要通过ioapic转发至各cpu的lapic。但随着pci/pcie设备呈指数级增长,传统中断机制暴露出诸多局限性。

为此,pci规范在2.2版本引入了msi(Message Signaled Interrupt)机制,其让pci设备通过pci总线存储器写事务,将特定的消息地址(message address)和消息数据(message data)直接写入内存映射的lapic寄存器中,从而绕过ioapic直接向lapic发送中断通知,其整体的架构图如下所示(具体硬件连线细节不一定准确,主要表现绕过ioapic)

msi整体结构图

中断设置

pci的msi中断的设置是通过pci的配置空间实现的,前面博客qemu的PCI设备中简单介绍过,其配置空间如下所示
pci的配置空间

其中,msi的配置通过msi capability/msix capability实现

msi capability

msi capability的结构如下所示

msi_capability结构

可以看到,其有多个字段,但重点是Message Address(Message Upper Address)和Message Data字段

其中Message Address的结构如下所示,其定义了pci设备发出中断时内存写TLP的目的地址
Message Address结构

而Message Data则定义了写入的内容,用来描述中断的相关信息,其结构如下所示
Message Data结构

msix capability

msix capability的结构如下所示

msix_capability结构

与msi capability不同的是,其使用一个数组存放Message Address字段和Message Data字段,数组结构如下所示

msix table

中断处理

pci的中断处理遵循下述流程

  1. 当pci设备需要发起中断请求时,pci硬件会向capability中设置的Message Address地址写Message Data数据,其会以存储器写TLP的形式发送到RC(Root Complex)
  2. RC收到后,将其转换为interrupt message总线事务并广播,与ioapic操作类似

中断子系统

中断子系统负责统一管理硬件中断资源,其接收并路由硬件中断信号,将其转换为对应的虚拟中断号,并调用注册的中断服务例程

中断向量

中断向量,即CPU真正接受并进行处理的东西(x86架构下即前面中断硬件介绍的IDT机制)

不同架构下中断触发的CPU硬件行为是不一致的,因此其会有不一样的硬件中断管理机制,这里以x86平台为例进行分析

根据前面中断硬件的介绍,内核会在系统初始化阶段配置中断控制器的中断向量信息。当硬件发起中断时,中断控制器会按照设置投递对应的中断向量,处理器会接收包含中断对应中断向量的信号,并将该中断向量作为索引值,访问IDT中对应的门描述符从而进行处理。

中断子系统会在idt_setup_apic_and_irq_gates中初始化该IDT,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//#0  idt_setup_apic_and_irq_gates () at arch/x86/kernel/idt.c:282
//#1 0xffffffff832a7262 in native_init_IRQ () at arch/x86/kernel/irqinit.c:103
//#2 0xffffffff8329ab9b in start_kernel () at init/main.c:977
//#3 0xffffffff832a59a8 in x86_64_start_reservations (real_mode_data=real_mode_data@entry=0x14770 <entry_stack_storage+1904> <error: Cannot access memory at address 0x14770>) at arch/x86/kernel/head64.c:507
//#4 0xffffffff832a5ae6 in x86_64_start_kernel (real_mode_data=0x14770 <entry_stack_storage+1904> <error: Cannot access memory at address 0x14770>) at arch/x86/kernel/head64.c:488
//#5 0xffffffff810a96f6 in secondary_startup_64 () at arch/x86/kernel/head_64.S:420
//#6 0x0000000000000000 in ?? ()
void __init idt_setup_apic_and_irq_gates(void)
{
int i = FIRST_EXTERNAL_VECTOR;
void *entry;

idt_setup_from_table(idt_table, apic_idts, ARRAY_SIZE(apic_idts), true);

for_each_clear_bit_from(i, system_vectors, FIRST_SYSTEM_VECTOR) {
entry = irq_entries_start + IDT_ALIGN * (i - FIRST_EXTERNAL_VECTOR);
set_intr_gate(i, entry);
}

for_each_clear_bit_from(i, system_vectors, NR_VECTORS) {
/*
* Don't set the non assigned system vectors in the
* system_vectors bitmap. Otherwise they show up in
* /proc/interrupts.
*/
entry = spurious_entries_start + IDT_ALIGN * (i - FIRST_SYSTEM_VECTOR);
set_intr_gate(i, entry);
}
...
}

可以看到,其设置中断向量从FIRST_EXTERNAL_VECTORFIRST_SYSTEM_VECTOR之间的中断的入口函数为irq_entries_start所在的代码段中;FIRST_SYSTEM_VECTOR及以上的中断的入口函数为spurious_entries_start所在的代码段中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
* ASM code to emit the common vector entry stubs where each stub is
* packed into IDT_ALIGN bytes.
*
* Note, that the 'pushq imm8' is emitted via '.byte 0x6a, vector' because
* GCC treats the local vector variable as unsigned int and would expand
* all vectors above 0x7F to a 5 byte push. The original code did an
* adjustment of the vector number to be in the signed byte range to avoid
* this. While clever it's mindboggling counterintuitive and requires the
* odd conversion back to a real vector number in the C entry points. Using
* .byte achieves the same thing and the only fixup needed in the C entry
* point is to mask off the bits above bit 7 because the push is sign
* extending.
*/
.align IDT_ALIGN
SYM_CODE_START(irq_entries_start)
vector=FIRST_EXTERNAL_VECTOR
.rept NR_EXTERNAL_VECTORS
UNWIND_HINT_IRET_REGS
0 :
ENDBR
.byte 0x6a, vector
jmp asm_common_interrupt
/* Ensure that the above is IDT_ALIGN bytes max */
.fill 0b + IDT_ALIGN - ., 1, 0xcc
vector = vector+1
.endr
SYM_CODE_END(irq_entries_start)

.align IDT_ALIGN
SYM_CODE_START(spurious_entries_start)
vector=FIRST_SYSTEM_VECTOR
.rept NR_SYSTEM_VECTORS
UNWIND_HINT_IRET_REGS
0 :
ENDBR
.byte 0x6a, vector
jmp asm_spurious_interrupt
/* Ensure that the above is IDT_ALIGN bytes max */
.fill 0b + IDT_ALIGN - ., 1, 0xcc
vector = vector+1
.endr
SYM_CODE_END(spurious_entries_start)

这些代码段会push对应的中断向量,并调用asm_common_interrupt/asm_spurious_interrupt函数。

这里asm_common_inerrupt/asm_spurious_interruptDECLARE_IDTENTRY_IRQ宏在arch/x86/entry/entry_64.S文件中定义,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
DECLARE_IDTENTRY_IRQ(X86_TRAP_OTHER,	common_interrupt);

#ifndef __ASSEMBLY__
...
#else /* !__ASSEMBLY__ */

/* Entries for common/spurious (device) interrupts */
#define DECLARE_IDTENTRY_IRQ(vector, func) \
idtentry_irq vector func
#endif

/*
* Interrupt entry/exit.
*
+ The interrupt stubs push (vector) onto the stack, which is the error_code
* position of idtentry exceptions, and jump to one of the two idtentry points
* (common/spurious).
*
* common_interrupt is a hotpath, align it to a cache line
*/
.macro idtentry_irq vector cfunc
.p2align CONFIG_X86_L1_CACHE_SHIFT
idtentry \vector asm_\cfunc \cfunc has_error_code=1
.endm

/**
* idtentry - Macro to generate entry stubs for simple IDT entries
* @vector: Vector number
* @asmsym: ASM symbol for the entry point
* @cfunc: C function to be called
* @has_error_code: Hardware pushed error code on stack
*
* The macro emits code to set up the kernel context for straight forward
* and simple IDT entries. No IST stack, no paranoid entry checks.
*/
.macro idtentry vector asmsym cfunc has_error_code:req
SYM_CODE_START(\asmsym)

.if \vector == X86_TRAP_BP
/* #BP advances %rip to the next instruction */
UNWIND_HINT_IRET_ENTRY offset=\has_error_code*8 signal=0
.else
UNWIND_HINT_IRET_ENTRY offset=\has_error_code*8
.endif

ENDBR
ASM_CLAC
cld

.if \has_error_code == 0
pushq $-1 /* ORIG_RAX: no syscall to restart */
.endif

.if \vector == X86_TRAP_BP
/*
* If coming from kernel space, create a 6-word gap to allow the
* int3 handler to emulate a call instruction.
*/
testb $3, CS-ORIG_RAX(%rsp)
jnz .Lfrom_usermode_no_gap_\@
.rept 6
pushq 5*8(%rsp)
.endr
UNWIND_HINT_IRET_REGS offset=8
.Lfrom_usermode_no_gap_\@:
.endif

idtentry_body \cfunc \has_error_code

_ASM_NOKPROBE(\asmsym)
SYM_CODE_END(\asmsym)
.endm

/**
* idtentry_body - Macro to emit code calling the C function
* @cfunc: C function to be called
* @has_error_code: Hardware pushed error code on stack
*/
.macro idtentry_body cfunc has_error_code:req

/*
* Call error_entry() and switch to the task stack if from userspace.
*
* When in XENPV, it is already in the task stack, and it can't fault
* for native_iret() nor native_load_gs_index() since XENPV uses its
* own pvops for IRET and load_gs_index(). And it doesn't need to
* switch the CR3. So it can skip invoking error_entry().
*/
ALTERNATIVE "call error_entry; movq %rax, %rsp", \
"call xen_error_entry", X86_FEATURE_XENPV

ENCODE_FRAME_POINTER
UNWIND_HINT_REGS

movq %rsp, %rdi /* pt_regs pointer into 1st argument*/

.if \has_error_code == 1
movq ORIG_RAX(%rsp), %rsi /* get error code into 2nd argument*/
movq $-1, ORIG_RAX(%rsp) /* no syscall to restart */
.endif

call \cfunc

/* For some configurations \cfunc ends up being a noreturn. */
REACHABLE

jmp error_return
.endm
...

DECLARE_IDTENTRY_IRQ宏,其使用idtentry定义和实现asm_common_interrupt/asm_spurious_interrupt,最终会调用common_interrupt/spurious_interrupt进行处理。

commont_interrupt/spurious_interrupt是由DEFINE_IDTENTRY_IRQ宏在arch/x86/kernel/irq.c中实现,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#ifndef __ASSEMBLY__
/**
* DEFINE_IDTENTRY_IRQ - Emit code for device interrupt IDT entry points
* @func: Function name of the entry point
*
* The vector number is pushed by the low level entry stub and handed
* to the function as error_code argument which needs to be truncated
* to an u8 because the push is sign extending.
*
* irq_enter/exit_rcu() are invoked before the function body and the
* KVM L1D flush request is set. Stack switching to the interrupt stack
* has to be done in the function body if necessary.
*/
#define DEFINE_IDTENTRY_IRQ(func) \
static void __##func(struct pt_regs *regs, u32 vector); \
\
__visible noinstr void func(struct pt_regs *regs, \
unsigned long error_code) \
{ \
irqentry_state_t state = irqentry_enter(regs); \
u32 vector = (u32)(u8)error_code; \
\
instrumentation_begin(); \
kvm_set_cpu_l1tf_flush_l1d(); \
run_irq_on_irqstack_cond(__##func, regs, vector); \
instrumentation_end(); \
irqentry_exit(regs, state); \
} \
\
static noinline void __##func(struct pt_regs *regs, u32 vector)

#else /* !__ASSEMBLY__ */
...
#endif

/*
* common_interrupt() handles all normal device IRQ's (the special SMP
* cross-CPU interrupts have their own entry points).
*/
DEFINE_IDTENTRY_IRQ(common_interrupt)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc;

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

desc = __this_cpu_read(vector_irq[vector]);
if (likely(!IS_ERR_OR_NULL(desc))) {
handle_irq(desc, regs);
} else {
apic_eoi();

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

set_irq_regs(old_regs);
}

/**
* spurious_interrupt - Catch all for interrupts raised on unused vectors
* @regs: Pointer to pt_regs on stack
* @vector: The vector number
*
* This is invoked from ASM entry code to catch all interrupts which
* trigger on an entry which is routed to the common_spurious idtentry
* point.
*/
DEFINE_IDTENTRY_IRQ(spurious_interrupt)
{
handle_spurious_interrupt(vector);
}

可以看到,实际上vector_irq中存储着每个硬件中断/中断向量对应的中断服务例程信息,即后面要提到的虚拟中断。

虚拟中断

上面只介绍了x86体系下的硬件中断资源的管理,实际上不同的体系架构有不同的处理方式。linux内核为了屏蔽这些不同,会以虚拟中断的形式向其他子系统或驱动提供服务,其中每一个硬件中断会分配一个全局唯一的虚拟中断,由struct irq_desc描述,统一存储在sparse_irqs变量中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
static struct maple_tree sparse_irqs = MTREE_INIT_EXT(sparse_irqs,
MT_FLAGS_ALLOC_RANGE |
MT_FLAGS_LOCK_EXTERN |
MT_FLAGS_USE_RCU,
sparse_irq_lock);

/**
* struct irq_desc - interrupt descriptor
* @irq_common_data: per irq and chip data passed down to chip functions
* @kstat_irqs: irq stats per cpu
* @handle_irq: highlevel irq-events handler
* @action: the irq action chain
* @status_use_accessors: status information
* @core_internal_state__do_not_mess_with_it: core internal status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple irq_set_irq_wake() callers
* @tot_count: stats field for non-percpu irqs
* @irq_count: stats field to detect stalled irqs
* @last_unhandled: aging timer for unhandled count
* @irqs_unhandled: stats field for spurious unhandled interrupts
* @threads_handled: stats field for deferred spurious detection of threaded handlers
* @threads_handled_last: comparator field for deferred spurious detection of threaded handlers
* @lock: locking for SMP
* @affinity_hint: hint to user space for preferred irq affinity
* @affinity_notify: context for notification of affinity changes
* @pending_mask: pending rebalanced interrupts
* @threads_oneshot: bitfield to handle shared oneshot threads
* @threads_active: number of irqaction threads currently running
* @wait_for_threads: wait queue for sync_irq to wait for threaded handlers
* @nr_actions: number of installed actions on this descriptor
* @no_suspend_depth: number of irqactions on a irq descriptor with
* IRQF_NO_SUSPEND set
* @force_resume_depth: number of irqactions on a irq descriptor with
* IRQF_FORCE_RESUME set
* @rcu: rcu head for delayed free
* @kobj: kobject used to represent this struct in sysfs
* @request_mutex: mutex to protect request/free before locking desc->lock
* @dir: /proc/irq/ procfs entry
* @debugfs_file: dentry for the debugfs file
* @name: flow handler name for /proc/interrupts output
*/
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
struct irqaction *action; /* IRQ action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int tot_count;
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
atomic_t threads_handled;
int threads_handled_last;
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
const struct cpumask *percpu_affinity;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PM_SLEEP
unsigned int nr_actions;
unsigned int no_suspend_depth;
unsigned int cond_suspend_depth;
unsigned int force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
struct dentry *debugfs_file;
const char *dev_name;
#endif
#ifdef CONFIG_SPARSE_IRQ
struct rcu_head rcu;
struct kobject kobj;
#endif
struct mutex request_mutex;
int parent_irq;
struct module *owner;
const char *name;
#ifdef CONFIG_HARDIRQS_SW_RESEND
struct hlist_node resend_node;
#endif
} ____cacheline_internodealigned_in_smp;

其中,handle_irqactionstruct irq_desc最重要的两个字段。handle_irq负责处理中断的硬件清理操作并触发驱动注册的中断处理程序;action则记录所有驱动注册的用于处理该中断的处理程序,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//#0  handle_edge_irq (desc=0xffff88810091dc00) at kernel/irq/chip.c:788
//#1 0xffffffff810af8fa in generic_handle_irq_desc (desc=<optimized out>)
// at ./include/linux/irqdesc.h:161
//#2 handle_irq (desc=<optimized out>, regs=<optimized out>)
// at arch/x86/kernel/irq.c:238
//#3 __common_interrupt (regs=<optimized out>, vector=0x20)
// at arch/x86/kernel/irq.c:257
//#4 0xffffffff81fd8eda in common_interrupt (regs=0xffffffff82a03d78,
// error_code=<optimized out>) at arch/x86/kernel/irq.c:247
//Backtrace stopped: Cannot access memory at address 0xffffc90000004010
static __always_inline void handle_irq(struct irq_desc *desc,
struct pt_regs *regs)
{
if (IS_ENABLED(CONFIG_X86_64))
generic_handle_irq_desc(desc);
else
__handle_irq(desc, regs);
}

/*
* Architectures call this to let the generic IRQ layer
* handle an interrupt.
*/
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
desc->handle_irq(desc);
}

中断路由

如前文中断硬件所述,系统通过 MMIO/PIO​ 按手册配置中断控制器的寄存器后,硬件产生的中断经控制器投递给CPU,最终触发回调函数。这里关键在于内核如何完成中断资源管理和配置,从而让配置后硬件发起的硬件中断能真正触发对应的虚拟中断的action:Linux 内核为此引入了 struct irq_datastruct irq_domainstruct irq_chip抽象层来描述中断控制器,从而优雅地适配了各异的硬件架构及复杂的中断路由关系。

数据结构

每一个中断控制器有不同的接口逻辑,因此内核中用struct irq_chip进行抽象描述,其提供了一系列中断控制器操作相关的回调函数,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* struct irq_chip - hardware interrupt chip descriptor
*
* @name: name for /proc/interrupts
* @irq_startup: start up the interrupt (defaults to ->enable if NULL)
* @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
* @irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
* @irq_disable: disable the interrupt
* @irq_ack: start of a new interrupt
* @irq_mask: mask an interrupt source
* @irq_mask_ack: ack and mask an interrupt source
* @irq_unmask: unmask an interrupt source
* @irq_eoi: end of interrupt
* @irq_set_affinity: Set the CPU affinity on SMP machines. If the force
* argument is true, it tells the driver to
* unconditionally apply the affinity setting. Sanity
* checks against the supplied affinity mask are not
* required. This is used for CPU hotplug where the
* target CPU is not yet set in the cpu_online_mask.
* @irq_retrigger: resend an IRQ to the CPU
* @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
* @irq_set_wake: enable/disable power-management wake-on of an IRQ
* @irq_bus_lock: function to lock access to slow bus (i2c) chips
* @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
* @irq_cpu_online: configure an interrupt source for a secondary CPU
* @irq_cpu_offline: un-configure an interrupt source for a secondary CPU
* @irq_suspend: function called from core code on suspend once per
* chip, when one or more interrupts are installed
* @irq_resume: function called from core code on resume once per chip,
* when one ore more interrupts are installed
* @irq_pm_shutdown: function called from core code on shutdown once per chip
* @irq_calc_mask: Optional function to set irq_data.mask for special cases
* @irq_print_chip: optional to print special chip info in show_interrupts
* @irq_request_resources: optional to request resources before calling
* any other callback related to this irq
* @irq_release_resources: optional to release resources acquired with
* irq_request_resources
* @irq_compose_msi_msg: optional to compose message content for MSI
* @irq_write_msi_msg: optional to write message content for MSI
* @irq_get_irqchip_state: return the internal state of an interrupt
* @irq_set_irqchip_state: set the internal state of a interrupt
* @irq_set_vcpu_affinity: optional to target a vCPU in a virtual machine
* @ipi_send_single: send a single IPI to destination cpus
* @ipi_send_mask: send an IPI to destination cpus in cpumask
* @irq_nmi_setup: function called from core code before enabling an NMI
* @irq_nmi_teardown: function called from core code after disabling an NMI
* @flags: chip specific flags
*/
struct irq_chip {
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);

void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);

int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);

void (*irq_bus_lock)(struct irq_data *data);
void (*irq_bus_sync_unlock)(struct irq_data *data);

#ifdef CONFIG_DEPRECATED_IRQ_CPU_ONOFFLINE
void (*irq_cpu_online)(struct irq_data *data);
void (*irq_cpu_offline)(struct irq_data *data);
#endif
void (*irq_suspend)(struct irq_data *data);
void (*irq_resume)(struct irq_data *data);
void (*irq_pm_shutdown)(struct irq_data *data);

void (*irq_calc_mask)(struct irq_data *data);

void (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
int (*irq_request_resources)(struct irq_data *data);
void (*irq_release_resources)(struct irq_data *data);

void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);

int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);

int (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);

void (*ipi_send_single)(struct irq_data *data, unsigned int cpu);
void (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);

int (*irq_nmi_setup)(struct irq_data *data);
void (*irq_nmi_teardown)(struct irq_data *data);

unsigned long flags;
};

由于目前中断控制器往往有复杂的层次关系(例如x86架构下外设中断会经过ioapic中断控制器->lapic中断控制器->CPU的IDT),因此还需要协同管理多层中断控制器之间的中断资源,内核中使用struct irq_domain进行抽象描述,通过parent来描述中断控制器的层次关系,也提供了一系列的回调函数ops来实现具体的映射逻辑,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* struct irq_domain - Hardware interrupt number translation object
* @link: Element in global irq_domain list.
* @name: Name of interrupt domain
* @ops: Pointer to irq_domain methods
* @host_data: Private data pointer for use by owner. Not touched by irq_domain
* core code.
* @flags: Per irq_domain flags
* @mapcount: The number of mapped interrupts
* @mutex: Domain lock, hierarchical domains use root domain's lock
* @root: Pointer to root domain, or containing structure if non-hierarchical
*
* Optional elements:
* @fwnode: Pointer to firmware node associated with the irq_domain. Pretty easy
* to swap it for the of_node via the irq_domain_get_of_node accessor
* @gc: Pointer to a list of generic chips. There is a helper function for
* setting up one or more generic chips for interrupt controllers
* drivers using the generic chip library which uses this pointer.
* @dev: Pointer to the device which instantiated the irqdomain
* With per device irq domains this is not necessarily the same
* as @pm_dev.
* @pm_dev: Pointer to a device that can be utilized for power management
* purposes related to the irq domain.
* @parent: Pointer to parent irq_domain to support hierarchy irq_domains
* @msi_parent_ops: Pointer to MSI parent domain methods for per device domain init
*
* Revmap data, used internally by the irq domain code:
* @revmap_size: Size of the linear map table @revmap[]
* @revmap_tree: Radix map tree for hwirqs that don't fit in the linear map
* @revmap: Linear table of irq_data pointers
*/
struct irq_domain {
struct list_head link;
const char *name;
const struct irq_domain_ops *ops;
void *host_data;
unsigned int flags;
unsigned int mapcount;
struct mutex mutex;
struct irq_domain *root;

/* Optional data */
struct fwnode_handle *fwnode;
enum irq_domain_bus_token bus_token;
struct irq_domain_chip_generic *gc;
struct device *dev;
struct device *pm_dev;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
const struct msi_parent_ops *msi_parent_ops;
#endif

/* reverse map data. The linear map gets appended to the irq_domain */
irq_hw_number_t hwirq_max;
unsigned int revmap_size;
struct radix_tree_root revmap_tree;
struct irq_data __rcu *revmap[] __counted_by(revmap_size);
};

/**
* struct irq_domain_ops - Methods for irq_domain objects
* @match: Match an interrupt controller device node to a host, returns
* 1 on a match
* @map: Create or update a mapping between a virtual irq number and a hw
* irq number. This is called only once for a given mapping.
* @unmap: Dispose of such a mapping
* @xlate: Given a device tree node and interrupt specifier, decode
* the hardware irq number and linux irq type value.
*
* Functions below are provided by the driver and called whenever a new mapping
* is created or an old mapping is disposed. The driver can then proceed to
* whatever internal data structures management is required. It also needs
* to setup the irq_desc when returning from map().
*/
struct irq_domain_ops {
int (*match)(struct irq_domain *d, struct device_node *node,
enum irq_domain_bus_token bus_token);
int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec,
enum irq_domain_bus_token bus_token);
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
void (*unmap)(struct irq_domain *d, unsigned int virq);
int (*xlate)(struct irq_domain *d, struct device_node *node,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type);
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
/* extended V2 interfaces to support hierarchy irq_domains */
int (*alloc)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs, void *arg);
void (*free)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs);
int (*activate)(struct irq_domain *d, struct irq_data *irqd, bool reserve);
void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *out_hwirq, unsigned int *out_type);
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
void (*debug_show)(struct seq_file *m, struct irq_domain *d,
struct irq_data *irqd, int ind);
#endif
};

而每个虚拟中断都在struct irq_data中包含这两个数据结构,从而用于设置虚拟中断对应的硬件中断信息,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* struct irq_data - per irq chip data passed down to chip functions
* @mask: precomputed bitmask for accessing the chip registers
* @irq: interrupt number
* @hwirq: hardware interrupt number, local to the interrupt domain
* @common: point to data shared by all irqchips
* @chip: low level interrupt hardware access
* @domain: Interrupt translation domain; responsible for mapping
* between hwirq number and linux irq number.
* @parent_data: pointer to parent struct irq_data to support hierarchy
* irq_domain
* @chip_data: platform-specific per-chip private data for the chip
* methods, to allow shared chip implementations
*/
struct irq_data {
u32 mask;
unsigned int irq;
irq_hw_number_t hwirq;
struct irq_common_data *common;
struct irq_chip *chip;
struct irq_domain *domain;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};

x86架构

这里我们就以x86架构为例,详细分析一下virtio-net-pci设备中断的设置和路由。根据前面msi章节的介绍,pci的msi中断通过在msix capability空间中写入Message Address和Message Data数据来进行设置,从而pci硬件会向lapic以TLP消息形式投递Message Data中制定的中断向量,从而完成中断触发。这里实际上涉及到msi中断控制器以及lapic中断控制器

初始化

lapic

Linux内核中lapic中断控制器的irq_chip结构是lapic_controller,其是静态初始化的,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static struct irq_chip lapic_controller = {
.name = "APIC",
.irq_ack = apic_ack_edge,
.irq_set_affinity = apic_set_affinity,
.irq_compose_msi_msg = x86_vector_msi_compose_msg,
.irq_retrigger = apic_retrigger_irq,
};

void apic_ack_edge(struct irq_data *irqd)
{
irq_complete_move(irqd_cfg(irqd));
apic_ack_irq(irqd);
}

void apic_ack_irq(struct irq_data *irqd)
{
irq_move_irq(irqd);
apic_eoi();
}

static __always_inline void apic_eoi(void)
{
static_call(apic_call_eoi)();
}

其相关的操作函数的实际回调函数由apic_driver定义,会在apic_install_driver()进行设置,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
static struct apic apic_x2apic_phys __ro_after_init = {

.name = "physical x2apic",
.probe = x2apic_phys_probe,
.acpi_madt_oem_check = x2apic_acpi_madt_oem_check,

.dest_mode_logical = false,

.disable_esr = 0,

.cpu_present_to_apicid = default_cpu_present_to_apicid,

.max_apic_id = UINT_MAX,
.x2apic_set_max_apicid = true,
.get_apic_id = x2apic_get_apic_id,

.calc_dest_apicid = apic_default_calc_apicid,

.send_IPI = x2apic_send_IPI,
.send_IPI_mask = x2apic_send_IPI_mask,
.send_IPI_mask_allbutself = x2apic_send_IPI_mask_allbutself,
.send_IPI_allbutself = x2apic_send_IPI_allbutself,
.send_IPI_all = x2apic_send_IPI_all,
.send_IPI_self = x2apic_send_IPI_self,
.nmi_to_offline_cpu = true,

.read = native_apic_msr_read,
.write = native_apic_msr_write,
.eoi = native_apic_msr_eoi,
.icr_read = native_x2apic_icr_read,
.icr_write = native_x2apic_icr_write,
};

apic_driver(apic_x2apic_phys);

void __init apic_install_driver(struct apic *driver)
{
...
/* Apply any already installed callback overrides */
restore_override_callbacks();
update_static_calls();

pr_info("Switched APIC routing to: %s\n", driver->name);
}

#define update_call(__cb) \
static_call_update(apic_call_##__cb, *apic->__cb)

//#0 update_static_calls () at arch/x86/kernel/apic/init.c:66
//#1 0xffffffff832c4871 in apic_install_driver (driver=0xffffffff82843e20 <apic_x2apic_phys>)
// at arch/x86/kernel/apic/init.c:107
//#2 0xffffffff832c5bb8 in x86_64_probe_apic () at arch/x86/kernel/apic/probe_64.c:25
//#3 0xffffffff832c3b70 in apic_intr_mode_init () at arch/x86/kernel/apic/apic.c:1367
//#4 0xffffffff832b3a1f in x86_late_time_init () at arch/x86/kernel/time.c:100
//#5 0xffffffff832a9dc6 in start_kernel () at init/main.c:1036
//#6 0xffffffff832b32f4 in x86_64_start_reservations (
// real_mode_data=real_mode_data@entry=0x14770 <entry_stack_storage+1904> <error: Cannot access memory at address 0x14770>) at arch/x86/kernel/head64.c:507
//#7 0xffffffff832b33ec in x86_64_start_kernel (
// real_mode_data=0x14770 <entry_stack_storage+1904> <error: Cannot access memory at address 0x14770>) at arch/x86/kernel/head64.c:488
//#8 0xffffffff810ab4b6 in secondary_startup_64 () at arch/x86/kernel/head_64.S:420
//#9 0x0000000000000000 in ?? ()
static __init void update_static_calls(void)
{
update_call(eoi);
update_call(native_eoi);
update_call(write);
update_call(read);
update_call(send_IPI);
update_call(send_IPI_mask);
update_call(send_IPI_mask_allbutself);
update_call(send_IPI_allbutself);
update_call(send_IPI_all);
update_call(send_IPI_self);
update_call(icr_read);
update_call(icr_write);
update_call(wait_icr_idle);
update_call(wakeup_secondary_cpu);
update_call(wakeup_secondary_cpu_64);
}

而其irq_domain结构则是x86_vector_domain,其在arch_early_irq_init()中初始化,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static const struct irq_domain_ops x86_vector_domain_ops = {
.select = x86_vector_select,
.alloc = x86_vector_alloc_irqs,
.free = x86_vector_free_irqs,
.activate = x86_vector_activate,
.deactivate = x86_vector_deactivate,
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
.debug_show = x86_vector_debug_show,
#endif
};

//#0 arch_early_irq_init () at arch/x86/kernel/apic/vector.c:794
//#1 0xffffffff832d2a5a in early_irq_init () at kernel/irq/irqdesc.c:583
//#2 0xffffffff832a9ca8 in start_kernel () at init/main.c:976
//#3 0xffffffff832b32f4 in x86_64_start_reservations (
// real_mode_data=real_mode_data@entry=0x14770 <entry_stack_storage+1904> <error: Cannot access memory at address 0x14770>) at arch/x86/kernel/head64.c:507
//#4 0xffffffff832b33ec in x86_64_start_kernel (
// real_mode_data=0x14770 <entry_stack_storage+1904> <error: Cannot access memory at address 0x14770>) at arch/x86/kernel/head64.c:488
//#5 0xffffffff810ab4b6 in secondary_startup_64 ()
// at arch/x86/kernel/head_64.S:420
//#6 0x0000000000000000 in ?? ()
int __init arch_early_irq_init(void)
{
struct fwnode_handle *fn;

fn = irq_domain_alloc_named_fwnode("VECTOR");
BUG_ON(!fn);
x86_vector_domain = irq_domain_create_tree(fn, &x86_vector_domain_ops,
NULL);
BUG_ON(x86_vector_domain == NULL);
irq_set_default_host(x86_vector_domain);

BUG_ON(!alloc_cpumask_var(&vector_searchmask, GFP_KERNEL));

/*
* Allocate the vector matrix allocator data structure and limit the
* search area.
*/
vector_matrix = irq_alloc_matrix(NR_VECTORS, FIRST_EXTERNAL_VECTOR,
FIRST_SYSTEM_VECTOR);
BUG_ON(!vector_matrix);

return arch_early_ioapic_init();
}

可以看到,其一方面初始化x86_vector_domain结构,后续用来和其他层次中断控制器进行交互;另一方面初始化vector_matrix,用来管理每个cpu的中断向量资源。

msix

类似的,Linux内核中msix中断控制器也有对应的irq_domain结构和irq_chip结构,但由于每个pci设备都会有一个msix中断控制器,是per-device的,因此内核是通过模板pci_msix_template进行初始化,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
static const struct msi_domain_template pci_msix_template = {
.chip = {
.name = "PCI-MSIX",
.irq_mask = pci_irq_mask_msix,
.irq_unmask = pci_irq_unmask_msix,
.irq_write_msi_msg = pci_msi_domain_write_msg,
.flags = IRQCHIP_ONESHOT_SAFE,
},

.ops = {
.prepare_desc = pci_msix_prepare_desc,
.set_desc = pci_device_domain_set_desc,
},

.info = {
.flags = MSI_COMMON_FLAGS | MSI_FLAG_PCI_MSIX |
MSI_FLAG_PCI_MSIX_ALLOC_DYN,
.bus_token = DOMAIN_BUS_PCI_DEVICE_MSIX,
},
};

/**
* msi_create_device_irq_domain - Create a device MSI interrupt domain
* @dev: Pointer to the device
* @domid: Domain id
* @template: MSI domain info bundle used as template
* @hwsize: Maximum number of MSI table entries (0 if unknown or unlimited)
* @domain_data: Optional pointer to domain specific data which is set in
* msi_domain_info::data
* @chip_data: Optional pointer to chip specific data which is set in
* msi_domain_info::chip_data
*
* Return: True on success, false otherwise
*
* There is no firmware node required for this interface because the per
* device domains are software constructs which are actually closer to the
* hardware reality than any firmware can describe them.
*
* The domain name and the irq chip name for a MSI device domain are
* composed by: "$(PREFIX)$(CHIPNAME)-$(DEVNAME)"
*
* $PREFIX: Optional prefix provided by the underlying MSI parent domain
* via msi_parent_ops::prefix. If that pointer is NULL the prefix
* is empty.
* $CHIPNAME: The name of the irq_chip in @template
* $DEVNAME: The name of the device
*
* This results in understandable chip names and hardware interrupt numbers
* in e.g. /proc/interrupts
*
* PCI-MSI-0000:00:1c.0 0-edge Parent domain has no prefix
* IR-PCI-MSI-0000:00:1c.4 0-edge Same with interrupt remapping prefix 'IR-'
*
* IR-PCI-MSIX-0000:3d:00.0 0-edge Hardware interrupt numbers reflect
* IR-PCI-MSIX-0000:3d:00.0 1-edge the real MSI-X index on that device
* IR-PCI-MSIX-0000:3d:00.0 2-edge
*
* On IMS domains the hardware interrupt number is either a table entry
* index or a purely software managed index but it is guaranteed to be
* unique.
*
* The domain pointer is stored in @dev::msi::data::__irqdomains[]. All
* subsequent operations on the domain depend on the domain id.
*
* The domain is automatically freed when the device is removed via devres
* in the context of @dev::msi::data freeing, but it can also be
* independently removed via @msi_remove_device_irq_domain().
*/
//#0 msi_create_device_irq_domain (dev=dev@entry=0xffff8881008750c0, domid=domid@entry=0x0,
// template=template@entry=0xffffffff82280e00 <pci_msix_template>, hwsize=hwsize@entry=0x3,
// domain_data=domain_data@entry=0x0 <fixed_percpu_data>,
// chip_data=chip_data@entry=0x0 <fixed_percpu_data>) at kernel/irq/msi.c:964
//#1 0xffffffff81611de0 in pci_create_device_domain (pdev=0xffff888100875000,
// tmpl=0xffffffff82280e00 <pci_msix_template>, hwsize=0x3) at drivers/pci/msi/irqdomain.c:247
//#2 0xffffffff81611111 in __pci_enable_msix_range (dev=dev@entry=0xffff888100875000,
// entries=entries@entry=0x0 <fixed_percpu_data>, minvec=minvec@entry=0x3, maxvec=maxvec@entry=0x3,
// affd=affd@entry=0x0 <fixed_percpu_data>, flags=flags@entry=0x4) at drivers/pci/msi/msi.c:823
//#3 0xffffffff8160f957 in pci_alloc_irq_vectors_affinity (dev=0xffff888100875000,
// min_vecs=min_vecs@entry=0x3, max_vecs=max_vecs@entry=0x3, flags=0x4,
// affd=affd@entry=0x0 <fixed_percpu_data>) at drivers/pci/msi/api.c:270
//#4 0xffffffff81695ee9 in vp_request_msix_vectors (vdev=0xffff8881009e6800, nvectors=<optimized out>,
// per_vq_vectors=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_common.c:133
//#5 vp_find_vqs_msix (vdev=vdev@entry=0xffff8881009e6800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff888100b44880, callbacks=callbacks@entry=0xffff888100b448a0,
// names=names@entry=0xffff888100b448c0, per_vq_vectors=per_vq_vectors@entry=0x1,
// ctx=0xffff88810ccfe198, desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#6 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff8881009e6800, nvqs=0x3,
// vqs=0xffff888100b44880, callbacks=0xffff888100b448a0, names=0xffff888100b448c0,
// ctx=0xffff88810ccfe198, desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#7 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff8881009e6800, nvqs=<optimized out>,
// vqs=<optimized out>, callbacks=<optimized out>, names=<optimized out>, ctx=<optimized out>,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_modern.c:604
//#8 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3, vqs=0xffff888100b44880,
// callbacks=0xffff888100b448a0, names=0xffff888100b448c0, ctx=0xffff88810ccfe198,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#9 virtnet_find_vqs (vi=0xffff88810ce52920) at drivers/net/virtio_net.c:4389
//#10 init_vqs (vi=0xffff88810ce52920) at drivers/net/virtio_net.c:4478
//#11 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff8881009e6800) at drivers/net/virtio_net.c:4799
//#12 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff8881009e6810) at drivers/virtio/virtio.c:311
//#13 0xffffffff81932472 in call_driver_probe (dev=0xffff8881009e6810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#14 really_probe (dev=dev@entry=0xffff8881009e6810, drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#15 0xffffffff8193268e in __driver_probe_device (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff8881009e6810) at drivers/base/dd.c:798
//#16 0xffffffff8193272a in driver_probe_device (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff8881009e6810) at drivers/base/dd.c:828
//#17 0xffffffff819329b3 in __driver_attach (dev=0xffff8881009e6810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#18 __driver_attach (dev=0xffff8881009e6810, data=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1154
//#19 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>, data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#20 0xffffffff81931f19 in driver_attach (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#21 0xffffffff81931637 in bus_add_driver (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#22 0xffffffff81933a9d in driver_register (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#23 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>) at drivers/virtio/virtio.c:370
//#24 0xffffffff832fb86c in virtio_net_driver_init () at drivers/net/virtio_net.c:5050
//#25 0xffffffff810019e6 in do_one_initcall (fn=0xffffffff832fb800 <virtio_net_driver_init>)
// at init/main.c:1238
//#26 0xffffffff832aa0ae in do_initcall_level (level=0x6, command_line=0xffff8881002c1480 "rdinit")
// at init/main.c:1300
//#27 do_initcalls () at init/main.c:1316
//#28 do_basic_setup () at init/main.c:1335
//#29 kernel_init_freeable () at init/main.c:1548
//#30 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>) at init/main.c:1437
//#31 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>, regs=0xffffc90000013f58,
// fn=0xffffffff81fddbc0 <kernel_init>, fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#32 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#33 0x0000000000000000 in ?? ()
bool msi_create_device_irq_domain(struct device *dev, unsigned int domid,
const struct msi_domain_template *template,
unsigned int hwsize, void *domain_data,
void *chip_data)
{
...
struct irq_domain *domain, *parent = dev->msi.domain;
struct msi_domain_template *bundle;
const struct msi_parent_ops *pops;
...

bundle = kmemdup(template, sizeof(*bundle), GFP_KERNEL);
if (!bundle)
return false;

...
bundle->info.chip = &bundle->chip;
bundle->info.ops = &bundle->ops;
...
pops = parent->msi_parent_ops;
snprintf(bundle->name, sizeof(bundle->name), "%s%s-%s",
pops->prefix ? : "", bundle->chip.name, dev_name(dev));
bundle->chip.name = bundle->name;
...
msi_lock_descs(dev);

if (WARN_ON_ONCE(msi_get_device_domain(dev, domid)))
goto fail;

if (!pops->init_dev_msi_info(dev, parent, parent, &bundle->info))
goto fail;

domain = __msi_create_irq_domain(fwnode, &bundle->info, IRQ_DOMAIN_FLAG_MSI_DEVICE, parent);
if (!domain)
goto fail;

domain->dev = dev;
dev->msi.data->__domains[domid].domain = domain;
msi_unlock_descs(dev);
return true;
...
}

static struct irq_domain *__msi_create_irq_domain(struct fwnode_handle *fwnode,
struct msi_domain_info *info,
unsigned int flags,
struct irq_domain *parent)
{
struct irq_domain *domain;

if (info->hwsize > MSI_XA_DOMAIN_SIZE)
return NULL;

/*
* Hardware size 0 is valid for backwards compatibility and for
* domains which are not backed by a hardware table. Grant the
* maximum index space.
*/
if (!info->hwsize)
info->hwsize = MSI_XA_DOMAIN_SIZE;

msi_domain_update_dom_ops(info);
if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
msi_domain_update_chip_ops(info);

domain = irq_domain_create_hierarchy(parent, flags | IRQ_DOMAIN_FLAG_MSI, 0,
fwnode, &msi_domain_ops, info);

if (domain) {
irq_domain_update_bus_token(domain, info->bus_token);
if (info->flags & MSI_FLAG_PARENT_PM_DEV)
domain->pm_dev = parent->pm_dev;
}

return domain;
}

static const struct irq_domain_ops msi_domain_ops = {
.alloc = msi_domain_alloc,
.free = msi_domain_free,
.activate = msi_domain_activate,
.deactivate = msi_domain_deactivate,
.translate = msi_domain_translate,
};

可以看到,其基于pci_msix_template模板,通过msi_create_irq_domain()完成irq_domain的初始化。其对应的irq_domain_ops,即msi_domain_ops,其实际上最后会调用到模板中相关的info->ops

而该domain对应的parent则是上个章节x86_vector_domain,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
int pcibios_device_add(struct pci_dev *dev)
{
...
/*
* Setup the initial MSI domain of the device. If the underlying
* bus has a PCI/MSI irqdomain associated use the bus domain,
* otherwise set the default domain. This ensures that special irq
* domains e.g. VMD are preserved. The default ensures initial
* operation if irq remapping is not active. If irq remapping is
* active it will overwrite the domain pointer when the device is
* associated to a remapping domain.
*/
msidom = dev_get_msi_domain(&dev->bus->dev);
if (!msidom)
msidom = x86_pci_msi_default_domain;
dev_set_msi_domain(&dev->dev, msidom);
return 0;
}

void __init x86_create_pci_msi_domain(void)
{
x86_pci_msi_default_domain = x86_init.irqs.create_pci_msi_domain();
}

/*
* The platform setup functions are preset with the default functions
* for standard PC hardware.
*/
struct x86_init_ops x86_init __initdata = {
...
.irqs = {
...
.create_pci_msi_domain = native_create_pci_msi_domain,
},
};

//#0 native_create_pci_msi_domain () at arch/x86/kernel/apic/msi.c:269
//#1 0xffffffff832c5aed in x86_create_pci_msi_domain ()
// at arch/x86/kernel/apic/msi.c:280
//#2 0xffffffff8330dadc in pci_arch_init () at arch/x86/pci/init.c:26
//#3 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff8330dab0 <pci_arch_init>) at init/main.c:1238
//#4 0xffffffff832aa0ae in do_initcall_level (level=0x3,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#5 do_initcalls () at init/main.c:1316
//#6 do_basic_setup () at init/main.c:1335
//#7 kernel_init_freeable () at init/main.c:1548
//#8 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#9 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#10 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#11 0x0000000000000000 in ?? ()
struct irq_domain * __init native_create_pci_msi_domain(void)
{
if (apic_is_disabled)
return NULL;

x86_vector_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
x86_vector_domain->msi_parent_ops = &x86_vector_msi_parent_ops;
return x86_vector_domain;
}

info->chip则是对应的irq_chip数据,其同样由pci_msix_template模板、parent->msi_parent_ops->init_dev_msi_info等共同初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
static const struct msi_parent_ops x86_vector_msi_parent_ops = {
.supported_flags = X86_VECTOR_MSI_FLAGS_SUPPORTED,
.init_dev_msi_info = x86_init_dev_msi_info,
};

/**
* x86_init_dev_msi_info - Domain info setup for MSI domains
* @dev: The device for which the domain should be created
* @domain: The (root) domain providing this callback
* @real_parent: The real parent domain of the to initialize domain
* @info: The domain info for the to initialize domain
*
* This function is to be used for all types of MSI domains above the x86
* vector domain and any intermediates. The domain specific functionality
* is determined via the @real_parent.
*/
//#0 x86_init_dev_msi_info (dev=0xffff8881009b20c0, domain=0xffff88810012b000, real_parent=0xffff88810012b000, info=0xffff8881008e0388) at arch/x86/kernel/apic/msi.c:208
//#1 0xffffffff81186f0d in msi_create_device_irq_domain (dev=dev@entry=0xffff8881009b20c0, domid=domid@entry=0x0, template=template@entry=0xffffffff82280e00 <pci_msix_template>,
// hwsize=hwsize@entry=0x3, domain_data=domain_data@entry=0x0 <fixed_percpu_data>, chip_data=chip_data@entry=0x0 <fixed_percpu_data>) at kernel/irq/msi.c:1015
//#2 0xffffffff81611de0 in pci_create_device_domain (pdev=0xffff8881009b2000, tmpl=0xffffffff82280e00 <pci_msix_template>, hwsize=0x3) at drivers/pci/msi/irqdomain.c:247
//#3 0xffffffff81611111 in __pci_enable_msix_range (dev=dev@entry=0xffff8881009b2000, entries=entries@entry=0x0 <fixed_percpu_data>, minvec=minvec@entry=0x3, maxvec=maxvec@entry=0x3,
// affd=affd@entry=0x0 <fixed_percpu_data>, flags=flags@entry=0x4) at drivers/pci/msi/msi.c:823
//#4 0xffffffff8160f957 in pci_alloc_irq_vectors_affinity (dev=0xffff8881009b2000, min_vecs=min_vecs@entry=0x3, max_vecs=max_vecs@entry=0x3, flags=0x4,
// affd=affd@entry=0x0 <fixed_percpu_data>) at drivers/pci/msi/api.c:270
//#5 0xffffffff81695ee9 in vp_request_msix_vectors (vdev=0xffff888100cf5800, nvectors=<optimized out>, per_vq_vectors=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_common.c:133
//#6 vp_find_vqs_msix (vdev=vdev@entry=0xffff888100cf5800, nvqs=nvqs@entry=0x3, vqs=vqs@entry=0xffff888100ef7b20, callbacks=callbacks@entry=0xffff888100ef7b40,
// names=names@entry=0xffff888100ef7b60, per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff888100a20e48, desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#7 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff888100cf5800, nvqs=0x3, vqs=0xffff888100ef7b20, callbacks=0xffff888100ef7b40, names=0xffff888100ef7b60,
// ctx=0xffff888100a20e48, desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#8 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff888100cf5800, nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>, names=<optimized out>, ctx=<optimized out>,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_modern.c:604
//#9 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3, vqs=0xffff888100ef7b20, callbacks=0xffff888100ef7b40, names=0xffff888100ef7b60, ctx=0xffff888100a20e48,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#10 virtnet_find_vqs (vi=0xffff888100ce7920) at drivers/net/virtio_net.c:4389
//#11 init_vqs (vi=0xffff888100ce7920) at drivers/net/virtio_net.c:4478
//#12 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff888100cf5800) at drivers/net/virtio_net.c:4799
//#13 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff888100cf5810) at drivers/virtio/virtio.c:311
//#14 0xffffffff81932472 in call_driver_probe (dev=0xffff888100cf5810, drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#15 really_probe (dev=dev@entry=0xffff888100cf5810, drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:656
//#16 0xffffffff8193268e in __driver_probe_device (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>, dev=dev@entry=0xffff888100cf5810) at drivers/base/dd.c:798
//#17 0xffffffff8193272a in driver_probe_device (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>, dev=dev@entry=0xffff888100cf5810) at drivers/base/dd.c:828
//#18 0xffffffff819329b3 in __driver_attach (dev=0xffff888100cf5810, data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#19 __driver_attach (dev=0xffff888100cf5810, data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#20 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>, start=start@entry=0x0 <fixed_percpu_data>, data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#21 0xffffffff81931f19 in driver_attach (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1231
//#22 0xffffffff81931637 in bus_add_driver (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/bus.c:673
//#23 0xffffffff81933a9d in driver_register (drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/driver.c:246
//#24 0xffffffff8168ec0b in register_virtio_driver (driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>) at drivers/virtio/virtio.c:370
//#25 0xffffffff832fb86c in virtio_net_driver_init () at drivers/net/virtio_net.c:5050
//#26 0xffffffff810019e6 in do_one_initcall (fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#27 0xffffffff832aa0ae in do_initcall_level (level=0x6, command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#28 do_initcalls () at init/main.c:1316
//#29 do_basic_setup () at init/main.c:1335
//#30 kernel_init_freeable () at init/main.c:1548
//#31 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>) at init/main.c:1437
//#32 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>, regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>, fn_arg=0x0 <fixed_percpu_data>)
// at arch/x86/kernel/process.c:147
//#33 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#34 0x0000000000000000 in ?? ()
static bool x86_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
struct irq_domain *real_parent, struct msi_domain_info *info)
{
const struct msi_parent_ops *pops = real_parent->msi_parent_ops;

/* MSI parent domain specific settings */
switch (real_parent->bus_token) {
case DOMAIN_BUS_ANY:
/* Only the vector domain can have the ANY token */
if (WARN_ON_ONCE(domain != real_parent))
return false;
info->chip->irq_set_affinity = msi_set_affinity;
break;
case DOMAIN_BUS_DMAR:
case DOMAIN_BUS_AMDVI:
break;
default:
WARN_ON_ONCE(1);
return false;
}

/* Is the target supported? */
switch(info->bus_token) {
case DOMAIN_BUS_PCI_DEVICE_MSI:
case DOMAIN_BUS_PCI_DEVICE_MSIX:
break;
case DOMAIN_BUS_PCI_DEVICE_IMS:
if (!(pops->supported_flags & MSI_FLAG_PCI_IMS))
return false;
break;
default:
WARN_ON_ONCE(1);
return false;
}

/*
* Mask out the domain specific MSI feature flags which are not
* supported by the real parent.
*/
info->flags &= pops->supported_flags;
/* Enforce the required flags */
info->flags |= X86_VECTOR_MSI_FLAGS_REQUIRED;

/* This is always invoked from the top level MSI domain! */
info->ops->msi_prepare = x86_msi_prepare;

info->chip->irq_ack = irq_chip_ack_parent;
info->chip->irq_retrigger = irq_chip_retrigger_hierarchy;
info->chip->flags |= IRQCHIP_SKIP_SET_WAKE |
IRQCHIP_AFFINITY_PRE_STARTUP;

info->handler = handle_edge_irq;
info->handler_name = "edge";

return true;
}

中断映射

Linux内核进行中断映射的一般流程如下所示

  1. 分配物理中断资源
  2. 分配中断剩余资源
  3. 激活中断

virtio-net-pci设备在vp_find_vqs_msix()中完成中断映射的流程

分配物理中断资源

virtio-net-pci设备会在msix_setup_msi_descs()中分配好物理的中断资源,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//#0  msix_setup_msi_descs (dev=dev@entry=0xffff88810087d000, 
// entries=entries@entry=0x0 <fixed_percpu_data>, nvec=nvec@entry=0x3,
// masks=masks@entry=0x0 <fixed_percpu_data>) at drivers/pci/msi/msi.c:611
//#1 0xffffffff816112d9 in msix_setup_interrupts (dev=<optimized out>,
// entries=<optimized out>, nvec=<optimized out>,
// affd=0x0 <fixed_percpu_data>) at drivers/pci/msi/msi.c:666
//#2 msix_capability_init (dev=0xffff88810087d000, entries=<optimized out>,
// nvec=<optimized out>, affd=<optimized out>) at drivers/pci/msi/msi.c:727
//#3 __pci_enable_msix_range (dev=dev@entry=0xffff88810087d000,
// entries=entries@entry=0x0 <fixed_percpu_data>, minvec=minvec@entry=0x3,
// maxvec=maxvec@entry=0x3, affd=affd@entry=0x0 <fixed_percpu_data>,
// flags=flags@entry=0x4) at drivers/pci/msi/msi.c:833
//#4 0xffffffff8160f957 in pci_alloc_irq_vectors_affinity (
// dev=0xffff88810087d000, min_vecs=min_vecs@entry=0x3,
// max_vecs=max_vecs@entry=0x3, flags=0x4,
// affd=affd@entry=0x0 <fixed_percpu_data>) at drivers/pci/msi/api.c:270
//#5 0xffffffff81695ee9 in vp_request_msix_vectors (vdev=0xffff88810080b800,
// nvectors=<optimized out>, per_vq_vectors=<optimized out>,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:133
//#6 vp_find_vqs_msix (vdev=vdev@entry=0xffff88810080b800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff88810c2ee200,
// callbacks=callbacks@entry=0xffff88810c2ee220,
// names=names@entry=0xffff88810c2ee240,
// per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#7 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff88810080b800,
// nvqs=0x3, vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#8 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff88810080b800,
// nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>,
// names=<optimized out>, ctx=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_modern.c:604
//#9 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3,
// vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#10 virtnet_find_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4389
//#11 init_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4478
//#12 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff88810080b800)
// at drivers/net/virtio_net.c:4799
//#13 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff88810080b810)
// at drivers/virtio/virtio.c:311
//#14 0xffffffff81932472 in call_driver_probe (dev=0xffff88810080b810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#15 really_probe (dev=dev@entry=0xffff88810080b810,
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#16 0xffffffff8193268e in __driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:798
//#17 0xffffffff8193272a in driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:828
//#18 0xffffffff819329b3 in __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#19 __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#20 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>,
// data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#21 0xffffffff81931f19 in driver_attach (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#22 0xffffffff81931637 in bus_add_driver (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#23 0xffffffff81933a9d in driver_register (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#24 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/virtio/virtio.c:370
//#25 0xffffffff832fb86c in virtio_net_driver_init ()
// at drivers/net/virtio_net.c:5050
//#26 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#27 0xffffffff832aa0ae in do_initcall_level (level=0x6,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#28 do_initcalls () at init/main.c:1316
//#29 do_basic_setup () at init/main.c:1335
//#30 kernel_init_freeable () at init/main.c:1548
//#31 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#32 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#33 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#34 0x0000000000000000 in ?? ()
static int msix_setup_msi_descs(struct pci_dev *dev, struct msix_entry *entries,
int nvec, struct irq_affinity_desc *masks)
{
int ret = 0, i, vec_count = pci_msix_vec_count(dev);
struct irq_affinity_desc *curmsk;
struct msi_desc desc;

memset(&desc, 0, sizeof(desc));

for (i = 0, curmsk = masks; i < nvec; i++, curmsk++) {
desc.msi_index = entries ? entries[i].entry : i;
desc.affinity = masks ? curmsk : NULL;
desc.pci.msi_attrib.is_virtual = desc.msi_index >= vec_count;

msix_prepare_msi_desc(dev, &desc);

ret = msi_insert_msi_desc(&dev->dev, &desc);
if (ret)
break;
}
return ret;
}

void msix_prepare_msi_desc(struct pci_dev *dev, struct msi_desc *desc)
{
desc->nvec_used = 1;
desc->pci.msi_attrib.is_msix = 1;
desc->pci.msi_attrib.is_64 = 1;
desc->pci.msi_attrib.default_irq = dev->irq;
desc->pci.mask_base = dev->msix_base;
desc->pci.msi_attrib.can_mask = !pci_msi_ignore_mask &&
!desc->pci.msi_attrib.is_virtual;

if (desc->pci.msi_attrib.can_mask) {
void __iomem *addr = pci_msix_desc_addr(desc);

desc->pci.msix_ctrl = readl(addr + PCI_MSIX_ENTRY_VECTOR_CTRL);
}
}

static inline int msi_insert_msi_desc(struct device *dev, struct msi_desc *init_desc)
{
return msi_domain_insert_msi_desc(dev, MSI_DEFAULT_DOMAIN, init_desc);
}

int msi_domain_insert_msi_desc(struct device *dev, unsigned int domid,
struct msi_desc *init_desc)
{
struct msi_desc *desc;

lockdep_assert_held(&dev->msi.data->mutex);

desc = msi_alloc_desc(dev, init_desc->nvec_used, init_desc->affinity);
if (!desc)
return -ENOMEM;

/* Copy type specific data to the new descriptor. */
desc->pci = init_desc->pci;

return msi_insert_desc(dev, desc, domid, init_desc->msi_index);
}

static int msi_insert_desc(struct device *dev, struct msi_desc *desc,
unsigned int domid, unsigned int index)
{
struct msi_device_data *md = dev->msi.data;
struct xarray *xa = &md->__domains[domid].store;
unsigned int hwsize;
int ret;

hwsize = msi_domain_get_hwsize(dev, domid);

if (index == MSI_ANY_INDEX) {
struct xa_limit limit = { .min = 0, .max = hwsize - 1 };
unsigned int index;

/* Let the xarray allocate a free index within the limit */
ret = xa_alloc(xa, &index, desc, limit, GFP_KERNEL);
if (ret)
goto fail;

desc->msi_index = index;
return 0;
} else {
if (index >= hwsize) {
ret = -ERANGE;
goto fail;
}

desc->msi_index = index;
ret = xa_insert(xa, index, desc, GFP_KERNEL);
if (ret)
goto fail;
return 0;
}
fail:
msi_free_desc(desc);
return ret;
}

可以看到,其在msix_prepare_msi_desc()中准备好硬件中断所需的资源(例如msix capability基地址desc->pci.mask_base等),然后在msi_insert_msi_desc()中从de->msi.data->__domains[domid].store中进行实际分配

分配中断剩余资源

即需要分配中断建立所需要的其余所有资源,主要就是虚拟中断资源以及irq_domain层次所需资源。virtio-net-pci设备会在__msi_domain_alloc_irqs()中进行分配,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//#0  __msi_domain_alloc_irqs (dev=0xffff88810087d0c0, 
// domain=0xffff88810c30f0c0, ctrl=0xffffc90000013a38)
// at kernel/irq/msi.c:1279
//#1 0xffffffff81187636 in msi_domain_alloc_locked (dev=0xffff88810087d0c0,
// ctrl=0xffffc90000013a38) at kernel/irq/msi.c:1383
//#2 msi_domain_alloc_irqs_all_locked (dev=dev@entry=0xffff88810087d0c0,
// domid=domid@entry=0x0, nirqs=nirqs@entry=0x3) at kernel/irq/msi.c:1461
//#3 0xffffffff81611c36 in pci_msi_setup_msi_irqs (
// dev=dev@entry=0xffff88810087d000, nvec=nvec@entry=0x3,
// type=type@entry=0x11) at drivers/pci/msi/irqdomain.c:17
//#4 0xffffffff816112ef in msix_setup_interrupts (dev=<optimized out>,
// entries=<optimized out>, nvec=<optimized out>,
// affd=0x0 <fixed_percpu_data>) at drivers/pci/msi/msi.c:670
//#5 msix_capability_init (dev=0xffff88810087d000, entries=<optimized out>,
// nvec=<optimized out>, affd=<optimized out>) at drivers/pci/msi/msi.c:727
//#6 __pci_enable_msix_range (dev=dev@entry=0xffff88810087d000,
// entries=entries@entry=0x0 <fixed_percpu_data>, minvec=minvec@entry=0x3,
// maxvec=maxvec@entry=0x3, affd=affd@entry=0x0 <fixed_percpu_data>,
// flags=flags@entry=0x4) at drivers/pci/msi/msi.c:833
//#7 0xffffffff8160f957 in pci_alloc_irq_vectors_affinity (
// dev=0xffff88810087d000, min_vecs=min_vecs@entry=0x3,
// max_vecs=max_vecs@entry=0x3, flags=0x4,
// affd=affd@entry=0x0 <fixed_percpu_data>) at drivers/pci/msi/api.c:270
//#8 0xffffffff81695ee9 in vp_request_msix_vectors (vdev=0xffff88810080b800,
// nvectors=<optimized out>, per_vq_vectors=<optimized out>,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:133
//#9 vp_find_vqs_msix (vdev=vdev@entry=0xffff88810080b800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff88810c2ee200,
// callbacks=callbacks@entry=0xffff88810c2ee220,
// names=names@entry=0xffff88810c2ee240,
// per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#10 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff88810080b800,
// nvqs=0x3, vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#11 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff88810080b800,
// nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>,
// names=<optimized out>, ctx=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_modern.c:604
//#12 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3,
// vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#13 virtnet_find_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4389
//#14 init_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4478
//#15 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff88810080b800)
// at drivers/net/virtio_net.c:4799
//#16 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff88810080b810)
// at drivers/virtio/virtio.c:311
//#17 0xffffffff81932472 in call_driver_probe (dev=0xffff88810080b810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#18 really_probe (dev=dev@entry=0xffff88810080b810,
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#19 0xffffffff8193268e in __driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:798
//#20 0xffffffff8193272a in driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:828
//#21 0xffffffff819329b3 in __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#22 __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#23 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>,
// data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#24 0xffffffff81931f19 in driver_attach (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#25 0xffffffff81931637 in bus_add_driver (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#26 0xffffffff81933a9d in driver_register (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#27 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/virtio/virtio.c:370
//#28 0xffffffff832fb86c in virtio_net_driver_init ()
// at drivers/net/virtio_net.c:5050
//#29 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#30 0xffffffff832aa0ae in do_initcall_level (level=0x6,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#31 do_initcalls () at init/main.c:1316
//#32 do_basic_setup () at init/main.c:1335
//#33 kernel_init_freeable () at init/main.c:1548
//#34 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#35 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#36 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#37 0x0000000000000000 in ?? ()
static int __msi_domain_alloc_irqs(struct device *dev, struct irq_domain *domain,
struct msi_ctrl *ctrl)
{
struct xarray *xa = &dev->msi.data->__domains[ctrl->domid].store;
struct msi_domain_info *info = domain->host_data;
struct msi_domain_ops *ops = info->ops;
unsigned int vflags = 0, allocated = 0;
msi_alloc_info_t arg = { };
struct msi_desc *desc;
unsigned long idx;
int i, ret, virq;

ret = msi_domain_prepare_irqs(domain, dev, ctrl->nirqs, &arg);
if (ret)
return ret;
...
/*
* Interrupt can use a reserved vector and will not occupy
* a real device vector until the interrupt is requested.
*/
if (msi_check_reservation_mode(domain, info, dev))
vflags |= VIRQ_CAN_RESERVE;

xa_for_each_range(xa, idx, desc, ctrl->first, ctrl->last) {
...
if (ops->prepare_desc)
ops->prepare_desc(domain, &arg, desc);

ops->set_desc(&arg, desc);

virq = __irq_domain_alloc_irqs(domain, -1, desc->nvec_used,
dev_to_node(dev), &arg, false,
desc->affinity);
...
allocated++;
}
return 0;
}

首先,其会调用msi_domain_prepare_irqs(),准备分配msix虚拟中断资源所需的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *arg)
{
struct msi_domain_info *info = domain->host_data;
struct msi_domain_ops *ops = info->ops;

return ops->msi_prepare(domain, dev, nvec, arg);
}

//#0 x86_msi_prepare (domain=0xffff88810c30f0c0, dev=0xffff88810087d0c0,
// nvec=0x3, alloc=0xffffc900000139b0) at arch/x86/kernel/apic/msi.c:177
//#1 0xffffffff81185f4a in msi_domain_prepare_irqs (domain=0xffff88810c30f0c0,
// dev=0xffff88810087d0c0, nvec=<optimized out>, arg=0xffffc900000139b0)
// at kernel/irq/msi.c:1097
//#2 __msi_domain_alloc_irqs (dev=0xffff88810087d0c0,
// domain=0xffff88810c30f0c0, ctrl=0xffffc90000013a38)
// at kernel/irq/msi.c:1289
//#3 0xffffffff81187636 in msi_domain_alloc_locked (dev=0xffff88810087d0c0,
// ctrl=0xffffc90000013a38) at kernel/irq/msi.c:1383
//#4 msi_domain_alloc_irqs_all_locked (dev=dev@entry=0xffff88810087d0c0,
// domid=domid@entry=0x0, nirqs=nirqs@entry=0x3) at kernel/irq/msi.c:1461
//#5 0xffffffff81611c36 in pci_msi_setup_msi_irqs (
// dev=dev@entry=0xffff88810087d000, nvec=nvec@entry=0x3,
// type=type@entry=0x11) at drivers/pci/msi/irqdomain.c:17
//#6 0xffffffff816112ef in msix_setup_interrupts (dev=<optimized out>,
// entries=<optimized out>, nvec=<optimized out>,
// affd=0x0 <fixed_percpu_data>) at drivers/pci/msi/msi.c:670
//#7 msix_capability_init (dev=0xffff88810087d000, entries=<optimized out>,
// nvec=<optimized out>, affd=<optimized out>) at drivers/pci/msi/msi.c:727
//#8 __pci_enable_msix_range (dev=dev@entry=0xffff88810087d000,
// entries=entries@entry=0x0 <fixed_percpu_data>, minvec=minvec@entry=0x3,
// maxvec=maxvec@entry=0x3, affd=affd@entry=0x0 <fixed_percpu_data>,
// flags=flags@entry=0x4) at drivers/pci/msi/msi.c:833
//#9 0xffffffff8160f957 in pci_alloc_irq_vectors_affinity (
// dev=0xffff88810087d000, min_vecs=min_vecs@entry=0x3,
// max_vecs=max_vecs@entry=0x3, flags=0x4,
// affd=affd@entry=0x0 <fixed_percpu_data>) at drivers/pci/msi/api.c:270
//#10 0xffffffff81695ee9 in vp_request_msix_vectors (vdev=0xffff88810080b800,
// nvectors=<optimized out>, per_vq_vectors=<optimized out>,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:133
//#11 vp_find_vqs_msix (vdev=vdev@entry=0xffff88810080b800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff88810c2ee200,
// callbacks=callbacks@entry=0xffff88810c2ee220,
// names=names@entry=0xffff88810c2ee240,
// per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#12 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff88810080b800,
// nvqs=0x3, vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#13 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff88810080b800,
// nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>,
// names=<optimized out>, ctx=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_modern.c:604
//#14 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3,
// vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#15 virtnet_find_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4389
//#16 init_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4478
//#17 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff88810080b800)
// at drivers/net/virtio_net.c:4799
//#18 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff88810080b810)
// at drivers/virtio/virtio.c:311
//#19 0xffffffff81932472 in call_driver_probe (dev=0xffff88810080b810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#20 really_probe (dev=dev@entry=0xffff88810080b810,
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#21 0xffffffff8193268e in __driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:798
//#22 0xffffffff8193272a in driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:828
//#23 0xffffffff819329b3 in __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#24 __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#25 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>,
// data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#26 0xffffffff81931f19 in driver_attach (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#27 0xffffffff81931637 in bus_add_driver (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#28 0xffffffff81933a9d in driver_register (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#29 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/virtio/virtio.c:370
//#30 0xffffffff832fb86c in virtio_net_driver_init ()
// at drivers/net/virtio_net.c:5050
//#31 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#32 0xffffffff832aa0ae in do_initcall_level (level=0x6,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#33 do_initcalls () at init/main.c:1316
//#34 do_basic_setup () at init/main.c:1335
//#35 kernel_init_freeable () at init/main.c:1548
//#36 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#37 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#38 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#39 0x0000000000000000 in ?? ()
static int x86_msi_prepare(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *alloc)
{
struct msi_domain_info *info = domain->host_data;

init_irq_alloc_info(alloc, NULL);

switch (info->bus_token) {
case DOMAIN_BUS_PCI_DEVICE_MSI:
alloc->type = X86_IRQ_ALLOC_TYPE_PCI_MSI;
return 0;
case DOMAIN_BUS_PCI_DEVICE_MSIX:
case DOMAIN_BUS_PCI_DEVICE_IMS:
alloc->type = X86_IRQ_ALLOC_TYPE_PCI_MSIX;
return 0;
default:
return -EINVAL;
}
}

然后调用pci_msix_prepare_desc()准备虚拟中断对应的硬件中断所需的信息,这里在前面已经初始化过了,所以这里实际不会进行实际触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void pci_msix_prepare_desc(struct irq_domain *domain, msi_alloc_info_t *arg,
struct msi_desc *desc)
{
/* Don't fiddle with preallocated MSI descriptors */
if (!desc->pci.mask_base)
msix_prepare_msi_desc(to_pci_dev(desc->dev), desc);
}

void msix_prepare_msi_desc(struct pci_dev *dev, struct msi_desc *desc)
{
desc->nvec_used = 1;
desc->pci.msi_attrib.is_msix = 1;
desc->pci.msi_attrib.is_64 = 1;
desc->pci.msi_attrib.default_irq = dev->irq;
desc->pci.mask_base = dev->msix_base;
desc->pci.msi_attrib.can_mask = !pci_msi_ignore_mask &&
!desc->pci.msi_attrib.is_virtual;

if (desc->pci.msi_attrib.can_mask) {
void __iomem *addr = pci_msix_desc_addr(desc);

desc->pci.msix_ctrl = readl(addr + PCI_MSIX_ENTRY_VECTOR_CTRL);
}
}

接着调用pci_device_domain_set_desc()填充好msix虚拟中断所需的信息后,调用__irq_domain_alloc_irqs()好完成其余所有资源的分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//#0  pci_device_domain_set_desc (arg=0xffffc900000139b0, 
// desc=0xffff88810c2fe200) at drivers/pci/msi/irqdomain.c:146
//#1 0xffffffff81186091 in __msi_domain_alloc_irqs (dev=0xffff88810087d0c0,
// domain=<optimized out>, ctrl=0xffffc90000013a38) at kernel/irq/msi.c:1319
//#2 0xffffffff81187636 in msi_domain_alloc_locked (dev=0xffff88810087d0c0,
// ctrl=0xffffc90000013a38) at kernel/irq/msi.c:1383
//#3 msi_domain_alloc_irqs_all_locked (dev=dev@entry=0xffff88810087d0c0,
// domid=domid@entry=0x0, nirqs=nirqs@entry=0x3) at kernel/irq/msi.c:1461
//#4 0xffffffff81611c36 in pci_msi_setup_msi_irqs (
// dev=dev@entry=0xffff88810087d000, nvec=nvec@entry=0x3,
// type=type@entry=0x11) at drivers/pci/msi/irqdomain.c:17
//#5 0xffffffff816112ef in msix_setup_interrupts (dev=<optimized out>,
// entries=<optimized out>, nvec=<optimized out>,
// affd=0x0 <fixed_percpu_data>) at drivers/pci/msi/msi.c:670
//#6 msix_capability_init (dev=0xffff88810087d000, entries=<optimized out>,
// nvec=<optimized out>, affd=<optimized out>) at drivers/pci/msi/msi.c:727
//#7 __pci_enable_msix_range (dev=dev@entry=0xffff88810087d000,
// entries=entries@entry=0x0 <fixed_percpu_data>, minvec=minvec@entry=0x3,
// maxvec=maxvec@entry=0x3, affd=affd@entry=0x0 <fixed_percpu_data>,
// flags=flags@entry=0x4) at drivers/pci/msi/msi.c:833
//#8 0xffffffff8160f957 in pci_alloc_irq_vectors_affinity (
// dev=0xffff88810087d000, min_vecs=min_vecs@entry=0x3,
// max_vecs=max_vecs@entry=0x3, flags=0x4,
// affd=affd@entry=0x0 <fixed_percpu_data>) at drivers/pci/msi/api.c:270
//#9 0xffffffff81695ee9 in vp_request_msix_vectors (vdev=0xffff88810080b800,
// nvectors=<optimized out>, per_vq_vectors=<optimized out>,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:133
//#10 vp_find_vqs_msix (vdev=vdev@entry=0xffff88810080b800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff88810c2ee200,
// callbacks=callbacks@entry=0xffff88810c2ee220,
// names=names@entry=0xffff88810c2ee240,
// per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#11 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff88810080b800,
// nvqs=0x3, vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#12 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff88810080b800,
// nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>,
// names=<optimized out>, ctx=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_modern.c:604
//#13 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3,
// vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#14 virtnet_find_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4389
//#15 init_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4478
//#16 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff88810080b800)
// at drivers/net/virtio_net.c:4799
//#17 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff88810080b810)
// at drivers/virtio/virtio.c:311
//#18 0xffffffff81932472 in call_driver_probe (dev=0xffff88810080b810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#19 really_probe (dev=dev@entry=0xffff88810080b810,
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#20 0xffffffff8193268e in __driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:798
//#21 0xffffffff8193272a in driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:828
//#22 0xffffffff819329b3 in __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#23 __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#24 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>,
// data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#25 0xffffffff81931f19 in driver_attach (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#26 0xffffffff81931637 in bus_add_driver (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#27 0xffffffff81933a9d in driver_register (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#28 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/virtio/virtio.c:370
//#29 0xffffffff832fb86c in virtio_net_driver_init ()
// at drivers/net/virtio_net.c:5050
//#30 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#31 0xffffffff832aa0ae in do_initcall_level (level=0x6,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#32 do_initcalls () at init/main.c:1316
//#33 do_basic_setup () at init/main.c:1335
//#34 kernel_init_freeable () at init/main.c:1548
//#35 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#36 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#37 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#38 0x0000000000000000 in ?? ()
static void pci_device_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
{
arg->desc = desc;
arg->hwirq = desc->msi_index;
}

int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
unsigned int nr_irqs, int node, void *arg,
bool realloc, const struct irq_affinity_desc *affinity)
{
int ret;

if (domain == NULL) {
domain = irq_default_domain;
if (WARN(!domain, "domain is NULL; cannot allocate IRQ\n"))
return -EINVAL;
}

mutex_lock(&domain->root->mutex);
ret = irq_domain_alloc_irqs_locked(domain, irq_base, nr_irqs, node, arg,
realloc, affinity);
mutex_unlock(&domain->root->mutex);

return ret;
}

static int irq_domain_alloc_irqs_locked(struct irq_domain *domain, int irq_base,
unsigned int nr_irqs, int node, void *arg,
bool realloc, const struct irq_affinity_desc *affinity)
{
int i, ret, virq;

if (realloc && irq_base >= 0) {
virq = irq_base;
} else {
virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node,
affinity);
if (virq < 0) {
pr_debug("cannot allocate IRQ(base %d, count %d)\n",
irq_base, nr_irqs);
return virq;
}
}

if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) {
pr_debug("cannot allocate memory for IRQ%d\n", virq);
ret = -ENOMEM;
goto out_free_desc;
}

ret = irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);
if (ret < 0)
goto out_free_irq_data;

for (i = 0; i < nr_irqs; i++) {
ret = irq_domain_trim_hierarchy(virq + i);
if (ret)
goto out_free_irq_data;
}

for (i = 0; i < nr_irqs; i++)
irq_domain_insert_irq(virq + i);

return virq;
...
}

可以看到其会在irq_domain_alloc_descs中从spare_irqs中分配虚拟中断资源,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int irq_domain_alloc_descs(int virq, unsigned int cnt, irq_hw_number_t hwirq,
int node, const struct irq_affinity_desc *affinity)
{
unsigned int hint;

if (virq >= 0) {
virq = __irq_alloc_descs(virq, virq, cnt, node, THIS_MODULE,
affinity);
} else {
hint = hwirq % nr_irqs;
if (hint == 0)
hint++;
virq = __irq_alloc_descs(-1, hint, cnt, node, THIS_MODULE,
affinity);
if (virq <= 0 && hint > 1) {
virq = __irq_alloc_descs(-1, 1, cnt, node, THIS_MODULE,
affinity);
}
}

return virq;
}

int __ref
__irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,
struct module *owner, const struct irq_affinity_desc *affinity)
{
...
mutex_lock(&sparse_irq_lock);

start = irq_find_free_area(from, cnt);
ret = -EEXIST;
if (irq >=0 && start != irq)
goto unlock;

if (start + cnt > nr_irqs) {
ret = irq_expand_nr_irqs(start + cnt);
if (ret)
goto unlock;
}
ret = alloc_descs(start, cnt, node, affinity, owner);
unlock:
mutex_unlock(&sparse_irq_lock);
return ret;
}

然后调用irq_domain_alloc_irq_data()给虚拟中断中每个irq_domain初始化irq_data资源,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static int irq_domain_alloc_irq_data(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs)
{
struct irq_data *irq_data;
struct irq_domain *parent;
int i;

/* The outermost irq_data is embedded in struct irq_desc */
for (i = 0; i < nr_irqs; i++) {
irq_data = irq_get_irq_data(virq + i);
irq_data->domain = domain;

for (parent = domain->parent; parent; parent = parent->parent) {
irq_data = irq_domain_insert_irq_data(parent, irq_data);
if (!irq_data) {
irq_domain_free_irq_data(virq, i + 1);
return -ENOMEM;
}
}
}

return 0;
}

static struct irq_data *irq_domain_insert_irq_data(struct irq_domain *domain,
struct irq_data *child)
{
struct irq_data *irq_data;

irq_data = kzalloc_node(sizeof(*irq_data), GFP_KERNEL,
irq_data_get_node(child));
if (irq_data) {
child->parent_data = irq_data;
irq_data->irq = child->irq;
irq_data->common = child->common;
irq_data->domain = domain;
}

return irq_data;
}

最后调用irq_domain_alloc_irqs_hierarchy()完成层次结构的irq_domain的分配回调函数的调用,填充irq_data等信息,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
int irq_domain_alloc_irqs_hierarchy(struct irq_domain *domain,
unsigned int irq_base,
unsigned int nr_irqs, void *arg)
{
if (!domain->ops->alloc) {
pr_debug("domain->ops->alloc() is NULL\n");
return -ENOSYS;
}

return domain->ops->alloc(domain, irq_base, nr_irqs, arg);
}

static int msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
struct msi_domain_info *info = domain->host_data;
struct msi_domain_ops *ops = info->ops;
irq_hw_number_t hwirq = ops->get_hwirq(info, arg);
int i, ret;

if (irq_find_mapping(domain, hwirq) > 0)
return -EEXIST;

if (domain->parent) {
ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg);
if (ret < 0)
return ret;
}

for (i = 0; i < nr_irqs; i++) {
ret = ops->msi_init(domain, info, virq + i, hwirq + i, arg);
if (ret < 0) {
if (ops->msi_free) {
for (i--; i > 0; i--)
ops->msi_free(domain, info, virq + i);
}
irq_domain_free_irqs_top(domain, virq, nr_irqs);
return ret;
}
}

return 0;
}

//#0 x86_vector_alloc_irqs (domain=0xffff88810012b000, virq=0x1b, nr_irqs=0x1,
// arg=0xffffc900000139b0) at arch/x86/kernel/apic/vector.c:544
//#1 0xffffffff811858fd in msi_domain_alloc (domain=0xffff88810c30f0c0,
// virq=0x1b, nr_irqs=0x1, arg=0xffffc900000139b0) at kernel/irq/msi.c:696
//#2 0xffffffff81183089 in irq_domain_alloc_irqs_hierarchy (
// domain=0xffff88810c30f0c0, irq_base=0x1b, nr_irqs=0x1,
// arg=0xffffc900000139b0) at kernel/irq/irqdomain.c:1471
//#3 irq_domain_alloc_irqs_locked (domain=domain@entry=0xffff88810c30f0c0,
// irq_base=irq_base@entry=0xffffffff, nr_irqs=nr_irqs@entry=0x1,
// node=node@entry=0xffffffff, arg=arg@entry=0xffffc900000139b0,
// realloc=realloc@entry=0x0, affinity=0x0 <fixed_percpu_data>)
// at kernel/irq/irqdomain.c:1498
//#4 0xffffffff811836dc in __irq_domain_alloc_irqs (
// domain=domain@entry=0xffff88810c30f0c0,
// irq_base=irq_base@entry=0xffffffff, nr_irqs=0x1, node=0xffffffff,
// arg=arg@entry=0xffffc900000139b0, realloc=realloc@entry=0x0,
// affinity=0x0 <fixed_percpu_data>) at kernel/irq/irqdomain.c:1555
//#5 0xffffffff811860b7 in __msi_domain_alloc_irqs (dev=0xffff88810087d0c0,
// domain=<optimized out>, ctrl=0xffffc90000013a38)
// at ./include/linux/device.h:882
//#6 0xffffffff81187636 in msi_domain_alloc_locked (dev=0xffff88810087d0c0,
// ctrl=0xffffc90000013a38) at kernel/irq/msi.c:1383
//#7 msi_domain_alloc_irqs_all_locked (dev=dev@entry=0xffff88810087d0c0,
// domid=domid@entry=0x0, nirqs=nirqs@entry=0x3) at kernel/irq/msi.c:1461
//#8 0xffffffff81611c36 in pci_msi_setup_msi_irqs (
// dev=dev@entry=0xffff88810087d000, nvec=nvec@entry=0x3,
// type=type@entry=0x11) at drivers/pci/msi/irqdomain.c:17
//#9 0xffffffff816112ef in msix_setup_interrupts (dev=<optimized out>,
// entries=<optimized out>, nvec=<optimized out>,
// affd=0x0 <fixed_percpu_data>) at drivers/pci/msi/msi.c:670
//#10 msix_capability_init (dev=0xffff88810087d000, entries=<optimized out>,
// nvec=<optimized out>, affd=<optimized out>) at drivers/pci/msi/msi.c:727
//#11 __pci_enable_msix_range (dev=dev@entry=0xffff88810087d000,
// entries=entries@entry=0x0 <fixed_percpu_data>, minvec=minvec@entry=0x3,
// maxvec=maxvec@entry=0x3, affd=affd@entry=0x0 <fixed_percpu_data>,
// flags=flags@entry=0x4) at drivers/pci/msi/msi.c:833
//#12 0xffffffff8160f957 in pci_alloc_irq_vectors_affinity (
// dev=0xffff88810087d000, min_vecs=min_vecs@entry=0x3,
// max_vecs=max_vecs@entry=0x3, flags=0x4,
// affd=affd@entry=0x0 <fixed_percpu_data>) at drivers/pci/msi/api.c:270
//#13 0xffffffff81695ee9 in vp_request_msix_vectors (vdev=0xffff88810080b800,
// nvectors=<optimized out>, per_vq_vectors=<optimized out>,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:133
//#14 vp_find_vqs_msix (vdev=vdev@entry=0xffff88810080b800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff88810c2ee200,
// callbacks=callbacks@entry=0xffff88810c2ee220,
// names=names@entry=0xffff88810c2ee240,
// per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#15 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff88810080b800,
// nvqs=0x3, vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#16 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff88810080b800,
// nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>,
// names=<optimized out>, ctx=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_modern.c:604
//#17 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3,
// vqs=0xffff88810c2ee200, callbacks=0xffff88810c2ee220,
// names=0xffff88810c2ee240, ctx=0xffff888100380c50,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#18 virtnet_find_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4389
//#19 init_vqs (vi=0xffff88810cdec920) at drivers/net/virtio_net.c:4478
//#20 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff88810080b800)
// at drivers/net/virtio_net.c:4799
//#21 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff88810080b810)
// at drivers/virtio/virtio.c:311
//#22 0xffffffff81932472 in call_driver_probe (dev=0xffff88810080b810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#23 really_probe (dev=dev@entry=0xffff88810080b810,
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#24 0xffffffff8193268e in __driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:798
//#25 0xffffffff8193272a in driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff88810080b810) at drivers/base/dd.c:828
//#26 0xffffffff819329b3 in __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#27 __driver_attach (dev=0xffff88810080b810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#28 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>,
// data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#29 0xffffffff81931f19 in driver_attach (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#30 0xffffffff81931637 in bus_add_driver (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#31 0xffffffff81933a9d in driver_register (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#32 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/virtio/virtio.c:370
//#33 0xffffffff832fb86c in virtio_net_driver_init ()
// at drivers/net/virtio_net.c:5050
//#34 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#35 0xffffffff832aa0ae in do_initcall_level (level=0x6,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#36 do_initcalls () at init/main.c:1316
//#37 do_basic_setup () at init/main.c:1335
//#38 kernel_init_freeable () at init/main.c:1548
//#39 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#40 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#41 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#42 0x0000000000000000 in ?? ()
static int x86_vector_alloc_irqs(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
struct irq_alloc_info *info = arg;
struct apic_chip_data *apicd;
struct irq_data *irqd;
int i, err, node;

if (apic_is_disabled)
return -ENXIO;

/*
* Catch any attempt to touch the cascade interrupt on a PIC
* equipped system.
*/
if (WARN_ON_ONCE(info->flags & X86_IRQ_ALLOC_LEGACY &&
virq == PIC_CASCADE_IR))
return -EINVAL;

for (i = 0; i < nr_irqs; i++) {
irqd = irq_domain_get_irq_data(domain, virq + i);
BUG_ON(!irqd);
node = irq_data_get_node(irqd);
WARN_ON_ONCE(irqd->chip_data);
apicd = alloc_apic_chip_data(node);
if (!apicd) {
err = -ENOMEM;
goto error;
}

apicd->irq = virq + i;
irqd->chip = &lapic_controller;
irqd->chip_data = apicd;
irqd->hwirq = virq + i;
irqd_set_single_target(irqd);
/*
* Prevent that any of these interrupts is invoked in
* non interrupt context via e.g. generic_handle_irq()
* as that can corrupt the affinity move state.
*/
irqd_set_handle_enforce_irqctx(irqd);

/* Don't invoke affinity setter on deactivated interrupts */
irqd_set_affinity_on_activate(irqd);

/*
* Legacy vectors are already assigned when the IOAPIC
* takes them over. They stay on the same vector. This is
* required for check_timer() to work correctly as it might
* switch back to legacy mode. Only update the hardware
* config.
*/
if (info->flags & X86_IRQ_ALLOC_LEGACY) {
if (!vector_configure_legacy(virq + i, irqd, apicd))
continue;
}

err = assign_irq_vector_policy(irqd, info);
trace_vector_setup(virq + i, false, err);
if (err) {
irqd->chip_data = NULL;
free_apic_chip_data(apicd);
goto error;
}
}

return 0;

error:
x86_vector_free_irqs(domain, virq, i);
return err;
}

static int msi_domain_ops_init(struct irq_domain *domain,
struct msi_domain_info *info,
unsigned int virq, irq_hw_number_t hwirq,
msi_alloc_info_t *arg)
{
irq_domain_set_hwirq_and_chip(domain, virq, hwirq, info->chip,
info->chip_data);
if (info->handler && info->handler_name) {
__irq_set_handler(virq, info->handler, 0, info->handler_name);
if (info->handler_data)
irq_set_handler_data(virq, info->handler_data);
}
return 0;
}
激活

此时已经分配好了中断所需的所有资源,需要在request_irq()中激活中断,完成真正的中断映射,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//#0  request_irq (irq=0x1b, handler=0xffffffff816956a0 <vp_config_changed>, 
// flags=0x0, name=0xffff888100e46400 "virtio1-config",
// dev=0xffff888100de6800) at ./include/linux/interrupt.h:171
//#1 vp_request_msix_vectors (vdev=0xffff888100de6800,
// nvectors=<optimized out>, per_vq_vectors=<optimized out>,
// desc=<optimized out>) at drivers/virtio/virtio_pci_common.c:143
//#2 vp_find_vqs_msix (vdev=vdev@entry=0xffff888100de6800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff888100a5ca40,
// callbacks=callbacks@entry=0xffff888100a5ca60,
// names=names@entry=0xffff888100a5ca80,
// per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff88810d520198,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#3 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff888100de6800,
// nvqs=0x3, vqs=0xffff888100a5ca40, callbacks=0xffff888100a5ca60,
// names=0xffff888100a5ca80, ctx=0xffff88810d520198,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#4 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff888100de6800,
// nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>,
// names=<optimized out>, ctx=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_modern.c:604
//#5 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3,
// vqs=0xffff888100a5ca40, callbacks=0xffff888100a5ca60,
// names=0xffff888100a5ca80, ctx=0xffff88810d520198,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#6 virtnet_find_vqs (vi=0xffff88810d63a920) at drivers/net/virtio_net.c:4389
//#7 init_vqs (vi=0xffff88810d63a920) at drivers/net/virtio_net.c:4478
//#8 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff888100de6800)
// at drivers/net/virtio_net.c:4799
//#9 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff888100de6810)
// at drivers/virtio/virtio.c:311
//#10 0xffffffff81932472 in call_driver_probe (dev=0xffff888100de6810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#11 really_probe (dev=dev@entry=0xffff888100de6810,
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#12 0xffffffff8193268e in __driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff888100de6810) at drivers/base/dd.c:798
//#13 0xffffffff8193272a in driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff888100de6810) at drivers/base/dd.c:828
//#14 0xffffffff819329b3 in __driver_attach (dev=0xffff888100de6810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#15 __driver_attach (dev=0xffff888100de6810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#16 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>,
// data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#17 0xffffffff81931f19 in driver_attach (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#18 0xffffffff81931637 in bus_add_driver (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#19 0xffffffff81933a9d in driver_register (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#20 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/virtio/virtio.c:370
//#21 0xffffffff832fb86c in virtio_net_driver_init ()
// at drivers/net/virtio_net.c:5050
//#22 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#23 0xffffffff832aa0ae in do_initcall_level (level=0x6,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#24 do_initcalls () at init/main.c:1316
//#25 do_basic_setup () at init/main.c:1335
//#26 kernel_init_freeable () at init/main.c:1548
//#27 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#28 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#29 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#30 0x0000000000000000 in ?? ()
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
...
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;

if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;

if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}

action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;

action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
...
retval = __setup_irq(irq, desc, action);
...
return retval;
}

static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
struct irqaction *old, **old_ptr;
unsigned long flags, thread_mask = 0;
int ret, nested, shared = 0;
...
new->irq = irq;
...
old_ptr = &desc->action;
old = *old_ptr;
if (old) {
...
/* add new interrupt at end of irq queue */
do {
/*
* Or all existing action->thread_mask bits,
* so we can find the next zero bit for this
* new action.
*/
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
}
...

if (!shared) {
...
/*
* Activate the interrupt. That activation must happen
* independently of IRQ_NOAUTOEN. request_irq() can fail
* and the callers are supposed to handle
* that. enable_irq() of an interrupt requested with
* IRQ_NOAUTOEN is not supposed to fail. The activation
* keeps it in shutdown mode, it merily associates
* resources if necessary and if that's not possible it
* fails. Interrupts which are in managed shutdown mode
* will simply ignore that activation request.
*/
ret = irq_activate(desc);
if (ret)
goto out_unlock;

desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
IRQS_ONESHOT | IRQS_WAITING);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

} else if (new->flags & IRQF_TRIGGER_MASK) {
...
}

*old_ptr = new;
...
return 0;
...
}

int irq_activate(struct irq_desc *desc)
{
struct irq_data *d = irq_desc_get_irq_data(desc);

if (!irqd_affinity_is_managed(d))
return irq_domain_activate_irq(d, false);
return 0;
}

int irq_domain_activate_irq(struct irq_data *irq_data, bool reserve)
{
int ret = 0;

if (!irqd_is_activated(irq_data))
ret = __irq_domain_activate_irq(irq_data, reserve);
if (!ret)
irqd_set_activated(irq_data);
return ret;
}

static int __irq_domain_activate_irq(struct irq_data *irqd, bool reserve)
{
int ret = 0;

if (irqd && irqd->domain) {
struct irq_domain *domain = irqd->domain;

if (irqd->parent_data)
ret = __irq_domain_activate_irq(irqd->parent_data,
reserve);
if (!ret && domain->ops->activate) {
ret = domain->ops->activate(domain, irqd, reserve);
/* Rollback in case of error */
if (ret && irqd->parent_data)
__irq_domain_deactivate_irq(irqd->parent_data);
}
}
return ret;
}

可以看到,其在设置完irq_action后,会在__irq_domain_activate_irq()中分别调用所有层级irq_domain(即lapic层级和msix层级)来激活中断。

对于lapic层级,如前面章节所说,其会在对应irq_domain_opsx86_vector_activate()中激活lapic层级的相关设置:即在irq_matrix_alloc()中更新每个cpu的中断向量资源vector_matrix,在apic_update_vector()中设置对应cpu中断向量服务服务例程vector_irq为对应的虚拟中断信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//#0  assign_vector_locked (irqd=0xffff888100a23d00, 
// dest=dest@entry=0xffffffff83536870 <vector_searchmask>)
// at arch/x86/kernel/apic/vector.c:234
//#1 0xffffffff810dea60 in assign_irq_vector_any_locked (
// irqd=irqd@entry=0xffff888100a23d00) at arch/x86/kernel/apic/vector.c:296
//#2 0xffffffff810deb18 in activate_reserved (irqd=0xffff888100a23d00)
// at arch/x86/kernel/apic/vector.c:404
//#3 x86_vector_activate (dom=<optimized out>, irqd=0xffff888100a23d00,
// reserve=0x0) at arch/x86/kernel/apic/vector.c:473
//#4 0xffffffff811818ae in __irq_domain_activate_irq (irqd=0xffff888100a23d00,
// reserve=reserve@entry=0x0) at kernel/irq/irqdomain.c:1830
//#5 0xffffffff8118188d in __irq_domain_activate_irq (
// irqd=irqd@entry=0xffff888100241428, reserve=reserve@entry=0x0)
// at kernel/irq/irqdomain.c:1827
//#6 0xffffffff81183b48 in irq_domain_activate_irq (
// irq_data=irq_data@entry=0xffff888100241428, reserve=reserve@entry=0x0)
// at kernel/irq/irqdomain.c:1853
//#7 0xffffffff81180093 in irq_activate (desc=desc@entry=0xffff888100241400)
// at kernel/irq/chip.c:293
//#8 0xffffffff8117d0a1 in __setup_irq (irq=irq@entry=0x1b,
// desc=desc@entry=0xffff888100241400, new=new@entry=0xffff88810cc75880)
// at kernel/irq/manage.c:1754
//#9 0xffffffff8117d580 in request_threaded_irq (irq=0x1b,
// handler=<optimized out>,
// handler@entry=0xffffffff816956a0 <vp_config_changed>,
// thread_fn=thread_fn@entry=0x0 <fixed_percpu_data>,
// irqflags=irqflags@entry=0x0,
// devname=devname@entry=0xffff888100d86400 "virtio1-config",
// dev_id=dev_id@entry=0xffff888100be6800) at kernel/irq/manage.c:2207
//#10 0xffffffff81695f58 in request_irq (irq=<optimized out>,
// handler=0xffffffff816956a0 <vp_config_changed>, flags=0x0,
// name=0xffff888100d86400 "virtio1-config", dev=0xffff888100be6800)
// at ./include/linux/interrupt.h:171
//#11 vp_request_msix_vectors (vdev=0xffff888100be6800,
// nvectors=<optimized out>, per_vq_vectors=<optimized out>,
// desc=<optimized out>) at drivers/virtio/virtio_pci_common.c:143
//#12 vp_find_vqs_msix (vdev=vdev@entry=0xffff888100be6800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff8881009e5a40,
// callbacks=callbacks@entry=0xffff8881009e5a60,
// names=names@entry=0xffff8881009e5a80,
// per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff88810cc60198,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#13 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff888100be6800,
// nvqs=0x3, vqs=0xffff8881009e5a40, callbacks=0xffff8881009e5a60,
// names=0xffff8881009e5a80, ctx=0xffff88810cc60198,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#14 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff888100be6800,
// nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>,
// names=<optimized out>, ctx=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_modern.c:604
//#15 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3,
// vqs=0xffff8881009e5a40, callbacks=0xffff8881009e5a60,
// names=0xffff8881009e5a80, ctx=0xffff88810cc60198,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#16 virtnet_find_vqs (vi=0xffff88810ce3a920) at drivers/net/virtio_net.c:4389
//#17 init_vqs (vi=0xffff88810ce3a920) at drivers/net/virtio_net.c:4478
//#18 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff888100be6800)
// at drivers/net/virtio_net.c:4799
//#19 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff888100be6810)
// at drivers/virtio/virtio.c:311
//#20 0xffffffff81932472 in call_driver_probe (dev=0xffff888100be6810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#21 really_probe (dev=dev@entry=0xffff888100be6810,
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#22 0xffffffff8193268e in __driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff888100be6810) at drivers/base/dd.c:798
//#23 0xffffffff8193272a in driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff888100be6810) at drivers/base/dd.c:828
//#24 0xffffffff819329b3 in __driver_attach (dev=0xffff888100be6810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#25 __driver_attach (dev=0xffff888100be6810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#26 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>,
// data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#27 0xffffffff81931f19 in driver_attach (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#28 0xffffffff81931637 in bus_add_driver (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#29 0xffffffff81933a9d in driver_register (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#30 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/virtio/virtio.c:370
//#31 0xffffffff832fb86c in virtio_net_driver_init ()
// at drivers/net/virtio_net.c:5050
//#32 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#33 0xffffffff832aa0ae in do_initcall_level (level=0x6,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#34 do_initcalls () at init/main.c:1316
//#35 do_basic_setup () at init/main.c:1335
//#36 kernel_init_freeable () at init/main.c:1548
//#37 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#38 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#39 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#40 0x0000000000000000 in ?? ()
static int x86_vector_activate(struct irq_domain *dom, struct irq_data *irqd,
bool reserve)
{
struct apic_chip_data *apicd = apic_chip_data(irqd);
unsigned long flags;
int ret = 0;

trace_vector_activate(irqd->irq, apicd->is_managed,
apicd->can_reserve, reserve);

raw_spin_lock_irqsave(&vector_lock, flags);
if (!apicd->can_reserve && !apicd->is_managed)
assign_irq_vector_any_locked(irqd);
else if (reserve || irqd_is_managed_and_shutdown(irqd))
vector_assign_managed_shutdown(irqd);
else if (apicd->is_managed)
ret = activate_managed(irqd);
else if (apicd->has_reserved)
ret = activate_reserved(irqd);
raw_spin_unlock_irqrestore(&vector_lock, flags);
return ret;
}

static int
assign_vector_locked(struct irq_data *irqd, const struct cpumask *dest)
{
...
vector = irq_matrix_alloc(vector_matrix, dest, resvd, &cpu);
...
apic_update_vector(irqd, vector, cpu);

return 0;
}

static void apic_update_vector(struct irq_data *irqd, unsigned int newvec,
unsigned int newcpu)
{
...
apicd->vector = newvec;
apicd->cpu = newcpu;
BUG_ON(!IS_ERR_OR_NULL(per_cpu(vector_irq, newcpu)[newvec]));
per_cpu(vector_irq, newcpu)[newvec] = desc;
}

而对于msix层级,如前面章节所说,其会在对应的irq_domain_opsmsi_domain_activate()中激活msix层级的相关设置:即在__irq_msi_compose_msg()中拼凑msi所需的Message信息,并在pci_write_msg_msix()中写入对应的msix capability指定的数组中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
static int msi_domain_activate(struct irq_domain *domain,
struct irq_data *irq_data, bool early)
{
struct msi_msg msg[2] = { [1] = { }, };

BUG_ON(irq_chip_compose_msi_msg(irq_data, msg));
msi_check_level(irq_data->domain, msg);
irq_chip_write_msi_msg(irq_data, msg);
return 0;
}

/**
* irq_chip_compose_msi_msg - Compose msi message for a irq chip
* @data: Pointer to interrupt specific data
* @msg: Pointer to the MSI message
*
* For hierarchical domains we find the first chip in the hierarchy
* which implements the irq_compose_msi_msg callback. For non
* hierarchical we use the top level chip.
*/
int irq_chip_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
{
struct irq_data *pos;

for (pos = NULL; !pos && data; data = irqd_get_parent_data(data)) {
if (data->chip && data->chip->irq_compose_msi_msg)
pos = data;
}

if (!pos)
return -ENOSYS;

pos->chip->irq_compose_msi_msg(pos, msg);
return 0;
}

//#0 __irq_msi_compose_msg (cfg=0xffff888100a23e00, msg=0xffffc90000013a48,
// dmar=0x0) at arch/x86/kernel/apic/apic.c:2272
//#1 0xffffffff811809e8 in irq_chip_compose_msi_msg (data=<optimized out>,
// data@entry=0xffff888100241428, msg=msg@entry=0xffffc90000013a48)
// at kernel/irq/chip.c:1572
//#2 0xffffffff81185de0 in msi_domain_activate (domain=<optimized out>,
// irq_data=0xffff888100241428, early=<optimized out>) at kernel/irq/msi.c:669
//#3 0xffffffff811818ae in __irq_domain_activate_irq (
// irqd=irqd@entry=0xffff888100241428, reserve=reserve@entry=0x0)
// at kernel/irq/irqdomain.c:1830
//#4 0xffffffff81183b48 in irq_domain_activate_irq (
// irq_data=irq_data@entry=0xffff888100241428, reserve=reserve@entry=0x0)
// at kernel/irq/irqdomain.c:1853
//#5 0xffffffff81180093 in irq_activate (desc=desc@entry=0xffff888100241400)
// at kernel/irq/chip.c:293
//#6 0xffffffff8117d0a1 in __setup_irq (irq=irq@entry=0x1b,
// desc=desc@entry=0xffff888100241400, new=new@entry=0xffff88810cc75880)
// at kernel/irq/manage.c:1754
//#7 0xffffffff8117d580 in request_threaded_irq (irq=0x1b,
// handler=<optimized out>,
// handler@entry=0xffffffff816956a0 <vp_config_changed>,
// thread_fn=thread_fn@entry=0x0 <fixed_percpu_data>,
// irqflags=irqflags@entry=0x0,
// devname=devname@entry=0xffff888100d86400 "virtio1-config",
// dev_id=dev_id@entry=0xffff888100be6800) at kernel/irq/manage.c:2207
//#8 0xffffffff81695f58 in request_irq (irq=<optimized out>,
// handler=0xffffffff816956a0 <vp_config_changed>, flags=0x0,
// name=0xffff888100d86400 "virtio1-config", dev=0xffff888100be6800)
// at ./include/linux/interrupt.h:171
//#9 vp_request_msix_vectors (vdev=0xffff888100be6800,
// nvectors=<optimized out>, per_vq_vectors=<optimized out>,
// desc=<optimized out>) at drivers/virtio/virtio_pci_common.c:143
//#10 vp_find_vqs_msix (vdev=vdev@entry=0xffff888100be6800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff8881009e5a40,
// callbacks=callbacks@entry=0xffff8881009e5a60,
// names=names@entry=0xffff8881009e5a80,
// per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff88810cc60198,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#11 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff888100be6800,
// nvqs=0x3, vqs=0xffff8881009e5a40, callbacks=0xffff8881009e5a60,
// names=0xffff8881009e5a80, ctx=0xffff88810cc60198,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#12 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff888100be6800,
// nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>,
// names=<optimized out>, ctx=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_modern.c:604
//#13 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3,
// vqs=0xffff8881009e5a40, callbacks=0xffff8881009e5a60,
// names=0xffff8881009e5a80, ctx=0xffff88810cc60198,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#14 virtnet_find_vqs (vi=0xffff88810ce3a920) at drivers/net/virtio_net.c:4389
//#15 init_vqs (vi=0xffff88810ce3a920) at drivers/net/virtio_net.c:4478
//#16 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff888100be6800)
// at drivers/net/virtio_net.c:4799
//#17 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff888100be6810)
// at drivers/virtio/virtio.c:311
//#18 0xffffffff81932472 in call_driver_probe (dev=0xffff888100be6810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#19 really_probe (dev=dev@entry=0xffff888100be6810,
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#20 0xffffffff8193268e in __driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff888100be6810) at drivers/base/dd.c:798
//#21 0xffffffff8193272a in driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff888100be6810) at drivers/base/dd.c:828
//#22 0xffffffff819329b3 in __driver_attach (dev=0xffff888100be6810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#23 __driver_attach (dev=0xffff888100be6810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#24 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>,
// data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#25 0xffffffff81931f19 in driver_attach (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#26 0xffffffff81931637 in bus_add_driver (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#27 0xffffffff81933a9d in driver_register (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#28 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/virtio/virtio.c:370
//#29 0xffffffff832fb86c in virtio_net_driver_init ()
// at drivers/net/virtio_net.c:5050
//#30 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#31 0xffffffff832aa0ae in do_initcall_level (level=0x6,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#32 do_initcalls () at init/main.c:1316
//#33 do_basic_setup () at init/main.c:1335
//#34 kernel_init_freeable () at init/main.c:1548
//#35 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#36 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#37 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#38 0x0000000000000000 in ?? ()
void __irq_msi_compose_msg(struct irq_cfg *cfg, struct msi_msg *msg,
bool dmar)
{
memset(msg, 0, sizeof(*msg));

msg->arch_addr_lo.base_address = X86_MSI_BASE_ADDRESS_LOW;
msg->arch_addr_lo.dest_mode_logical = apic->dest_mode_logical;
msg->arch_addr_lo.destid_0_7 = cfg->dest_apicid & 0xFF;

msg->arch_data.delivery_mode = APIC_DELIVERY_MODE_FIXED;
msg->arch_data.vector = cfg->vector;

msg->address_hi = X86_MSI_BASE_ADDRESS_HIGH;
/*
* Only the IOMMU itself can use the trick of putting destination
* APIC ID into the high bits of the address. Anything else would
* just be writing to memory if it tried that, and needs IR to
* address APICs which can't be addressed in the normal 32-bit
* address range at 0xFFExxxxx. That is typically just 8 bits, but
* some hypervisors allow the extended destination ID field in bits
* 5-11 to be used, giving support for 15 bits of APIC IDs in total.
*/
if (dmar)
msg->arch_addr_hi.destid_8_31 = cfg->dest_apicid >> 8;
else if (virt_ext_dest_id && cfg->dest_apicid < 0x8000)
msg->arch_addr_lo.virt_destid_8_14 = cfg->dest_apicid >> 8;
else
WARN_ON_ONCE(cfg->dest_apicid > 0xFF);
}

static inline void irq_chip_write_msi_msg(struct irq_data *data,
struct msi_msg *msg)
{
data->chip->irq_write_msi_msg(data, msg);
}

static void pci_msi_domain_write_msg(struct irq_data *irq_data, struct msi_msg *msg)
{
struct msi_desc *desc = irq_data_get_msi_desc(irq_data);

/*
* For MSI-X desc->irq is always equal to irq_data->irq. For
* MSI only the first interrupt of MULTI MSI passes the test.
*/
if (desc->irq == irq_data->irq)
__pci_write_msi_msg(desc, msg);
}

void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
{
struct pci_dev *dev = msi_desc_to_pci_dev(entry);

if (dev->current_state != PCI_D0 || pci_dev_is_disconnected(dev)) {
/* Don't touch the hardware now */
} else if (entry->pci.msi_attrib.is_msix) {
pci_write_msg_msix(entry, msg);
} else {
pci_write_msg_msi(dev, entry, msg);
}

entry->msg = *msg;

if (entry->write_msi_msg)
entry->write_msi_msg(entry, entry->write_msi_msg_data);
}

//#0 pci_write_msg_msix (desc=0xffff88810cc75700, msg=0xffffc90000013a48)
// at drivers/pci/msi/msi.c:212
//#1 __pci_write_msi_msg (entry=0xffff88810cc75700, msg=0xffffc90000013a48)
// at drivers/pci/msi/msi.c:243
//#2 0xffffffff81185e06 in irq_chip_write_msi_msg (data=0xffff888100241428,
// msg=0xffffc90000013a48) at kernel/irq/msi.c:620
//#3 msi_domain_activate (domain=<optimized out>, irq_data=0xffff888100241428,
// early=<optimized out>) at kernel/irq/msi.c:671
//#4 0xffffffff811818ae in __irq_domain_activate_irq (
// irqd=irqd@entry=0xffff888100241428, reserve=reserve@entry=0x0)
// at kernel/irq/irqdomain.c:1830
//#5 0xffffffff81183b48 in irq_domain_activate_irq (
// irq_data=irq_data@entry=0xffff888100241428, reserve=reserve@entry=0x0)
// at kernel/irq/irqdomain.c:1853
//#6 0xffffffff81180093 in irq_activate (desc=desc@entry=0xffff888100241400)
// at kernel/irq/chip.c:293
//#7 0xffffffff8117d0a1 in __setup_irq (irq=irq@entry=0x1b,
// desc=desc@entry=0xffff888100241400, new=new@entry=0xffff88810cc75880)
// at kernel/irq/manage.c:1754
//#8 0xffffffff8117d580 in request_threaded_irq (irq=0x1b,
// handler=<optimized out>,
// handler@entry=0xffffffff816956a0 <vp_config_changed>,
// thread_fn=thread_fn@entry=0x0 <fixed_percpu_data>,
// irqflags=irqflags@entry=0x0,
// devname=devname@entry=0xffff888100d86400 "virtio1-config",
// dev_id=dev_id@entry=0xffff888100be6800) at kernel/irq/manage.c:2207
//#9 0xffffffff81695f58 in request_irq (irq=<optimized out>,
// handler=0xffffffff816956a0 <vp_config_changed>, flags=0x0,
// name=0xffff888100d86400 "virtio1-config", dev=0xffff888100be6800)
// at ./include/linux/interrupt.h:171
//#10 vp_request_msix_vectors (vdev=0xffff888100be6800,
// nvectors=<optimized out>, per_vq_vectors=<optimized out>,
// desc=<optimized out>) at drivers/virtio/virtio_pci_common.c:143
//#11 vp_find_vqs_msix (vdev=vdev@entry=0xffff888100be6800, nvqs=nvqs@entry=0x3,
// vqs=vqs@entry=0xffff8881009e5a40,
// callbacks=callbacks@entry=0xffff8881009e5a60,
// names=names@entry=0xffff8881009e5a80,
// per_vq_vectors=per_vq_vectors@entry=0x1, ctx=0xffff88810cc60198,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:312
//#12 0xffffffff81696319 in vp_find_vqs (vdev=vdev@entry=0xffff888100be6800,
// nvqs=0x3, vqs=0xffff8881009e5a40, callbacks=0xffff8881009e5a60,
// names=0xffff8881009e5a80, ctx=0xffff88810cc60198,
// desc=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_pci_common.c:408
//#13 0xffffffff81694826 in vp_modern_find_vqs (vdev=0xffff888100be6800,
// nvqs=<optimized out>, vqs=<optimized out>, callbacks=<optimized out>,
// names=<optimized out>, ctx=<optimized out>, desc=0x0 <fixed_percpu_data>)
// at drivers/virtio/virtio_pci_modern.c:604
//#14 0xffffffff819e3e34 in virtio_find_vqs_ctx (vdev=<optimized out>, nvqs=0x3,
// vqs=0xffff8881009e5a40, callbacks=0xffff8881009e5a60,
// names=0xffff8881009e5a80, ctx=0xffff88810cc60198,
// desc=0x0 <fixed_percpu_data>) at ./include/linux/virtio_config.h:242
//#15 virtnet_find_vqs (vi=0xffff88810ce3a920) at drivers/net/virtio_net.c:4389
//#16 init_vqs (vi=0xffff88810ce3a920) at drivers/net/virtio_net.c:4478
//#17 0xffffffff819e4b86 in virtnet_probe (vdev=0xffff888100be6800)
// at drivers/net/virtio_net.c:4799
//#18 0xffffffff8168f3fd in virtio_dev_probe (_d=0xffff888100be6810)
// at drivers/virtio/virtio.c:311
//#19 0xffffffff81932472 in call_driver_probe (dev=0xffff888100be6810,
// drv=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:578
//#20 really_probe (dev=dev@entry=0xffff888100be6810,
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:656
//#21 0xffffffff8193268e in __driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff888100be6810) at drivers/base/dd.c:798
//#22 0xffffffff8193272a in driver_probe_device (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>,
// dev=dev@entry=0xffff888100be6810) at drivers/base/dd.c:828
//#23 0xffffffff819329b3 in __driver_attach (dev=0xffff888100be6810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1214
//#24 __driver_attach (dev=0xffff888100be6810,
// data=0xffffffff82c10280 <virtio_net_driver>) at drivers/base/dd.c:1154
//#25 0xffffffff8193020a in bus_for_each_dev (bus=<optimized out>,
// start=start@entry=0x0 <fixed_percpu_data>,
// data=data@entry=0xffffffff82c10280 <virtio_net_driver>,
// fn=fn@entry=0xffffffff81932930 <__driver_attach>) at drivers/base/bus.c:368
//#26 0xffffffff81931f19 in driver_attach (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/dd.c:1231
//#27 0xffffffff81931637 in bus_add_driver (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/bus.c:673
//#28 0xffffffff81933a9d in driver_register (
// drv=drv@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/base/driver.c:246
//#29 0xffffffff8168ec0b in register_virtio_driver (
// driver=driver@entry=0xffffffff82c10280 <virtio_net_driver>)
// at drivers/virtio/virtio.c:370
//#30 0xffffffff832fb86c in virtio_net_driver_init ()
// at drivers/net/virtio_net.c:5050
//#31 0xffffffff810019e6 in do_one_initcall (
// fn=0xffffffff832fb800 <virtio_net_driver_init>) at init/main.c:1238
//#32 0xffffffff832aa0ae in do_initcall_level (level=0x6,
// command_line=0xffff8881002c1480 "rdinit") at init/main.c:1300
//#33 do_initcalls () at init/main.c:1316
//#34 do_basic_setup () at init/main.c:1335
//#35 kernel_init_freeable () at init/main.c:1548
//#36 0xffffffff81fddbd5 in kernel_init (unused=<optimized out>)
// at init/main.c:1437
//#37 0xffffffff810bb27f in ret_from_fork (prev=<optimized out>,
// regs=0xffffc90000013f58, fn=0xffffffff81fddbc0 <kernel_init>,
// fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#38 0xffffffff810022fa in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#39 0x0000000000000000 in ?? ()
static inline void pci_write_msg_msix(struct msi_desc *desc, struct msi_msg *msg)
{
void __iomem *base = pci_msix_desc_addr(desc);
u32 ctrl = desc->pci.msix_ctrl;
bool unmasked = !(ctrl & PCI_MSIX_ENTRY_CTRL_MASKBIT);

if (desc->pci.msi_attrib.is_virtual)
return;
/*
* The specification mandates that the entry is masked
* when the message is modified:
*
* "If software changes the Address or Data value of an
* entry while the entry is unmasked, the result is
* undefined."
*/
if (unmasked)
pci_msix_write_vector_ctrl(desc, ctrl | PCI_MSIX_ENTRY_CTRL_MASKBIT);

writel(msg->address_lo, base + PCI_MSIX_ENTRY_LOWER_ADDR);
writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR);
writel(msg->data, base + PCI_MSIX_ENTRY_DATA);

if (unmasked)
pci_msix_write_vector_ctrl(desc, ctrl);

/* Ensure that the writes are visible in the device */
readl(base + PCI_MSIX_ENTRY_DATA);
}

中断下半部

根据前面中断硬件的介绍,在CPU处理中断的过程中会屏蔽相关中断,直到CPU完成中断处理并发送EOI命令。如果中断处理程序耗时过长,则会导致其他中断得不到有效处理,导致硬件中断丢失。为了避免这种情况,Linux内核把中断处理分为中断上半部和中断下半部:其中中断上半部在中断屏蔽期间执行,其主要快速响应硬件并调度中断下半部的执行;而中断下半部用于异步的处理耗时任务。

目前主要的中断下半部包括softirq(软中断)、tasklet和workqueue。其中tasklet基于softirq,都运行在软中断上下文中;而workqueue则运行在内核进程上下文

softirq

softirq事件

softirq事件在Linux内核编译时就确定好的,每个软中断号对应一个事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS
};

其内部使用数组softirq_vec管理事件的handler,并使用open_softirq()注册handler

1
2
3
4
5
6
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

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

一般的,中断上半部中会调用raise_softirq()标记待处理的软中断事件,后续会在异步执行的中断下半部中调用__do_softirq()处理软中断事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#define or_softirq_pending(x)	(__this_cpu_or(local_softirq_pending_ref, (x)))
void raise_softirq(unsigned int nr)
{
unsigned long flags;

local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}

void __raise_softirq_irqoff(unsigned int nr)
{
lockdep_assert_irqs_disabled();
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr);
}

#define local_softirq_pending() (__this_cpu_read(local_softirq_pending_ref))
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 as the 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 swapping.
*/
current->flags &= ~PF_MEMALLOC;

pending = local_softirq_pending();

softirq_handle_begin();
in_hardirq = lockdep_softirq_start();
account_softirq_enter(current);

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;
}

if (!IS_ENABLED(CONFIG_PREEMPT_RT) &&
__this_cpu_read(ksoftirqd) == current)
rcu_softirq_qs();

local_irq_disable();

pending = local_softirq_pending();
if (pending) {
if (time_before(jiffies, end) && !need_resched() &&
--max_restart)
goto restart;

wakeup_softirqd();
}

account_softirq_exit(current);
lockdep_softirq_end(in_hardirq);
softirq_handle_end();
current_restore_flags(old_flags, PF_MEMALLOC);
}

可以看到,其软中断处理逻辑还是很清晰。当中断上半部准备产生一个需要异步处理的软中断事件时,其会标记软中断事件号对应的比特。而当中断下半部准备处理软中断事件时,其会执行所有被标记的软中断事件的handler

触发时机

如前面所说,被标记的软中断任务会在中断下半部被异步地执行。其执行操作主要在以下两个时机被调度触发

  1. CPU调度到ksoftirqd内核线程
  2. 在中断上半部(硬件中断处理程序)退出时(irq_exit_rcu)
ksoftirqd

linux内核会在spawn_ksoftirqd()中为每一个CPU都初始化一个ksoftirqd内核线程,专门用于处理softirq事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//#0  spawn_ksoftirqd () at kernel/softirq.c:972
//#1 0xffffffff81001a60 in do_one_initcall (fn=0xffffffff832c8940 <spawn_ksoftirqd>) at init/main.c:1238
//#2 0xffffffff8329e100 in do_pre_smp_initcalls () at init/main.c:1344
//#3 kernel_init_freeable () at init/main.c:1537
//#4 0xffffffff81fa3a85 in kernel_init (unused=<optimized out>) at init/main.c:1437
//#5 0xffffffff810cea7c in ret_from_fork (prev=<optimized out>, regs=0xffffc90000013f58, fn=0xffffffff81fa3a70 <kernel_init>, fn_arg=0x0 <fixed_percpu_data>) at arch/x86/kernel/process.c:147
//#6 0xffffffff8100244a in ret_from_fork_asm () at arch/x86/entry/entry_64.S:243
//#7 0x0000000000000000 in ?? ()
static __init int spawn_ksoftirqd(void)
{
cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
takeover_tasklets);
BUG_ON(smpboot_register_percpu_thread(&softirq_threads));

return 0;
}
early_initcall(spawn_ksoftirqd);

可以看到,其在内核初始化阶段,基于softirq_threads参数调用smpboot_register_percpu_thread()为每个CPU创建内核线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
static struct smp_hotplug_thread softirq_threads = {
.store = &ksoftirqd,
.thread_should_run = ksoftirqd_should_run,
.thread_fn = run_ksoftirqd,
.thread_comm = "ksoftirqd/%u",
};

/**
* smpboot_register_percpu_thread - Register a per_cpu thread related
* to hotplug
* @plug_thread: Hotplug thread descriptor
*
* Creates and starts the threads on all online cpus.
*/
int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread)
{
unsigned int cpu;
int ret = 0;

cpus_read_lock();
mutex_lock(&smpboot_threads_lock);
for_each_online_cpu(cpu) {
ret = __smpboot_create_thread(plug_thread, cpu);
if (ret) {
smpboot_destroy_threads(plug_thread);
goto out;
}
smpboot_unpark_thread(plug_thread, cpu);
}
list_add(&plug_thread->list, &hotplug_threads);
out:
mutex_unlock(&smpboot_threads_lock);
cpus_read_unlock();
return ret;
}

static int
__smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
{
struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);
struct smpboot_thread_data *td;

if (tsk)
return 0;

td = kzalloc_node(sizeof(*td), GFP_KERNEL, cpu_to_node(cpu));
if (!td)
return -ENOMEM;
td->cpu = cpu;
td->ht = ht;

tsk = kthread_create_on_cpu(smpboot_thread_fn, td, cpu,
ht->thread_comm);
...
return 0;
}

/**
* smpboot_thread_fn - percpu hotplug thread loop function
* @data: thread data pointer
*
* Checks for thread stop and park conditions. Calls the necessary
* setup, cleanup, park and unpark functions for the registered
* thread.
*
* Returns 1 when the thread should exit, 0 otherwise.
*/
static int smpboot_thread_fn(void *data)
{
struct smpboot_thread_data *td = data;
struct smp_hotplug_thread *ht = td->ht;

while (1) {
set_current_state(TASK_INTERRUPTIBLE);
preempt_disable();
...
if (!ht->thread_should_run(td->cpu)) {
preempt_enable_no_resched();
schedule();
} else {
__set_current_state(TASK_RUNNING);
preempt_enable();
ht->thread_fn(td->cpu);
}
}
}

整体逻辑也很清晰,smpboot_register_percpu_thread()会给每个CPU创建一个执行smpboot_thread_fn()循环的内核线程。在循环里会调用回调函数run_ksoftirqd()函数,并最终调用__do_softirq()处理softirq事件

irq_exit_rcu

前面硬件中断小节介绍了,中断上半部的handler由DEFINE_IDTENTRY_IRQ宏定义,其在handler最后会调用到irq_exit_rcu(),并触发invoke_softirq()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#define DEFINE_IDTENTRY_IRQ(func)					\
static void __##func(struct pt_regs *regs, u32 vector); \
\
__visible noinstr void func(struct pt_regs *regs, \
unsigned long error_code) \
{ \
irqentry_state_t state = irqentry_enter(regs); \
u32 vector = (u32)(u8)error_code; \
\
instrumentation_begin(); \
kvm_set_cpu_l1tf_flush_l1d(); \
run_irq_on_irqstack_cond(__##func, regs, vector); \
instrumentation_end(); \
irqentry_exit(regs, state); \
} \
\
static noinline void __##func(struct pt_regs *regs, u32 vector)


#define run_irq_on_irqstack_cond(func, regs, vector) \
{ \
...
call_on_irqstack_cond(func, regs, ASM_CALL_IRQ, \
IRQ_CONSTRAINTS, regs, vector); \
}

/*
* Macro to invoke system vector and device interrupt C handlers.
*/
#define call_on_irqstack_cond(func, regs, asm_call, constr, c_args...) \
{ \
...
irq_enter_rcu(); \
func(c_args); \
irq_exit_rcu(); \
...
}

void irq_exit_rcu(void)
{
__irq_exit_rcu();
/* must be last! */
lockdep_hardirq_exit();
}

static inline void __irq_exit_rcu(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
lockdep_assert_irqs_disabled();
#endif
account_hardirq_exit(current);
preempt_count_sub(HARDIRQ_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();

tick_irq_exit();
}

//#0 invoke_softirq () at kernel/softirq.c:421
//#1 __irq_exit_rcu () at kernel/softirq.c:633
//#2 irq_exit_rcu () at kernel/softirq.c:645
//#3 0xffffffff81f8aabe in common_interrupt (regs=0xffffc9000009be38, error_code=<optimized out>) at arch/x86/kernel/irq.c:247
static inline void invoke_softirq(void)
{
if (!force_irqthreads() || !__this_cpu_read(ksoftirqd)) {
#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();
}
}

而在invoke_softirq()中,其会直接调用__do_softirq()直接处理软中断时间或是唤醒ksoftirqd内核线程去处理

tasklet

由于softirq事件在内核编译时就确定了,如果想新添加还需要修改并重新编译内核,这是无法接受的。因此内核实现了一种更常用的中断下半部,即tasklet。tasklet构建在softirq之上,其实现基于两个softirq事件,TASKLET_SOFTIRQHI_SOFTIRQ

tasklet事件

tasklet用struct tasklet_struct表示每一个动态事件

1
2
3
4
5
6
7
8
9
10
11
12
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
bool use_callback;
union {
void (*func)(unsigned long data);
void (*callback)(struct tasklet_struct *t);
};
unsigned long data;
};

其内部使用链表tasklet_hi_vec/tasklet_vec进行管理,其链表头是个per_cpu变量,即每个cpu一个链表

1
2
3
4
5
6
7
8
9
10
/*
* Tasklets
*/
struct tasklet_head {
struct tasklet_struct *head;
struct tasklet_struct **tail;
};

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

内核其他子系统和内核驱动模块可以通过tasklet_setup()/tasklet_init()初始化动态创建的tasklet事件,或DECLARE_TASKLET()/DECLARE_TASKLET_OLD()静态创建tasklet事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void tasklet_setup(struct tasklet_struct *t,
void (*callback)(struct tasklet_struct *))
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->callback = callback;
t->use_callback = true;
t->data = 0;
}

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->use_callback = false;
t->data = data;
}

#define DECLARE_TASKLET(name, _callback) \
struct tasklet_struct name = { \
.count = ATOMIC_INIT(0), \
.callback = _callback, \
.use_callback = true, \
}

#define DECLARE_TASKLET_OLD(name, _func) \
struct tasklet_struct name = { \
.count = ATOMIC_INIT(0), \
.func = _func, \
}

一般的,中断上半部会调用tasklet_hi_schedule()/tasklet_schedule()将待处理的tasklet事件插入到tasklet_vec/tasklet_hi_vec队列队尾并标记对应的softirq事件,后续会在TASKLET_SOFTIRQ/HI_SOFTIRQ softirq事件中调用tasket_action()/tasklet_hi_action()处理tasklet事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
void __tasklet_schedule(struct tasklet_struct *t)
{
__tasklet_schedule_common(t, &tasklet_vec,
TASKLET_SOFTIRQ);
}

static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_hi_schedule(t);
}
void __tasklet_hi_schedule(struct tasklet_struct *t)
{
__tasklet_schedule_common(t, &tasklet_hi_vec,
HI_SOFTIRQ);
}

static void __tasklet_schedule_common(struct tasklet_struct *t,
struct tasklet_head __percpu *headp,
unsigned int softirq_nr)
{
struct tasklet_head *head;
unsigned long flags;

local_irq_save(flags);
head = this_cpu_ptr(headp);
t->next = NULL;
*head->tail = t;
head->tail = &(t->next);
raise_softirq_irqoff(softirq_nr);
local_irq_restore(flags);
}

处理逻辑

根据前面softirq事件小节,内核在softirq_init()中注册TASKLET_SOFTIRQ事件和HI_SOFTIRQ事件的handler分别为tasklet_action()tasklet_hi_action(),如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
workqueue_softirq_action(false);
tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
}
static __latent_entropy void tasklet_hi_action(struct softirq_action *a)
{
workqueue_softirq_action(true);
tasklet_action_common(a, this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ);
}

static void tasklet_action_common(struct softirq_action *a,
struct tasklet_head *tl_head,
unsigned int softirq_nr)
{
struct tasklet_struct *list;

local_irq_disable();
list = tl_head->head;
tl_head->head = NULL;
tl_head->tail = &tl_head->head;
local_irq_enable();

while (list) {
struct tasklet_struct *t = list;

list = list->next;

if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (tasklet_clear_sched(t)) {
if (t->use_callback) {
trace_tasklet_entry(t, t->callback);
t->callback(t);
trace_tasklet_exit(t, t->callback);
} else {
trace_tasklet_entry(t, t->func);
t->func(t->data);
trace_tasklet_exit(t, t->func);
}
}
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}

local_irq_disable();
t->next = NULL;
*tl_head->tail = t;
tl_head->tail = &t->next;
__raise_softirq_irqoff(softirq_nr);
local_irq_enable();
}
}

对于workqueue_softirq_action(),其用于workqueue的POOL_BH类型的worker处理work事件,这个在后面workqueue小节中再详细介绍

tasklet_action_common()的逻辑很清晰,遍历per_cpu变量的tasklet_hi_vec/tasklet_vec链表来执行待处理tasklet事件。因为处理tasklet事件前会先获取锁,这保证了同一时刻同一个tasklet事件只会在一个cpu上执行

workqueue

由于softirq事件在内核编译时就确定了,如果想新添加还需要修改并重新编译内核,这是无法接受的。因此内核实现了一种支持动态创建事件的中断下半部,即workqueue:即workqueue使用者生产work事件,而基于内核线程的worker异步地消费work事件

内核中workqueue有多个版本的实现,目前最新版本的workqueue实现是CMWQ(Concurrency Managed Workqueue)。其整体思路为如下两点:

  1. worker由worker_pool进行管理,其会根据负载弹性扩缩worker数量
  2. 内核会静态/动态创建有限数量的worker_pool,workqueue公用这些worker_pool

worker_pool

CWMQ中最核心的概念就是worker_pool,其用于管理worker线程及其需要处理的work事件,由struct worker_pool结构体进行描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
struct worker_pool {
raw_spinlock_t lock; /* the pool lock */
int cpu; /* I: the associated cpu */
int node; /* I: the associated node ID */
int id; /* I: pool ID */
unsigned int flags; /* L: flags */

unsigned long watchdog_ts; /* L: watchdog timestamp */
bool cpu_stall; /* WD: stalled cpu bound pool */

/*
* The counter is incremented in a process context on the associated CPU
* w/ preemption disabled, and decremented or reset in the same context
* but w/ pool->lock held. The readers grab pool->lock and are
* guaranteed to see if the counter reached zero.
*/
int nr_running;

struct list_head worklist; /* L: list of pending works */

int nr_workers; /* L: total number of workers */
int nr_idle; /* L: currently idle workers */

struct list_head idle_list; /* L: list of idle workers */
struct timer_list idle_timer; /* L: worker idle timeout */
struct work_struct idle_cull_work; /* L: worker idle cleanup */

struct timer_list mayday_timer; /* L: SOS timer for workers */

/* a workers is either on busy_hash or idle_list, or the manager */
DECLARE_HASHTABLE(busy_hash, BUSY_WORKER_HASH_ORDER);
/* L: hash of busy workers */

struct worker *manager; /* L: purely informational */
struct list_head workers; /* A: attached workers */

struct ida worker_ida; /* worker IDs for task name */

struct workqueue_attrs *attrs; /* I: worker attributes */
struct hlist_node hash_node; /* PL: unbound_pool_hash node */
int refcnt; /* PL: refcnt for unbound pools */

/*
* Destruction of pool is RCU protected to allow dereferences
* from get_work_pool().
*/
struct rcu_head rcu;
};

其中,worklist字段标记该pool所有待处理的work事件,其会被该pool的所有worker线程一起消费;而worker字段则标记该pool管理的所有worker线程,其会共同消费该pool管理的work事件队列,具体细节在下面章节具体介绍

内核中一共有两类worker_pool,即bound worker_pool和unbound worker_pool

其中bound worker_pool中所有的worker线程只能运行在指定CPU上。该类型的worker_pool是由内核基于DEFINE_PER_CPU_SHARED_ALIGNED()静态定义的,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
enum wq_internal_consts {
NR_STD_WORKER_POOLS = 2, /* # standard pools per cpu */
...
};

/* to raise softirq for the BH worker pools on other CPUs */
static DEFINE_PER_CPU_SHARED_ALIGNED(struct irq_work [NR_STD_WORKER_POOLS], bh_pool_irq_works);

/* the BH worker pools */
static DEFINE_PER_CPU_SHARED_ALIGNED(struct worker_pool [NR_STD_WORKER_POOLS], bh_worker_pools);

/* the per-cpu worker pools */
static DEFINE_PER_CPU_SHARED_ALIGNED(struct worker_pool [NR_STD_WORKER_POOLS], cpu_worker_pools);

可以看到,内核中一共定义了bh_pool_irq_worksbh_worker_poolscpu_worker_pools三个bound worker_pools,并且每一个worker_pools在每个CPU上都有两个worker_pool,后一个(下标为1)的worker_pool其调度优先级会更高一些

而对于unbound worker_pool,其管理的worker可以在多个cpu上运行,内核会使用get_unbound_pool()来获取和创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* get_unbound_pool - get a worker_pool with the specified attributes
* @attrs: the attributes of the worker_pool to get
*
* Obtain a worker_pool which has the same attributes as @attrs, bump the
* reference count and return it. If there already is a matching
* worker_pool, it will be used; otherwise, this function attempts to
* create a new one.
*
* Should be called with wq_pool_mutex held.
*
* Return: On success, a worker_pool with the same attributes as @attrs.
* On failure, %NULL.
*/
static struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs)
{
struct wq_pod_type *pt = &wq_pod_types[WQ_AFFN_NUMA];
u32 hash = wqattrs_hash(attrs);
struct worker_pool *pool;
int pod, node = NUMA_NO_NODE;

lockdep_assert_held(&wq_pool_mutex);

/* do we already have a matching pool? */
hash_for_each_possible(unbound_pool_hash, pool, hash_node, hash) {
if (wqattrs_equal(pool->attrs, attrs)) {
pool->refcnt++;
return pool;
}
}

/* If __pod_cpumask is contained inside a NUMA pod, that's our node */
for (pod = 0; pod < pt->nr_pods; pod++) {
if (cpumask_subset(attrs->__pod_cpumask, pt->pod_cpus[pod])) {
node = pt->pod_node[pod];
break;
}
}

/* nope, create a new one */
pool = kzalloc_node(sizeof(*pool), GFP_KERNEL, node);
if (!pool || init_worker_pool(pool) < 0)
goto fail;

pool->node = node;
copy_workqueue_attrs(pool->attrs, attrs);
wqattrs_clear_for_pool(pool->attrs);

if (worker_pool_assign_id(pool) < 0)
goto fail;

/* create and start the initial worker */
if (wq_online && !create_worker(pool))
goto fail;

/* install */
hash_add(unbound_pool_hash, &pool->hash_node, hash);

return pool;
fail:
if (pool)
put_unbound_pool(pool);
return NULL;
}

可以看到,内核会使用unbound_pool_hash管理所有的unbound worker_pool,只有workqueue使用者需要当前特定的worker_pool并且unbound_pool_hash中没有记录时才会创建该worker_pool

work事件

前面worker_pool管理的work事件可以用struct work_struct进行描述

1
2
3
4
5
6
7
8
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

workqueue使用者可以通过INIT_WORK()初始化动态创建的work事件,或使用DECLARE_WORK()静态创建work事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static inline void __init_work(struct work_struct *work, int onstack) { }
#define WORK_DATA_INIT() ATOMIC_LONG_INIT((unsigned long)WORK_STRUCT_NO_POOL)

#define __INIT_WORK_KEY(_work, _func, _onstack, _key) \
do { \
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->func = (_func); \
} while (0)

#define __INIT_WORK(_work, _func, _onstack) \
do { \
static __maybe_unused struct lock_class_key __key; \
\
__INIT_WORK_KEY(_work, _func, _onstack, &__key); \
} while (0)

#define INIT_WORK(_work, _func) \
__INIT_WORK((_work), (_func), 0)



#define WORK_DATA_STATIC_INIT() \
ATOMIC_LONG_INIT((unsigned long)(WORK_STRUCT_NO_POOL | WORK_STRUCT_STATIC))

#define __WORK_INITIALIZER(n, f) { \
.data = WORK_DATA_STATIC_INIT(), \
.entry = { &(n).entry, &(n).entry }, \
.func = (f), \
__WORK_INIT_LOCKDEP_MAP(#n, &(n)) \
}

#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)

而所有的work事件会被以队列的形式管理在不同队列中(例如前面worker_pool中所有待处理事件被管理在struct worker_poolworklist字段),struct work_structentry字段指向work事件所在的事件队列。kernel使用insert_work()将work事件插入到事件队列中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* insert_work - insert a work into a pool
* @pwq: pwq @work belongs to
* @work: work to insert
* @head: insertion point
* @extra_flags: extra WORK_STRUCT_* flags to set
*
* Insert @work which belongs to @pwq after @head. @extra_flags is or'd to
* work_struct flags.
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock).
*/
static void insert_work(struct pool_workqueue *pwq, struct work_struct *work,
struct list_head *head, unsigned int extra_flags)
{
debug_work_activate(work);

/* record the work call stack in order to print it in KASAN reports */
kasan_record_aux_stack_noalloc(work);

/* we own @work, set data and link */
set_work_pwq(work, pwq, extra_flags);
list_add_tail(&work->entry, head);
get_pwq(pwq);
}
worker进程

前面介绍的worker线程则由struct worker进行描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
* The poor guys doing the actual heavy lifting. All on-duty workers are
* either serving the manager role, on idle list or on busy hash. For
* details on the locking annotation (L, I, X...), refer to workqueue.c.
*
* Only to be used in workqueue and async.
*/
struct worker {
/* on idle list while idle, on busy hash table while busy */
union {
struct list_head entry; /* L: while idle */
struct hlist_node hentry; /* L: while busy */
};

struct work_struct *current_work; /* K: work being processed and its */
work_func_t current_func; /* K: function */
struct pool_workqueue *current_pwq; /* K: pwq */
u64 current_at; /* K: runtime at start or last wakeup */
unsigned int current_color; /* K: color */

int sleeping; /* S: is worker sleeping? */

/* used by the scheduler to determine a worker's last known identity */
work_func_t last_func; /* K: last work's fn */

struct list_head scheduled; /* L: scheduled works */

struct task_struct *task; /* I: worker task */
struct worker_pool *pool; /* A: the associated pool */
/* L: for rescuers */
struct list_head node; /* A: anchored at pool->workers */
/* A: runs through worker->node */

unsigned long last_active; /* K: last active timestamp */
unsigned int flags; /* L: flags */
int id; /* I: worker id */

/*
* Opaque string set with work_set_desc(). Printed out with task
* dump for debugging - WARN, BUG, panic or sysrq.
*/
char desc[WORKER_DESC_LEN];

/* used only by rescuers to point to the target workqueue */
struct workqueue_struct *rescue_wq; /* I: the workqueue to rescue */
};

node字段指向所在的worker_pool管理的所有worker列表
内核会调用create_worker()为worker_pool创建worker对应的内核线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* create_worker - create a new workqueue worker
* @pool: pool the new worker will belong to
*
* Create and start a new worker which is attached to @pool.
*
* CONTEXT:
* Might sleep. Does GFP_KERNEL allocations.
*
* Return:
* Pointer to the newly created worker.
*/
static struct worker *create_worker(struct worker_pool *pool)
{
struct worker *worker;
int id;
char id_buf[23];

/* ID is needed to determine kthread name */
id = ida_alloc(&pool->worker_ida, GFP_KERNEL);
if (id < 0) {
pr_err_once("workqueue: Failed to allocate a worker ID: %pe\n",
ERR_PTR(id));
return NULL;
}

worker = alloc_worker(pool->node);
if (!worker) {
pr_err_once("workqueue: Failed to allocate a worker\n");
goto fail;
}

worker->id = id;

if (!(pool->flags & POOL_BH)) {
if (pool->cpu >= 0)
snprintf(id_buf, sizeof(id_buf), "%d:%d%s", pool->cpu, id,
pool->attrs->nice < 0 ? "H" : "");
else
snprintf(id_buf, sizeof(id_buf), "u%d:%d", pool->id, id);

worker->task = kthread_create_on_node(worker_thread, worker,
pool->node, "kworker/%s", id_buf);
if (IS_ERR(worker->task)) {
if (PTR_ERR(worker->task) == -EINTR) {
pr_err("workqueue: Interrupted when creating a worker thread \"kworker/%s\"\n",
id_buf);
} else {
pr_err_once("workqueue: Failed to create a worker thread: %pe",
worker->task);
}
goto fail;
}

set_user_nice(worker->task, pool->attrs->nice);
kthread_bind_mask(worker->task, pool_allowed_cpus(pool));
}

/* successful, attach the worker to the pool */
worker_attach_to_pool(worker, pool);

/* start the newly created worker */
raw_spin_lock_irq(&pool->lock);

worker->pool->nr_workers++;
worker_enter_idle(worker);

/*
* @worker is waiting on a completion in kthread() and will trigger hung
* check if not woken up soon. As kick_pool() is noop if @pool is empty,
* wake it up explicitly.
*/
if (worker->task)
wake_up_process(worker->task);

raw_spin_unlock_irq(&pool->lock);

return worker;

fail:
ida_free(&pool->worker_ida, id);
kfree(worker);
return NULL;
}

可以看到,对于非POOL_BH类型,内核调用kthread_create_on_node函数,创建名为kworker/X的内核线程,该内核线程会执行worker_thread()线程函数来处理work事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/**
* worker_thread - the worker thread function
* @__worker: self
*
* The worker thread function. All workers belong to a worker_pool -
* either a per-cpu one or dynamic unbound one. These workers process all
* work items regardless of their specific target workqueue. The only
* exception is work items which belong to workqueues with a rescuer which
* will be explained in rescuer_thread().
*
* Return: 0
*/
static int worker_thread(void *__worker)
{
struct worker *worker = __worker;
struct worker_pool *pool = worker->pool;

/* tell the scheduler that this is a workqueue worker */
set_pf_worker(true);
woke_up:
raw_spin_lock_irq(&pool->lock);

/* am I supposed to die? */
if (unlikely(worker->flags & WORKER_DIE)) {
raw_spin_unlock_irq(&pool->lock);
set_pf_worker(false);

set_task_comm(worker->task, "kworker/dying");
ida_free(&pool->worker_ida, worker->id);
worker_detach_from_pool(worker);
WARN_ON_ONCE(!list_empty(&worker->entry));
kfree(worker);
return 0;
}

worker_leave_idle(worker);
recheck:
/* no more worker necessary? */
if (!need_more_worker(pool))
goto sleep;

/* do we need to manage? */
if (unlikely(!may_start_working(pool)) && manage_workers(worker))
goto recheck;

/*
* ->scheduled list can only be filled while a worker is
* preparing to process a work or actually processing it.
* Make sure nobody diddled with it while I was sleeping.
*/
WARN_ON_ONCE(!list_empty(&worker->scheduled));

/*
* Finish PREP stage. We're guaranteed to have at least one idle
* worker or that someone else has already assumed the manager
* role. This is where @worker starts participating in concurrency
* management if applicable and concurrency management is restored
* after being rebound. See rebind_workers() for details.
*/
worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND);

do {
struct work_struct *work =
list_first_entry(&pool->worklist,
struct work_struct, entry);

if (assign_work(work, worker, NULL))
process_scheduled_works(worker);
} while (keep_working(pool));

worker_set_flags(worker, WORKER_PREP);
sleep:
/*
* pool->lock is held and there's no work to process and no need to
* manage, sleep. Workers are woken up only while holding
* pool->lock or from local cpu, so setting the current state
* before releasing pool->lock is enough to prevent losing any
* event.
*/
worker_enter_idle(worker);
__set_current_state(TASK_IDLE);
raw_spin_unlock_irq(&pool->lock);
schedule();
goto woke_up;
}

/**
* assign_work - assign a work item and its linked work items to a worker
* @work: work to assign
* @worker: worker to assign to
* @nextp: out parameter for nested worklist walking
*
* Assign @work and its linked work items to @worker. If @work is already being
* executed by another worker in the same pool, it'll be punted there.
*
* If @nextp is not NULL, it's updated to point to the next work of the last
* scheduled work. This allows assign_work() to be nested inside
* list_for_each_entry_safe().
*
* Returns %true if @work was successfully assigned to @worker. %false if @work
* was punted to another worker already executing it.
*/
static bool assign_work(struct work_struct *work, struct worker *worker,
struct work_struct **nextp)
{
struct worker_pool *pool = worker->pool;
struct worker *collision;

lockdep_assert_held(&pool->lock);

/*
* A single work shouldn't be executed concurrently by multiple workers.
* __queue_work() ensures that @work doesn't jump to a different pool
* while still running in the previous pool. Here, we should ensure that
* @work is not executed concurrently by multiple workers from the same
* pool. Check whether anyone is already processing the work. If so,
* defer the work to the currently executing one.
*/
collision = find_worker_executing_work(pool, work);
if (unlikely(collision)) {
move_linked_works(work, &collision->scheduled, nextp);
return false;
}

move_linked_works(work, &worker->scheduled, nextp);
return true;
}

/**
* process_one_work - process single work
* @worker: self
* @work: work to process
*
* Process @work. This function contains all the logics necessary to
* process a single work including synchronization against and
* interaction with other workers on the same cpu, queueing and
* flushing. As long as context requirement is met, any worker can
* call this function to process a work.
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock) which is released and regrabbed.
*/
static void process_one_work(struct worker *worker, struct work_struct *work)
__releases(&pool->lock)
__acquires(&pool->lock)
{
struct pool_workqueue *pwq = get_work_pwq(work);
struct worker_pool *pool = worker->pool;
unsigned long work_data;
int lockdep_start_depth, rcu_start_depth;
bool bh_draining = pool->flags & POOL_BH_DRAINING;
#ifdef CONFIG_LOCKDEP
/*
* It is permissible to free the struct work_struct from
* inside the function that is called from it, this we need to
* take into account for lockdep too. To avoid bogus "held
* lock freed" warnings as well as problems when looking into
* work->lockdep_map, make a copy and use that here.
*/
struct lockdep_map lockdep_map;

lockdep_copy_map(&lockdep_map, &work->lockdep_map);
#endif
/* ensure we're on the correct CPU */
WARN_ON_ONCE(!(pool->flags & POOL_DISASSOCIATED) &&
raw_smp_processor_id() != pool->cpu);

/* claim and dequeue */
debug_work_deactivate(work);
hash_add(pool->busy_hash, &worker->hentry, (unsigned long)work);
worker->current_work = work;
worker->current_func = work->func;
worker->current_pwq = pwq;
if (worker->task)
worker->current_at = worker->task->se.sum_exec_runtime;
work_data = *work_data_bits(work);
worker->current_color = get_work_color(work_data);

/*
* Record wq name for cmdline and debug reporting, may get
* overridden through set_worker_desc().
*/
strscpy(worker->desc, pwq->wq->name, WORKER_DESC_LEN);

list_del_init(&work->entry);

/*
* CPU intensive works don't participate in concurrency management.
* They're the scheduler's responsibility. This takes @worker out
* of concurrency management and the next code block will chain
* execution of the pending work items.
*/
if (unlikely(pwq->wq->flags & WQ_CPU_INTENSIVE))
worker_set_flags(worker, WORKER_CPU_INTENSIVE);

/*
* Kick @pool if necessary. It's always noop for per-cpu worker pools
* since nr_running would always be >= 1 at this point. This is used to
* chain execution of the pending work items for WORKER_NOT_RUNNING
* workers such as the UNBOUND and CPU_INTENSIVE ones.
*/
kick_pool(pool);

/*
* Record the last pool and clear PENDING which should be the last
* update to @work. Also, do this inside @pool->lock so that
* PENDING and queued state changes happen together while IRQ is
* disabled.
*/
set_work_pool_and_clear_pending(work, pool->id, 0);

pwq->stats[PWQ_STAT_STARTED]++;
raw_spin_unlock_irq(&pool->lock);

rcu_start_depth = rcu_preempt_depth();
lockdep_start_depth = lockdep_depth(current);
/* see drain_dead_softirq_workfn() */
if (!bh_draining)
lock_map_acquire(&pwq->wq->lockdep_map);
lock_map_acquire(&lockdep_map);
/*
* Strictly speaking we should mark the invariant state without holding
* any locks, that is, before these two lock_map_acquire()'s.
*
* However, that would result in:
*
* A(W1)
* WFC(C)
* A(W1)
* C(C)
*
* Which would create W1->C->W1 dependencies, even though there is no
* actual deadlock possible. There are two solutions, using a
* read-recursive acquire on the work(queue) 'locks', but this will then
* hit the lockdep limitation on recursive locks, or simply discard
* these locks.
*
* AFAICT there is no possible deadlock scenario between the
* flush_work() and complete() primitives (except for single-threaded
* workqueues), so hiding them isn't a problem.
*/
lockdep_invariant_state(true);
trace_workqueue_execute_start(work);
worker->current_func(work);
/*
* While we must be careful to not use "work" after this, the trace
* point will only record its address.
*/
trace_workqueue_execute_end(work, worker->current_func);
pwq->stats[PWQ_STAT_COMPLETED]++;
lock_map_release(&lockdep_map);
if (!bh_draining)
lock_map_release(&pwq->wq->lockdep_map);

if (unlikely((worker->task && in_atomic()) ||
lockdep_depth(current) != lockdep_start_depth ||
rcu_preempt_depth() != rcu_start_depth)) {
pr_err("BUG: workqueue leaked atomic, lock or RCU: %s[%d]\n"
" preempt=0x%08x lock=%d->%d RCU=%d->%d workfn=%ps\n",
current->comm, task_pid_nr(current), preempt_count(),
lockdep_start_depth, lockdep_depth(current),
rcu_start_depth, rcu_preempt_depth(),
worker->current_func);
debug_show_held_locks(current);
dump_stack();
}

/*
* The following prevents a kworker from hogging CPU on !PREEMPTION
* kernels, where a requeueing work item waiting for something to
* happen could deadlock with stop_machine as such work item could
* indefinitely requeue itself while all other CPUs are trapped in
* stop_machine. At the same time, report a quiescent RCU state so
* the same condition doesn't freeze RCU.
*/
if (worker->task)
cond_resched();

raw_spin_lock_irq(&pool->lock);

/*
* In addition to %WQ_CPU_INTENSIVE, @worker may also have been marked
* CPU intensive by wq_worker_tick() if @work hogged CPU longer than
* wq_cpu_intensive_thresh_us. Clear it.
*/
worker_clr_flags(worker, WORKER_CPU_INTENSIVE);

/* tag the worker for identification in schedule() */
worker->last_func = worker->current_func;

/* we're done with it, release */
hash_del(&worker->hentry);
worker->current_work = NULL;
worker->current_func = NULL;
worker->current_pwq = NULL;
worker->current_color = INT_MAX;

/* must be the last step, see the function comment */
pwq_dec_nr_in_flight(pwq, work_data);
}

/**
* process_scheduled_works - process scheduled works
* @worker: self
*
* Process all scheduled works. Please note that the scheduled list
* may change while processing a work, so this function repeatedly
* fetches a work from the top and executes it.
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock) which may be released and regrabbed
* multiple times.
*/
static void process_scheduled_works(struct worker *worker)
{
struct work_struct *work;
bool first = true;

while ((work = list_first_entry_or_null(&worker->scheduled,
struct work_struct, entry))) {
if (first) {
worker->pool->watchdog_ts = jiffies;
first = false;
}
process_one_work(worker, work);
}
}

其逻辑比较清晰,worker_thread()函数会循环执行获取事件并处理事件流程直到被标记WORKER_DIE。具体的,在每个循环中其使用assign_work()struct worker_poolworklist中获取事件,并根据情况将其添加到当前thread的worker或对应worker的scheduled字段的队列中;会使用process_scheduled_works()中持续调用process_one_work()处理所有的schedules事件队列

除此之外,worker_thread()manage_workers()worker_enter_idle()也实现了worker_pool中worker线程数量的弹性扩容和缩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/**
* manage_workers - manage worker pool
* @worker: self
*
* Assume the manager role and manage the worker pool @worker belongs
* to. At any given time, there can be only zero or one manager per
* pool. The exclusion is handled automatically by this function.
*
* The caller can safely start processing works on false return. On
* true return, it's guaranteed that need_to_create_worker() is false
* and may_start_working() is true.
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock) which may be released and regrabbed
* multiple times. Does GFP_KERNEL allocations.
*
* Return:
* %false if the pool doesn't need management and the caller can safely
* start processing works, %true if management function was performed and
* the conditions that the caller verified before calling the function may
* no longer be true.
*/
static bool manage_workers(struct worker *worker)
{
struct worker_pool *pool = worker->pool;

if (pool->flags & POOL_MANAGER_ACTIVE)
return false;

pool->flags |= POOL_MANAGER_ACTIVE;
pool->manager = worker;

maybe_create_worker(pool);

pool->manager = NULL;
pool->flags &= ~POOL_MANAGER_ACTIVE;
rcuwait_wake_up(&manager_wait);
return true;
}

/**
* maybe_create_worker - create a new worker if necessary
* @pool: pool to create a new worker for
*
* Create a new worker for @pool if necessary. @pool is guaranteed to
* have at least one idle worker on return from this function. If
* creating a new worker takes longer than MAYDAY_INTERVAL, mayday is
* sent to all rescuers with works scheduled on @pool to resolve
* possible allocation deadlock.
*
* On return, need_to_create_worker() is guaranteed to be %false and
* may_start_working() %true.
*
* LOCKING:
* raw_spin_lock_irq(pool->lock) which may be released and regrabbed
* multiple times. Does GFP_KERNEL allocations. Called only from
* manager.
*/
static void maybe_create_worker(struct worker_pool *pool)
__releases(&pool->lock)
__acquires(&pool->lock)
{
restart:
raw_spin_unlock_irq(&pool->lock);

/* if we don't make progress in MAYDAY_INITIAL_TIMEOUT, call for help */
mod_timer(&pool->mayday_timer, jiffies + MAYDAY_INITIAL_TIMEOUT);

while (true) {
if (create_worker(pool) || !need_to_create_worker(pool))
break;

schedule_timeout_interruptible(CREATE_COOLDOWN);

if (!need_to_create_worker(pool))
break;
}

del_timer_sync(&pool->mayday_timer);
raw_spin_lock_irq(&pool->lock);
/*
* This is necessary even after a new worker was just successfully
* created as @pool->lock was dropped and the new worker might have
* already become busy.
*/
if (need_to_create_worker(pool))
goto restart;
}

可以看到,所有worker在准备处理work事件会调用manage_workers()来创建worker直到至少有一个idle worker,从而完成扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
static int init_worker_pool(struct worker_pool *pool)
{
...
timer_setup(&pool->idle_timer, idle_worker_timeout, TIMER_DEFERRABLE);
INIT_WORK(&pool->idle_cull_work, idle_cull_fn);
...
}

/**
* worker_enter_idle - enter idle state
* @worker: worker which is entering idle state
*
* @worker is entering idle state. Update stats and idle timer if
* necessary.
*
* LOCKING:
* raw_spin_lock_irq(pool->lock).
*/
static void worker_enter_idle(struct worker *worker)
{
struct worker_pool *pool = worker->pool;

if (WARN_ON_ONCE(worker->flags & WORKER_IDLE) ||
WARN_ON_ONCE(!list_empty(&worker->entry) &&
(worker->hentry.next || worker->hentry.pprev)))
return;

/* can't use worker_set_flags(), also called from create_worker() */
worker->flags |= WORKER_IDLE;
pool->nr_idle++;
worker->last_active = jiffies;

/* idle_list is LIFO */
list_add(&worker->entry, &pool->idle_list);

if (too_many_workers(pool) && !timer_pending(&pool->idle_timer))
mod_timer(&pool->idle_timer, jiffies + IDLE_WORKER_TIMEOUT);

/* Sanity check nr_running. */
WARN_ON_ONCE(pool->nr_workers == pool->nr_idle && pool->nr_running);
}

/**
* idle_worker_timeout - check if some idle workers can now be deleted.
* @t: The pool's idle_timer that just expired
*
* The timer is armed in worker_enter_idle(). Note that it isn't disarmed in
* worker_leave_idle(), as a worker flicking between idle and active while its
* pool is at the too_many_workers() tipping point would cause too much timer
* housekeeping overhead. Since IDLE_WORKER_TIMEOUT is long enough, we just let
* it expire and re-evaluate things from there.
*/
static void idle_worker_timeout(struct timer_list *t)
{
struct worker_pool *pool = from_timer(pool, t, idle_timer);
bool do_cull = false;

if (work_pending(&pool->idle_cull_work))
return;

raw_spin_lock_irq(&pool->lock);

if (too_many_workers(pool)) {
struct worker *worker;
unsigned long expires;

/* idle_list is kept in LIFO order, check the last one */
worker = list_last_entry(&pool->idle_list, struct worker, entry);
expires = worker->last_active + IDLE_WORKER_TIMEOUT;
do_cull = !time_before(jiffies, expires);

if (!do_cull)
mod_timer(&pool->idle_timer, expires);
}
raw_spin_unlock_irq(&pool->lock);

if (do_cull)
queue_work(system_unbound_wq, &pool->idle_cull_work);
}

/**
* idle_cull_fn - cull workers that have been idle for too long.
* @work: the pool's work for handling these idle workers
*
* This goes through a pool's idle workers and gets rid of those that have been
* idle for at least IDLE_WORKER_TIMEOUT seconds.
*
* We don't want to disturb isolated CPUs because of a pcpu kworker being
* culled, so this also resets worker affinity. This requires a sleepable
* context, hence the split between timer callback and work item.
*/
static void idle_cull_fn(struct work_struct *work)
{
struct worker_pool *pool = container_of(work, struct worker_pool, idle_cull_work);
LIST_HEAD(cull_list);

/*
* Grabbing wq_pool_attach_mutex here ensures an already-running worker
* cannot proceed beyong set_pf_worker() in its self-destruct path.
* This is required as a previously-preempted worker could run after
* set_worker_dying() has happened but before detach_dying_workers() did.
*/
mutex_lock(&wq_pool_attach_mutex);
raw_spin_lock_irq(&pool->lock);

while (too_many_workers(pool)) {
struct worker *worker;
unsigned long expires;

worker = list_last_entry(&pool->idle_list, struct worker, entry);
expires = worker->last_active + IDLE_WORKER_TIMEOUT;

if (time_before(jiffies, expires)) {
mod_timer(&pool->idle_timer, expires);
break;
}

set_worker_dying(worker, &cull_list);
}

raw_spin_unlock_irq(&pool->lock);
detach_dying_workers(&cull_list);
mutex_unlock(&wq_pool_attach_mutex);

reap_dying_workers(&cull_list);
}

可以看到,在初始化每个struct worker_pool时会初始化一个timer(idle_timer)和idle_cull_work的work事件。当worker进程没有待处理的work事件要进入idle状态时,其会调用worker_enter_idle(),设置前面初始化的idle_timer定时器,当定时器超时后调用设置的处理函数idle_worker_timeout()来产生idle_cull_work的work事件,该事件会调用idle_cull_fn()来完成最终的worker删除,实现缩容

接口

而workqueue暴露给使用者的前端接口,则是struct workqueue_struct,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*
* The externally visible workqueue. It relays the issued work items to
* the appropriate worker_pool through its pool_workqueues.
*/
struct workqueue_struct {
struct list_head pwqs; /* WR: all pwqs of this wq */
struct list_head list; /* PR: list of all workqueues */

struct mutex mutex; /* protects this wq */
int work_color; /* WQ: current work color */
int flush_color; /* WQ: current flush color */
atomic_t nr_pwqs_to_flush; /* flush in progress */
struct wq_flusher *first_flusher; /* WQ: first flusher */
struct list_head flusher_queue; /* WQ: flush waiters */
struct list_head flusher_overflow; /* WQ: flush overflow list */

struct list_head maydays; /* MD: pwqs requesting rescue */
struct worker *rescuer; /* MD: rescue worker */

int nr_drainers; /* WQ: drain in progress */

/* See alloc_workqueue() function comment for info on min/max_active */
int max_active; /* WO: max active works */
int min_active; /* WO: min active works */
int saved_max_active; /* WQ: saved max_active */
int saved_min_active; /* WQ: saved min_active */

struct workqueue_attrs *unbound_attrs; /* PW: only for unbound wqs */
struct pool_workqueue __rcu *dfl_pwq; /* PW: only for unbound wqs */

#ifdef CONFIG_SYSFS
struct wq_device *wq_dev; /* I: for sysfs interface */
#endif
#ifdef CONFIG_LOCKDEP
char *lock_name;
struct lock_class_key key;
struct lockdep_map lockdep_map;
#endif
char name[WQ_NAME_LEN]; /* I: workqueue name */

/*
* Destruction of workqueue_struct is RCU protected to allow walking
* the workqueues list without grabbing wq_pool_mutex.
* This is used to dump all workqueues from sysrq.
*/
struct rcu_head rcu;

/* hot fields used during command issue, aligned to cacheline */
unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */
struct pool_workqueue __percpu __rcu **cpu_pwq; /* I: per-cpu pwqs */
struct wq_node_nr_active *node_nr_active[]; /* I: per-node nr_active */
};

前面介绍workqueue会公用所有的worker_pool,其使用struct pool_workqueue结构体来管理workqueue_structworker_pool的一一映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
* The per-pool workqueue. While queued, bits below WORK_PWQ_SHIFT
* of work_struct->data are used for flags and the remaining high bits
* point to the pwq; thus, pwqs need to be aligned at two's power of the
* number of flag bits.
*/
struct pool_workqueue {
struct worker_pool *pool; /* I: the associated pool */
struct workqueue_struct *wq; /* I: the owning workqueue */
int work_color; /* L: current color */
int flush_color; /* L: flushing color */
int refcnt; /* L: reference count */
int nr_in_flight[WORK_NR_COLORS];
/* L: nr of in_flight works */
bool plugged; /* L: execution suspended */

/*
* nr_active management and WORK_STRUCT_INACTIVE:
*
* When pwq->nr_active >= max_active, new work item is queued to
* pwq->inactive_works instead of pool->worklist and marked with
* WORK_STRUCT_INACTIVE.
*
* All work items marked with WORK_STRUCT_INACTIVE do not participate in
* nr_active and all work items in pwq->inactive_works are marked with
* WORK_STRUCT_INACTIVE. But not all WORK_STRUCT_INACTIVE work items are
* in pwq->inactive_works. Some of them are ready to run in
* pool->worklist or worker->scheduled. Those work itmes are only struct
* wq_barrier which is used for flush_work() and should not participate
* in nr_active. For non-barrier work item, it is marked with
* WORK_STRUCT_INACTIVE iff it is in pwq->inactive_works.
*/
int nr_active; /* L: nr of active works */
struct list_head inactive_works; /* L: inactive works */
struct list_head pending_node; /* LN: node on wq_node_nr_active->pending_pwqs */
struct list_head pwqs_node; /* WR: node on wq->pwqs */
struct list_head mayday_node; /* MD: node on wq->maydays */

u64 stats[PWQ_NR_STATS];

/*
* Release of unbound pwq is punted to a kthread_worker. See put_pwq()
* and pwq_release_workfn() for details. pool_workqueue itself is also
* RCU protected so that the first pwq can be determined without
* grabbing wq->mutex.
*/
struct kthread_work release_work;
struct rcu_head rcu;
} __aligned(1 << WORK_STRUCT_PWQ_SHIFT);

可以看到,pool_workqueuewq字段和pool字段则分别为struct workqueue_structworker_pool,完成了一一映射

创建

内核使用alloc_workqueue()创建一个workqueue,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
__printf(1, 4)
struct workqueue_struct *alloc_workqueue(const char *fmt,
unsigned int flags,
int max_active, ...)
{
va_list args;
struct workqueue_struct *wq;
size_t wq_size;
int name_len;

if (flags & WQ_BH) {
if (WARN_ON_ONCE(flags & ~__WQ_BH_ALLOWS))
return NULL;
if (WARN_ON_ONCE(max_active))
return NULL;
}

/* see the comment above the definition of WQ_POWER_EFFICIENT */
if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
flags |= WQ_UNBOUND;

/* allocate wq and format name */
if (flags & WQ_UNBOUND)
wq_size = struct_size(wq, node_nr_active, nr_node_ids + 1);
else
wq_size = sizeof(*wq);

wq = kzalloc(wq_size, GFP_KERNEL);
if (!wq)
return NULL;

if (flags & WQ_UNBOUND) {
wq->unbound_attrs = alloc_workqueue_attrs();
if (!wq->unbound_attrs)
goto err_free_wq;
}

va_start(args, max_active);
name_len = vsnprintf(wq->name, sizeof(wq->name), fmt, args);
va_end(args);

if (name_len >= WQ_NAME_LEN)
pr_warn_once("workqueue: name exceeds WQ_NAME_LEN. Truncating to: %s\n",
wq->name);

if (flags & WQ_BH) {
/*
* BH workqueues always share a single execution context per CPU
* and don't impose any max_active limit.
*/
max_active = INT_MAX;
} else {
max_active = max_active ?: WQ_DFL_ACTIVE;
max_active = wq_clamp_max_active(max_active, flags, wq->name);
}

/* init wq */
wq->flags = flags;
wq->max_active = max_active;
wq->min_active = min(max_active, WQ_DFL_MIN_ACTIVE);
wq->saved_max_active = wq->max_active;
wq->saved_min_active = wq->min_active;
mutex_init(&wq->mutex);
atomic_set(&wq->nr_pwqs_to_flush, 0);
INIT_LIST_HEAD(&wq->pwqs);
INIT_LIST_HEAD(&wq->flusher_queue);
INIT_LIST_HEAD(&wq->flusher_overflow);
INIT_LIST_HEAD(&wq->maydays);

wq_init_lockdep(wq);
INIT_LIST_HEAD(&wq->list);

if (flags & WQ_UNBOUND) {
if (alloc_node_nr_active(wq->node_nr_active) < 0)
goto err_unreg_lockdep;
}

if (alloc_and_link_pwqs(wq) < 0)
goto err_free_node_nr_active;

if (wq_online && init_rescuer(wq) < 0)
goto err_destroy;

if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
goto err_destroy;

/*
* wq_pool_mutex protects global freeze state and workqueues list.
* Grab it, adjust max_active and add the new @wq to workqueues
* list.
*/
mutex_lock(&wq_pool_mutex);

mutex_lock(&wq->mutex);
wq_adjust_max_active(wq);
mutex_unlock(&wq->mutex);

list_add_tail_rcu(&wq->list, &workqueues);

mutex_unlock(&wq_pool_mutex);

return wq;

err_free_node_nr_active:
if (wq->flags & WQ_UNBOUND)
free_node_nr_active(wq->node_nr_active);
err_unreg_lockdep:
wq_unregister_lockdep(wq);
wq_free_lockdep(wq);
err_free_wq:
free_workqueue_attrs(wq->unbound_attrs);
kfree(wq);
return NULL;
err_destroy:
destroy_workqueue(wq);
return NULL;
}

可以看到,其函数逻辑比较清晰,在完成相关数据结构分配和初始化后,其会调用alloc_and_link_pwqs(),根据flags参数完成对应worker_pool的选择和映射,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
static int alloc_and_link_pwqs(struct workqueue_struct *wq)
{
bool highpri = wq->flags & WQ_HIGHPRI;
int cpu, ret;

wq->cpu_pwq = alloc_percpu(struct pool_workqueue *);
if (!wq->cpu_pwq)
goto enomem;

if (!(wq->flags & WQ_UNBOUND)) {
for_each_possible_cpu(cpu) {
struct pool_workqueue **pwq_p;
struct worker_pool __percpu *pools;
struct worker_pool *pool;

if (wq->flags & WQ_BH)
pools = bh_worker_pools;
else
pools = cpu_worker_pools;

pool = &(per_cpu_ptr(pools, cpu)[highpri]);
pwq_p = per_cpu_ptr(wq->cpu_pwq, cpu);

*pwq_p = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL,
pool->node);
if (!*pwq_p)
goto enomem;

init_pwq(*pwq_p, wq, pool);

mutex_lock(&wq->mutex);
link_pwq(*pwq_p);
mutex_unlock(&wq->mutex);
}
return 0;
}

cpus_read_lock();
if (wq->flags & __WQ_ORDERED) {
struct pool_workqueue *dfl_pwq;

ret = apply_workqueue_attrs(wq, ordered_wq_attrs[highpri]);
/* there should only be single pwq for ordering guarantee */
dfl_pwq = rcu_access_pointer(wq->dfl_pwq);
WARN(!ret && (wq->pwqs.next != &dfl_pwq->pwqs_node ||
wq->pwqs.prev != &dfl_pwq->pwqs_node),
"ordering guarantee broken for workqueue %s\n", wq->name);
} else {
ret = apply_workqueue_attrs(wq, unbound_std_wq_attrs[highpri]);
}
cpus_read_unlock();

/* for unbound pwq, flush the pwq_release_worker ensures that the
* pwq_release_workfn() completes before calling kfree(wq).
*/
if (ret)
kthread_flush_worker(pwq_release_worker);

return ret;

enomem:
if (wq->cpu_pwq) {
for_each_possible_cpu(cpu) {
struct pool_workqueue *pwq = *per_cpu_ptr(wq->cpu_pwq, cpu);

if (pwq)
kmem_cache_free(pwq_cache, pwq);
}
free_percpu(wq->cpu_pwq);
wq->cpu_pwq = NULL;
}
return -ENOMEM;
}

可以看到,对于bound类型,其对应的per-cpu字段cpu_pwq会被分别初始化一个struct pool_workqueue实例。该实例将直接映射到内核静态定义好的per-cpu worker_pool,即bh_worker_pools/cpu_worker_pools,从而建立workqueue到指定per-cpu worker_pool的映射关系。

而对于unbound类型,则是使用apply_workqueue_attrs()完成worker_pool的选择和映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
int apply_workqueue_attrs(struct workqueue_struct *wq,
const struct workqueue_attrs *attrs)
{
...
mutex_lock(&wq_pool_mutex);
ret = apply_workqueue_attrs_locked(wq, attrs);
mutex_unlock(&wq_pool_mutex);

return ret;
}

static int apply_workqueue_attrs_locked(struct workqueue_struct *wq,
const struct workqueue_attrs *attrs)
{
struct apply_wqattrs_ctx *ctx;

/* only unbound workqueues can change attributes */
if (WARN_ON(!(wq->flags & WQ_UNBOUND)))
return -EINVAL;

ctx = apply_wqattrs_prepare(wq, attrs, wq_unbound_cpumask);
if (IS_ERR(ctx))
return PTR_ERR(ctx);

/* the ctx has been prepared successfully, let's commit it */
apply_wqattrs_commit(ctx);
apply_wqattrs_cleanup(ctx);

return 0;
}

/* allocate the attrs and pwqs for later installation */
static struct apply_wqattrs_ctx *
apply_wqattrs_prepare(struct workqueue_struct *wq,
const struct workqueue_attrs *attrs,
const cpumask_var_t unbound_cpumask)
{
struct apply_wqattrs_ctx *ctx;
struct workqueue_attrs *new_attrs;
int cpu;

lockdep_assert_held(&wq_pool_mutex);

if (WARN_ON(attrs->affn_scope < 0 ||
attrs->affn_scope >= WQ_AFFN_NR_TYPES))
return ERR_PTR(-EINVAL);

ctx = kzalloc(struct_size(ctx, pwq_tbl, nr_cpu_ids), GFP_KERNEL);

new_attrs = alloc_workqueue_attrs();
if (!ctx || !new_attrs)
goto out_free;

/*
* If something goes wrong during CPU up/down, we'll fall back to
* the default pwq covering whole @attrs->cpumask. Always create
* it even if we don't use it immediately.
*/
copy_workqueue_attrs(new_attrs, attrs);
wqattrs_actualize_cpumask(new_attrs, unbound_cpumask);
cpumask_copy(new_attrs->__pod_cpumask, new_attrs->cpumask);
ctx->dfl_pwq = alloc_unbound_pwq(wq, new_attrs);
if (!ctx->dfl_pwq)
goto out_free;

for_each_possible_cpu(cpu) {
if (new_attrs->ordered) {
ctx->dfl_pwq->refcnt++;
ctx->pwq_tbl[cpu] = ctx->dfl_pwq;
} else {
wq_calc_pod_cpumask(new_attrs, cpu, -1);
ctx->pwq_tbl[cpu] = alloc_unbound_pwq(wq, new_attrs);
if (!ctx->pwq_tbl[cpu])
goto out_free;
}
}

/* save the user configured attrs and sanitize it. */
copy_workqueue_attrs(new_attrs, attrs);
cpumask_and(new_attrs->cpumask, new_attrs->cpumask, cpu_possible_mask);
cpumask_copy(new_attrs->__pod_cpumask, new_attrs->cpumask);
ctx->attrs = new_attrs;

/*
* For initialized ordered workqueues, there should only be one pwq
* (dfl_pwq). Set the plugged flag of ctx->dfl_pwq to suspend execution
* of newly queued work items until execution of older work items in
* the old pwq's have completed.
*/
if ((wq->flags & __WQ_ORDERED) && !list_empty(&wq->pwqs))
ctx->dfl_pwq->plugged = true;

ctx->wq = wq;
return ctx;

out_free:
free_workqueue_attrs(new_attrs);
apply_wqattrs_cleanup(ctx);
return ERR_PTR(-ENOMEM);
}

static void apply_wqattrs_commit(struct apply_wqattrs_ctx *ctx)
{
int cpu;

/* all pwqs have been created successfully, let's install'em */
mutex_lock(&ctx->wq->mutex);

copy_workqueue_attrs(ctx->wq->unbound_attrs, ctx->attrs);

/* save the previous pwqs and install the new ones */
for_each_possible_cpu(cpu)
ctx->pwq_tbl[cpu] = install_unbound_pwq(ctx->wq, cpu,
ctx->pwq_tbl[cpu]);
ctx->dfl_pwq = install_unbound_pwq(ctx->wq, -1, ctx->dfl_pwq);

/* update node_nr_active->max */
wq_update_node_max_active(ctx->wq, -1);

/* rescuer needs to respect wq cpumask changes */
if (ctx->wq->rescuer)
set_cpus_allowed_ptr(ctx->wq->rescuer->task,
unbound_effective_cpumask(ctx->wq));

mutex_unlock(&ctx->wq->mutex);
}

内核会将dfl_pwq字段和per-cpu字段的cpu_pwq都初始化一个struct pool_workqueue实例,但是这些实例都会映射同一个通过get_unbound_pool()获取/创建的worker_pool实例,从而确保不受特定cpu限制

work调度

内核使用queue_work()/queue_work_on()让workqueue使用者向workqueue提交work事件,其逻辑如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
static inline bool queue_work(struct workqueue_struct *wq,
struct work_struct *work)
{
return queue_work_on(WORK_CPU_UNBOUND, wq, work);
}

bool queue_work_on(int cpu, struct workqueue_struct *wq,
struct work_struct *work)
{
bool ret = false;
unsigned long irq_flags;

local_irq_save(irq_flags);

if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
__queue_work(cpu, wq, work);
ret = true;
}

local_irq_restore(irq_flags);
return ret;
}

static void __queue_work(int cpu, struct workqueue_struct *wq,
struct work_struct *work)
{
struct pool_workqueue *pwq;
struct worker_pool *last_pool, *pool;
unsigned int work_flags;
unsigned int req_cpu = cpu;

/*
* While a work item is PENDING && off queue, a task trying to
* steal the PENDING will busy-loop waiting for it to either get
* queued or lose PENDING. Grabbing PENDING and queueing should
* happen with IRQ disabled.
*/
lockdep_assert_irqs_disabled();

/*
* For a draining wq, only works from the same workqueue are
* allowed. The __WQ_DESTROYING helps to spot the issue that
* queues a new work item to a wq after destroy_workqueue(wq).
*/
if (unlikely(wq->flags & (__WQ_DESTROYING | __WQ_DRAINING) &&
WARN_ON_ONCE(!is_chained_work(wq))))
return;
rcu_read_lock();
retry:
/* pwq which will be used unless @work is executing elsewhere */
if (req_cpu == WORK_CPU_UNBOUND) {
if (wq->flags & WQ_UNBOUND)
cpu = wq_select_unbound_cpu(raw_smp_processor_id());
else
cpu = raw_smp_processor_id();
}

pwq = rcu_dereference(*per_cpu_ptr(wq->cpu_pwq, cpu));
pool = pwq->pool;

/*
* If @work was previously on a different pool, it might still be
* running there, in which case the work needs to be queued on that
* pool to guarantee non-reentrancy.
*/
last_pool = get_work_pool(work);
if (last_pool && last_pool != pool) {
struct worker *worker;

raw_spin_lock(&last_pool->lock);

worker = find_worker_executing_work(last_pool, work);

if (worker && worker->current_pwq->wq == wq) {
pwq = worker->current_pwq;
pool = pwq->pool;
WARN_ON_ONCE(pool != last_pool);
} else {
/* meh... not running there, queue here */
raw_spin_unlock(&last_pool->lock);
raw_spin_lock(&pool->lock);
}
} else {
raw_spin_lock(&pool->lock);
}

/*
* pwq is determined and locked. For unbound pools, we could have raced
* with pwq release and it could already be dead. If its refcnt is zero,
* repeat pwq selection. Note that unbound pwqs never die without
* another pwq replacing it in cpu_pwq or while work items are executing
* on it, so the retrying is guaranteed to make forward-progress.
*/
if (unlikely(!pwq->refcnt)) {
if (wq->flags & WQ_UNBOUND) {
raw_spin_unlock(&pool->lock);
cpu_relax();
goto retry;
}
/* oops */
WARN_ONCE(true, "workqueue: per-cpu pwq for %s on cpu%d has 0 refcnt",
wq->name, cpu);
}

/* pwq determined, queue */
trace_workqueue_queue_work(req_cpu, pwq, work);

if (WARN_ON(!list_empty(&work->entry)))
goto out;

pwq->nr_in_flight[pwq->work_color]++;
work_flags = work_color_to_flags(pwq->work_color);

/*
* Limit the number of concurrently active work items to max_active.
* @work must also queue behind existing inactive work items to maintain
* ordering when max_active changes. See wq_adjust_max_active().
*/
if (list_empty(&pwq->inactive_works) && pwq_tryinc_nr_active(pwq, false)) {
if (list_empty(&pool->worklist))
pool->watchdog_ts = jiffies;

trace_workqueue_activate_work(work);
insert_work(pwq, work, &pool->worklist, work_flags);
kick_pool(pool);
} else {
work_flags |= WORK_STRUCT_INACTIVE;
insert_work(pwq, work, &pwq->inactive_works, work_flags);
}

out:
raw_spin_unlock(&pool->lock);
rcu_read_unlock();
}

其整体逻辑也很清晰,如果是bound类型的workqueue,则调用insert_work()将其添加到cpu_pwq字段对应的当前cpu的per_cpu的worker_pool中;如果是unbound类型的workqueue,则调用insert_work()将其添加到wq_select_unbound_cpu()选择的cpu的per_cpu的worker_pool

在添加玩work事件后,还需要调用kick_pool()来唤醒worker_pool中的worker线程,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* kick_pool - wake up an idle worker if necessary
* @pool: pool to kick
*
* @pool may have pending work items. Wake up worker if necessary. Returns
* whether a worker was woken up.
*/
static bool kick_pool(struct worker_pool *pool)
{
struct worker *worker = first_idle_worker(pool);
struct task_struct *p;

lockdep_assert_held(&pool->lock);

if (!need_more_worker(pool) || !worker)
return false;

if (pool->flags & POOL_BH) {
kick_bh_pool(pool);
return true;
}

p = worker->task;

#ifdef CONFIG_SMP
/*
* Idle @worker is about to execute @work and waking up provides an
* opportunity to migrate @worker at a lower cost by setting the task's
* wake_cpu field. Let's see if we want to move @worker to improve
* execution locality.
*
* We're waking the worker that went idle the latest and there's some
* chance that @worker is marked idle but hasn't gone off CPU yet. If
* so, setting the wake_cpu won't do anything. As this is a best-effort
* optimization and the race window is narrow, let's leave as-is for
* now. If this becomes pronounced, we can skip over workers which are
* still on cpu when picking an idle worker.
*
* If @pool has non-strict affinity, @worker might have ended up outside
* its affinity scope. Repatriate.
*/
if (!pool->attrs->affn_strict &&
!cpumask_test_cpu(p->wake_cpu, pool->attrs->__pod_cpumask)) {
struct work_struct *work = list_first_entry(&pool->worklist,
struct work_struct, entry);
p->wake_cpu = cpumask_any_distribute(pool->attrs->__pod_cpumask);
get_work_pwq(work)->stats[PWQ_STAT_REPATRIATED]++;
}
#endif
wake_up_process(p);
return true;
}

这里可以看到,对于非POOL_BH类型的,其就是唤醒worker_pool中的idle worker进程即可。

而对于POOL_BH类型,前面worker进程小节中提到过,对于POOL_BH类型并不会创建worker进程,而是在tasklet的下半部的workqueue_softirq_action()中直接调用bh_worker()进行处理,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void workqueue_softirq_action(bool highpri)
{
struct worker_pool *pool =
&per_cpu(bh_worker_pools, smp_processor_id())[highpri];
if (need_more_worker(pool))
bh_worker(list_first_entry(&pool->workers, struct worker, node));
}

static void bh_worker(struct worker *worker)
{
struct worker_pool *pool = worker->pool;
int nr_restarts = BH_WORKER_RESTARTS;
unsigned long end = jiffies + BH_WORKER_JIFFIES;

raw_spin_lock_irq(&pool->lock);
worker_leave_idle(worker);

/*
* This function follows the structure of worker_thread(). See there for
* explanations on each step.
*/
if (!need_more_worker(pool))
goto done;

WARN_ON_ONCE(!list_empty(&worker->scheduled));
worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND);

do {
struct work_struct *work =
list_first_entry(&pool->worklist,
struct work_struct, entry);

if (assign_work(work, worker, NULL))
process_scheduled_works(worker);
} while (keep_working(pool) &&
--nr_restarts && time_before(jiffies, end));

worker_set_flags(worker, WORKER_PREP);
done:
worker_enter_idle(worker);
kick_pool(pool);
raw_spin_unlock_irq(&pool->lock);
}

可以看到,基本和worker_thread()逻辑类似,但是没有了睡眠和唤醒的逻辑,即执行完待处理的work事件后就退出,避免阻塞tasklet下半部的逻辑。因此workqueue只需要在kich_bh_pool()中在对应的cpu上生成HI_SOFTIRQ/TASKLET_SOFTIRQ事件即可,后续会在tasklet中断下半部完成work事件的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static void kick_bh_pool(struct worker_pool *pool)
{
#ifdef CONFIG_SMP
/* see drain_dead_softirq_workfn() for BH_DRAINING */
if (unlikely(pool->cpu != smp_processor_id() &&
!(pool->flags & POOL_BH_DRAINING))) {
irq_work_queue_on(bh_pool_irq_work(pool), pool->cpu);
return;
}
#endif
if (pool->attrs->nice == HIGHPRI_NICE_LEVEL)
raise_softirq_irqoff(HI_SOFTIRQ);
else
raise_softirq_irqoff(TASKLET_SOFTIRQ);
}

void __init workqueue_init_early(void)
{
...
void (*irq_work_fns[2])(struct irq_work *) = { bh_pool_kick_normal,
bh_pool_kick_highpri };
...
/* initialize BH and CPU pools */
for_each_possible_cpu(cpu) {
...
for_each_bh_worker_pool(pool, cpu) {
...
init_irq_work(bh_pool_irq_work(pool), irq_work_fns[i]);
i++;
}
...
}
...
}

static void bh_pool_kick_normal(struct irq_work *irq_work)
{
raise_softirq_irqoff(TASKLET_SOFTIRQ);
}

static void bh_pool_kick_highpri(struct irq_work *irq_work)
{
raise_softirq_irqoff(HI_SOFTIRQ);
}

参考

  1. 一文了解 OS-中断
  2. 计算机中断体系一:历史和原理
  3. 计算机中断体系二:中断处理
  4. 计算机中断体系三:中断路由
  5. 8259A PIC手册
  6. Intel® 64 and IA-32 Architectures Software Developer’s Manua-Volume 3A: System Programming Guide, Part 1-CHAPTER 11 ADVANCED PROGRAMMABLE INTERRUPT CONTROLLER (APIC)
  7. 再谈中断(APIC)
  8. 82093AA I/O ADVANCED PROGRAMMABLE INTERRUPT CONTROLLER (IOAPIC
  9. PCI/PCIe 总线概述(6)—MSI和MSI-X中断机制
  10. Linux kernel的中断子系统之(二):IRQ Domain介绍
  11. 【原创】Linux中断子系统(二)-通用框架处理
  12. 一文完全读懂 | Linux中断处理
  13. IRQ域层级结构
  14. Linux内核 | 中断机制
  15. 深度剖析Linux 网络中断下半部处理(看完秒懂)
  16. Linux 中断(IRQ/softirq)基础:原理及内核实现(2022)
  17. tasklet(linux kernel 中断下半部的实现机制)
  18. 扒开 Linux 中断的底裤之 workqueue
  19. Linux 的 workqueue 机制浅析
  20. IRQ Descriptor and irq_chip