インターバルタイマ

プロファイルが云々という話がどこかで出ててちょろっと手に取った本でソレ系の演習問題があったりなんかしたので朝から着手。
プロトタイプとしては

  • {get, set}itimer を使う
  • 1 秒おきにシグナルハンドラ呼び出すように

みたいなカンジ。

とりあえず

man を見たところ、プロトタイプは以下なカンジらしい。

       #include <sys/time.h>

       int getitimer(int which, struct itimerval *curr_value);
       int setitimer(int which, const struct itimerval *new_value,
                     struct itimerval *old_value);

タイマ的には

Timers decrement from it_value to zero, generate a signal, and reset to it_interval. A timer which is set to zero (it_value is zero or the timer expires and it_interval is zero) stops.

つうことなので 1 秒おきに、ということであれば

    struct itimerval new;
    new.it_interval.tv_sec = 1;
    new.it_interval.tv_usec = 0;
    new.it_value.tv_sec = 1;
    new.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, new, NULL);

みたいなカンジで良いのかな。ちょっと動作確認用のソレを作成してみます。

#include <stdio.h>
#include <signal.h>
#include <sys/time.h>

static long int real_timer;

static void sig_handler(int signo) {
    switch (signo) {
    case SIGALRM:
        real_timer++;
        break;
    default:
        break;
    }
}

static long unsigned int fibonacci(unsigned int n) {
    if(n == 0)
        return 0;
    else if(n == 1 || n == 2)
        return 1;
    else
        return (fibonacci(n - 1) + fibonacci(n - 2));
}

int main(void) {
    int n = 45;
    long unsigned int ret;
    struct itimerval new, current;

    if(signal(SIGALRM, sig_handler) == SIG_ERR)
        fprintf(stderr, "Unable to create handler for SIGARM\n");

    new.it_interval.tv_sec = 1;
    new.it_interval.tv_usec = 0;
    new.it_value.tv_sec = 1;
    new.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &new, NULL);

    ret = fibonacci(n);

    getitimer(ITIMER_REAL, &current);

    printf("fib(%d) = %ld\n", n, ret);
    printf("real time = %ld sec, %ld usec\n",
           real_timer, current.it_value.tv_usec);

    signal(SIGALRM, SIG_DFL);

    return 0;
}

これに ITIMER_VIRTUAL とか ITIMER_PROF とかなナニを盛り込めば良いのか。出力は以下なカンジです。

$ gcc -o test timertest.c -Wall -g
$ ./test
fib(45) = 1134903170
real time = 18 sec, 104566 usec
$

これって v6 は無理にしても xv6 あたりだと何とかなりそげな気がしてるんですがアマいかなぁ。

追記

一応全部ナニするのができてたんですが出力が微妙な気がしてて。

$ ./timertest
fib(45) = 1134903170
real time   =  17 sec, 903 msec
cpu time    =  17 sec, 892 msec
user time   =  17 sec, 864 msec
kernel time =   0 sec,  28 msec
$

とは言え、フィボナッチ数列な自前の関数呼び出してるだけで、システムコールが云々な訳ではないからこの実装だと kernel なソレは使わなくて正解なんスよね。
でっちあがったのが以下です。シグナルと {get, set}itimer が使えればプロファイリングは可能なのかどうなのか。

#include <stdio.h>
#include <signal.h>
#include <sys/time.h>

static long int real_timer, virtual_timer, prof_timer;

static void sig_handler(int signo) {
    switch (signo) {
    case SIGALRM:
        real_timer++;
        break;
    case SIGVTALRM:
        virtual_timer++;
        break;
    case SIGPROF:
        prof_timer++;
        break;
    default:
        break;
    }
}

static long unsigned int fibonacci(unsigned int n) {
    if(n == 0)
        return 0;
    else if(n == 1 || n == 2)
        return 1;
    else
        return (fibonacci(n - 1) + fibonacci(n - 2));
}

int main(void) {
    int n = 45;
    long unsigned int ret;
    struct itimerval realt, virtt, proft, current_r, current_v, current_p;

    if(signal(SIGALRM, sig_handler) == SIG_ERR)
        fprintf(stderr, "Unable to create handler for SIGARM\n");

    if(signal(SIGVTALRM, sig_handler) == SIG_ERR)
        fprintf(stderr, "Unable to create handler for SIGARM\n");

    if(signal(SIGPROF, sig_handler) == SIG_ERR)
        fprintf(stderr, "Unable to create handler for SIGARM\n");

    realt.it_interval.tv_sec = 1;
    realt.it_interval.tv_usec = 0;
    realt.it_value.tv_sec = 1;
    realt.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &realt, NULL);

    virtt.it_interval.tv_sec = 1;
    virtt.it_interval.tv_usec = 0;
    virtt.it_value.tv_sec = 1;
    virtt.it_value.tv_usec = 0;
    setitimer(ITIMER_VIRTUAL, &virtt, NULL);

    proft.it_interval.tv_sec = 1;
    proft.it_interval.tv_usec = 0;
    proft.it_value.tv_sec = 1;
    proft.it_value.tv_usec = 0;
    setitimer(ITIMER_PROF, &proft, NULL);

    ret = fibonacci(n);

    getitimer(ITIMER_REAL, &current_r);
    getitimer(ITIMER_VIRTUAL, &current_v);
    getitimer(ITIMER_PROF, &current_p);

    printf("fib(%d) = %ld\n", n, ret);
    printf("real time   = %3ld sec, %3ld msec\n",
           real_timer, 1000 - (current_r.it_value.tv_usec/1000));
    printf("cpu time    = %3ld sec, %3ld msec\n",
           prof_timer, 1000 - (current_p.it_value.tv_usec/1000));
    printf("user time   = %3ld sec, %3ld msec\n",
           virtual_timer, 1000 - (current_v.it_value.tv_usec/1000));
    printf("kernel time = %3ld sec, %3ld msec\n",
           prof_timer - virtual_timer, 
           (1000 - (current_p.it_value.tv_usec/1000)) - 
           (1000 - (current_v.it_value.tv_usec/1000)));

    signal(SIGALRM, SIG_DFL);
    signal(SIGVTALRM, SIG_DFL);
    signal(SIGPROF, SIG_DFL);

    return 0;
}

面倒なので fork して云々は略で。