Linux ローダブル・カーネル・モジュールの徹底調査

2.6 カーネルの 1 つの見方

バージョン 1.2 のカーネルで導入された Linux® ローダブル・カーネル・モジュールは、Linux カーネルで最も重要な革新技術の 1 つで、カーネルはローダブル・カーネル・モジュールによって拡張可能かつ動的になります。この記事を読んで、このローダブル・カーネル・モジュールの背後にある概念を知り、この独立したオブジェクトを動的に Linux カーネルに組み込む方法を学んでください。

M. Tim Jones (mtj@mtjones.com), Consultant Engineer, Emulex Corp.

M. Tim JonesM. Tim Jones は組み込みソフトウェアのエンジニアであり、『Artificial Intelligence: A Systems Approach』、『GNU/Linux Application Programming』(現在、第 2 版です) や『AI Application Programming』(こちらも現在、第 2 版です)、それに『BSD Sockets Programming from a Multilanguage Perspective』などの著者でもあります。技術的な経歴は静止軌道衛星用のカーネル開発から、組み込みシステム・アーキテクチャーやネットワーク・プロトコル開発まで、広範にわたっています。また、コロラド州ロングモン所在のEmulex Corp. の顧問エンジニアでもあります。



2008年 7月 16日

Linux カーネルはモノリシック・カーネルとして知られる類のカーネルです。つまり、オペレーティング・システムの機能の大多数がカーネルと呼ばれ、特権モードで実行されます。マイクロ・カーネルはそれとは異なり、基本機能 (プロセス間通信 (IPC)、スケジューリング、基本入出力 (I/O)、メモリー管理) だけをカーネルとして実行し、その他の機能は特権スペース (ドライバー、ネットワーク・スタック、ファイルシステム) から除外します。このことから、Linux はかなり静的なカーネルだと思うかもしれませんが、実はそれとはまるで正反対です。Linux は、LKM (Linux Kernel Module) を使用することによって実行時に動的に変更させることができます。

動的に変更可能であるということは、新しい機能をカーネルにロードすることも、カーネルから機能をアンロードすることも可能だということです。さらには他の LKM を使用する新しい LKM を追加することさえできます。LKM の利点は、必要な要素だけをロードして、カーネルのメモリー・フットプリントを最小化できることです (これは、組み込みシステムには重要な機能となります)。

動的に変更可能なモノリシック・カーネルは、Linux だけではありません (Linux が初めてというわけでもありません)。ローダブル・モジュールのサポートは、BSD (Berkeley Software Distribution) ベースのオペレーティング・システム、Sun Solaris、OpenVMS などの古いカーネル、そしてその他にも Microsoft® Windows® や Apple Mac OS X などのよく使われるオペレーティング・システムにも見られます。

カーネル・モジュールの徹底調査

LKM には、直接カーネルにコンパイルされる要素や一般的なプログラムとは基本的な違いがいくつかあります。まず、一般的なプログラムには main があるのに対し、LKM にあるのはモジュールの entry 関数と exit 関数です (バージョン 2.6 では、これらの関数にどんな名前を付けても構いません)。entry 関数は、モジュールがカーネルに挿入されると呼び出され、 exit 関数はモジュールが関数から削除されると呼び出されます。entry 関数と exit 関数はユーザー定義なので、module_init および module_exit マクロでこれらの関数がどれに該当するのかを定義します。LKM には、必須およびオプションのモジュール・マクロのセットも組み込まれています。これらのセットが、モジュールのライセンス、モジュールの作成者、モジュールの説明などを定義します。図 1 は、極めて単純な LKM の例です。

図 1. 単純な LKM のソース
単純な LKM のソース

バージョン 2.6 の Linux カーネルには、LKM をビルドするための新しい (さらに単純な) メソッドが用意されています。LKM がビルドされると、標準 insmod (LKM をインストール)、rmmod (LKM を削除)、modprobe (insmod および rmmod のラッパー)、depmod (モジュール依存関係を作成)、そして modinfo (モジュール・マクロの値を検索) などの一般的なユーザー・ツールを使ってモジュールを管理することができます (ただし、内部は変更されています)。バージョン 2.6 カーネルの LKM をビルドする方法についての詳細は、「参考文献」を参照してください。


カーネル・モジュール・オブジェクトの徹底調査

LKM は、単に特殊な ELF (Executable and Linkable Format) オブジェクト・ファイルであるというだけに過ぎません。一般に、複数のオブジェクト・ファイルがそれぞれのシンボルを解決するために相互にリンクされ、1 つ実行可能ファイルとなります。けれども LKM の場合は、LKM がカーネルにロードされるまでそのシンボルを解決できないため、LKM は ELF オブジェクトの状態を維持するというわけです。LKM では標準オブジェクト・ツールを使用できます (バージョン 2.6 での LKM のサフィックスは、kernel object を表す .ko です)。例えば、LKM で objdump ユーティリティーを使用すると、.text (命令)、.data (初期化データ)、.bss (Block Started Symbol、つまり未初期化データ) など、このモジュールにはいくつかの見慣れたセクションがあることがわかるはずです。

さらに、モジュールにはその動的性質をサポートするためのセクションも追加されます。module_init コードは .init.text セクションに含まれ、module_exit コードは .exit.text セクションに含まれます (図 2 を参照)。.modinfo セクションには、モジュールのライセンス、作成者、説明などを示すさまざまなマクロ・テキストが含まれます。

図 2. 各種の ELF セクションで構成される LKM の一例
各種の ELF セクションで構成される LKM の一例

以上の LKM の基本的な説明を基に、ここからはモジュールがどのようにカーネルに組み込まれ、内部で管理されるのかを詳しく探っていきます。


LKM のライフサイクル

モジュールのロード・プロセスをユーザー空間で開始するのは、insmod (モジュールを挿入) です。insmod コマンドはロードするモジュールを定義し、ユーザー空間のシステム・コールである init_module を呼び出してロード・プロセスを開始します。バージョン 2.6 カーネルの insmod コマンドは、カーネル内での処理量を増やすための変更に基づき、かなり単純化されています (70 行のコード)。そのため insmod コマンドは (kerneld を操作して) 必要なシンボルの解決を行うのではなく、単に init_module 関数を使って、モジュールのバイナリーをカーネルにコピーすることしかしません。残りの作業は、カーネルが引き受けます。

init_module 関数はシステム・コール層を介してカーネル内に働きかけ、sys_init_module というカーネル関数を呼び出します (図 3 を参照)。このカーネル関数がモジュールをロードする際に主要な関数となり、他のさまざまな関数を利用して難しい作業を行います。モジュールのアンロードでも同様に、rmmod コマンドが delete_module に対するシステム・コールとなり、最終的にはモジュールをカーネルから削除する sys_delete_module を呼び出します。

図 3. モジュールのロードおよびアンロードに必要な主要なコマンドと関数
モジュールのロードおよびアンロードに必要な主要なコマンドと関数

モジュールをロードおよびアンロードする間、モジュール・サブシステムは単純な状態変数のセットによってモジュールの運用状態を示します。モジュールのロード中には状態変数は MODULE_STATE_COMING になり、モジュールがロードされて使用可能になると MODULE_STATE_LIVE になります。モジュールがアンロード中であることを示す状態変数は、MODULE_STATE_GOING です。


モジュールのロード詳細

ここで、モジュールをロードする際の内部関数に目を向けてみましょう (図 4 を参照)。カーネル関数 sys_init_module が呼び出されると、まずこの関数はアクセス権をチェックし、呼び出し側にこの操作を (capable 関数を使用して) 実際に実行する許可が与えられているかどうかを確認します。続いて呼び出されるのは、機械的な作業を行う load_module 関数です。この関数がモジュールをカーネルに組み込み、必要な接続作業 (この後、説明します) を実行します。load_module 関数は、新しくロードされたモジュールを参照するモジュール参照を返します。システム内のすべてのモジュールの二重リンク・リスト上に、この新しいモジュールがロードされると、通知機能のリストを通じて、モジュールの状態変更の通知を待機しているあらゆるスレッドにロードの完了が通知されます。最後にモジュールの init() 関数が呼び出され、モジュールの状態が、ロード済みで使用可能であることを示す状態に更新されます。

図 4. モジュール・ロードの内部 (簡易化) プロセス
モジュール・ロードの内部 (簡易化) プロセス

モジュール・ロードの内部では、具体的には ELF モジュールの構文解析と操作が行われます。load_module 関数 (./linux/kernel/module.c に常駐) は、まず初めに ELF モジュール全体を保持する一時メモリーのブロックを割り当てます。次に、copy_from_user を使用して、ELF モジュールをユーザー空間から一時メモリーに読み込みます。ELF オブジェクトであるこのファイルは、構文解析するにも検証するにも簡単な、極めて特異な構造になっています。

次のステップでは、ロードされたイメージに対して一連の sanity チェック (有効な ELF ファイルかどうか、現行のアーキテクチャーに応じて定義されているかどうかなど) が実行されます。これらの sanity チェックに合格すると、ELF イメージが解析され、後でセクションにアクセスしやすくするために、セクション・ヘッダーごとにコンビニエンス変数のセットが作成されます。ELF オブジェクトは再配置されるまでオフセット 0 を基準にするため、コンビニエンス変数によって一時メモリー・ブロックに相対オフセットを組み込むわけです。コンビニエンス変数の作成プロセス中には、有効なモジュールがロードされていることを確実にするために ELF セクション・ヘッダーも検証されます。

オプションのモジュール引数はすべてユーザー空間から、カーネル・メモリーの別の割り当てブロックにロードされ (ステップ 4)、モジュールの状態変数はロード中であることを示す MODULE_STATE_COMING に更新されます。CPU ごとのデータが必要な場合には (セクション・ヘッダーのチェックにより判断)、CPU 単位のブロックが割り当てられます。

以上のステップで、モジュール・セクションがカーネルの (一時) メモリーにロードされ、どのセクションが存続し、どのセクションを削除できるかも明らかになります。そこで次のステップ (7) となるのが、モジュールの最終的な位置をメモリー内に割り当て、必要なセクション (ELF ヘッダーに SHF_ALLOC と示されているセクション、または実行中にメモリーを占有するセクション) を移動させることです。さらに、モジュールの必須セクションに必要となるサイズの割り当ても実行されます。一時 ELF ブロック内の各セクションが繰り返し処理され、実行に際して存続させる必要があるセクションが新しいブロックにコピーされます。これに続いてさらなるハウスキーピングが行われます。シンボルの解決も行われ、カーネル内に常駐するシンボル (カーネル・イメージにコンパイルされるシンボル) や一時的なシンボル (他のモジュールからエクスポートされるシンボル) が解決されます。

残りのセクションについて、それぞれに新規モジュールが繰り返し処理され、再配置が行われます。このステップはアーキテクチャーに依存するため、そのアーキテクチャーに対して定義されたヘルパー関数によって異なります (./linux/arch/<arch>/kernel/module.c を参照)。そして最後にインストラクション・キャッシュがフラッシュされて (一時 .text セクションが使用されたため)、さらに多少のハウスキーピング (一時モジュール・メモリーの解放、sysfs のセットアップ) が行われた後、最終的にモジュールが load_module に返されます。


モジュールのアンロード詳細

モジュールのアンロードは基本的にはロードを逆にしたプロセスですが、モジュールを安全に削除するために、いくつかの sanity チェックが必要となる点が異なります。モジュールのアンロードをユーザー空間で開始するのは、rmmod (モジュールの削除) コマンドの呼び出しです。rmmod コマンド内部では、delete_module に対するシステム・コールが行われ、それによってカーネル内部で sys_delete_module が呼び出されることになります (図 3 を思い出してください)。 図 5 に、モジュールを削除するプロセスでの基本動作を示します。

図 5. モジュール・アンロードの内部 (簡易化) プロセス
モジュール・アンロードの内部 (簡易化) プロセス

カーネル関数 sys_delete_module が (削除するモジュールの名前が引数として渡されて) 呼び出されると、最初のステップとして、呼び出し側にアクセス権があることが確認されます。次に、リストがチェックされて、この削除されるモジュールに依存しているモジュールの有無が確認されます。このリストは、従属モジュールごとの要素が含まれる modules_which_use_me です。このリストが空であれば、モジュールに依存関係がないことになるため、このモジュールは削除対象となります (リストが空でない場合は、エラーが返されます)。次のテストでは、モジュールがロードされているかどうかが確認されます。現在インストールされているモジュールに対し、ユーザーが rmmod を呼び出すことを妨げるものは何もありません。そのため、このチェックによってモジュールが使用可能な状態であることを確認するわけです。さらにいくつかのハウスキーピング・チェックが行われた後、最後から 2 番目のステップとして、モジュールの exit 関数 (モジュール自体のなかに提供) が呼び出され、最後に free_module 関数が呼び出されます。

free_module が呼び出される時点では、すでにモジュールを安全に削除できることはわかっています。モジュールには依存関係がないため、このモジュールに対してカーネルのクリーンアップ・プロセスを開始することができます。このプロセスではまず、インストール時にそのモジュールが配置された各種リスト (sysfs やモジュール・リストなど) からモジュールを削除します。続いて呼び出されるのは、(./linux/arch/<arch>/kernel/module.c の中に見つかる) アーキテクチャー固有のクリーンアップ・ルーチンです。これによって、従属モジュールを繰り返し処理し、これらのモジュールのリストから該当モジュールを削除します。カーネルの観点でのクリーンアップが完了すると、引数メモリー、CPU 単位のメモリー、そしてモジュールの ELF メモリーを含め、モジュールに割り当てられたさまざまなメモリーが解放されます (core および init)。


モジュールを管理する上でのカーネルの最適化

多くのアプリケーションでは、モジュールを動的にロードすることが重要であっても、いったんロードされたモジュールはアンロードする必要はないのが一般的です。そのことから、アプリケーションの起動時には (検出されたデバイスに応じてモジュールをロードするなど) カーネルが動的であっても、その実行中をとおしてカーネルが動的である必要はありません。モジュールをロードした後にアンロードする必要がない場合には、いくつかの最適化によって、モジュール管理に必要なコードを減らすことができます。例えば、カーネル構成オプション CONFIG_MODULE_UNLOAD を「設定解除」すると、モジュールのアンロードに関連するカーネルの機能を大幅に削除することができます。


さらに詳しく調べてください

この記事で説明したのは、カーネルでのモジュール管理プロセスの概要です。モジュール管理について徹底的な詳細を知るには、ソース自体が最適な資料となります。モジュール管理で必要になる主要な関数については、./linux/kernel/module.c (および、./linux/include/linux/module.h にある関連ヘッダー・ファイル) を参照してください。./linux/arch/<arch>/kernel/module.c には、アーキテクチャー特有の関数が含まれています。さらに、./linux/kernel/kmod.c では、カーネルの自動ロード関数 (必要に応じてカーネルから自動的にモジュールをロードする関数) も確認できます。この機能は、CONFIG_KMOD 構成オプションによって有効になります。

参考文献

学ぶために

  • Rusty Russell のブログ、「Bleeding Edge」で、彼が現在行っている Linux カーネル開発についての最新情報を入手してください。Rusty は、新しい Linux モジュール・アーキテクチャーの主任開発者です。
  • 多少時間は経っていますが、「Linux Kernel Module Programming Guide」は、LKM とその開発についての詳しい情報を満載しています。
  • /proc ファイルシステムを利用した Linux カーネルへのアクセス」(developerWorks、2006年3月) では、/proc ファイルシステムを使用した LKM プログラミングについて詳しく説明しています。
  • Linux システム・コールを使用したカーネル・コマンド」(developerWorks、2007年3月) を読んで、システム・コールの内部詳細について学んでください。
  • Linux カーネルの詳細を学ぶには、Tim の「Linux カーネルの徹底調査」(developerWorks、2007年6月) を読んでください。シリーズ第 1 回目のこの記事では、Linux カーネルの概要に加え、その興味深い特徴について説明しています。
  • Standards and specs: An unsung hero: the hardworking ELF」(developerWorks、2005年12月) では、ELF をわかりやすく紹介しています。Linux の標準オブジェクト・フォーマットである ELF は、実行可能イメージ、オブジェクト、共有ライブラリー、さらにはコア・ダンプにまで対応する柔軟なファイル・フォーマットです。さらに詳しく調べるには、このフォーマット・リファレンス (PDF 文書) および詳細な ELF フォーマットに関する本を参照してください。
  • Captain's Universe では、サンプル Make ファイルを使用して LKM をビルドする方法をわかりやすく紹介しています。LKM のビルド・プロセスは、バージョン 2.6 カーネルで改善されています。
  • モジュールの挿入、除去、および管理用のモジュール・ユーティリティーは数が限られています。insmod はモジュールにカーネルに挿入するためのコマンド、rmmod はカーネルからモジュールを削除するためのコマンドです。現在カーネル内にあるモジュールを照会するには lsmod コマンドを使用します。モジュールは他のモジュールの存在に依存する場合があるため、依存関係ファイルを作成するための depmod コマンドも用意されています。modprobe コマンド (insmod のラッパー) を使用すると、該当モジュールをロードする前にその従属モジュールを自動的にロードできます。また、LKM についてのモジュール情報を読み取るには、modinfo コマンドを使用します。
  • Linux Journal の記事、「Linkers and Loaders」(2002年11月) は、シンボルの解決と再配置を含め、ELF ファイルを使用するリンカーおよびローダーの目的をわかりやすく説明しています。
  • developerWorks Linux ゾーンに豊富に揃った Linux 開発者向けの資料を調べてください。記事とチュートリアルの人気ランキングも要チェックです。
  • developerWorks に掲載されているすべての Linux のヒントLinux のチュートリアルを参照してください。
  • developerWorks technical events and webcasts で最新情報を入手してください。

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=329934
ArticleTitle=Linux ローダブル・カーネル・モジュールの徹底調査
publish-date=07162008