Linuxなどのメモ書き

proc fs を使ったカーネル情報の表示


1.概要

Linuxでは/proc/配下に擬似ファイルシステム(proc fs)がマウントされており、ここの擬似的なファイルからカーネル内の様々な情報を簡単に取得できる。ここでは、proc fsにノードを作成して、カーネル内の情報を出力するモジュールの作り方をまとめる。Loadable Moduleとして作成する。

2. サンプルモジュール

proc fsにノードを作成して、そのノードからカーネル内の情報を出力するモジュールの例を以下に示す。ダウンロードはこちらから。

2.1 SouceとMakefile

以下のMakefileとSource(proctest.c)を同じディレクトリに置いてmakeするとLoadable Module proctestmod.koができる(Loadable Moduleの基本的な作りはLoadable Kernel Moduleの作り方を参照)。

このモジュールはカーネルにロードされると/proc/に'test'という名前の擬似ファイルを作成する。そして、/proc/testの中を見るとこのモジュール内の関数のアドレスを表示する。

Makefile

KERNELSRCDIR = /usr/src/linux
BUILD_DIR := $(shell pwd)
VERBOSE = 0

# モジュール名
obj-m := proctestmod.o

# <モジュール名>-objs にモジュールを構成するオブジェクトの一覧を列挙する
proctestmod-objs := proctest.o

all:
        make -C $(KERNELSRCDIR) SUBDIRS=$(BUILD_DIR) KBUILD_VERBOSE=$(VERBOSE) modules

clean:
        rm -f *.o
        rm -f *.ko
        rm -f *.mod.c
        rm -f *~

proctest.c

#include <linux/config.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>

MODULE_DESCRIPTION("Proc FS Test Module");
MODULE_AUTHOR("kztomita");
MODULE_LICENSE("GPL");

#define PROC_NAME "test"

static int proctest_read(char *, char **, off_t, int, int *, void *);

static struct proc_dir_entry *dirp;

static int proctest_init_module(void)
{
        /*
         * Create a proc file.
         */
        dirp = (struct proc_dir_entry *)
                create_proc_entry(PROC_NAME, 0444, (struct proc_dir_entry *) 0);
        if (dirp == 0)
                return(-EINVAL);

        dirp->read_proc = (read_proc_t *) proctest_read;

        return 0;
}

static void proctest_cleanup_module(void)
{
        remove_proc_entry(PROC_NAME, (struct proc_dir_entry *) 0);
}

static int proctest_read(char *buffer, char **start, off_t offset,
                         int count, int *peof, void *dat)
{
        int len = 0;
        len += sprintf(buffer + len,
                       "proctest_init_module()::    0x%08x\n",
                       (unsigned int) proctest_init_module);
        len += sprintf(buffer + len,
                       "proctest_cleanup_module():: 0x%08x\n",
                       (unsigned int) proctest_cleanup_module);

        return len;
}

module_init(proctest_init_module);
module_exit(proctest_cleanup_module);

2.2 実行結果

作成したモジュールをロードして/proc/testを読みだした結果は以下のとおり。
# /sbin/insmod proctestmod.ko    モジュールをロード
# ls /proc/ | grep test
test                             testファイルができている
# more /proc/test                カーネル内の情報(関数アドレス)が確認できる
proctest_init_module()::    0xf88ee000
proctest_cleanup_module():: 0xf88ee03c
#       

3. 解説

3.1 ノードの作成

モジュールが読みこまれた時にproc fs上に擬似ファイル/proc/testを作成している。ファイルの作成にはcreate_proc_entry()を使用する。

create_proc_entry()のプロトタイプ
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
                                         struct proc_dir_entry *parent)

引数nameはファイル名、modeはパーミッション、parentはファイルを作る親ディレクトリのディレクトリエントリを指定する(*1)。NULLを指定すると、/proc直下に作成される。返り値は作成したノードのディレクトリエントリであるstruct proc_dir_entryへのポインタを返す。

ファイルを作成した後は、ファイルをRead/Writeした時にファイルシステムから呼び出されるハンドラを登録する。ハンドラはRead/Writeでそれぞれ、proc_dir_entry.read_proc,write_procに登録する。上記のサンプルではReadのみ処理するためread_procのみ指定している。

(*1) ディレクトリを作成したい場合は、proc_mkdir()を使用する。ディレクトリを指定するにはproc_mkdir()で返されたstruct proc_dir_entry をcreate_proc_entry()のparentに渡せばよい。

3.2 Readハンドラ

File SystemからよびだされるReadルーチン。関数のプロトタイプは以下のとおり。

int f(char *buffer, char **start, off_t offset, int count, int *peof, void *dat)

各引数の意味は表1の通り。


表1 Readハンドラの引数
引数
説明
buffer データ格納用Bufferの先頭アドレス。ハンドラはこの領域にデータを格納して返す。
start
*startにある値を入れてリターンすることで、呼び出し側のFile SystemとReadハンドラ間のデータの渡し方を指定する。呼び出し時は必ず *start == NULLで呼び出される。詳細は3.3参照。
offset
要求されているデータのオフセット。ハンドラはこのオフセット以降のデータを返す。
count
bufferが指しているバッファのサイズ。
peof
データがもうない(EOF)ことを呼び出し側に知らせるのに使用する。
*peof = 1としてリターンする。
dat


proctest_read()はバッファ(buffer)にデータを格納して書き込んだデータのサイズを返している。データの返し方には複数種類あり、各方法の詳細は3.3節に示す。

3.3 File SystemとReadハンドラのインタフェース

File SystemとReadハンドラ間でのデータの受渡し方法はfs/proc/generic.cのコメントに記述されている。以下に訳とメモをまとめる。

How to be a proc read function

Read関数の呼び出し方

Prototype:

int f(char *buffer, char **start, off_t offset, int count, int *peof, void *dat)

Assume that the buffer is "count" bytes in size.

bufferの大きさはcountバイトとして扱ってください。

If you know you have supplied all the data you have, set *peof.

Read関数は全データを渡したら、*peofをセットしてください(*1)。

You have three ways to return data:

データの返し方には3種類の方法があります。

0) Leave *start = NULL. (This is the default.)

*start = NULLのままにしておくケース (デフォルト(*2))

Put the data of the requested offset at that offset within the buffer. Return the number (n) of bytes there are from the beginning of the buffer up to the last byte of data. If the number of supplied bytes (= n - offset) is greater than zero and you didn't signal eof and the reader is prepared to take more data you will be called again with the requested offset advanced by the number of bytes absorbed. This interface is useful for files no larger than the buffer.

この場合は、要求されたオフセット(offset引数で指定)からのデータをbufferのoffsetの位置から格納してください。そして、bufferの先頭から格納したデータの最後のバイトまでのバイト数(n)を返してください(*3)。Read関数が格納したデータサイズ(n - offset)が0より大きく、EOFを通知(*4)していなくて、読み出し側プロセスの用意したReadバッファがもっとあるなら、読み込んだバイト数分だけoffsetが進められてRead関数が再度呼び出されます。このインタフェースはbufferより小さいデータを扱うのに便利です。

1) Set *start = an unsigned long value less than the buffer address but greater than zero.

*startにbufferアドレスよりも小さい値(非0)をセットするケース

Put the data of the requested offset at the beginning of the buffer. Return the number of bytes of data placed there. If this number is greater than zero and you didn't signal eof and the reader is prepared to take more data you will be called again with the requested offset advanced by *start. This interface is useful when you have a large file consisting of a series of blocks which you want to count and return as wholes.(Hack by Paul.Russell@rustcorp.com.au)

この場合は、要求されたオフセットからのデータをbufferの先頭から格納してください。そして、格納したデータのバイト数を返してください。データサイズが0より大きく、EOFを通知していなくて、読み出し側プロセスの用意したReadバッファがもっとあるなら、*start分だけoffsetが進められてRead関数が再度呼び出されます。このインタフェースは一連のデータ郡を含む大きなデータを扱うのに便利です。

2) Set *start = an address within the buffer.

*startにbuffer内のアドレスをセットするケース

Put the data of the requested offset at *start. Return the number of bytes of data placed there. If this number is greater than zero and you didn't signal eof and the reader is prepared to take more data you will be called again with the requested offset advanced by the number of bytes absorbed.

*startの位置に要求されたオフセットからのデータを格納してください。そして、格納したデータのバイト数を返してください。データサイズが0より大きく、EOFを通知していなくて、読み出し側プロセスの用意したReadバッファがもっとあるなら、読み込んだバイト数分だけoffsetが進められてRead関数が再度呼び出されます。

(*1) *peof = 1とすればよい。
(*2) Read関数が呼ばれるときは*startは必ずNULLに初期化されている。
(*3) データサイズを返すのではないので注意。格納したデータのサイズは(n - offset)となる。
(*4) Read関数が*peof = 1としてリターンするということ。

[サンプルモジュール proctest_read() の補足]

proctest_read()では上記の0)手順でデータをFile Systemに渡している。proctest_read()ではbufferにデータを書き込む際、図3.1(a)のようにoffset以降を格納するのではなく、offsetを見ずに全データをbufferに書き込んで(b)のようにしている。0)の方式ではFile System側でbuffer + offset以降からデータが取り出されるので、Read関数ではoffsetを特に取り扱わなくても正常に扱われる。"This interface is useful for files no larger than the buffer."とされるのは、このため。


図3.1 bufferへのデータの書き込み

proctest_read()では*peof=1を行っていないが、offsetが進められて再度呼び出された時にデータサイズ(n - offset)が0になり、データの終端に達したものとして読み出し処理は正常に終了する。

bufferの実体は物理ページ1ページ分。このため、Read関数ではcountを越えてデータを書き込んでもページサイズ(i386系であれば4KB)を越えなければ、データを破壊することはない。proctest_read()でも明らかに4KBには収まるサイズしか返さないのでデータ長のチェックは行っていない。countを越えてページに格納されたデータはFile Systemの方で切り捨てて、読み出しプロセスのバッファにcopy_to_user()するようになっている。

countは(物理ページサイズ - 1KB)か読み出し側プロセスが用意したバッファの残りバイト数のうち小さい方がRead関数に渡される。

関連ページ

Loadable Kernel Moduleの作り方


最終更新 2007/02/17 12:58:37 - kztomita
(2006/11/16 01:14:09 作成)
添付ファイル
buffer.png - kztomita


リンク

その他のWiki
Linuxメモ
Xnuメモ

会社
(有)ビットハイブ
受託開発やってます。

よくやる仕事

・Webシステム開発(LAMP環境)
・Linuxサーバー設定関連
サーバー移転作業代行

開発事例にデジタルカタログ/マンガビューワーを追加しました。

draggable.jsのスマホ対応版デモページを追加しました。説明はこちら

検索

Adsense
最近のコメント