debugfs
debugfsはprocfsのように、カーネル内部情報を参照するもので、本質機能の目的はprocfsと同じです。ただ実装の仕方がprocfsのような実装上の制約がなく、通常のファイルイメージでファイルを作成するように、ファイルを作成することができます。そしてファイル単位に、ファイルオペレーションコールバック関数を設定可能となります。
カーネルは/procのようにマウントまで行っていません。マウントはユーザランドの操作となり、従ってシステム依存になります。tools/perf/util/debugfs.cにデフォルトで
サンプルです。
カーネルは/procのようにマウントまで行っていません。マウントはユーザランドの操作となり、従ってシステム依存になります。tools/perf/util/debugfs.cにデフォルトで
static const char *debugfs_known_mountpoints[] = { "/sys/kernel/debug/", "/debug/", 0, };とされていて、通常は/sys/kernel/debugにマウントされています。
サンプルです。
#include <linux/kernel.h> #include <linux/sched.h> #include <linux/module.h> #include <linux/debugfs.h> #include <asm/uaccess.h> MODULE_LICENSE("GPL"); static struct dentry *hogehoge_root; static char hoge_buff[128]; static ssize_t debugfs_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos) { return simple_read_from_buffer(buf, len, ppos, hoge_buff, strlen(hoge_buff)); } static ssize_t debugfs_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) { return simple_write_to_buffer(hoge_buff, sizeof(hoge_buff), ppos, buf, len); } static struct file_operations hoge_fops = { .owner = THIS_MODULE, .read = debugfs_read, .write = debugfs_write, }; static int debugfs_init_module(void) { hogehoge_root = debugfs_create_dir("a-hogehoge", NULL); if (!hogehoge_root) return -ENOMEM; if (!debugfs_create_file("test", 0444, hogehoge_root, NULL, &hoge_fops)) return -ENOMEM; return 0; } static void debugfs_exit_module(void) { debugfs_remove_recursive(hogehoge_root); } module_init(debugfs_init_module); module_exit(debugfs_exit_module);実効結果
[root@localhost lkm]# insmod debugfs.ko [root@localhost lkm]# ls /sys/kernel/debug/ a-hogehoge boot_params extfrag kprobes sched_features usb wakeup_sources acpi cxgb4 gpio mce suspend_stats virtio-ports x86 bdi dynamic_debug hid regmap tracing vmmemctl [root@localhost lkm]# ls /sys/kernel/debug/a-hogehoge/ test [root@localhost lkm]# echo "abcde" > /sys/kernel/debug/a-hogehoge/test [root@localhost lkm]# cat /sys/kernel/debug/a-hogehoge/test abcdedebugfs_create_dir()は、debugfsにディレクトリを作成します。parentは作成する親ディレクトリです。NULLの場合、debugfsのroot(/sys/kernel/debug)に作成されます。
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent) { return debugfs_create_file(name, S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO, parent, NULL, NULL); }debugfs_create_file()は、ディレクトリ/ファイル/リンク作成のすべてにおいて、modeを該当値に設定することでコールされます。simple_pin_fs()はdebugfsのmnt構造体の参照カウンターをインクリメントし、debugfs_create_by_name()で対応するinodeおよびdentryを取得します。
struct dentry *debugfs_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops) { struct dentry *dentry = NULL; int error; pr_debug("debugfs: creating file '%s'\n",name); error = simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count); if (error) goto exit; error = debugfs_create_by_name(name, mode, parent, &dentry, data, fops); if (error) { dentry = NULL; simple_release_fs(&debugfs_mount, &debugfs_mount_count); goto exit; } exit: return dentry; }debugfs_create_by_name()でmodeに対応するdentryを取得します。
static int debugfs_create_by_name(const char *name, umode_t mode, struct dentry *parent, struct dentry **dentry, void *data, const struct file_operations *fops) { int error = 0; if (!parent) parent = debugfs_mount->mnt_root; *dentry = NULL; mutex_lock(&parent->d_inode->i_mutex); *dentry = lookup_one_len(name, parent, strlen(name)); if (!IS_ERR(*dentry)) { switch (mode & S_IFMT) { case S_IFDIR: error = debugfs_mkdir(parent->d_inode, *dentry, mode, data, fops); break; case S_IFLNK: error = debugfs_link(parent->d_inode, *dentry, mode, data, fops); break; default: error = debugfs_create(parent->d_inode, *dentry, mode, data, fops); break; } dput(*dentry); } else error = PTR_ERR(*dentry); mutex_unlock(&parent->d_inode->i_mutex); return error; }debugfs_get_inode()は、debugfs_mkdir()/debugfs_link()/debugfs_create()でinodeを取得する時にコールされます。この時ファイルの場合、inode->i_fop = fops ? fops : &debugfs_file_operationsとしています。debugfs_file_operationsは、何もしない関数です。
static struct inode *debugfs_get_inode(struct super_block *sb, umode_t mode, dev_t dev, void *data, const struct file_operations *fops) { struct inode *inode = new_inode(sb); if (inode) { inode->i_ino = get_next_ino(); inode->i_mode = mode; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; switch (mode & S_IFMT) { default: init_special_inode(inode, mode, dev); break; case S_IFREG: inode->i_fop = fops ? fops : &debugfs_file_operations; inode->i_private = data; break; case S_IFLNK: inode->i_op = &debugfs_link_operations; inode->i_fop = fops; inode->i_private = data; break; case S_IFDIR: inode->i_op = &simple_dir_inode_operations; inode->i_fop = fops ? fops : &simple_dir_operations; inode->i_private = data; /* directory inodes start off with i_nlink == 2 * (for "." entry) */ inc_nlink(inode); break; } } return inode; }ファイル毎のファイルオペレーションを定義していない場合、以下のコールバックが設定されます。
const struct file_operations debugfs_file_operations = { .read = default_read_file, .write = default_write_file, .open = default_open, .llseek = noop_llseek, }; static ssize_t default_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return 0; } static ssize_t default_write_file(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { return count; } static int default_open(struct inode *inode, struct file *file) { if (inode->i_private) file->private_data = inode->i_private; return 0; }
補足
kprobeでのdebugfsの利用例です。kprobeのkprobeサンプルをinsmodした時の内容です。[root@localhost kprobes]# pwd /sys/kernel/debug/kprobes [root@localhost kprobes]# ls enabled list [root@localhost kprobes]# cat list c0436270 k do_fork+0x0
実装(kprobes)
debugfs_kprobe_init()は、モジュール初期化時にコールされ(late_initcallマクロで宣言)、最初にdebugfs_create_dir()で、/sys/kernel/debug下にkprobesが作成され、debugfs_create_fill()で、その下にlistとenableのファイルが作成されます。そのファイルの参照は、debugfs_kprobes_operations/fops_kpオペレーションに記述する事で、kprobeでの内部情報をユーザプロセスとやり取りする事が可能となります。static const struct file_operations debugfs_kprobes_operations = { .open = kprobes_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int __kprobes debugfs_kprobe_init(void) { struct dentry *dir, *file; unsigned int value = 1; dir = debugfs_create_dir("kprobes", NULL); if (!dir) return -ENOMEM; file = debugfs_create_file("list", 0444, dir, NULL, &debugfs_kprobes_operations); if (!file) { debugfs_remove(dir); return -ENOMEM; } file = debugfs_create_file("enabled", 0600, dir, &value, &fops_kp); if (!file) { debugfs_remove(dir); return -ENOMEM; } return 0; } late_initcall(debugfs_kprobe_init);ちなみに、listの内容は以下の様に、kproobeのアドレス、kprobeタイプ(r:kretprobe j:kjprobe k:kprobe)、kprobeのシンボル+オフセット 設定モジュール)となっています。
[root@localhost kprobes]# cat list c0436270 k do_fork+0x0
static void __kprobes report_probe(struct seq_file *pi, struct kprobe *p, const char *sym, int offset, char *modname, struct kprobe *pp) { char *kprobe_type; if (p->pre_handler == pre_handler_kretprobe) kprobe_type = "r"; else if (p->pre_handler == setjmp_pre_handler) kprobe_type = "j"; else kprobe_type = "k"; if (sym) seq_printf(pi, "%p %s %s+0x%x %s ", p->addr, kprobe_type, sym, offset, (modname ? modname : " ")); else seq_printf(pi, "%p %s %p ", p->addr, kprobe_type, p->addr); if (!pp) pp = p; seq_printf(pi, "%s%s%s\n", (kprobe_gone(p) ? "[GONE]" : ""), ((kprobe_disabled(p) && !kprobe_gone(p)) ? "[DISABLED]" : ""), (kprobe_optimized(pp) ? "[OPTIMIZED]" : "")); }module_init/late_initcallマクロは、関数を.initcallセクションに配置するものですが、module_initマクロは6、late_initcallマクロは7と言う具合に、そのセクション内で順位を有しています。従って、late_initcallマクロで宣言された関数は、module_initマクロで宣言された関数の後にコールされる事になります。
#define module_init(x) __initcall(x); #define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall("6",fn,6) #define late_initcall(fn) __define_initcall("7",fn,7) #define __define_initcall(level,fn,id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn