レベル: 初級 M. Tim Jones, Consultant Engineer, Emulex
2006年 3月 14日 /procファイルシステムは、Linux®のカーネル空間とユーザー空間の間での新しい通信アプローチを可能にする仮想ファイルシステムです。/procファイルシステムでは、カーネル内のエンティティーとの通信手段として、仮想ファイルの読み書きができますが、通常のファイルと違って、これらの仮想ファイルの内容は動的に作成されます。この記事では、/proc仮想ファイルシステムの概要と、その使用例を紹介します。
/procファイルシステムは、もともと、システム内のプロセスに関する情報を提供するために開発されました。しかし、このファイルシステムの有用性は、カーネルの多くの要素がそれを使用して、情報を報告したり、動的なランタイム構成を有効にしたりできる点にあります。
/procファイルシステムは、ディレクトリー(情報を組織化する手段として)と仮想ファイルで構成されます。仮想ファイルは、カーネルからの情報をユーザーに提示することができ、ユーザーからカーネルに情報を送信する手段にもなります。実際には、この両方を行う必要はありませんが、この記事では、このファイルシステムを入力および出力用に構成する方法を示します。
このように短い記事では/procの用途のすべてを詳しく述べることはできませんが、2つの使用例を示すことで、/procがいかにパワフルかをわかっていただけるでしょう。リスト1は、/procのいくつかの要素のインタラクティブ・ツアーです。表示されているのは、/procファイルシステムのルート・レベルです。左側に並ぶ番号の付いたファイルに注目してください。これらはそれぞれ、システム内のプロセスを表すディレクトリーです。GNU/Linuxで最初に作成されるプロセスはinitプロセスなので、このプロセスのプロセスIDは1です。次に、ディレクトリーに対してlsを実行すると、ファイルの一覧が表示されます。各ファイルは、特定のプロセスの詳細を示します。たとえば、initのコマンドライン入力を表示するには、単にcmdlineファイルに対してcatを実行します。
/proc内には他にも面白いファイルがあります。プロセッサーの種類と速度を示すcpuinfo、PCIバス上で検出されたデバイスを示すpci、現在カーネルにロードされているモジュールを示すmodulesなどです。
リスト1./procのインタラクティブ・ツアー
[root@plato]# ls /proc
1 2040 2347 2874 474 fb mdstat sys
104 2061 2356 2930 9 filesystems meminfo sysrq-trigger
113 2073 2375 2933 acpi fs misc sysvipc
1375 21 2409 2934 buddyinfo ide modules tty
1395 2189 2445 2935 bus interrupts mounts uptime
1706 2201 2514 2938 cmdline iomem mtrr version
179 2211 2515 2947 cpuinfo ioports net vmstat
180 2223 2607 3 crypto irq partitions
181 2278 2608 3004 devices kallsyms pci
182 2291 2609 3008 diskstats kcore self
2 2301 263 3056 dma kmsg slabinfo
2015 2311 2805 394 driver loadavg stat
2019 2337 2821 4 execdomains locks swaps
[root@plato 1]# ls /proc/1
auxv cwd exe loginuid mem oom_adj root statm task
cmdline environ fd maps mounts oom_score stat status wchan
[root@plato]# cat /proc/1/cmdline
init [5]
[root@plato]#
|
リスト2は、/proc内の仮想ファイルに対する読み書きを示しています。この例では、カーネルのTCP/IPスタック内をチェックし、IPフォワーディングを有効にします。
リスト2./procの読み書き(カーネルの構成)
[root@plato]# cat /proc/sys/net/ipv4/ip_forward
0
[root@plato]# echo "1" > /proc/sys/net/ipv4/ip_forward
[root@plato]# cat /proc/sys/net/ipv4/ip_forward
1
[root@plato]#
|
別の方法として、sysctlを使用して、カーネル項目を構成することもできます。詳しくは、「参考文献」を参照してください。
ところで、/procファイルシステムは、GNU/Linuxの唯一の仮想ファイルシステムではありません。このようなシステムの1つであるsysfsは、/procと似ていますが、もう少し整理されています(/procから学習しているのです)。しかし、/procの方が定着しているので、sysfsには/procにはない利点がありますが、ここでは/procに限って述べることにします。また、debugfsというファイルシステムもありますが、(名前からわかるように)どちらかというとデバッグ用のインターフェースです。debugfsの利点は、単一の値をユーザー空間にエクスポートするのが非常に簡単なことです(実際に1回の呼び出しで済みます)。
カーネル・モジュールについて
ロード可能カーネル・モジュール(LKM)は、/procファイルシステムを説明する好例です。LKMは、Linuxカーネルに対して動的にコードの追加や削除を行う新しい手法だからです。また、LKMは、Linuxカーネルのデバイス・ドライバーとファイルシステムとして普及しているメカニズムです。
Linuxカーネルを再コンパイルしたことがあれば、カーネル構成プロセスで、多くのデバイス・ドライバーや他のカーネル要素がモジュールとしてコンパイルされることに気づいているかもしれません。ドライバーが直接カーネルにコンパイルされる場合、そのコードと静的データは、使用されていなくても空間を占有します。しかし、ドライバーがモジュールとしてコンパイルされる合は、必要なときだけメモリーを要求し、その後カーネルにロードされます。興味深いことに、LKMのパフォーマンスへの影響はほとんどないので、使用可能なハードウェアと接続されたデバイスに応じて環境に合わせるスリムなカーネルを作成するための有効な手段となります。
Linuxカーネルに見られる標準的な(動的にロードされない)コードとの違いがわかるように簡単なLKMを示します。リスト3に、最も単純なLKMを示します。(この記事のサンプル・コードは、下に示す「ダウンロード」セクションからダウンロードできます。)
リスト3では、(モジュールAPI、タイプ、およびマクロを定義する)必要なモジュール・ヘッダーをインクルードしています。次に、MODULE_LICENSEを使用して、モジュールのライセンスを定義しています。この例では、カーネルの汚染(tainted)を避けるためにGPLを指定しています。
次に、リスト3では、モジュールのinit関数およびcleanup関数を定義しています。my_module_init関数は、モジュールがロードされるときに呼び出され、初期化するために使用できます。my_module_cleanup関数は、モジュールがアンロードされるときに呼び出され、メモリーを解放し、通常はモジュールのトレースを削除するために使用されます。ここでprintkを使用していることに注意してください。これは、カーネル用のprintf関数です。KERN_INFOシンボルは、カーネル・リング・バッファー(syslogのようなもの)の入力から情報をフィルタリングするための文字列です。
最後に、リスト3では、module_initマクロとmodule_exitマクロを使用して、entry関数とexit関数を宣言しています。これにより、モジュールのinit関数とcleanup関数を指定し、どの関数が保守関数であるかをカーネルに知らせることができます。
リスト3.シンプルながら機能的なLKM(simple-lkm.c)
#include <linux/module.h>
/* Defines the license for this LKM */
MODULE_LICENSE("GPL");
/* Init function called on module entry */
int my_module_init( void )
{
printk(KERN_INFO "my_module_init called. Module is now loaded.\n");
return 0;
}
/* Cleanup function called on module exit */
void my_module_cleanup( void )
{
printk(KERN_INFO "my_module_cleanup called. Module is now unloaded.\n");
return;
}
/* Declare entry and exit functions */
module_init( my_module_init );
module_exit( my_module_cleanup );
|
リスト3は、シンプルですが実際のLKMです。これをビルドして、カーネル2. 6で試してみましょう。カーネル2.6では、カーネル・モジュールのビルド方法に、以前より簡単な新しい方法が導入されています。ファイルsimple-lkm.cを使って、次の内容のmakeファイルを作成します。
LKMをビルドするには、リスト4に示すmakeコマンドを使用します。
リスト4.LKMのビルド
[root@plato]# make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules
make: Entering directory `/usr/src/linux-2.6.11'
CC [M] /root/projects/misc/module2.6/simple/simple-lkm.o
Building modules, stage 2.
MODPOST
CC /root/projects/misc/module2.6/simple/simple-lkm.mod.o
LD [M] /root/projects/misc/module2.6/simple/simple-lkm.ko
make: Leaving directory `/usr/src/linux-2.6.11'
[root@plato]#
|
結果として、simple-lkm.koが生成されます。新しい命名規則では、カーネル・オブジェクト(LKM)と標準オブジェクトが区別しやすくなっています。これで、モジュールのロードとアンロードを行い、出力を表示することができます。モジュールをロードするにはinsmodコマンドを使用し、モジュールをアンロードするにはrmmodコマンドを使用します。lsmodは、現在ロードされているLKMを表示します(リスト5)。
リスト5.LKMの挿入、チェック、および削除
[root@plato]# insmod simple-lkm.ko
[root@plato]# lsmod
Module Size Used by
simple_lkm 1536 0
autofs4 26244 0
video 13956 0
button 5264 0
battery 7684 0
ac 3716 0
yenta_socket 18952 3
rsrc_nonstatic 9472 1 yenta_socket
uhci_hcd 32144 0
i2c_piix4 7824 0
dm_mod 56468 3
[root@plato]# rmmod simple-lkm
[root@plato]#
|
カーネル出力はstdoutではなくカーネル・リング・バッファーに送られます。stdoutはプロセス固有のものだからです。カーネル・リング・バッファー上のメッセージを検査するには、dmesgユーティリティーを使用します(または、/procそのものからコマンドcat /proc/kmsgを使用します)。リスト6は、dmesgから出力された最後の数メッセージを示しています。
リスト6.LKMからのカーネル出力の確認
[root@plato]# dmesg | tail -5
cs: IO port probe 0xa00-0xaff: clean.
eth0: Link is down
eth0: Link is up, running at 100Mbit half-duplex
my_module_init called. Module is now loaded.
my_module_cleanup called. Module is now unloaded.
[root@plato]#
|
カーネル出力でモジュールのメッセージを確認することができます。では、この単純な例から一歩進んで、実用的なLKMの開発に役立つカーネルAPIをいくつか見てみましょう。
/procファイルシステムへの統合
カーネル・プログラマーが使用できる標準APIを、LKMプログラマーも使用できます。LKMでは、カーネルが使用できる新しい変数と関数をエクスポートすることも可能です。APIの取り扱いの詳細は、この記事の範囲を超えているので、後で、実用的なLKMの例示に使用する要素のいくつかを示すだけにしておきます。
/procエントリーの作成と削除
/procファイルシステムに仮想ファイルを作成するには、create_proc_entry関数を使用します。この関数は、ファイル名、パーミッションのセット、およびファイルを作成する/procファイルシステム内の位置を受け取ります。create_proc_entryのリターン値は、proc_dir_entryポインターです(または、作成エラーを示すNULL)。返されたポインターを使用して、仮想ファイルに対して読み取りを実行するときに呼び出す関数など、仮想ファイルのその他の局面を構成することができます。create_proc_entryのプロトタイプとproc_dir_entry構造体の一部をリスト7に示します。
リスト7./procファイルシステム・エントリーを管理するための要素
struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,
struct proc_dir_entry *parent );
struct proc_dir_entry {
const char *name; // virtual file name
mode_t mode; // mode permissions
uid_t uid; // File's user id
gid_t gid; // File's group id
struct inode_operations *proc_iops; // Inode operations functions
struct file_operations *proc_fops; // File operations functions
struct proc_dir_entry *parent; // Parent directory
...
read_proc_t *read_proc; // /proc read function
write_proc_t *write_proc; // /proc write function
void *data; // Pointer to private data
atomic_t count; // use count
...
};
void remove_proc_entry( const char *name, struct proc_dir_entry *parent );
|
後ほど、read_procコマンドおよびwrite_procコマンドを使用して、仮想ファイルを読み書きする関数をプラグインする方法を示します。
/procからファイルを削除するには、remove_proc_entry関数を使用します。この関数を使用するには、ファイル名文字列と、/procファイルシステム(親)内のファイルの位置を指定します。この関数のプロトタイプも、リスト7に示されています。
parent引数には、ファイルを配置する位置に応じて、NULL(/procのルートの場合)または他の値(数値)を指定できます。表1に、使用できる他の親proc_dir_entryと、ファイルシステム内での位置を示します。
表1.proc_dir_entry変数のショートカット
| proc_dir_entry | ファイルシステム位置 |
|---|
proc_root_fs
|
/proc
|
proc_net
|
/proc/net
|
proc_bus
|
/proc/bus
|
proc_root_driver
|
/proc/driver
|
コールバック書き込み関数
write_proc関数を使用すると、/procエントリーに書き込むことができます(ユーザーからカーネルへ)。この関数のプロトタイプは、次のとおりです。
int mod_write( struct file *filp, const char __user *buff,
unsigned long len, void *data );
|
filp引数は、基本的にオープン・ファイル構造体です(ここでは無視します)。buff引数は、ユーザーに渡される文字列データです。バッファー・アドレスは実際にはユーザー空間バッファーなので、直接読み取ることはできません。len引数は、buffに書き込まれるデータ量を定義します。data引数は、プライベート・データのポインターです(リスト7を参照)。このモジュールでは、受け取るデータを処理するために、このタイプの関数を宣言しています。
Linuxには、ユーザー空間とカーネル空間の間でデータを移動するためのAPIのセットが用意されています。write_procには、copy_from_user関数を使用して、ユーザー空間データを操作しています。
コールバック読み取り関数
read_proc関数を使用すると、/procエントリーからデータを読み取ることができます(カーネルからユーザーへ)。この関数のプロトタイプは、次のとおりです。
int mod_read( char *page, char **start, off_t off,
int count, int *eof, void *data );
|
page引数は、ユーザー用のデータを書き込む位置であり、countは、書き込むことができる最大文字数を定義します。1ページ(一般に4KB)を超えるデータを返すときには、start引数およびoff引数を使用します。データがすべて書き込まれたら、eof(end-of-file)引数を設定します。writeの場合と同様に、dataはプライベート・データを表します。ここで指定されるpageバッファーは、カーネル空間にあります。したがって、copy_to_userを呼び出さなくても書き込むことができます。
その他の便利な関数
proc_mkdirを使用して/procファイルシステム内にディレクトリーを作成し、proc_symlinkを使用してシンボリックリンクを作成することができます。read関数しか必要としない単純な/procエントリーの場合は、create_proc_read_entryを使用します。これは、1回の呼び出しで/procエントリーを作成し、read_proc関数を初期化します。ここに挙げた関数のプロトタイプをリスト8に示します。
リスト8.その他の便利な/proc関数
/* Create a directory in the proc filesystem */
struct proc_dir_entry *proc_mkdir( const char *name,
struct proc_dir_entry *parent );
/* Create a symlink in the proc filesystem */
struct proc_dir_entry *proc_symlink( const char *name,
struct proc_dir_entry *parent,
const char *dest );
/* Create a proc_dir_entry with a read_proc_t in one call */
struct proc_dir_entry *create_proc_read_entry( const char *name,
mode_t mode,
struct proc_dir_entry *base,
read_proc_t *read_proc,
void *data );
/* Copy buffer to user-space from kernel-space */
unsigned long copy_to_user( void __user *to,
const void *from,
unsigned long n );
/* Copy buffer to kernel-space from user-space */
unsigned long copy_from_user( void *to,
const void __user *from,
unsigned long n );
/* Allocate a 'virtually' contiguous block of memory */
void *vmalloc( unsigned long size );
/* Free a vmalloc'd block of memory */
void vfree( void *addr );
/* Export a symbol to the kernel (make it visible to the kernel) */
EXPORT_SYMBOL( symbol );
/* Export all symbols in a file to the kernel (declare before module.h) */
EXPORT_SYMTAB
|

 |
/procファイルシステムによるフォーチュン・クッキー
ここで読み取りと書き込みの両方をサポートするLKMを示します。この簡単なアプリケーションは、フォーチュン・クッキーを1つずつ取り出す装置のはたらきをします。モジュールがロードされた後、ユーザーはechoコマンドでフォーチュン・テキストをロードし、catコマンドで、1つずつ読み出すことができます。
リスト9に、基本的なモジュール関数と変数を示します。init関数(init_fortune_module)は、vmallocでクッキー・ポット用のスペースを割り当て、memsetでクリアします。cookie_potが割り当てられ、空になったら、/procルートの次にfortuneという名前のproc_dir_entryを作成します。proc_entryが正常に作成されたら、ローカル変数とproc_entry構造体を初期化します。/procのread関数とwrite関数をロードし(リスト9および10)、モジュールの所有者の情報を記述します。cleanup関数は、単に/procファイルシステムからエントリーを削除した後、cookie_potが占有していたメモリーを解放しています。
cookie_potは、長さ4KBのページであり、2つのインデックスによって管理されます。最初のインデックスcookie_indexは、次のクッキーを書き込む場所を示します。変数next_fortuneは、次のクッキーを出力するために読み出す場所を示します。すべてのフォーチュンが読み出されたら、next_fortuneを先頭にラップします。
リスト9.モジュールinit/cleanupと変数
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fortune Cookie Kernel Module");
MODULE_AUTHOR("M. Tim Jones");
#define MAX_COOKIE_LENGTH PAGE_SIZE
static struct proc_dir_entry *proc_entry;
static char *cookie_pot; // Space for fortune strings
static int cookie_index; // Index to write next fortune
static int next_fortune; // Index to read next fortune
int init_fortune_module( void )
{
int ret = 0;
cookie_pot = (char *)vmalloc( MAX_COOKIE_LENGTH );
if (!cookie_pot) {
ret = -ENOMEM;
} else {
memset( cookie_pot, 0, MAX_COOKIE_LENGTH );
proc_entry = create_proc_entry( "fortune", 0644, NULL );
if (proc_entry == NULL) {
ret = -ENOMEM;
vfree(cookie_pot);
printk(KERN_INFO "fortune: Couldn't create proc entry\n");
} else {
cookie_index = 0;
next_fortune = 0;
proc_entry->read_proc = fortune_read;
proc_entry->write_proc = fortune_write;
proc_entry->owner = THIS_MODULE;
printk(KERN_INFO "fortune: Module loaded.\n");
}
}
return ret;
}
void cleanup_fortune_module( void )
{
remove_proc_entry("fortune", &proc_root);
vfree(cookie_pot);
printk(KERN_INFO "fortune: Module unloaded.\n");
}
module_init( init_fortune_module );
module_exit( cleanup_fortune_module );
|
ポットに新しいクッキーを書き込むのは、単純なプロセスです(リスト10)。書き込まれるクッキーの長さで、クッキーを書き込むスペースがあるかどうかをチェックします。スペースがない場合は、-ENOSPCを返し、これがユーザー・プロセスに伝えられます。スペースがある場合は、copy_from_userを使用して、ユーザー・バッファーをcookie_potに直接コピーします。次に、cookie_indexを増分して(ユーザー・バッファーの長さに基づいて)、NULLで文字列を終了します。最後に、cookie_potに実際に書き込まれた文字数を返して、それがユーザー・プロセスに伝えられます。
リスト10.フォーチュンを書き込む関数
ssize_t fortune_write( struct file *filp, const char __user *buff,
unsigned long len, void *data )
{
int space_available = (MAX_COOKIE_LENGTH-cookie_index)+1;
if (len > space_available) {
printk(KERN_INFO "fortune: cookie pot is full!\n");
return -ENOSPC;
}
if (copy_from_user( &cookie_pot[cookie_index], buff, len )) {
return -EFAULT;
}
cookie_index += len;
cookie_pot[cookie_index-1] = 0;
return len;
}
|
フォーチュンを読み取るのも、リスト11に示されているように単純です。書き込むバッファー(page)はすでにカーネル空間にあるので、直接操作することができ、sprintfを使用して次のフォーチュンを書き込むことができます。next_fortuneインデックスがcookie_index(次に書き込む位置)より大きい場合は、next_fortuneを最初のフォーチュンのインデックスであるゼロにラップします。フォーチュンがユーザー・バッファーに書き込まれた後、最後に書き込まれたフォーチュンの長さだけ、next_fortuneインデックスを増分します。これで、次に使用可能なフォーチュンのインデックスになります。フォーチュンの長さが返されて、ユーザーに伝えられます。
リスト11.フォーチュンを読み出す関数
int fortune_read( char *page, char **start, off_t off,
int count, int *eof, void *data )
{
int len;
if (off > 0) {
*eof = 1;
return 0;
}
/* Wrap-around */
if (next_fortune >= cookie_index) next_fortune = 0;
len = sprintf(page, "%s\n", &cookie_pot[next_fortune]);
next_fortune += len;
return len;
}
|
このシンプルな例から、/procファイルシステムによってカーネルとの通信が簡単にできることがわかると思います。では、fortuneモジュールの動作を見てみましょう(リスト12)。
リスト12.フォーチュン・クッキーLKMのデモンストレーション
[root@plato]# insmod fortune.ko
[root@plato]# echo "Success is an individual proposition. Thomas Watson" > /proc/fortune
[root@plato]# echo "If a man does his best, what else is there? Gen. Patton" > /proc/fortune
[root@plato]# echo "Cats: All your base are belong to us. Zero Wing" > /proc/fortune
[root@plato]# cat /proc/fortune
Success is an individual proposition. Thomas Watson
[root@plato]# cat /proc/fortune
If a man does his best, what else is there? Gen. Patton
[root@plato]#
|
/proc仮想ファイルシステムは、カーネル情報の報告や動的構成のために広く使用されています。ドライバーとモジュールのどちらのプログラミングにとっても欠かせないものであることがわかるでしょう。さらに詳しくは、下記の「参考文献」を参照してください。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Linux kernel module source | l-proc-lkm.zip | 2KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | M. Tim Jonesは、埋め込みソフトウェアのエンジニアであり、GNU/Linux Application Programming, AI Application Programming と BSD Sockets Programming from a Multilanguage Perspective の著者でもあります。エンジニアとして経歴は幅広く、静止衛星用のカーネル開発から埋め込みシステム・アーキテクチャー、そしてネットワーク・プロトコル開発まで経験しています。現在はEmulex Corp.のシニア主席エンジニアです。 |
記事の評価
|