再試行で次の処理に間に合わなかった
一言でいうと
転送・復旧・リトライは、1つの時間バジェットを分け合って使います。成功の応答でも、遅れて届いたなら、今回の呼び出しの成功ではありません。
なぜ必要なのか
転送に10ms、復旧に10ms、リトライにさらに10msを与えると、「10ms以内に終わる読み取り」ではなくなります。制御ループが次の仕事をしなければならないのに、センサー1つが実行の流れを引き止めてしまうことがあります。timeoutは、個別のAPIのオプションである前に、呼び出し側が作業全体に約束した期限です。このラボの時計は決定的なモデル時間であり、実際のCPUの実行遅延を測定するものではありません。
どう動くのか
開始時刻を1回読み、deadline = start + budgetを求めます。transferとrecoverには、常に同じdeadlineを渡します。最初の転送が3ms、復旧が2ms、2回目の転送が3msなら、合計は8msです。バジェットが10msなら成功できますが、7msでは2回目の転送が終わりません。リトライのときに新しいバジェットを作るのは、時間を延長したことになります。
HALが時間の約束を守るという前提もテストします。遅れた成功を返すモデルでは、呼び出しの直後に時計をもう一度読み、バジェット以上が経過していたら、SENSOR_TIMEOUTを返さなければなりません。復旧の後にも、同じ検査を行います。ちょうど期限に到着した結果も、この課題ではタイムアウトです。境界を含むかどうかを明示しないと、テストと実装が別々の約束を守ることになります。
時計はuint32_tのミリ秒で、最大値の次は0に戻ります。したがって、now >= deadlineという絶対値の比較は、ラップアラウンドの直前・直後で間違うことがあります。この課題は、バジェットを1–INT32_MAXに制限し、観察区間でラップアラウンドが最大1回という前提のもとで、unsignedの引き算によって経過時間を求めます。uint32_tのモジュロ演算を使うのであって、あらゆる時計のジャンプを解決する魔法ではありません。
最後のステップでは、引数も境界として見ます。busとout、3つのコールバックがNULLかどうか、アドレスとバジェットが許容範囲かどうかを、I/Oの前に検査します。不正なポインターをデリファレンスしてから、エラーを返すことはできません。ctxはHALが決める不透明な値なので、ドライバーが無条件にNULLを拒否してはいけません。出力ポインターの2つのフィールドは、成功した呼び出しでだけ変わります。
時計が0に戻る瞬間を自分で計算する
開始を0xfffffffe、バジェットを4msと決めると、32ビットの期限は2になります。現在時刻が1のときは3ms、2のときは4msが経過しています。後者は、今回の契約ではタイムアウトです。以下のPythonは、センサーを制御しない算術の実験です。無制限の整数であるPythonでも、32ビットの時計を再現するように、マスクを明示します。シェルからPythonに貼り付けて実行すると、2つの境界が合っているときだけ、最後の文が出力されます。
mask = (1 << 32) - 1
start, budget = 0xfffffffe, 4
deadline = (start + budget) & mask
elapsed_before = (1 - start) & mask
elapsed_at = (2 - start) & mask
assert deadline == 2
assert elapsed_before == 3 and elapsed_before < budget
assert elapsed_at == 4 and elapsed_at >= budget
print("기한 직전 성공 후보 / 기한 도착 시간 초과")
このコードブロックの最後の韓国語の文字列は、期限の直前は成功候補で、期限に到着したらタイムアウトという意味です。
成功候補と書いた理由は、時間を満たしただけでは、データまで有効だとは限らないからです。期限内に届いたSENSOR_SHORTは、やはり失敗であり、予約ビットが誤っている2バイトも、成功ではありません。成功には、転送のステータス・形式・時間の3つの条件がすべて必要です。逆に、転送がSENSOR_OKでも、完了を確認した時点が期限以上なら、公開サンプルは変えません。
開始が100ms、バジェットが7msの別の記録を比べてみましょう。最初の転送が103msにBUS、復旧が105msにOK、再転送が108msにOKを返します。3回の呼び出しに渡す期限は、すべて107msです。最後のステータスがOKでも、経過が8msなのでタイムアウトであり、最初の温度と時刻を保存します。復旧から新しいバジェットを与えたり、復旧の時間を除いたりすると、「7ms以内に終わらせる」という呼び出し側の約束が、静かに変わってしまいます。
呼び出しの後の時間の検査では、無限に止まったHAL関数を強制的に中断できません。コールバックが戻ってきて初めて、ドライバーは時計を読み直せるからです。実物で応答時間の上限を保証するには、HAL自体の制限付き待機・キャンセル・ハードウェアタイマーのような、別の設計と測定が必要です。このラボの決定的なコールバックと実行時間の制限は、そのようなリアルタイム保証の代わりにはなりません。モデル時間で契約を検証したという結果と、実際の最悪実行時間を測定したという結果を、区別してください。
現場での姿
センサーサンプルのobserved_msは、ホストが転送の完了を確認した時点です。ADCが実際に温度を変換した時刻ではないので、「今読んだ」だけで、「今測定した」とは言えません。上位のプログラムは、最後に成功した時刻と現在のエラーを一緒に見せなければ、古い値が正常な最新値のように見えてしまいます。失敗の後、次の正常な読み取りで再び更新されるかどうかも、テストする必要があります。
完成した後にも、実物の検証は残ります。プルアップ・配線・電源・クロック・センサーの変換周期・実際のHAL実装は、このモデルでは検証されません。ポートフォリオには、「ホストC + 決定的なHALモデル」と書き、検証した入力・状態・期限と、検証していない物理的な項目を分けてください。モデルを通ったことは、次の実物の試験を準備する根拠であり、現場の安全保証ではありません。
次のラボですること
ステップ7–8で、共有する期限・遅れた成功・ラップアラウンド・引数の検査を完成させます。全体の採点では、前のステップまで再確認します。正常→部分的な失敗→正常の収集で、最後の正常なサンプルが保存され、新しいサンプルに置き換わるかどうかを確認してください。セッションが終了するとファイルが消えるので、ソースと試験の記録を別に保管してください。