• お問い合わせ
  • ユーザー登録
Turbolinux関連書籍
トレーニング&資格 パートナー コンサルティング ユーザー登録 Turbolinux関連書籍
turbolinux関連書籍 Turbolinux関連特集&Turbolinux執筆記事 Turbolinux関連教材ほか
Turbolinux関連書籍

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もその例外ではありません。

LinuxのVirtual File System


例外でない証拠として、/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の理解にトライしてはどうでしょうか。では健闘を祈ります。