時刻と時間の管理

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

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

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2011/2012-02-21
あるいは、次のページから手繰っていくこともできます。
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 では、ナノ秒単位。
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. 変数が 0 になると、次のことを行う。
  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)

古いタイマ・デバイス。

◆CMOS RTC (Real Time Clock)

電源オフ時にも、バッテリで動作している。BIOS 用の設定を保持するメモリの 一部。 当時、CMOS は、「低消費電力」用としてだけ使われていた。 今は、普通。  

2つの機能がある。

TOD (Time of Day) clock
時刻を、year/month/day hour:minute:second という形式で持つ。 秒以下は読めない。
定期的な割込み用
2Hz から 8192Hz の範囲で、2 の冪乗の周期で割込みを起こせる。
制約

その他の割込み

◆Local APIC (Advanced Programmable Interrupt Controller) Timers

Local APIC は、割込みのルーティングに使われる。 マルチプロセッサでも、CPU 毎に独立。 Pentium 以降では、CPU に内蔵。

◆ACPI (Advanced Configuration and Power Interface)

チップセット・タイマとも呼ばれる。

◆TSC (Time Stamp Counter)

Pentium 以降で利用可能。

◆HPET(High Precision Event Timer)

新しい目の PC で搭載されている。

■jiffiesとHZ

jiffies は、Linux で、モノトニック時刻を提供する変数。単位は、tick。 利用例 実装。
linux-3.1.3/kernel/timer.c
  53:	u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;

linux-3.1.3/include/linux/jiffies.h
  81:	extern u64 __jiffy_data jiffies_64;
  82:	extern unsigned long volatile __jiffy_data jiffies;

◆tick_periodic()

tick_periodic() は、ハードウェアから独立したtick ごとの処理を行う関数 である。
linux-3.1.3/kernel/time/tick-common.c
  63:	static void tick_periodic(int cpu)
  64:	{
  65:	        if (tick_do_timer_cpu == cpu) {
...
  71:	                do_timer(1);
...
  73:	        }
  75:	        update_process_times(user_mode(get_irq_regs()));
...
  77:	}

linux-3.1.3/kernel/timer.c
1286:	void update_process_times(int user_tick)
1287:	{
1288:	        struct task_struct *p = current;
1289:	        int cpu = smp_processor_id();
...
1292:	        account_process_tick(p, user_tick);
1293:	        run_local_timers();
...
1300:	        scheduler_tick();
1301:	        run_posix_cpu_timers(p);
1302:	}

1320:	void run_local_timers(void)
1321:	{
1322:	        hrtimer_run_queues();
1323:	        raise_softirq(TIMER_SOFTIRQ);
1324:	}

◆do_timer()

linux-3.1.3/kernel/timer.c
  53:	u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;

linux-3.1.3/kernel/time/timekeeping.c
1102:	void do_timer(unsigned long ticks)
1103:	{
1104:	        jiffies_64 += ticks;
1105:	        update_wall_time();
...
1107:	}

■カレンダー時刻の実装

◆xtime変数

xtime は、1970年1月1日00:00:00(GMT)からの秒数をナノ秒単位で構造体 struct timespec の形式で保持している。
linux-3.1.3/kernel/time/timekeeping.c
 160:	static struct timespec xtime __attribute__ ((aligned (16)));

◆gettimeofday()システム・コール

linux-3.1.3/kernel/time.c

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

linux-3.1.3/kernel/time/timekeeping.c
 340:	void do_gettimeofday(struct timeval *tv)
 341:	{
 342:	        struct timespec now;
 343:	
 344:	        getnstimeofday(&now);
 345:	        tv->tv_sec = now.tv_sec;
 346:	        tv->tv_usec = now.tv_nsec/1000;
 347:	}

 217:	void getnstimeofday(struct timespec *ts)
 218:	{
...
 227:	                *ts = xtime;
 228:	                nsecs = timekeeping_get_ns();
 231:	                nsecs += arch_gettimeoffset();
...
 235:	        timespec_add_ns(ts, nsecs);
 236:	}

◆update_wall_time()

update_wall_time() は、 do_timer() から呼ばれ、掛け時計の時刻 xtime を更新する。
linux-3.1.3/kernel/time/timekeeping.c
 885:	static void update_wall_time(void)
 886:	{
....
 953:	        xtime.tv_nsec = ((s64) timekeeper.xtime_nsec >> timekeeper.shift) + 1;
...
 962:	        if (unlikely(xtime.tv_nsec >= NSEC_PER_SEC)) {
 963:	                xtime.tv_nsec -= NSEC_PER_SEC;
 964:	                xtime.tv_sec++;
...
 966:	        }

■時間切れ処理(タイマ)

Linux カーネルには、ある時間が経過したら、関数を実行する仕組みがある。

◆struct timer_list

linux-3.1.3/include/linux/timer.h
  12:	struct timer_list {
...
  17:	        struct list_head entry;
  18:	        unsigned long expires;
  19:	        struct tvec_base *base;
  20:	
  21:	        void (*function)(unsigned long);
  22:	        unsigned long data;
...
  34:	};

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

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

add_timer(), add_timer_on()
タイマの登録。_on() は、CPU ごと。
mod_timer()
タイマの起動時刻の変更
del_timer(), del_timer_sync(), try_to_del_timer_sync()
タイマのキャンセル。 del_timer_sync() は、もし既に実行中だったら、実行完了を待つ。
利用例。
{
    struct timer_list my_timer;           // 構造体の宣言
    init_timer(&my_timer);        // 初期化
    my_timer.expires = jiffies + delay;   // どのくらい待ちたいか
    my_timer.data = (unsigned long)data;  // 渡したいデータ
    my_timer.function = my_timer_func;    // 関数
    add_timer(&my_timer);                 // 登録
}

void my_timer_func(unsigned long data) {
    ...
}

◆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()
カレンダ時刻の取得。
利用例1: 今から(相対的に) t_nano 秒後に関数 my_timer_handler() を1度だけ呼びたい。
    struct hrtimer my_timer;
    hrtimer_init(&my_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    my_timer.function = my_timer_handler;
    ...
    hrtimer_start(&my_timer,  ktime_set(0, t_nano), HRTIMER_MODE_REL);
    ...

enum hrtimer_restart my_timer_handler(struct hrtimer *timer)
{
        ...
        return HRTIMER_NORESTART;
}

■プロセスにおけるtickの処理

◆account_process_tick()

linux-3.1.3/kernel/sched.c

3964:	void account_process_tick(struct task_struct *p, int user_tick)
3965:	{
3966:	        cputime_t one_jiffy_scaled = cputime_to_scaled(cputime_one_jiffy);
3967:	        struct rq *rq = this_rq();
...
3977:	        if (user_tick)
3978:	                account_user_time(p, cputime_one_jiffy, one_jiffy_scaled);
3979:	        else if ((p != rq->idle) || (irq_count() != HARDIRQ_OFFSET))
3980:	                account_system_time(p, HARDIRQ_OFFSET, cputime_one_jiffy,
3981:	                                    one_jiffy_scaled);
3982:	        else
3983:	                account_idle_time(cputime_one_jiffy);
3984:	}

3733:	void account_user_time(struct task_struct *p, cputime_t cputime,
3734:	                       cputime_t cputime_scaled)
3735:	{
...
3740:	        p->utime = cputime_add(p->utime, cputime);
3741:	        p->utimescaled = cputime_add(p->utimescaled, cputime_scaled);
3742:	        account_group_user_time(p, cputime);
...
3754:	}

■実行の遅延

デバイスドライバでは、遅いデバイスに合わせるためにしばらく待ってから処 理をしたいことが多い。得に割り込みの後半部(bottom-half)で。

例: Ethernet のドライバでモードを変更して 2 マイクロ秒だけ待つ。

様々な方法がある。

◆空ループ(busy loop)

待つための単純な方法は、空ループを回ること。

例1: 10 tick (インターバル・タイマによる割り込み)を待つ。

unsigned long timeout = jiffies + 10; // 10 ticks
while (time_before(jiffies,timeout))
    continue;
例2: 2秒待つ
unsigned long delay = jiffies + 2*HZ; // 2秒
while (time_before(jiffies,timeout))
    continue;

◆time_befefore()

jiffies は、32 ビットなので、オーバフローする可能性がある。 次のコードは危ない。
unsigned long timeout = jiffies + 10; // 10 ticks
while (jiffies<timeout)
    continue;
引き算して 0 と比較すると、オーバフローの問題が解決できる。
unsigned long timeout = jiffies + 10; // 10 ticks
while (jiffies-timeout<0)
    continue;
次のマクロを使う方法もある。
linux-3.1.3/include/linux/jiffies.h

 106:	#define time_after(a,b)         \
 107:	        (typecheck(unsigned long, a) && \
 108:	         typecheck(unsigned long, b) && \
 109:	         ((long)(b) - (long)(a) < 0))
 110:	#define time_before(a,b)        time_after(b,a)
 111:	
 112:	#define time_after_eq(a,b)      \
 113:	        (typecheck(unsigned long, a) && \
 114:	         typecheck(unsigned long, b) && \
 115:	         ((long)(a) - (long)(b) >= 0))
 116:	#define time_before_eq(a,b)     time_after_eq(b,a)

◆cond_resched()

空ループは、CPU が無駄になるので良くない。その代わりに次の方法が使える。
unsigned long delay = jiffies + 2*HZ; // 2秒
while (time_before(jiffies,timeout))
    cond_resched();
他に実行すべき重要なプロセスが存在する(条件)時には、スケジューラを呼ん で、実行する。存在しなければ、空ループと同じ。ただし、スケジューラを呼 ぶ(sleepする可能性がある)ので、割り込みコンテキストからは使えない。

◆小さな遅延

tick 単位 (10ミリ秒-1ミリ秒) では大きすぎる場合、小さな遅延を実現するよ うな関数がある。
void ndelay(unsigned long nsecs)
void udelay(unsigned long usecs)
void mdelay(unsigned long msecs)
udelay() は、ある回数のループで実装されている。回数は、CPUの速度等で決 まる。ndelay(), mdelay() は、udelay() を呼んでいる。

udelay() で1ミリ秒以上待ってはいけない。 ループのインデックスがオーバフローする可能性がある。

◆schedule_timeout()

set_current_state( TASK_INTERRUPTIBLE ); // signal で起きる可能性がある
schedule_timeout( s * HZ );
実装には struct timer_list が使われている。

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

★問題(901) PIT

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

★問題(902) モノトニック時刻の利用

カーネルの中で、カレンダ時刻ではなくてモノトニック時刻が使われている場 所がある。その理由を簡単に説明しなさい。その場所をカレンダ時刻を使うよ うにすると、「どのような場合に」どのような不都合が生じるか。

★問題(903) struct timer_listの利用

関数f()を実行している時に、次の関数h()を、50 ミリ秒後に実行したいとする。
void h(int a,int b, int c) {
   ....
}
これを実現するために、どのようなコードを書けばよいか。以下の空欄を埋め なさい。
struct timer_list my_timer;

int my_arg_a,my_arg_b,my_arg_c;

void f(unsigned long data) {
    init_timer( /*空欄(a)*/ );
    my_timer.expires  = /*空欄(b)*/;
    my_timer.data     = 0;
    my_timer.function = /*空欄(c)*/;
    /*空欄(d)*/;
}

void my_timer_func(unsigned long data) {
     h( my_arg_a,my_arg_b,my_arg_c );
}

Last updated: 2012/02/20 21:35:27
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>