Linuxなどのメモ書き

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


1. 概要

proc fs を使ったカーネル情報の表示ではproc fsを使用してカーネル内の情報を表示する方法を示した。この方法では、バッファに収まる小さいデータを出力するのに適していたが、大量のデータを出力するのは手間がかかる。ここでは、seq_file(*1)を使用してプロセス情報のようなレコード情報を大量に出力する方法を示す。seq_fileを使用することで、バッファの境界を気にすること無く大量の情報を出力できる。

(*1) seq_fileは複数のレコードをシーケンシャルに並べて1つのファイルとして扱うためのLinuxカーネルの機構。

2. サンプルモジュール

2.1 Source

以下サンプルは/proc/processを読むと、全プロセスの情報(task_structのアドレス、mm_structのアドレス、ページディレクトリの内容)を出力する。

MakefileとSource(proc.c)を同じディレクトリに置いてmakeするとseqfilemod.koができる。

ダウンロードはこちらから。

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


# obj-m にモジュール名を定義
obj-m := seqfilemod.o

seqfilemod-objs := proc.o

include $(KERNELSRCDIR)/.config

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


proc.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>
#include <linux/seq_file.h>

static int process_init_module(void);
static void process_cleanup_module(void);
static int procinfo_open(struct inode *, struct file *);
static void *s_start(struct seq_file *, loff_t *);
static void *s_next(struct seq_file *, void *, loff_t *);
static void s_stop(struct seq_file *, void *);
static int s_show(struct seq_file *, void *);

MODULE_DESCRIPTION("Process Information");
MODULE_AUTHOR("Kazuyoshi Tomita");
MODULE_LICENSE("GPL");

#define ENTRY_NAME      "process"

static struct proc_dir_entry *infop;

/* seq_file用 file_operations */
static struct file_operations proc_procinfo_operations = {
        .open           = procinfo_open,
        .read           = seq_read,
        .llseek         = seq_lseek,
        .release        = seq_release,
};

/* seq_file用のハンドラ */
struct seq_operations procinfo_op = {
        .start  = s_start,
        .next   = s_next,
        .stop   = s_stop,
        .show   = s_show,
};

static int process_init_module(void)
{
        /*
         * Create a proc file.
         */
        infop = (struct proc_dir_entry *)
                create_proc_entry(ENTRY_NAME, 0444, (struct proc_dir_entry *) 0);
        if(infop == 0) {
                return(-EINVAL);
        }

        infop->proc_fops = &proc_procinfo_operations;

        return(0);
}

/* Cleanup - undo whatever init_module did */
static void process_cleanup_module(void)
{

        remove_proc_entry( ENTRY_NAME, (struct proc_dir_entry *) 0);
}


static int procinfo_open(struct inode *inode, struct file *file)
{
        return seq_open(file, &procinfo_op);
}

static void *s_start(struct seq_file *m, loff_t *pos)
{
        struct task_struct *taskp;
        int i;

        if (*pos == 0) {
                seq_puts(m, "## Process Information ##\n");
        }

        read_lock(&tasklist_lock);

        taskp = &init_task;
        for (i = 0 ; i < *pos ; i++) {
                taskp = next_task(taskp);
                if (taskp == &init_task)
                        return NULL;
        }

        return taskp;
}

static void *s_next(struct seq_file *m, void *p, loff_t *pos)
{
        struct task_struct *taskp = (struct task_struct *) p;

        ++*pos;
        taskp = next_task(taskp);
        if (taskp == &init_task)
                taskp = NULL;

        return taskp;
}

static void s_stop(struct seq_file *m, void *p)
{
        read_unlock(&tasklist_lock);
}


static int s_show(struct seq_file *m, void *p)
{
        struct task_struct *taskp = (struct task_struct *) p;
        int i;

        seq_printf(m, "\ttask\t0x%08x\n", (unsigned int) taskp);
        seq_printf(m, "\tmm\t%08x\n", (unsigned int) taskp->mm);
        seq_printf(m, "\tactive\t%08x\n", (unsigned int) taskp->active_mm);

        if (taskp->active_mm) {
                pgd_t *pgd = taskp->active_mm->pgd;

                seq_printf(m, "\tpgd\t%08x\n",
                               (unsigned int) pgd);

                if (pgd) {
                        for (i = 768 ; i < 773 ; i+=4) {
                                seq_printf(m, "\t%08x %08x %08x %08x\n",
                                           (unsigned int) pgd[i].pgd,
                                           (unsigned int) pgd[i+1].pgd,
                                           (unsigned int) pgd[i+2].pgd,
                                           (unsigned int) pgd[i+3].pgd);
                        }
                }
        }
        seq_puts(m, "\n");

        return 0;
}


module_init(process_init_module);
module_exit(process_cleanup_module);

2.2 実行結果

作成したモジュールをロードして/proc/processを読みだした結果は以下のとおり。
# /sbin/insmod seqfilemod.ko
# more /proc/process
## Process Information ##
        task    0xc046cbe0
        mm      00000000
        active  f5eec080
        pgd     f4130000
        000001e3 004001e3 008001e3 00c001e3
        010001e3 014001e3 018001e3 01c001e3

        task    0xc1d4fa30
        mm      f7e94a80
        active  f7e94a80
        pgd     f7e98000
        000001e3 004001e3 008001e3 00c001e3
        010001e3 014001e3 018001e3 01c001e3
   <略>

3. 解説

3.1 ノードの作成

モジュール初期化時(process_init_module())にcreate_proc_entry()でproc fs上にファイルを作成するのは、「proc fs を使ったカーネル情報の表示」と同じ。

ファイル(/proc/process)を作成した後は、ファイルへアクセスした時のハンドラ群を登録する。これには、create_proc_entry()が返したstruct proc_dir_entryのproc_fopsに/proc/process用のstruct file_operationsを定義して登録すればよい。ここでは/proc/process用のstruct file_operationsとしてproc_procinfo_operationsを定義している。proc_procinfo_operationsは.openのみseq_fileをopenするための自前のルーチンprocinfo_open()を指定して後はseq_file.cが提供しているルーチンseq_read(), seq_lseek(), seq_release()を登録しておけばよい。

3.2 ファイルのオープン

/proc/processがopenされるとproc_fops.openに登録したprocinfo_open()が呼び出される。procinfo_open()はseq_open()でseq_fileをopenする。seq_open()では、seq_fileにアクセスした時のハンドラ群struct seq_operationsを登録する。struct seq_operationsに登録する各ハンドラの用途は表1のとおり。

表1 struct seq_operations のフィールド

フィールド
説明
start seq_fileの読みだしを開始する時に呼び出されるルーチンを登録する。
このルーチンでは、ヘッダ情報を出力したり、指定されたオフセットのレコードの頭出しを行う。
next
レコードを読み出し後、次のレコードを取り出すルーチンを登録する。
stop
seq_fileの読み込みが終了した時に呼び出されるルーチンを登録する。
フッタ情報などを出力できる。
show
レコードの情報を出力するルーチンを登録する。


3.3 seq_fileのハンドラ

seq_fileをopenした後は、struct seq_operationsに登録したルーチンが順次呼び出されてレコードが読み出されていく。

s_start()

レコード(ここでは、プロセス情報)の読み出しを開始する前(*1)に呼び出されるルーチン。読み出しを開始するオフセットが引数posで渡されるので、posに該当するレコードのポインタをリターンすると、そのレコードから読み出しが開始される。

プロセス(struct task_struct)はinit_taskリストにチェーンされているので、ここでは、pos分だけinit_taskを読み飛ばしてからtask_structのアドレスを返している。また、読み出し処理中にinit_taskリストが変更されないようにread lockを取っている。

表示開始位置が先頭(*pos == 0)の場合は、ヘッダ情報を出力している。seq_fileでは情報を出力する際、seq_puts()やseq_printf()を使用する。


s_show()

レコード情報の出力を行うルーチン。引数pで読み出すべきレコードのポインタ(ここではstruct task_struct *)が渡される。


s_next()

レコードを読み出した後、次のレコードを返すルーチン。このルーチンが返したレコードで次のs_show()が呼び出される。

引数pで読み出しが完了したレコードのポインタが渡されるので、そこから次のレコードをたどってリターンすればよい。もうレコードが無い場合は、NULLをリターンすれば呼び出しが終了する。


s_stop()

読み出しが完了(*1)すると呼び出される。読み出し開始時(s_start())において、何らかのロックを取っていたような場合はここで解放する。

(*1) 正確にはs_start()はseq_fileのReadハンドラであるseq_read()に入ってs_show()、s_next()のループに入る前に呼び出される。また、s_stop()はseq_read()からリターンする前に呼び出される。

関連ページ

Loadable Kernel Moduleの作り方
proc fs を使ったカーネル情報の表示


最終更新 2007/02/17 13:17:47 - kztomita
(2006/11/30 01:52:44 作成)


リンク

その他のWiki
Linuxメモ
Xnuメモ

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

よくやる仕事

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

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

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

検索

Adsense
最近のコメント