2011年11月6日日曜日

ビルトインを自作して、bash で処理時間をミリ秒単位で計測

先日、bash で処理時間(経過時間)を 10 ミリ秒の粒度で計測する方法を書きました。おそらく、bash 自体やプロセス生成のオーバーヘッドを考えると、実用上はそれで十分と思います。
この記事では、いささかオーバースペック(と言うか諸々のオーバーヘッドを加味すると無意味かも? )ですが、bash のビルトイン(builtin)作成方法に興味があり、練習として、ミリ秒より細かく計測するためのインターフェースを作成してみました。

以下、CentOS 5.7 x86_64 上、root で作業しています。

まずは、bash のソース RPM (.src.rpm) を入手して、展開します。
http://vault.centos.org/5.7/os/SRPMS/
この時点で最新の bash-3.2-32.el5.src.rpm を使いました(作業環境と同じバージョン)。
# rpm -ivh bash-3.2-32.el5.src.rpm
# rpmbuild -bc /usr/src/redhat/SPEC/bash.spec
上の操作で、/usr/src/redhat/BUILD/bash-3.2/ の下にソースが展開されます。
ここで、bash ビルトインのコンパイル環境を整えるため、-bp ではなくて、-bc とします。
-bp のあとに、手動で configure という流れで作業すると、作成したビルトインの enable 時にリンクエラーになってしまいます(具体的には sh_xrealloc で引っかかりました)。

次に、ソースツリーの下の example/loadables の下へ作業ディレクトリを移動して、ひとまずサンプルをビルドして、使ってみます。
# cd /usr/src/redhat/BUILD/bash-3.2/examples/loadables/
# make -k
※ビルド過程は省略します。わたしの環境では、多少エラーが出てました。
# enable -f ./strftime strftime  ※もしリンクエラーになると、bash が落ちます
# enable | grep strftime         ※落ちなければロードされたはずですが、確認です
enable strftime
# strftime "%F %T"               ※試しに strftime を実行
2011-11-06 15:11:13

ここまでで、ビルトインを自作する環境が整います。あとは、サンプルを参考にしながら monotonic というビルトインを自作してみました。strftime.c を下敷きにしています。
     1  /* monotonic - loadable builtin interface to clock_gettime(CLOCK_MONOTONIC) */
     2  
     3  /* Copyright (C) 2011-2011
     4     luna2 at http://luna2-linux.blogspot.com/
     5     If you contact luna2, email to blue3waters at gmail.com.
     6  
     7     This file is subject to the GNU General Public License.
     8     It comes with NO WARRANTY.
     9   */
    10  
    11  /* See Makefile for compilation details. */
    12  
    13  #include <config.h>
    14  
    15  #if defined (HAVE_UNISTD_H)
    16  #  include <unistd.h>
    17  #endif
    18  
    19  #include "bashtypes.h"
    20  #include "posixtime.h"
    21  
    22  #include <stdio.h>
    23  
    24  #include "builtins.h"
    25  #include "shell.h"
    26  #include "common.h"
    27  #include "bashgetopt.h"
    28  
    29  #include <time.h>       /* clock_gettime */
    30  
    31  int
    32  monotonic_builtin (list)
    33    WORD_LIST *list ;
    34  {
    35    char  *tbuf = NULL ;
    36    size_t tbsize ;
    37    char  *tv_sec = NULL ;
    38    char  *tv_nsec = NULL ;
    39    struct timespec t ;
    40  
    41    if (list == 0) {
    42      builtin_usage() ;
    43      return EX_USAGE ;
    44    }
    45  
    46    tv_sec = list->word->word ;
    47    if (tv_sec == 0 || *tv_sec == 0) {
    48      builtin_usage() ;
    49      return EX_USAGE ;
    50    }
    51  
    52    list = list->next ;
    53  
    54    if (list && list->word->word) {
    55      tv_nsec = list->word->word ;
    56    } else {
    57      builtin_usage() ;
    58      return EX_USAGE ;
    59    }
    60  
    61    clock_gettime(CLOCK_MONOTONIC, &t) ;
    62  
    63    tbsize = 32 ;         /* enough to store ULONG_MAX */
    64    tbuf = xmalloc(tbsize) ;
    65    snprintf(tbuf, tbsize, "%lu", t.tv_sec) ;
    66    bind_variable(tv_sec, tbuf, 0) ;
    67    free(tbuf) ; tbuf = NULL ;    /* erase immediately */
    68  
    69    tbsize = 32 ;
    70    tbuf = xmalloc(tbsize) ;
    71    snprintf(tbuf, tbsize, "%lu", t.tv_nsec) ;
    72    bind_variable(tv_nsec, tbuf, 0) ;
    73    free(tbuf) ; tbuf = NULL ;
    74  
    75    return EXECUTION_SUCCESS ;
    76  }
    77  
    78  /* An array of strings forming the `long' documentation for a builtin xxx,
    79     which is printed by `help xxx'.  It must end with a NULL. */
    80  char *monotonic_doc[] = {
    81          "The interface to clock_gettime(CLOCK_MONOTONIC).",
    82          "Store the results in shell variable TV_SEC and TV_NSEC",
    83          (char *)NULL
    84  } ;
    85  
    86  /* The standard structure describing a builtin command.  bash keeps an array
    87     of these structures.  The flags must include BUILTIN_ENABLED so the
    88     builtin can be used. */
    89  struct builtin monotonic_struct = {
    90          "monotonic",            /* builtin name */
    91          monotonic_builtin,      /* function implementing the builtin */
    92          BUILTIN_ENABLED,        /* initial flags for builtin */
    93          monotonic_doc,          /* array of long documentation strings. */
    94          "monotonic TV_SEC TV_NSEC",     /* usage synopsis; becomes short_doc */
    95          0                       /* reserved for internal use */
    96  } ;
これをビルドするため、Makefile を書き換えます。差分は、次の通りです。
# diff -u Makefile.org Makefile
--- Makefile.org        2011-11-06 12:35:25.000000000 +0900
+++ Makefile    2011-11-06 14:00:09.000000000 +0900
@@ -69,7 +69,7 @@
 SHOBJ_CFLAGS = -fPIC
 SHOBJ_LD = ${CC}
 SHOBJ_LDFLAGS = -shared -Wl,-soname,$@
-SHOBJ_XLDFLAGS = 
+SHOBJ_XLDFLAGS = -lrt
 SHOBJ_LIBS = 
 SHOBJ_STATUS = supported
 
@@ -83,7 +83,7 @@
 
 ALLPROG = print truefalse sleep pushd finfo logname basename dirname \
          tty pathchk tee head mkdir rmdir printenv id whoami \
-         uname sync push ln unlink cut realpath getconf strftime
+         uname sync push ln unlink cut realpath getconf strftime monotonic
 OTHERPROG = necho hello cat
 
 all:   $(SHOBJ_STATUS)
@@ -186,6 +186,9 @@
 strftime:      strftime.o
        $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ strftime.o $(SHOBJ_LIBS)
 
+monotonic:     monotonic.o
+       $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ monotonic.o $(SHOBJ_LIBS)
+
 # pushd is a special case.  We use the same source that the builtin version
 # uses, with special compilation options.
 #
@@ -236,3 +239,4 @@
 mkdir.o: mkdir.c
 realpath.o: realpath.c
 strftime.o: strftime.c
+monotonic.o: monotonic.c
特に、librt のリンクが必要なので、SHOBJ_XLDFLAGS に -lrt を設定しています。

もう一度 make -k を実行すれば、monotonic が出来上がります。
# make -k
# ls -l monotonic*
-rwxr-xr-x 1 root root 11463 Nov  6 15:29 monotonic
-rw-r--r-- 1 root root  2379 Nov  6 15:29 monotonic.c
-rw-r--r-- 1 root root  9992 Nov  6 15:29 monotonic.o

試しに動かしてみた結果です。
# enable -f ./monotonic monotonic
# enable | grep monotonic
enable monotonic
# help monotonic
monotonic: monotonic TV_SEC TV_NSEC
    The interface to clock_gettime(CLOCK_MONOTONIC).
    Store the results in shell variable TV_SEC and TV_NSEC
# monotonic TV_SEC TV_NSEC
# echo $TV_SEC $TV_NSEC
12115 474565954
# cat /proc/uptime ; monotonic TV_SEC TV_NSEC ; echo $TV_SEC $TV_NSEC
12148.86 11976.46
12148 861371954
どうやら、うまく動きました。
monotonic は、指定されたシェル変数(上では TV_SEC および TV_NSEC ですが、他の名前でも可)に、clock_gettime(CLOCK_MONOTONIC) で得られた値を設定します。

経過時間を計測する場合、得られた値の差を求めれば良いので、実際にやってみます。
#!/bin/bash
#
# Name: elaps_ns.bash
#

enable -f ./monotonic monotonic

set_START() {
        monotonic START START_NSEC
}

get_ELAPS() {
        monotonic END END_NSEC
        if [ $END_NSEC -ge $START_NSEC ] ; then
                let ELAPS=END-START
                let ELAPS_NS=END_NSEC-START_NSEC
        else
                let ELAPS=END-START-1
                let ELAPS_NS=1000000000+END_NSEC-START_NSEC
        fi
}

echo SECONDS=$SECONDS   #bash's builtin
time {
        set_START
}
echo START=$START NSEC=$START_NSEC

echo -e "\n### sleep (2500 x 1000) [us]  using usleep"
time {
        usleep 2500000
}

time {
        get_ELAPS
}
echo END=$END NSEC=$END_NSEC
printf "ELAPS=START-END=$ELAPS.%09d\n" $ELAPS_NS
echo
echo SECONDS=$SECONDS   #bash's builtin
# ./elaps_ns.bash 
SECONDS=0

real    0m0.000s
user    0m0.000s
sys     0m0.000s
START=14665 NSEC=852954954

### sleep (2500 x 1000) [us]  using usleep

real    0m2.503s
user    0m0.000s
sys     0m0.000s

real    0m0.000s
user    0m0.000s
sys     0m0.000s
END=14668 NSEC=356643954
ELAPS=START-END=2.503689000

SECONDS=2
bash 組み込みの time で計った経過時間 2.503 と一致することが確認できます。

利用したシステムコール clock_gettime(CLOCK_MONOTONIC) については、次の記事を参照してください。

経過時間の計測方法

0 件のコメント:

コメントを投稿

人気ブログランキングへ にほんブログ村 IT技術ブログへ