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

デバイスファイルと /dev

Linuxでは「すべてはファイル」という思想のもと、ハードウェアもファイルとして扱います。その入口が /dev ディレクトリにある「デバイスファイル」で、ここを読み書きするとドライバを通じて機器とやり取りできます。たとえば /dev/null は捨て場、/dev/sda はディスク全体、/dev/ttyS0 はシリアルポートを表します。アプリは通常のファイル操作(open・read・write)と同じ作法でデバイスを操作でき、その先の橋渡しをドライバが担います。

ドライバはハードウェアを握る。では、ユーザ空間で動くアプリは、その機器とどうやって話すのだろう。

専用の特殊な命令を機器ごとに覚えるのだろうか。Linuxの答えはもっと潔い。「すべてはファイルである」という一貫した思想だ。テキストや画像のような普通のデータだけでなく、ハードウェアまでもファイルとして扱う、という考え方である。

💡
ポイントLinuxは「すべてはファイルである」。ハードウェアまでファイルに見立て、同じ作法で読み書きできる。

その入口になるのが、/dev というディレクトリに置かれたデバイスファイルだ。

デバイスファイルは見た目こそ普通のファイルに似ているが、中に文字や数値のデータが保存されているわけではない。ここを読み書きすると、その先に控えているデバイスドライバを通じて、実際の機器とやり取りができる。いわばハードウェアへの「窓口」になっている。

アプリ側は特別な作法を新しく覚えなくても、通常のファイルと同じ open(開く)・read(読む)・write(書く)といった操作でハードウェアを扱える、というのがこの仕組みの大きな利点だ。

アプリユーザ空間デバイスファイル/dev/...ハードへの窓口ドライバ→ 機器カーネル空間open/read/writeファイルを読み書きする作法のまま、機器を操作できる

🔎 /dev をのぞくと、何が並んでいるのか

/dev は、こうしたデバイスファイルが集まる特別なディレクトリだ。中をのぞくと、用途のはっきりした代表的なファイルがいくつも見つかる。

たとえば /dev/null は、書き込んだものをすべて吸い込んで消してしまう「捨て場」で、不要な出力をここへ流し込んで黙らせるのに使う。

/dev/sda は1台目のディスク全体を表し、/dev/ttyS0 はシリアルポートを表す。同じディスクでも、その中の特定の区画(パーティション)には /dev/sda1 のように番号の付いた別のファイルが対応する、といった具合に、機器とその一部分が細かくファイルとして並んでいる。

これらはどれも、ファイルという同じ見た目をしていながら、その背後ではまったく違うハードウェアにつながっている。

「ファイルを操作する同じ作法で、捨て場もディスクもシリアル通信も扱える」というのが、/dev というディレクトリが体現している世界観だ。

🤝 なぜわざわざ「ファイルのふり」をさせるのか

ハードウェアをファイルに見立てる最大の利点は、操作の作法を統一できることにある。

もし機器ごとにまったく別の特殊な命令を覚えなければならないとしたら、新しい機器を扱うたびに学び直しが必要になり、プログラムも機器ごとにばらばらの作りになってしまう。

ところがファイルという共通の枠に当てはめておけば、開いて・読んで・書いて・閉じる、という同じ手順でさまざまな機器を扱える。

🔗
たとえどの機器も「開く・読む・書く・閉じる」の同じ作法。窓口の形を統一した役所のように、相手が変わっても手順は同じだ。

たとえばシリアルポートにデータを送りたいなら、対応するデバイスファイルに書き込むだけで済むし、その同じ書き込みという操作が、別の機器では別の意味の制御として働く。

アプリ開発者の側から見れば、相手がディスクなのか通信ポートなのかをいちいち意識せずに済み、その違いはすべてドライバが引き受けてくれる。

さらに、ファイル操作にはパーミッション(アクセス権)という既存の仕組みがそのまま使えるので、どのユーザがどの機器を読み書きできるかも、ファイルと同じ感覚で管理できる。

この「共通の窓口」があるおかげで、Linuxは多種多様なハードウェアを見通しよく、統一的に扱えるのだ。

🔁 ファイルへの読み書きは、その先で誰が受け取るのか

では、アプリがファイルに書いた1バイトは、どこへ行き着くのか。実際の流れを追ってみよう。

アプリは、まず目的の機器に対応したデバイスファイルを open システムコールで開く。続いて read でデータを受け取ったり、write でデータを送ったりし、用が済んだら閉じる。

このとき、ファイルへの読み書きの要求は、そのままドライバの中に用意された対応する処理へと橋渡しされ、ドライバが実際のハードウェアを動かして、その結果をアプリへ返す。

ドライバ側は、open や read、write といった操作のそれぞれに応える処理をあらかじめ備えており、アプリからの要求が来るたびにカーネルがそれらを呼び出す、という形で連携する。

💡
ポイントアプリの open/read/write は、そのままドライバ内の対応処理に橋渡しされる。ドライバが機器を動かし、結果を返す。

つまりデバイスファイルは、ユーザ空間で動くアプリと、カーネル空間で動くドライバとをつなぐ接点として働いているわけだ。

アプリは「ファイルを操作しているつもり」でいて、その裏側ではドライバが特権を使って機器を制御している、という二段構えの構造になっている。この境界をまたぐデータの受け渡しには専用の作法があり、ドライバを自分で書くときに重要になってくる。

👀 自分の /dev を、その目で確かめる

理屈が分かったら、/dev の様子を実際に見てみよう。ふだんのファイル操作と同じコマンドで確かめられる。

ls /dev で一覧を見れば、null や sda、ttyS0 といったファイルがずらりと並んでいるのが分かる。

さらに ls -l を使うと、行頭の1文字でそのファイルがデバイスファイルかどうかが分かり、後の回で詳しく扱う「担当ドライバを示す番号」まで読み取れる。

デバイスファイルが正しく用意されているか、目的の機器に対応するものが存在するかを調べるときの、最初の一手になる。何か機器がうまく扱えないとき、まず /dev に対応するファイルがあるかを見る、という習慣をつけておくとよい。

♻ /dev のファイルは、いつ誰が並べているのか

ところで、これだけのファイルは誰が作ったのだろう。

かつて /dev のファイルは、考えうる機器のぶんだけ、あらかじめ手作業で大量に並べておくものだった。しかし現代のLinuxでは、udev というしくみが機器の接続や切断をその都度検知して、必要なデバイスファイルを動的に作ったり消したりする。

USBメモリを挿した瞬間に、それに対応するファイルが /dev に現れ、抜けば消える、という振る舞いはこの udev のおかげだ。

🔗
たとえ/dev は固定された名簿ではなく、機器の出入りに合わせて並びが変わる受付台。挿せば現れ、抜けば消える。

自作のドライバでも、ドライバ側で適切な手続きを踏めば、人が手作業でファイルを作らなくても、組み込んだ時点で /dev にファイルが自動的に現れるようにできる。

そしてもうひとつ、自作のドライバ開発でこうした出来事を追うときに頼りになるのが dmesg だ。機器を挿した瞬間や、ドライバを組み込んだ瞬間に、認識のログやエラーが時系列で流れるので、デバイスファイルが期待どおりに用意されたか、機器が正しく認識されたかを確かめる助けになる。

「/dev のファイルは固定された一覧ではなく、機器の状態に応じて生き物のように変化する」と捉えておくと、現代のLinuxの挙動に戸惑わずに済む。

この項目に出てくる用語

デバイスファイルでばいすふぁいる
ハードウェアをファイルとして扱うための特殊ファイル。/dev にある。
/devでぶ
デバイスファイルが集まる特別なディレクトリ。

関連コマンド

lsdmesg

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