無料Wikiサービス | デモページ
Linuxなどのメモ書き
検索

Adsense

proc fs を使ったカーネル情報の表示 の差分
Rev.19→Rev.20  追加箇所 削除箇所


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)を返してください。Read関数が返したデータサイズ(n - offset)が0より大きく、EOFを通知(*3)していなくて、読み出し側プロセスの用意した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)

2) Set *start = an address within the 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.

(*1) *peof = 1とすればよい。
(*2) Read関数が呼ばれるときは*startは必ずNULLに初期化されている。
(*3) Read関数が*peof = 1としてリターンすること

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

proctest_read()では上記の0)手順でデータをFile Systemに渡している。proctest_read()ではoffsetを見ずに全データをbufferに書き込んでいるが、File System側でbuffer + offset以降からデータが読み出されるのでoffsetの処理も正常に扱われる。

EOFを返していないが、offsetが進められて再度呼び出された時にデータサイズ(n-offset)が0になり、データの終端に達したものとして正常に扱われる。

bufferは実際には物理ページが1ページ分渡されるため、i386系であれば4KBのサイズをもつ。countは(物理ページサイズ-1KB)か読み出し側

関連ページ

Loadable Kernel Moduleの作り方