process on erlang
vi on emacs
前回、C語上で ブラケット {} 間を飛び回る方法として、vi を模倣したviper-mode を試してみた。こういう要求は絶対に有るはずなんで、きっとemacsだけで実現出来るのだろう。オイラーの検索力のせいで、直ぐに見つけられなかったのよ。
この際、emacs上でvi機能を実現するっていう変態指向にすこし首を突っ込んでみる。 いえね、オイラーはviもemacsも普通に使えるけど、やっぱりlispと親和性が高いemacs派でしたよ。出張先にemacsなんて入ったマシンが置いてある保障なんて無い。
だから、ポータブルemacsしてたのさ。昔の事だからフロッピー1枚に収まるようにカスタマイズしたemacsを自作し、出張の折にはそれを持参してたんだ。こう書くとソフト屋さんと思われるかも知れないけど、職業はハード屋さん、対象の機械はunixで動いていたから、ハードの診断ソフトを動かすにもunixの知識が必要。だって、オシロスコープで波形を観測しやすいように、ソフトを修正するなんで、日常茶飯事でしたからね。
所が、ある時emacsの入ったフロッピーを忘れたまま出張に出てしまった。標準のviしかない。編集もおぼつかない。右往左往しましたね。で、結論は、いきがってemacsなんて使うな。どこにもあるviを使いこなせるようになっておけ。emacs断ちを数年続けたら、身についた。
リタイアしてからは、向かう先は自分のパソコンだけ。だったらemacsの封印を解いてもいいんじゃない。
こんな楽しいアーティクルを見つけた。設計思想が色々で面白い。
vimに追いつけが多分に開発動機になってるみたいだけど、オイラーは、(n)viは好きですけど、vimは大嫌い。基本ツールは、どんな状況でもちゃんと働いてくれる事を望む。だったら、edでも使っとけ。
最近edやってないから、たまには非常訓練しとくかな。災害は忘れた頃にやってくる。
ああ、そうそう、 viper-modeで動いている時、C-z で、vi/emacsを簡単に行き来出来るそう(但しcurrent bufferのみに適用)。こんな悪魔的な機能が有るなんて、ちっとも知りませんでしたよ。
try crash for erlang
前回(こればっかりだな。まるで納豆みたいに尾を引いているぞ)、ひょんな事からerlangのクラッシュを体験した。調べておけっているオラクル(神の宣託)だな。
How to Interpret the Erlang Crash Dumps
りっぱな資料が用意されてた。これに則り、以前のデータを検視してみると、
=erl_crash_dump:0.5 Fri May 21 06:00:14 2021 Slogan: erl_child_setup closed System version: Erlang/OTP 23 [erts-11.2] [source] [smp:1:1] [ds:1:1:10] [async- threads:1] [hipe] Compiled: Fri May 21 05:24:59 2021 Taints: Atoms: 6784 Calling Thread: scheduler:1 :
スローガンって所に載ってるのが、クラッシュした原因。スローガンって標語って意味もあるけど、この場合プチ違和感が有るな。
beam.smp が立ち上がると、サブプロセスでダエモン君もどきの erl_child_setup
1024 みたいに隠れたやつが起動する。こいつを強引に kill -9 すると、上のスローガンと同じになるぞ。
それから、自前でクラッシュダンプも取れるとな。
by halt
まずは、一番簡単なやつ。
1> halt("DOWN NOW"). DOWN NOW Crash dump is being written to: erl_crash.dump...done vbox$
こんなに簡単に落とせてしまっていいものだろうか? 世の中は善人だけでなりたっているって方針なんだな。
=erl_crash_dump:0.5 Sat May 22 07:56:03 2021 Slogan: DOWN NOW System version: Erlang/OTP 21 [erts-10.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1] :
これが原因の説明。おかしな事が起こった場合。ここに説明を書いておくって使い方を想定してるんだろう。広い意味でdebugにどうぞって事か。
by SIGUSR1
次は、ちょっと悪人ぽいやつ。erlに入れないような場合を想定かな。
vbox$ ps a PID TT STAT TIME COMMAND 97305 p1 S+ 0:00.52 /usr/local/lib/erlang21/erts-10.2/bin/beam.smp -- -r : vbox$ kill -SIGUSR1 97305
外側から、信号を送って、自死してもらうんだな。
1> Received SIGUSR1 Crash dump is being written to: erl_crash.dump...done
by abort with dump
次はerl起動時に出て来る非常ボタンに触れてみる。
1> ^G User switch command --> ^C BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution A Crash dump requested by user Crash dump is being written to: erl_crash.dump...done Abort trap (core dumped)
場合によっては、大文字のA相当の表示が出ない版もあるけど、ちゃんと隠しコマンドになってる。
Slogan: Crash dump requested by user
これはもう非常事態。OSレベルでもcoreを生成してくれてる。検視せいって声があるぞ。
vbox$ gdb -q /usr/local/lib/erlang21/erts-10.2/bin/beam.smp beam.smp.core : #0 thrkill () at /tmp/-:3 3 /tmp/-: No such file or directory. [Current thread is 1 (process 275253)] (gdb) bt #0 thrkill () at /tmp/-:3 #1 0x054999b0 in _libc_raise (s=6) at /usr/src/lib/libc/gen/raise.c:37 #2 0x05441d6b in _libc_abort () at /usr/src/lib/libc/stdlib/abort.c:51 #3 0x145f7518 in erts_exit_epilogue () at beam/erl_init.c:2424 #4 0x145f761a in erts_exit_vv (n=-4, flush_async=<optimized out>, fmt=<optimized out>, args1=<optimized out>, args2=<optimized out>) at beam/erl_init.c:2410 #5 0x145f40a2 in erts_exit (n=<optimized out>, fmt=<optimized out>) at beam/erl_init.c:2434 #6 0x146b236a in do_break () at beam/break.c:535 #7 0x14709cf8 in erts_do_break_handling () at sys/unix/sys.c:773 #8 0x145a78c2 in aux_thread (unused=0x0) at beam/erl_process.c:3126 #9 0x1476d687 in thr_wrapper (vtwd=0xcf7e1f30) at pthread/ethread.c:118 #10 0x018bc25d in _rthread_start (v=0x44e5922c) at /usr/src/lib/librthread/rthread.c:96 #11 0x05469dea in __tfork_thread () at /usr/src/lib/libc/arch/i386/sys/tfork_thread.S:92
at boot
次は無理して起こすcore。beam.smpは手順を踏んで起動するプログラムなんだけど、その手順を吹っ飛ばして、いきなりの起動。
vbox$ $BINDIR/beam.smp {"init terminating in do_boot",no_or_multiple_root_variables} init terminating in do_boot (no_or_multiple_root_variables) Crash dump is being written to: erl_crash.dump...done
OSで言うなら、カーネルが起動してないのに、initを実行しちゃうみたいなものか。
Slogan: init terminating in do_boot (no_or_multiple_root_variables)
ダンプに至る経路は一緒かな。
(gdb) bt #0 thrkill () at /tmp/-:3 #1 0x054999b0 in _libc_raise (s=6) at /usr/src/lib/libc/gen/raise.c:37 #2 0x05441d6b in _libc_abort () at /usr/src/lib/libc/stdlib/abort.c:51 #3 0x145f7518 in erts_exit_epilogue () at beam/erl_init.c:2424 #4 0x145f761a in erts_exit_vv (n=-4, flush_async=<optimized out>, fmt=<optimized out>, args1=<optimized out>, args2=<optimized out>) at beam/erl_init.c:2410 #5 0x145f40a2 in erts_exit (n=<optimized out>, fmt=<optimized out>) at beam/erl_init.c:2434 #6 0x146b236a in do_break () at beam/break.c:535 #7 0x14709cf8 in erts_do_break_handling () at sys/unix/sys.c:773 #8 0x145a78c2 in aux_thread (unused=0x0) at beam/erl_process.c:3126 #9 0x1476d687 in thr_wrapper (vtwd=0xcf7e1f30) at pthread/ethread.c:118 #10 0x018bc25d in _rthread_start (v=0x44e5922c) at /usr/src/lib/librthread/rthread.c:96 #11 0x05469dea in __tfork_thread () at /usr/src/lib/libc/arch/i386/sys/tfork_thread.S:92
uftrace for erlang (2)
これまた前回の続きで、uftraceで特定の機能を実行した時のみ記録するに挑戦。まずはその前段階として、erlのプロンプトまで届くか確認。
debian:bin$ LD_PRELOAD=`pwd`/libstart_stop.so uftrace record --disable ./erlexec mcount: /home/sakae/src/uftrace/libmcount/misc.c:103:uftrace_send_message ERROR: writing shmem name to pipe: Bad file descriptor erl_child_setup closed Crash dump is being written to: erl_crash.dump...done
こりゃ駄目だわさ。門前払いを喰らったぞ。
escript
だったら、shellを表に出さないスクリプトはどうだ?
debian:bin$ cat z.erl -module(z). -compile(main/1). main([Arg]) -> io:format("Hello, ~s!~n", [Arg]).
debian:bin$ ./escript z.erl Erlang Hello, Erlang!
コンパイルしてからの実行になるんで、とろいけどね。ちょっと心配。
debian:bin$ uftrace record ./escript z.erl Erlang mcount: /home/sakae/src/uftrace/libmcount/misc.c:103:uftrace_send_message ERROR: writing shmem name to pipe: Bad file descriptor erl_child_setup closed Crash dump is being written to: erl_crash.dump...done
見事に心配が的中しました。マーフィーの法則、健在なり。
debian:bin$ grep exename uftrace.data/task.txt | cut -d' ' -f 5 exename="/home/sakae/MINE/lib/erlang/erts-11.2/bin/escript" exename="/bin/dash" exename="/bin/sed" exename="/home/sakae/MINE/lib/erlang/erts-11.2/bin/erlexec" exename="/home/sakae/MINE/lib/erlang/erts-11.2/bin/beam.smp" exename="/home/sakae/MINE/lib/erlang/erts-11.2/bin/erl_child_setup"
dashなんて言うshellとかsedを繰り出してソースを加工。そして普通にerlを起動してるのか。 escriptの正体を見たりはいいんだけど、ちょいとお手上げ状態だな。残念だけど、これぐらいにしておくか。
noshell and run
debian:bin$ cat z.erl -module(z). -compile(export_all). hello([Arg]) -> io:format("Hello, ~s!~n", [Arg]), erlang:halt().
最後の悪あがき。上記を事前にコンパイルしておき、それをshell無しで走らせる。
debian:bin$ erlc z.erl z.erl:2: Warning: export_all flag enabled - all functions will be exported debian:bin$ ./erlexec -noshell -run z hello ERLANG Hello, ERLANG!
やっぱり駄目だった。
debian:bin$ grep exename uftrace.data/task.txt | cut -d' ' -f 5 exename="/home/sakae/MINE/lib/erlang/erts-11.2/bin/erlexec" exename="/home/sakae/MINE/lib/erlang/erts-11.2/bin/beam.smp" exename="/home/sakae/MINE/lib/erlang/erts-11.2/bin/erl_child_setup"
erlcでコンパイルするとVM用のバイトコードが作成される。それを実行するには、重厚な環境が、どうしても必要なんだな。
まあ、erlangの舞台裏を垣間見えたんで佳しとするか。余力が有るならuftraceを追ってみろってのは無しね(だって、リナでしか通用しない、特殊な世界だから。世の中にはBSD族も居るのよね)。
アクターモデル
erlangの特徴として並行実行がある。今度はこれに手を出してみる。複数のスレッドを起動して並行実行しようとすると、競合が発生する。
一つの解決方法は、ロックを取得し、それを得たスレッドのみが実行。それ以外は待機って方法。javaで使ってるね。
競合が発生するのは、スレッドが処理の途中で割り込むからだ。割り込むのを止めさせてしまえって方針で行くのがファイバーとかコルーチンと言う方法。
ファイバーと言えば、あの言語を思い出すな。 【Ruby】Fiber(ファイバー)を理解する とか RubyのThread、Fiber、Kernel、forkで並列処理やプロセスの深淵へ だ。オイラーが活躍してたのはruby1.4の頃。実はそれ以降のファイバーとかは、全く知らない。名前だけ知ってる状態。ここで、そちら方面にスイッチすると、戻ってこれなくなるから、メインスレッドに復帰する。
そもそもメモリーを共有するから問題が発生するのよ。だから共有止めるってのが、アクターモデル。プロセスは独立法人で共有が無い。プロセス間は、パイプ通信でしょ。こればアクターモデルの代表だ。でも、プロセスを作ったり消したりはOSに負荷をかける重い処理。気軽に出来ない。軽い奴って事でpthreadとかが有るけどね。これは、あからさまにメモリー共有になってるんで、振り出しに戻るって事になっちゃう。
天下のgolang様はどうしてる? 一応つまみグイ。 Go言語と並列処理 の連載に、 自立した複数のシステムで協調動作: アクターモデル (by GOLANG) こんなのも有った。高い所から俯瞰しとくのも大事だな。
そこん所を深く考察して、OSレベルでのプロセスじゃ無く、一つのプロセス(OSからみて)内で、実現したのがerlangだ。erlang内でのプロセスの作成とかは軽く出来るようになってるので、ホイホイとプロセス(erlang内のね)を作ったり消したり出来る。
通信の方法はパイプじゃなくて、メッセージ(バイナリーデータでも文字列でも何でもよい)を、送りつけるって方法になる。
構文は非常に簡単。送り先(のプロセス)を指定してメッセージを送るだけ。
1> self(). <0.81.0> 2> self() ! hoge. hoge 3> self() ! [123,456]. [123,456] 4> flush(). Shell got hoge Shell got [123,456] ok
self() は自分自身(この場合erl-shell)のプロセスidを得る関数。hogeとか配列データを自身に送信。flush()で、受け取ったメッセージを確認出来る。勿論、メッセージが送られて来るのを待ち構えていて、メッセージに反応するってのが、普通の使い方になる。
今回はshellにあからさまな受け手が居なかったので、こっそりメッセージを蓄えていた。それをflushで吐き出したって事になる。尚、メッセージを貯めておく箱を、未決済箱(部長の机にある奴です)とは言わずに、メールボックスなんて普通に呼んでいる。
メッセージは何でもいいって書いた。受け取る方は何でもに対応するため、一種のパターンマッチ構文 receive を使って場合分けする。そして、処理が終わると、そのメッセージは削除される(やっぱり、未決済箱と呼びたいぞ)。
send / receive
ob$ cat sr.erl -module(sr). -export([hoger/1, send_and_receive/2]). hoger(P) -> receive X -> P ! {hoge,X} end, hoger(P). send_and_receive( P, X ) -> P ! X, receive R -> R end.
簡単な例だけど、hogerは、何か受け取ったら、指定されたプロセスへ hogeって前置詞を付けて、受け取った何かを送り返す。 send_and_receive
の方は、指定されたプロセスへメッセージを送り、また待ちに入るってやつ。
1> c(sr). {ok,sr} 2> Q = spawn(sr, hoger, [self()]). <0.86.0> 3> sr:send_and_receive(Q, hello). {hoge,hello} 4> sr:send_and_receive(Q, {fumu, hage}). {hoge,{fumu,hage}}
spawanは指定した関数を、プロセスとして起動するやつ。起動したプロセスidが返ってくるので、Qに保存してる。後は、そのプロセスQに対して、適当なデータを送信。hogerが前置詞を付けて、オウム返ししてるのを見て取れる。
3文字の省略
erlangでは、3文字の省略系がよく出てくるので(ソースを見た時にね)まとめておく。 ヘイ、シリ… とか言って聴くのが恥ずかしいので、erlang what stands for XXX ってggしたよ。
NIF Native Implemented Functions OTP Open Telecom Platform BIF Built In Functions MFA Module, Function, Argument BEAM Bogdan/Björn's Erlang Abstract Machine EPMD Erlang Port Mapper Daemon
MFAはマニュアルにも出て来るな。上のswawn(M,F,A)に限らず、頻出してるぞ。
pthreadもどき
erlangでは、ホイホイとプロセスを作って、その間をメッセージで通信しあうってのが普通のスタイル。 プロセスの作成は、spawn関数で指定したMFAで起動される。この起動された関数がずっと居座れば、サーバーとして使える。関数だから状態を持たないってのが味噌になる。サーバーが応答しなくなったら、手軽に別な関数をspawnするだけだ。
そんじょそこらに有る、オブジェクト指向言語(Pyton,ruby,…)とは、違うんです。状態が無いですからねぇ。あなたとは違うんです どういう訳か、こんな発言を思い出したぞ。
この状態を持たないって性質を利用すると、気軽に稼働中のシステムにパッチを当てられる。そう、問題のあるモジュールを新しい物に入れ替えちゃうって技。絶対に止められない、電話交換システムを作るにあたっての重要事項だった。現場から生まれた言語なんだな。
で、ネットを見てたら、 Erlangによるスケーラブルなマルチコア対応分散プログラミング なんて言う研究が行われていた。こういう事も手軽に出来るんだなあ。以前にやったpthreadみたいなのをerlangでやったらどうなる? 素晴らしい例が有ったので引用させて頂く。
-module(test). -export([calc_fibs/1, fib_send/2]). % like main calc_fibs( Lst ) -> calc_fibs_list( Lst ), receive_results( length(Lst) ). % target calc fib(0) -> 1; fib(1) -> 1; fib(N) -> fib(N-1) + fib(N-2). fib_send(P,N) -> P ! {N,fib(N)}. % like pthread_create calc_fibs_list([]) -> ok; calc_fibs_list([N|Lst]) -> spawn( test, fib_send, [self(),N] ), calc_fibs_list(Lst). % like pthread_join receive_results( 0 ) -> complete; receive_results( N ) -> receive {X, Fx} -> io:format("fib(~w) = ~w~n", [X, Fx] ), receive_results( N-1 ) after 1000 -> timeout end.
毎度おなじみの引数によっては膨大な計算時間がかかるfibの計算です。複数の計算を並行に行う、pthread風なやつです。以前にやったpthreadに強引にこじつけてみました。
1> c(test). {ok,test} 2> test:fib_send( self(), 5). {5,8} 3> test:fib_send( self(), 20). {20,10946} 4> test:calc_fibs( [20, 15, 10]). fib(10) = 89 fib(20) = 10946 fib(15) = 987 complete 5> test:calc_fibs( [20, 15, 40, 10]). fib(15) = 987 fib(10) = 89 fib(20) = 10946 timeout
fib_send(P, N)
で、fib(N)の結果を引数と共にeshellに送ってみた。次は、複数の引数を計算させて、ただ表示。after 1000 となってるのは、1000ms以内に計算が終了しないと、joinを諦める仕様にしたからです。
6> c:flush(). Shell got {40,165580141} ok
でも、解き放たれた計算はそのまま進行してて、結果をeshellが受け取ってくれている。
今、ふと思ったんだけど、spawnって関数、shellから使っていると、バックグランドでのコマンド実行って見做せるね。
sakae@pen:~$ ls sim & [1] 1744 sakae@pen:~$ i386 RASP xv6-public [1]+ Done ls --color=auto sim
コマンドlsに引数simを付けて、& で、spawnするって事。pidの1744が返ってきた。ね、そっくりでしょ。起動したコマンドが終了したからいいようなものだけど、ずっと居座って、よそのプロセスと通信を始めちゃったら、これはもう、サーバーですよ。bashとかだと、裏に回ったコマンドをfgで表に引き出せる。表に引き出せるって事は、再び標準入出力とのやり取りを可能にするって事だ。
本当のサーバーだと、裏に回すと同時に、手足をもぎ取ってしまったり、ワーキング・ディレクトリを/に変更したりするんだけどね。