proc fs を使ったカーネル情報の表示 その2
Rev.12を表示中。最新版はこちら。
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するとprocmod.koができる。
KERNELSRCDIR = /usr/src/linux BUILD_DIR := $(shell pwd) VERBOSE = 0 # obj-m にモジュール名を定義 obj-m := procmod.o procmod-objs := proc.o include $(KERNELSRCDIR)/.config all: make -C $(KERNELSRCDIR) SUBDIRS=$(BUILD_DIR) \ KBUILD_VERBOSE=$(VERBOSE) modules
#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 procmod.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()レコード(ここでは、プロセス情報)の読み出しを開始する前に呼び出されるルーチン。読み出しを開始するオフセットが引数posで渡されるので、posに該当するレコードのポインタをリターンすると、そのレコードから読み出しが開始される。
プロセス(struct task_struct)はinit_taskリストにチェーンされているので、ここでは、pos分だけinit_taskを読み飛ばしてからtask_structのアドレスを返している。
表示開始位置が先頭(*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())において、何らかのロックを取っていたような場合はここで解放できる。
関連ページ
Loadable Kernel Moduleの作り方proc fs を使ったカーネル情報の表示