TinySCHEME のソースを読む -3 TinySCHEME の処理エンジン

TinySCHEME のソースを読む -1 セルの構造
TinySCHEME のソースを読む -2 シンボル、環境
TinySCHEME のソースを読む -3 TinySCHEME の処理エンジン
TinySCHEME のソースを読む -4 トップレベルからの処理の流れ
TinySCHEME のソースを読む -5 define の例
tsdbg TinyScheme 用デバッグ extension

TinySCHEME の処理ロジック

TinySCHEME は再帰というものを一切用いず、独自の処理ロジックを持つ。

Eval_Cycle

main() から初期化処理を経ると、TinySCHEME の中心的な処理エンジンである Eval_Cycle() が呼ばれる。
この関数は無限ループになっており、オペレーションコード sc->op に従って処理を呼び出す構造になっている。

4305 行目から Eval_Cycle() のソースが定義されている。以下は Eval_Cycle() の宣言である:

    static void Eval_Cycle(scheme *sc, enum scheme_opcodes op);

この関数は、まず sc->op を引数 op で初期化する。

次に for(;;) でループに入り、sc->op を評価する。そして、sc->op 毎に定義された処理(以後オペレーションと呼ぶ)、へ飛ぶ。
各オペレーションは、それぞれ決まった処理をこなし、T か NIL を返す。
オペレーションから T が返る場合、Eval_Cycle() はループに戻り、処理を続ける。この場合、オペレーションは sc->op を更新していることが多い。
オペレーションから NIL が返った場合、Eval_Cycle() はループを抜け、終了する。

sc->op は、4308 行目で定義された pcd の初期化に使われる:

    op_code_info *pcd = dispatch_table + sc->op;

op_code_info の定義は以下である:

    typedef pointer (*dispatch_func) (scheme *, enum scheme_opcodes);

    typedef struct {
        dispatch_func func;
        char *name;
            ...     // 略
    } op_code_info;

4367 行目で、オペレーション func() を呼び出す:

    if (pcd->func(sc, (enum scheme_opcodes)sc->op) == sc->NIL) {

func() を呼び出すまでに行っている処理があるが、これは引数のエラーチェックである。


オペレーションで使われる特別な関数 / マクロ

オペレーションは特別な関数やマクロを使って処理の継続や終了を行う。

s_goto()

s_goto() は、引数で渡されるオペレーションに強制的にジャンプするものである。
このマクロは 2319 行目に定義がある:

    #define BEGIN     do {
    #define END  } while (0)
    #define s_goto(sc, a) BEGIN     \
                sc->op = (int)(a);  \
                return sc->T; END

sc->op を更新し、sc->T を返すことで、Eval_Cycle() に戻り、次の sc->op の処理へ飛ぶ。

s_save()

s_save() はある時点での状態を保存するものである。
s_save() は 2438 行目で定義されている関数である:

    static void s_save(scheme *sc, enum scheme_opcodes op, pointer args, pointer code) {
        sc->dump = cons(sc, sc->envir, cons(sc, (code), sc->dump));
        sc->dump = cons(sc, (args), sc->dump);
        sc->dump = cons(sc, mk_integer(sc, (long)(op)), sc->dump);
    }

TinySCHEME は dump というセルを使い、スタックフレームのようなものを実現している。
dump には、op コード、引数を表す args、s_save() が呼ばれた時点の環境 envir、処理する手続きを表す code、そして、以前の dump がリストとして含まれる。
dump に積まれた情報は、s_return() で復元される。

s_return()

s_return() はオペレーションを終了する場合に呼び出される。
直前の s_save() で保存されたデータを復元する処理を行う。
2323 行目と 2427 行目に定義されている。

    #define s_return(sc, a) return _s_return(sc, a)

    static pointer _s_return(scheme *sc, pointer a) {
        sc->value = (a);
        if (sc->dump == sc->NIL) return sc->NIL;
        sc->op = ivalue(car(sc->dump));
        sc->args = cadr(sc->dump);
        sc->envir = caddr(sc->dump);
        sc->code = cadddr(sc->dump);
        sc->dump = cddddr(sc->dump);
        return sc->T;
    }

ここでは、s_save() で保存されたデータ以外に、sc->value という変数が出てくる。
これはオペレーションの戻り値ないし処理結果という認識で良い。
s_return() は、見て分かる通り、dump にデータが積まれていればそれを復元し sc->T を返すので、結果的に Eval_Cycle に戻り、復元した sc->op へ飛ぶ。
dump が無い場合は NIL を返すため、Eval_Cycle() は終了する。

s_return() には return というキーワードが含まれているが、感覚的には return and call としたほうが、しっくりくる。
s_return() は、自分を呼び出した元に戻るわけでは無い。
s_save() によって登録ないし予約された処理を、これから呼び出すのである。


syntax

syntax は if や define, lambda などを表すセルで、4379 行目 assign_syntax() で作られる。

static void assign_syntax(scheme *sc, char *name) {
    pointer x;

    x = oblist_add_by_name(sc, name);
    typeflag(x) |= T_SYNTAX;
}

syntax は、car 部に syntax 名 ( string )、cdr に NIL を指した、コンスセル一つだけで表現される。このコンスセルには T_SYMBOL | T_SYNTAX フラグが付く。
各 syntax のオペレーションコードは、4405 行目 syntaxnum() で返される。

TinySCHEME のソースを読む -2 シンボル、環境

TinySCHEME のソースを読む -1 セルの構造
TinySCHEME のソースを読む -2 シンボル、環境
TinySCHEME のソースを読む -3 TinySCHEME の処理エンジン
TinySCHEME のソースを読む -4 トップレベルからの処理の流れ
TinySCHEME のソースを読む -5 define の例
tsdbg TinyScheme 用デバッグ extension

Symbol をどう表現するか

Symbol は、T_SYMBOL を _flag にしたセルと、T_STRING を _flag にしたセルの、二つのセルで表現される。T_SYMBOL セルの car 部が、T_STRING セルを指すようになっている。( 尚、T_SYMBOL セルの cdr 部は使われない。)

ただ、この表記は冗長なので、以後、セルを図で表す場合は以下のように省略して表記する:

TinySCHEME は、シンボル名を oblist (後述する) という hash テーブルで管理する。
また、T_SYMBOL セル自体は、シンボルの値というものを持たない。シンボルの値は、環境を表現する envir というセルが管理する。envir についても後述する。


oblist, vector

oblist シンボル名を管理するための hash テーブルである。実際は vector を使っており、同じ hash 値のものはリストになる。vector の要素数は 461 で固定され、増えることは無い。
vector は 1005 行目 mk_vector() によって作られる。vector は連続したセルを配列のように使い、全部で 1 + 要素数 / 2 のセルを使う。
先頭のセルは T_VECTOR セルで、要素数を数字で持つ。先頭のセル以降は、vector に格納するデータを"指す"ために使われる。これらのデータを指すために、car 部と cdr 部をそれぞれ使うことが出来るので、データのために vector が使うセルは要素数 / 2 で済む。

my-sum と name は共に hash 値が 4 で、oblist に登録されている例:

oblist に対するインターフェースは、(初期化などを除くと)以下の二つである:

    pointer oblist_add_by_name(scheme *sc, const char *name);
    pointer oblist_find_by_name(scheme *sc, const char *name);

環境, envir

TinySCHEME は環境を sc->envir で表現する。sc->envir は、現在の環境を示す T_ENVIRONMENT のセルと、その環境に属する slot のリストから成る。slot とは、シンボルとシンボルの値をそれぞれ car 部/ cdr 部に持つ cons セルである。
T_ENVIRONMENT のセルの car 部には slot のリストが格納され、envir の cdr 部には直前の環境が入る。
以下の例は、sc->envir の指す環境に、(define name "hal") と (define number 666) で定義された slog が含まれていることを示す:

global_env はグローバル環境を表す hash テーブルである。構造は oblist と全く同一である。


envir と oblist の関係

envir と oblist の関係を図にすると、以下のようになる。
この例では、二つの異なる環境に x が定義され、それぞれ値が違うが指しているシンボルは一つである:

envir に対するインターフェース

global_env が初期化されるのは、4594 行目である:

    new_frame_in_env(sc, sc->NIL);
    sc->global_env = sc->envir;

new_frame_in_env() は、二つ目の引数が NIL の場合、要素数 461 の vector を作り、vector のセルを car 部にした cons セルを作り、envir に代入する。envir に対するインターフェースは以下である:

    static void new_frame_in_env( scheme *sc, pointer old_env );
        // 新しい環境を作り、envir に代入する。
        // old_env が NIL の場合は envir の初期化
        // そうでない場合、old_env が新しい envir の cdr 部へ入る

    static INLINE void new_slot_spec_in_env( scheme *sc, pointer env, pointer variable, pointer value );
        // 環境 env に、シンボル variable と値 value から成る slot を作り、追加する

    static INLINE void new_slot_in_env( scheme *sc, pointer variable, pointer value );
        // new_slot_spec_in_env() のラッパー

    static pointer find_slot_in_env( scheme *sc, pointer env, pointer hdl, int all );
        // 環境 env から、シンボル hdl を探して slot を返す。all が 1 の場合は、env をさかのぼって検索を行う
        // シンボル hdl が環境 env に無い場合は NIL が戻る

    static INLINE void set_slot_in_env( scheme *sc, pointer slot, pointer value );
        // slot に value を代入する
        // この関数は、find_slot_in_env() で slot を取得してから、セットで呼ばれる

    static INLINE pointer slot_value_in_env( pointer slot );
        // slot の値を返す

TinySCHEME のソースを読む -1 セルの構造

TinySCHEMEscheme 実装の一つで、コード量は 4千行程度である。R5RS の大部分の仕様が実装されており、学習のためにソースを読む機会があったので記録しておく。

TinySCHEME のソースはインデントが統一されていなかったり、数字と演算子の間の空白が極端に少なかったりと、可読性はあまり高いとは言えないので、私は整形してから読んだ。
文中、ファイル名を明示せず行数が出てくるものは全て scheme.c のものである。それ以外のファイルについてはファイル名を明示してある。

TinySCHEME のソースを読む -1 セルの構造
TinySCHEME のソースを読む -2 シンボル、環境
TinySCHEME のソースを読む -3 TinySCHEME の処理エンジン
TinySCHEME のソースを読む -4 トップレベルからの処理の流れ
TinySCHEME のソースを読む -5 define の例
tsdbg TinyScheme 用デバッグ extension

セル

ischeme-private.h の 44 行目からセルの定義がある。

struct cell {
    unsigned int _flag;
    union {
        struct {
            char   *_svalue;
            int   _length;
        } _string;
        num _number;
        port *_port;
        foreign_func _ff;
        struct {
            struct cell *_car;
            struct cell *_cdr;
        } _cons;
    } _object;
};

_flag により、_object ユニオンのどの要素を使うかが決定される。

_flag には、110 行目から定義がある enum scheme_types と:

enum scheme_types {
    T_STRING=1,
    T_NUMBER=2,
    T_SYMBOL=3,
    T_PROC=4,
    T_PAIR=5,
    T_CLOSURE=6,
    T_CONTINUATION=7,
    T_FOREIGN=8,
    T_CHARACTER=9,
    T_PORT=10,
    T_VECTOR=11,
    T_MACRO=12,
    T_PROMISE=13,
    T_ENVIRONMENT=14,
    T_LAST_SYSTEM_TYPE=14
};

132 行目から define されている以下のマクロが入る:

    #define T_SYNTAX      4096
    #define T_IMMUTABLE   8192
    #define T_ATOM       16384

主要なフラグで、かつ T_STRING のようにあからさまに意味が明白なものを省いて簡単に説明すると、
T_PROC は opdefines.h で定義されているオペレーションで、かつ名前を持つものに対して付与される。round, +, -, *, /, car, cdr, cons などである。
T_PAIR はコンスセルを表す。
T_CLOSURE は、コード(プログラム)と環境がそれぞれ car, cdr に格納されたセルを表す。ユーザ定義関数や lambda 式は T_CLOSURE で表される。
T_CONTINUATION は継続を表すものだろうが良く調査していない。
T_FOREIGN は外部ライブラリで定義された関数に対して付与される。(TinySCHEME に外部ライブラリを読み込む機能があり、C で書いた処理を scheme から呼び出すことが出来る)
T_PROMISE については未調査、
T_ENVIRONMENT は環境を表す。

T_SYNTAX は if や define, lambda などのシンボルに対して付加される。
T_IMMUTABLE は変更不可能なセルに対して付加される。
T_ATOM は、そのままの意味で、アトムのセルに対して付加される。

尚、cell へのポインタとして、以下の型が定義されている:

    typedef struct cell *pointer;

セルの確保

557 行目の alloc_cellseg() で確保される。セルは 5000 個の連続したセルを一つの単位とし、確保される。この5000個のセルの単位をセグメントという。
セグメントは cell_seg という配列で管理される。起動直後、セグメントは 3 つ (= 15,000セル)用意されるが、必要に応じて最大 10 個まで拡張される。

新しいセルが必要な場合、745 行目の get_cell() が呼ばれ、まだ利用されていないセルを返す。

もしセグメントに十分なセルが無い場合、get_cell() はガベージコレクションを試みるが、それでも足りなさそうな場合( gc を行っても、現在のセグメント数 * 8 より、空きセルが少ない場合)は新たなセグメントが allocate され、cell_seg に代入される。
cell_seg[] に代入される各セグメントは、アドレス順にソートされる。これは、TinySCHEME の vector が連続したセル領域を(配列として、場合によってはセグメントをまたいで)必要とするためである。

tsdbg TinyScheme 用デバッグ extension

TinySCHEME のソースを読む機会があったので、内部のセルがどうなっているか調べるために extension を作った。

http://sourceforge.net/projects/tsdbg/
git clone git://git.code.sf.net/p/tsdbg/code tsdbg-code

セルを再帰的に表示したり、graphviz dot ファイルへ変換したりすることが出来る。

ts> (load-extension "tsdbg/tsdbg")
ts >(define counter
        (let ((c 0))
                (lambda ()
                        (set! c (+ c 1))
                        c )))
counter
ts> (cell-to-dot counter "counter.dot")
#t
ts> (quit)

$ dot counter.dot -Tpng -o counter.png

得られる画像はこんな感じ:

cx_Oracle で python から Oracle に接続する

SQL*Plus があまりにも使いにくいので、cx_Oracle を使って python から、別のサーバにある Oracle DB へ接続する環境を整えたのでその時のメモ。最初は Cygwin 環境に作ろうとしたが何故かうまくいかず、結局 VMWare を入れて環境を作ることにした。
使用環境は VMWare Player + Ubuntu 10.04 Desktop と Python 3.1, Oracle XE 10g

def __mopemope__(self, *args, **kwargs)プログラマの実態を大いに参考にさせて頂いた。感謝。

まず OracleXE をインストールする。インストールがうまくいけば、アプリケーションのメニューに Oracle Database 10g Express Edition のメニューが追加されているはずだ。「SQL コマンドラインの実行」をクリックすると SQL*Plus が起動するので、DB サーバに接続出来るか確認しておく。

つぎに cx_Oralce のインストール。linux 向けの cx_Oracle のバイナリは .rpm でしか提供されていないようで、alien コマンドを使い .deb ファイルへの変換を試みたのだが、

hal@ubuntu-vm:~/dist$ sudo alien cx_Oracle-5.1-10g-py26-1.i386.rpm 
[sudo] password for hal: 
error: incorrect format: unknown tag

というエラーが出てしまったので、ソースからビルドすることにした。

cx_Oracleから、現時点で最新版の 5.1 のソースをダウンロードする

解凍したあとは、環境変数を設定して、ビルドするだけ。

$ export ORACLE_HOME=/usr/lib/oracle/xe/app/oracle/product/10.2.0/server
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib
$ tar xvfz cx_Oracle-5.1.tar.gz 
$ cd cx_Oracle-5.1
$ python setup.py build
$ sudo env ORACLE_HOME=$ORACLE_HOME python setup.py install

setup.py が、Oracle のバージョンまでチェックしてくれる。
インストールがうまくいけば、site-packages の下に cx_Oracle.so が出来てるはず:

hal@ubuntu-vm:/usr/lib/python3.1/site-packages$ ls -ltr
合計 276
-rwxr-xr-x 1 root root 273240 2011-03-20 08:29 cx_Oracle.so
-rw-r--r-- 1 root root    896 2011-03-20 08:29 cx_Oracle-5.1-py3.1.egg-info

尚、ビルドにあたり、 G.Akahane さんの「プログラマの実態」にあったパッチは必要無かった。(cx_Oracle のバージョンが違うためだと思われる)

cx_Oracle を使ったサンプル:

import sys
import cx_Oracle

def show(cur):
	while 1:
		line = cur.fetchone()
		if line is None:
			break
		print( line )

#
connection = cx_Oracle.connect("user/pass@hostname:port/sid")
cur = connection.cursor()

needCommit = False

for query in sys.stdin:
	print( query )
	cur.execute(query)
	if query.upper().startswith("SELECT"):
		show(cur)
	else:
		needCommit = True

if needCommit:
    connection.commit()

cur.close()
connection.close()

標準入力からクエリを受け付けるので、シェルプロと組み合わせることで、かなり仕事が楽になる。はずだ。


これでとりあえず動く環境は出来たが、DB に入っている日本語のデータが文字化けしてうまく出ない。
この問題は時間があれば調べてみる予定。

Qt Embedded を DirectFB で動かす

仕事の都合上、Qt の移植作業が中途半端な状態でストップしてしまう。途中経過をメモ。
ARM Omap3 のボードに DirectFB を移植し、Qt をその上で動かす、というもの。

1. ソースをダウンロード
http://qt.nokia.com/downloads/embedded-linux-cpp
http://get.qt.nokia.com/qt/source/qt-everywhere-opensource-src-4.7.3.tar.gz

(*) インストールのドキュメントがあるが、ほとんど役に立たない
http://doc.qt.nokia.com/4.7/qt-embedded-install.html


2. ライセンス
商用ライセンスは使わないので無視


3. configure
DirectFB 向けに configure したい。とりあえず以下で configure した:

ARM_INSTALL_DIR=$HOME/xxx/install \
PKG_CONFIG_PATH=$HOME/xxx/install/lib/pkgconfig \
PKG_CONFIG_LIBDIR=$HOME/xxx/install/lib/pkgconfig \
CFG_IPV6=no \
../qt-everywhere-opensource-src-4.7.3/configure \
 -prefix /home/hal/xxx/install \
 -embedded arm \
 -xplatform qws/linux-arm-gnueabi-g++ \
 -little-endian \
 -host-little-endian \
 -force-pkg-config \
 -qt-gfx-directfb \
 -system-zlib \
 -system-libpng \
 -system-libjpeg \
 -no-multimedia \
 -no-qt3support \
 -no-svg \
 -no-webkit \
 -no-script \
 -no-scripttools \
 -no-xmlpatterns \
 -no-sql-db2 \
 -no-sql-ibase \
 -no-sql-mysql \
 -no-sql-oci \
 -no-sql-odbc \
 -no-sql-psql \
 -no-sql-sqlite \
 -no-sql-sqlite2 \
 -no-sql-sqlite_symbian \
 -no-sql-tds \
 -no-audio-backend \
 -no-phonon \
 -no-javascript-jit \
 -no-declarative \
 -no-declarative-debug \
 -debug

configure な主要なポイント:

 -embedded arm \
 -xplatform qws/linux-arm-gnueabi-g++ \

-embedded を指定しないとそもそも DirectFB を使えない。
-xplatform は、コンパイルオプションなどを記載する qt-everywhere-opensource-src-4.7.3/mkspecs/ 以下のパスを指すようにする。qmake.conf は以下:

#
# qmake configuration for building with arm-none-linux-gnueabi-g++
#

include(../../common/g++.conf)
include(../../common/linux.conf)
include(../../common/qws.conf)

# modifications to g++.conf
QMAKE_CC                = arm-none-linux-gnueabi-gcc
QMAKE_CXX               = arm-none-linux-gnueabi-g++
QMAKE_LINK              = arm-none-linux-gnueabi-g++
QMAKE_LINK_SHLIB        = arm-none-linux-gnueabi-g++

# modifications to linux.conf
QMAKE_AR                = arm-none-linux-gnueabi-ar cqs
QMAKE_OBJCOPY           = arm-none-linux-gnueabi-objcopy
QMAKE_STRIP             = arm-none-linux-gnueabi-strip

QMAKE_CFLAGS="-mcpu=cortex-a8 -mfpu=neon -I$(ARM_INSTALL_DIR)/include"
QMAKE_CXXFLAGS="-mcpu=cortex-a8 -mfpu=neon -I$(ARM_INSTALL_DIR)/include"
QMAKE_LFLAGS="-L$(ARM_INSTALL_DIR)/lib"

load(qt_config)

QMAKE_CFLAGS で与えているコンパイルオプションは正しく無いので参考にしないこと。ちゃんとした調査が必要だが、以下のような感じになると思われる:

"-march=armv7-a -mtune=cortex-a8 -mfpu=neon -ftree-vectorize -mfloat-abi=softfp"

configure の続きで、

 -qt-gfx-directfb \

これで DirectFB が有効になるはず。

また、-no-xxx で機能を大量に抑制しているが、これは単にコンパイル速度を早くしたいためである。まだ configure のオプションや、後述するコンパイルオプションなど、見直したい箇所が多い。

configure を実行してみて、期待通りの設定が受理されたか確認するにはログを見る必要があるが、qt の configure は、どうもログを残さない。もしかしたら残しているのかもしれないが、お決まりの configure.log という名前では残さない。とりあえずコンソールに表示される以下のメッセージで、 configure がうまくいっているか目で確認する:

Building on:   qws/linux-x86-g++
Building for:  qws/linux-arm-gnueabi-g++
Architecture:  arm
Host architecture: i386

Build .................. libs examples demos docs translations
Configuration ..........  cross_compile debug shared dll embedded largefile stl
precompile_header exceptions_off  minimal-config small-config medium-config larg
e-config full-config accessibility embedded reduce_exports ipv6 clock-gettime cl
ock-monotonic mremap getaddrinfo ipv6ifname getifaddrs inotify system-jpeg syste
m-png png freetype system-zlib nis debug
Debug .................. no
Qt 3 compatibility ..... no
QtDBus module .......... no
QtConcurrent code ...... yes
QtGui module ........... yes
QtScript module ........ no
QtScriptTools module ... no
QtXmlPatterns module ... no
Phonon module .......... no
Multimedia module ...... no
SVG module ............. no
WebKit module .......... no
Declarative module ..... no
Declarative debugging ...no
Support for S60 ........ no
Symbian DEF files ...... no
STL support ............ yes
PCH support ............ yes
MMX/3DNOW/SSE/SSE2/SSE3. no/no/no/no/no
SSSE3/SSE4.1/SSE4.2..... no/no/no
AVX..................... no
iWMMXt support ......... no
NEON support ........... no
IPv6 support ........... yes
IPv6 ifname support .... yes
getaddrinfo support .... yes
getifaddrs support ..... yes
Accessibility .......... yes
NIS support ............ yes
CUPS support ........... no
Iconv support .......... no
Glib support ........... no
GStreamer support ...... no
PulseAudio support ..... no
Large File support ..... yes
GIF support ............ plugin
TIFF support ........... plugin (qt)
JPEG support ........... plugin (system)
PNG support ............ yes (system)
MNG support ............ plugin (qt)
zlib support ........... system
Session management ..... no
Embedded support ....... arm
Freetype2 support ...... auto (yes)
Graphics (qt) .......... linuxfb multiscreen directfb
Graphics (plugin) ......
Decorations (qt) ....... styled windows default
Decorations (plugin) ...
Keyboard driver (qt) ... tty
Keyboard driver (plugin) ..
Mouse driver (qt) ...... pc linuxtp
Mouse driver (plugin) ..
OpenGL support ......... no
OpenVG support ......... no
OpenSSL support ........ no
Alsa support ........... no
ICD support ............ no

Graphics (qt) .......... linuxfb multiscreen directfb

に directfb が含まれているかがポイント。


4. 動作確認
ドキュメントをまともに読まなかったのでかなりハマったが、以下のようにしないと directfb が使われない:

$ ./linghtmap -qws -display directfb


5. その他のオプション
Qt directfb では、QT_NO_DIRECTFB_WM などのコンパイルオプションを与えることで、機能やふるまいを変えることが出来る。
これについては全然調査していないので調査が必要。
http://doc.qt.nokia.com/latest/qt-embeddedlinux-directfb.html


6. ドキュメントなど
directfb 向けにコンパイルするためのドキュメント
http://doc.qt.nokia.com/latest/qt-embeddedlinux-directfb.html

-display のオプションについてのドキュメント
http://doc.qt.nokia.com/latest/qt-embedded-displaymanagement.html

Qt DirectFB で、devmem の領域が解放されない

Qt Embedded を DirectFB の devmem で動かすと、devmem の領域が解放されない、という問題があった。記録して役に立つかは分からないが、一応メモしておく。
環境は Qt Embedded 4.7.3 と DirectFB 1.4.3。DirectFB は devmem と自前の gfxdriver で blit, fill などの描画処理をアクセラレータで行うようにしている。


問題は Qt の demos/embedded/lightmap を実行したときに起きた。
何故か blit のさい、devmem の領域が余分に allocation され、それが解放されないのである。解放されないままだと devmem の領域が圧迫されるので、プログラムを動かしている途中で OUT OF MEMORY のエラーが起きてしまう。


問題の原因を調べるため、まず、何故 lightmap の blit で allocation が余分に呼ばれるのか、理由を調査した。
DirectFB の blit は、blit 元の src surface が system memory で、かつ blit 先の dst surface が devmem の領域である場合、ハードウェアを使って描画を行うために、わざわざ以下のような処理を行う:
1. src surface と同じサイズの領域を devmem から allocate する
2. src surface のデータを devmem から allocate した仮想アドレスにコピーする(このコピーはソフトウェアで行う)
3. devmem から allocate した物理アドレスから、dst sourface の物理アドレスに、ハードウェアを使ってコピーをする


ハードウェアを使ってコピーするために、わざわざ、devmem から領域を取り直し、ソフトウェアを使ってコピーし、その後ハードウェアでコピーしているのである。高々 blit するだけの処理なのに、実に無駄だ。
以上が、余分に allocate が呼ばれる理由である。
これはまぁ仕方ないとして、次の問題は allocate されたバッファがいつ解放されるか、である。


同じ問題を発生させる、以下のようなシンプルなテストを作り、問題を調査してみた:

#include 
#include      // for sleep()
#include 

static IDirectFB *dfb = NULL;
static IDirectFBSurface *devmem_surface = NULL;
static int screen_width  = 800;
static int screen_height = 480;

#define DFBCHECK(x...) {                                        \
        DFBResult err = x;                                      \
                                                                \
        if (err != DFB_OK) {                                    \
        fprintf( stderr, "%s <%d>:\n\t", __FILE__, __LINE__ );  \
        DirectFBErrorFatal( #x, err )                           \
        }                                                       \
    }

int
main(int argc, char **argv)
{
    char *filename;
    DFBSurfaceDescription dsc_image;
    DFBSurfaceDescription dsc_devmem;
    IDirectFBImageProvider *provider;
    IDirectFBSurface *image_surface;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s [image-file]\n", argv[0]);
        return 1;
    }

    filename = argv[1];

    DFBCHECK( DirectFBInit(&argc, &argv) );
    DFBCHECK( DirectFBCreate(&dfb) );
    DFBCHECK( dfb->SetCooperativeLevel(dfb, DFSCL_FULLSCREEN) );

    //-----------------------------------------------------------------
    // create devmem surface
    dsc_devmem.flags = DSDESC_CAPS;
    dsc_devmem.caps = DSCAPS_VIDEOONLY;
    dsc_devmem.width = screen_width;
    dsc_devmem.height = screen_height;
    DFBCHECK( dfb->CreateSurface(dfb, &dsc_devmem, &devmem_surface) );

    DFBCHECK( devmem_surface->SetColor( devmem_surface, 190, 200, 180, 255 ) );
    DFBCHECK( devmem_surface->FillRectangle( devmem_surface, 0, 0, screen_width, screen_height ) );

    //-----------------------------------------------------------------
    // create image surface using SYSTEM MEMORY
    dsc_image.flags = DSDESC_CAPS;
    dsc_image.caps = DSCAPS_NONE;
    dsc_image.width = screen_width;
    dsc_image.height = screen_height;
    DFBCHECK( dfb->CreateImageProvider(dfb, filename, &provider) );
    DFBCHECK( dfb->CreateSurface(dfb, &dsc_image, &image_surface) );
    DFBCHECK( provider->RenderTo(provider, image_surface, NULL) );
                                 provider->Release(provider);

    DFBCHECK( devmem_surface->SetBlittingFlags(devmem_surface, DSBLIT_NOFX) );

    DFBCHECK( devmem_surface->Blit(devmem_surface, image_surface, NULL,
                            (screen_width - dsc_devmem.width) / 2,
                            (screen_height - dsc_devmem.height) / 2) );

    sleep(1);
    printf( "\n wait...\n\n" );
    sleep(4);

    image_surface->Release(image_surface);
    devmem_surface->Release(devmem_surface);
    dfb->Release(dfb);

    return 0;
}

話が逸れるが、DirectFB は、surface の allocate するときに、ディスクリプタをチェックし、どの領域から allocate するかを判断する。
ハードウェアを使う場合は devmem から allocate する必要があるし、ハードウェアを使わないのなら system memory から allocate される。
テストの image_surface は、CreateSurface() する際に

dsc_image.caps = DSCAPS_NONE;

とすることで、surface の使用目的( というより、どこから allocate するか) を指定しないため、system memory から優先して allocate される。


問題に戻ると、問題は image_suraface->Release() を呼んでも deallocate が呼ばれないことだが、これは Blit のさい、devmem_surface が image_surface への参照を残しているためであった。DirectFB は fusion を使って参照カウンタを管理しているが、参照が残っている場合、その surface は Release() が呼ばれても解放されない。
参照を増やすコードは、Blit の延長で呼ばれる dfb_sate_set_source() である:

DirectFB-1.4.3/src/core/state.c :

213 DFBResult
214 dfb_state_set_source( CardState *state, CoreSurface *source )
215 {
216      D_MAGIC_ASSERT( state, CardState );
217
218      dfb_state_lock( state );
219
220      if (state->source != source) {
221           if (source && dfb_surface_ref( source )) {
222                D_WARN( "could not ref() source" );
223                dfb_state_unlock( state );
224                return DFB_DEAD;
225           }
226
227           if (state->source) {
228                D_ASSERT( D_FLAGS_IS_SET( state->flags, CSF_SOURCE ) );
229                dfb_surface_unref( state->source );
230           }
231
232           state->source    = source;

221行目で dfb_surface_ref() を呼ぶため、source の参照カウンタがインクリメントされる。もし、別の Blit が呼ばれて、かつ source が以前と違う場合、229行目で参照カウンタがデクリメントされる仕組みになっている。


以上が、 image_suraface->Release() を呼んでも deallocate が呼ばれない理由である。
( image_surface が最終的にいつ解放されるか、というと、devmem_surface->Release() の延長で呼ばれる、dfb_state_set_source( &data->state, NULL ); によって、参照カウンタがゼロになり、その延長で deallocate される)


lightmap の使っている QDirectFBPixmapData::fromImage() のコードを抜粋すると、以下のようになる:

qt-everywhere-opensource-src-4.7.3/src/plugins/gfxdrivers/directfb/qdirectfbpixmap.cpp:

299 void QDirectFBPixmapData::fromImage(const QImage &img, Qt::ImageConversionFlags flags)
300 {

313     IDirectFBSurface *imageSurface = screen->createDFBSurface(image, image.format(), QDirectFBScreen::DontTrackSurface);

320     dfbSurface = screen->createDFBSurface(image.size(), imageFormat, QDirectFBScreen::TrackSurface);

334     dfbSurface->Blit(dfbSurface, imageSurface, 0, 0, 0);
335     imageSurface->Release(imageSurface);

335行目で Release() を呼んでも、前述した参照カウンタが邪魔して、dfbSurface が Release されない限り、解放はされない。


7/21追記
その後、ReleaseSource() http://directfb.org/docs/DirectFB_Reference_1_4/IDirectFBSurface_ReleaseSource.html という、まさに上記の問題に対応するための API があることが分かった。というか、普通に仕様書に載っており、見落としていた。
こうなると、Qt が QDirectFBPixmapData::fromImage() にて、ReleaseSource() を呼んで無いのが問題だと思える。
Qt に以下のパッチを当てることで問題は解決される。imageSource を Release する以上は、imageSource への参照は不要であるはずなので、単純な呼び忘れなのだろうか、、、

diff --git a/qt-everywhere-opensource-src-4.7.3/src/plugins/gfxdrivers/directfb/
index d030a9b..7c28aee 100644
--- a/qt-everywhere-opensource-src-4.7.3/src/plugins/gfxdrivers/directfb/qdirect
+++ b/qt-everywhere-opensource-src-4.7.3/src/plugins/gfxdrivers/directfb/qdirect
@@ -332,6 +332,7 @@ void QDirectFBPixmapData::fromImage(const QImage &img, Qt::I
     }

     dfbSurface->Blit(dfbSurface, imageSurface, 0, 0, 0);
+    dfbSurface->ReleaseSource(dfbSurface);
     imageSurface->Release(imageSurface);

     w = image.width();

7/21 さらに追記
Qt のバグトラッカーにバグを登録しようと問題を書いていて気付いたのだが、良く考えると blit が非同期で動くような場合、src が解放されてはまずいような気が、直観的にする、、、
なので上記の修正は恐らく正しく無い。