首先看一下vector_irq的定义,此每处理器数组变量,保存每个处理器上中断向量所对应的中断号,其以中断向量值为索引。系统中定义了256个中断向量。相关代码如下:
typedef int vector_irq_t[NR_VECTORS];
DEFINE_PER_CPU(vector_irq_t, vector_irq) = {
[0 ... NR_VECTORS - 1] = VECTOR_UNDEFINED,
};
#define NR_VECTORS 256
另一个是used_vector的定义,其为一个位图变量,用来标记系统中已经使用的中断向量。
DECLARE_BITMAP(used_vectors, NR_VECTORS);
中断向量初始化
中断初始化入口函数init_IRQ。为支持传统的PIC类型的终端控制器,如8259,内核预留了IRQ0_VECTOR 到 IRQ15_VECTOR 共16个中断向量,分别对应IRQ号0到15。在初始化时,也不是全部预留这16个中断向量,而是根据检测到的传统PIC中断控制器所支持中断数量在预留,有可能小于16个。x86_init.irqs.intr_init()指针对应的为native_init_IRQ函数,接下来介绍。
void __init init_IRQ(void)
{
for (i = 0; i < nr_legacy_irqs(); i++)
per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i;
x86_init.irqs.intr_init();
}
函数apic_intr_init负责分配系统自身所需的中断向量。x86架构的IDT中断描述表可用的外部中断向量由0x20(FIRST_EXTERNAL_VECTOR)开始,其中0x80(IA32_SYSCALL_VECTOR)为系统调用所用的中断向量;0x30-0x3f共16个中断向量保留给ISA使用。interrupt函数参见文件arch/x86/kernel/entry_64.S中的定义,表示中断处理程序的入口地址,for_each_clear_bit_from循环将所有未使用的中断向量的处理程序入口设定为interrupt的相应偏移。最后对于传统的中断控制器,irq2用作两片控制器的级联。
void __init native_init_IRQ(void)
{
apic_intr_init();
i = FIRST_EXTERNAL_VECTOR;
for_each_clear_bit_from(i, used_vectors, NR_VECTORS) {
set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]);
}
if (!acpi_ioapic && !of_ioapic)
setup_irq(2, &irq2);
}
系统中断向量分配
函数apic_intr_init。首先分配SMP相关的中断向量,参见函数smp_intr_init,随后是分配其他的向量。这些中断向量值由大到小分配,首先是:SPURIOUS_APIC_VECTOR(0xff),目前最小的是:LOCAL_TIMER_VECTOR(0xef)。内核中定义了变量first_system_vector表示当前系统中断向量的最小值。
static void __init apic_intr_init(void)
{
smp_intr_init();
#ifdef CONFIG_X86_THERMAL_VECTOR
alloc_intr_gate(THERMAL_APIC_VECTOR, thermal_interrupt);
}
static void __init smp_intr_init(void)
{
#ifdef CONFIG_SMP
#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
alloc_intr_gate(RESCHEDULE_VECTOR, reschedule_interrupt);
}
alloc_intr_gate宏由alloc_system_vector和set_intr_gate两个函数组成。前者为注册的系统中断向量设置used_vectors中的使用标志,并且更新系统最小向量值:first_system_vector。后者set_intr_gate函数设置处理器的IDT表。
static inline void alloc_system_vector(int vector)
{
if (!test_bit(vector, used_vectors)) {
set_bit(vector, used_vectors);
if (first_system_vector > vector)
first_system_vector = vector;
} else {
BUG();
}
}
#define alloc_intr_gate(n, addr) \
do { \
alloc_system_vector(n); \
set_intr_gate(n, addr); \
} while (0)
外部中断向量分配
由如下函数irq_alloc_hwirqs可见,首先调用函数__irq_alloc_descs分配中断号,内核中已经使用的中断号标记在全局位图变量allocated_irqs中,函数的第一个参数表示期望的中断号,-1表示由系统来选择中断号。系统将在allocated_irqs位图中选择一个不为零的位返回,作为新的中断号。
函数arch_setup_hwirq负责为中断号查找合适的中断向量。注意在执行过程中出现错误的话,函数将执行回退处理,包括由函数irq_free_descs复位之前分配中断号时设置的allocated_irqs中的位。arch_teardown_hwirq函数是对应arch_setup_hwirq函数的清理函数。
unsigned int irq_alloc_hwirqs(int cnt, int node)
{
int i, irq = __irq_alloc_descs(-1, 0, cnt, node, NULL);
if (irq < 0)
return 0;
for (i = irq; cnt > 0; i++, cnt--) {
if (arch_setup_hwirq(i, node))
goto err;
irq_clear_status_flags(i, _IRQ_NOREQUEST);
}
return irq;
err:
for (i--; i >= irq; i--) {
irq_set_status_flags(i, _IRQ_NOREQUEST | _IRQ_NOPROBE);
arch_teardown_hwirq(i);
}
irq_free_descs(irq, cnt);
return 0;
}
arch_setup_hwirq函数的主体是由__assign_irq_vector函数完成,其定义如下。由于终端级别是由中断向量后4位决定的,而本地的APIC不能够很好的处理多个同级别的中断,所以内核以2的4次方,即16为区间,进行中断向量的分配。
使用静态变量current_vector和current_offset分别记录当前的区间起始值和区间内偏移。第一次分配中断向量时,current_vector为33,偏移值为1,所以第一个可供分配的中断向量为34。
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 % 16;
以下的中断向量查找由两个循环组成。第一个循环遍历中断可用的处理器列表,由APIC控制器的可用处理器列表和在线处理器列表中第一个同时置位的处理器开始,第二个循环(内部循环)遍历所有可用的中断向量vector,如果没有合适的选择,即vector的值又回到了最初的current_vector,进行第二次循环,遍历第二个可用的处理器。
获取第二个可用处理器的算法为:1)当前分配的处理器域domain(处理器列表),与老的域值old_domain进行或操作;2)将上一步的结果和APIC处理器列表进行与操作;3)在第二步所得结果和在线处理器列表中获取第一个同时置位的处理器。此即下一个循环要遍历的处理器。
1287 cpumask_clear(cfg->old_domain);
1288 cpu = cpumask_first_and(mask, cpu_online_mask);
1289 while (cpu < nr_cpu_ids) {
1290 int new_cpu, vector, offset;
1291
1294 apic->vector_allocation_domain(cpu, tmp_mask, mask);
1335 if (unlikely(current_vector == vector)) {
1340 cpumask_or(cfg->old_domain, cfg->old_domain, tmp_mask);
1345 cpumask_andnot(tmp_mask, mask, cfg->old_domain);
1348 cpu = cpumask_first_and(tmp_mask, cpu_online_mask);
1350 continue;
1351 }
内部的循环由标签和goto语句组成。每次遍历16长度的区间中的第一个偏移向量值,一直遍历完所有的区间。之后,当vector向量值大于最大可用向量值first_system_vector时,重新由第一个区间的第二个偏移值开始遍历。
如果遍历的向量值没有被使用,即used_vectors位图没有置位。并且,tmp_mask中的所有有效处理器上vector_irq映射还没有被占用,表明此中断向量可用。
1324 vector = current_vector;
1325 offset = current_offset;
1326 next:
1329 vector += 16;
1330 if (vector >= first_system_vector) {
1331 offset = (offset + 1) % 16;
1332 vector = FIRST_EXTERNAL_VECTOR + offset;
1333 }
1334
1335 if (unlikely(current_vector == vector)) {
1340 cpumask_or(cfg->old_domain, cfg->old_domain, tmp_mask);
1345 cpumask_andnot(tmp_mask, mask, cfg->old_domain);
1348 cpu = cpumask_first_and(tmp_mask, cpu_online_mask);
1350 continue;
1351 }
1352
1353 if (test_bit(vector, used_vectors))
1355 goto next;
1357
1358 for_each_cpu_and(new_cpu, tmp_mask, cpu_online_mask) {
1359 if (per_cpu(vector_irq, new_cpu)[vector] > VECTOR_UNDEFINED)
1360 goto next;
1361 }
成功找到可用的中断向量之后,更新静态的current_vector和current_offset的值,并更新tmp_mask中所有处理器的vector_irq中断向量和中断号映射值,写入分配的irq中断号,表明此中断向量已被占用。使用cfg中的domain成员记录有效的处理器列表。
除去第一次申请中断向量外,系统也可能动态的调整中断向量,及为其服务的处理器列表,old_domain用来保存调整前的处理器列表,move_in_progress表示是否正处于调整期间,调整结束之后old_domain为空,move_in_progress也值为0。
1362 /* Found one! */
1363 current_vector = vector;
1364 current_offset = offset;
1365 if (cfg->vector) {
1371 cpumask_copy(cfg->old_domain, cfg->domain);
1374 cfg->move_in_progress = cpumask_intersects(cfg->old_domain, cpu_online_mask);
1377 }
1380 for_each_cpu_and(new_cpu, tmp_mask, cpu_online_mask)
1381 per_cpu(vector_irq, new_cpu)[vector] = irq;
1382 cfg->vector = vector;
1383 cpumask_copy(cfg->domain, tmp_mask);
1387
1388 err = 0;
1389 break;
另外,在中断调整时,如果服务的处理器列表调整前后一致,直接跳出循环(cpumask_equal(tmp_mask, cfg->domain))。如果新的处理器列表是调整前处理器列表的子集,移除多余的处理器,并更新domain为调整之后的处理器列表。
1294 apic->vector_allocation_domain(cpu, tmp_mask, mask);
1295
1296 if (cpumask_subset(tmp_mask, cfg->domain)) {
1297 err = 0;
1303 if (cpumask_equal(tmp_mask, cfg->domain))
1304 break;
1305 /*
1306 * New cpumask using the vector is a proper subset of the current in use mask. So cleanup the vector
1308 * allocation for the members that are not used anymore.
1309 */
1310 cpumask_andnot(cfg->old_domain, cfg->domain, tmp_mask);
1313
1314 cfg->move_in_progress = cpumask_intersects(cfg->old_domain, cpu_online_mask);
1317 cpumask_and(cfg->domain, cfg->domain, tmp_mask);
1320
1321 break;
1322 }
以上的介绍打乱了代码的顺序,以行号为准。
内核版本:3.10.0