Pylone Blog - 2009年01月

seq_fileの使い方

Linuxカーネルが持つ、疑似ファイルの実装を補助する機構を紹介します。

カーネルからユーザ空間へデータを渡す手段としては、 /proc や /sys 以下に作成した疑似ファイルを使うことが多いでしょう。

渡したい値が単純な型の場合はカーネル組込みのヘルパ関数を使えば十分(sysfs 経由でモジュールパラメータにアクセスsysfs 経由でモジュールパラメータにアクセス (2))ですが、 より複雑なデータを渡すならば:

  • 疑似ファイルの内容となるテキスト量が大きい場合、カーネル空間に全体を保持たくない。
  • データ構造を操作に時間がかかる場合、ユーザ空間から要求された部分だけを処理することが望ましい。
  • 同時に複数の読み手が存在したり、読み出し途中でデータが変更される場合の排他が必要。

などの点を考慮するべきです。

本記事では、シーケンシャルな疑似ファイルを実装するために用意されている補助関数群について解説します。 カーネル内でも広く使われている機構なので、知っておくとコードを読む際にも役立つでしょう。

seq_file

VFSの層ではファイル中の位置はバイト単位で扱われます。固定長のバッファで扱える程度のデータ量ならよいのですが、 疑似ファイル内容を一括生成・保持できない場合、自力で出力済のバイト数管理やバイト単位でのシークを実装するのは手間がかかります。

fs/seq_file.cには出力したいデータが

  • 通し番号が付けられる(配列や木などの)要素の集合として表現できる
  • 要素の番号がわかれば、その要素の文字列表現が得られる

という条件を満たすとき、各要素へのアクセスをイテレータ操作として抽象化する仕組みが用意されています。

これを使うと、所定のアクセサを作成するだけで、VFSに登録する struct file_operations のメンバのほとんどをカーネルから提供される汎用関数でまかなうことができます。

使用例

サンプルとして、全ttyについてdebugfs上の疑似ファイル経由として termios状態を一覧するカーネルモジュールのソースtermios_dumper.cを用意しました。

個別のttyの状態取得ならtcgetattr()でも十分なのですが、システム上で 現在activeな全てのttyをリストするために、カーネル内部のデータ構造を直接読んでいます。

疑似ファイルの登録

seq_fileを使用する場合でも、ファイルシステムへの疑似ファイルの登録は通常のまま

  1. struct file_operationsを作成
  2. ファイルシステム毎の登録関数の呼び出し

という手順で行います。

ただし、VFSに渡すstruct file_operationsのうち、読みこみ可能な疑似ファイルを実装するために必要な

  • open
  • read
  • llseek
  • release

のうちread, llseek, releaseとしては、seq_fileで実装されているseq_read, seq_lseek, seq_releaseをそのまま使用できます。

.openに登録する関数では、

  • open()されたファイルに対して、seq_open()を呼んでハンドラを登録。
  • 必要ならリソースの確保/ロック

を行ないます。

サンプルでは登録先のファイルシステムをdebugfsとしたので、以下のようにしています。

static const struct file_operations fops = {
    .open    = termios_dumper_open,
    .read    = seq_read,
    .llseek  = seq_lseek,
    .release = seq_release,
};

...

static int __init termios_dumper_init(void)
{
...
    if(IS_ERR(fs_root = debugfs_create_dir(KBUILD_BASENAME, NULL)))
        return PTR_ERR(fs_root);

    if(IS_ERR(fs_file = debugfs_create_file("state", 0444, fs_root, NULL,
                                            &fops)))
        return PTR_ERR(fs_file);

この例では、特にリソースの初期化が必要ないため、.openに登録したtest_seqfile_openの実装はseq_open()の呼び出しだけです。

static int test_seqfile_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &seqfile_ops);
}

seq_operations

読み出し用のイテレータに必要な操作:

  • 初期化
  • 終了処理
  • 「次」の要素に移動
  • 値(文字列表現)の取得

は、

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);
};

の各メンバとして登録されます。

start

seq_file用のファイルがopen()された時に呼ばれます。

  • 使用するリソースの確保
  • イテレータの初期化

を行ない、成功した場合には初期化済みのイテレータ、失敗した場合はNULLを返すことになるでしょう。

引数*posが0より大きい場合には、その位置を指すようにしたイテレータが返却されるべきです。

※ イテレータの更新処理は、(pos)ではなく、(*pos)を使います。

イテレータを進める処理は、next()からの処理でも必要となるため、共通化してもよいでしょう。サンプルでは update_iterator()としています。

next

seq_fileが、現在のイテレータからのデータを消費し終えた時、 現在のイテレータと次に指すべき位置を引数として呼ばれます。

読み手のread()に対応していると考えてよいでしょう。 ただし、カーネルによる先読み処理の対象となるため、read()の度にかならず呼ばれるわけではありあせん。 また、読み手が小量のデータしが要求していなくても、さらに先まで要求されることがあります。

指定位置を指すイテレータを用意して、それへのポインタを返します。 この場合返却したポインタは後述するshow()への引数として使われます。

処理に失敗した場合はNULLを返却します。このとき、続いてstop()が呼ばれますが、stop()の引数にはNULLしか渡らないため、リソースの開放にイテレータが必要なら、next()内で済ませておく必要があります。

stop

読み手に十分なテキストが渡ったか、next()からデータがもうないことを通知した(NULLを返した)後に呼びだされます。

リソースの開放を行なうべきですが、引数として渡されるポインタはNULLの場合があることに注意が必要です。

show

引数として渡されたイテレータから、読み手に渡す(疑似ファイルの内容となる)文字列を構築します。

構築にはseq_printf(), seq_puts()などのヘルパ関数を使用します。これらのヘルパは繰替えし呼ぶこともでき(追記されます)、最終的な長さは自動的に管理されるので、成功時には0を返せば適切に処理されます。

ただし、一度に構築できる文字列の全長はPAGE_SIZE程度なので、長くなりすぎる場合はイテレータの構造を変える必要があるでしょう。

サンプルコードでは、現在のイテレータが指しているtermiosの情報を、seq_printf()経由でgrepしやすい書式に変換しています。

サンプルtermios_dumper.cについての補足

サンプルとして使用したコードtermios_dumper.cでは、カーネルから公開されていはいttyドライバのリストの先頭要素のアドレスを知るために、struct tty_driverのmagic要素を用いて検索していますが、これは低い確率ですが(メモリ中の値が偶然TTY_MAGICと一致したり、get_current_tty()が呼べない場合に)失敗する可能性のある処理です。 確実を期すなら、カーネルの公開シンボルにtty coreのtty_driversを追加するか、専用のアクセサを追加するべきでしょう。

また、ttyサブシステム内の構造体を辿る処理はttyのmutexを取得した上で行うべきなのですが、

  • tty_mutexを占有すると副作用が大きい(ロック中に新規にttyを開こうとした他のプロセス全てが停止するため)
  • 改造して失敗したときにシステムをロックしてしまう
  • 今回のサンプルでは読み出ししかおこなわないため、整合性が崩れても大きな問題にならない

ため、デモ目的では不要と判断してコメントアウトしてあります。 このため、疑似ファイルからの読出し途中でttyが増減すると、表示の一貫性が崩れることがあるでしょう。 同様の理由で、kobjectの参照数カウントについても省略しています。

Bishopバージョンアップのお知らせ

組込みLinux開発用CPUボードBishopに同梱されるソフトウェアのバージョンアップを実施いたします。

旧バージョン新バージョン
U-Boot1.2.0-pylone5 (変更なし)
Linuxカーネル2.6.22.1-pylone02.6.26.8-pylone0
ルートファイルシステムDebian GNU/Linux etch 4.0r3Debian GNU/Linux etch 4.0r5

2009年1月以降にご注文いただいた分から新バージョンにて出荷いたします。

既にご購入いただいたお客様へ

2008年12月までにご購入いただいたお客様につきましては、別途バージョンアップ手順をご案内いたします。

関連リンク