WasmLinux: 目指せユーザーランド
ついに(とりあえずの)ゴールが見えてきた。。
ここまでで、 read
write
pipe
の3つのsyscallが正常に動作することを確認できた。LKLの仕組み上、普通にsyscallすると全てカーネルコンテキスト(要するにinit)で実行されてしまい、ユーザランドのエミュレーションする目的としては厳しいもんがある。
というわけで、何とかカーネル内で clone
を実行してユーザーランドのpidを追加したい。
作戦を考える
これマジで難しいゾ...
kernel_clone
直接法
fork.c
の user_mode_thread
を使うのが一番簡単だろうか。。つまり、 /init
の実行時にも使われている、
のように kernel_clone
を直接呼ぶ方法が良い気がしている。この場合は、作成されたカーネルスレッドから(新規追加する) lkl_ops経由で上に載るユーザーコードを実行させることになる。
vfork
法
他の手法としては、nommu Linuxの伝統通り、vforkしてしまうというのも考えられる。この場合は、 clone3
に CLONE_VFORK|CLONE_VM
を渡すことになる。
clone後はそのままユーザー側に戻ってくることになるので、適当なbinfmtを書いて execve
をhookすることになる。 ...手間としては kernel_clone
直接法とあんまり変わらんな。。
ハイブリッドカーネル法 ★
そもそも、カーネルから見た個々のプロセスでアドレス空間とかを共有するのは変わんないんだから、主従を逆転させてsyscallの発行タイミングでプロセスコンテキストを紐付けてしまう方が簡単なのではないか説。
syscallの実行コンテキストであるhost taskを作成する際に CLONE_THREAD
を常に指定している。これ抜きのhost taskを作成する方法を考えれば良いんではないか。
ハイブリッドカーネル法で往く
つまり、ユーザープロセスのforkとかスレッドの生成はLinux側の clone
を使わずにエミュレーションする。これはvforkとかexecveも同様にエミュレーションしないといけないということになる。
Linuxカーネル側には wasmlinux_*
として、それ用のhookを追加した。
でWasm側の呼出しコードも同様に実装:
これで、晴れて pid = 27 が割り当てられた。
write res = 4
res = 4
[stdout]: out
TLS[58]: 1 -> 383fa400
Oneshot timer: 8 2455730
Rearm: 2455730
Wait: 2455730
TLS[62]: 1 -> 383fa800
Run: 383e1410 0x7ffd74000bc0
Throw: 4144fc8c 0x7ffd74000bc0
(user) pid = 27
[stdout]: out
は、init(pid = 1)で生成したfdを読み出すスレッドが出力していて、これはプロセス間で fd
を渡せていることを意味する。