■
割り込みアフィニティ
割り込みは物理的には、割り込みコントローラ(PIC: Programmable Interrupt Controller)によって CPU の割り込みピンがアサートされることで発生します(内部割り込みは違います)。SMP(マルチプロセッサ)環境では、割り込みのアフィニティ(どの CPU に割り込みを上げるのか)を決定するため、PIC の Destination Register をセットする必要があります。現代の普通の CPU では、コア数に対応した PIC がチップ内に収められているので、基板を見ても存在しません。古いドキュメントでは、単独チップの Intel 8259 が割り込みの説明に使われているようです。
割り込みを上げる CPU を決定するのは、Linux では request_irq で IRQ を登録するタイミングです。ここでは IRQ は、CPU の物理的なピンを指しています。Linux の /proc/interrupts で見える番号(virq)は、IRQ に対応した仮想的な値です。 以下、カーネルバージョン 2.6.39 、CPU は e500mc コア、PIC は MPIC の例です。
/kernel/irq/manage.c extern int __must_check request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev); 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); }
request_irq は request_threaded_irq のラッパーです。
/kernel/irq/manage.c 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; /* * Sanity-check: shared interrupts must pass in a real dev-ID, * otherwise we'll have trouble later trying to figure out * which interrupt is which (messes up the interrupt freeing * logic etc). */ if ((irqflags & IRQF_SHARED) && !dev_id) return -EINVAL; /* desc 番号を取っておく */ desc = irq_to_desc(irq); if (!desc) return -EINVAL; if (!irq_settings_can_request(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; /* * request_irq だと thread_fn は NULL * thread_fn はカーネルスレッドで実行される */ action->thread_fn = thread_fn; action->flags = irqflags; action->name = devname; action->dev_id = dev_id; chip_bus_lock(desc); /* action を desc テーブルに登録する */ retval = __setup_irq(irq, desc, action); chip_bus_sync_unlock(desc); if (retval) kfree(action); /* 略 */ return retval; }
割り込みハンドラ、フラグ、デバイス名等は、desc テーブル(割り込みディスクリプタ)に登録されます。
/kernel/irq/manage.c static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) { struct irqaction *old, **old_ptr; const char *old_name = NULL; unsigned long flags, thread_mask = 0; int ret, nested, shared = 0; cpumask_var_t mask; if (!desc) return -EINVAL; if (desc->irq_data.chip == &no_irq_chip) return -ENOSYS; /* * Some drivers like serial.c use request_irq() heavily, * so we have to be careful not to interfere with a * running system. */ if (new->flags & IRQF_SAMPLE_RANDOM) { /* * This function might sleep, we want to call it first, * outside of the atomic block. * Yes, this might clear the entropy pool if the wrong * driver is attempted to be loaded, without actually * installing a new handler, but is this really a problem, * only the sysadmin is able to do this. */ rand_initialize_irq(irq); } /* 略 thread_fn 関連 */ if (!alloc_cpumask_var(&mask, GFP_KERNEL)) { ret = -ENOMEM; goto out_thread; } /* * The following block of code has to be executed atomically */ /* IRQ 共有時のチェック */ raw_spin_lock_irqsave(&desc->lock, flags); old_ptr = &desc->action; old = *old_ptr; if (old) { /* * Can't share interrupts unless both agree to and are * the same type (level, edge, polarity). So both flag * fields must have IRQF_SHARED set and the bits which * set the trigger type must match. Also all must * agree on ONESHOT. */ /* * 異なるタイプの割り込みを SHARED で登録することはできない * 例えば、エッジトリガとレベルトリガのデバイスが同じIRQ番号を共有することはできない */ if (!((old->flags & new->flags) & IRQF_SHARED) || ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) || ((old->flags ^ new->flags) & IRQF_ONESHOT)) { old_name = old->name; goto mismatch; } /* All handlers must agree on per-cpuness */ if ((old->flags & IRQF_PERCPU) != (new->flags & IRQF_PERCPU)) goto mismatch; /* add new interrupt at end of irq queue */ do { thread_mask |= old->thread_mask; old_ptr = &old->next; old = *old_ptr; } while (old); shared = 1; } /* * Setup the thread mask for this irqaction. Unlikely to have * 32 resp 64 irqs sharing one line, but who knows. */ if (new->flags & IRQF_ONESHOT && thread_mask == ~0UL) { ret = -EBUSY; goto out_mask; } new->thread_mask = 1 << ffz(thread_mask); if (!shared) { init_waitqueue_head(&desc->wait_for_threads); /* Setup the type (level, edge polarity) if configured: */ if (new->flags & IRQF_TRIGGER_MASK) { ret = __irq_set_trigger(desc, irq, new->flags & IRQF_TRIGGER_MASK); if (ret) goto out_mask; } desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \ IRQS_ONESHOT | IRQS_WAITING); irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); if (new->flags & IRQF_PERCPU) { irqd_set(&desc->irq_data, IRQD_PER_CPU); irq_settings_set_per_cpu(desc); } if (new->flags & IRQF_ONESHOT) desc->istate |= IRQS_ONESHOT; if (irq_settings_can_autoenable(desc)) irq_startup(desc); else /* Undo nested disables: */ desc->depth = 1; /* Exclude IRQ from balancing if requested */ if (new->flags & IRQF_NOBALANCING) { irq_settings_set_no_balancing(desc); irqd_set(&desc->irq_data, IRQD_NO_BALANCING); } /* Set default affinity mask once everything is setup */ /* ここで割り込みを上げる CPU の初期値を決定する */ setup_affinity(irq, desc, mask); } else if (new->flags & IRQF_TRIGGER_MASK) { unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK; unsigned int omsk = irq_settings_get_trigger_mask(desc); if (nmsk != omsk) /* hope the handler works with current trigger mode */ pr_warning("IRQ %d uses trigger mode %u; requested %u\n", irq, nmsk, omsk); } new->irq = irq; *old_ptr = new; /* * 略 後処理色々 * スピンロックを解除する */ return 0; mismatch: /* 略 */ out_mask: /* 略 */ out_thread: /* 略 */ }
setup_affinity が、割り込みの上がる CPU を決定する本体のコードです。
/kernel/irq/manage.c #ifndef CONFIG_AUTO_IRQ_AFFINITY /* * Generic version of the affinity autoselector. */ static int setup_affinity(unsigned int irq, struct irq_desc *desc, struct cpumask *mask) { struct irq_chip *chip = irq_desc_get_chip(desc); struct cpumask *set = irq_default_affinity; int ret; /* Excludes PER_CPU and NO_BALANCE interrupts */ if (!irq_can_set_affinity(irq)) return 0; /* * Preserve an userspace affinity setup, but make sure that * one of the targets is online. */ /* /proc/irq/xxx/smp_affinity での設定を準備すると思われる */ if (irqd_has_set(&desc->irq_data, IRQD_AFFINITY_SET)) { if (cpumask_intersects(desc->irq_data.affinity, cpu_online_mask)) set = desc->irq_data.affinity; else irqd_clear(&desc->irq_data, IRQD_AFFINITY_SET); } cpumask_and(mask, cpu_online_mask, set); /* * アフィニティを設定する * chip->irq_set_affinity はハードウェア(PIC)に依存するコード */ ret = chip->irq_set_affinity(&desc->irq_data, mask, false); switch (ret) { case IRQ_SET_MASK_OK: cpumask_copy(desc->irq_data.affinity, mask); case IRQ_SET_MASK_OK_NOCOPY: irq_set_thread_affinity(desc); } return 0; } #else static inline int setup_affinity(unsigned int irq, struct irq_desc *d, struct cpumask *mask) { return irq_select_affinity(irq); } #endif
ここでは PIC は MPIC を使用するので、chip->irq_set_affinity は mpic_set_affinity になります。 MPIC は AMD の OpenPIC 規格がベースになっているマルチプロセッサ向け割り込みコントローラです。
/arch/powerpc/sysdev/mpic.c int mpic_set_affinity(struct irq_data *d, const struct cpumask *cpumask, bool force) { struct mpic *mpic = mpic_from_irq_data(d); unsigned int src = mpic_irq_to_hw(d->irq); /* 通常 MPIC_SINGLE_DEST_CPU をコンフィグするはず */ if (mpic->flags & MPIC_SINGLE_DEST_CPU) { int cpuid = irq_choose_cpu(cpumask); mpic_irq_write(src, MPIC_INFO(IRQ_DESTINATION), 1 << cpuid); } else { cpumask_var_t tmp; alloc_cpumask_var(&tmp, GFP_KERNEL); cpumask_and(tmp, cpumask, cpu_online_mask); mpic_irq_write(src, MPIC_INFO(IRQ_DESTINATION), mpic_physmask(cpumask_bits(tmp)[0])); free_cpumask_var(tmp); } return 0; }
/arch/powerpc/sysdev/mpic.c static int irq_choose_cpu(const struct cpumask *mask) { int cpuid; if (cpumask_equal(mask, cpu_all_mask)) { static int irq_rover = 0; static DEFINE_RAW_SPINLOCK(irq_rover_lock); unsigned long flags; /* Round-robin distribution... */ /* CPU の数に応じて、ラウンドロビン形式で割り込み先 CPU を指定している */ do_round_robin: raw_spin_lock_irqsave(&irq_rover_lock, flags); irq_rover = cpumask_next(irq_rover, cpu_online_mask); if (irq_rover >= nr_cpu_ids) irq_rover = cpumask_first(cpu_online_mask); cpuid = irq_rover; raw_spin_unlock_irqrestore(&irq_rover_lock, flags); } else { cpuid = cpumask_first_and(mask, cpu_online_mask); if (cpuid >= nr_cpu_ids) goto do_round_robin; } return get_hard_smp_processor_id(cpuid); }
IRQ_DESTINATION は、MPIC の割り込み Destination Register に対応しています。 CPU にもよりますが、グローバルタイマ割り込み・IPI(プロセッサ間割り込み)のように複数 CPU を宛先とする割り込みではない外部割り込みは、このレジスタに複数 CPU に相当するビットを立てると動作が未定義になります。このあたりは CPU のリファレンスを読むと詳しく理解することができるでしょう。
また、CONFIG_AUTO_IRQ_AFFINITY がコンフィグされていたら、以下のオートセレクタが動作します。こちらはセレクト方法が PIC に依存しません。
/linux/arch/alpha/kernel/irq.c #ifdef CONFIG_SMP static char irq_user_affinity[NR_IRQS]; int irq_select_affinity(unsigned int irq) { struct irq_data *data = irq_get_irq_data(irq); struct irq_chip *chip; static int last_cpu; int cpu = last_cpu + 1; if (!data) return 1; chip = irq_data_get_irq_chip(data); if (!chip->irq_set_affinity || irq_user_affinity[irq]) return 1; /* * cpu_possible マクロで搭載された CPU かどうか、 * この CPU にアフィニティ設定ができるかを検査し、CPU をラウンドロビンで選択する * */ while (!cpu_possible(cpu) || !cpumask_test_cpu(cpu, irq_default_affinity)) cpu = (cpu < (NR_CPUS-1) ? cpu + 1 : 0); last_cpu = cpu; /* アフィニティを設定する */ cpumask_copy(data->affinity, cpumask_of(cpu)); chip->irq_set_affinity(data, cpumask_of(cpu), false); return 0; } #endif /* CONFIG_SMP */
以上で、request_irq から割り込みのアフィニティが決定するまでの過程は終了です。基本的にはカーネル起動時に登録される順序、モジュールがinsmodされる順序は変わらないので、手動で設定しない限り、再起動しても割り込みが上がるCPUは固定になるはずです。