Why use MSIX message signal interrupt
处理一个低版本内核中断向量表不够问题:__assign_irq_vector 关联irq 和 vector失败问题; (bug还没解决先记录一下吧)
同时先学习一下MSI-X:MSI, message signal interrupt, 是PCI设备通过写一个特定消息到特定地址,从而触发一个CPU中断。
什么是MSI-X中断?
MSI, message signal interrupt, 是PCI设备通过写一个特定消息到特定地址,从而触发一个CPU中断。特定消息指的是PCIe总线中的Memory Write TLP, 特定地址一般存放在MSI capability中。
MSI-x是MSI的扩展和增强。MSI有它自身的局限性,MSI最多支持32个中断,且要求中断向量连续, 而MSI-x没有这个限制,且支持的中断数量更多。此外,MSI-X的中断向量信息并不直接存储在capability中,而是在一块特殊Memory中.
Pin-based PCI interrupts are often shared amongst several devices. To support this, the kernel must call each interrupt handler associated with an interrupt, which leads to reduced performance for the system as a whole. MSIs are never shared, so this problem cannot arise.
When a device writes data to memory, then raises a pin-based interrupt, it is possible that the interrupt may arrive before all the data has arrived in memory (this becomes more likely with devices behind PCI-PCI bridges). In order to ensure that all the data has arrived in memory, the interrupt handler must read a register on the device which raised the interrupt. PCI transaction ordering rules require that all the data arrive in memory before the value may be returned from the register. Using MSIs avoids this problem as the interrupt-generating write cannot pass the data writes, so by the time the interrupt is raised, the driver knows that all the data has arrived in memory.
PCI devices can only support a single pin-based interrupt per function. Often drivers have to query the device to find out what event has occurred, slowing down interrupt handling for the common case. With MSIs, a device can support more interrupts, allowing each interrupt to be specialised to a different purpose. One possible design gives infrequent conditions (such as errors) their own interrupt which allows the driver to handle the normal interrupt handling path more efficiently. Other possible designs include giving one interrupt to each packet queue in a network card or each port in a storage controller
也就是说和传统的INTx中断相比,MSI中断有以下几个优点:
(1) 基于引脚的传统中断会被多个设备所共享,中断共享时,如果触发了中断,linux需要一一调用对应的中断处理函数,这样会有性能上的损失,而MSI不存在共享的问题。
(2) 设备向内存写入数据,然后发起引脚中断, 有可能会出现CPU收到中断时,数据还没有达到内存。 而使用MSI中断时,产生中断的写不能越过数据的写,驱动可以确信所有的数据已经达到内存。
(3) 多功能的PCI设备,每一个功能最多只有一个中断引脚,当具体的事件产生时,驱动需要查询设备才能知道是哪一个事件产生,这样会降低中断的处理速度。而一个设备可以支持32个MSI中断,每个中断可以对应特定的功能。
- PCIe设备在提交MSI中断请求时,都是向MSI/MSI-X Capability结构中的Message Address的地址写Message Data数据,从而组成一个存储器写TLP,向处理器提交中断请求
参考:https://www.kernel.org/doc/html/latest/PCI/index.html
现在来说一说近期处理的一个关于MSI的bug
硬件平台中1g电口、1g光口、10g光口,目前已经加载了电口以及1g光口驱动, 接口能够正常UP,insmod i40e驱动时,当注册第三个网卡的中断时,出现error log,申请了irq到289 就发现报错。
[ 116.699175] i40e 0000:12:00.0: MSI-X vector reservation failed: -1 [ 116.699177] i40e 0000:12:00.0: init msix vectors:=-19 [ 116.699179] i40e 0000:12:00.0: MSI-X not available, trying MSI [ 116.700066] i40e 0000:12:00.0: MSI init failed - -1
分析代码可以看到是中断向量表不够:
分析一下linux kernel的中断以及i40e驱动代码:
/** * i40e_init_msix - Setup the MSIX capability * @pf: board private structure * Work with the OS to set up the MSIX vectors needed. * Returns the number of vectors reserved or negative on failure **/ static int i40e_init_msix(struct i40e_pf *pf) { -------------------------------------- /* reserve some vectors for the main PF traffic queues. Initially we * only reserve at most 50% of the available vectors, in the case that * the number of online CPUs is large. This ensures that we can enable * extra features as well. Once we've enabled the other features, we * will use any remaining vectors to reach as close as we can to the * number of online CPUs. */ cpus = num_online_cpus(); pf->num_lan_msix = min_t(int, cpus, vectors_left / 2); vectors_left -= pf->num_lan_msix; /* reserve one vector for sideband flow director */ if (pf->flags & I40E_FLAG_FD_SB_ENABLED) { v_budget++; --------------------------- } /* can we reserve enough for iWARP? */ if (pf->flags & I40E_FLAG_IWARP_ENABLED) { v_budget += pf->num_iwarp_msix; vectors_left -= pf->num_iwarp_msix; } /* any vectors left over go for VMDq support */ if (pf->flags & I40E_FLAG_VMDQ_ENABLED) { -------------------- v_budget += vmdq_vecs; vectors_left -= vmdq_vecs; dev_info(&pf->pdev->dev, "I40E_FLAG_VMDQ_ENABLED v_budget:=%d vectors_left:=%d\n", v_budget, vectors_left); } } /* On systems with a large number of SMP cores, we previously limited * the number of vectors for num_lan_msix to be at most 50% of the * available vectors, to allow for other features. Now, we add back * the remaining vectors. However, we ensure that the total * num_lan_msix will not exceed num_online_cpus(). To do this, we * calculate the number of vectors we can add without going over the * cap of CPUs. For systems with a small number of CPUs this will be * zero. */ ------------------------------ v_budget += pf->num_lan_msix; dev_info(&pf->pdev->dev, "msix kmalloc v_budget:=%d vectors_left:=%d\n", v_budget, vectors_left); pf->msix_entries = kcalloc(v_budget, sizeof(struct msix_entry), GFP_KERNEL); if (!pf->msix_entries) return -ENOMEM; for (i = 0; i < v_budget; i++) pf->msix_entries[i].entry = i; //v_budget:the number of MSI-X vectors to request v_actual = i40e_reserve_msix_vectors(pf, v_budget); if (v_actual < I40E_MIN_MSIX) { pf->flags &= ~I40E_FLAG_MSIX_ENABLED; kfree(pf->msix_entries); pf->msix_entries = NULL; pci_disable_msix(pf->pdev); return -ENODEV; } -------------------------- return v_actual; }
根据代码可知:中断i40e 中断初始化主要做两件事:
-
计算所需要的中断向量表vector数目
- 对于vector计算主要考虑:cpu_num 、以及网卡相关特性如VMDQ等
- 根据计算的vector数目申请中断号irq、并且关联中断向量
- 向内核申请MSI-X中断表:i40e_reserve_msix_vectors------>pci_enable_msix_range(pf->pdev, pf->msix_entries, I40E_MIN_MSIX, v_budget);
- 最后调用pci_enable_msix申请中断号
pci_enable_msix函数向PCI子系统请求分配nvec个msix中断,第二个参数entries指向msix_entry结构体数组,元素个数不能少于nvec;
struct msix_entry { u32 vector; /* kernel uses to write allocated vector */ u16 entry; /* driver uses to specify entry, OS writes */ };
/** * pci_enable_msix - configure device's MSI-X capability structure * @dev: pointer to the pci_dev data structure of MSI-X device function * @entries: pointer to an array of MSI-X entries * @nvec: number of MSI-X irqs requested for allocation by device driver * * Setup the MSI-X capability structure of device function with the number * of requested irqs upon its software driver call to request for * MSI-X mode enabled on its hardware device function. A return of zero * indicates the successful configuration of MSI-X capability structure * with new allocated MSI-X irqs. A return of < 0 indicates a failure. * Or a return of > 0 indicates that driver request is exceeding the number * of irqs or MSI-X vectors available. Driver should use the returned value to * re-send its request. **/ int pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries, int nvec) { int status, nr_entries; int i, j; if (!entries) return -EINVAL; //检测是否支持MSI-X status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSIX); if (status) return status; // 计算本网络设备最多支持多少个中断向量表 entries,和期望申请的中断向量表相比 取最小值 nr_entries = pci_msix_table_size(dev); --------------------------------------------------------- /* Check whether driver already requested for MSI irq */ if (dev->msi_enabled) { dev_info(&dev->dev, "can't enable MSI-X " "(MSI IRQ already assigned)\n"); return -EINVAL; } //配置MSXI Capability结构 完成MSI-X中断相关设置 status = msix_capability_init(dev, entries, nvec); dev_info(&dev->dev, "status:%d \n", status); return status; }
msix_capability_init中实现中断初始化的是arch_setup_msi_irqs,对于X86系统,其为x86_setup_msi_irqs,x86_setup_msi_irqs中直接调用了native_setup_msi_irqs,该函数是X86系统中实现MSIX中断初始化的关键函数
static int msix_capability_init(struct pci_dev *dev, struct msix_entry *entries, int nvec) { int pos, ret; u16 control; void __iomem *base; pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); pci_read_config_word(dev, pos + PCI_MSIX_FLAGS, &control); /* Ensure MSI-X is disabled while it is set up */ control &= ~PCI_MSIX_FLAGS_ENABLE; pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control); /* Request & Map MSI-X table region */ base = msix_map_region(dev, pos, multi_msix_capable(control)); ret = msix_setup_entries(dev, pos, base, entries, nvec); ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSIX); //native_setup_msi_irqs ---- setup_msi_irq ------------------------ }
主要工作有:
- 首先调用create_irq_nr函数为此msix中断分配一个中断号irq。
- 对于启用interrupt remap的系统,如果是此设备的第一个msix中断,此时调用msi_alloc_irte分配此设备的nvec个msix中断映射表项。后面的msix中断则调用set_irte_irq函数来建立相应的中断映射。
- 调用setup_msi_irq函数会首先填充MSIX Table中的low_addr、hi_addr和data项;为设备的每一个msix中断指定一个vectore向量号;并将MSIX Table中的内容写到前面映射的地址。
int native_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) { int node, ret, sub_handle, index = 0; unsigned int irq, irq_want; struct msi_desc *msidesc; struct intel_iommu *iommu = NULL; /* x86 doesn't support multiple MSI yet */ if (type == PCI_CAP_ID_MSI && nvec > 1) return 1; node = dev_to_node(&dev->dev); /*nr_irqs_gsi是MSI-X的irq号起始地址 为什么是这个值 不懂*/ irq_want = nr_irqs_gsi; sub_handle = 0; list_for_each_entry(msidesc, &dev->msi_list, list) { /*按照全局配置分配irq号和对应的irq_desc*/ irq = create_irq_nr(irq_want, node); dev_printk(KERN_DEBUG, &dev->dev, "irq want:[%d] irq res [%d] \n", irq_want, irq); ----------------------- if (!intr_remapping_enabled) goto no_ir; if (!sub_handle) { /* * allocate the consecutive block of IRTE's * for 'nvec' */ index = msi_alloc_irte(dev, irq, nvec); ---------------------- } else { iommu = map_dev_to_ir(dev); ----------------------------/* * setup the mapping between the irq and the IRTE * base index, the sub_handle pointing to the * appropriate interrupt remap table entry. */ set_irte_irq(irq, iommu, index, sub_handle); } no_ir: /*使能MSIX中断,把中断信息(关键的vector) 写入PCIE配置区,设置irq_desc数据*/ ret = setup_msi_irq(dev, msidesc, irq); if (ret < 0) goto error; sub_handle++; } return 0; }
该函数中有两个关键函数,分别是create_irq_nr和setup_msi_irq
- create_irq_nr是分配一个vector给当前的中断,分配vector的同时,也为该中断指定了执行CPU
- setup_msi_irq则负责把相关配置信息写入到PCIE配置区,并设置irq_desc的数据,其中关键的是irq_desc的handle_irq被设置 handle_edge_irq。
/* * Dynamic irq allocate and deallocation */ unsigned int create_irq_nr(unsigned int from, int node) { struct irq_cfg *cfg; unsigned long flags; unsigned int ret = 0; int irq; if (from < nr_irqs_gsi) from = nr_irqs_gsi; /*从全局bitmap中查找一个没有用的irq号,并分配和 初始化一个对应的irq_desc,也就是说 irq是系统公用的 不是某一个CPU都有自己的irq bitmap*/ irq = alloc_irq_from(from, node); if (irq < 0) return 0; cfg = alloc_irq_cfg(irq, node); -----------------------//关键函数:负责分配一个vector,并和irq号关联 ret = __assign_irq_vector(irq, cfg, apic->target_cpus()); printk(KERN_DEBUG "..... assign irq ret: %d\n", ret); ---------------------- ----------------return ret; }/*@mask:该vector可以分配在哪些CPU上*/static int
__assign_irq_vector(int irq, struct irq_cfg *cfg, const struct cpumask *mask) { static int current_vector = FIRST_EXTERNAL_VECTOR + VECTOR_OFFSET_START; static int current_offset = VECTOR_OFFSET_START % 8; unsigned int old_vector; int cpu, err; cpumask_var_t tmp_mask;
-----------------------------------
old_vector = cfg->vector; if (old_vector) {/*如果已经存在满足CPU绑定要求的vector,不 多次分配*/ cpumask_and(tmp_mask, mask, cpu_online_mask); cpumask_and(tmp_mask, cfg->domain, tmp_mask); if (!cpumask_empty(tmp_mask)) { free_cpumask_var(tmp_mask); return 0; } } /* Only try and allocate irqs on cpus that are present */ err = -ENOSPC; for_each_cpu_and(cpu, mask, cpu_online_mask) { int new_cpu; int vector, offset; /*获取当前APIC配置要求的中断运行cpu,大部分 (包括当前系统apic_physflat)为tmp_mask就对应变量'cpu'*/ apic->vector_allocation_domain(cpu, tmp_mask); vector = current_vector; offset = current_offset; next: vector += 8;//对 取值+8 不懂if (vector >= first_system_vector) { /* If out of vectors on large boxen, must share them. 不懂*/ offset = (offset + 1) % 8; vector = FIRST_EXTERNAL_VECTOR + offset; } if (unlikely(current_vector == vector)) continue; /*如果是系统预留的vector,不可使用*/ if (test_bit(vector, used_vectors)) goto next; /*如果apic要求的任何cpu核心上该vector已经被占用,则 该vector不可用*/ for_each_cpu_and(new_cpu, tmp_mask, cpu_online_mask) if (per_cpu(vector_irq, new_cpu)[vector] != -1) goto next; /* Found one! */ current_vector = vector; current_offset = offset; if (old_vector) { cfg->move_in_progress = 1; cpumask_copy(cfg->old_domain, cfg->domain); } /*关键步骤:对当前apic配置的可能运行该中断的所有CPU 配置其vector_irq,以便中断发生时可以通过vector查找到 irq号--- 注意是每CPU 的vector 都会关联这个irq*/ for_each_cpu_and(new_cpu, tmp_mask, cpu_online_mask) per_cpu(vector_irq, new_cpu)[vector] = irq; cfg->vector = vector; cpumask_copy(cfg->domain, tmp_mask); err = 0; break; } free_cpumask_var(tmp_mask); return err; }
从实现中可以看到,该函数从FIRST_EXTERNAL_VECTOR(外设中断的起始vector号,通常是0x20) 到first_system_vector(外部中断结束vector号,通常是254,255被系统作为保留的SPURIOUS_APIC_VECTOR使用)的范围中,为当前中断分配一个vector,要求该vector在对应的cpu上均可用,该vector按照系统配置的要求和对应的cpu核心绑定,并在要求的cpu中没有被其它中断使用。需要说明的是,在setup_msi_irq中会再次通过msi_compose_msg再次调用__assign_irq_vector,但是由于这时已经存在满足CPU绑定要求的vector,不会多次分配。
从以上分析可以得到MSI-X中断的一个绑定特征:根据当前APIC配置,每个中断都有对应的可以运行的cpu,pci_enable_msix在这些要求的cpu核心上建立了vector (APIC的配置由数据结构struct apic来抽象,其vector_allocation_domain用于决定需要在那些cpu核心上为该中断建立vector),当前我的系统使用的是apic_physflat,对每个MSI中断,其只在一个cpu核心上建立vector,对应的MSI-X中断事实上被绑定到该cpu核心上。在用户通过echo xxx > /proc/irq/xxx/affinity来调整中断的绑定属性时,内核会重新为该中断分配一个新的在对应核心上可用的vector,但是irq号不会改变。绑定属性调整的调用路径大致为irq_affinity_proc_fops===>irq_affinity_proc_write===> write_irq_affinity===>irq_set_affinity===>irq_set_affinity_locked===>chip->irq_set_affinity(msi_set_affinity)。也就是最终通过msi_set_affinity来实现,在该函数中首先通过 ioapic_set_affinity在绑定属性要求的cpu中选择空闲vector,然后通过__write_msi_msg把配置写入PCIE配置区。需要说明的是:该irq最终可以运行的cpu数量并不完全由用户指定,还与apic的模式相关,对于apic_physflat,实际上只为该irq分配了一个cpu核心,该irq只能运行在用户指定的cpu中的一个,而不是全部。
说一说中断:
先说一说相关概念:
-
vector(中断向量)
vector是一个整数,在X86CPU上,使用vector对中断(interrupt,外部设备产生)和异常(exception,CPU在程序执行中产生)统一编号,每个CPU核心内部,中断/异常和vector所以一一对应的;但是在各个不同的CPU核心上,相同的vector可以对应不同的中断(至少对于linux的设置,异常还是使用相同的vector)。 vector的取值范围为[0,255],其中[0,31]被系统保留使用(多数作为异常的vector),其余的可供外设中断使用(系统设备比如local APIC也占用了部分[32,255]这个范围的vector)。内核使用全局bitmap used_vectors来标识那些vector被系统预留,不能被外设分配使用
-
IDT(interrupt descriptor table)
X86 CPU采用一个有256个元素的数组来描述中断/异常,该数组的index为vector;其内容包括了三种gate descriptor,用于描述一个中断/异常的处理接口;这个数组就是IDT,CPU在收到中断请求的时候,就利用vector获取到对应的中断处理接口描述并执行。
-
irq NO
系统中全局唯一,对应内核数据结构struct irq_desc,每个外设的中断有一个irq号(体系结构预留的中断,是没有对应的irq_desc结构和irq号的),该irq在该中断的生命周期内都不会改变,且和该中断的中断处理函数关联;内核使用一个bitmap allocated_irqs来标识当前系统已经分配的irq;irq号的管理与底层中断设备和配置无关,属于Generic Interrupt Layer;对于irq号分布集中的情况,不配置CONFIG_SPARSE_IRQ,内核采用数组直接管理,数组下标就是irq号;而对于irq号比较分散的,设置CONFIG_SPARSE_IRQ,内核采用radix tree来管理所有的irq号;
irq号和vector号的关联:内核中使用per-cpu变量vector_irq来描述irq号和vector号的关联,对每个CPU,vector_irq是一个数组,在X86架构下成员数量为256,其数组的index为vector,值为irq,如果为-1则表示该CPU上的这个vector尚未分配。
中断初始化
-
对X86 CPU,Linux内核使用全局idt_table来表达当前的IDT,该变量定义在traps.c
-
gate_desc idt_table[NR_VECTORS] __page_aligned_data = { { { { 0, 0 } } }, };//初始化为全
对中断相关的初始化,内核主要有以下工作:
- 设置used_vectors,确保外设不能分配到X86保留使用的vector(预留的vector范围为[0,31],另外还有其他通过apic_intr_init等接口预留的系统使用的vector);
- 设置X86CPU保留使用的vector对应的IDT entry;这些entry使用特定的中断处理接口;
- 设置外设 (包括ISA中断)使用的中断处理接口,这些中断处理接口都一样。
- 设置ISA IRQ使用的irq_desc;
- 把IDT的首地址加载到CPU的IDTR(Interrupt Descriptor Table Register);
void __init trap_init(void) { int i; set_intr_gate(0, ÷_error); set_intr_gate_ist(2, &nmi, NMI_STACK); ----------------------- set_intr_gate(16, &coprocessor_error); set_intr_gate(17, &alignment_check); set_intr_gate(19, &simd_coprocessor_error); /* Reserve all the builtin and the syscall vector: */ for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++) set_bit(i, used_vectors); #ifdef CONFIG_X86_32 set_system_trap_gate(SYSCALL_VECTOR, &system_call); set_bit(SYSCALL_VECTOR, used_vectors); #endif /*Should be a barrier for any external CPU state: */ cpu_init(); x86_init.irqs.trap_init(); } void __init init_IRQ(void) { int i; /* * We probably need a better place for this, but it works for * now ... */ x86_add_irq_domains(); /* * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR's to IRQ 0..15. * If these IRQ's are handled by legacy interrupt-controllers like PIC, * then this configuration will likely be static after the boot. If * these IRQ's are handled by more mordern controllers like IO-APIC, * then this vector space can be freed and re-used dynamically as the * irq's migrate etc. */ for (i = 0; i < legacy_pic->nr_legacy_irqs; i++) per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i; //每个CPU有一个per_cpu变量叫vector_irq,一个数组,来描述irq号和vector号的关联,大小为256,index为vector号,对应值为irq num。 x86_init.irqs.intr_init();//intr_init = native_init_IRQ } void __init native_init_IRQ(void) { int i; /* Execute any quirks before the call gates are initialised: */ //调用init_ISA_irqs,设置了radix tree中对应legacy irq的irq_desc的内容,如desc->irq_data.chip、 desc->handle_irq、 desc->name 等 x86_init.irqs.pre_vector_init(); //apic和一些系统vector的初始化,设置used_vectors,填充对应的idt table选项和used_vectors apic_intr_init(); /* * Cover the whole vector space, no vector can escape * us. (some of these will be overridden and become * 'special' SMP interrupts) //共有256个vector,FIRST_EXTERN_VECTOR为0x20,初始化剩余的IDT表项(剩余项中也有一些是保留的,比如系统调用等 */ for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) { /* IA32_SYSCALL_VECTOR could be used in trap_init already. */ if (!test_bit(i, used_vectors))////调用set_intr_gate将剩余未初始化的向量全部设置中断门,并且用interrupt数组中的元素作为中断处理函数 set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]); } if (!acpi_ioapic && !of_ioapic) setup_irq(2, &irq2); //到此为止,IDT的初始化已经完成了,当系统发生中断时,会通过IDT找到interrupt数组中对应向量的处理指针// //----也就是------初始化interrupt数组之后,对应内存中保存了各个vector的 中断处理函数的地址 }
X86保留vector,这些vector包括[0,0x1f]和APIC等系统部件占用的vector,对这些vector,会记录在bitmap used_vectors中,确保不会被外设分配使用;同时这些vector都使用各自的中断处理接口,其中断处理过程相对简单(没有generic interrupt layer的参与,CPU直接调用到各自的ISR)。
ISA irqs,对这些中断,在初始化过程中已经完成了irq_desc、vector_irq、以及IDT中对应entry的分配和设置,
同时可以发现ISA中断,在初始化的时候都被设置为运行在0号CPU。 0x30-0x3f是ISA的
start_kernel trap_init 使用set_intr_gate等接口初始化保留vector used_vectors中[0,0x1f]对应的vector被置1,表示已经预留不能再使用 cpu_init load_idt((const struct desc_ptr *)&idt_descr) 把&idt_descr的地址加载到idtr native_load_idt() init_IRQ 初始化0号CPU的vector_irq:其vector[0x30-0x3f]和irq号[0-15](ISA中断)对应 x86_init.irqs.intr_init(native_init_IRQ) x86_init.irqs.pre_vector_init(init_ISA_irqs) init_ISA_irqs:初始化ISA,设置irq号[0,15] (ISA中断)的irq_desc apic_intr_init 设置APIC相关中断(使用的alloc_intr_gate会设置used_vectors) 使用interrupt数组初始化设置外设中断idt entry
中断处理接口interrupt数组
interrupt数组是内核中外设中断对应的IDT entry,其在arch/x86/entry/entry_64.S中定义,定义如
[arch/x86/entry/entry_64.S] ENTRY(irq_entries_start) vector=FIRST_EXTERNAL_VECTOR .rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR) /* 代码段内容*/ pushq $(~vector+0x80) /* Note: always in signed byte range */ vector=vector+1 jmp common_interrupt .align 8 .endr END(irq_entries_start)
这些汇编代码意义:在代码段,生成了一个符号irq_entries_start,该符号对应的内容是一组可执行代码.
X86 CPU在准备好了中断执行环境后,会调用中断描述符定义的中断处理入口;
对于用户自定义中断,中断处理入口都是(对系统预留的,就直接执行定义的接口了)common_interrupt
#公共处理函数 common_interrupt: SAVE_ALL /*寄存器值入栈*/ movl %esp,%eax /*栈顶指针保存到eax*/ call do_IRQ /*处理中断*/ jmp ret_from_intr /*从中断返回*/
CPU执行中断的过程
1、 利用vector,查IDT得到中断描述符;
2、 如果中断发生在用户态,会首先执行stack switch切换到内核态执行;
3、 依次保存EFLAGS CS IP到当前栈,如果需要(有error code的异常),把error code PUSH到当前栈。并把IF/TF位清零屏蔽可屏蔽中断;至此,CPU完成了中断处理程序执行环境的建立。
4、 执行中断描述符定义的中断处理入口(IDT中指定地址的代码);
5、 根据环境执行不同的中断退出方式,比如执行现场调度操作(retint_careful和retint_kernel),最终都会执行IRET指令;至此,中断执行完成。 异常的执行过程类似,只不过异常在执行前不会把IF位清零,只清零TF位
转载参考:https://blog.csdn.net/yin262/article/details/53928178
所以说到这里:i40e驱动注册中断失败就清楚了, 就是因为每cpu 中断向量表 都被用完或者被保留了, 结合实际情况,目前一个网卡使用了18个中断向量表,所以为了处理此问题,目前先限制一个网卡最多只能使用cpu_num个中断向量表,毕竟网卡多队列,一个队列需要一个中断,足够了,相比使用18个中断而言,8核cpu节约了不少中断向量表。
所以改动就是:读取的hw->func_caps.num_msix_vectors 值直接修改cpu_num,不适用网卡硬件支持的最大值
modinfo igb---------------可以查看