ZMODEM送信の相互運用性およびパフォーマンス
Eröffnet am: 2019-09-07 11:09
Letztes Update: 2019-09-23 21:26
Auswertung: | chapuni | Verantwortlicher: | (Keine) |
---|---|---|---|
Priorität: | 5 - Mittel | Meilenstein: | (Keine) |
Typ: | Fehler | Schweregrad: | 5 - Mittel |
Komponente: | Tera Term | Status: | Offen |
Lösung | Keine |
Einzelheiten
対向の組み込みOSに対しZMODEM受信を試みています。 Teraterm ZMODEM 送信の思わしくない挙動・実装を見出したので報告します。 主に利用しているのは 4.103 ですが trunk でも同様です。 使用レートは 115200~921600bps, 8N1 noflow です。
ウィンドウサイズ
現実装ではウィンドウサイズを超過した送信を試みるような実装になっているようです。 対向の受信バッファサイズをTeratermウィンドウサイズ(デフォ 32767)と同じにした場合、この送信は受信側でオーバーランすることがあります。 オーバーランした場合、(ZCRCQを投げているにもかかわらず)ZACKが返ってくることはまずありません。
zmodem.c:625
else if ((zv->WinSize >= 0) && (zv->Pos - zv->LastPos > zv->WinSize))
これは送信動作後に実施されていますが、チェックは zv->WinSize を拠り所とするのではなく (zv->WinSize - zv->MaxDataLen) にした方が安全ではないでしょうか?
ZDATA 時の ZCRC* 選定
現実装では、ウィンドウサイズに収まっている場合に ZCRCG, ウィンドウサイズを超過したときにZCRCQを投げるようになっています。 対向は ZCRCG に対して ZACK を返送しないのがふつうなので、以下のようなシーケンスが発生します。
- Teraterm はウィンドウサイズ超過するまで ZCRCG を投げる
- 対向はエラー検出しない限り何も返送しない。
- Teraterm はウィンドウサイズ超過したときに ZCRCQ を投げ、ZACK を待つステート(Z_SendDataDat2)に移行。
- 対向がオーバーランせずにZCRCQまで受け切ったら、ZACKをTeratermに返送。
- Teraterm は ZACK を受領したのち、次のウィンドウにおける ZDATA を送信開始。
短くまとめると、Teraterm はスライディングウィンドウ動作ではなく、ウィンドウサイズ単位の半二重通信をしてしまっています。
ホスト側が USB Serial を利用しているような場合だと、USB起因の遅延も加味され、半二重通信のレイテンシ(ZCRCQ->ZACK->ZDATA)は顕著に増大します。
ZMODEM プロトコル仕様書を斜め読みしたうえで Teraterm 現実装の問題を以下にまとめます。
- 対向が信頼のおけるものではないのに ZCRCG を多用している。ZCRCG が許容されるのは、対向が報告した ZRINIT の受信バッファサイズ==0(non-stop)のときにとどめておくべきではありませんか?
- データ送信中における ZCRCQ の扱いが ZCRCW のように扱われている。仕様書を読んだ限り、ZCRCQ を投げたあとは直ちに次のZDATAを投げてもいいと理解できますが、ZACKを待つようになっています。
改善案を以下に挙げます。
- デフォで ZCRCG を投げず、ZCRCQ を投げるようにする。
- non-stop のときは ZCRCG を利用してもいいが、ときどき ZCRCQ を投げないと、エラー時の巻き戻しロスが多大になる。
- ウィンドウサイズを超過しそうなときのみ、(ZCRCQではなく)ZCRCW を投げる。
- ZACK を待つのは、ZCRCQ 送信後ではなく、ZCRCW 送信後とする。
- ZParseHdr() において、Z_SendData 時においても ZACK を受理し、zv->LastPos を更新するようにする。
- ただし ZACK 時には LastPos の巻き戻しが発生しないようにチェックを入れる。
- 巻き戻しは ZRPOS のみが行うようにする。
- ただし ZACK 時には LastPos の巻き戻しが発生しないようにチェックを入れる。
以上の変更を 1.103 の ttpfile/zmodem.c に施したうえで試してみましたが、結果は良好です。対向のCPU時間に余裕があるケースでは ZCRCW を投げることはなくなり、対向が間に合わない場合でも ZCRCW により再送を発生させず待つことができるようになりました。
Teraterm 自体、歴史のあるものなので、互換オプションを設けるのはいかがでしょうか。
- 旧仕様: {ZCRCG, ZCRCQ} を用い、ZCRCQ は ZACK を待つ。
- 新仕様: {ZCRCQ, ZCRCW} を用いる。
- ZCRCQ は ZACK を待たないが、呼応する ZACK に対しては LastPos を更新する。
- ZCRCW は ZACK を待つ。
以上、提案します。ご検討ください。
Kommentar
返答が遅くなり申し訳ありません。
まずはウィンドウサイズについてですが、あれからいろいろ考えてみたところ、先の提案では不十分だったと理解しました。 よって変更の提案自体は取り下げたいとおもいます。 問題点および改善策については今後考えてみたいとおもいます。
本質的には、Teraterm が数えている winsize は、ペイロードを数えていて、 受け側(対向)の許容受信サイズの指定が困難です。ヘッダ, CRC, そしてエスケープを加味しなくてはなりません。 対向が充足する受信バッファの大きさを決定するのは容易ではありません。 現に、対向の受信バッファを 32767 octets にしてみたのですが、対向の処理が間に合わないケースでは容易にオーバーランします。 オーバーランの結果、再送が生じ、パフォーマンスロスとなります。
ZCRC* の件につきましては、先の提案でおおむね快調に動いていますが、ZDATA において ZCRCW を選択するのは ベターではなく、すべて ZCRCQ を用いる方が動作に一貫性が出てきます。 半二重なシーケンスがなくなるため、転送レートもややアップします。
旧仕様を選択可能にする件につきましては、Teraterm依存のクライアントを組んでしまっている人がいるのではないかという 危惧によるものです。旧仕様を残すべきか、旧版を利用することで回避してもらうかは、私からなんとも申し上げられません。
パッチ作成についてはぜひと申し上げたいところですが、職場で試していたもので、それを持ち出すのは大げさな話に なりそうなので、自宅でビルド環境および再現環境を構築してからの話になりそうです。少々お時間をいただくことになりそうです。 もし他の方が実装して追試していただけるのであれば歓迎です。 (参考までに、私はTeraterm全体をビルドせず、CMake+cygwin-mingw で ttpfile.dll だけを作成しリリースバイナリと差し替えて実験してました)
余談になりますが、いろいろ評価していて、teratermのオーバーヘッドが無視できない程度に大きく、 実効値が理論値に届いていないのでは? という疑念を持っています。 ZMODEM送信のケースですと 921600 noflow において転送レートが 56KB/s 程度です。 気が向いたら自宅でループバック(USBシリアルどうしのクロス接続)組んで計測してみます。