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

キャラクタ型デバイスの登録(alloc_chrdev_region と cdev)

自作のキャラクタ型ドライバをカーネルに認識させるには、まず alloc_chrdev_region で空きメジャー番号を動的に受け取り、cdev_init で file_operations と結びつけた cdev を作り、cdev_add でカーネルに登録する。取り外しでは cdev_del と unregister_chrdev_region で逆順に片付ける。割り当てられた番号は /proc/devices で確認でき、その番号を mknod に渡してデバイスファイルを作る。

file_operations という窓口の表は用意できた。だが、それだけではアプリの操作はまだドライバに届かない。カーネルに「このデバイスは私が担当します」と名乗り出る手続きが要る。

自作のキャラクタ型ドライバをカーネルに認識させるまでには、いくつかの決まった段取りがある。この回では、その登録の流れを順に追う。

💡
ポイント登録は「番号をもらう → 窓口の表を入れ物に結ぶ → カーネルに登録する」の3段階。取り外しは逆順にほどく。

大きく分けると、担当を示すデバイス番号を確保し、file_operations を cdev という入れ物に結びつけ、それをカーネルに登録する、という3つの段階になる。

そして、組み込みで準備したものは、取り外しのときに逆の順序できちんと片付ける。この対称性が、後始末のしっかりしたドライバを作る鍵になる。順に見ていこう。

🎫 まず番号をもらう — alloc_chrdev_region

登録の第一歩は、このドライバが担当することを示すメジャー番号を手に入れることだ。

番号を自分で決め打ちにすると、他のドライバが使っている番号とぶつかる恐れがある。そこで一般的なのが、いま空いている番号をその場でカーネルに割り当ててもらう動的割り当てで、これを行うのが alloc_chrdev_region という関数だ。

alloc_chrdev_region(&dev, baseminor, count, name) ……空きメジャー番号を動的に確保し、dev に受け取る。

この関数には、受け取った番号を格納する変数、開始するマイナー番号、いくつのマイナー番号を確保するか、そしてこのデバイスの名前を渡す。成功すると、確保されたデバイス番号(メジャーとマイナーを一つにまとめた値)が、指定した変数に入る。

こうして受け取った番号は、後で /proc/devices を見れば、どのメジャー番号がこのドライバに割り当てられたかを確認できる。

🔢 番号を組み立て・取り出すマクロ

デバイス番号は、メジャーとマイナーを一つの値に詰め込んだ形をしている。この値を扱うために、専用の短い命令(マクロ)が用意されている。

MKDEV(major, minor) は、メジャー番号とマイナー番号から一つのデバイス番号を組み立てる。逆に、一つのデバイス番号から MAJOR() でメジャー番号を、MINOR() でマイナー番号を取り出せる。

MKDEV(major, 0) で番号を組み立て、MAJOR(dev) / MINOR(dev) で取り出す。番号を直接ビット演算しなくて済む。

なぜマクロを使うのか。番号の詰め込み方(どのビットがメジャーで、どこからがマイナーか)を自分で計算すると間違えやすく、カーネルの実装が変わったときにも壊れやすいからだ。マクロに任せておけば、その内部の詰め方を気にせず、安全に番号を出し入れできる。

alloc_chrdev_region で受け取った番号から MAJOR() で自分のメジャー番号を取り出す、といった使い方をする。

🧷 窓口の表を入れ物に結ぶ — cdev_init と cdev_add

番号が手に入ったら、次は前のトピックで作った file_operations を、実際のデバイスとしてカーネルに登録する。ここで登場するのが cdev という入れ物だ。

cdev は、カーネルがキャラクタ型デバイスを管理するために使うデータ構造で、これに file_operations を結びつけることで「この番号のデバイスが操作されたら、この窓口の表を使え」とカーネルに伝えられる。

手順は2段階だ。まず cdev_init で、cdev と file_operations を紐づけて初期化する。次に cdev_add で、その cdev を、先ほど確保したデバイス番号とともにカーネルへ登録する。

cdev_init(&mycdev, &myfops) で結びつけ、cdev_add(&mycdev, dev, count) でカーネルに登録する。

この cdev_add が成功した時点で、はじめてアプリからの操作がドライバの file_operations 経由で届くようになる。ここまでが「組み込み時の準備」の中心だ。

🗑 取り外しは、逆の順で片付ける

組み込みで確保したものは、取り外しのときに必ず解放する。ここを怠ると、番号が使われっぱなしになったり、カーネル内に不整合が残ったりする。

片付けは、準備したのと逆の順序で行うのが基本だ。まず cdev_del で、登録した cdev をカーネルから取り除く。続いて unregister_chrdev_region で、alloc_chrdev_region で確保したデバイス番号を返す。

💡
ポイントcdev_add したものは cdev_del で、alloc_chrdev_region したものは unregister_chrdev_region で。確保と解放を対で持つ。

「登録したら削除する」「番号を確保したら返す」という対応を、組み込み関数と取り外し関数のあいだできちんと鏡写しにしておく。この対称性を崩さないことが、何度でも安全に着脱できるドライバの条件になる。

終了関数の中でこの後始末をやり忘れると、再び組み込もうとしたときに番号がぶつかる、といった厄介な不具合につながる。

🚪 登録できても、まだデバイスファイルは無い

cdev_add まで済ませても、それだけでは /dev にファイルが現れるわけではない、という点に注意がいる。

登録によってカーネルは「この番号のキャラクタ型デバイスがある」と認識するが、アプリがアクセスする入口である /dev のデバイスファイルは、別に用意しなければならない。

確保されたメジャー番号は /proc/devices で確認できるので、その番号を mknod に渡し、キャラクタ型(c)として、メジャー番号とマイナー番号を指定してデバイスファイルを作る。これで、ようやくアプリがそのファイルを open して操作できるようになる。

cat /proc/devices で番号を調べ、sudo mknod /dev/mydev c その番号 0 でファイルを作る。

なお、ドライバ側でさらに手続きを加えれば、組み込んだ時点で /dev にファイルを自動生成させることもできるが、まずは「登録」と「デバイスファイル作成」が別の手順である、という骨格をつかんでおくとよい。

✅ まとめ — 名乗り出て、窓口を開く

キャラクタ型デバイスの登録は、番号をもらい(alloc_chrdev_region)、窓口の表を入れ物に結び(cdev_init)、カーネルに登録する(cdev_add)、という流れだった。取り外しでは cdev_del と unregister_chrdev_region で逆順にほどく。

この一連の段取りが、file_operations という設計図を、実際に動くデバイスへと立ち上げる手続きにあたる。ここまで来れば、アプリの open・read・write が、自分の書いたハンドラに届く。あとは、そのハンドラの中でユーザ空間とどう安全にデータをやり取りするか、という次の課題に進める。

この項目に出てくる用語

alloc_chrdev_regionあろけーとしーえるでぶりーじょん
空きメジャー番号を動的に確保するカーネル関数。
cdevしーでぶ
カーネルがキャラクタ型デバイスを管理する入れ物。

関連コマンド

mknod

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