2018年8月8日水曜日

Ubuntu 18.04劇遅!!

なんか、最近、Virtual Box上で動かしてる Ubuntu 18.04がとても遅い。
絶えられないくらい。
端末1つ出すだけで2~3分、平気で掛かったりする。

他の人のも同様だった。
いままで RAMは 1,024MB割り当てていたが、これを 2,048MBにしたら嘘のように速くなった。
Ubuntu 17.10→18.04でメモリ消費が増えたようだ。
だれ(どのプロセス)が浪費してるかは調査していない。

2017年9月28日木曜日

Ubuntu環境で cppcheckを使ってみた

SonerQube用のプラグインを入れて Cのコードをスキャンさせたら解析結果は空っぽで、ソースコードのみが表示できるだけになってしまった。
どうやら Javaの場合とちがって、C/C++用のプラグインはコードの解析を他のツールに委譲し、結果の集計と整形を行うだけの機能しか無い様だ。
なので、オフィシャルの情報に従ってツールを入れることにした。
解析ツールは複数の選択肢があるが、最初に示されているのが cppcheckというヤツだ。
ソースコードの中の危ない箇所や怪しい箇所を見つけて指摘してくれる。

インストール

Ubuntu 17.04使ってるので aptで cppcheckを installするだけ。
事前に updateしても1分と掛からない。
インストール後の OS再起動も不要。

実行

個別のソースを指定して解析することもできるが、SonarQubeを使うときは対象のディレクトリにあるのを一度にやりたいので --ename=allを指定する。
こんな感じだ。
cppcheck --xml-version=2 --enable=all . 2> cppcheck.xml
--xml を指定すると XML形式のレポートが出力される。 --xml-version=2はそのバージョン2ということで、出力される内容が少し詳しくなっている。

この例で "." を指定してるのは解析するソースを探すパスで、カレントを指定している。これは絶対パス、相対パスのいづれかを指定すればよい。

標準エラーに出力された内容を cppcheck.xmlファイル(名前は任意で良い)に出力する。

それでは次のコードを解析してみる。(一目見て怪しいコードだと解かるでしょう。チェック対象だから何らかのエラーか警告が欲しいのでわざとやってるのです。普段からこんな危険なコード製品に組み込んだりはしてませんから…)
#include <stdio.h>

static int* getPointer() {
 int value;
 return &value;
}

int main() {
 printf("Hello, world.\n");
 int* p = getPointer();
 if (*p != 0) {
  *p = 2;
 }
 printf("[INFO] *p(%p): %d\n", p, *p);
 return 0;
}

結果は次のとおり、エラーが含まれている。
<?xml version="1.0" encoding="UTF-8"?>
<results version="2">
    <cppcheck version="1.76.1"/>
    <errors>
        <error id="returnAddressOfAutoVariable" severity="error"
        msg="Address of an auto-variable returned."
        verbose="Address of an auto-variable returned." cwe="562">
            <location file="Main.c" line="5"/>
        </error>
        <error id="missingIncludeSystem" severity="information"
        msg="Cppcheck cannot find all the include files (use
        --check-config for details)"
        verbose="Cppcheck cannot find all the include files. Cppcheck
        can check the code without the include files found. But the
        results will probably be more accurate if all the include files
        are found. Please check your project's include directories
        and add all of them as include directories for Cppcheck. To see
        what files Cppcheck cannot find use --check-config."/>
    </errors>
</results>
このままでも役に立つけど、SonarQubeでより見やすくすることができる。SonarQubeに関する報告は、またの機会とする。
実際のプロジェクトで使用するにはコンフィグレーションも必要になるが、Softiesプロジェクトでは少しずつ調整して行くことにする。

2017年9月24日日曜日

コードカバレッジツール kcovを使ってみた

Softiesプロジェクトは 2006年から gcovツールを使っているが、シェルスクリプトのカバレッジを計測することができる kcovというのがあるのを最近になって知ったので、早速使ってみた。


結論から言えば、『なんとか使える』レベル。「ないよりはマシ」。


bashスクリプトのカバレッジを計測するのた目的で、今回使用した環境は 64bit版 Ubuntu 17.04。

aptで簡単にインストールできたが、実は 2日前に別の Ubuntu 17.04環境にインストールしたときはインストールは成功したものの、実行すると C++のライブラリで異常な配列インデクスが原因でコアダンプしてしまう現象が発生した。ソースからダウンロードして cmakeからやってみたが解決しなかった。そのときの詳しい経過やエラーメッセージは持ち出しできないので再現できず原因は不明だが、依存するライブラリとの相性問題があるかも知れない。(訂正:その動かなかった環境のものも動いた。エラーが発生したのは kcovと一緒にインストールしたテストフレームワークのものだった。)

機能の簡単な紹介

  • ELF, Mach-Oの両形式でコンパイルされたバイナリ、Python、bash、shプログラムのカバレッジを計測するテスタで、Linux/OSXで動作
  • オプションなしにコンパイルされたバイナリから直接カバレッジを取得
  • 特別な工程なしに HTML, Cobertura XMLを出力
詳しい情報は https://simonkagstrom.github.io/kcov/ を参照

実行状況

とりあえず今日動かした状況は下記のとおり。
$ kcov d ~/bin/bu
  • dは出力先のディレクトリ、その次に計測対象のプログラムを指定する。
  • 出力先ディレクトリは予め作っておく必要はない。
  • 逆に出力先ディレクトリに以前の計測結果があるときはマージされるので、まっさらな状況から計測したいときは空にしておかなければならない。
  • プログラムを指定するときは、絶対パスまたは相対パスを指定する必要があり、環境変数に指定したパスにあるものではプログラムは usageを表示してしまい計測できない。
  • コンパイル時にオプションが不要とのことだが、ビルドID ファイルがない場合は -gまたは -ggdbオプションでビルドする必要がある。(ビルドIDファイルって何だ?)
  • プログラムの後にプログラムに与えるオプションやオプション引数を指定できる。

レポート

前述のコマンドを実行するとつぎのような HTMLのレポートが得られた。
gcovでもよく使われれ、見慣れた lcov風の書式。いい感じ。

しかし、コードを表示させると妙な行間があって見にくい。

charsetの指定がないため、文字化けも発生。 手動で UTF-8を選択すれば正しく表示されるが、ページを移動するたびにやらなければならない。

doneや fiのカウントが可笑しい。(他のカバレッジツールでも起こる問題だが)



いつくか難点はあるものの、ここまで作ってくれた作者に感謝するとともに、今後の改善を祈りながら使っていきたいと思う。
冒頭で「ないよりマシ」とか酷い書き方をしたが、実際には「使えねぇ」ものも数ある中では、使い続けようかという気にさせたツールの1つである。

2017年5月19日金曜日

Clangでユニバーサルキャラクタ識別子を使う

Softiesプロジェクトとは直接は関係ない話題。

1年前から Softiesプロジェクトがこれまで使用してきた gccを clangに置き換える方向で準備を進めている。
と言っても gccを廃すかどうかまでは決まって折らず、平行運用することになるかも知れない。

そんな中、gccでは使えないユニバーサルキャラクタ識別子が clangでも使えることが判った。
ちょっと、悪乗りした感じのプログラムを紹介する。
つぎのプログラムが Cで書いた物。実際には C99の仕様である。

main.c
#include "C文法定義"
#include "ライブラリ定義"

型定義 構造体 自動車* 自動車;

構造体 自動車 {
 文字列 車種;
 整数 価格;
};

整数 メイン関数(整数 argc, 文字列* argv) {
 自動車 俺の車 = (自動車)メモリ割り当て(サイズ(構造体 自動車));
 もし (俺の車 != ぬるぽ) のとき {
  俺の車->車種 = "プリウス";
  俺の車->価格 = 1234567;
 }
 表示("[INFO] %s, %d円\n", 俺の車->車種, 俺の車->価格);
 戻る 0;
}

ヘッダファイルはつぎの2つ


C文法定義
#ifndef _Included_C文法定義
#define _Included_C文法定義

#define 文字列  char*
#define でないとき else
#define もし  if
#define 整数  int
#define 戻る  return
#define サイズ  sizeof 
#define 構造体  struct
#define 型定義  typedef
#define メイン関数 main
#define のとき

#endif /* _Included_C文法定義 */

ライブラリ定義
#ifndef _Included_ライブラリ定義
#define _Included_ライブラリ定義

#include 
#include 

#define ぬるぽ  NULL
#define 表示  printf
#define メモリ割り当て malloc

#endif /* _Included_ライブラリ定義 */

ついでに必要最小限の Makefile
CC = clang

つぎのようにビルドして
make Main

実行する。
$ ./Main
[INFO] プリウス, 1234567円

Javaは初期の言語仕様から識別子に日本語が使えた。
しかし、#define機能がないのでキーワードまでは置き換えられない。

2016年10月21日金曜日

変数の値が非数 NaNかどうか調べるために、定数 NANと比較してはならない。

次に示すのは CUnitを用いたテストケースである。
/**
 * 定数 NANと比較すると偽を返すこと。
 *
 * NaNかどうか判定するために、定数 NANと比較してはならない。
 */
public void testCompareNanAndNan() {
        /* 事前条件の準備 */
        double value = NAN;

        /* 実行 */
        boolean result = (value == NAN);

        /* 事後条件の検証 */
        Assert_false("結果は偽であること。", result);
}
これを実行すると、テストは成功する。
変数 valueが定数 NANで初期化されているが、その変数を NANと比較しても真にならない。
比較演算子で NANと比較しても NaNかどうか判断できないということで、これは正しい挙動である。
なお、このテストケースは gccを使用してコンパイルしたが、定数 NANを使用するためには math.hをインクルードする必要がある。
ISOの C99よりも前のバージョンでは、処理系によっては NANが使えない。
NaNであるかどうかの正しい判定方法を次のテストケースに示す。
/**
 * NaNであることを判定できること。
 *
 * NaNかどうかの判定には isnan()を使用すること。
 */
public void testIsNan() {
        /* 事前条件の準備 */
        double value = NAN;

        /* 実行 */
        boolean result = isnan(value);

        /* 事後条件の検証 */
        Assert_true("結果は真であること。", result);
}
isnan()も math.hが必要。
==比較演算子を使用して「強引に」判定する例を次に示す。
/**
 * ポインタを使用して NANと比較することで、NaNかどうかを判定できること。
 *
 * この使い方を推奨しているわけではないが、定数を代入しただけのものは
 * ビットパターンの比較により判断ができる。
 * この例ではポインタを使用したが、unionを用いても同様のことができる。
 *
 * NaNの値は1通りではないため、計算結果や外部から読み込んだ値を正しく
 * 判断するには == は使えない。
 */
public void testCompareNanAsPointer() {
        /* 事前条件の準備 */
        double value = NAN;
        double nan = NAN;

        /* 実行 */
        boolean result = (*(long long*)&value == *(long long*)&nan);

        /* 事後条件の検証 */
        Assert_true("結果は真であること。", result);
}
ドキュメントコメントにも書いてあるよう、この方法では特定の条件でした判断できない。
非数 NaNを理解するための例をもう1つ示しておこう。
/**
 * NaNは 指数部の全ビットが 1で、仮数部が 0でないこと。
 *
 * これは例を示しただけで、実際にやるのは無駄。 isnan()を使うべき。
 */
public void testNanAsPattern() {
        /* 事前条件の準備 */
        double value = NAN;
        long long exponentMask = 0x7ff0000000000000;
        long long mantissaMask = 0x000fffffffffffff;

        /* 実行 */
        long long longValue = *(long long*)&value;
        boolean result = ((longValue & exponentMask) == exponentMask)
                && ((longValue & mantissaMask) != 0);

        /* 事後条件の検証 */
        Assert_true("結果は真であること。", result);
}
valueを定数 NANで初期化したものだけでなく、0.0 / 0.0した結果も正しく判断できる。
printfを使って調べると、gccの場合、NANのパターンは 0x7ff8000000000000、0.0 / 0.0の結果は 0xfff8000000000000で 1ビット異なるが、いづれも NaNと判定できた。
注:value = 0.0 / 0.0; というコードはコンパイル時点でエラーになる。 分母を変数にしてその変数を 0.0で初期化するようにすれば、コンパイルが通るようになる。

普段の判定は isnan()を使用すればよいが、isnan()が使えない場合や NaNを使ったテストケースを記述するとき、この知識は役にたつだろう。
なお、上記の例で使った CUnitテストフレームワークについてはこのブログで紹介してきたが、はじめてみる人のために簡単に説明しておく。
これらのテスト関数はつぎのようなコードを含むソースコードとして記述され、softiesの CUnitライブラリとリンクされる。
#define import_lwd_unit_Assert
#define import_lwd_unit_CUnit

#include <lwd/lwd.h>
#include <lwd/unit/Assert.h>
#include <lwd/unit/CUnit.h>
#include <math.h>

/**
 * @file
 *
 * NaNの振る舞いのテスト。
 */

/****************************************************************
        test cases
****************************************************************/

// ここに、テスト関数を記述する。
// テスト関数名が異なれば複数個記述可能。

/****************************************************************
        main
****************************************************************/

public int main(int argc, char** argv) {
        return CUnit_test(argc, argv);
}
main関数のなかで CUnit_test()を呼び出すことにより、自動的にテスト関数を認識して実行する。
なおテスト関数の名前は textXXXのように先頭4文字は testでなければならない。

2016年10月16日日曜日

単体テストフレームワーク CUnit(その2)

テスト仕様をテストレポートに表示できるようになったのは、つい最近の 2016年 6月。それまではテストケースのクラス名や、テストメソッドの名称でしか表現できなかった。
レポートにテスト仕様の表示が必要なことは最初のころから感じていたが、うまい実現方法が思いつかなかった。
仕様を記録するために、専用の関数を定義しようかと考えたことがある。
たとえば、事前条件や事後条件のテスト仕様を示すために、テストコード中につぎのような関数呼び出しを記述するのである。

 Specification_postcondition("valueは 100になっていること。");

技術的には何の問題も無いが、テストコードの作りやすさや保守性を考えると冗長すぎないかと思い、まだ実現していない。
テストコードそのものがテスト仕様になるはずなので、文字列で記述せずにテストコードから仕様の記述を生成できればそのほうがよいと考えている。しかしこちらの方は技術的に難しい。
いよいよ方法が見つからないようなら上記のような関数を提供することになる。

そこで当面の間、もう少し気楽に使えるできるよう、テストケースとテストメソッドに対してドキュメントコメントで概要だけを記述できるようにした。
例)

/**
 * 2つの値を加算できること。
 */
public void testAdd() {

この程度の記述でテストレポートの内容がわかり易くなるなら、書くようになるでしょう。


過去に記述したテストケースなど、ドキュメントコメントの記述が無いものを最近のフレームワークで処理すると、テストレポートに警告文が表示されるようになっている。

折角テストがOKになっても警告文はカッコ悪いですよね。これには既存のテストコードにもドキュメントを書いてくださいという思いが込められている。


将来的には、事前条件や事後条件、実行内容もレポートできるようにして、さらには Excelや Wordのテスト仕様書、テスト報告書が自動作成できるようにしたい。

2016年10月14日金曜日

単体テストフレームワーク CUnit


Javaでコードをテストするときには JUnitを使用しているが、C/C++でもそのようなのがあったら便利だと思った。
2006年ごろ、自社製(まだ会社設立前だったが)の Java VM(TCK通してないので、公式には“もどき”)の品質が悪く、ちゃんと細部までテストをしたいなというのが始まりだった。
ネット上で検索すると沢山の CUnitが見つかり、そのうちのいくつかを試してみた。
しかし、どれもあまりパッとしなかった。 使いたくないと感じたのは、
  1. プロジェクトが放置されて久しいもの
  2. 完成度があまりにも低いもの
  3. 手続きが多くて使いにくいもの
  4. ドキュメントが全く無いもの
  5. エラーが発生したときの処理が貧弱なもの
  6. レポートを見て「ワクワク」しないもの

3の使いにくいとは、リポジトリとかテストスイートなどのオブジェクトを作り、テストケースを1個ずつ追加しなければならないのは、無精者の自分には向かないと思った。
テストケースを追加したのに、テストスイートに追加を忘れると、エラーにもならずに黙ってテスト漏れになってしまう。

しばらく我慢して使っていたがあまりにも作業効率が悪いので、自分も CUnitを作ろうということになった。(VM作りに集中せず、横道に逸れてしまうのだが)

今なら Google Testを使うでしょう。 Google Testレポートはワクワクしないけど、Softiesの CUnitの目標と共通している点も多い。 ただ当時は日文のドキュメントが多くなかったので Google Testは不採用とした。(なんと無謀な…。 皆さんは真似してはいけません。時間の無駄です。 Google Testを使いましょう。)

CUnitの目標は
  1. JUnitライクとする。(Cではアノテーションができないので 3.8ベース)
  2. リポジトリやスイートはデフォルトで不要または自動とする。
  3. JUnit同様、テスト関数の結果が他のテスト関数に影響しない。(ファイルや DBを除く)
  4. テスト中にエラーが発生してプロセスが停止しても、次にテスト関数は実行できること。
  5. 赤と緑を使った刺激的でモチベーションを維持できるレポートを出力できること。

では細かな機能紹介は別にするとして、テストケースの記述例とテストレポートのスナップショットをご覧頂きましょう。
(バージョンは違うが、以前、パッケージ一覧でも例示してある。)

テストケースのサンプル

テストプログラムの冒頭


テストケースの仕様をドキュメントコメント @fileタグで記述する。

スタブ(必要なときのみ)


フィクスチャ


テストケース


テスト関数の仕様をドキュメントコメントで記述する。
Assertクラスのコメントもテストレポートに引用される。

main関数


main関数はスタティックリンク(デフォルト)する場合に必要。
CUnitクラスの testメソッドを呼ぶだけのため、覚えるのは簡単。
main関数の中で argc, argvの内容を testメソッドを呼ぶ前に書き換えることも可能。

テストレポートのスナップショット

テストケース一覧


テスト関数一覧


テスト関数の実行結果(1)


テストレポートに事前条件の表示が無いため、事後条件の検証結果に唐突感がある。
事前条件の表示は今後の課題。
printf等で標準出力に出力した結果は、"stdout"に表示される。(この例では空)
標準エラー出力に出力した結果は、"stderr"に表示される。
malloc, free, reallocは追跡され、"heap"に表示される。 この例ではメモリリークが起きているがこれはテストケースでおきたものではなく、softiesが使用している libbfdのバグによるもの。
なお、softiesが未公開の理由の1つはこの libbfdが GPLライセンスであるため。 これを排除して softiesを公開する予定であるが、まだ時間を要する。

テスト関数の実行結果(2)


テストによりテキストファイルのほか、イメージや動画、オーディオファイルが出力されると、自動的にテストレポート中に表示される。

2014年9月28日日曜日

Ubuntuに H8/300H用の gccをインストールしてみる。

Ubuntu 14.04に H8/300H用の gccをインストールしてみた。

H8/300Hは Softiesプロジェクトとは直接の関係は無い。
わざわざ直接の関係は無いと言う事は、間接の関係があると察していただけたと思う。
そもそも、Softiesプロジェクトは組み込み開発のテスト環境を提供するのが始まりだった。特に、単体テストフレームワークの CUnitがそれだ。そしてその CUnitは自社製の "ra7taj"という仮想マシンをテストするためのものであった。ra7tajの開発を 2年間一時停止して Softiesプロジェクトを進めてきた。CUnitの開発が今年の盆休みに実用域に達してひと段落したので、今月から ra7tajの開発を再開した。
それがなんで H8/300Hと関係があるのか??

ra7tajは不安定ながらも Solarisや Linux上で動作しているので、いよいよ次のステップではターゲットに組み込んで使えるようにすることになるが、そのターゲットが H8/300Hなのである。
今時なんで ARMとかじゃなくてH8/300Hかというと、…長くなるので止めとくが、CPUパワーもメモリも「そこそこ」しか無い環境で動かしてみたかったからである。
欲を言えば、8051くらいでも動作させたいが、ちょっとメモリが足りなさすぎるので見送りとなった。実を言うと、もう少し速くターゲット環境で動かしたかったが、2011年に欲を出してコンパクション可能なコレクタにしたら、それまで動作していたところがあちこちと調子悪くなってしまい、現在デバッグ中である。実際にターゲットを動かすのはいつになるやら。 ビルド環境を作るなんて気が早いかもしれない。
それに CUnitで単体テストやるのが楽しすぎて…。

CUnit作る前はどうやって ra7tajテストしていたかというと、Javaのプログラムから JNI経由で関数を呼び出していた。 Heapの状況を Swingで視覚化したりして大変便利だったのだが、ra7taj自体に JNI互換の関数を提供しはじめたら、テスト対象の ra7tajの JNIと テストドライバである JDKの JNIのシンボルが衝突して破綻してしまった。

なお、Softiesはオープンソースにしたいが、使用している一部のライブラリのライセンスの都合で未だ公開できる状況ではない。
ライセンス問題をクリアするまでもう少し掛かりそうである。

インストール

話が脱線してしまったので、元に戻そう。

インストールといっても、apt-getコマンドを使うだけである。
$sudo apt-get update

した後、
$ sudo apt-get install gcc-h8300-hms
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の特別パッケージがインストールされます:
  binutils-h8300-hms
提案パッケージ:
  binutils-doc gcc-doc
以下のパッケージが新たにインストールされます:
  binutils-h8300-hms gcc-h8300-hms
アップグレード: 0 個、新規インストール: 2 個、削除: 0 個、保留: 5 個。
5,554 kB のアーカイブを取得する必要があります。
この操作後に追加で 14.9 MB のディスク容量が消費されます。
続行しますか? [Y/n] y
取得:1 http://jp.archive.ubuntu.com/ubuntu/ trusty/universe binutils-h8300-hms
 amd64 2.16.1-9ubuntu1 [2,125 kB]
取得:2 http://jp.archive.ubuntu.com/ubuntu/ trusty/universe gcc-h8300-hms amd64
 1:3.4.6+dfsg2-1ubuntu1 [3,429 kB]
5,554 kB を 14秒 で取得しました (381 kB/s)                                     
以前に未選択のパッケージ binutils-h8300-hms を選択しています。
(データベースを読み込んでいます ... 現在 328030 個のファイルとディレクトリが
インストールされています。)
Preparing to unpack .../binutils-h8300-hms_2.16.1-9ubuntu1_amd64.deb ...
Unpacking binutils-h8300-hms (2.16.1-9ubuntu1) ...
以前に未選択のパッケージ gcc-h8300-hms を選択しています。
Preparing to unpack .../gcc-h8300-hms_1%3a3.4.6+dfsg2-1ubuntu1_amd64.deb ...
Unpacking gcc-h8300-hms (1:3.4.6+dfsg2-1ubuntu1) ...
Processing triggers for man-db (2.6.7.1-1) ...
binutils-h8300-hms (2.16.1-9ubuntu1) を設定しています ...
gcc-h8300-hms (1:3.4.6+dfsg2-1ubuntu1) を設定しています ...
$

gcc本体だけでなく、binutilsも一緒にインストールされた。

gccのバージョンは最近の 4.8.xとかでなくて少し古い。

コマンドが認識されるかいくつか試してみる。
$ h8300-hms-gcc --version
h8300-hms-gcc (GCC) 3.4.6
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$

$ h8300-hms-objdump --version
GNU objdump 2.16.1 Debian GNU/Linux
Copyright 2005 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License.  This program has absolutely no warranty.
$

スタートアップルーチンは準備してないので、Hello, world.とかを試すのは今度にしよう。

2014年9月10日水曜日

言語Cで synchronizedを実現する(その1)

Javaの synchronized構文は便利だ。
オブジェクトをロックし、クリチカルセクションを実行したあと、確実にロックを解除するすることが言語でサポートされている。

そのような機構がない言語の場合、つぎのように記述するだろう。
lock();
// critical section
unlock();


クリチカルセクションが 1~2行程度ならよいが、長くなると何が起こるか分からない。
うっかり、この中で
if (hasError)
 return errorCode;


などとやってしまうとロックが解除されず、処理がとまってしまうか、再びロックしようとして多重ロックしてしまうかも知れない。

今回のチャレンジは、次のように関数を記述することだ。

void synchronized function(Something this) {
 // critical section
}


Javaでは synchronizedなメソッドのほかに、synchronizedブロックが記述できるが、ここでは関数だけを対象とする。

synchronizedな関数に入るときオブジェクトをロックし、出るときにアンロックする。
これを実現するための課題は、
  1. 関数に入るときと出る時に処理を行なえること。
  2. 関数に synchronized属性を持たせること。
  3. 関数が synchronizedを持って意いるか判断可能なこと。
  4. ロックするオブジェクトを認識できること。

1.関数に入るときと出る時に処理を行なう

これは以前の記事で取り上げたことのある、gcc拡張の __cyg_profile_func_enter, __cyg_profile_func_exitを使えそうである。
実験ではオブジェクトをロックせず、下記のようにトレース出力をする。
void NO_INSTRUMENT __cyg_profile_func_enter(void *function, void *caller) {
        if (isSynchronized(function))
                printf("enter synchronized function %p\n", function);
        else
                printf("enter %p\n", function);
}

void NO_INSTRUMENT __cyg_profile_func_exit(void *function, void *caller) {
        if (isSynchronized(function))
                printf("exit synchronized function %p\n", function);
        else
                printf("exit %p\n", function);
}


なお、NO_INSTRUMENTはコードを見やすくするための下記のようなマクロである。
#define NO_INSTRUMENT __attribute__((no_instrument_function))

2.関数に synchronized属性を持たせる

これはちょっと難しい。

上記の例にある、 isSynchronized(function)をどうやって実現するか。

関数に任意の属性を持たせたい。
関数とは別管理のルックアップテーブルを持たせれば簡単そうだが、別管理が面倒くさい。
関数が synchronizedかどうか人間が知りたいとき、いちいち管理テーブルを見ないとならない。
void synchronized function...のような記述できるほうが管理しやすい。
そのためには Javaのアノテーションのような機能が必要になる。
しかし、言語Cにはアノテーションは無い。 gcc拡張を探したがそれに近いものは無い。

諦め掛けていたが、トンでもないアイデアを思いついた。
synchronized用のメモリセクションを作り、そこに配置した関数を synchronizedとみなすことにしよう。

synchronizedセクションを作って関数を配置するには、gcc拡張の __attribute__構文が使える。
次のようなマクロを定義すれば、あとは関数に synchronizedを付けるだけ。
#define synchronized __attribute__((section("synchronized")))
gcc以外にもソースコード上にセクションを明記できるものがあるが、#pragma構文を使うものは残念ながら #defineすることはできない。

早速書いてみよう。 使い分けできるのが分かるよう synchronizedでない普通の関数も作っておく。

Sub.h(主要部分だけ)
extern void sub1();
extern synchronized void sub2();


Sub.c(主要部分だけ)
void sub1() {
 printf("sub1 called.\n");
}

void synchronized sub2() {
 printf("sub2 called.\n");
}


これをビルドしてできた Sub.oを逆アセンブルしてみると、ちゃんと synchronizedセクションに対応づけられている。
$ objdump -d Sub.o

Sub.o:     file format elf32-i386


Disassembly of section .text:

00000000 :
   0: 55                    push   %ebp
   1: 89 e5                 mov    %esp,%ebp
   3: 83 ec 18              sub    $0x18,%esp
   6: 8b 45 04              mov    0x4(%ebp),%eax
   9: 89 44 24 04           mov    %eax,0x4(%esp)
   d: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  14: e8 fc ff ff ff        call   15 
  19: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  20: e8 fc ff ff ff        call   21 
  25: 8b 45 04              mov    0x4(%ebp),%eax
  28: 89 44 24 04           mov    %eax,0x4(%esp)
  2c: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  33: e8 fc ff ff ff        call   34 
  38: c9                    leave  
  39: c3                    ret    

Disassembly of section synchronized:

00000000 :
   0: 55                    push   %ebp
   1: 89 e5                 mov    %esp,%ebp
   3: 83 ec 18              sub    $0x18,%esp
   6: 8b 45 04              mov    0x4(%ebp),%eax
   9: 89 44 24 04           mov    %eax,0x4(%esp)
   d: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  14: e8 fc ff ff ff        call   15 
  19: c7 04 24 0d 00 00 00  movl   $0xd,(%esp)
  20: e8 fc ff ff ff        call   21 
  25: 8b 45 04              mov    0x4(%ebp),%eax
  28: 89 44 24 04           mov    %eax,0x4(%esp)
  2c: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  33: e8 fc ff ff ff        call   34 
  38: c9                    leave  
  39: c3                    ret 

因みに synchronizedでない普通の関数は .textセクション。

3.関数が synchronizedを持って意いるか判断

そして、isSynchronized関数はつぎの通り。
int NO_INSTRUMENT isSynchronized(void* function) {
 extern char __start_synchronized;
 extern char __stop_synchronized;

 return (function >= (void*)&__start_synchronized)
  && (function < (void*)&__stop_synchronized);
}

gccでは、__start_, __stop_の後にセクション名をつけたものが、それぞれセクションの先頭と後端のアドレスを示している。 これを実行時に関数アドレスと比較することで、そのセクションの中の関数かどうか判断できるというわけである。

あとは次のようなプログラムで sub1()と sub2()を実際に呼び出してみる。 動作が確認しやすいよう、synchronizedセクションの先頭、後端アドレスと、sub1(), sub2()のアドレスも出力しておき、つづいてsub1()と sub2()を実際に呼び出す。

int NO_INSTRUMENT main(int argc, char** argv) {
 extern char __start_synchronized;
 extern char __stop_synchronized;

 printf("[synchronized section]\n");
 printf("start: %p\n", &__start_synchronized);
 printf("end: %p\n", &__stop_synchronized);
 printf("\n");

 printf("[function address]\n");
 printf("sub1: %p\n", sub1);
 printf("sub2: %p\n", sub2);
 printf("\n");

 printf("[call functions]\n");
 sub1();
 printf("\n");
 sub2();
 return 0;
}
結果は次のように、synchronizedとそうでないものがちゃんと区別できることを確認できた。
$ ./Main
[synchronized section]
start: 0x80486ec
end: 0x8048726

[function address]
sub1: 0x8048600
sub2: 0x80486ec

[call functions]
enter 0x8048600
sub1 called.
exit 0x8048600

enter synchronized function 0x80486ec
sub2 called.
exit synchronized function 0x80486ec

なお、Sub.h, Sub.cの両方で sub2()に synchronizedを記述したが、Sub.h, Sub.cのいづれか一方だけに記述しても結果は同じになる。

この手法は、synchronized以外にも使えるかも知れない。 なんとも馬鹿げたやり方だが。
※本プロジェクトの名称 softiesの先頭の sは stupidの略ですから、何でもアリです。 お許しください。

2014年7月4日金曜日

Ubuntuを 14.04にアップデート

Softiesプロジェクトでは、Open Solarisと Ubuntuを使用しているが、10-2から 12.04と 14.04にアップデートした。

ホスト OSを Windows 7 Professionalとし、VirtualBox 4.2.10の上で動かす。

ところが、OSインストール後、Guest Additionsを設定しようとすると、エラーが発生。

「Building the shared folder support module ...fail!」

これでは、ウインドウサイズが小さいままで使いにくい。

Guest Additionsの設定手順を間違えたと思い、コンソールから手動でスクリプトを起動してみたりいろいろ試してみたが何度やっても同じ。

試しに、VirtualBoxを 4.3.12にしたら、 Guest Additionsの設定が成功した。

と思ったら、今度は『仮想マシンの状態を保存』にして VirtualBoxを閉じようとすると、「Oracle VM VirtualBox Managerは動作を停止しました」のエラーが発生。

「問題が発生したため、プログラムが正しく動作しなくなりました。 プログラムは閉じられ、解決策がある場合は Windowsから通知されます。」とメッセージが表示される。(12.04, 14.04とも)

サスペンドできないのはこれまた大変不便。 VirtualBox停止させるまえに必ずシャットダウンしなければならない。

VirtualBoxのディスプレイ設定で、3Dアクセラレーションを有効化を外したら、エラーが出なくなった。

VMwareでもいろいろ問題起こすが、開発環境をアップデートするたびに何かしらの試行錯誤が強いられるのはしんどい。

2014年5月19日月曜日

Sun Microsystems, Inc. 同窓会

いよいよ今週末 May 24, 2014の 6:00pm-11:00pm、マウンテンビューで Alumni Reunionが開催される。

参加費用は $160。

残念ながら申し込みは 5月 1日に締め切られているようだ。

Sun Microsystems Reunion Announcement
Top Ten Signs That You Miss Sun Microsystems

我々が最も影響を受けたのは、Digital Reserch Inc., Netscape Communications Corporationそして Sun Microsystems, Inc.だった。
そして、それらの輝きと興奮は今も失われることは無い。

2014年4月30日水曜日

pthreadの条件変数

今日は決算報告書を 作成して 顧問の税理士さんに作成してもらって確認したあと、銀行、役所、税務署を 徘徊して 廻って国税(法人税、復興特別法人税、消費税)と県税、市税を納付してきた。
今期は若干の設備投資が発生したため、利益は少なかった。 というより、前の期での設備更新を先延ばしにしたためその期は利益が出て、逆に今期に更新分が偏ってしまった。
設備投資は計画的に行わなければ資金の運用にも、生産性にも問題が生じてしまう。 しっかりやらないと。

夕方には帰社できたが取引先は休みに入っているので、自社のプロジェクトに時間を割くことにした。

softies projectとは別に、Unix上で動作する T-Kernelもどき(サブセット)を作ることにする。
T-Kernelを扱う業務では簡単なモックを作ってテストしているが、それとは別にシミュレータも作りたくなった。
既にシミュレータはあるはずなので新たに作るのは時間の無駄になるのだが、今回の目的はシミュレーションというよりも T-Kernelの仕様を理解することにある。


主成功シナリヲ

まず、1st iterationでは、下記の関数に対して主成功シナリヲ(最も基本的な使い方)だけを実装して単体テストしてみると、それなりにうまくいった。

tk_cre_mtx  tk_del_tsk  tk_ext_tsk  tk_ref_mtx tk_slp_tsk
tk_cre_tsk  tk_dly_tsk  tk_get_tid  tk_ref_tsk tk_sta_tsk
tk_del_mtx  tk_exd_tsk  tk_loc_mtx  tk_rel_wai tk_unl_mtx
taskは pthreadに、mutexは pthread_mutexに単純に対応させた。

代替シナリヲ

2st iterationでは、代替シナリヲ(主成功シナリヲではない別の使い方)を実装して単体テストしてみる。

tk_dly_tskを使用しているテストが通らなくなった。たとえば tk_dly_tsk(2000U)としてあるので 2秒経過後に抜けてきて欲しいのだが、10ミリ秒そこそこで抜けてきてしまう。
10ミリ秒というのは単体テストのフレームワークのオーバヘッドも含んでいるので、もう少し短い時間かも知れない。
いづれにしても期待する動作になってない。

tk_dly_tskの主成功シナリヲでは、引数で指定した時間を経過した後関数から復帰するだけだったので、単純な usleep(3C)で遅延を実装していた。

tk_dly_tskの代替シナリヲでは、他タスクから tk_rel_waiを呼ばれると、SUSPEND状態が解除されてエラーコード E_RLWAIを返さなければならない。
SUSPEND状態を抜けるには他にも tk_ter_tskもあるし、tk_slp_tskで SUSPENDして tk_wup_tskで解除というのもある。

このように待ち合わせと解除の方法が多岐にわたるため、何の要因により SUSPENDを解除しようとしているのかを記憶しなければならない。

usleepではシグナルを使うことにより中断させることはできるが、複数のタスクから条件変数の操作を安全に行うことはできない。
そこで、mutexおよび条件変数を使用することにした。


バグの原因

時間待ちを行うために pthread_cond_timedwait(3T)を使用したが、問題はそこにあった。

この関数は時間待ちをするものではなく、時刻を待つものである。
そしてその時刻とは 1970年 1月 1日 0時 0分 0秒 GMTを期限とする相対値である。 多くのマニュアルには、abstimeと書かれている。

これを見落として、2st iterationで最初に書いたコードでは単純に待ち時間を与えたものを引数に使ったものだから、2秒間待つつもりが、1970年 1月 1日 0時 0分 2秒 GMTまで待つことを指定したことになる。
1970年というのは読者の中にはまだ生まれてない方もいることだろう。そのくらい大昔である。
pthread_cont_timedwaitを呼び出しても僅かなオーバヘッドが生ずるだけで、ほとんど直ちに復帰してくるのである。

バグの影響

それでも組み込みの開発でありがちな、時計が設定されていない状態では偶然それっぽい時間になるかもしれなかった。
そんな出鱈目な状態でテストをパスさせてしまうと、市場で不具合になることだろう。
シミュレーションでよかった。

そうなると、過去に開発したものが怪しくなってきた。

幸い、請け負ったものの中には pthread_cond_timedwait(3T)を使ったものが無いことが確認できた。

softies projectでは、lwd_lang_Objectクラスの waitAsMillis関数で使っている。
しかし、ちゃんと現在時刻を取得し、待ち時間を加えて使っている。
エラー処理の違いのため、lwd_lang_Objectをそのまま使用することはできないが、参考にすべきだったかも知れない。


教訓

  • ちゃんとマニュアル読め
  • 正常に動くコードを参照しろ

参考文献

  • ビル・ルイス+ダニエル・バーグ著『Pスレッドプログラミング』岩本信一訳 プレンティスホール 1999年
  • David R.Butenhof著『POSIXスレッドプログラミング』油井 尊訳 アジソン・ウェスレイ 1998年

2014年1月2日木曜日

mallocライブラリ その1 (alternative malloc library 1)

現代のプログラミングに於いてメモリの動的割り当ては欠かせない機能の一つである。
リソースは「要る物を」「要る時に」「要る分だけ」無駄なく割り当てることは重要だと考える。

近代的な言語にはガベージコレクタを標準で備えるようになっているが、言語 Cでのメモリ管理はプログラマに任せられている。
我々は動的メモリ管理のために mallocや free関数を使用するが、どうしても間違いが生じてしまう。

  • 未割り当て領域へのアクセス
  • 使用中の領域を解放/解放済み領域へのアクセス
  • 二重解放
  • 解放漏れ(メモリリーク)
  • オーバラン/アンダーラン

softiesプロジェクトでは、これらの問題を検出するための LWD mallocライブラリを開発している。
この用途には valgrindというメモリの監視以外にも使える使いやすいツールが既にあるが、Solarisには非対応で、動作も若干鈍調である。
我々はより軽量で Solarisでも使用できることを目標に置いている。

使用法

このライブラリはメモリを大量に消費する。
そのため、専ら小規模な単体テストに使用されることを想定している。

静的ライブラリ libmalloc.aをテスト対象のプログラムにリンクして使用する。
(都合が悪ければライブラリ名称は任意に変更して良い)

libmalloc.aがホームディレクトリの下のlibにあり、Test.cからデバッグ版の Testをビルドする例を以下に示す。
 $ cc -g -o Test -L ~/lib -lmalloc Test.c

Makefileを使用する場合には、下記の定義を含めればよい。
CFLAGS = -g
LFLAGS = -L ~/lib
LDLIBS = -lmalloc


正常な使用例

まず、メモリを割り当て、それを解放した場合の例を見てみよう。

#include <malloc.h>
#include <stdio.h>

int main() {
 char* bytes1 = (char*)malloc(5);
 char* bytes2 = (char*)malloc(8);
 free(bytes1);
 free(bytes2);
 return 0;
}

このプログラムは 5バイトと 8バイトの領域を割り当て、その両方を解放する Test1.cである。

$ ./Test1
HEAP REPORT
  SUMMARY
   total allocated: 13(bytes)  2
   memory leak: 0(bytes)  0

SUMMARYには、total allocatedに 2回の割り当てで合計 13バイト割り当て、memory leakに解放漏れが無いことが示されている。
(bytes)の後ろの値は領域の数を表している。

メモリリークの検出例

つぎは、メモリの解放を忘れた場合の例である。
#include <malloc.h>
#include <stdio.h>

int main() {
 char* bytes1 = (char*)malloc(5);
 char* bytes2 = (char*)malloc(8);
 free(bytes2);
 return 0;
}

先ほどと同じ 5バイトと 8バイトの割り当てを行った後、8バイトの方のみ解放する。

$ ./Test2
HEAP REPORT
  SUMMARY
   total allocated: 13(bytes)  2
   memory leak: 5(bytes)  1

  DETAIL OF MEMORY LEAK
 no.     memory size(byte) created by
   1 0xfae00000          5

SUMMARYには、total allocatedに 2回の割り当てで合計 13バイト割り当て、memory leakに そのうちの 1回分の 5バイトが解放漏れを起こしたことが示されている。
さらに DETAIL OF MEMORY LEAKでは、5バイトの解放漏れ領域のアドレスが 0xfea0000であることを示している。

解放漏れが複数ある場合には、その数分だけ 1行ずつ表示される。

created byの欄が空欄なのは許して欲しい。
libdwarfを使用して割り当てたコードのソースファイルとその行番号を表示するためのものだが、現在機能しない。

未割り当て領域へのアクセス検出例

メモリを割り当てていない領域にアクセスする例を示す。
#include <malloc.h>
#include <stdio.h>

int main() {
 char* bytes = (char*)malloc(5);
 char* p = bytes - 1;
 char c = *p;
 free(bytes);
 return 0;
}

このプログラムは 5バイトの領域を割り当てるが、それよりも前のアドレスから読み出しを行おうとする。

$ ./Test3
not allocated memory access at 0xfadfffff from (0xfeee01bf)

HEAP REPORT
  SUMMARY
   total allocated: 5(bytes)  1
   memory leak: 5(bytes)  1

  DETAIL OF MEMORY LEAK
 no.     memory size(byte) created by
   1 0xfae00000          5
Abort (core dumped)

HEAPレポートよりも前にエラーメッセージとアクセスしたアドレス、およびそのとき実行していたコード付近のアドレスが表示される。

最後は Abortして終了する。コアダンプするかどうかは環境設定による。

この例では、割り当てた 5バイトよりも 1バイト小さいアドレスにアクセスしていてアンダーランが検出できたかのように見えるが、そうではない。
直前に別の用途のために割り当てた領域があれば、エラーは検出できない。

注:コアダンプする設定の場合、ダンプに時間が掛かる分 Abortが表示されてシェルに復帰するまで時間を要する。

二重解放の検出例

既に解放済みの領域をもう一度解放しようとしたらどうなるか。
#include <malloc.h>
#include <stdio.h>

int main() {
 char* bytes = (char*)malloc(5);
 free(bytes);
 free(bytes);
 return 0;
}

このプログラムでは、5バイトの領域を割り当てたあと、2回つづけて解放を行おうとする。

$ ./Test4
bad memory release.(already released.)

HEAP REPORT
  SUMMARY
   total allocated: 5(bytes)  1
   memory leak: 0(bytes)  0
Abort (core dumped)

HEAP REPORTよりも前にエラーメッセージが表示され、Abortする。

制約

下記のような制約がある。
  • 現バージョンでは、アンダーラン、オーバランの検出はできない。
  • MMUのメモリ保護機構を必要とする。
  • メモリ割り当て回数の上限は 256 * 1024回。(変更可)
  • ヒープのサイズは 64MB。(変更可)
  • 同じヒープサイズでも通常より早くメモリが枯渇する。
  • main関数が呼ばれる以前に別スレッドが起動された場合、動作は保証されない。

ライセンス (license)

このライブラリは MITライセンスで公開されている。

GPLとのマルチライセンスが計画されていたが、手続きの都合により保留され、とりあえず MITライセンス単独でのリリースとなった。

ダウンロード (download)(下载)

ライブラリのソースは下記からダウンロードできる。

lwd_malloc_1.0.tgz

Solarisと Linux用の静的ライブラリ(いづれも Intel 32bit版)も含まれている。

2013年12月3日火曜日

bash-completion

softies projectの開発環境は Solarisと Ubuntuを使用しているが、先日、Ubuntuを 10.04→ 12.04に変えたところ、何やらいろいろと拙いことが発生し始めた。

makeが通らない

ちょっとした実験をする Main.cというソースがあったとしよう。たとえば次のようなもの。
#include 

int main() {
 printf("Hello, world.\n");
 return 0;
}

これだけなら、わざわざ makefile(や build.xml, pom.xml)を作らなくとも、
$ make Main
cc     Main.c   -o Main

の様にビルドが通る。これはデフォルトで定義されているサフィックスルールだけで何とかなる場合だからだ。

しかし、Ubuntu 12.04にしたところ、
$ make Main
make: *** ターゲット 'Main' を make するルールがありません. 中止.

と表示されてしまう。

makeコマンドで補完ができない

まだ可笑しい所がある。
シェルでmake Mのあと、Tabキーを押しても補完されない。
make Main.cが出ることを期待していたのだが。
使っているシェルが悪いのかと psしてみたが、bashである。
試しに echoとか、lsとか、catとかのあとに Mに続けて Tabキーを押すとちゃんと補完される。
Tabキーの接触不良でもないようだ。
また誰か余計な機能を発明したようだ。

理由は bash-completion

ネットで調べてみると出てきたのは bash-completionという機能。
makeや gitなど、使用するコマンドによって補完の推測方法を変えているようだ。
makeでは、makefileのターゲットを調べ、あるものしか入力できない。
makefile無しの場合のターゲットの推測までは頭が回ってないようだ。
中途半端なヤツ。
bas-completionが有効になってるかどうかは、次のように complete -pを実行して completeルールがゾロゾロと出てくるかどうかで確かめることができる。
$ complete -p
 :
 :
complete -F _route route
complete -o default -o dirnames -F _mount mount
complete -F _badblocks badblocks
complete -F _filedir_xspec lyx
complete -F _filedir_xspec rgvim
complete -F _filedir_xspec timidity
complete -F _filedir_xspec dvitype
complete -F _filedir_xspec dviselect
complete -F _filedir_xspec xdvi
complete -o default -F _longopt uniq
complete -F _root_command sudo
complete -F _command tsocks
complete -a unalias


bash-completionを無効にする

この機能を無効にするには、
sudo apt-get remove bash-completion

とやってアンインストールすれば良いのだが、それだと他のユーザまで影響してしまうので、つぎのようにしてローカルにアンインストールすれば良い。
$ complete -r

アンインストールがうまく行っていれば、
$ complete -p

でなにも出てこない。

bash-completionを無効にすると、makefileにないターゲットや makefile自体が無い状態での補完ができるようになる。

なお、makeでは無効にしたいが、gitでは有効にしておきたいということもできるようだ。



先輩、暮らす図って何ですか?

2013年12月2日月曜日

入れ子関数 その2 (nested function 2)

前回の入れ子関数 その1では、1つのスコープで同一名称の関数を定義してもエラーにはならないが使えないことが確認されたが、入れ子関数に関してはローカル変数にアクセスできるという便利な機能についても触れておきたい。

ローカル変数へのアクセス

次のプログラムは、入れ子関数 getが、その外側の mainのローカル変数 vの値を参照する例である。
#include 

int main() {
 int v = 5;
 int get() { return v; }
 printf("%d\n", get());
 return 0;
}

実行してみよう。
$ make NestedFunctionExample11
cc     NestedFunctionExample11.c   -o NestedFunctionExample11
$ ./NestedFunctionExample11
5

入れ子関数が、関数の中の単なるブロックであるかのように変数にアクセスできることがわかる。

入れ子関数のポインタ

関数のポインタを渡して、その渡した先で vにアクセスできるだろうか。
#include 

typedef int (*function)();

void sub(function f) {
 printf("%d\n", f());
}

int main() {
 int v = 5;
 int get() { return v; }
 sub(get);
 return 0;
}

$ make NestedFunctionExample12
cc     NestedFunctionExample12.c   -o NestedFunctionExample12
$ ./NestedFunctionExample12
5

subの中から呼び出されている関数 fが mainのローカル変数 vにアクセスできている。

subは mainのスタックフレームにアクセスできるだけなのか、自身のフレームも持っているのだろうか。
#include 

typedef int (*function)();

void sub(function f) {
        printf("%d\n", f());
}

int main() {
        int v = 5;
        int get() {
                int u = 3;
                printf("%d\n", u);
                return v + u;
        }
        sub(get);
        return 0;
}

$ make NestedFunctionExample13
cc     NestedFunctionExample13.c   -o NestedFunctionExample13
$ ./NestedFunctionExample13
3
8

これを見ると、自身のスタックフレームを持った上で、その外側にある関数のスタックフレームにもアクセスできるようだ。

外側の関数から復帰後

入れ子関数を定義した関数から復帰してしまったらどうか。
#include 

typedef int (*function)();

void sub(function f) {
 printf("%d\n", f());
}

function getFunction() {
 int v = 899;
 int get() { return v; }
 return get;
}

int main() {
 function f = getFunction();
 sub(f);
 return 0;
}

多分外側の関数のスタックフレームはもう無いからアクセスできないはず。
$ make NestedFunctionExample14
cc     NestedFunctionExample14.c   -o NestedFunctionExample14
$ ./NestedFunctionExample14
3
902

あれ、うまくアクセスできてしまった。

実はこれ、GCC 4.6.0 on SunOS 5.11 (x86)環境ではうまく行ってしまう。(様に見える)

同じコードを GCC 4.6.3 on Ubuntu 12.04 (x86)でやってみると。
$ make NestedFuctionExample14
cc     NestedFuctionExample14.c   -o NestedFuctionExample14
$ ./NestedFuctionExample14 
134513727

ちゃんと滅茶苦茶な値になった。(うまく行かなかったことを安心しているようで妙な表現だが)
Solarisでうまく行ったように見えたのはスタックフレームの内容が残っていたからで、いつもこう行くとは限らない。

たとえば、関数ポインタを取得してから呼び出すまでの間にスタックを使用するなにか別の関数を呼んでしまうと壊れてるはず。
#include 

typedef int (*function)();

void sub(function f) {
 printf("%d\n", f());
}

function getFunction() {
 int v = 899;
 int get() {
  int u = 3;
  printf("%d\n", u);
  return v + 3;
 }
 return get;
}

int main() {
 function f = getFunction();
 printf("Hello, world.\n");
 sub(f);
 return 0;
}

この様に、printfを入れてみよう。
$ make NestedFunctionExample15
cc     NestedFunctionExample15.c   -o NestedFunctionExample15
$ ./NestedFunctionExample15
Hello, world.
Segmentation Fault (core dumped)

値が可笑しくなるだけでなく、アクセス違反が生じてしまった。

2013年12月1日日曜日

入れ子関数 その1 (nested function 1)

例外機構例外機構 その2で紹介した言語 Cでの例外処理の機構はマクロと関数を組み合わせて実現しているが、制約が多くまだ実験の初期段階の域を出ない。

制約のうちの幾つかは finally節に関するものである。
  • finallyは省略できない。
  • 同一スコープの中では finallyが2回以上記述できない。
これは、gccの入れ子関数(nested function)機能の使い方に由来する。

同一スコープの中で複数の入れ子関数を定義した場合になにが起こるか、GCCのオンラインドキュメントNested Functionsで調べてみたが、書かれていない。

そこで、幾つかの実験を行って見た。
使用した環境はつぎの通り。

  • GCC 4.4.3 on Ubuntu 4.4.3-4ubuntu5.1 (x86)
  • GCC 4.6.0 on SunOS 5.11 (x86)

異なるスコープで同じ名称の関数を定義した場合の振る舞い

まず同一スコープで複数回定義した場合の実験の前に、同じ名称の関数を異なるスコープで定義しそれを呼び出した場合になにが実行されるかを試してみる。

次のコードの関数 subが実験コードである。
外側と内側のスコープでは各々 "f(1)"、"f(2)"を表示する関数 fを定義している。
その後、fという名前の関数を呼び出すと呼び出したスコープに一番違い f(2)が呼び出されると思われる。

#include <stdio.h>

void sub() {
 void f() { printf("f(1)\n"); }
 {
  void f() { printf("f(2)\n"); }
  f();
 }
}

int main() {
 sub();
 return 0;
}

ビルドして、実行してみる。
$ make NestedFunctionExample1
cc     NestedFunctionExample1.c   -o NestedFunctionExample1
$ ./NestedFunctionExample1
f(2)

予想通り、f(2)が表示された。
念のため、関数 subを書き換えて、内部の定義が無くとも外部の定義が呼ばれることを確認しておく。
void sub() {
 void f() { printf("f(1)\n"); }
 {
  f();
 }
}

$ make NestedFunctionExample2
cc     NestedFunctionExample2.c   -o NestedFunctionExample2
$ ./NestedFunctionExample2
f(1)

期待通り、外側のスコープで定義した入れ子関数が呼び出された。
内側のスコープで 関数 fを定義する前に fという名前で呼び出しを行なおうとしたらコンパイルは通るのか、通ったとしたら何が呼ばれるのかを試してみる。
void sub() {
 void f() { printf("f(1)\n"); }
 {
  f();
  void f() { printf("f(2)\n"); }
  f();
 }
}

$ make NestedFunctionExample3
cc     NestedFunctionExample3.c   -o NestedFunctionExample3
$ ./NestedFunctionExample3
f(1)
f(2)

コンパイルはエラーにならなかった。自動変数の場合もスコープが違えば同じ名前でもちゃんと区別されるので期待通りだ。
内側の定義を行う前と後では呼び出される関数が異なることが判明した。
これは自然な感じがする。
前方参照を用いて、外側の関数定義を呼び出しより後に定義したらコンパイル、実行はどうなるだろうか。
void sub() {
 auto void f();
 {
  f();
  void f() { printf("f(2)\n"); }
  f();
 }
 void f() { printf("f(1)\n"); }
}

$ make NestedFunctionExample4
cc     NestedFunctionExample4.c   -o NestedFunctionExample4
$ ./NestedFunctionExample4
f(1)
f(2)


おお、すばらしい。

同一スコープで同じ名称の関数を定義した場合の振る舞い

いよいよ、同じ関数名で定義したらどうなるか。
void sub() {
 void f() { printf("f(1)\n"); }
 f();
 void f() { printf("f(2)\n"); }
 f();
}

$ make NestedFunctionExample5
cc     NestedFunctionExample5.c   -o NestedFunctionExample5
NestedFunctionExample5.c: In function ‘sub’:
NestedFunctionExample5.c:11:7: error: redefinition of ‘f’
NestedFunctionExample5.c:9:7: note: previous definition of ‘f’ was here
make: *** [NestedFunctionExample5] Error 1

関数fの再定義はコンパイルエラーとなる。
でも諦めない。なぜなら例外機構では try~catch~finallyを 2個並べてもコンパイルは通っているのだから。
1個目の定義のあと、宣言を入れてみよう。
void sub() {
 void f() { printf("f(1)\n"); }
 f();
 auto void f();
 void f() { printf("f(2)\n"); }
 f();
}

vi NestedFunctionExample6.c
$ make NestedFunctionExample6
cc     NestedFunctionExample6.c   -o NestedFunctionExample6
$ ./NestedFunctionExample6
f(2)
f(2)

コンパイルは通った。
だが、どちらも2個目の定義が呼ばれていて、1個目の定義は無視されている。
これが、例外機構での制約の原因だ。

最初の呼び出しでは1個目の定義のが呼ばれても良いような気がするのだが。

gccのソースを見ていないので憶測だが、同一スコープで同じ名前の関数を定義しようとすると1つ前の実験でわかるように名前の衝突が起きてエラーになるが、
宣言を書けば衝突は回避されるようだ。
しかし、定義の前でも後でも2個目に定義した関数が呼ばれているところを見れば、単純に関数定義を上書きしているのだろう。

宣言は前方参照できるはずなので、定義を呼び出しを逆にしても結果は同じである。
void sub() {
 void f() { printf("f(1)\n"); }
 f();
 auto void f();
 f();
 void f() { printf("f(2)\n"); }
}

$ make NestedFunctionExample7
cc     NestedFunctionExample7.c   -o NestedFunctionExample7
$ ./NestedFunctionExample7
f(2)
f(2)

当然だけど、次の例も再定義でエラーになる。
void sub() {
 auto void f();
 f();
 void f() { printf("f(1)\n"); }
 f();
 void f() { printf("f(2)\n"); }
}

$ make NestedFunctionExample8
cc     NestedFunctionExample8.c   -o NestedFunctionExample8
NestedFunctionExample8.c: In function ‘sub’:
NestedFunctionExample8.c:13:7: error: redefinition of ‘f’
NestedFunctionExample8.c:11:7: note: previous definition of ‘f’ was here
make: *** [NestedFunctionExample8] Error 1

これまで定義が2個の例を示したが、次のように3個以上でも同じことである。
void sub() {
 auto void f();
 f();
 void f() { printf("f(1)\n"); }

 auto void f();
 f();
 void f() { printf("f(2)\n"); }

 auto void f();
 f();
 void f() { printf("f(3)\n"); }

 auto void f();
 f();
 void f() { printf("f(4)\n"); }
}

$ vi NestedFunctionExample9.c
$ make NestedFunctionExample9
cc     NestedFunctionExample9.c   -o NestedFunctionExample9
$ ./NestedFunctionExample9
f(4)
f(4)
f(4)
f(4)

以上の結果から、定義と宣言を分けると同一スコープに同じ名称の関数定義を記述することはできるが、最後の定義のみが有効となることが判明した。

これでは現在の例外機構の案では実用にはならない。

gccのバージョンが変わったら挙動が変わる可能性もある。

2013年11月13日水曜日

非同期シグナルセーフ その2 (asynchronous signal safe stderr library -- experimental)

シグナルハンドラの中から安全に呼び出すことのできるエラー出力関数のセットを紹介する。

printfのような書式指定機能は付いていないため、幾つかの関数をバラバラに並べて呼び出さなければならない。

実は、softiesプロジェクトで使われている mallocライブラリ内部のエラーハンドリングに用いているため、各々の関数には static宣言が付いているが、掲載したものは staticは外してある。(この mallocライブラリについては後日紹介する機会があるかも知れない。)

クラス名などのプレフィックスは付いてないため、使う場合には関数名が干渉しないか注意しなければならない。

コンパイルを通すには unistd.hのインクルードが必要となる。

例によって十分なテストは行われていないため、動作は保証しない。


改行出力

void newline() {
 write(STDERR_FILENO, "\n", 1);
 fsync(STDERR_FILENO);
}


文字列出力

NUL文字で終端された char配列 stringを出力する。
void printString(char* string) {
 if (string == null)
  string = "(null)";

 int length = 0;
 while (string[length] != '\0')
  ++length;
 write(STDERR_FILENO, string, length);
}


バイナリデータ中の ASCII文字列を抽出して出力

char配列 stringの lengthで指定されたバイト数分を出力する。
ASCII文字で無い値はスキップされる。
void printAsciiLetters(char* string, int length) {
 if (string == null)
  string = "(null)";

 int index = 0;
 while (index < length && string[index] >= 0x20 && string[index] < 0x7f)
  ++index;
 write(STDERR_FILENO, string, index);
}

整数出力

符号ありの整数しか扱えない。 int型が符号を含めて 16桁を超える処理系では正しく動作しない。
void printInteger(int value, int length) {
 char buffer[16];
 if (length > 16)
  length = 16;
 int index = 0;
 boolean negative = false;
 if (value < 0) {
  negative = true;
  value = -value;
 }
 do {
  buffer[15 - index++] = (value % 10) + '0';
  value /= 10;
 } while (value != 0 && (length == 0 || index < length));
 if (negative && index <= 15)
  buffer[15 - index++] = '-';
 while (index < length)
  buffer[15 - index++] = ' ';
 write(STDERR_FILENO, &buffer[15 - index + 1], index);
}

ポインタ出力

ポインタを '0x'が先行する 16進数で出力する。 ポインタが 0xを含めて 16桁を超える処理系では正しく動作しない。
void printPointer(void* pointer, int length) {
 char buffer[16];
 long value = (long)pointer;
 if (length > 16)
  length = 16;
 int index = 0;
 do {
  int d = (int)(value & 0xf);
  buffer[15 - index++] = (d < 10)
   ? (d + '0')
   : (d - 10 + 'a');
  value >>= 4;
  value &= 0xfffffff;
 } while (value != 0 && (length == 0 || index < length));
 buffer[15 - index++] = 'x';
 buffer[15 - index++] = '0';
 while (index < length)
  buffer[15 - index++] = ' ';
 write(STDERR_FILENO, &buffer[15 - index + 1], index);
}

2013年11月12日火曜日

非同期シグナルセーフ

ライブラリ関数の多くは非同期なシグナルハンドラの中で使用できない。

printfもその一つ。
プログラムのエラーをシグナルで捕えても、そこで printfなどのライブラリを用いてメッセージを出力することはできない。

たしか System V Release 3.0が出た頃はまだそのような制約は無かったと思う。
その頃は平気で printfを使っていた記憶があるが、いつの日かそのような使い方は禁止されている。
そこで手元にある文献を幾つか調べてみた。

Marc J.Rochkind著、福崎俊博訳
『UNIXシステムコール・プログラミング』
アスキー出版局 1987年(原書は1985年)

第8章 シグナル
シグナルハンドラのクリーンアップ時に fprintfする例が示されている。
また、longjumpの例もある。
シグナルが到着したときに単にフラグをセットし、プログラムがレディになってからそれをチェックする賢いトリックを使うのは悪いテクニックとあり、これは現在の常識とは反対である。


AT&Tユニックス パシフィック発行
『UNIX System V プログラマ・リファレンス・マニュアル 第2版 リリース 3.0』
共立出版 1986年(原書も1986年)

signal, sigset系システムコールの説明があるが、シグナルセーフについては書かれていない。
同シリーズの UNIX System V プログラマ・ガイド リリース3.1は調べていない。


塩谷修著
『実用UNIXシステムプログラミング』
日刊工業新聞社 1986年

第13章 シグナル
SIGINT, SIGHUP, SIGSEGV, SIGFPE, SIGPIPE, SIGALARM, SIGCLDについて、各々 printfを使った例が示されている。
第18章 SetJmp LongJmp
シグナルハンドラから longjmpする例が示されている。


David A.Curry著、アスキー書籍編集部監訳
『UNIX Cプログラミング』
アスキー出版局 1997年(原書は1989年版)

8章 シグナル処理
シグナルのリセット、システムコールの再スタートに関する問題が示されている。
4.2BSDのバークレーUNIXのシグナル機構の説明も示されている。
しかし、例示されているハンドラの内部では printfが使われており、さらにはハンドラの中から longjumpを行う例まで扱われている。


M.R.ホートン著、長尾高広訳
『ポータブルCプログラミング』
トッパン 1990年(原書も 1990年)

12章 シグナル処理ルーチンの設定
元のシグナルのメカニズムには信頼性上の問題があったが、4.2BSD, ANSI C, POSIX, System V release 3では何らかの改良が施されたことが書かれている。
ANSI Cでは、シグナルハンドラでの longjumpの動作が未定義となったことは書かれている。


Bil Lewis, Daniel J.Berg共著、岩本信一訳
『マルチスレッドプログラミング入門』
アスキー出版局 1996年(原書も 1996年)

第6章 オペレーティングシステムの問題
非同期に関する安全性のところで、非同期シグナルの問題をmallocを例に説明している。
非同期シグナル安全な関数の一覧は掲載されていないが、マニュアルを参照するように書かれている。
第10章 プログラミング事例
シグナルハンドラでは、非同期シグナルのハンドリングは安全な sigwaitを使った例を示している。


David R.Butenhof著、油井尊訳
『POSIXスレッドプログラミング』
アジソン・ウェスレイ 1998年(原書は 1997年)

6.6 シグナル
まず、スレッドとシグナルの関係について説明している。
その後で、スレッドコード内部で非同期シグナルを扱うには必ず sigwaitを使用するように書かれている。
ここでは、非同期シグナル安全という説明はされていない。
その後、非同期シグナル安全についての説明と、その関数の一覧が示されている。


ビル・ヒルズ+ダニエル・バーグ著、岩本信一訳
『Pスレッドプログラミング』
プレンティスホール 1999年(原書は1998年)

10章 シグナル
非同期セーフ
mallocの例を挙げて非同期シグナルの問題を提起し、非同期シグナルセーフのカテゴリがあることが説明されている。
非同期シグナルセーフの関数一覧は示されず、ベンダのドキュメントを参照するように書かれている。


こう見ると、スレッドプログラミングが導入されるようになってからの制約の様である。
元々シグナル処理には再入性などの問題があったが、スレッドプログラミングでは mutexを用いた排他処理などが組み込まれていて、シグナルハンドラではそれがうまく機能ささせられないのである。
ライブラリ関数はほとんど使用できないが、システムコールはファイルアクセスであれ、ソケット通信であれ使用できる。



国会議員には国民の先頭に立って震災の復興のために尽力して頂きたい。
綸旨を奏請するのはあらゆる手を尽くしてからだ。

2013年8月12日月曜日

マルチスレッド その2 (multi thread 2)

pthreadでは、スレッドごとにデフォルトサイズのスタックが割り当てられるが、このサイズは pthread属性によって変更することができる。
デフォルトサイズはシステムで定義された値で、OSごとに経験的に決められた一般的なスレッドに充分な値になっている。

しかし、より多くのスタックを必要とする場合は大きな値にしなければならない。

また、使用するアプリケーションがスタックをあまり消費しないことが解かっていて、多くのスレッドを使用したい場合には積極的にスタックサイズを小さくしたい。
ただし、1スレッドあたりのスタックの最小値が決められていて、limits.hファイルの中にある記号名 PTHREAD_STACK_MINに定義されているのでこれを下回らないようにしなければならない。

Solaris 11の場合

Solarisのデフォルトサイズは 1Mバイトである。(OpenMPを使った場合は 32bitシステムでは 4Mバイト、64bitシステムでは 8Mバイト)

スタックサイズを指定しなければならないようなケースは多くないはずである。
スタックのサイズをオーバした場合、なにも警告することなしに隣接スレッドを破壊することが Solarisのドキュメントには明記されている。
値は試行錯誤を経て決められるようになっているようだ。

mprotectを呼び出してレッドゾーンを付加しておけば、オーバした場合にセグメント例外が発生するはずである。
(OpneMPでは-xcheck=stkovf を付けると良いと示されているが、gccを使用した場合にどうするかは不明。)


PTHREAD_STACK_MINを使用したプログラムを gcc 4.6.0でコンパイルしようとすると、limits.hをインクルードしてあるにも係わらずつぎのようなエラーが出てしまう。
error: ‘PTHREAD_STACK_MIN’ undeclared (first use in this function)


ヘッダファイルを調べると、次の条件を満たしていなければならないことが判る。
#if     defined(__EXTENSIONS__) || (_POSIX_C_SOURCE >= 199506L)



そういえば、『鎌坪商店これにて閉店。ちょわーん。』してから半年近く経った。
夏休み子供電話相談室くらいはと期待してたのだが…。

2013年8月1日木曜日

マルチスレッド (multi thread)

当プロジェクトのクラスはマルチスレッドのためのコードが含まれている。
しかし、実装が不完全で限られた使い方でしか動作は保証されていない。
取り分け、シングルトンオブジェクトの排他制御がまともに出来ていないのと、スレッドごとに異なる値を持たなければならない変数が静的変数になっていてスレッド間で共有されているのが課題。


mutexの初期化

オブジェクト固有の mutexは、そのオブジェクトを生成するときに初期化を行えばよい。

問題はシングルトンの場合だ。
static boolean initialized = false;

struct Something object;

  :

 if (!initialized) {
  /* object will be initialized. */
 }

このようなコードは、単一のスレッドから呼び出す場合にのみ動作が保証される。

複数スレッドから同時に呼ばれる可能性がある場合、initializedのチェックは役に立たない。

そこで、mutexにより排他制御を行って見よう。(pthreadの場合。)

#include 

static boolean initialized = false;
static pthread_t mutex;

struct Something object;

  :

 pthread_mutex_lock(&mutex);
 if (!initialized) {
  /* object will be initialized. */
 }
 pthread_mutex_unlock(&mutex);

この処理が呼ばれる度に mutex操作が発生するとオーバヘッドが大きくなるので、それを回避する必要がある場合には外側にもう一段 initializedのチェックを入れると良い。

いづれにしてもこの mutexは事前に初期化されていなければならない。

特定のアプリケーションであれば、mutex(または objectの)初期化が完了してから複数スレッドを起動するように設計すれば十分であるが、ライブラリやフレームワークなどのミドルウェアの場合そのような制約はあてにならない。

そのようなミドルウェアは、mutex(または objectの)初期化を何らかの初期化関数として提供することになるが、仮にアプリケーションが初期化関数を呼んだ後にしかスレッドを起動しなかったとしてもまだ不十分である。

アプリケーションだけでは初期化の順序を守れないケースがあるからである。


2011年のことだったと思う。開発していた組み込み製品のデバッグ担当者から彼らの上司に『main関数にブレークポイントを設定したのに効きません。』とか『main関数よりも前に関数が呼ばれます。』という電話連絡が入った。

組み込みの開発では言語 Cを使用することが多いが、そのプロジェクトは C++が使われていたのだ。

Cでは、静的変数や外部変数を初期化するときに、main関数を呼ぶ startupルーチンの中から 変数領域の初期化が行われる。(bss領域はゼロフィルされ、data領域は初期値がコピーされる。)

ユーザのコードが mainよりも前に走り始めることは無い。
しかし、C++では定数で初期化するだけでなく、コンストラクタを起動したり、関数の結果を初期値に出来る。
#include 
#include 

std::string message("Hello, world.");

int len = message.length();

int main() {
        std::cout << message << std::endl;
        std::cout << len << std::endl;
        return 0;
}
C++のときだけではない。gccの拡張機能である constructor属性を関数に指定すると、main関数よりも前に実行される。
アプリケーションが main関数よりも前に何もしないつもりでも、使用しているライブラリが何かスレッドを起こして、なにかするかも知れない。

pthread_once

pthreadの場合、pthread_onceを使えば、そのプロセスでただ一度だけ処理を実行することが保証される。
mutexの初期化を気にせずにシングルトンを実現できる。
#include 

struct Something object;

void initializer() {
 /* object will be initialized. */
}

 :

 pthread_once(initializer)
 /* use object */

これで簡単確実にオブジェクトを初期化することができるようになった。

PTHREAD_MUTEX_INITIALIZERによる mutexの初期化

多くの場合のオブジェクトの初期化は pthread_onceでうまくいくが、pthread_onceでロックしてしまう場合がある。
softiesプロジェクトでは、Objectクラスはは Classクラスのインスタンスを持っていてこれは Objectを使用する前に初期化されなければならない。
その Classクラスは Objectクラスのサブクラスであり、Classクラスを初期化する場合にはそれに先立って Objectクラスを初期化しなければならない。
このように循環している場合、pthread_onceではロックするのである。
勿論、Object用の初期化関数と Class用の初期化関数は分けてあり、pthread_onceは各々別々に呼び出されるが、その中で相手側を呼び出すことになる。 (アプリケーションから明示的にフレームワーク、ライブラリの初期化関数を呼び出すようにしていないため、順序固定で初期化されるようなコーディングは行っていない。)
Objectの pthread_onceで指定された初期化関数の中から、Classの pthread_onceで指定された初期化関数を呼ぶところまでは何の問題もない。 (順序が逆になってもそうだ。)
ところが、その Classの初期化関数から再び Objectの pthread_onceを呼び出すと pthread_onceの呼び出しから復帰しなくなる。
そこで、何とかもう一度 mutexを使う方法を検討してみた。 普段、 mutexを使用するときは pthread_mutex_init関数を使用していた。そうしなければならないと思っていた。
しかし、ドキュメントをよく読むと、PTHREAD_MUTEX_INITIALIZERという初期値があるではないか。
つぎのように定数として mutexを初期化できる。
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
これなら、main関数よりも前に startupルーチン関連以外の処理が走るよりも前に mutexが使えるようになる為、最初のような initializer変数と組み合わせてオブジェクトの初期化を行うことができる。
Classクラスと Objectクラスのような初期化が循環していても、どちらの初期化から先に行おうとしたときでもロックすることなく処理を行うことができるようになった。
ただし、ヒープメモリから割り当てた領域に mutexを配置したり、pthread_mutexattr_tを指定した初期化したい場合には PTHREAD_MUTEX_INITIALIZERは使えない。
数ヶ月前から、電車のディスプレイに中日新聞ニュースが流れないことがしばしばあり、折角μチケット買ったのに残念な思いをする。