Open4

WasmLinux: 正常系exitに対応する

okuokuokuoku

終了(exit)なんて誰でもできるじゃんという気はするが、実は結構難しい。 マルチスレッドのアプリでは自分以外のスレッドの実行も中断して終了させる必要がある ので。

とりあえず、 pthread_exit による自発的な終了は正常に動作したので、syscall中やbusy loop中に終了させると何が起こるのかを確認する。

okuokuokuoku

ERESTART_RESTARTBLOCKERESTARTSYS を返却するようになる

Linuxカーネルでは、どうもスレッドへの割り込みは特殊な errno で表現されているようだ。 とりあえずprintfデバッグ。

TLS[61]: 1 -> 38880800
Thread: 58 Call = 63 Ret = 10
res = 10 (from: 58, 38880400)
[stderr]: Sleep...2
Rearm: 4851802
TLS[58]: 1 -> 38880400
Rearm: 4503550
Thread: 61 Call = 407 Ret = 0 // ★ メインスレッドのnanosleep
USERTLS[61] = 3f260c9c
USERTLS[61] = 3f260c9c
(user) syscall = 94 // ★ メインスレッドのexit_group
TLS[61]: 1 -> 38880800
Thread: 63 Call = 407 Ret = -516 // ★ ワーカースレッドのnanosleepが-516
USERTLS[63] = 388e2f90
USERTLS[63] = 388e2f90
USERTLS[63] = 388e2f90
(user) syscall = 422
TLS[63]: 1 -> 38880c00
Thread: 63 Call = 422 Ret = -512 // ★ ワーカースレッドのfutex_time64が-512
(user) syscall = 422

(以降は -512 が返却され続ける)

syscallはエラー時に errno の負値を返却する。で、 512 よりも絶対値の大きいエラーはカーネルで予約されている:

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/include/linux/errno.h#L8-L21

直接これらのエラーが返却されてきているということは、今のところLKLはこれらのerrnoをハンドルしていないということになる。WasmLinux限定でこれらのハンドリングを実装すればOKだろう。ただし、 WebAssemblyではカーネルさえプログラムの実行フローを改変できない ため、カーネル内でこれらの処理を完結させることができない。

okuokuokuoku

ビジーループを止める方法が無い

これ詰んだのでは。。 とりあえず try_to_wake_up には至るものの、 何もしないことになる 。もっとも安直な方法はポーリング(適当な周期でスレッドに割り込んで無理矢理syscallさせる)だがもうちょっと安い方法は無いもんか。。

まぁとりあえず、ここにhookを入れてイベントをユーザーに廻すのが良いだろうか。。SIGPENDINGかつカーネル内に居ないケースではrunner側に割り込む感じで。。

(lldb) bt
* thread #28, name = 'runner', stop reason = breakpoint 2.1
  * frame #0: 0x00000000010a9663 runner`w2c_kernel_try_to_wake_up(instance=0x00007ffd78000b70, var_p0=948440064, var_p1=265, var_p2=0) at lin.c:1425515
    frame #1: 0x00000000010aa975 runner`w2c_kernel_wake_up_state(instance=0x00007ffd78000b70, var_p0=948440064, var_p1=265) at lin.c:1425924
    frame #2: 0x0000000001022188 runner`w2c_kernel_zap_other_threads(instance=0x00007ffd78000b70, var_p0=948438016) at lin.c:1372122
    frame #3: 0x0000000000f18e29 runner`w2c_kernel_do_group_exit(instance=0x00007ffd78000b70, var_p0=256) at lin.c:1269714
    frame #4: 0x0000000000f18eb6 runner`w2c_kernel_0x5F_se_sys_exit_group(instance=0x00007ffd78000b70, var_p0=1) at lin.c:1269735
    frame #5: 0x0000000001350b0c runner`w2c_kernel_lkl_syscall(instance=0x00007ffd78000b70, var_p0=94, var_p1=1, var_p2=1042546656) at lin.c:1692129
    frame #6: 0x0000000001373ae0 runner`w2c_kernel_syscall_0(instance=0x00007ffd78000b70, var_p0=94, var_p1=1, var_p2=1042546656) at lin.c:1704282
    frame #7: 0x0000000000403f37 runner`w2c_kernel_syscall(instance=0x00007ffd78000b70, var_p0=94, var_p1=1, var_p2=1042546656) at lin.c:191124:10
    frame #8: 0x00000000026a4da2 runner`runsyscall32(no=94, nargs=1, in=1042546656) at runner.cpp:365:27
    frame #9: 0x00000000026a4f80 runner`w2c_env_wasmlinux_syscall32(env=0x0000000000000000, argc=1, no=94, args=1042546656) at runner.cpp:444:24
    frame #10: 0x0000000002684cff runner`w2c_user_0x5FExit(instance=0x00000000029af260, var_p0=1) at user.c:2864:12
    frame #11: 0x0000000002684fe2 runner`w2c_user_exit(instance=0x00000000029af260, var_p0=1) at user.c:2926:3
    frame #12: 0x0000000002684bdf runner`w2c_user_libc_start_main_stage2(instance=0x00000000029af260, var_p0=2, var_p1=1, var_p2=1059577860) at user.c:2833:3
    frame #13: 0x0000000002684a70 runner`w2c_user_0x5F_libc_start_main(instance=0x00000000029af260, var_p0=2, var_p1=1, var_p2=1059577860, var_p3=1, var_p4=0, var_p5=0) at user.c:2813:12
    frame #14: 0x0000000002681a47 runner`w2c_user_0x5Fstart_c_0(instance=0x00000000029af260, var_p0=1059577856) at user.c:1726:12
    frame #15: 0x000000000267f626 runner`w2c_user_0x5Fstart_c(instance=0x00000000029af260, var_p0=1059577856) at user.c:1317:3
    frame #16: 0x00000000026a533f runner`thr_user(procctx=948438016) at runner.cpp:549:29
    frame #17: 0x00000000026a9b7f runner`void std::__invoke_impl<void, void (*)(unsigned int), unsigned int>((null)=__invoke_other @ 0x00007ffd94ff8b10, __f=0x0000000002a0bcd0, (null)=0x0000000002a0bcc8) at invoke.h:61:36
    frame #18: 0x00000000026a98d3 runner`std::__invoke_result<void (*)(unsigned int), unsigned int>::type std::__invoke<void (*)(unsigned int), unsigned int>(__fn=0x0000000002a0bcd0, (null)=0x0000000002a0bcc8) at invoke.h:96:40
    frame #19: 0x00000000026a95c7 runner`void std::thread::_Invoker<std::tuple<void (*)(unsigned int), unsigned int>>::_M_invoke<0ul, 1ul>(this=0x0000000002a0bcc8, (null)=_Index_tuple<0, 1> @ 0x00007ffd94ff8b70) at std_thread.h:292:26
    frame #20: 0x00000000026a9438 runner`std::thread::_Invoker<std::tuple<void (*)(unsigned int), unsigned int>>::operator()(this=0x0000000002a0bcc8) at std_thread.h:299:20
    frame #21: 0x00000000026a93ae runner`std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)(unsigned int), unsigned int>>>::_M_run(this=0x0000000002a0bcc0) at std_thread.h:244:20
    frame #22: 0x00007ffff7ce31b3 libstdc++.so.6`execute_native_thread_routine + 19
    frame #23: 0x00007ffff7aae947 libc.so.6`start_thread + 759
    frame #24: 0x00007ffff7b34860 libc.so.6`__clone3 + 48
okuokuokuoku

とりあえず get_signal

Linuxカーネル側の関数 get_signal を呼ぶと、スレッドにdeliverされたシグナルをクリアできる。KILL性のシグナルだった場合はその場でexitするので、とりあえず表題の exit には対応できる(ビジーループ中を除く)。

https://github.com/okuoku/lkl-wasm/commit/4373761d8d146b60bdb000af197a45b6f2ed048e

https://github.com/okuoku/lkl-wasm/commit/f5740e5abcf67496418a85d00bfb0b0a931a3143

(lldb) bt
* thread #30, name = 'runner', stop reason = breakpoint 2.1
  * frame #0: 0x00000000026a68c4 runner`mod_threads(in=0x00007ffe38e81c18, out=0x00007ffdf7a22000) at runner.cpp:835:23
    frame #1: 0x00000000026a779d runner`w2c_env_nccc_call64(env=0x0000000000000000, inptr=1095105552, outptr=0) at runner.cpp:1215:24
    frame #2: 0x0000000001375d50 runner`w2c_kernel_host_thread_exit(instance=0x00007ffd74000b70) at lin.c:1705245
    frame #3: 0x000000000120051c runner`w2c_kernel_0x5F_switch_to(instance=0x00007ffd74000b70, var_p0=948440064, var_p1=2182832) at lin.c:1560857
    frame #4: 0x00000000010aec3e runner`w2c_kernel_0x5F_schedule(instance=0x00007ffd74000b70, var_p0=0) at lin.c:1427313
    frame #5: 0x00000000010ad492 runner`w2c_kernel_do_task_dead(instance=0x00007ffd74000b70) at lin.c:1426859
    frame #6: 0x0000000000f17dfc runner`w2c_kernel_do_exit(instance=0x00007ffd74000b70, var_p0=1095106184) at lin.c:1269332
    frame #7: 0x0000000000f18c8d runner`w2c_kernel_do_group_exit(instance=0x00007ffd74000b70, var_p0=9) at lin.c:1269682
    frame #8: 0x0000000001029b0c runner`w2c_kernel_get_signal(instance=0x00007ffd74000b70, var_p0=1095106216) at lin.c:1374915
    frame #9: 0x0000000001372be3 runner`w2c_kernel_wasmlinux_get_signal(instance=0x00007ffd74000b70) at lin.c:1703906
    frame #10: 0x0000000001373b35 runner`w2c_kernel_taskmgmt_0(instance=0x00007ffd74000b70, var_p0=5, var_p1=0, var_p2=0, var_p3=0) at lin.c:1704296
    frame #11: 0x0000000000403f0b runner`w2c_kernel_taskmgmt(instance=0x00007ffd74000b70, var_p0=5, var_p1=0, var_p2=0, var_p3=0) at lin.c:191120:10
    frame #12: 0x00000000026a57fa runner`runsyscall32(no=407, nargs=6, in=1042546288) at runner.cpp:369:32
    frame #13: 0x00000000026a599d runner`w2c_env_wasmlinux_syscall32(env=0x0000000000000000, argc=6, no=407, args=1042546288) at runner.cpp:448:24
    frame #14: 0x00000000026a2f79 runner`w2c_user_sccp(instance=0x00000000029b0260, var_p0=407, var_p1=0, var_p2=0, var_p3=1042546320, var_p4=1042546336, var_p5=0, var_p6=0) at user.c:13463:12
    frame #15: 0x00000000026a3019 runner`w2c_user_0x5F_syscall_cp(instance=0x00000000029b0260, var_p0=407, var_p1=0, var_p2=0, var_p3=1042546320, var_p4=1042546336, var_p5=0, var_p6=0) at user.c:13484:12
    frame #16: 0x00000000026a318d runner`w2c_user_0x5F_clock_nanosleep(instance=0x00000000029b0260, var_p0=0, var_p1=0, var_p2=1042546336, var_p3=1042546336) at user.c:13521:12
    frame #17: 0x00000000026a321c runner`w2c_user_0x5F_nanosleep_time64(instance=0x00000000029b0260, var_p0=1042546336, var_p1=1042546336) at user.c:13542:12
    frame #18: 0x00000000026a32ff runner`w2c_user_sleep(instance=0x00000000029b0260, var_p0=2) at user.c:13568:12
    frame #19: 0x0000000002682aac runner`w2c_user_thr_test(instance=0x00000000029b0260, var_p0=0) at user.c:2097:14
    frame #20: 0x00000000026a22b8 runner`w2c_user_start(instance=0x00007ffd74000bc0, var_p0=948842352) at user.c:13132:12
    frame #21: 0x00000000026a6060 runner`thr_uthr(args=0x0000000000000000) at runner.cpp:650:14
    frame #22: 0x00000000026aa514 runner`void std::__invoke_impl<void, void (*)(userthr_args*), userthr_args*>((null)=__invoke_other @ 0x00007ffd7fffeb10, __f=0x00007ffd78000d40, (null)=0x00007ffd78000d38) at invoke.h:61:36
    frame #23: 0x00000000026aa261 runner`std::__invoke_result<void (*)(userthr_args*), userthr_args*>::type std::__invoke<void (*)(userthr_args*), userthr_args*>(__fn=0x00007ffd78000d40, (null)=0x00007ffd78000d38) at invoke.h:96:40
    frame #24: 0x00000000026a9f9b runner`void std::thread::_Invoker<std::tuple<void (*)(userthr_args*), userthr_args*>>::_M_invoke<0ul, 1ul>(this=0x00007ffd78000d38, (null)=_Index_tuple<0, 1> @ 0x00007ffd7fffeb70) at std_thread.h:292:26
    frame #25: 0x00000000026a9e3a runner`std::thread::_Invoker<std::tuple<void (*)(userthr_args*), userthr_args*>>::operator()(this=0x00007ffd78000d38) at std_thread.h:299:20
    frame #26: 0x00000000026a9dac runner`std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)(userthr_args*), userthr_args*>>>::_M_run(this=0x00007ffd78000d30) at std_thread.h:244:20
    frame #27: 0x00007ffff7ce31b3 libstdc++.so.6`execute_native_thread_routine + 19
    frame #28: 0x00007ffff7aae947 libc.so.6`start_thread + 759
    frame #29: 0x00007ffff7b34860 libc.so.6`__clone3 + 48

シグナルハンドラを起動する必要がある場合は、 get_signal の結果からsiginfoを拾ってシグナルハンドラに処理を移し、完了後にsyscallをリスタートすることになる。