« Yash 2 その 296: 文字列ではないバイト列 | トップページ

2024年11月 2日 (土)

Yash 2 その 297: POSIX.1-2024 のジョブ制禦

これまで POSIX ではジョブ制禦のやり方は大雑把にしか規定されてゐなかったが、2024 年版でかなり細かく仕様が指定された。XCU に 2.11 Job Control といふ新しい節が追加されてゐる。ただし、規定の内容は既存のシェルの動作とは異なる部分もある。

Yash 2.57 との違ひを中心に規定内容を見てゆかう。

フォアグラウンドのジョブが停止した時は対話シェルはすぐにその状況を (jobs のやうに) 報告することになった。既存の多くのシェルは (yash も) 次にコマンドプロンプトを表示する直前にしかこれを行ってゐない。まあすぐに表示された方が (連続して複数のコマンドを実行しようとしてゐるときには) 便利かもしれないが、強制でなくてもよいのではといふ気もする。

連続する複数の同期的 and-or リスト (とそれに続く非同期的 and-or リスト一つ) はまとめて一つのフォアグラウンドジョブとして扱はれることになった。例へば sleep 1; sleep 2; sleep 3& といふコマンドを一行に入力するとこれがまとめて一つのフォアグラウンドジョブといふ扱ひに (少なくとも形式的には) なる。ただしこれは三つの sleep を同じプロセスグループで実行しろといふことではない。POSIX のジョブの概念はプロセスグループとは一致しない。とはいへ、三つのコマンドは同じジョブに属するので、一つ目の sleep が停止されたからといってすぐに二つ目の sleep を開始してはならない。これは yash を含む既存の多くのシェルがやってしまってゐることなのでいろいろなシェルに変更要求が突き付けられてゐることになる。なほ、フォアグラウンドジョブが停止されたときは、その停止されたパイプラインの後続のコマンドは破棄してもよいとされたので、停止された一つ目の sleep を再開してそれが終はった後に二つ目以降の sleep が実行されるかどうかは実装依存である。シェルにおける現実的な実装方法としては、フォアグラウンドのパイプラインが停止されたら break や return などと同じやうな仕組みを使って現在実行中のコマンドを中断し、次の非同期 and-or リストの直後から処理を再開するといふのが最も採用されやすいだらう。Yash-rs はプログラム全体を非同期処理として書いてゐるのでジョブの管理方法を工夫すれば停止されたパイプラインの後続のコマンドを破棄せずに再開できるやうにすることも可能かもしれない。

シェルが終了を待ってゐるフォアグラウンドのプロセスが一つでも停止したらその時点でジョブは停止したバックグラウンドジョブになると規定された。これは ksh がやってゐる少数派の動作で、yash を含む多数派は全ての子プロセスが停止するまで待つ。もっとも、シェルがどのプロセスの終了を待つかは実装者に任された自由度が広いので、パイプラインの最後の部分に相当するプロセスしか気にしないことにしてしまへば他のプロセスが停止した時にバックグラウンドジョブ化しなくても違反にはならない。

フォアグラウンドジョブが停止した時に端末の状態を保存しジョブを再開するときに端末の状態も復元することが求められるやうになった。Yash でもこれまで似たやうなことをやってゐるが、POSIX.1-2024 に書かれてゐるやり方とは多少異なる。これは端末の状態を変更するプログラムが中断された後、シェルが行編集を行ったり別のプログラムを起動したりして端末の状態が変はった後でも元のプログラムを再開した時に正常に動作できるやうにするための処置だが、どのみち全ての場合でうまくゆく完璧なやり方は存在しないので、POSIX で規定するほどのものなのかといふとやや疑問ではある。Less や vim みたいなプログラムは自分が SIGTSTP を受信したら (そのシグナルのデフォルトの挙動によってそのまま停止するのではなく) 端末を自分で元の状態に復元してから自分に SIGTSTP を送り返して停止する。従って、さういふお行儀のよいプログラムの相手をしてゐる限りはシェルは端末の状態について面倒を見てやる必要はない。(rlwrap みたいに行儀のよくないプログラムも存在するので、対処するに越したことはないのだが。) 問題はさういふプログラムがまだ活動してゐる間にシェルや他のプログラムを動かしてしまふことで端末の状態が混乱に陥ることだ。シェルがプログラムの停止を確実に待ってから次の動作に移るならば問題は起きないが、上記の「一部のプロセスが停止しただけでジョブ全体が停止したと見做す」仕様や「シェルの子プロセスは停止したが孫プロセスはまだ停止してゐない」競合状態などが原因で複数のプログラムが同時に端末の状態を操作しようとする状況が発生し、不整合が生じる。シェルとしては基本的にはベストエフォートで対応するしかないが、POSIX.1-2024 のやり方が十分なのかは一見して判らない。

POSIX.1-2024 では制禦端末がない状態でジョブ制禦を行ふことも想定した文体になってゐる。Yash(-rs) ではこれまでそのやうな状況を想定してゐない。現実的に需要があるのかどうかは不明だが処理を見直した方がよさうだ。特に、制禦端末がない状態で普通に /dev/tty を開くと制禦端末になってしまふので注意を要する。制禦端末がない状態では、端末のフォアグラウンドプロセスグループを切り替へることはできないが、プロセスグループを生成したり何らかの方法でそれらのプロセスを停止させることはできる。

ジョブ制禦を行ふシェル自身がバックグラウンドジョブとして起動された場合、そのシェルは自分以外のフォアグラウンドジョブや自分を起動した親のシェルが行ってゐるジョブ制禦に勝手に割り込むわけにはいかないので、自分がフォアグラウンドになるまで自分自身を停止させる。Yash (と zsh) ではこれを行ふのに SIGTTOU を使ってゐたが、POSIX では SIGTTIN を使ふやうに規定された。これまでの yash ではバックグラウンドプロセスグループから tcsetpgrp を呼んだときに SIGTTOU が発生するのを利用してゐたが、POSIX はそれをやってほしくないやうなので、やり方を変へる必要がある。

ジョブ制禦を行ふシェルが起動されたとき、シェルのプロセスグループ ID がプロセス ID と異なってゐれば、それらが一致するやうに自分自身のプロセスグループ ID を変更することが求められるやうになった。これは yash 以外のシェルが行ってゐる動作であり、大昔は yash も行ってゐた。これをやることのメリットは、シェルにコマンドを入力してゐる途中で susp (Ctrl-Z) を入力してもシェルにしか SIGTSTP が飛ばなくなるので susp の効果を確実に無効化することができることである。シェルのプロセスグループに他のプロセスがゐると、それが SIGTSTP によって停止させられることにより、(シェルは SIGTSTP を無視してゐるから停止しないにもかかはらず) 親のシェルがそれを停止したジョブだと見做してしまふ。一方で、シェルが head | sh -i みたいなコマンドラインで起動された場合に、sh だけが head とは別のプロセスグループに移ってしまふと、head がバックグラウンドから標準入力を読まうとすることで SIGTTIN を受けて停止してしまふ。端末を非カノニカルモードで使ふ行編集では susp による SIGTSTP の発生を抑制することができるので、後者の問題を緩和するためにシェルはプロセスグループ ID を変更しない方が増しだといふのが自分の考へであるが、POSIX は多数派に迎合する道を選んだやうだ。

|

« Yash 2 その 296: 文字列ではないバイト列 | トップページ

コメント

POSIX のいふジョブの定義を取り違へてゐたので少し本文を修正した。

「Yash-rs はプログラム全体を非同期処理として書いてゐるのでジョブの管理方法を工夫すれば停止されたパイプラインの後続のコマンドを破棄せずに再開できるやうにすることも可能かもしれない」と書いたものの、複数のジョブを交互に停止させながら動作させることが安全にできるのかは定かではない。Yash-rs の場合、RefCell の borrow を跨いでジョブを切り替へたりすると実行時エラーになる。また Rust 以外の言語で書いてゐたとしても、代入やリダイレクトを伴ふ単純コマンドを停止したまま他のジョブに切り替へたらシェル実行環境はどうなるのか自明ではない。

投稿: まじかんと | 2024年11月 5日 (火) 00時37分

コメントを書く



(ウェブ上には掲載しません)




« Yash 2 その 296: 文字列ではないバイト列 | トップページ