GNU/Linux で開発するアプリケーションには当然、具体的なカーネル関数の呼び出し (システム・コール) が組み込まれますが、それとは逆に、カーネル空間からユーザー空間を呼び出すことはあるのでしょうか?考えてみると、皆さんが日常的に使用しているさまざまなアプリケーションで、カーネル空間からユーザー空間への呼び出しは行われていることがわかります。例えば、モジュールをロードする必要のあるデバイスが、カーネルによって検出されたときに、ロードのプロセスはどのように行われるでしょうか。モジュールの動的なロードは、ユーザー・モード・ヘルパー・プロセスにより、カーネルから行われます。
この記事ではまず、ユーザー・モード・ヘルパーとそのアプリケーション・プログラミング・インターフェース (API) について説明した上で、カーネルのなかでユーザー・モード・ヘルパー API が使用されている例をいくつか見てみます。次に、ユーザー・モード・ヘルパー API が実際にどのように機能するか、どのような制約があるのかを十分に理解するために、ユーザー・モード・ヘルパー API を使用してサンプル・アプリケーションを作成します。
ユーザー・モード・ヘルパー API は、よく知られたオプションのセットを持つ単純な API です。例えばユーザー空間から開始するプロセスを作成するには通常、実行可能プログラムの名前、その実行可能プログラムのオプション、そして一連の環境変数を指定します (プログラムを実行するための execve については、man ページを参照してください)。これは、カーネルから開始するプロセスを作成する場合でも変わりありませんが、プロセスがカーネル空間から開始されることから、使用できるオプションはいくぶん多くなります。
表 1 に、ユーザー・モード・ヘルパー API で使用可能なカーネル関数のコア・セットを記載します。
表 1. ユーザー・モード・ヘルパー API のコア関数
| API 関数 | 説明 |
|---|---|
call_usermodehelper_setup | ユーザー空間への呼び出しを行うためのハンドラーを準備する |
call_usermodehelper_setkeys | ヘルパーのセッション・キーを設定する |
call_usermodehelper_setcleanup | ヘルパーのクリーンアップ関数を設定する |
call_usermodehelper_stdinpipe | ヘルパーの stdin パイプを作成する |
call_usermodehelper_exec | ユーザー空間への呼び出しを行う |
上記の表に記載した関数の他に、表 2 に示す、カーネル関数をカプセル化する簡易関数もあります (簡易関数の場合、複数の関数を呼び出す代わりに、1 回だけ呼び出せば済みます)。これらの簡易関数はほとんどの場合に有効なので、可能な場合には簡易関数を使用するようにしてください。
表 2. ユーザー・モード・ヘルパー API の簡易関数
| API 関数 | 説明 |
|---|---|
call_usermodehelper | ユーザー空間への呼び出しを行う |
call_usermodehelper_pipe | stdin パイプを使用してユーザー空間への呼び出しを行う |
call_usermodehelper_keys | セッション・キーを使用してユーザー空間への呼び出しを行う |
まずはコア関数についてひととおり説明し、それから簡易関数が提供する機能を詳しく探っていくことにします。コア API は、subprocess_info 構造体と呼ばれるハンドラー参照を使用して動作します。指定されたユーザー・モード・ヘルパーのインスタンスに必要な要素は、この構造体 (./kernel/kmod.c にあります) にすべて集約されます。call_usermodehelper_setup の呼び出しによって返されるのは、この構造体の参照です。構造体 (および以降の呼び出し) は、call_usermodehelper_setkeys (資格情報が含まれる場合)、call_usermodehelper_setcleanup、および call_usermodehelper_stdinpipe の呼び出しによってさらに構成され、最終的に構成が完了した時点で call_usermodehelper_exec を呼び出すと、構成済みのユーザー・モード・アプリケーションが実行されるという仕組みです。
コア関数が最大限の制御を可能にし、そこでヘルパー関数を 1 回呼び出すことで、さらに多くの作業を代わりに行ってくれます。パイプが関連する呼び出し (call_usermodehelper_stdinpipe およびヘルパー関数 call_usermodehelper_pipe) は、ヘルパー関数用の関連パイプを作成します。具体的に言うと、作成されるパイプ (カーネルでのファイル構造体) は、ユーザー空間のアプリケーションから読み取ることができ、カーネル側で書き込むことができます。この記事を執筆している時点では、ユーザー・モード・ヘルパーでパイプを使用できるアプリケーションは、コア・ダンプのみとなっています。このアプリケーション (./fs/exec.c do_coredump()) では、パイプによってコア・ダンプがカーネル空間からユーザー空間に書き込まれます。
図 1 に、これらの関数と sub_processinfo の相互関係、そして subprocess_info 構造体の内容を示します。
図 1. ユーザー・モード・ヘルパー API の相互関係
表 2 の簡易関数は、内部で call_usermodehelper_setup 関数および call_usermodehelper_exec 関数を実行します。表 2 の最後に記載された 2 つの呼び出しは、それぞれ call_usermodehelper_setkeys、call_usermodehelper_stdinpipe を起動します。call_usermodehelper_pipe のソースは ./kernel/kmod.c に、call_usermodehelper と call_usermodhelper_keys のソースは ./include/linux/kmod.h にあります。
カーネルからユーザー空間のアプリケーションを呼び出す理由とは?
ここで、カーネルのなかでユーザー・モード・ヘルパー API が利用されている場所に目を向けてください。表 3 はすべてのアプリケーションを網羅してはいませんが、記載されているのは興味深い使用例を代表するアプリケーションです。
表 3. カーネルでのユーザー・モード・ヘルパー API のアプリケーション
| アプリケーション | ソースの場所 |
|---|---|
| カーネル・モジュールのロード | ./kernel/kmod.c |
| 電源管理 | ./kernel/sys.c |
| 制御グループ | ./kernel/cgroup.c |
| セキュリティー用の鍵の生成 | ./security/keys/request_key.c |
| カーネル・イベントの通知 | ./lib/kobject_uevent.c |
ユーザー・モード・ヘルパー API を利用した最も単純なアプリケーションの 1 つは、カーネル空間からカーネル・モジュールをロードするアプリケーションです。request_module 関数がユーザー・モード・ヘルパー API の機能をカプセル化して、単純なインターフェースを提供しています。一般的な使用モデルは、カーネルがデバイスまたは必要なサービスを特定すると、request_module を呼び出してモジュールのロードを行うというものです。ユーザー・モード・ヘルパー API により、モジュールが modprobe (ユーザー空間で request_module により呼び出されたアプリケーション) を介してカーネルにロードされます。
モジュールのロードと同様のアプリケーションに、デバイスのホットプラグ (実行時にデバイスの追加や除去をすること) を行うものもあります。この機能はユーザー・モード・ヘルパー API を使って実装され、ユーザー空間の sbin/hotplug ユーティリティーを呼び出します。
ユーザー・モード・ヘルパー API の (request_module を使用した) 興味深いアプリケーションは、テキスト検索用 API (./lib/textsearch.c) を提供するアプリケーションです。このアプリケーションは、構成可能なテキスト検索機能の基盤をカーネルに提供するアプリケーションで、検索アルゴリズムをローダブル・モジュールとして動的にロードするという部分でユーザー・モード・ヘルパー API を使用します。カーネル 2.6.30 のリリースでサポートされている検索アルゴリズムは、Boyer-Moore アルゴリズム (./lib/ts_bm.c)、ネイティブ有限状態マシンによる手法 (./lib/ts_fsm.c)、そして Knuth-Morris-Pratt アルゴリズム (./lib/ts_kmp.c) の 3 つです。
ユーザー・モード・ヘルパー API は、正常な手順でのシステム・シャットダウンにおいても Linux をサポートします。システムの電源を切らなければならない場合、カーネルはユーザー空間の /sbin/poweroff コマンドを呼び出すことによって電源断を行うからです。これ以外のアプリケーションについては、表 3 にソースの場所と併せて記載されています。
ユーザー・モード・ヘルパー API のソースと API は kernel/kmod.c にあります (このファイルには、カーネル空間のカーネル・モジュール・ローダーとしての基本的な使用方法が説明されています)。この実装では、面倒な作業に kernel_execve を使用しています。kernel_execve は、ブート時に init プロセスを開始するために使用される関数なので、ユーザー・モード・ヘルパー API を使用しないことに注意してください。
ユーザー・モード・ヘルパー API の実装は至って単純明快です (図 2 を参照)。ユーザー・モード・ヘルパー API の仕事は、call_usermodehelper_exec (事前に構成された subprocess_info 構造体からユーザー空間のアプリケーションを起動するために使用する関数) を呼び出すところから始まります。この関数が取る引数は、subprocess_info 構造体の参照と、列挙型の引数 (待機しないか、プロセスが起動されるまで待機するか、あるいはプロセス全体が完了するまで待機するか) の 2 つです。引数として指定された subprocess_info (厳密には、この構造体の work_struct 要素) が作業構造体 (khelper_wq) にエンキューされ、この構造体によって、関数呼び出しが非同期で実行されます。
図 2. ユーザー・モード・ヘルパー API の内部実装
要素が khelper_wq に入れられると、この作業キューを対象とするハンドラー関数 (この場合は __call_usermodehelper) が呼び出されて、khelper スレッドで実行されます。このハンドラー関数はまず、ユーザー空間の呼び出しに必要なすべての情報が含まれる subprocess_info 構造体をデキューします。次のパスは列挙型の wait 変数の値によって異なります。要求側が、ユーザー空間の呼び出しからプロセス全体が完了するまで待機する場合 (UMH_WAIT_PROC)、またはまったく待機しない場合 (UMH_NO_WAIT) には、wait_for_helper 関数によってカーネル・スレッドが作成されます。それ以外の場合、要求側はプロセス全体が完了するまでは待機せず、ユーザー空間のアプリケーションが呼び出される時点まで待機することになります (UMH_WAIT_EXEC)。この場合には、____call_usermodehelper() のためのカーネル・スレッドが作成されます。
wait_for_helper スレッドでは SIGCHLD シグナル・ハンドラーがインストールされます。また、別のカーネル・スレッドが ____call_usermodehelper を対象に作成されますが、wait_for_helper スレッドでは sys_wait4 が呼び出され、____call_usermodehelper カーネル・スレッドが終了するまで待機します (このカーネル・スレッドの終了が、SIGCHLD シグナルによって通知されます)。その後で、スレッドは必要なクリーンアップを実行します (つまり、UMH_NO_WAIT の構造体を解放するか、単純に完了通知を call_usermodehelper_exec() に送信します)。
ユーザー空間でアプリケーションを起動させるための実際の作業が行われる場所は、____call_usermodehelper 関数です。この関数は最初にすべてのシグナルをブロック解除し、セッション・キー・リングを設定します。また、要求されている場合には stdin パイプのインストールも行います。さらに多少の初期化を行った後で、(kernel/syscall.c の) kernel_execve 関数を呼び出すことによってユーザー空間のアプリケーションが起動されます。この関数には前に定義された path、(ユーザー空間のアプリケーション名が含まれる) argv リスト、そして環境変数が含まれます。このプロセスが完了すると、do_exit() の呼び出しにより、スレッドが終了します。
このプロセスでは、セマフォーのような動作をする Linux の completion も使用します。completion は、call_usermodehelper_exec 関数が呼び出されると宣言されます。subprocess_info 構造体が khelper_wq に入れられた後、completion 変数を唯一の引数として wait_for_completion が呼び出されます。subprocess_info 構造体でも、この変数が complete フィールドとして保存されることに注意してください。子スレッドが call_usermodehelper_exec 関数をウェイクアップする必要があるときには、subprocess_info 構造体の completion 変数を使ってカーネル・メソッド complete を呼び出します。この呼び出しによって関数がアンロックされるため、この関数の実行が続けられるというわけです。この API の実装は、/include/linux/completion.h にあります。
ユーザー・モード・ヘルパー API について、さらに詳しく調べるには、「参考文献」セクションのリンクにアクセスしてください。
ここからは、ユーザー・モード・ヘルパー API の単純な使い方を紹介します。まずは標準的な API について説明し、続いてヘルパー関数による単純化の方法を説明します。
このサンプル・アプリケーションのデモでは、API を起動する単純なローダブル・カーネル・モジュールを開発します。リスト 1 ではモジュールの定型関数として、モジュールの entry 関数と exit 関数を定義しています。この 2 つの関数は、モジュールの modprobe または insmod (モジュールの entry 関数の場合)、およびモジュールの rmmod (モジュールの exit 関数の場合) で呼び出されます。
リスト 1. モジュールの定型関数
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kmod.h>
MODULE_LICENSE( "GPL" );
static int __init mod_entry_func( void )
{
return umh_test();
}
static void __exit mod_exit_func( void )
{
return;
}
module_init( mod_entry_func );
module_exit( mod_exit_func );
|
リスト 2 ではユーザー・モード・ヘルパー API が使用されています。このユーザー・モード・ヘルパー API の使用方法を詳しく説明すると、この関数はまず、必要なさまざまな変数と構造体を宣言します。最初に宣言されている subprocess_info は、ユーザー空間の呼び出しに必要なすべての情報が含まれる構造体です。この構造体の呼び出しは、call_usermodehelper_setup の呼び出しによって初期化されます。次に定義されているのは、argv という名前の引数リストです。このリストは一般的な C プログラムで使用する argv リストと同じく、アプリケーション (配列の最初の要素) と引数リストを定義します。リストの終わりは、NULL 終了文字で明示する必要があります。ここでは、argc 変数 (引数の数) が暗黙的に指定されていますが、これは、argv リストの長さは既知であるためです。この例では、アプリケーションの名前は /usr/bin/logger、その引数は help! で、その後に終了文字 NULL が続きます。次に必要な変数は、環境変数の配列 (envp) です。この配列は、ユーザー空間のアプリケーションの実行環境を定義するパラメーターのリストです。この例では、シェル用の典型的なパラメーターをいくつか定義し、終了文字の NULL エントリーでリストを終了しています。
リスト 2. 単純なユーザー・モード・ヘルパー API のテスト
static int umh_test( void )
{
struct subprocess_info *sub_info;
char *argv[] = { "/usr/bin/logger", "help!", NULL };
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };
sub_info = call_usermodehelper_setup( argv[0], argv, envp, GFP_ATOMIC );
if (sub_info == NULL) return -ENOMEM;
return call_usermodehelper_exec( sub_info, UMH_WAIT_PROC );
}
|
変数と構造体を定義した後、call_usermodehelper_setup を呼び出すことによって、subprocess_info 構造体を初期化します。すでに初期化された変数に加え、4 番目のパラメーターとして、メモリーを初期化するための GFP マスクを指定していることに注意してください。この call_usermodehelper_setup 関数の内部では、カーネルにメモリーを割り当て、そのメモリーをゼロに設定する kzalloc 関数が呼び出されます。この関数には、GFP_ATOMIC または GFP_KERNEL いずれかのフラグが必要です (前者は呼び出しをスリープ状態にできないこと、後者はスリープ状態が許可されることを定義するフラグです)。新しい構造体を簡単にテスト (つまり、NULL ではないことをテスト) した後、call_usermodehelper_exec 関数を使用して呼び出しを続けます。この関数は subprocess_info 構造体と、待機するかどうかを定義する列挙型 (内部実装についてのセクションで説明) を引数に取ります。プロセスの内容はこれだけです。モジュールがロードされると、/var/log/messages ファイルにロードが正常に完了したことを示すメッセージが記録されます。
このプロセスをさらに単純化するには、call_usermodehelper_setup 関数と call_usermodehelper_exec 関数をまとめて実行する call_usermodehelper API 関数を使用するという方法があります。リスト 3 に示されているように、この関数によって、使用する関数が 1 つ減るだけでなく、呼び出し側が subprocess_info 構造体を管理する必要もなくなります。
リスト 3. さらに単純なユーザー・モード・ヘルパー API のテスト
static int umh_test( void )
{
char *argv[] = { "/usr/bin/logger", "help!", NULL };
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };
return call_usermodehelper( argv[0], argv, envp, UMH_WAIT_PROC );
}
|
リスト 3 では、呼び出しのセットアップと実行に、リスト 2 と同じ要件が適用されることに注意してください (例えば、argv および envp 配列を初期化するなど)。唯一の違いは、call_usermodehelper 関数によって、call_usermodehelper_setup 関数と call_usermodehelper_exec 関数が実行するのと同じ内容が実行されることだけです。
その多種多様な用途 (カーネル・モジュールのロードから、デバイスのホットプラグ、そして udev 用のイベント・ディストリビューションに至るまで) を考えると、ユーザー・モード・ヘルパー API は、カーネルにとって重要な側面となります。この API を利用した純粋なアプリケーションを検証することも重要ですが、ユーザー・モード・ヘルパー API は理解しておくべきカーネルの重要な側面であるため、Linux カーネルのツールキットに加えておくと役に立ちます。
学ぶために
- ユーザー・モード・ヘルパー API に関する情報はそれほどありませんが、実装はかなり簡潔なので、簡単に理解できます。ユーザー・モード・ヘルパー API の実装は、すべてのソース・リビジョンに共通のソース・ブラウザー、LXR (Linux Cross Referencer) で確認できます。主要なファイルは、kmod.c と kmod.h の 2 つです。
- /proc ファイルシステムは、ユーザー空間とカーネル間の革新的な通信方法、つまり仮想ファイルシステムによる通信を実現します。/proc ファイルシステムについては、「/proc ファイルシステムを使用した Linux カーネルへのアクセス」(developerworks、2006年3月) で詳しく説明されています。
- Linux システム・コール・インターフェースは、ユーザー空間のアプリケーションがカーネルの機能を呼び出すための手段です。「Linux システム・コールを使用したカーネル・コマンド」(developerWorks、2007年3月) では、新しいシステム・コールを追加する方法をはじめ、Linux システム・コール・インターフェースについての詳細を説明しています。
- この記事ではユーザー・モード・ヘルパー API について説明するために、ローダブル・カーネル・モジュールを使用して、テスト・アプリケーションをカーネルにインストールしました。ローダブル・カーネル・モジュールとその実装について詳しく学ぶには、「Linux ローダブル・カーネル・モジュールの徹底調査」(developerworks、2008年7月) を読んでください。
- 2.6 カーネルの作業キュー・インターフェースについて詳しく学ぶには、2003 年の Linux Journal 記事を調べてください。カーネル作業キューのAPI と操作について分かりやすく説明しています。
- developerWorks Linux ゾーンには、Linux 開発者用の資料が豊富に揃っています。
- developerWorks の Technical events and webcasts で最新情報を入手してください。
- Twitter で developerWorks をフォローしてください。
製品や技術を入手するために
- developerWorks から直接ダウンロードできる IBM ソフトウェアの試用版を使用して、Linux で次の開発プロジェクトを構築してください。
議論するために
- My developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。

M. 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. の顧問エンジニアでもあります。