 |
Technote 1067
Traditional Device Drivers: Sync or Swim
or, "I've Got the vSyncWait Blues"
目次
デバイスドライバを管理する 2 つのルール
事例による証明
デッドロックの回避
ルールを証明する例外
要約 |
一般的なソフトウェアシステムと同様、誰もがルールにしたがっている限り、Mac OS は正常に動作します。一般的な Toolbox オペレーションの場合は、これらのルールはかなり容易に理解することができます。しかし、伝統的な Mac OS デバイスドライバ (DRVR) は、その本来の性質からして、複雑なプログラムであり、それらの動作を管理するルールはなかなか理解できません。
この TECHNOTE では、伝統的な Mac OS デバイスドライバを書くときの危険の一つ、つまり、あるデバイスドライバから別のデバイスドライバを同期的に呼び出すときに発生するデッドロックの可能性について説明します。こうしたデッドロックが発生するいくつかのケースを具体的に説明し、さらにデッドロックの発生を回避する方法についても解説します。
この TECHNOTE では、読者として、伝統的な Mac OS デバイスドライバ、特に、他のデバイスドライバを呼び出すドライバを書いているデベロッパを想定しています。また、伝統的な Mac OS デバイスドライバの内部構造に関心を持っている人にも参考になると思います。 |
デバイスドライバを管理する 2 つのルール
伝統的な Macintosh デバイスドライバを書くときには、2 つの重要なルールにしたがう必要があります。
ルール #1
デバイスドライバを非同期的に呼び出すことができ、かつ別のデバイスドライバを呼び出す場合、そのドライバも非同期的に呼び出す必要があります。
ルール #1 にしたがわないと、システムをデッドロックさせてしまう危険があり、ユーザにとって破局的な結果が生じる可能性があります。
注意:
| Andrew S. Tanenbaum の『Modern Operating Systems』によると、「プロセスのセットがデッドロックに陥るのは、そのセットに含まれる各プロセスがそのセット内の別のプロセスによってのみ発生するイベントを待ち続けるときです」たとえば、読者の信頼できるディスクドライバを得るまで私がこの TECHNOTE を書き終えることができず、同時にこの TECHNOTE を読み終えるまで、読者が信頼できるディスクドライバを開発することができない場合、われわれはデッドロックに陥ることになります。人間にとって、デッドロックを回避したり、デッドロックから抜け出すことは必ずしも不可能ではありませんが、コンピュータにはそんな芸当はできません。 |
ルール #2
デバイスドライバを非同期的に呼び出すことができる場合は、あたかも非同期的に呼び出されているように、常にオペレーションを実行してください。
言い換えれば、このオペレーションが同期的であるか非同期的であるかを確認するためにテストを行わないでください。また、それぞれの場合で異なる処理を実行しないでください。
ルール #2 にしたがわないと、システムは期待に反して、割り込み時に不正な動作を実行する可能性があります。たとえば、ルール #1 に対する違反のような動作です。
事例による証明
ここでは、2 つの事例を通して、ルールに違反すると、どのようにしてシステムがデッドロックに陥るかを説明します。これら
2 つの事例は、次のようなシナリオに基づいています。
"旧" AppleTalk デバイスドライバを使って、ネットワーク経由でディスクブロックをフェッチするブロックデバイスドライバを書いたとします。Finder を使って、ファイルをディスクイメージにコピーするとき、システムが vSyncWait という名前のルーチンでときどきデッドロックに陥ります。このような事態は、旧ネットワークを実行しているときよりも、Open Transport を実行しているときの方がより頻繁に発生します。
最近、このようなドライバを書いている何人かの Macintosh デベロッパからの問合せに対応した経験から、この事例を選びました。これらのデベロッパはルールに違反していましたが、すべてが正常に動作すると思い込んでいました。残念ながら、Open Transport は、AppleTalk がインプリメントされる方法を大幅に変更しました。その結果、われわれは、予期しないシステムのデッドロックという、ルール違反のペナルティを突然発見することになりました。
ファイルをコピーするとき、Finder は、ソースボリュームからデータのチャンクを読み込み、それらをデスティネーションボリュームに書き込む、連続する ioCompletion のシーケンスを作成します (詳細については、develop 13 の "Asynchronous Routines on the Macintosh" を参照してください)。Finder
は実際には、File Manager 呼び出しを実行しますが、このような場合、File Manager は効率を上げるために、これらの呼び出しを適切なブロックデバイスドライバに直接渡します。
次の 2 つの事例では、ブロックデバイスドライバが前述のルールにしたがわないことで、どのようにしてシステムがデッドロックに陥るかを示します。
ルール #1 に対する違反の事例
デバイスドライバがルール #1 に違反している、つまりデバイスドライバが別のデバイスドライバを同期的に呼び出すと仮定します。次に、この違反がどのようにシステムのデッドロックに至るかを段階的に説明します。
この場合の基本的な問題点は、伝統的な Mac OS デバイスドライバがシングルスレッドである、つまり、一度に 1 つのリクエストしか処理できないことです。第 2 のリクエストを行い、ドライバがビジーで、ドライバにリクエストを完了する機能を与えていない場合、デッドロックに陥ります。
ルール #2 に対する違反の事例
デバイスドライバがルール #2 に違反している、つまりデバイスドライバが同期的に呼び出されているか、非同期的に呼び出されているかをテストし、それぞれの場合で異なる動作を実行すると仮定します。次に、この違反がどのようにシステムのデッドロックに至るかを段階的に説明します。
この場合の基本的な問題点は、ParamBlock の ioTrap ワードがリクエストが同期的に行われたかどうかを示すだけで、リクエストが非割り込み時に実行されているかどうかを示さないことです。
デッドロックの回避
前述の問題を回避する唯一の方法は、デバイスドライバの内部で非同期的に実行されるデバイスドライバ呼び出しを発行することです。
別のドライバ (DRVR B) を呼び出す伝統的な Mac OS デバイスドライバ (DRVR A) が、次のようなオペレーションを実行するようにします。
サブリクエストは ioCompletion ルーチンを持っているとします。その ioCompletion ルーチンのコードは次のように動作する必要があります。
このような方法でデバイスドライバを構造化すると、デバイスドライバをステータスマシンに変えることができます。たとえば、受け取ったそれぞれのリクエストに対して2 つのサブリクエストを発行する必要がある場合、デバイスドライバは、最終的にリクエストB1、およびリクエストは B2 という 2 つのステータスを処理します。完了ルーチンは次のように動作することになります。
この構造は、ドライバ (DRVR A) が同期的に呼び出されるか非同期的に呼び出されるかに関係なく、また呼び出しているドライバ(DRVR B) が同期的であるか非同期的であるかに関係なく、正常に動作します。
図 1、2、3、および 4 は、これら 4 つのケースを図示しています。
|
図 1 ドライバが同期的に呼び出され、同期ドライバを呼び出すケース
図 2 ドライバが非同期的に呼び出され、同期ドライバを呼び出すケース
図 3 ドライバが同期的に呼び出され、非同期ドライバを呼び出すケース
図 4 ドライバが非同期的に呼び出され、非同期ドライバを呼び出すケース
|
 |
同期的に呼び出されているのか、非同期的に呼び出されているのかを検出し、それぞれのケースでオペレーションを変えると、事態を単純化できるとも考えられます。たしかにこの方法は理論的にはエレガントなアイデアに聞こえるかもしれませんが、次の2 つの理由から実際には最悪のアイデアです。
ルールを証明する例外
"ルールを証明する例外" という表現は、実際には例外がルールをテストするという意味です。ルール#1 と #2 には、2 つの重要な例外があり、このトピックを完璧に理解するためには、これらの例外を詳しく調べてみる必要があります。
非同期的に呼び出されないドライバ: 例外 #1
多くの人は、モデルに合致しないことのために伝統的な Mac OS デバイスドライバを使用しています。明らかな例をあげるとすれば、それはデスクアクセサリです。しかし、古いプログラムでは、しばしばデバイスドライバを共有ライブラリメカニズムとして使用しています。この方法で使用されるドライバは、実際にはI/O システムの一部ではなく、一般に非同期的に呼び出されません。ルールの中に、"非同期的に呼び出される場合"という一節があるのはこのためです。
共有ライブラリメカニズムとしてデバイスドライバを使用することはできるかぎり避けてください。現在のMacintosh には必要に応じた、いくつかの現実の共有ライブラリメカニズムが用意されています。
注意:
DRVR を疑似共有ライブラリとして使用する場合は、クライアントにドライバの呼び出しを即座に発行させてください (たとえば、PBControlImmed)。これらの呼び出しは直接ドライバに送信され、いかなる場合もキューに格納されることはありません。 |
クラシック SCSI Manager: 例外 #2
ルール #1 に矛盾するように見えるケースの一つとして、File Manager と "クラシック" SCSI デバイスドライバとの関係があります。"クラシック" という名称は、SCSI Manager 4.3 以前に書かれた SCSI デバイスドライバを指しています。SCSI Manager が非同期オペレーションをサポートしていなかったため、これらのドライバは必然的に同期ドライバでした。
そうすると、SCSI Manager が必然的に同期的であっても、割り込み時に File Manager を呼び出し、File Manager がさらにデバイスドライバを呼び出し、デバイスドライバが SCSI Manager を呼び出すことはそもそも可能なのでしょうか。
この問いに対する答は簡単です。File Manager には、これをサポートするための特殊なコードが含まれています。非同期リクエストを発行するとき、File Manager は、SCSI ハードウェアがすでに使用されているかどうかをチェックします。すでに使用されている場合、File Manager は、SCSI ハードウェアが再度解放されるまで、コマンドのオペレーションを遅延させます。これにより潜在的なデッドロックを回避することはできますが、ルール #1 の精神には矛盾することになります。
リクエストは非同期であるため、File Manager だけがリクエストを遅延させることができます。File Manager を同期的に呼び出すと、デッドロックに陥ることになります。
要約
大部分の Macintosh プログラマは、ルールにしたがってプログラミングを行おうとしています。しかし残念なことに、伝統的な Mac OS デバイスドライバの場合、そのルールはほとんどマニュアル化されておらず、ときによっては理解するのも困難な状況です。この TECHNOTE では、伝統的な Mac OS デバイスドライバにとって重要な 2 つのルールについて説明しました。これらのルールにしたがうことで、システムを全体としてより信頼性の高いものにすることができます。
参考文献
- develop 13, Asynchronous Routines on the Macintosh, by Jim Luther
- Modern Operating Systems, by Andrew S. Tanenbaum, Prentice-Hall, 1992, ISBN 0-13-588187-0
|
|
|