C MAGAZINE Linux programming Tips
第3回 procファイルシステム調理法
■はじめに
このLinux Programming Tipsの第1回(ifconfigもどきを作ってみよう)、 第2回 (PCIプローブ)でも procファイルシステム(以下 proc fs)に関する話題にふれていました。 簡単にPCIプローブなどのカーネルよりの実装を行いたい場合は、proc fsは非常に便利なものです。proc fsがないと、 カーネルに特別なインタフェイスを追加したり、 カーネルメモリーをアクセスして必要な情報をシンボルテーブルを使ってデコードしたりするはめになりかねません。
前回までのお話しでは、ある特定のプログラムを実装するためにproc fsを利用していましたが、 今回はproc fs全般について説明し、それを利用するプログラム例を紹介したいと思います。また、 proc fsは、動作しているLinuxカーネル(モジュールも含む)のさまざまな情報を手軽に我々に提供してくれるものなので、 linuxのトラブルシューティングも行う際もきっと役立つでしょう。
■procファイルシステムとは
procファイルシステムとは、ユーザー空間のプロセスに対してカーネルが保持している情報を提供するための一つの手段であり、 ファイルシステムとして実装されている。硬い表現ですが、要するに便利なものです。 もちろんカーネルに関する情報の取得は、ファイルシステムでなくとも /dev/procなどのデバイスエントリを用意する実装やioctl経由で値を得る実装などに色々考えられますが、扱いの面でも面倒そうです。
ご存知の通りLinuxでは、ext2,ext3(まだか ),msdosなどを始め多くのファイルシステムをサポートしています。 おそらく現在のLinuxは一番多くのファイルシステムをサポートしているOSだと言えるでしょう(カーネル2.2.16では20ぐらいサポートしています)。 これらのファイルシステムが別々にアクセスインターフェイスを持っていてはプログラムを作成する側としてはたまったものではありません。 Linux(ほとんどのUNIX)では、 Virtual File Systemという抽象的なレイヤーを介して各ファイルシステムへアクセスするような構造になっています(下図参照)。 もちろんproc fsもその例外ではありません。
例外でない証拠として、/etc/fstabに procファイルシステムのエントリーが存在しています。 あたかも他のファイルシステム同様に起動時に勝手にディレクトリー/procにマウントされるようになっています。
/etc/fstabの例
/dev/hda1 / ext2 defaults 1 1
/dev/hda2 swap swap defaults 0 0
proc /proc proc defaults 0 0
|
もちろんその他のファイルシステムと同じように扱えるためproc fsのマウントポイントはどこでもよいのですが、 通常は /procにマウントされることになっています。この位置をあてにしているプログラムが多く存在しているので、 違うマウントポイントにする場合は関連プログラムの修正も必要になります。 本当にマウントポイントはどこでもよいかという疑いを持っている人は以下を試してみてください(ファイルシステム的には mount/umountシステムコール)。
# mount proc /tmp/proc -t proc
# ls /tmp/proc
# umount /tmp/proc
一般的に /proc 以下のファイルは、人間が可読可能な(それ以外のものもある )テキストファイルなので cat コマンドなどで 参照することができます(ファイルシステム的にはreadシステムコール)。
# cat /proc/version
また一部のファイルに関しては、書き換え可能なものが存在しており、 カーネル内部の変数を動的に変更可能です(ファイルシステム的にはwriteシステムコール )。例えば、 以下のようなコマンドを実行するとホスト名が変更されたことを確認できると思います (意味もなくホスト名を変更する必要はありませんが...)。
# cat /proc/sys/kernel/hostname
# echo hogehoge > /proc/sys/kernel/hostname
# cat /proc/sys/kernel/hostname
以上のようにproc fsは、基本的に、その他のファイルシステムと同様な振舞をしていることが確認できたと思います。 もちろんファイルシステムに関する全てのシステムコールが実装されているわけではありません(実装は可能ですが、 おそらく必要ないので)。ためしに rm, ln, chownなどを /proc以下のファイルに対して行ってみてください(多分、 文句を言われるでしょう)。
他のファイルシステムと異なる点としては、 proc fsに格納されているファイルは何らかの特定のストレージに格納されているものではなく仮想的に存在しているという点です。 以前は、バックアップをとる際に 間違って /proc以下も tarで固めてしまってはまったという話しをよく耳にしました(最近は、ディスクも大きいので間違って /procをtarでかためても気にかからないかな...)。
■procファイルシステムから参照できる情報
procファイルシステからはどれだけの情報が得れるのでしょう。 まず何はともあれ /proc以下をlsしてみましょう。 おそらくほとんどのファイルはそのファイル名前から何が格納されているか推測できると思います。
ls /procの出力例 (kernel version 2.2.16)
1/ 127/ 3/ 623/ 979/ fb ksyms mtrr stat
1016/ 128/ 387/ 624/ 980/ filesystems loadavg net/ swaps
105/ 149/ 391/ 67/ apm fs/ locks partitions sys/
116/ 150/ 4/ 7/ bus/ ide/ mdstat pci tty/
123/ 151/ 448/ 77/ cmdline interrupts meminfo rtc uptime
124/ 152/ 449/ 85/ cpuinfo ioports misc scsi/ version
125/ 2/ 5/ 887/ devices kcore modules self@
126/ 209/ 6/ 95/ dma kmsg mounts slabinfo
|
この出力例では、整数の名前のついた数個のディレクトリー、 普通っぽい名前のついて数個のファイルとディレクトリーと一つのリンクを確認することができます。実は、 整数の名前のついたディレクトリー(プロセスサブディレクトリー)は、プロセスと関連しており、整数は、 プロセスIDを示しています。プロセスサブディレクトリー以下では、プロセスに関するさまざまな情報を取得することができます。 また普通っぽい名前のついたファイルでは、カーネル(モジュールも含む)に関する情報が取得できます。 残りの普通っぽい名前のついたディレクトリーは、特定の機能に関する情報がまとまって格納されています。
ここでは、/procのファイル/ディレクトリーを3つのカテゴリーに分類し、各分類の内容を表を用いて説明します。
・Table-1: プロセスサブディレクトリー以下のファイル/ディレクトリー
・Table-2: ファイル
・Table-3: サブディレクトリー(プロセス情報以外)、リンク
Table-1: プロセスサブディレクトリー
| ファイル名内容 |
備考 |
| cmdlineコマンドライン引数のリスト |
0x00で区切られている |
| cwdプロセスのカレントディレクトリーへのリンク |
|
| environプロセスがロードした環境変数のリスト |
0x00で区切られている |
| exe実行元のバイナリーファイルへのリンク |
|
| fdプロセスによってオープンされたファイルへのリンクを含んだディレクトリー |
|
| mapsプロセスのアドレススペース内のメモリーゾーンのリスト |
|
| memプロセスのアドレススペースの内容 |
|
| rootプロセスのルートディレクトリーへのリンク |
|
| statプロセスステータス |
|
| statmプロセスのメモリー情報 |
|
| status(human readableな)プロセスステータス |
|
| cpuプロセスが占有しているCPUの情報 |
SMPの場合のみ表示される |
Table-2: ファイル
| ファイル名内容 |
備考 |
| cmdlineシステム起動時にカーネルに渡された引数 |
liloなどで渡される引数 |
| cpuinfocpuに関する情報 |
|
| devicesカーネルに含まれるデバイスコントローラのリスト |
|
| dmaデバイスコントローラで使用されているdmaのリスト |
|
| filesystemsカーネルがサポートしているファイルシステムのリスト |
新たなファイルシステムモジュールをロードした場合はリストが増える |
| interruptsデバイスコントローラが使用しているハード割り込みのリスト |
|
| ioportsデバイスコントローラで使用されるI/Oポートのリスト |
|
| kcoreカーネルに割り当てられているメモリー |
|
| kmsgカーネルによって表示された最後のメッセージ |
|
| ksymsモジュールして使用されているカーネルシンボルのリスト |
|
| loadavgシステム負荷 |
top,wコマンドなどが使用する |
| locksファイルにアサインされているロックのリスト |
|
| mdstatソフトウエアRAIDに関する情報 |
|
| meminfoメモリー情報 |
freeコマンドが使用する |
| miscデバイスノードを持たないデバイスコントローラのリスト |
|
| modulesカーネルにロードされているモジュールのリスト |
lsmodが使用する |
| mountsマウントされているファイルシステムのリスト |
/etc/mtabとほぼ同じ |
| partitionsカーネルが認識しているパーティション情報 |
|
| pciPCIバスに接続されているPCIデバイスのリスト |
/proc/bus/pciでより正確な情報を取得できる |
| rtcリアルタイムクロックの情報 |
|
| slabinfoカーネル内部でアロケートされているメモリなどの情報 |
|
| statさまざまなカーネルオペレーションの統計的な情報 |
|
| swaps 使用中 のスワップの情報 |
swapon -sと同等 |
| uptimeシステムが起動してからの合計時間 |
|
| versionカーネルバージョンなど |
いくつかのプログラムはこの情報で動作を変更する |
Table-3: サブディレクトリー(プロセス情報以外)
| ディレクトリー名内容 |
備考 |
| busシステムバスに接続されているバスに関する情報 |
ノーマルの2.2.16カーネルではpciの情報のみ利用可能。2.4からはisaなどに関する情報も利用可能。 |
| ideカーネルが認識しているIDEに関するさまざまな情報 |
|
| netカーネルが認識しているネットワークの情報 |
ipmasqなどは設定可能 |
| scsi/カーネルが認識しているSCSIの情報 |
|
| selfカレントプロセスのプロセスディレクトリーへのリンク |
|
| syssysctlで管理されているカーネル変数の情報 |
デバックパラメータなどの設定 |
■procファイルシステムに関するプログラミング
まず/procのプロセスサブディレクトリーを利用してpsのような動作をするプログラムを紹介します。 このプログラムは、/procを利用するとこのぐらいコードである程度のことはできてることをアピールするために、 あえてbourne shellで記述しました(Cマガの趣旨に反する?)。 実際に動かしてみると非常に遅いので暇がある人はCで書き直してみてください(そのほうがCマガらしい)。
一応、このスクリプトにコメントを入れておきます。このスクリプトは、 statusファイルが含まれているディレクトリーをプロセスサブディレクトリーとして扱うようになっています。 /proc/selfはカレントプロセスのプロセスサブディレクトリーへのリンクが例外になるので、 コード中ではそれは除くようになっています。それ以外の処理は、単純に /proc/$PID/statusと /proc/$PID/cmdlineから情報を取ってきて表示しているだけにすぎません。
[List-1: psx.sh]
#! /bin/sh
#
# This is a simple shell scripts which emulates ps command by parsing
# the process subdirectory in proc filesystem
#
for status in /proc/*/status ; do
if ! echo $stat | grep -q 'self' ; then
PID=`cat $status|grep "^Pid:"|cut -f 2`
STATE=`cat $status|grep "^State:"|cut -f 2`
PSUID=`cat $status|grep "^Uid:"|cut -f 2`
PSGID=`cat $status|grep "^Gid:"|cut -f 2`
NAME=`cat $status|grep "^Name:"|cut -f 2`
DIRNAME=`dirname $status`
CMDLINE=`cat $DIRNAME/cmdline`
echo -e $PID\\t$STATE\\t$PSUID\\t$PSGID\\t$NAME\\t$CMDLINE
fi
done
|
readコール
次は、いきなり shellスクリプトからカーネルモジュールに話しは飛んでしまいます。 もちろんこのプログラムはC言語で記述されます(もちろん他の言語で書けと言われても断りますが)。 既存のprof fsの内容を見ているだけだは物足りない人にお勧めです。
最初に紹介するプログラム(カーネルモジュール)は、 /proc/hogeという名前の読み込み専用ファイルをprocfsに登録するものです(List-2)。 /proc/hogeを catすると"Welcome to hoge"の行と(あの有名な)bogo mipsが表示されたている行を見ることができます。
List-2のプログラムの実行例を以下に示します。コンパイル方法は、 ソースコードの末尾についているコンパイル例を参考にしてください。
# insmod hoge.o
# cat /proc/hoge
Welcome to hoge
bogomips : 901.12
# rmmod hoge
このコードのきもは、特にございません(簡単なサンプルなので)。手順的には、 create_proc_entry()関数でエントリーを作成し、read要求が入ったときに呼び出される関数を登録するだけです。 最後にモジュールがリムーブされるときは忘れずにremove_proc_entry()関数を呼び出すといったところです。
/proc/hoge に入る read要求を処理するhoge_proc_read()でバッファに関する少しばかりややこしい処理がありますが、 これはoffset指定でのread要求に対する処理です。
またproc fsとは直接関係ありませんが、このコードからも分かるようにカーネルモジュールも一旦ロードされてしまえば、 カーネルが持っている名前空間を共有することになります(だからCPUの情報が取得できる)。モジュールのソースコードの関数は、 必要の無い限り staticで宣言しておくのが無難です。さらに付け加えて言っておくと hoge.c内はすでにカーネル空間です。 コードを書き換える場合はくれぐれも慎重に。
[list-2: hoge.c]
/*
*
* An exmaple code of read call on procfs
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/proc_fs.h>
static int hoge_proc_read(char *buffer, char **start, off_t offset,
int length, int *eof, void *data)
{
int len = 0;
#if defined(__i386__)
/* get cpu info (it works on x86 arch)*/
struct cpuinfo_x86 *c = cpu_data;
len = sprintf(buffer, "Welcome to hoge!\nbogomips\t: %lu.%02lu\n",
(c->loops_per_sec+2500)/500000,
((c->loops_per_sec+2500)/5000) % 100) - offset;
#else
len = sprintf(buffer, "Welcome to hoge!\n") - offset ;
#endif
if (len < length) {
*eof = 1;
if (len <= 0) return 0;
} else {
len = length;
}
*start = buffer + offset;
return len;
}
#ifdef MODULE
int init_module(void)
{
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *ent;
#endif
printk("hoge: init_module()\n");
#ifdef CONFIG_PROC_FS
ent = create_proc_entry("hoge", 0, 0);
if( ent ) {
ent->read_proc = &hoge_proc_read;
}
#endif
return 0;
}
void cleanup_module(void)
{
#ifdef CONFIG_PROC_FS
remove_proc_entry("hoge", NULL);
#endif
printk("hoge: cleanup_module()\n");
}
#endif
/*
Here is an example of compilation.
If you are working on SMP machine, please add -D__SMP__ to CFLAGS.
# egcs -D__KERNEL__ -I/usr/src/linux/include -Wall -Wstrict-prototypes -O2
-fomit-frame-pointer -fno-strict-aliasing -pipe -fno-strength-reduce -m486
-DCPU=486 -DMODULE -DMODVERSIONS -include /usr/src/linux/include/linux/modversions.h
-DEXPORT_SYMTAB -c hoge.c
*/
|
writeコール
次に、proc fsのwriteコールのサンプルとしてproc fs経由で10進/16進変換を行うカーネルモジュール(list-3)の例を示します。 このカーネルモジュールをロードすると ディレクトリー/proc/hoge2 の以下にファイルdigitとhexが作成されます。 digitには10進数、hexには16進数がそれぞれ読み書き可能になっておりどちらの値も同じ数値を示すようになっています。例えば、 digitに65535を書き込むとhexからはfffffを得ることができます。
前述のプログラムの処理に加え、proc fsにディレクトリーを作成する処理、 ファイルに書き込み属性を処理などが新しくされています。実際に動作させてみて確認してください。
[list-2: hoge.c]
/*
*
* An exmaple code of write call on procfs
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/proc_fs.h>
static long val=0;
static int hoge2_hex_proc_read(char *buffer, char **start, off_t offset,
int length, int *eof, void *data)
{
int len = 0;
long *val = (long*)data;
len = sprintf(buffer, "%lx\n", *val) - offset;
if (len < length) {
*eof = 1;
if (len <= 0) return 0;
} else {
len = length;
}
*start = buffer + offset;
return len;
}
static int hoge2_digit_proc_read(char *buffer, char **start, off_t offset,
int length, int *eof, void *data)
{
int len = 0;
long *val = (long*)data;
len = sprintf(buffer, "%ld\n", *val) - offset;
if (len < length) {
*eof = 1;
if (len <= 0) return 0;
} else {
len = length;
}
*start = buffer + offset;
return len;
}
static int hoge2_digit_proc_write(struct file *file, const char *buffer,
unsigned long count, void *data)
{
long *v = (long *)data;
*v = simple_strtoul(buffer, NULL, 10);
return count;
}
static int hoge2_hex_proc_write(struct file *file, const char *buffer,
unsigned long count, void *data)
{
long *v = (long *)data;
*v = simple_strtoul(buffer, NULL, 16);
return count;
}
#ifdef MODULE
int init_module(void)
{
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *ent;
#endif
printk("hoge2: init_module()\n");
#ifdef CONFIG_PROC_FS
ent = create_proc_entry("hoge2",S_IFDIR, 0);
if( ent ) {
ent = create_proc_entry("hoge2/digit",S_IFREG|S_IRUGO|S_IWUSR, 0);
if( ent ) {
ent->read_proc = &hoge2_digit_proc_read;
ent->write_proc = &hoge2_digit_proc_write;
ent->data = (void*)&val;
}
ent = create_proc_entry("hoge2/hex",0, 0);
if( ent ) {
ent->read_proc = &hoge2_hex_proc_read;
ent->write_proc = &hoge2_hex_proc_write;
ent->data = (void*)&val;
}
}
#endif
return 0;
}
void cleanup_module(void)
{
#ifdef CONFIG_PROC_FS
remove_proc_entry("hoge2/hex", NULL);
remove_proc_entry("hoge2/digit", NULL);
remove_proc_entry("hoge2", NULL);
#endif
printk("hoge2: cleanup_module()\n");
}
#endif
|
前述の2つの例では、create_proc_entry()で動的に確保された構造体 proc_dir_entryにread, writeなどを処理する関数を登録するような形を取っていましたが、 以下のように 構造体 proc_dir_entry の実体を宣言し、登録することも可能です。
static int hoge3_proc_read(char *buffer, char **start, off_t offset,
int length, int *eof, void *data)
{
....
}
struct proc_dir_entry hoge3_proc_entry = {
0, /* low_ino: the inode--dynamic */
5, "hoge3", /* length of name, string of name */
S_IFREG|S_IRUGO, /* creation mode */
1,0,0, /* nlinks, owner, group */
0, /* unused */
NULL, /* operations--use default */
&hoge3_proc_read,/* function used to read */
};
int init_module(void)
{
....
proc_register_dynamic( &proc_root, &hoge3_proc_entry );
....
}
void cleanup_modue(void)
{
....
proc_unregister( &proc_root, &hoge3_proc_entry.low_ino );
....
}
|
■おわりに
今回は、大雑把にproc fsに関する説明をさせて頂きました。proc fsに関するさらなる情報は、 カーネルソースの中にあるlinux/Documentation/proc.txtや各ドライバーのソースコード、 もちろんlinux/fs/proc/以下のソースコードなどに隠れています(だれも隠してない?)。 今回の講座をかわきりにさらなるproc fsの理解にトライしてはどうでしょうか。では健闘を祈ります。