[Linux] 割込みとaffinity

Linux で割込みはどの CPU に入るのか? 決まった CPU に入れられるのか? という質問がよくあって、聞かれるごとに忘れてることが多いのでメモしておきます。

$ cat /proc/irq/<IRQ>/smp_affinity
f
$ echo 1 > /proc/irq/<IRQ>/smp_affinity

いきなり答えを書いてますが、要は procfs の割込み番号に対応した smp_affinity が CPUマスクを表していて、CPU0,1,2,3 を 4bit で表した 16進数 “f” が表示されています。つまり、CPU0,1,2,3 のどれかにこの割込みが入る、ということになります。

また、 smp_affinity に CPU マスクを設定すれば、指定した CPU にだけその割込みが入るようになります。上の例では “1” なので CPU0 だけに入ります。

arm64 (GICv3) での実装

では実際のところどうなってるのか気になるので、ソースを追ってみます。割込み関連は kernel/irq/manage.c が制御の起点になっていて、procfs からの要求は irq_select_affinity_usr() を経由して irq_setup_affinity() が呼ばれます。

int irq_setup_affinity(struct irq_desc *desc)
{
...
        cpumask_and(&mask, cpu_online_mask, set);
        if (cpumask_empty(&mask))
        cpumask_copy(&mask, cpu_online_mask); 
...
        ret = irq_do_set_affinity(&desc->irq_data, &mask, false); 

set に /proc/irq/<IRQ>/smp_affinity の値、cpu_online_mask に現在有効な CPU を表すマスク値が入り、and を取って 割込みを入れる CPU の候補を作ります。

int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask, bool force) 
{
...
        ret = chip->irq_set_affinity(data, mask, force); 

irq_do_set_affinity() では割込みが属している irqchip の関数を呼び出します。GICv3 は drivers/irqchip/irq-gic-v3.c の gic_set_affinity() です。

static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val,
                            bool force)
{
...
        if (force)
                cpu = cpumask_first(mask_val);
        else
                cpu = cpumask_any_and(mask_val, cpu_online_mask);
...
        enabled = gic_peek_irq(d, GICD_ISENABLER);
        if (enabled)
               gic_mask_irq(d);

        reg = gic_dist_base(d) + GICD_IROUTER + (gic_irq(d) * 8);
        val = gic_mpidr_to_affinity(cpu_logical_map(cpu));

        gic_write_irouter(val, reg);

まず cpumask_any_and() で動作している CPU と CPUマスク値の and を(再び)取り、割り込む CPU を決定します。any ではありますが、結局はマスクの若い番号が選ばれます。

GIC に “GICD_IROUTERn” というレジスタがあります。該当する割込みのレジスタに CPU を指定すれば完了です。とはいえ CPU の表現は単なる数字ではなく、”MPIDR” レジスタで表現された値を使います。

static u64 gic_mpidr_to_affinity(unsigned long mpidr)
{
        u64 aff;

        aff = ((u64)MPIDR_AFFINITY_LEVEL(mpidr, 3) << 32 |
               MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16 |
               MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8  |
               MPIDR_AFFINITY_LEVEL(mpidr, 0));

        return aff;
} 

CPU は単純に 0,1,2,3… で表現しますが、実際にはいくつかの CPU がクラスタ(グループ)を形成し、いくつかのクラスタがさらにクラスタを形成し…というように階層化されています。1つのクラスタに入る CPU は個数やアーキテクチャが決まっていることから、例えば A73 x2 + A53 x4 のシステムだと2クラスタを形成します。MPIDR は 4階層で表現するため、A73 がクラスタ0を形成しているとして CPU0 が(0.0.0.0), CPU1 が (0.0.0.1)、A53 がクラスタ1を形成しているとして CPU2 が (0.0.1.0), CPU3 が (0.0.1.1), CPU4 が (0.0.1.2), CPU5 が (0.0.1.3) と表現します。

GICD_IROUTERn に設定する CPU値もこの4階層で表現しますが、MPIDR とはビット配置が異なるため gic_mpidr_to_affinity() で変換しています。

gic_write_irouter() で実際に書き込めば、割込みの CPU 指定は完了です。


コメント

タイトルとURLをコピーしました