linux: seq_fileの使い方めも

sysfs、debugfs、procfs等でファイルを作ってユーザーランドからreadするときにseq_file構造体を使う方法が有るけど使ったことなかったのでめもです。

コード

こんな感じです。

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>

MODULE_DESCRIPTION("seq file test");
MODULE_AUTHOR("masami256");
MODULE_LICENSE("GPL");

#define SEQ_FILE_TEST_DATA_ARRAY_SIZE 8

struct seq_file_test_data {
    char s[16];
};

static struct seq_file_test_data data_array[SEQ_FILE_TEST_DATA_ARRAY_SIZE];

static struct dentry *seq_file_test_file;

static void *seq_file_get_next_pos(loff_t *pos)
{
    if (*pos > SEQ_FILE_TEST_DATA_ARRAY_SIZE)
        return NULL;

    return data_array + *pos;
}

static void *seq_file_test_start(struct seq_file *m, loff_t *pos)
{
    return seq_file_get_next_pos(pos);
}

static void seq_file_test_stop(struct seq_file *m, void *v)
{
}

static void *seq_file_test_next(struct seq_file *m, void *v, loff_t *pos)
{
    (*pos)++;

    return seq_file_get_next_pos(pos);
}

static int seq_file_test_show(struct seq_file *m, void *v)
{
    struct seq_file_test_data *p = v;

    seq_printf(m, "%s", p->s);
    return 0;
}

static struct seq_operations seq_file_test_seq_ops = {
    .start = seq_file_test_start,
    .stop = seq_file_test_stop,
    .next = seq_file_test_next,
    .show = seq_file_test_show,
};

static int seq_file_test_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &seq_file_test_seq_ops);
}

static struct file_operations seq_file_test_fops = {
    .owner = THIS_MODULE,
    .open = seq_file_test_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = seq_release,
};

static int seq_file_test_init(void)
{
    int i;
    pr_info("%s start\n", __func__);

    for (i = 0; i < SEQ_FILE_TEST_DATA_ARRAY_SIZE; i++) {
        struct seq_file_test_data *p = &data_array[i];
        sprintf(p->s, "test data %d\n", i);
    }

    seq_file_test_file = debugfs_create_file("seq_file_test", 0444,
                        NULL, NULL,
                        &seq_file_test_fops);

    if (IS_ERR(seq_file_test_file))
        return PTR_ERR(seq_file_test_file);

    return 0;
}

static void seq_file_test_exit(void)
{
    pr_info("%s bye\n", __func__);
    debugfs_remove(seq_file_test_file);
}

module_init(seq_file_test_init);
module_exit(seq_file_test_exit);

実行例

masami@kerntest:~/seq_file_test$ sudo cat /sys/kernel/debug/seq_file_test
test data 0
test data 1
test data 2
test data 3
test data 4
test data 5
test data 6
test data 7
masami@kerntest:~/seq_file_test$

動作の概要

seq_file構造体はinclude/linux/seq_file.hでこんな感じで定義されてます。

struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};

show()がデータを表示するための関数です。start()がファイル読み込み時に呼ばれて、データがあればデータのポインタを返します。続いてnext()が呼ばれていってNULLが返るまでは処理が続く感じです。 show()のほうは引数のvにstart()とかnext()で返したポインタが渡ってきます。なのでseq_file_test_show()ではseq_printf()で一つのデータの内容を表示させてるだけなのにcatはちゃんとすべてのデータが読めるってことですね。 ファイルを作成したときはfile_operations構造体でファイルのopen、readなどの関数を設定しますがseq_file構造体を使ってreadの処理を行う場合はopen()を行う関数の方でseq_open()を呼びます。seq_open()に渡すのはfile_operations構造体のopen()に渡されるfile構造体と、seq_file用のseq_operations構造体です。

分かってみればそんなに難しいことはないのでこれからは利用していきたいですね( ´∀`)bグッ!