オブジェクトをロックし、クリチカルセクションを実行したあと、確実にロックを解除するすることが言語でサポートされている。
そのような機構がない言語の場合、つぎのように記述するだろう。
lock();
// critical section
unlock();
クリチカルセクションが 1~2行程度ならよいが、長くなると何が起こるか分からない。
うっかり、この中で
if (hasError)
return errorCode;
などとやってしまうとロックが解除されず、処理がとまってしまうか、再びロックしようとして多重ロックしてしまうかも知れない。
今回のチャレンジは、次のように関数を記述することだ。
void synchronized function(Something this) {
// critical section
}
Javaでは synchronizedなメソッドのほかに、synchronizedブロックが記述できるが、ここでは関数だけを対象とする。
synchronizedな関数に入るときオブジェクトをロックし、出るときにアンロックする。
これを実現するための課題は、
- 関数に入るときと出る時に処理を行なえること。
- 関数に synchronized属性を持たせること。
- 関数が synchronizedを持って意いるか判断可能なこと。
- ロックするオブジェクトを認識できること。
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することはできない。
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の略ですから、何でもアリです。 お許しください。
0 件のコメント:
コメントを投稿