x86-64 / Linux ~ Inter processor interrupt (IPI)を理解する

# はじめに
Inter processor interrupt(IPI) の動作を説明します。
IPI は Multi-core processor(including hyper-threading) 間の通信に利用されます。

Linux kernel / QEMU(x86-64)を動作させて、gdbで動きを確認します。
[QEMU / gdb で Linux kernel の動きを確認する](http://qiita.com/tobira-code/items/4ac30b34453da7afbbd5)を利用します。

# IPIを発行しているところ
__default_send_IPI_dest_fieldを使ってIPIを発行します。

>
arch/x86/include/asm/ipi.h
static inline void
__default_send_IPI_dest_field(unsigned int mask, int vector, unsigned int dest)
>

コンソールでEnterを入力したタイミングでgdb break / backtraceを取得しました。
smp_irq_work_interruptからの一連の呼出しで__default_send_IPI_dest_fieldがCallされています。

__default_send_IPI_dest_fieldは次のIPIを発行しています。

> vector=253

はRESCHEDULE_VECTORです。スケジュール用のIPIです。

```:backtrace
(gdb) b __default_send_IPI_dest_field

Breakpoint 14 at 0xffffffff81020f9f: __default_send_IPI_dest_field. (5 locations)
(gdb) commands
Type commands for breakpoint(s) 14, one per line.
End with a line saying just "end".
>bt
>c
>end
(gdb) 
(gdb) c
Continuing.

Breakpoint 14, _flat_send_IPI_mask (vector=253, mask=1) at arch/x86/kernel/apic/apic_flat_64.c:64
64		__default_send_IPI_dest_field(mask, vector, apic->dest_logical);
#0  _flat_send_IPI_mask (vector=253, mask=1) at arch/x86/kernel/apic/apic_flat_64.c:64
#1  flat_send_IPI_mask (cpumask=0xffffffff81206e28 <cpu_bit_bitmap+8>, vector=253) at arch/x86/kernel/apic/apic_flat_64.c:72
#2  0xffffffff8104d7ae in smp_send_reschedule (cpu=0) at /home/user/linux-3.13.0/arch/x86/include/asm/smp.h:140
#3  ttwu_queue_remote (cpu=0, p=0xffff88000744a160) at kernel/sched/core.c:1540
#4  ttwu_queue (cpu=0, p=0xffff88000744a160) at kernel/sched/core.c:1556
#5  try_to_wake_up (p=0xffff88000744a160, state=<optimized out>, wake_flags=<optimized out>) at kernel/sched/core.c:1629
#6  0xffffffff8104d7fe in default_wake_function (curr=curr@entry=0xffff880007467e80, mode=<optimized out>, wake_flags=<optimized out>, key=<optimized out>) at kernel/sched/core.c:2722
#7  0xffffffff81055f79 in autoremove_wake_function (wait=0xffff880007467e80, mode=<optimized out>, sync=<optimized out>, key=<optimized out>) at kernel/sched/wait.c:292
#8  0xffffffff81055f3f in __wake_up_common (q=q@entry=0xffffffff8141f060 <rcu_sched_state+288>, mode=mode@entry=3, nr_exclusive=nr_exclusive@entry=1, wake_flags=wake_flags@entry=0, key=key@entry=0x0 <irq_stack_union>) at kernel/sched/wait.c:72
#9  0xffffffff8105611f in __wake_up (q=0xffffffff8141f060 <rcu_sched_state+288>, mode=3, nr_exclusive=1, key=0x0 <irq_stack_union>) at kernel/sched/wait.c:94
#10 0xffffffff8106b99f in __irq_work_run () at kernel/irq_work.c:140
#11 irq_work_run () at kernel/irq_work.c:156
#12 0xffffffff81005d16 in __smp_irq_work_interrupt () at arch/x86/kernel/irq_work.c:22
#13 smp_irq_work_interrupt (regs=<optimized out>) at arch/x86/kernel/irq_work.c:28
#14 <signal handler called>
#15 0xffffffffffffff09 in ?? ()
```

smp_irq_work_interruptの割り込みを発行している場所を見ましょう。
smp_apic_timer_interruptからの一連の呼出しでCallされています。

```backtrace
Breakpoint 27, arch_irq_work_raise () at arch/x86/kernel/irq_work.c:44
44		if (!cpu_has_apic)
#0  arch_irq_work_raise () at arch/x86/kernel/irq_work.c:44
#1  0xffffffff8106b8c7 in irq_work_queue (work=work@entry=0xffffffff8141f1e8 <rcu_sched_state+680>) at kernel/irq_work.c:82
#2  0xffffffff81061dd0 in rcu_start_gp_advanced (rsp=rsp@entry=0xffffffff8141ef40 <rcu_sched_state>, rdp=<optimized out>, rnp=<optimized out>) at kernel/rcu/tree.c:1664
#3  0xffffffff8106272d in rcu_start_gp (rsp=rsp@entry=0xffffffff8141ef40 <rcu_sched_state>) at kernel/rcu/tree.c:1689
#4  0xffffffff81062a28 in __rcu_process_callbacks (rsp=0xffffffff8141ef40 <rcu_sched_state>) at kernel/rcu/tree.c:2297
#5  rcu_process_callbacks (unused=<optimized out>) at kernel/rcu/tree.c:2319
#6  0xffffffff8103060d in __do_softirq () at kernel/softirq.c:253
#7  0xffffffff810308f5 in invoke_softirq () at kernel/softirq.c:339
#8  irq_exit () at kernel/softirq.c:381
#9  0xffffffff8102000b in exiting_irq () at /home/user/linux-3.13.0/arch/x86/include/asm/apic.h:708
#10 smp_apic_timer_interrupt (regs=<optimized out>) at arch/x86/kernel/apic/apic.c:931
#11 <signal handler called>
#12 0xffffffffffffff10 in ?? ()
```

smp_apic_timer_interruptの割り込みを発行している場所を見ましょう。
smp_apic_timer_interruptは頻繁に呼び出されていました。

```
Breakpoint 79, raise_softirq (nr=nr@entry=1) at kernel/softirq.c:408
408	{
#0  raise_softirq (nr=nr@entry=1) at kernel/softirq.c:408
#1  0xffffffff81036001 in run_local_timers () at kernel/timer.c:1385
#2  update_process_times (user_tick=0) at kernel/timer.c:1356
#3  0xffffffff810699a3 in tick_periodic (cpu=cpu@entry=1) at kernel/time/tick-common.c:90
#4  0xffffffff81069ad8 in tick_handle_periodic (dev=0xffff880007b0c900) at kernel/time/tick-common.c:102
#5  0xffffffff81020006 in smp_apic_timer_interrupt (regs=<optimized out>) at arch/x86/kernel/apic/apic.c:930
#6  <signal handler called>
#7  0xffffffffffffff10 in ?? ()
Cannot access memory at address 0x202
(gdb) 
Continuing.

Breakpoint 8, raise_softirq (nr=nr@entry=7) at kernel/softirq.c:408
408	{
#0  raise_softirq (nr=nr@entry=7) at kernel/softirq.c:408
#1  0xffffffff81054258 in trigger_load_balance (rq=<optimized out>, cpu=<optimized out>) at kernel/sched/fair.c:6896
#2  0xffffffff8104cc00 in scheduler_tick () at kernel/sched/core.c:2321
#3  0xffffffff81036024 in update_process_times (user_tick=0) at kernel/timer.c:1362
#4  0xffffffff810699a3 in tick_periodic (cpu=cpu@entry=1) at kernel/time/tick-common.c:90
#5  0xffffffff81069ad8 in tick_handle_periodic (dev=0xffff880007b0c900) at kernel/time/tick-common.c:102
#6  0xffffffff81020006 in smp_apic_timer_interrupt (regs=<optimized out>) at arch/x86/kernel/apic/apic.c:930
#7  <signal handler called>
#8  0xffffffffffffff10 in ?? ()

Breakpoint 8, raise_softirq (nr=nr@entry=9) at kernel/softirq.c:408
408	{
#0  raise_softirq (nr=nr@entry=9) at kernel/softirq.c:408
#1  0xffffffff8106188a in invoke_rcu_core () at kernel/rcu/tree.c:2344
#2  0xffffffff81063475 in rcu_check_callbacks (cpu=cpu@entry=1, user=user@entry=0) at kernel/rcu/tree.c:2179
#3  0xffffffff8103600b in update_process_times (user_tick=0) at kernel/timer.c:1357
#4  0xffffffff810699a3 in tick_periodic (cpu=cpu@entry=1) at kernel/time/tick-common.c:90
#5  0xffffffff81069ad8 in tick_handle_periodic (dev=0xffff880007b0c900) at kernel/time/tick-common.c:102
#6  0xffffffff81020006 in smp_apic_timer_interrupt (regs=<optimized out>) at arch/x86/kernel/apic/apic.c:930
#7  <signal handler called>
#8  0xffffffffffffff10 in ?? ()
Cannot access memory at address 0x246
(gdb) c
Continuing.
```

smp_apic_timer_interrupt から raise_softirq 1 / 7 / 9 が呼ばれます。
値の定義は以下にあります。TIMER_SOFTIRQ(1) / SCHED_SOFTIRQ(7) / RCU_SOFTIRQ(9)です。

```c:include/linux/interrupt.h
enum
{
>---HI_SOFTIRQ=0,
>---TIMER_SOFTIRQ,
>---NET_TX_SOFTIRQ,
>---NET_RX_SOFTIRQ,
>---BLOCK_SOFTIRQ,
>---BLOCK_IOPOLL_SOFTIRQ,
>---TASKLET_SOFTIRQ,
>---SCHED_SOFTIRQ,
>---HRTIMER_SOFTIRQ,
>---RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

>---NR_SOFTIRQS
};
```

割り込みハンドラの対応は次の通りです。

```
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);
open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);
```

# \_\_default_send_IPI_dest_field

__default_send_IPI_dest_fieldの処理を見ましょう。

```c:arch/x86/include/asm/ipi.h
 92 static inline void
 93  __default_send_IPI_dest_field(unsigned int mask, int vector, unsigned int dest)
 94 {
 95 >---unsigned long cfg;
 96 
 97 >---/*
 98 >--- * Wait for idle.
 99 >--- */
100 >---if (unlikely(vector == NMI_VECTOR))
101 >--->---safe_apic_wait_icr_idle();
102 >---else
103 >--->---__xapic_wait_icr_idle();
104 
105 >---/*
106 >--- * prepare target chip field
107 >--- */
108 >---cfg = __prepare_ICR2(mask);
109 >---native_apic_mem_write(APIC_ICR2, cfg);
110 
111 >---/*
112 >--- * program the ICR
113 >--- */
114 >---cfg = __prepare_ICR(0, vector, dest);
115 
116 >---/*
117 >--- * Send the IPI. The write to APIC_ICR fires this off.
118 >--- */
119 >---native_apic_mem_write(APIC_ICR, cfg);
120 }
```

native_apic_mem_writeでIPIを発行します。
native_apic_mem_writeを見ましょう。

```c:arch/x86/include/asm/apic.h
static inline void native_apic_mem_write(u32 reg, u32 v)
{
>---volatile u32 *addr = (volatile u32 *)(APIC_BASE + reg);

>---alternative_io("movl %0, %1", "xchgl %0, %1", X86_FEATURE_11AP,
>--->---       ASM_OUTPUT2("=r" (v), "=m" (*addr)),
>--->---       ASM_OUTPUT2("0" (v), "m" (*addr)));
}
```

native_apic_mem_writeではAPIC_BASE + regにデータを書き込むことでIPIを発行します。

> 109 >---native_apic_mem_write(APIC_ICR2, cfg);
> 119 >---native_apic_mem_write(APIC_ICR, cfg);

APIC_ICR / APIC_ICR2 の定義は次の通りです。

```c:arch/x86/include/asm/apicdef.h
#define>APIC_ICR>---0x300
#define>APIC_ICR2>--0x310
```

flat_send_IPI_maskをdisassembleしてみましょう。

```
(gdb) disas flat_send_IPI_mask
Dump of assembler code for function flat_send_IPI_mask:
(snip)
   0xffffffff8102395e <+46>:	mov    %r12d,%eax
   0xffffffff81023961 <+49>:	shl    $0x18,%eax
   0xffffffff81023964 <+52>:	mov    %eax,0xffffffffff5fb310
   0xffffffff8102396b <+59>:	mov    %esi,%eax
   0xffffffff8102396d <+61>:	or     %ebx,%eax
   0xffffffff8102396f <+63>:	or     $0x4,%bh
   0xffffffff81023972 <+66>:	cmp    $0x2,%esi
   0xffffffff81023975 <+69>:	cmove  %ebx,%eax
   0xffffffff81023978 <+72>:	mov    %eax,0xffffffffff5fb300
```

次の命令がIPI発行命令です。

>   0xffffffff81023964 <+52>:	mov    %eax,0xffffffffff5fb310
>   0xffffffff81023978 <+72>:	mov    %eax,0xffffffffff5fb300

%eaxには何が設定されているのでしょうか。
0xffffffff81023964 / 0xffffffff81023978で実行を止めて値を見てみます。

```
(gdb) b *0xffffffff81023964
Breakpoint 2 at 0xffffffff81023964: file /home/user/linux-3.13.0/arch/x86/include/asm/apic.h, line 105.
(gdb) c
Continuing.

Breakpoint 2, __default_send_IPI_dest_field (dest=2048, vector=<optimized out>, mask=2) at /home/user/linux-3.13.0/arch/x86/include/asm/ipi.h:109
109		native_apic_mem_write(APIC_ICR2, cfg);
(gdb) p/x $eax
$1 = 0x2000000
(gdb) until *0xffffffff81023978
__default_send_IPI_dest_field (dest=<optimized out>, vector=<optimized out>, mask=2) at /home/user/linux-3.13.0/arch/x86/include/asm/ipi.h:119
119		native_apic_mem_write(APIC_ICR, cfg);
(gdb) p/x $eax
$2 = 0x8fd
```

%eaxは次の値になっています。

> $1 = 0x2000000
> $2 = 0x8fd

# Interrupt Command Register (ICR)
x86-64にはIPIを発行する仕組みがあります。
Interrupt Command Register (ICR)を使用します。
Intelのmanualからの抜粋です。

![1.JPG](https://qiita-image-store.s3.amazonaws.com/0/84205/c514c889-5c05-6105-bf48-76e9a7e94992.jpeg)

先ほどの%eaxの値は次のようになっています。

>
Vector = 0xfd (RESCHEDULE_VECTOR:253)
Delivery Mode = 00 : 000 (Fixed)
Destination Mode  = 1: Logical
Delivery Status (Read Only) = 0 : 0 (Idle), 1 (Send Pending)
Destination Shorthand = 00: (No Shorthand)
Destination Field = 2
>

※ Level / Trigger Mode は INIT level de-assert delivery modeでのみ有効であるため省略します。

送り先のprocessorsはDestination Fieldで指定します。
Logical Destination Modeではmessage destination address (MDA)といいます。

Logical Destination Modeでは次のRegisterも併せて使用します。

- Local destination register (LDR)
- Destination format register (DFR)

![1.JPG](https://qiita-image-store.s3.amazonaws.com/0/84205/a85cbf66-b004-3b99-2f44-ecf66c649d8f.jpeg)

![2.JPG](https://qiita-image-store.s3.amazonaws.com/0/84205/a4b81686-6e8c-7f21-5529-7240c530b7b3.jpeg)

DFR設定はFlat Modelを使用していました。したがって以降はFlat Modelについて説明します。

使い方は次の通りです。

準備

- processor毎にLogical APIC IDのビットを割り当てる。
- processor毎に割り当てたビットのみを立てた値をLDRのLogical APIC ID fieldに設定する。

IPI発行

- 送信したいprocessorに対応するビットを立てたDestination Fieldを用意する。
- ICRに書き込む。

# IPI受け付け
smp_reschedule_interruptで RESCHEDULE_VECTOR(253)を受け付けます。

```c:arch/x86/kernel/smp.c
__visible void smp_reschedule_interrupt(struct pt_regs *regs)
{
>---ack_APIC_irq();
>---__smp_reschedule_interrupt();
(snip)
}
```

ack_APIC_irqでIPIの受け付けを完了させます。
End-of-interrupt(EOI) registerに0を書き込みます。

```c:arch/x86/include/asm/apic.h
static inline void apic_eoi(void)
{
>---apic->eoi_write(APIC_EOI, APIC_EOI_ACK);
}
```

![1.JPG](https://qiita-image-store.s3.amazonaws.com/0/84205/7cebd1fd-9c11-0bf0-f71a-de12fc129f61.jpeg)