2009年1月31日土曜日

つまらないけど重要

趣味で開発している時には問題にならないけど、仕事で開発となるとついてまわるのが仕様書の問題。特に大きい会社では仕様書を書くのに割く時間は馬鹿にならない。ソフトウェアの場合、doxygenみたいなツールを使えば、わりとつまらない図を書いたりする時間も削減できたりするんだけど、ハードウェアだとなかなか良いツールがない(知らないだけ?)。

という事で、ソースから図を自動生成しようかな・・・とか考える。とりあえず
  • スクリプトでお手軽かつ適当に生成したい(テキストベースが楽ちん)
  • Wordに貼付けた際に汚くならない(ベクトルデータが良い)
って方針ですか。そう考えて思い出したのがSVG。XMLが出てきた当時、どう使うと便利なんだろう・・・とか思い悩んだもんですが、今や空気みたいなもんですね。出力するにはテキストが楽。でも入力時に解析するにはバイナリの方が楽。。。そんなわがままを受け入れてくれるのがXMLのうれしいところです。

さっそくSVGの仕様書(*1)を眺めてみたわけですが、結構シンプルで簡単。仕様書にも図付きでサンプルが載っていて、みながら数分で基本的な図形は作れるようになります。あとは、ここで理解したタグをスクリプトから自動生成してやるだけ。RTLからの変換にはRubyを使いました。って事でできたのが下の図。こんな馬鹿げた図でも、VisioやExcelでヘロヘロ作っていたらどんどん時間が飛んでいきます。くだらない事にタラタラ時間使って「忙しい」とか言う人間にはなりたくないものです。作業の効率化は研究者、技術者にとって永遠の課題です。

ソースはこんな感じ。お手軽ですよね。
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g>
<text fill="black" x="62" y="50" font-size="6">RI2S1_DATA</text>
<line stroke="black" stroke-width="1" x1="50" y1="53" x2="250" y2="53"/>
<polygon fill="black" stroke="black" stroke-width="1" points="244,50 244,56 250,53"/>
</g>
... 中略 ...
</svg>
生成したSVGのプレビューや変換にはInkscape(*2)を使いました。Ubuntuからはapt-getでヘロっとインストールできました。EPSへの変換ができます。SVG Import Filter(*3)を使えばOpenOffice経由で色々と変換も可能。いきなりWMFとかにも出来るんだけど、グループ化を解除すると黒い豆腐になる問題があって、どういう経路で変換するのがベストかは今のところ模索中です。

*1: Scalable Vector Graphics (SVG) 1.1 Specification
*2: Inkscape
*3: SVG Import Filter for OpenOffice 2.0

2009年1月4日日曜日

LinuxでDLLを流用する

XMMSのpluginなんかでは昔から使われていた方法ですが、wineを使ってDLLを読み込む方法があります。・・・というか、ありました、なのかな?

古いwineの実装では、PROCESS_InitWineとか使ってexec的な事ができたんだけど、今は実装が変わっちゃってるみたい。

wine/library.hを見ると、wine_dll_loadとかwine_dlopenなんて関数があって期待しちゃうんですが、こいつらはwine_initを叩いた後でないと動作しません。ではwine_initを・・・とか思うと、ntdll.dll内で落ちます。実は(少なくとも今現在のcurrentに関しては)wine_initだけでは不十分で、wine_pthread_set_functionsという関数を使って、スレッド回りの処理をする関数群をまとめた関数テーブルをセットしてやる必要があります。この関数はlibrary.hには見えてこないけど、objdump -T libwine.soで確認でき、実際にextern指定してあげればリンクして呼び出すことができます。ではここでセットするwine_pthread_functions構造体を用意すれば良いのか・・・と思って準備を進めると・・・必要となる関数は、
  • init_process
  • init_thread
  • create_thread
  • init_current_teb
  • get_current_teb
  • exit_thread
  • abort_thread
  • pthread_sigmask
これだけあります。プロセスとスレッドの管理はまぁpthreadをラッパーを書いてあげれば済む話なんだけど、TEB関連はちょっとヘビー。細かいことは1年くらい前にmixiで書いたのでそちらを参照してもらうとして、ようはセグメントレジスタをいじって、特殊なアドレスにスレッド情報を格納しておく必要があるって事。ここまでいくと、wineコマンドをスクラッチから書くのとたいしてかわらない手間だという事がわかる。

そうすると、いっそのことexecでwineを叩けばいいじゃんって話になって、結局は必要なDLLを読むEXEを作ってあげて、forkした先の子供でwineをexec。あとは親子でプロセス間通信をして用を足す、って流れに。とりあえずこの方向でやる場合、pipeとmmapで通信できる事は確認してあります。

サンプルコードを書くとこんな感じ。どっちかの方法というよりかは組み合わせて通信するんだろうな。pipeで同期化も兼ねてコマンドを指示。データはmmapの共有メモリにて、って。pipeだとデータの管理が大変だし、共有メモリのポーリングは辛そう。

他にも自作のWinBeを使うって方法もあります。現在、DarwinとLinuxにも移植してあって、勝手しったる〜ってやつで。こっちだとWin32とLinux両方の実行環境を1つのプロセス内に混在させる事ができるんですが・・・如何せん、ひとりで作ったものなので、wineに比べると圧倒的に互換性が低いのが難点です。それでもcodecやら、リンクしたいなと思う類いの物なら大抵動いてくれるので、それなりに重宝します。


-------- winmain.cpp --------

#include <windows.h>
#include <stdio.h>

int WINAPI
WinMain
(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HANDLE hFile = CreateFile("shared_file",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE != hFile) MessageBoxA(NULL, "CreateFile: success", "TEST", MB_OK);
HANDLE hShare = CreateFileMapping(hFile,
NULL,
PAGE_READWRITE,
0,
1024,
"shared_memory");
if (INVALID_HANDLE_VALUE != hShare) MessageBoxA(NULL, "CreateFileMapping: success", "TEST", MB_OK);
char *sharedBuffer;
sharedBuffer = (char *)MapViewOfFile(hShare,
FILE_MAP_WRITE,
0,
0,
1024);
if (NULL != sharedBuffer) MessageBoxA(NULL, "MapViewOfFile: success", "TEST", MB_OK);

snprintf(sharedBuffer, 1024, "this message is send via mmap");
MessageBoxA(NULL, "Write to Memory Mapped File", "TEST", MB_OK);

DWORD dwWrote;
BOOL rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
"hello from exe",
15,
&dwWrote,
NULL);
if (rc) MessageBoxA(NULL, "WriteFile to STDOUT: success", "TEST", MB_OK);
else MessageBoxA(NULL, "WriteFile to STDOUT: failed", "TEST", MB_OK);

char readBuffer[256];
DWORD dwRead = 0;
rc = ReadFile(GetStdHandle(STD_INPUT_HANDLE),
readBuffer,
255,
&dwRead,
NULL);
readBuffer[dwRead] = 0;
char message[512];
snprintf(message, 512, "ReadFile from STDIN: %s", readBuffer);
MessageBoxA(NULL, message, "TEST", MB_OK);

return 0;
}


-------- main.cpp --------

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/mman.h>

int
main
(int argc, char **argv)
{
int elf2exe_pipefd[2];
int exe2elf_pipefd[2];

if (-1 == pipe(elf2exe_pipefd)) perror("pipe: elf2exe");
if (-1 == pipe(exe2elf_pipefd)) perror("pipe: exe2elf");

pid_t cpid = fork();
if (-1 == cpid) perror("fork");

if (cpid == 0) {
// child (exe)
close(STDIN_FILENO);
close(STDOUT_FILENO);
if (-1 == dup2(elf2exe_pipefd[0], STDIN_FILENO)) perror("dup2: elf2exe");
if (-1 == dup2(exe2elf_pipefd[1], STDOUT_FILENO)) perror("dup2: exe2elf");
close(elf2exe_pipefd[0]);
close(elf2exe_pipefd[1]);
close(exe2elf_pipefd[0]);
close(exe2elf_pipefd[1]);

char *exec_argv[] = {
"wine",
"test.exe",
NULL,
};
execvp(exec_argv[0], exec_argv);
// no return
} else {
// parent (elf)
close(elf2exe_pipefd[0]);
close(exe2elf_pipefd[1]);
int fd_rd = exe2elf_pipefd[0];
int fd_wr = elf2exe_pipefd[1];

char buf[256];
ssize_t size = read(fd_rd, buf, 255);
if (-1 == size) perror("read");
buf[size] = 0;
printf("read from exe: %s\n", buf);
write(fd_wr, "hello from elf", 14);

int fd_map = open("shared_file", O_RDWR);
if (-1 == fd_map) perror("open");
char *shared = (char *)mmap(NULL, 1024,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd_map, 0);
if (NULL == shared) perror("mmap");
puts(shared);
fflush(stdout);
wait(NULL);
puts("parent process exit");
}
return 0;
}

2009年1月3日土曜日

Subversionでクライアントが固まる障害

年末から、Subversionでクライアントがupdate中にハングアップしたり、redMine側のレポジトリブラウザを開くとredMine自体が応答しなくなる、といった症状に悩まされていました。

色々調べてみたんですが、Subversionのデータベースが壊れた場合の方法しか出てこない。うちもこれなのかなぁ・・・と思ってレポジトリを直接覗いて調べていたら・・・

% ls -hl .../db/revs/
:
:
:
-rw-r--r-- 1 apache apache 1.4G 12月 23 04:49 5665
:
:
:

なぬ? 1.4Gの差分ってなんだよー・・・orz
ってことで、原因は大量ファイルの一括commitでした。さらに調べると、この時commitされたファイルのうち1つが650MBちかくある。・・・こいつが原因か!! むぅ・・・もう少し考えてcommitして欲しいなぁorz よくよく見るとcommitしてあるファイルを日にち付けてtar.gzで固めただけっぽいし。バージョン管理してるんだから、そういうの不要じゃん。

原因に気づき、update中のクライアントをtcpdumpで追いかけてやると、確かにハング中もHTTPで大量データのやりとりを続けてたorz 仕方ないので「でかすぎて有害なので消すよ」とコメントを付けてtrunkからremoveしました。これでredMineのレポジトリブラウザも生き返るかなぁ・・・。途中経過を内部で持ってたらアウトかもなぁ。最初のアクセスに凄く時間がかかった記憶があるので、内部で差分を変換してデータベース化してそうな予感なんだけど・・・。

2009年1月2日金曜日

お金をかけずにSystemVerilogを使ってみる

設計に関して言えば、まだまだ他のツールチェインとの相互運用の面で問題が起きやすく、手放しに喜ぶ事のできないSystemVerilogですが、検証に関して言えば逆にVerilogなんかじゃやってられません。会社で使う分にはライセンスに困らないので普通にIUSとかQuestaを使えば良いのだけど、個人で使うとなると・・・わりと選択肢がないです。

フリーなVerilog処理系で有名なのはIcurus Verilog。ウェブなんかではSystemVerilogにも対応してるとか書いてるページもあるんですが、それは嘘。引数の互換性を持たせるためにVerilogのバージョンを指定できるようになってますが、パーザは引数を見ずに、全てのモードで同じ動作をします。

という事で商用の評価版として重宝しているのがModelSim。ModelSim Xilinx Edition-III Starterが制限付きながらも利用できます。Windows版しか利用できないのが残念ですが・・・。あくまでインストールしたコンソール端末だけで利用せよ、との意図で、リモートデスクトップからログインしているとライセンス違反で起動できません。そうなると、Cygwinを入れてsshdを立てておき、sloginしてコマンドを叩く、って使い方をするしかありません。もちろんちょっとした試験にはこれで十分なんですが、波形を表示しようとするとちょっと面倒。wlfをvcdに変換してscpしたり、gtkwaveを使ったり。あるいはVNCを使うのもありですが。。。もう少しなんとかならんかなぁ、って事でLinux+wineにインストールしてみました。って事で、以下書きメモです。

  1. Configure Wineのアプリケーション設定でWindowsのバージョンを「Windows 98」に設定。
  2. Configure Wineのドライブ設定でCドライブの詳細設定を選びシリアルを適当に設定(8桁の[0-9A-F]です)。
  3. インストーラから標準インストール(mxe_3_6.3c/setup.exeを使いました)。
  4. ライセンス申請
    • インストーラからの流れでうまくいかない場合、Modeltech_xe_starter/lic_request.txtの6行目に書かれているURLを叩けばライセンス申請に入れる
    • lic_request.txtで最後から2番目の行に「Disk Serial Number」の行がなければシリアルの取得に失敗してるので、上記のURLのQueryにも「ds=xxxxxxxx」(xxxxxxxxは2で設定したシリアル番号)を追加してやる必要あり。
こんな感じでうまく動いてそうです。あとはvlib.exe、vlog.exe、vsim.exeあたりに

--- /usr/local/bin/vlib ---
#!/bin/sh
exec wine $HOME/.wine/drive_c/Modeltech_xe_starter/win32xoem/vlog.exe $*
------------
こんな感じのラッパーを用意してやれば、ほぼLinux版のつもりで使えそうです。とりあえずovm-2.0.1のexample/tlm/tlm_fifoが動くのは確認しました。

そんなこんなで、近々OVMあたりも紹介したいなぁ、と思ってます。

追記(2009/1/4):バージョン設定についてはmodelsim.exe、vsim.exe、vish.exeあたりを個別に行っておけば、Default Settingsは変えずにOKっぽいです。