Linux vs. *BSD
del comment
前回、カーネルの統計って事で、ソース行数とオブジェクトファイルのサイズの関係とか調べた。その時ソースの行数はさらっと wc -l *.c してしまった。これでは、コメント込みの行数になってしまう。そこで、さらっとコメント除去の方法を調べてみた。
C言語のコードからコメントをまるっと削除する for gcc
gccに特化したやつはさておき、自作してる方が多い。手頃な例題なんですかね。オイラーもやってみるか。但し、なるべくコードを書かない方法で。。。
まずは、テストデータだな。
vbox$ cat -n t.c 1 /* 2 * comment 1 3 */ 4 5 // commet 2 6 main(){ 7 /* comment 3 */ 8 printf("Hello\n"); // comment 4 9 }
こいつを cc の -E オプション付でコンパイル。結果を表示。
vbox$ cc -E t.c | cat -n 1 # 1 "t.c" 2 # 1 "<built-in>" 1 3 # 1 "<built-in>" 3 4 # 341 "<built-in>" 3 5 # 1 "<command line>" 1 6 # 1 "<built-in>" 2 7 # 1 "t.c" 2 8 9 10 11 12 13 main(){ 14 15 printf("Hello\n"); 16 }
どうやら、コメントが削除された時は、空行になるみたい。それから冒頭に # でコメントが入っている。ならば、これらも削除すればいいな。
vbox$ cat delcom.sh #! /bin/sh cc -E t.c | sed -e '/^$/d' | sed -e '/^# */d'
例によって、旧原人のスクリプトです。
vbox$ sh delcom.sh main(){ printf("Hello\n"); }
空行はさっと削除してるけど、それで委員会。ええ、勿論、空行はコメントの一種ですから。
これで完成? と思ったら甘いぞ。ソースに #include を入れてみんしゃい。展開されたものが、ごそっと入ってしまって、行数が膨大になる。ならば、事前に #include は削除だな。
#! /bin/sh # delete comment Usage: delcom.sh c-srcfile cat $1 | sed -e '/#include */d' > /tmp/${$}.c cc -E /tmp/${$}.c | sed -e '/^$/d' | sed -e '/^# */d' rm -f /tmp/${$}.c
vbox$ wc kern_xxx.c 151 608 4348 kern_xxx.c vbox$ /tmp/delcom.sh kern_xxx.c | wc 25 68 518
冒頭に33行のお約束のクレジット・コメントが入っている。それを除いたとしても、親切な解説が入っているのね。空行もコメントですって、よく分るよ。
余りに削られ過ぎているので、原本と見くらべてみたら、#ifdef xxx にコンパイラーは反応しちゃってる。これは、このコンパイラー任せには解決出来無いな。ショボンな気分ですよ。
待て、そもそも #include だけを特別扱いしてるっておかしいぞ。# は、コンパイラーに取って特別扱いする目印なんだから、それをやめさせてしまえば良いと思うよ。無害な _ にでも変更しちゃえ。
#! /bin/sh # delete comment, Usage: delcom.sh c-srcfile cat $1 | sed -e 's/#/_/' | cc -E - | sed -e '/^$/d' | sed -e '/^# */d'
どうやら、妥当そうな結果が得られた。
sakae@deb:/tmp$ ./delcom.sh kern_xxx.c | wc 94 293 2393 sakae@deb:/tmp$ ./delcom.sh kern_xxx.c | less _include <sys/param.h> _include <sys/systm.h> _include <sys/kernel.h> _include <sys/reboot.h> _include <sys/sysctl.h> _include <sys/mount.h> _include <sys/syscallargs.h> int rebooting = 0; int sys_reboot(struct proc *p, void *v, register_t *retval) { struct sys_reboot_args *uap = v; int error; if ((error = suser(p)) != 0) return (error); _ifdef MULTIPROCESSOR sched_stop_secondary_cpus(); KASSERT(CPU_IS_PRIMARY(curcpu())); _endif reboot(SCARG(uap, opt)); return (0); } :
今、Debianでセッションしてるんで、gcc方言も試してみる。
sakae@deb:/tmp$ gcc -fpreprocessed -dD -E kern_xxx.c | wc 120 296 2436 sakae@deb:/tmp$ gcc -fpreprocessed -dD -E kern_xxx.c | less # 35 "kern_xxx.c" #include <sys/param.h> : #ifdef MULTIPROCESSOR sched_stop_secondary_cpus(); KASSERT(CPU_IS_PRIMARY(curcpu())); #endif reboot(SCARG(uap, opt)); return (0); } :
冒頭にごみが一行入っているな。ソースオリジナルな空行は、そのまま保存されてるって特性になった。空行はごみですって訴えておこう。まあ、目的によりさまざまだけど。。。
こやつを本当のノーコメント・アプリとして使うなら、このままでは駄目だ。コンパイラーのプリプロセスのターゲットから逃れる為、# を _ に変更した。ならば、最後にそれを戻しておかないとね。で、安易な選定 _ が問題。
ソースにスネーク・ケースの関数名やら変数名が潜んでいる。例えばこんなの、 days_in_month(i)
。これを安易に戻すと大変な事になる。そう、_ なんてのを使っちゃ駄目って事。
#! /bin/sh # delete comment, Usage: delcom.sh c-srcfile cat $1 | sed -e 's/#/@/' | cc -E - | sed -e '/^$/d' | sed -e '/^# */d' | sed -e 's/@/#/'
オイラー的には、永久保存版だと思うので、/usr/local/binに鎮座させておいたよ。
尚、空行は全部削除してるけど、それじゃあんまりだ。せめて、連続する空行だけは、一行に圧縮したいと言う要求が有るかもしれない。そんな場合は、空行を削除してるsedの行をuniq に変更すれば良い。
Linux vs. *BSD
折角ノーコメアプリを作ったんだから、少し利用してみる。
色々なOSのカーネルdirの中にあるCのソースで、ソースの行数、コメントを除いたいわゆるノーコメント行数、割合を求めてみる。リナの場合はサブdirが切ってあるので、その中のソースも含めた。
for f in *.c */*.c do all=`cat $f | wc -l` nc=`delcom $f | wc -l` ps=`echo "scale=2; 100 * $nc / $all" | bc` printf "%7d%7d%7.1f %s\n" $nc $all $ps $f done
こんな、一生に一度のスクリプトで採取。全体像ってか、ファイルの個数ってか母集団の状況。
sakae@deb:/tmp/work$ wc -l * 218 freebsd 347 linux 102 openbsd
OpenBSDは小粒で、追い掛けるにはうって付って分るな。 じゃ、小さい方から。
OpenBSD Rev 7.2
sakae@deb:/tmp/work$ sort -k 2 -n openbsd nc lines % name -------------------------------------- 20 59 33.9 sysv_ipc.c 26 66 39.4 exec_conf.c 37 77 48.0 uipc_proto.c 49 81 60.5 dma_alloc.c : 1880 2496 75.3 tty.c 2043 2540 80.4 kern_sysctl.c 1847 2598 71.1 subr_witness.c 2529 3453 73.2 vfs_syscalls.c
FreeBSD Rev 13.1
24 30 80.0 stack_protector.c 4 33 12.1 imgact_elf32.c 4 33 12.1 imgact_elf64.c 12 42 28.6 genoffset.c : 3565 6017 59.2 subr_bus.c 4327 6132 70.6 vfs_cache.c 5009 7104 70.5 vfs_subr.c 9849 11078 88.9 systrace_args.c
Linux linux-5.10.144/kernel
4 10 40.0 trace/trace_kprobe_selftest.c 10 15 66.7 trace/trace_selftest_dynamic.c 11 21 52.4 trace/power-traces.c 12 21 57.1 trace/rpm-traces.c : 6716 9697 69.2 trace/trace.c 6226 11432 54.5 sched/fair.c 9092 12491 72.8 bpf/verifier.c 8517 13245 64.3 events/core.c
次は、少し統計をば
gnuplot> stat 'openbsd' using 3:2 ;; % vs. lines Mean: 66.2882 838.7255 Std Dev: 10.6408 702.7683 Minimum: 33.9000 [ 69] 59.0000 [ 69] Maximum: 98.4000 [ 7] 3453.0000 [ 99] Quartile: 60.3000 316.0000 Median: 67.9000 684.0000 Quartile: 72.7000 1110.0000 Linear Model: y = 24.85 x - 808.7 Slope: 24.85 +- 6.119 Intercept: -808.7 +- 410.8 Correlation: r = 0.3763
gnuplot> stat 'freebsd' using 3:2 Mean: 67.2523 1216.2202 Std Dev: 11.4473 1410.1852 Minimum: 12.1000 [ 4] 30.0000 [ 96] Maximum: 98.8000 [174] 11078.0000 [175] Quartile: 62.6000 352.0000 Median: 68.5500 691.5000 Quartile: 74.4000 1624.0000 Linear Model: y = 43.68 x - 1722 Slope: 43.68 +- 7.837 Intercept: -1722 +- 534.7 Correlation: r = 0.3546
gnuplot> stat 'linux' using 3:2 Mean: 64.5942 1036.2997 Std Dev: 10.7153 1690.8837 Minimum: 32.7000 [234] 10.0000 [330] Maximum: 88.8000 [111] 13245.0000 [160] Quartile: 57.3000 223.0000 Median: 65.1000 511.0000 Quartile: 72.9000 1052.0000 Linear Model: y = 1.218 x + 957.6 Slope: 1.218 +- 8.495 Intercept: 957.6 +- 556.3 Correlation: r = 0.007719
リナの場合、行数とノーコメの行数には、有意な相関は無いと分った。*BSDの場合は微妙に正の相関が有るな。
リナはソースを文書として大事にしていない。対して*BSDは、それなりに監査されてるって事かな。
GENERIC.MP
前回relinkなんてのを調べていて、-g 付のカーネルが鎮座してる事を発見してしまった。 ならば、これを取出してきて、gdb出来るのではなかろうか? 検証してみる。
ホスト側のOpenBSDに下記を用意。
ob$ pwd /usr/obj/sys/arch/i386/compile/GENERIC.MP ob$ ls -l total 151072 -rwxr-x--- 1 root sakae 77267496 Nov 28 07:11 newbsd.gdb
問題なく動いたぞ。これでOpenBSDの開発マシンと同期出来た事になる。あの2時間もかけて、クライアント側でコンパイルした苦労は何だったんだ。
でも、GENERICは巨大な仕様、自前でコンパイルするとダイエット出来る特典がある。
この間、寒かったので近くの温泉に行ったのよ。そこに有った体重計に乘ったら、去年より5Kgも痩せていた。その理由は?
夕食でしっかり御飯を食べていたけど、胃が重いってんで、おかゆを常食するようにした。 そしたら、自然に痩せた、と思えるな。これが噂の炭水化物ダイエットか。何の苦労も無く痩せらたよ。
gettimeofday
ついでに、何回か前の疑問、riscvなシステムでdateした時、 sys_gettimeday
が呼びだされないのを調べておく。dateコマンドをgdb出来るように設定。
riscv$ gdb -q date Reading symbols from date... (gdb) b gettimeofday Breakpoint 1 at 0x15722: file /usr/src/lib/libc/sys/w_gettimeofday.c, line 24. (gdb) r Starting program: /tmp/date (gdb) bt #0 _libc_gettimeofday_wrap (tp=0x3ffffedd40, tzp=0x0) at /usr/src/lib/libc/sys/w_gettimeofday.c:24 #1 0x0000000a2db6fc02 in _libc_time (t=0xa2db87ad0 <tval>) at /usr/src/lib/libc/gen/time.c:39 #2 0x0000000a2db6d33a in main (argc=0, argv=0x3ffffee230) at date.c:98 (gdb) l 20,40 20 int 21 WRAP(gettimeofday)(struct timeval *tp, struct timezone *tzp) 22 { 23 int rc = 0; 24 struct timekeep *timekeep = _timekeep; 25 static struct timezone zerotz = { 0, 0 }; 26 27 if (timekeep == NULL || timekeep->tk_user == 0) 28 return gettimeofday(tp, tzp); 29 30 if (tp) 31 rc = _microtime(tp, timekeep); 32 if (rc) 33 return gettimeofday(tp, tzp); 34 35 if (tzp) 36 *tzp = zerotz; 37 38 return 0; 39 }
gettimeofdayをラップしてる。
(gdb) b _microtime Breakpoint 2 at 0xa2db7c926: file /usr/src/lib/libc/sys/microtime.c, line 101. (gdb) c Continuing. Breakpoint 2, _microtime (tvp=0x3ffffedd40, tk=<optimized out>) at /usr/src/lib/libc/sys/microtime.c:101
この中から最終的にbintimeが呼ばれていた。きっと、riscvの周辺の構造上、共通のシステムコールが使えないんだろうね。アーキテクチャの違いがユーザーランド側にも影響を及ぼすって例になるな。