January 2011

この記事は カーネル/VM Advent Calendar : ATND のために書かれました。

カーネルネタとか思いつかなかったので、今日はGNU GLOBALの話をするよっ。

このGLOBALさん、emacsからのタグジャンプがetagsより賢いのがお気に入りで(特に同名関数がたくさんあるばあい、etags.elは問答無用で最初にマッチした関数にジャンプするので全然役に立たない)長いこと愛用してる。
で、最近困ってたのがrubyの開発しててrubyとC言語を行ったり来たりするので、ruby上でついいつものクセでC-. とか押して「イラッ」とかしてた。だってもうクセになってるんだもん

で、ソース見たら案外拡張が簡単そうだったのでさくっと対応してみた。2時間いらなかったんじゃないかな。調査時間含めても。いや、まじめにやるとRubyの構文解析は死ねるんだけど、なにせ今がgrepでしのいでいるぐらいだから、どんだけバカでもこれより下はないという・・・


そんなわけで、さっそく ダウンロードページ みながら、CVSからチェックアウトする。
ftpからダウンロードしてはいけない。そこには configure.ac とか Makefile.am とかが入ってなくて二度手間だ。

なお、Linuxでは普通にビルドするとgtags実行時に毎回以下の警告がでて非常にうっとうしい。

Warning: POSIX sort program not found. If available, the program will be speed up.



これはglobalのconfigureがsortコマンドが /usr/bin にあると決めうちしているが、Linuxでは
/bin にあるとか、そういう問題

僕はめんどくさいので configure.ac を直接書き換えたけど、たぶん正しいのは sort -k が動くかどうかを
configure中で実際にsortコマンド叩いてみて確認する方法。やらなかったけど


でまあ、このへん 見ながらおもむろに予約語一覧をガーーーっとコピペして ruby_res.in ファイルつくる。


alias word
and word
begin,BEGIN word
break word
case word
class word
def word
defined word
do word
else word
elsif word
end,END word
ensure word
false word
for word
if word
in word
module word
next word
nil word
not word
or word
redo word
rescue word
retry word
return word
self word
super word
then word
true word
undef word
unless word
until word
when word
while word
yield word



たぶん、gperfの制限だと思うけどglobalは予約語リストに書いた名前がそのままCのマクロ名の一部として
転用されるため"="とか"?"とかみたいな記号が使えない。
おかげで defined? のかわりに defined を予約語に登録するはめになったけど多分大過なかろう。こういうのでハマるのは凝ったことしたい人だけだ。・・・・たぶん

あとはパーサーだけど、まとまな構文解析はスッパリあきらめて、出現したシンボルを
何も考えずにタグファイルに書き込んでいくというアホアホ関数を作成


void
ruby(const struct parser_param *param)
{
int c;
int level; /* brace level */
const char *interested = NULL;

if (!opentoken(param->file))
die("'%s' cannot open.", param->file);
while ((c = nexttoken(interested, ruby_reserved_word)) != EOF) {
switch (c) {
case SYMBOL:
PUT(PARSER_REF_SYM, token, lineno, sp);
break;
case RUBY_CLASS:
case RUBY_MODULE:
case RUBY_DEF:
if ((c = nexttoken(interested, ruby_reserved_word)) == SYMBOL) {
PUT(PARSER_DEF, token, lineno, sp);
}
break;
default:
break;
}
}
closetoken();
}


あとは適当にMakefileやらparser.cのlang_switchやらを適当に追記していくだけ。

https://github.com/kosaki/gtags にソース入れといたんで興味ある人はどぞ。


余談1: あ、aliasの対応わすれてることに今気づいた。あとで直しておこう

余談2: 「接頭辞$, @、@@が先頭についたものは予約語 とは見なされません。」のルールは難しそうだったので未実装。てゆーか、そこまでして予約語変数に使いたい人いないよ(>_<

余談3: ほんとうはCのソースで rb_define_method() 関数読んだらタグファイルに登録とかもやってみたかったんだけど、うーん、C parserのgenericなルートを書き換えずにruby用のタグを作るときだけHook仕込むいい方法が思いつかんかった。global plug-in の仕組みだと総入れ替えっぽいから激しくメドイ気配が濃厚なんだよなぁ

余談4: プラグインといえば。globalには任意のコマンドを呼び出せるプラグイン機構があるので頑張ればripperベースのまっとうなrubyパーサを呼び出すこともできなくもないような気がした。もちろんripperを勉強するのがメドイのでやらなかったが。

余談5: 今回の調査中に気づいたのだけど、最近のバージョンアップでタグ作成時に各ソースファイルを2回ずつ舐める悪癖が修正され gtags コマンドの動作が劇的に開眼している。
どのぐらい速くなったかというと

global-5.7.5-1.fc11

% time gtags
gtags 142.60s user 48.30s system 50% cpu 6:19.00 total



global-5.9.3

% time gtags
gtags 140.16s user 42.14s system 80% cpu 3:47.16 total



圧倒的じゃないか我が軍は!
これに気づいただけでも、今回の作業をした価値があったよ。
このエントリーをはてなブックマークに追加

Rubyのconfigure.inには以下のように、*BSD以外は強制的にdaemon(3)がないと判定させる
あやしげなロジックがある。


dnl Checks for libraries.
AS_CASE(["$target_os"],[*bsd*|dragonfly*],[],[ac_cv_func_daemon=no])


gitで履歴をしらべると、元々Mac OS X だけが腐ってる扱いだったのだが、
後に一般化されている。困ったことに理由はまったく記載されていない。
それをいったら元々のMacOS X が腐ってるという扱いにした理由も書いてないんだけど。


さて、本当かどうか分からないがある人から、Linuxのdaemon(3)は本来二回
forkが必要なところを一回しかforkしてないので腐ってる。それが原因ではないか
と教えて貰った

https://www.codeblog.org/blog/akr/200511.html

ほー。こんな話があったのか。しらなんだ。


ちなみにLinuxのカーネルのソースコード読める人は以下のあたりを読むと良い

kernel/sys.c

SYSCALL_DEFINE0(setsid)
{
struct task_struct *group_leader = current->group_leader;
struct pid *sid = task_pid(group_leader);
pid_t session = pid_vnr(sid);
int err = -EPERM;

write_lock_irq(&tasklist_lock);
/* Fail if I am already a session leader */
if (group_leader->signal->leader)
goto out;

/* Fail if a process group id already exists that equals the
* proposed session id.
*/
if (pid_task(sid, PIDTYPE_PGID))
goto out;

group_leader->signal->leader = 1;
__set_special_pids(sid);

proc_clear_tty(group_leader);

err = session;
out:
write_unlock_irq(&tasklist_lock);
if (err > 0)
proc_sid_connector(group_leader);
return err;
}


drivers/char/tty_io.c

static int tty_open(struct inode *inode, struct file *filp)
{
(snip)
mutex_lock(&tty_mutex);
tty_lock();
spin_lock_irq(¤t->sighand->siglock);
if (!noctty &&
current->signal->leader &&
!current->signal->tty &&
tty->session == NULL)
__proc_set_tty(current, tty);
spin_unlock_irq(¤t->sighand->siglock);
tty_unlock();
mutex_unlock(&tty_mutex);
return 0;
}



ためしに、Solarisのlibcのdaemonを見てみると・・・・
おお、たしかに二回forkしとる。

http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libc/port/gen/daemon.c


42 int
43 daemon(int nochdir, int noclose)
44 {
45 int retv, fd;
46
47 /*
48 * By the first fork+setsid, we disconnect from our current controlling
49 * terminal and become a session group leader.
50 */
51 retv = fork();
52 if (retv == -1)
53 return (-1);
54 if (retv != 0)
55 _exit(EXIT_SUCCESS);
56 if (setsid() == -1)
57 return (-1);
58 /*
59 * By forking again without calling setsid again, we make certain
60 * that we are not the session group leader and can never reacquire
61 * a controlling terminal.
62 */
63 retv = fork();
64 if (retv == -1)
65 return (-1);
66 if (retv != 0)
67 _exit(EXIT_SUCCESS);
68
69 if (nochdir == 0)
70 (void) chdir("/");
71
72 if (noclose == 0) {
73 /*
74 * Missing the PRIV_FILE_READ privilege may be one of the
75 * reasons that prevent the opening of /dev/null to succeed.
76 */
77 if ((fd = open("/dev/null", O_RDWR)) == -1)
78 return (-1);
79
80 /*
81 * Also, if any of the descriptor redirects fails we should
82 * return with error to signal to the caller that his request
83 * cannot be fulfilled properly. It is up to the caller to
84 * do the cleanup.
85 */
86 if ((fd != STDIN_FILENO) && (dup2(fd, STDIN_FILENO) < 0)) {
87 (void) close(fd);
88 return (-1);
89 }
90 if ((fd != STDOUT_FILENO) && (dup2(fd, STDOUT_FILENO) < 0)) {
91 (void) close(fd);
92 return (-1);
93 }
94 if ((fd != STDERR_FILENO) && (dup2(fd, STDERR_FILENO) < 0)) {
95 (void) close(fd);
96 return (-1);
97 }
98
99 if (fd > STDERR_FILENO)
100 (void) close(fd);
101 }
102 return (0);
103 }



・・・しかしだ。cross referenceでサーチしていて分かったのだが、OpenSolarisの場合付属コマンドが
*BSDからもってきたdaemon()を自分で抱えてるケースがいっぱいあって、libcだけ真面目に二回
forkしていても現実的には全然安心できないというのがトホホである。

そういうわけで、Linuxのlibcのdaemonはいまいちイケテないってことと、SolarisのlibcはまともだけどSolarisの
ユーザランドのコマンドはまともとは限らないって事が分かった。

だから何って感じだが
このエントリーをはてなブックマークに追加

↑このページのトップヘ