FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

TCG最適化が施された後にQEMUはどのように割り込み条件を検出しているのか

前回のQEMU解析でBlock Chainingのテクニックにより直接TCG同士をジャンプする方法について調査したが、ここで出てくる疑問は、例えば割り込みの検出やシミュレーションの停止条件など、「一度ホストマシンの制御コードに戻らないと分からないような処理はどのように実現しているのか」という点だ。

これに関していろいろ調査していたのだが、結論としては複数スレッドを用意して実行用のスレッドと監視用のスレッドを並列に動作させている、という点だと思う。

まず、QEMUはシミュレーションを開始すると監視用のメインループが実行される。

  • qemu-5.1.0/softmmu/main.c
int main(int argc, char **argv, char **envp)
{
    qemu_init(argc, argv, envp);
    qemu_main_loop();
    qemu_cleanup();

    return 0;
}

例えば、qemu_main_loop()内で実行されるmain_loop_wait()GDBを使ってBreakPointを張ってみると、

Invalid thread ID: info
(gdb) info threads
  Id   Target Id                                           Frame
* 1    Thread 0x7ffff8740f00 (LWP 10601) "qemu-system-ris" main_loop_wait (nonblocking=nonblocking@entry=0) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/util/main-loop.c:488
  2    Thread 0x7ffff8730700 (LWP 10606) "qemu-system-ris" 0x00007ffffdb812d0 in __GI___nanosleep (requested_time=0x7ffff872f630, remaining=0x7ffff872f640) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
  3    Thread 0x7ffff7e90700 (LWP 10607) "qemu-system-ris" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:103

この時点で3種類のスレッドができていることが分かるのだけれども、ここでは例えば2番目のスレッドに移ってみよう。

(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff8730700 (LWP 10606))]
#0  0x00007ffffdb812d0 in __GI___nanosleep (requested_time=0x7ffff872f630, remaining=0x7ffff872f640) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
28      ../sysdeps/unix/sysv/linux/nanosleep.c: No such file or directory.
(gdb) bt
#0  0x00007ffffdb812d0 in __GI___nanosleep (requested_time=0x7ffff872f630, remaining=0x7ffff872f640) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
#1  0x00007fffff4e78bf in g_usleep () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#2  0x000000000867e190 in call_rcu_thread (opaque=opaque@entry=0x0) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/util/rcu.c:250
#3  0x0000000008675a8a in qemu_thread_start (args=<optimized out>) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/util/qemu-thread-posix.c:521
#4  0x00007ffffdb77164 in start_thread (arg=<optimized out>) at pthread_create.c:486
#5  0x00007ffffda9adef in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

そして実際にスレッドをTCGを動かしているのはThread 3のようだ。

(gdb) info threads
  Id   Target Id                                           Frame
  1    Thread 0x7ffff8740f00 (LWP 10601) "qemu-system-ris" main_loop_wait (nonblocking=nonblocking@entry=0) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/util/main-loop.c:488
* 2    Thread 0x7ffff8730700 (LWP 10606) "qemu-system-ris" 0x00007ffffdb812d0 in __GI___nanosleep (requested_time=0x7ffff872f630, remaining=0x7ffff872f640) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
  3    Thread 0x7ffff7e90700 (LWP 10607) "qemu-system-ris" __fprintf (stream=0x7ffffdb65680 <_IO_2_1_stderr_>, format=format@entry=0x86b3c76 "0x%08lx: ") at fprintf.c:27
(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff7e90700 (LWP 10607))]
#0  __fprintf (stream=0x7ffffdb65680 <_IO_2_1_stderr_>, format=format@entry=0x86b3c76 "0x%08lx: ") at fprintf.c:27
27      fprintf.c: No such file or directory.
(gdb) bt
#0  0x00007ffffd9e5830 in __fprintf (stream=0x7ffffdb65680 <_IO_2_1_stderr_>, format=format@entry=0x86b3c76 "0x%08lx: ") at fprintf.c:27
#1  0x00000000082d590e in cap_dump_insn (info=info@entry=0x7ffff7e8f370, insn=insn@entry=0x7fffa8012dd0, note=0x0) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/disas.c:271
#2  0x00000000082d6580 in cap_disas_host (note=<optimized out>, size=<optimized out>, code=<optimized out>, info=0x7ffff7e8f370) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/disas.c:369
#3  0x00000000082d6580 in disas (out=<optimized out>, code=<optimized out>, size=size@entry=69, note=<optimized out>) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/disas.c:676
#4  0x00000000082fd171 in log_disas (note=<optimized out>, size=69, code=<optimized out>) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/include/exec/log.h:65
#5  0x00000000082fd171 in tb_gen_code (cpu=cpu@entry=0x8cf8b70, pc=pc@entry=4108, cs_base=cs_base@entry=0, flags=flags@entry=259, cflags=-16252928, cflags@entry=524288)
    at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/accel/tcg/translate-all.c:1831
#6  0x00000000082fa6b8 in tb_find (cf_mask=524288, tb_exit=0, last_tb=0x0, cpu=0x0) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/accel/tcg/cpu-exec.c:407
#7  0x00000000082fa6b8 in cpu_exec (cpu=cpu@entry=0x8cf8b70) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/accel/tcg/cpu-exec.c:748
#8  0x000000000834f43f in tcg_cpu_exec (cpu=0x8cf8b70) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/softmmu/cpus.c:1356
#9  0x000000000835142b in qemu_tcg_cpu_thread_fn (arg=arg@entry=0x8cf8b70) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/softmmu/cpus.c:1664
#10 0x0000000008675a8a in qemu_thread_start (args=<optimized out>) at /home/msyksphinz/work/riscv/qemu-work/qemu-5.1.0/util/qemu-thread-posix.c:521
#11 0x00007ffffdb77164 in start_thread (arg=<optimized out>) at pthread_create.c:486
#12 0x00007ffffda9adef in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread3はqemu_tcg_cpu_thread_fn()から始まって、tcg_cpu_exec()が呼び出されTCGの構築と実行が行われる。これがエミュレーションの中心環境だ。

次に、qemu_main_loop()を実行する側のスレッドを観察して、どのようにして終了条件を監視しているのか見てみよう。

int main(int argc, char **argv, char **envp)
{
    qemu_init(argc, argv, envp);
    qemu_main_loop();
    qemu_cleanup();

    return 0;
}

qemu_main_loop()が呼び出され、終了条件が成立するまで一定時間待ちながら条件をチェックしているように見える。

void qemu_main_loop(void)
{
...
    while (!main_loop_should_exit()) {
...
        main_loop_wait(false);
...
    }
}

main_loop_wait()では明らかに待ち状態に入る操作が入っている。この辺がポイントかな?

void main_loop_wait(int nonblocking)
{
...
    
    timeout_ns = qemu_soonest_timeout(timeout_ns,
                                      timerlistgroup_deadline_ns(
                                          &main_loop_tlg));

    ret = os_host_main_loop_wait(timeout_ns);
    mlpoll.state = ret < 0 ? MAIN_LOOP_POLL_ERR : MAIN_LOOP_POLL_OK;
...