🐧 Linux 総合学習プラットフォーム
デバイスドライバ実装 ・ 上級

file_operations とハンドラ登録

アプリの open・read・write・close は、ドライバ側のどの関数に届くのか。その対応表が file_operations 構造体だ。.open や .read といったメンバに自作のハンドラ関数を割り当てておくと、アプリがデバイスファイルを操作したときにカーネルが対応する関数を呼び出す。close に対応するメンバ名が .release である点に注意。この構造体がアプリとドライバをつなぐ窓口の一覧になる。

デバイスファイルをアプリが open・read・write・close する、という話はした。だが、その操作は、ドライバの中の「どの関数」に届くのだろう。

アプリが /dev のファイルに write したとき、カーネルはドライバのどの処理を呼べばいいのかを、どこかで知っていなければならない。その対応表にあたるのが file_operations という構造体だ。

💡
ポイントfile_operations は「アプリの操作 → ドライバのどの関数」を対応づける表。ドライバの窓口一覧にあたる。

この構造体には、open のとき呼ぶ関数、read のとき呼ぶ関数、write のとき呼ぶ関数……というメンバが並んでいて、そこに自分で書いたハンドラ関数を割り当てておく。

そうしておけば、アプリがデバイスファイルを操作した瞬間、カーネルがこの表を見て、対応する自作関数を呼び出してくれる。ドライバ側から見れば「どの操作が来たら、どの処理を動かすか」を宣言する場所であり、アプリとドライバをつなぐ窓口の一覧だと言える。

🗂 メンバに関数を割り当てる、という発想

file_operations の中身は、操作の種類ごとに用意されたメンバの集まりだ。

それぞれのメンバには、あらかじめ「.open は開くときの関数」「.read は読むときの関数」といった役割が決まっている。私たちがすることは、その役割に合わせて、自分で書いたハンドラ関数の名前をメンバに書き込んでいくことだ。

構造体の中に .open = mydev_open, .read = mydev_read, .write = mydev_write, のように書く。

たとえば .open に自作の open ハンドラを、.read に read ハンドラを、.write に write ハンドラを割り当てる、という具合になる。すべてのメンバを埋める必要はなく、そのドライバが対応したい操作のぶんだけ書けばよい。書かなかった操作には、カーネルの既定の振る舞いが使われる。

この「関数の名前を、役割の決まった置き場に差し込む」という書き方は、初めて見ると独特だが、慣れると対応関係がそのまま目に見えて分かりやすい。

⚠ close なのに .release、という引っかけ

ここで、多くの人が最初につまずく落とし穴がある。

アプリ側で close したときに呼ばれるハンドラを登録するメンバの名前が、素直に .close ではなく .release になっているのだ。名前が操作名と一致していないので、初見だと探しても見つからず戸惑いやすい。

つまずきアプリの close に対応する file_operations のメンバは .close ではなく .release。名前のずれに注意。

なぜ名前がずれているのか。カーネル内部では、ファイルを扱う仕組みが複数のアプリで共有されることもあり、「本当に最後の利用者が手を離したときの後始末」という意味合いを込めて release という語が使われている、という背景がある。

細かい理由はさておき、実務上は「close の相手は .release」とだけ覚えておけば足りる。ここを .close と書いてしまうとハンドラが呼ばれず、原因の分かりにくい不具合になりやすいので、最初に一度はっきり意識しておきたい。

🔌 ハンドラの形は、操作ごとに決まっている

メンバに割り当てるハンドラ関数は、好き勝手な形では書けない。

read や write のハンドラには、カーネルから決まった引数が渡される。どのファイルへの操作かを示す情報、データを受け渡しするためのバッファのアドレス、扱うデータの長さ、そして今どの位置を読み書きしているかを示す情報、といったものだ。

とりわけ read と write では、渡されるバッファがユーザ空間のアドレスである点が重要になる。ドライバはカーネル空間で動いているため、このアドレスを自分で直接読み書きするのは危険で、専用の受け渡しの作法が必要になる。これは次のトピックで詳しく扱う。

💡
ポイントread/write ハンドラに渡るバッファはユーザ空間のもの。直接触らず、専用の作法でやり取りする必要がある。

いまの段階では、「ハンドラの引数の形はカーネルが決めていて、その形に合わせて自分の処理を書く」という受け身の関係を押さえておけば十分だ。

🧩 表を用意しただけでは、まだ動かない

file_operations という表を作り、ハンドラを割り当てた。これで完成、とはいかない。

この構造体は、あくまで「操作と関数の対応表」を用意しただけで、それをカーネルの側に「このデバイスの窓口はこの表です」と結びつける手続きが別に要る。

その結びつけは、キャラクタ型デバイスを登録するときに行う。具体的には、この file_operations を、デバイスを表す cdev という入れ物に紐づけてからカーネルに登録する、という流れになる。

🔗
たとえfile_operations は受付の対応マニュアル。作っただけでは意味がなく、受付台(cdev)に据えて初めて機能する。

つまり file_operations は単独では働かず、次のトピックで扱う登録の手続きと組み合わさって、はじめてアプリの操作がドライバに届くようになる。

✅ まとめ — 窓口の設計図

file_operations は、キャラクタ型ドライバの設計の中心にある構造体だ。

アプリの open・read・write・close が、それぞれドライバのどの関数に届くのかを、この一枚の表で決める。close の相手が .release である点、read/write のバッファがユーザ空間のものである点、そして表は登録の手続きと組み合わせて初めて働く点。この3つを押さえておけば、あとの登録やデータ受け渡しの話がすんなりつながる。

自作ドライバを読むときは、まずこの file_operations を探し、どの操作にどんな処理が割り当てられているかを見る。それがそのドライバの「できること一覧」になる。

この項目に出てくる用語

file_operationsふぁいるおぺれーしょんず
アプリの操作とドライバのハンドラ関数を対応づける構造体。
.releaseどっとりりーす
file_operations で close に対応するメンバ。名前が .close でない点に注意。

▶ 学習アプリでこの続きを学ぶ・演習する