SystemtapでSleep sort

なんか書かねばならぬ気がしたのでやってしまった。

probe begin {
	foreach(i in argv)
		system("sleep " . argv[i])
}

probe syscall.nanosleep.return {
	ctsk = task_current();
	ptsk = task_parent(ctsk);
	if (task_execname(ctsk) != "sleep" ||
	    task_execname(ptsk) != "stapio")
		next;
	println($rqtp->tv_sec);
}
  1. コマンドライン引数をうけとってsleepを呼び出す。
  2. sleepはnanosleepシステムコールを呼び出すので、その終了時をフック。
  3. 一応実行コマンド名と、親コマンド名があってるかどうかチェック(適当ですね)。
  4. 引数の秒数部分を表示(本当はちゃんとユーザ空間アクセスチェックをすべき(笑))。

実行するとこんな感じ。

# stap sleepsort.stp 2 4 1 3  
1
2
3
4
^CToo many interrupts received, exiting.

最後自分でCtrl+Cを押す必要がある。最大値でもとっておけばいいんだろうけどねー。
やはり 「sleep」 sortなので、sleepを呼び出すところは譲れない。

fizzbuzzっぽいもの

これだけだと面白くないので、fizzbuzzモードを追加した

global fillstr;
global candidate;
global readpos;

%{
#include <linux/string.h>
%}

function fizzbuzz:string (num:long) {
        s = ""
        if (num % 3 == 0)
                s = "fizz"
        if (num % 5 == 0)
                s = s . "buzz"
        return s
}

function basename:string (path:string) %{
        const char *p, *s = (void *)(unsigned long)THIS->path;
        p = strrchr(s, '/');
        if (!p)
                p = s;
        else
                p++;
        if (p)
                strlcpy(THIS->__retvalue, p, MAXSTRINGLEN);
%}

function write_string (buf:long, str:string, len:long, pos:long) %{
        char *buf = (void *)(unsigned long)THIS->buf;
        int64_t len = THIS->len,  slen = strlen(THIS->str);
        int64_t cnt, pos = THIS->pos;

        if (pos % slen) {
                memcpy(buf, (const char *)THIS->str + (pos % slen),
                         slen - (pos % slen));
                buf += slen - (pos % slen);
                len -= slen - (pos % slen);
        }
        for (cnt = 0; (cnt + 1) * slen < len; cnt++)
                strlcpy(buf + (cnt * slen), THIS->str, slen + 1);
        memcpy(buf + (cnt * slen), THIS->str, len - cnt * slen);
%}

probe syscall.open {
        candidate[pid()] = basename(filename)
}

probe kernel.function("memory_open") {
        fillstr[pid()] = candidate[pid()]
}

probe kernel.function("read_zero").return {
        if (fillstr[pid()] == "zero")
                next
        len = $return
        str = fillstr[pid()]
        if (str == "fizzbuzz") {
                str = fizzbuzz(pid());
                printf("fizzbuzz: %d, %s\n", pid(), str)
                if (str == "")
                        next
        }
        if (len > 0) {
                write_string($buf, str, len, readpos[tid()])
                readpos[tid()] += len;
        }
}

リンク先のファイル名がfizzbuzzの場合、fizzbuzzモードに入る。fizzbuzzモードでは、PIDが3の倍数ならfizz fill、5の倍数ならbuzz fill、15の倍数ならfizzbuzz fill、それ以外はzero fillするというもの。
ちなみにSystemTapでは"."が文字列結合演算になるが、これを利用している例というのはほとんど見たことがないぐらいマイナーだ。たぶん、初めにこの演算を定義した本人も悔やんでいると思う。というのは、大体の場合、この演算子は以下のように間違って使われてエラーになり、ユーザが迷惑する仕様になっている。

 var = $struct.member

ただしくは、以下のようにどのような場合でもアロー演算子を使うのがSystemTap流。

 var = $struct->member

perf probeではこの教訓を生かし、Cと同じく.と->を分けている。何事も他山の石だと思う。
短いがこのあたりでさらば。

/dev/zeroに対抗して/dev/0を作る

読み出したら zero fillならぬ0 fillをするデバイスを作る。とはいっても、デバイスファイルの登録とか面倒だし、SystemTapでちょっと小細工をする。
出来たのが以下。

global fillstr;
global candidate;
global readpos;

%{
#include <linux/string.h>
%}

function basename:string (path:string) %{
        const char *p, *s = (void *)(unsigned long)THIS->path;
        p = strrchr(s, '/');
        if (!p)
                p = s;
        else
                p++;
        if (p)
                strlcpy(THIS->__retvalue, p, MAXSTRINGLEN);
%}

function write_string (buf:long, str:string, len:long, pos:long) %{
        char *buf = (void *)(unsigned long)THIS->buf;
        int64_t len = THIS->len,  slen = strlen(THIS->str);
        int64_t cnt, pos = THIS->pos;

        if (pos % slen) {
                memcpy(buf, (const char *)THIS->str + (pos % slen),
                         slen - (pos % slen));
                buf += slen - (pos % slen);
                len -= slen - (pos % slen);
        }
        for (cnt = 0; (cnt + 1) * slen < len; cnt++)
                strlcpy(buf + (cnt * slen), THIS->str, slen + 1);
        memcpy(buf + (cnt * slen), THIS->str, len - cnt * slen);
%}

probe syscall.open {
        candidate[pid()] = basename(filename)
}

probe kernel.function("memory_open") {
        fillstr[pid()] = candidate[pid()]
}

probe kernel.function("read_zero").return {
        if (fillstr[pid()] == "zero")
                next
        len = $return
        if (len > 0) {
                write_string($buf, fillstr[pid()], len, readpos[tid()])
                readpos[tid()] += len;
        }
}

使い方は、このスクリプトをGuruモードで走らせてから/dev/zeroから/dev/0へSymbolic linkを張るだけ。

stap -gF zerohook.stp
ln -s /dev/zero /dev/0
less /dev/0

適当に長いbasenameでも使えるし、/devにある必要すらないので、ln -s /dev/zero hogehugaとかするとhogehuga fillされたデータが出てくる。
基本アイデアは、system callのopenをフックしてbasenameを取得、次に/dev/zeroの処理をフックして、渡されたバッファを先ほどのbasenameで埋め尽くすというもの。
工夫というほどでもないが、単にフックするだけだと他のプロセスやスレッドのopenと重なる場合があるので、tidごとに変数を分けるようにしている。

stapgamesをgithubに移したよ

すっかり放置していたstapgamesをgithubに移すことにした。
いろいろ用意を整えて、さあ移行するか!と思ったら、・・・subversionから直接インポートできるのか。(´・ω・`)
とりあえず、https://github.com/mhiramat/stapgamesで公開しておきました。まだ最新のsystemtap対応とかしてないですが・・・。

djprobeマージ

V10にしてようやくマージされた。長かった。これからまだしばらくは安定化が必要だけど、とりあえず一区切りついた。
今回のパッチその物は去年の6月からだから10ヶ月モノだ。
だが待ってほしい。Djprobeその物についてはOLS2007で発表している。もちろん発表の前に構想があり、試作があった。それはいつか。なんと2005年だった。
つまりdjprobeの試作は、2005年にkprobesがマージされた直後から始まっているわけで、実のところプロジェクトとしてみたら5年かかったわけだ。その間にいろいろと実装は変わった*1ものの、x86上での基本的な仕組みはほとんど変わっていない。当時の説明スライドがそのまま使えるのではないかと思うぐらいだ。

さて、次はperf probeの機能追加を考えねば。

*1:実際にはx86の命令デコーダがついたり、rip相対命令を含めたx86-64をサポートしたり、kprobesを自動的に最適化するようになったりしたために、関連コードを含めるとかなりの規模になった。

Xperia X10 mini / QUE / Ideapad U1

気になるなあ。

Xperia mini

これだけ小さいと、PVにあるように、スマートフォンなのに普通の携帯の延長の使い方ができる。スマートフォン=アプリを使う、ブラウズする、というPCを取り込んだような使い方から、一周して普通の携帯のように日常生活の補助をする、という存在になりうるんじゃないかと思う。ネックはスマートフォンの料金モデルに乗せないといけない所かなあ。AppleiPhone miniとか出すと変わるかも。

QUE

もっと安ければ・・・。せめて200ドルなら、印刷機の置き換えになるはず。会社とかでペーパレスに出来ないのは、印刷してみんなで読んだりチェックしたりする資料が多いからだと思うんだけど、こういうデバイスがあれば、メモも出来るし印刷の時間も不要になる(仕様書100ページレビューで輪転機回すとか時間と金がかかりすぎる)。生産性向上のために導入したいなあ。

Ideapad U1

製品化したら買いたい。こういうキワモノっぽいのが好きすぎる。本体でカーネルコンパイルしながらスレートの方でメール書いたりしたい。いかんせん本体がWindowsでスレートが独自Linuxというのがいけてない所だけど、速攻でFedoraAndroidに置き換えたい。