Linux vs. *BSD

del comment

前回、カーネルの統計って事で、ソース行数とオブジェクトファイルのサイズの関係とか調べた。その時ソースの行数はさらっと wc -l *.c してしまった。これでは、コメント込みの行数になってしまう。そこで、さらっとコメント除去の方法を調べてみた。

Cソースファイルからコメント行だけを削除する

C言語のコードからコメントをまるっと削除する for gcc

コメントアウトを除去するコードをRubyで書いてみた

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の周辺の構造上、共通のシステムコールが使えないんだろうね。アーキテクチャの違いがユーザーランド側にも影響を及ぼすって例になるな。

etc