時刻と時間の管理

					2010年02月23日
情報科学類 オペレーティングシステム II

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/literacy-2009/2010-02-23
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/

■連絡事項

試験について 欠席した時のクイズの回答を、レポートして受け取ります。試験の日までに提 出して下さい。紙はA4にしてください。

■今日の大事な話

■時刻と時間

◆求められる機能

◆カレンダ時刻のAPI

gettimeofday() は、μ(マイクロ)秒単位の時刻を扱う。
struct timeval {
	long    tv_sec;         /* seconds since Jan. 1, 1970 */
	long    tv_usec;        /* and microseconds */
};

int gettimeofday(struct timeval *tp, struct timezone *tzp);
int settimeofday(const struct timeval *tp, const struct timezone *tzp);
使い方
    struct timeval  tv;
    gettimeofday(&tv, NULL);
POSIX 1003.1, 2003 では、ナノ秒単位。
struct timespec  {
    time_t tv_sec;            /* Seconds.  */
    long int tv_nsec;         /* Nanoseconds.  */
};

int clock_settime(clockid_t clock_id, const struct timespec *tp);
int clock_gettime(clockid_t clock_id, struct timespec *tp);
int clock_getres(clockid_t clock_id, struct timespec *res);
カレンダ時刻は、変更できる。逆走させることも可能。

順方向のジャンプや逆走を避けて、カレンダ時刻を合わせるには、adjtime() を使う。

int adjtime(const struct timeval *delta, struct timeval *olddelta);

◆インターバルタイマのAPI

定期的な仕事をしたい時に使う
struct itimerval {
    struct timeval it_interval; /* next value */
    struct timeval it_value;    /* current value */
};

int setitimer(int which, const struct itimerval *value, 
    struct itimerval *ovalue);
次のように考えてもよい。
  1. 内部にカウントダウンしていく変数がある。
  2. 初期値として、it_value を設定する。
  3. 変数が 0 になると、シグナルが通知される。
  4. 変数の値を、it_interval に設定する。
  5. 3. を、解除されるまで繰り返す。

◆時間切れ処理のAPI

int  select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
     		struct timeval *timeout);
int  poll(struct pollfd *fds, nfds_t nfds, int timeout);
ネットワーク・プログラムでよく使う。複数の入力を監視する。指定された時 間、入力がなければ、システム・コールから復帰する。

なにもしない時間切れ。

unsigned int sleep(unsigned int seconds);
int usleep(useconds_t usec)
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);

■時刻・時間関連のハードウェア

コンピュータによって違う。ここでは、PC/AT の話。

◆基本的なモデル

ハードウェアは、簡単。発振器とカウントダウンするカウンタを使う。

図? 発振器、カウンタ、比較、CPU、割込み、再設定

図? タイマ関連のハードウェアの基本モデル

カウントダウンではなくカウントアップするものもある。

◆PIT (Programmable Interval Timer)

古いタイマ・デバイス。 Linux は、0 番を主タイマとして利用している。

◆TSC (Time Stamp Counter)

Pentium 以降で利用可能。 Linux の gettimeofday() で、PIT 以上の精度を出す時に使う。

■Liux における時刻・時間管理

◆jiffies変数

jiffies は、Linux で、モノトニック時刻を提供する。 単位は、tick。
kernel/timer.c
  49: u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;
...
1197: void do_timer(unsigned long ticks)
1198: {
1199:         jiffies_64 += ticks;
1200:         update_wall_time();
1201:         calc_global_load();
1202: }

kernel/time/tick-common.c
  60: static void tick_periodic(int cpu)
  61: {
...
  68:                 do_timer(1);
...
  72:         update_process_times(user_mode(get_irq_regs()));
...
  74: }
..
  79: void tick_handle_periodic(struct clock_event_device *dev)
  80: {
  81:         int cpu = smp_processor_id();
...
  84:         tick_periodic(cpu);
...
 109: }

kernel/time/tick-internal.h:125:        dev->event_handler = tick_handle_periodic;
kernel/time/tick-broadcast.c:108:               dev->event_handler = tick_handle_periodic;
kernel/time/tick-broadcast.c:288:               dev->event_handler = tick_handle_periodic;

arch/x86/include/asm/i8253.h
  11: extern struct clock_event_device *global_clock_event;

arch/x86/include/asm/do_timer.h:
  13: static inline void do_timer_interrupt_hook(void)
  14: {
  15:         global_clock_event->event_handler(global_clock_event);
  16: }

arch/x86/kernel/time_32.c
  75: irqreturn_t timer_interrupt(int irq, void *dev_id)
  76: {
...
  95:         do_timer_interrupt_hook();
...
 113:         return IRQ_HANDLED;
 114: }

arch/x86/kernel/setup.c
1069: static struct irqaction irq0  = {
1070:         .handler = timer_interrupt,
1071:         .flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_IRQPOLL | IRQF_TIMER,
1072:         .name = "timer"
1073: };

1092: void __init x86_quirk_time_init(void)
1093: {
...
1104:         irq0.mask = cpumask_of_cpu(0);
1105:         setup_irq(0, &irq0);
1106: }

◆xtime変数

xtime は、1970年1月1日00:00:00(GMT)からの秒数。gettimeofday() で返され る値をnano 秒単位で保持している。tick 単位で更新されるが、それより細か い単位を TSC 等の高精度の clocksource から読み込み補正する。
kernel/time.c

 102: SYSCALL_DEFINE2(gettimeofday, struct timeval __user *, tv,
 103:                 struct timezone __user *, tz)
 104: {
 105:         if (likely(tv != NULL)) {
 106:                 struct timeval ktv;
 107:                 do_gettimeofday(&ktv);
 108:                 if (copy_to_user(tv, &ktv, sizeof(ktv)))
 109:                         return -EFAULT;
 110:         }
 111:         if (unlikely(tz != NULL)) {
 112:                 if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))
 113:                         return -EFAULT;
 114:         }
 115:         return 0;
 116: }

kernel/time/timekeeping.c

 134: void do_gettimeofday(struct timeval *tv)
 135: {
 136:         struct timespec now;
 137: 
 138:         getnstimeofday(&now);
 139:         tv->tv_sec = now.tv_sec;
 140:         tv->tv_usec = now.tv_nsec/1000;
 141: }

  45: struct timespec xtime __attribute__ ((aligned (16)));
...
  96: void getnstimeofday(struct timespec *ts)
  97: {
...
 107:                 *ts = xtime;
 110:                 cycle_now = clocksource_read(clock);
 113:                 cycle_delta = (cycle_now - clock->cycle_last) & clock->mask;
 116:                 nsecs = cyc2ns(clock, cycle_delta);
 119:                 nsecs += arch_gettimeoffset();
...
 123:         timespec_add_ns(ts, nsecs);
 124: }

 493: void update_wall_time(void)
 494: {
...
 519:                         xtime.tv_sec++;
...
 562:         xtime.tv_nsec = ((s64)clock->xtime_nsec >> clock->) + 1;
...
 571: }

◆struct timer_list

ある時間が経過したら、関数を実行する。

include/linux/timer.h
  12: struct timer_list {
  13:         struct list_head entry;
  14:         unsigned long expires;
  15: 
  16:         void (*function)(unsigned long);
  17:         unsigned long data;
  18: 
  19:         struct tvec_base *base;
...
  28: };
  29: 

jiffies が増加して expires に達すれば、(*function)(data) を呼ぶ。

主に次の関数で操作する。

add_timer(), add_timer_on()
タイマの登録。_on() は、CPU ごと。
mod_timer()
タイマの起動時刻の変更
del_timer(), del_timer_sync(), try_to_del_timer_sync()
タイマのキャンセル
利用例。
    init_timer(&timer);
    timer.expires = jiffies + delay;
    timer.data = (unsigned long)data;
    timer.function = func;
    add_timer(&timer);

◆High-resolution kernel timers

struct timer_listのAPIでは、tick単位だが、struct hrtimer の API では、 ナノ秒単位で指定できる。ただし、実行されるのは、ハードウェアのタイマ割 り込みなので、それ以上の精度はない。

主に次の関数で操作する。

hrtimer_init()
struct hrtimer 構造体の初期化。
hrtimer_start()
タイマの開始。モノトニック時刻(CLOCK_MONOTONIC)か、カレンダ 時刻(CLOCK_REALTIME)か選べる。引数は、絶対指定(HRTIMER_MODE_ABS)か現在 からの相対(HRTIMER_MODE_REL)か指定できる。
hrtimer_cancel()
タイマのキャンセル。
schedule_hrtimeout()
指定した時間だけ現在実行中のプロセスを sleep させる。
ktime_get()
モノトニック時刻の取得。ktime は、ナノ秒単位。64ビット。
ktime_get_real()
カレンダ時刻の取得。
利用例。
    hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    timer.function = timer_handler;
    ...
    hrtimer_start(&timer,  ktime_set(0, nano), HRTIMER_MODE_REL);
    ...

enum hrtimer_restart timer_handler(struct hrtimer *timer)
{
        ...
        return HRTIMER_NORESTART;
}
ハンドラで HRTIMER_RESTART を return すると、タイマが再設定される。
    hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
    timer.function = func;
    ...
    hrtimer_start(&timer, ktime_add_ns(ktime_get(), period),
                      HRTIMER_MODE_ABS);
hrtimer の管理には、red-black tree が使われている。

■クイズ10 時刻と時間の管理

★問題(1001) PIT

PIT (Programmable Interval Timer)では、 発振器の周波数は、1193182Hz である。 再設定用のレジスタを 11931 に設定したら、 何に1回、割り込みが発生するか。

★問題(1002) モノトニック時刻

カーネルの中で、カレンダ時刻ではなくてモノトニック時刻が使われている場 所がある。その理由を簡単に説明しなさい。その場所をカレンダ時刻を使うよ うにすると、どのような場合にどのような不都合が生じるか。
Last updated: 2010/02/23 03:37:13
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>