私は、他の最近の質問で説明した「不快な動作」(バグと言う人もいるかもしれません) なしで、Linux に pthread キャンセルを実装することに取り組んでいます。これまでの pthread キャンセルに対する Linux/glibc のアプローチは、これをカーネル サポートを必要としないものとして扱い、syscall を行う前に非同期キャンセルを有効にし、以前のキャンセル状態を復元することによって純粋にライブラリ レベルで処理できるものとして扱ってきました。システムコールが戻った後。これには少なくとも 2 つの問題があり、そのうちの 1 つは非常に深刻です。
- キャンセルは、syscall がカーネル空間から戻った後、ユーザー空間が戻り値を保存する前に実行できます。これにより、syscall がリソースを割り当てた場合にリソース リークが発生し、キャンセル ハンドラを使用してリソースにパッチを適用する方法がありません。
- キャンセル可能な syscall でスレッドがブロックされている間にシグナルが処理される場合、シグナル ハンドラー全体が非同期キャンセルを有効にして実行されます。これは、シグナル ハンドラーが async-signal-safe であるが async-cancel-safe ではない関数を呼び出す可能性があるため、非常に危険な場合があります。
問題を解決するための最初のアイデアは、非同期キャンセルを有効にするのではなく、スレッドがキャンセル ポイントにあるというフラグを設定することでした。このフラグが設定されている場合は、キャンセル シグナル ハンドラーが保存された命令ポインターをチェックして、それが次を指しているかどうかを確認します。 syscall 命令 (アーキテクチャ固有)。その場合、これは syscall が完了していないことを示しており、シグナル ハンドラが戻ったときに再開されるため、キャンセルできます。そうでない場合は、システムコールが既に返されていると想定し、キャンセルを延期しました。ただし、競合状態もあります。スレッドがまだ syscall 命令にまったく到達していない可能性があります。その場合、syscall がブロックされ、キャンセルにまったく応答しない可能性があります。もう 1 つの小さな問題は、シグナル ハンドラから実行されたキャンセル不可能なシステムコールが誤ってキャンセル可能になったことです。
私は新しいアプローチを検討しており、それに関するフィードバックを探しています。満たさなければならない条件:
- syscall の完了前に受け取ったキャンセル要求は、syscall がかなりの時間ブロックする前に処理する必要がありますが、シグナル ハンドラによる中断により再起動が保留されている間は処理しないでください。
- syscall の完了後に受け取ったキャンセル要求は、次のキャンセル ポイントまで延期する必要があります。
私が念頭に置いているアイデアには、キャンセル可能なシステムコール ラッパー用の特別なアセンブリが必要です。基本的な考え方は次のとおりです。
- 次の syscall 命令のアドレスをスタックにプッシュします。
- スタック ポインターをスレッド ローカル ストレージに格納します。
- スレッド ローカル ストレージからキャンセル フラグをテストします。設定されている場合、ルーチンをキャンセルするためにジャンプします。
- システムコールを行います。
- スレッド ローカル ストレージに保存されているポインターをクリアします。
キャンセル操作には、次のものが含まれます。
- ターゲット スレッドのスレッド ローカル ストレージにキャンセル フラグを設定します。
- ターゲット スレッドのスレッド ローカル ストレージでポインターをテストします。null でない場合は、ターゲット スレッドにキャンセル シグナルを送信します。
キャンセル シグナル ハンドラは次のようになります。
- 保存されたスタック ポインター (シグナル コンテキスト内) が、スレッド ローカル ストレージ内の保存されたポインターと等しいことを確認します。そうでない場合は、キャンセル ポイントがシグナル ハンドラーによって中断されており、現在何もする必要がありません。
- プログラム カウンター レジスタ (シグナル コンテキストに保存されている) が、保存されているスタック ポインターに保存されているアドレス以下であることを確認します。もしそうなら、これはシステムコールがまだ完了していないことを意味し、キャンセルを実行します。
これまでに見た唯一の問題は、シグナル ハンドラーのステップ 1 にあります。動作しないと決定した場合、シグナル ハンドラーが戻った後、保留中のキャンセル要求を無視して、スレッドが syscall でブロックされたままになる可能性があります。これには、次の 2 つの解決策が考えられます。
- この場合、タイマーをインストールして特定のスレッドにシグナルを配信し、基本的には運が良くなるまでミリ秒ごとに再試行します。
- キャンセル シグナルを再度発生させますが、キャンセル シグナルのマスクを解除せずにキャンセル シグナル ハンドラーから戻ります。中断されたシグナルハンドラーが戻ると、マスクは自動的に解除され、再試行できます。ただし、これはシグナル ハンドラ内のキャンセル ポイントの動作に干渉する可能性があります。
どのアプローチが最適か、または私が見逃している他のより根本的な欠陥があるかどうかについて何か考えはありますか?