fishを捌く(後編)_backgroundの処理の実装
はじめに
これは、東京大学電気電子・電気情報工学科の3年次の実験、「大規模ソフトウェアを手探る」実験においてfish_shellのソースコードを手探り、機能の追加を行ったものです。
今回は、並列for文の機能を追加しました。
(前編)で、ソースコードをダウンロードし、ビルドをおこない、デバッグをして、手探り、並列for文(pfor)を(for文と全く同じ挙動ですが)シェルの関数として呼び出すことができるようになりました。前編は以下から
やったこと
並列に処理させるためには、for文での実行の際、一つの処理が終わるまで、残りの(繰り返しを含む)すべての処理をOSに待ってもらっているので、そこで待たないようにする必要がありました。以下のような処理の時に、backgroundで行うと、並列に実行されることから、バックグラウンド処理を使うことで、待たせないことができることがわかりました。
・backgroundで実行されるsleep
よって、for文の処理をbackgroundにするということを目指してやっていきます。
fish-shellを手探る
基本的には、&というbackgroundjobを指示するコマンドを書いた時をデバッグしていきました。(具体的にはsleep infinity &)
fishがjobをbackgroundで行われるものだと決めている場所
parse_execution.cppの中に、
job->set_flag(JOB_FOREGROUND, !tree.job_should_be_backgrounded(job_node));
という文があり、実行しようとしているjobののノードのflagという変数を書き換えている。
job_should_be_backgroundという関数の中身は以下のようになっている。
get_child(job,2,symbol_optional_background)は引数のノード(parse_node_t &job)から見て、順番として二番目の子ノードでかつ、そのノードのtypeという変数がsymbol_optional_backgroundであるもののノードのアドレスをとってきます。
イメージ↓
opt_background(上の図で2の番号のついたノード)のtagがparse_backgroundであれば、
trueを返すようになっている。その後set_flagでflagもbackgroundのものに帰られていたので、tagをparse_backgroundに書き換えればいいのではないかとわかりました。
type = symbol_jobのノードの子ノードの2番目がsymbol_optional_backgroundであることもわかります。
構文木についてもう少し詳しく書いた部分があるので、読みたい人はこちらをクリックしてください。(このページの下部にあります。)
pfor_statementに渡されているnodeからsymbol_optional_backgroundのnodeにたどり着く
前編で出てきた(p)for_statementの引数は以下のようにsymbol_job_listです。
type=symbol_job_listのノードからtype=symbol_jobのノードを取ってくる関数が、parse_tree.cppの中にあったのでこれを用いることにする。
参考用のコード↓
これを用いて、type = symbol_jobのノードを取り出し、job_should_be_background
関数のようにget_childでtype=symbol_optional_backgroundのノードのアドレスを手に入れて、そのtagをparse_backgroundに変えればよいと方針がたった。
実装と結果
以上のことを踏まえて、実装をしたのが↓のコードである。
const_cast<parse_node_t*>はノードがread_onlyだったので、constを外してtagをかきかえるために使いました。
実際に動かしてみると次のようになり、jobが並列に動いていることがわかりました。
しかし、この実装では、pforのブロックの中身も並列に実行されてしまっているようで、本質的な実装ができていないと思われます。
これを解決するためには、blockごとに中のjobidに対して、同じブロックの前の処理が終わるまでは処理をしないように待たせる必要があります。
pipeなどの機能を用いてデバッグをすれば解決の手がかりを得られると思うので、今後の課題としたいと思います。
補足:構文木の構造を探る
構文木の構造が把握できれば、何をしているのかわかるだろうということでやってみました。ここについては、自分でもっと手探りたい人の助けになるようにと、書いたものです。興味のない人は飛ばしてもらって構わないかと思います。
ここで説明するのはfor文の構造です。
1.木の全体像とパターン
はじめにまとめた図を載せます。
ひとつひとつの四角がノードです。ノードの左肩の数字はノードに付与された番号です。ノードの中の文字は、先ほど出てきたノードのもつ変数typeです。(前編)でも出てきたsymbol_for_headerなどもここにあります。大きな流れとしては、次の図のようになっています。
symbol_job_listから少しずつjobが取り出されていく感じですね。また、
symbol_jobの子ノードの二番目は必ずsymbol_optional_backgroundであることもわかりました。
2.ノードの中身
例として上の全体図の番号3のノードを見て見ます。
{source_start = 0, source_length = 32, parent = 1, child_start = 6,
child_count = 1 '\001', type = symbol_statement,
keyword = parse_keyword_none, flags = 0 '\000', tag = 0 '\000'}
source_start・・・ソースコードのどこから始まっているか。0は1文字目のこと。
source_length・・・該当する部分の長さ
parent・・・親ノードのノード番号
child_start・・・子ノードの最も若いノード番号
child_count・・・子ノードの個数
type・・・その部分の型のようなもの、symbol_xxx、parse_xxxと書かれている(parse_xxxは葉に存在する。)
keyword・・・これは、構文解析時にtagを変更したりするのに用いる。
falgs,tag・・・tagによってflagが書き換えられる仕組み
以上で木の説明は終わります。