sysfs再び

sysfsは、この連載コラムで最初に取り上げた、カーネル2.6で採用されているカーネルとドライバやデバイスとの連係を取り扱う新しいファイルシステム型インタフェイスである。すでにカーネル2.6を使っている人はご存知の通り、必ずしも使い易く洗練された構造になっているとは言えず、見方によっては矛盾や一貫性の欠如といった不具合を抱えているとも言える。

最近では、このsysfsの実装方法自体や、sysfs内の特定のサブシステム構造に関する議論が、LKMLで増えて来ている。

Don’t use a klist for drivers’ set-of-devices

8月10日に、 以前からsysfsに関する意見をポストしていたAlan Sternが、[PATCH] Don’t use a klist for drivers’ set-of-devicesと題した次のメールとPatchをLKMLに投稿した。

Greg(GregKHudevを始めとする2.6カーネルのsysfs関連の実装を担当)とPat(Patric Mochel、sysfsを実現するkobjectとsysfsの基本的な部分の開発担当者でsysfsを使用したパワーマネジメントの実装も担当)へ、

このパッチ(as536)は、ドライバに関わるデバイスセットを格納するために使用するklistを、mutexによって保護された規則的なリストで置き換えることにより、ドライバモデルのコアを単純化する。klistでは、非常に多くの競合が発生する可能性があることが判明している。それは同時に1つ以上のスレッドがリストで起こすもので、本来は安全に利用されるべきものである。また、1つのスレッドだけがリストを使用するときには、klistにすることで生じるあらゆる余分なオーバヘッドを加えるべきではない。

このPatchは、Patが以前に投げかけた懸念に関するものである。リストとmutexにはより簡潔な名前を与え、device_attachの中の不明瞭な特別な場合のコードは、FIXMEコメントで置き換えた。もし関数がリストの真中からスタートする機能を用意しない場合、list_for_each_entry_safeとlist_for_each_entryを使用するために容易にdriver.cの中の新しい繰り返し操作手順を変更することができることに注意して欲しい。呼び出し元がその機能を使用しなければ、削除すべきである。

私は、まだdevice_bind_driverとdevice_release_driverのAPIを変更する必要があると思っている。それらで保護するべきロックが無い時に、dev->drvが変わらないということに依存しているからである。 後のパッチの中でこれを修正しなければならないだろう。

Greg KHが答えた。「しかしながら、その競合の回避と単純化こそが、最初にklistを導入した理由の全てだった。」

Christoph Hellwigが付け加えた。「そしてまたもや今回、klistによるアプローチが、全体としては誤った方向に導かれたということが指摘されたわけだね。」

Alanは次のように言った。

私はPatには、Christophのコメントに答えて欲しい。このPatchで特に注意して欲しいのは、バスのデバイスのリストとバスの登録したドライバのリストは、まだklistsであるということである。デバイスに関連付けられたドライバのリストだけを、普通のリストに振り向けるようにしているのである。

一方で、このPatchの内容を見たDmitry Torokhovが提起した。

うーむ、そうすると次のようなシナリオではどうしたらばいいのだろうか。私は、serio port (AUX)を持っていて、それには、psmouseドライバによってドライブされるsynapticsタッチパッドが繋がっている。psmouseドライバはプローブ呼び出しの間は、(synapticsパススルーの)child portを登録する。child port自身もpsmouseモジュールでドライブされるのだが、それは接続するときにデッドロックするような気がする。

私はここで何か忘れているのだろうか?

これに対して、Alanが答えた。

あまり言いたくは無いが、あなたの言う事は正しい。この問題に関連する解決法を、あなたが提案することができないだろうか?事態を整理してみると恐らく、プローブ中にではなく実際の__device_bind_driver呼出しにだけ、devlist_mutexを保持することが必要だろう…しかし、非常に多くのトリッキーなインタラクションと、競合の可能性があるので、これには多くの考えが要求される。私はまた後で答えることによう。

翌日、Alanが考えて答えた。

Dmitryの指摘は的を得ている。Greg、私はklistパッチ(as536)を撤回したい。改訂したバージョンを後で送ることにする。

最良のアプローチは、単にドライバに関連するデバイスのリストを、すべて除去することではないかと思われる。klistもなく、普通のリストもなく、何も無いので、Christophを幸せにすることができるだろう。ドライバがアンロードされる場合は普通、リストは使用されるべきではない。バスのデバイス・リストに対する繰り返し操作を行い、ドライバに関係ないものを全てスキップすることにより、既に同じ結果を得ることができている。

これは(私が望むように)、競合の全体集合を除去し、コードをよりクリアにするだろう。

さらに数日して、Alanがポストした。

Dmitry, Pat, Gregとそのほかのみんなへ、

これは先週送ったパッチの改訂版である。まだ提案段階ではないが、RFCというよりは上位のものである。例えば新しいデバイスを登録する場合はいつでもこれは起こりうるのだが、接続するデバイスがバスのすべてのklistにまだ無いような状況があるので、ドライバに接続するデバイスリストの削除が役に立たないことが判明している。

この新バージョンではとにかく、ロッキングをものすごく単純化する。また要求は、ソースコード中のコメントで、さらに明示している。ドライバのプローブルーチンが同じドライバを使用するchildデバイスを登録する時、あるいは削除ルーチンが同じドライバからのchildデバイスを削除する時、このコードではデッドロックにならないようにしている。私は、このバージョンでOKだと思うが、全体を見て、何かが不適切なことが無いかを見て欲しい。

次の手順で、device_bind_driverとdevice_release_driverでの、本質的な競合は無くなるはずである。これには2つの明らかなアプローチがある

dev->driverに格納された値を信頼するのではなく、引数としてドライバに渡すこと。

dev->semをロックすることを呼び出し元に要求すること。

競合という面においては、どちらも特に魅力的ではない。しかし、これらのルーチン(例えばdrivers/input/serio/serio.cやdrivers/pnp/card.c)を呼び出す、様々な部分のコードを読むことで、そのパターンを明らかにできる。ほとんどの場合、device_bind_driverの呼び出し元は*本当に*、例えばdriver_probe_deviceのようだが、ただしマッチングの手順なしで機能するような何かが欲しいのである。呼び出し元はそうするために、ドライバコア依存することではなく、手動でプローブ・メソッドを起動している。プローブではなく、呼び出し元がドライバへのデバイス接続を実際に望む場所が、恐らく1箇所だけあるはずである。

これはdriver_probe_deviceを、マッチング用とプローブ・接続用の2つに分割する方が、よい場合があるということを示している。device_bind_driverの代わりには、第2の部分を使用すればよい。

別のことも明らかになった。現在のところ、これらのルーチンを使用する場所はすべて、バスサブシステムの同期用rwsemに依存している。それらがrwsemをロックするのは、単に古いAPIが要求しているのか、あるいはそれらが実際に何かの相互排除機構を必要とするからなのか、私はそれらの異なるすべてのドライバのことは良く知らない。これはまだ、dev->semのロックを呼び出し元に要求するということが、最も良い場合があるということを示している。つまりバスのrwsemの置き換えとして、dev->semを使用するという考えである。

fix klist semantics for lists which have elements removedon traversal

一方Alanとか別に、SCSI, MCA, EISA等のバス関係の開発を行っているJames Bottomleyが、次のようなメールをPatchとともにLKMLにポストした。彼は様々なイベントで講義を持っていて、2005年のLinuxシンポジウムでは、Block Devices and Transport Classes: Where are we going?というセッションを担当した。また先日も彼は、klist_add(head, node)の書式が後からできたので、元からある、list_add(node, head)に従うべきだとして、Pacthをポストしていた。

問題点は、klistsが修正されているリストを安全に渡るための書式を用意すると主張していることである。失敗している一般的な場合は、リストを渡るときに要素の削除を引き起こしている。この課題は次のようになる。リストのノードは参照数をカウントされるが、もしオブジェクトにそれを埋め込んでいる場合(これは通常あることだが)、klistの参照カウントが壊れるにもかかわらず、そのまま開放されてしまうということである。

その解決策は、klistに埋め込みオブジェクトの参照数を獲得、または開放させることである。すなわちころれは、リストへの参照が無くなるまで、埋め込みオブジェクトを開放しないことを意味する。

Andrew(2.6系カーネルメンテナのAndrew Morton)へ、

我々は、-mmシリーズPatchと、もしできれば2.6.13で、これを入れてみることができないだろうか。7月に報告されたように、我々の transportクラスベースのfibreドライバの両方のモジュール削除には、この問題で紹介したバグがある。

残念ながらGregは今週休みなので、すぐにこれには返答できない。

Elimination of klists

これをみつけた、AlanはElimination of klists(klistの消去)というメールをポストした。

Jamesへ、

どのようにあなたがklistsを使用しているのかはまだ見ていないが、あなたがklistのコードについて、いくつかのPatchをポストしていることに、最近気づいた。

klistsを完全に取り去り、その代りに、mutexかrwsemのどちらかで保護された一般的なリストを使用することについては、どう思うのだろうか。それは多くのオーバヘッドを削減するだろうし、またドライバコアを変更するのも難しくないだろうと思っている。これはあなたがやっていることについて、合致しないだろうか?

Jamesが答えた。

私のは主に、実際に埋め込むオブジェクトに参照カウントのモデルを持ち込むことだった。基本部分を何か変更するといいうのではなく、実装上での間違った考え方(THINKO)を単に修正しただけである。

実際のところ、klistのコンセプトは非常に良い。また、ロッキングがすべてそれらの内部であるということは、美しい。従ってユーザは、実際のところはそれを間違うことはあり得ない。私は、このようなインタフェースが好きである。

この後半部分についてAndrewが意見を言った。「割込みからそれを使う場合には、ちょっと違っている。」

Alanが言った。

私は、それが実際に間違った考え方(THNKO)かどうか分からない。Pat Mochelはその問題を良く知っていて、それについては何もしないと決定していたからだ。

やってみたい人が誰か試みれば、内部のロッキングを普通のリストに内部にすることは可能である。

大きな違いは次のようである。ドライバモデルでは元々、サブシステム中のすべてをカバーする_single_rwsemを使用していた。私が指摘しているのは各リストで使用している、多くのrwsemsやmutexのことである。ドライバコアでは、既に各デバイスにセマフォを加えている。それははるかにきめが細かいので、デッドロックにはならない。

Andrewの割込みに関する指摘について言うと、確かにオリジナルの形式のklistsは、スピンロックだけを含んでいたので、割込みコンテキストの中で使用できる。しかし、最早それは正しいとは言えない。私が知っている限り、誰も割込みコンテキストの中で使用したいと思ってはいない。

JamesもAndrewに答えた。

参照カウントについては、その通りである。かなり多くの参照カウント構造体もまた、最終的に割込みから置かれる場合には安全でない。私は、リストヘッドの供給側にロックの選択を任せたいという、暗黙の点については理解しているのだが…
どのくらいしっかりとklistの繰り返し操作を、ロックと参照カウントに結び付けるべきかという事に対して、実装するエレガントな方法をまったく知らない。

我々はいつでも、list_head_lock() list_head_unlock()とは別の組の関数を加えることができるし、それらを提供することはlist_head提供側の責任である…
私は、get/put関数に代表される、邪悪なOOコンセプトを使ったことについて、非難されなかったことには驚いているのだが…
それを2回目にやったときには、誰かが気づくだろうと確信している。

しかしklistに関するこれら問題については、この後は今のところ、進展が無い様子である。