「自分が Linux Kernel Driver を書いてきた経験を整理したい」という目的でこのページを書く予定です。既に世の中には数多くの Linux Kernel Driver を書くための参考書があります。このページがこれらの参考書より優れた記述になる自信はありません。Linux Kernel Driver を書こうとするとき、様々な疑問や気になることをフラフラしながら書いていく予定です。
Driver を書く上で知っていれば良い範囲で Linux Kernel を読んでいきます。多くの Kernel 解説書で触れているような 起動処理、排他制御、リソース管理(メモリ、ディスク、他)、プロセス・スレッド管理/スケジューリング、ファイルシステム等は軽く触れるだけにします。
全体を通して ソースコードを追跡できる環境 を使って Linux Kernel を読み進め、理解を深めるやり方で進めます。ソースコードを追跡する環境は web page として用意します。説明の各所でソースコード中のシンボルとそれに関連するソースコートを読めるリンクを張ります。一々リンクを辿るのは面倒かもしれません。それでも、ソースコードを読んでいくことを強く希望します。自分は Linux Kernel はソース自体が仕様書だと思っています。1 行、1 行、あるいはもっと細かく 1 演算子に相当する処理であっても、なぜその順で処理していくのか。いくつかの計算方法から、なぜその方法を選んだのか意図が込められている箇所があります。人間の言葉では書ききれない仕様です。
文字の字体や記号、段落の背景色を使い文書の意図や背景を表現する箇所があります。記述が進むにつれ、表現は追加され、継続して読むのに問題が起きない範囲で若干の修正を加えるかもしれません。
字体と記号を使って、単語や文字列の由来や表現する内容を次のようにします。
表現内容 | 表現例 | 説明 |
---|---|---|
キーボード操作 | [Ctrl] + [Alt] + [Del] | キーボードを押す操作を表現します。 |
GUI コントロール | xterm | GUI の ウインドウ・タイトル、テキスト・ボックス、チェック・ボックスの様なコントロールを表現します。 |
可変要素 | major_number | 環境や状況によって変化する内容を表現します。 |
コンソール入力 | gcc -Wall -O -o main main.c | コンソールの入力を表現します。 |
コンソール出力 | Hello world. | コンソールの出力を表現します。 |
コンソール入力の可変要素 | down_loaded_directory | コンソールの入力の可変要素を表現します。 |
コンソール出力の可変要素 | 4.1.27 | コンソール出力の可変要素のうち入力や次の作業に再利用する箇所を示します。 |
コンソール出力注目箇所 | See here | コンソール出力の可変要素のうち注目が必要な部分を表現します。 |
コード断片 | if (likely(condition)) | 文章中のコード断片を表現します。関数名のようなシンボルはフォントを変えず、そのまま表記します。 |
本文の流れに付け加えるような参考になる情報、注意を要する事、深刻な問題を起こすような事項は囲みを使って表現します。表現内容が短い場合は、背景色だけ違う段落を用いて表現します。
ノート
付け加えて参考になる情報を緑色の囲みで書きます。応用できる事項、関連する事項、詳細や背景を説明します。
注意
注意が必要な情報を黄色の囲みで書きます。予測に反して起きる事柄、応用や修正が思ったほどには上手く行かない場合、深くソースコードの読解や挙動の把握が必要な場合を説明します。
警告
ほぼ禁止事項に関する内容を赤色の囲みで書きます。復旧困難な問題、追跡・解析が難しい問題、動作に不具合を起こす問題など、深刻な問題について説明します。
ちょっとした考え事
自分が考えている事について書きます。解決したところで大きく何かが変わるわけでもない疑問、Linux Kernel 内のしきたりから外れていること、広く一般的に同意が得られないことについて書きます。
手短に言えば Linux あるいは POSIX 相当環境で、C 言語によるアプリケーション開発経験があると期待しています。最低限 2,000 行程度のアプリケーションを 4, 5 本は書いたという程度です。man page section 2 にある system call を使った経験があるのが望ましいです。たとえば open() (kernel 内 SYSCALL_DEFINE3 open), read() (kernel 内 SYSCALL_DEFINE3 read), write() (kernel 内 SYSCALL_DEFINE3 write), lseek() (kernel 内 SYSCALL_DEFINE3 lseek), ioctl() (kernel 内 SYSCALL_DEFINE3 ioctl), close() (kernel 内 SYSCALL_DEFINE1 close)など低水準な入出力関数です。
恐らく仕事でコーディングルールが決まっている。あるいは教育機関で「良い」とされるコーディングルールを習ったと思います。Linux Kernel にもコーディング・スタイル /Documentation/CodingStyle があります。おおよそあり得るルールだと思います。
では Linux Kernel のコーディング・スタイルに従う必要が有るのか?
正直に言えば、CodingStyle に書かれている一部のルールはバグを誘引しやすいルールを含んでいると考えています。特に if () 周りの中括弧の使い方は追跡困難なバグを作りやすいと思っています。 いい方向で、なるべく違和感が無い程度のルール変更はありだと思っています。
自分が書いた Linux Kernel に組み込むデバイス・ドライバは main line にマージされるのか?なさそうであれば、気にせず独自のコーディング・スタイルでと思っています。デバイス・ドライバはファイル・システム、ブロック IO エレベータ、メモリ管理、スケジューラー、ネットワーク・フィルター、セキュリティ等とは違いテストが可能な環境は限られています。Kernel 内 API の大胆な変更に伴いデバイス・ドライバも修正を受けますが、それが他者によってテストされることは期待できないでしょう。
CodingStyle ファイルに書かれていないルールはどうなっているのでしょうか? いくつかはアプリケーションプログラマにとって衝撃的な現状でしょう。いくつかの例を見ていきます。
ノート
current マクロ のもう一つの特徴はプロセッサごとに実装が違うことです。ソースコードを追跡している途中で、このように多くの実装箇所を見つけた場合は、ターゲットに使っているプロセッサ、プラットホーム、.config ファイルに書かれた条件と一致するコードを追いかけます。
仮想環境を使って Linux Kernel に組み込むドライバの基本的な事柄を探っていきます。ある程度なれたら、実働ターゲットを使い実践的なドライバを作っていく予定です。
仮想環境を使い、Linux Kernel の基礎を理解していく予定です。driver を書き始める前に理解し、慣れることが多くあります。あまりにも多いので飽きてしまったり、実務のために必要な時間が少なくなってしまうかもしれません。
beagle bone black と実際のデバイスを使用して driver を開発していく予定です。
Linux Kernel 対する理解を深めるにはソースを読むことです。このページで説明を加えようとしているのは自己矛盾かもしれません。ソースコードは膨大な量です。何処を読めばよいのか、grep でシンボルを探しても見つからない。といった理解を困難にする問題もできる限り解決できるようにしていきます。
デバイス・ドライバを作る上で、読む・修正する箇所はおおよそ次の場所です。関数の機能を探るためもっと他の場所も読むこともあるでしょう。
場所 | 主な内容 | 説明 |
/Documentation | 技術資料 | 00-INDEX を索引ページとして Linux Kernel に関するドキュメントが格納されています。一通り読むのは大変です。grep で関数をはじめとするシンボルを検索して見つかったファイルを読んでみると、理解が進み用法が解ってくることがあります。 |
/include | ヘッダ・ファイル | 主に kernel source code で使用するヘッダファイルが格納されています。 |
/drivers | デバイス・ドライバ | 殆どのデバイス・ドライバがここに格納されています。おそらく、新しいデバイスために 1 からドライバを書くのではなく、似たようなデバイス・ドライバを手本に、デバイス固有部分を書き直す場合が多いでしょう。 |
/sound | サウンド・デバイス・ドライバ | サウンド・デバイス・ドライバとその抽象化レイヤ (ALSA API) はここに格納されています。おそらく多くのプラットホームでサウンド・デバイス・ドライバは完成された状態で SoC メーカーから提供されているはずです。変更するとしたら DAC, ADC, Volime, Mixer を好みの音がするチップに変える場合でしょう。 |
/arch 以下 processor/mach-your_platform | plat-your_platform | 初期化コード・コード、プラットホーム固有コード、ヘッダファイル | プラットホーム(PC で言えばマザーボードに乗っている CPU と チップセットの組)に固有のコードです。/arch ディレクトリ以下は各プロセッサ、各プラットホーム(System On Chip device, 評価用ボード) によって構成はまちまちです。PC では /arch/x86 の下にまとめられていて、デバイスを初期化するコードの一部は kernel の下に入っています。ARM の様に多様なプラットホームが存在するとサブ・ディレクトリ mach-your_platform か plat-your_platform が作られ、この中に各プラットホームに固有なコードやヘッダファイルが格納されます。 デバイス・ドライバ開発でこの付近のコードを修正する機会は、Kernel 起動時にハードウエアの初期化が必要な場合、共用のグローバル変数領域が必要で初期化をする場合、SoC 内に組み込まれた IP ブロックをデバイスとして組み込む platform_device_register、I2C device を登録 i2c_register_board_info するなどの処理です。 Linux Kernel 起動中に LED 等の表示デバイスを点灯・点滅させるなど、凝った処理を要求される場合も、修正を加える所になります。 |
*_defconfig の一覧 /arch/processor/configs/*-defconfig | make, コンパイラが参照するマクロ値 | 各プラットホームに適した make, compile 時に参照されるマクロの値定義を格納したファイルです。make platform-defconfig コマンドでこのファイルから、ソース・コード・ツリーのルートに .config ファイルを作成します。 |