時刻と時間の管理、プロセスのスケジューリング

					2025年01月31日
情報科学類 オペレーティングシステム II

                                       筑波大学 システム情報系 
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
https://www.coins.tsukuba.ac.jp/~yas/coins/os2-2024/2025-01-31
あるいは、次のページから手繰っていくこともできます。
https://www.coins.tsukuba.ac.jp/~yas/
https://www.cs.tsukuba.ac.jp/~yas/

■今日の大事な話

時刻と時間の管理 プロセスのスケジューリングの関連

■時刻と時間

◆求められる機能

◆カレンダ時刻のAPI

gettimeofday() は、μ(マイクロ)秒単位の時刻を扱う。
struct timeval {
    time_t      tv_sec;     /* seconds. long int */
    suseconds_t tv_usec;    /* microseconds. long int */
};

int gettimeofday(struct timeval *tp, struct timezone *tzp);
int settimeofday(const struct timeval *tp, const struct timezone *tzp);
使い方
   1:	/*
   2:	        gettimeofday-print.c -- get colander time and print
   3:	        Created on: 2014/01/22 20:40:34
   4:	*/
   5:	
   6:	#include <sys/time.h>   /* gettimeofday() */
   7:	#include <time.h>       /* ctime() */
   8:	#include <stdio.h>
   9:	
  10:	main()
  11:	{
  12:	    struct timeval tv;
  13:	    time_t sec;
  14:	        gettimeofday( &tv, NULL );
  15:	        sec = tv.tv_sec;
  16:	        printf("%s", ctime(&sec) );
  17:	}
$ make gettimeofday-print [←]
cc     gettimeofday-print.c   -o gettimeofday-print
$ ./gettimeofday-print [←]
Mon Jan 27 11:50:00 2025
$ date [←]
Mon Jan 27 11:50:01 JST 2025
$ []
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);
clock_id としては、CLOCK_REALTIME (カレンダ時刻)やCLOCK_MONOTONIC があ る。 カレンダ時刻は、変更できる。逆走させることも可能。

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

int adjtime(const struct timeval *delta, struct timeval *olddelta);
adjtime() を使った時刻同期の方法。
  1. 外部の時刻サーバから時刻を得る。
  2. 自分自身の時刻が遅れていた時: adjtime() で進み方のペースを上げる。
  3. 自分自身の時刻が進みすぎていた時: adjtime() で進み方のペースを下げる。

◆インターバルタイマの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);
int  epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
int  kevent(int kq, const struct kevent *changelist, int nchanges,
       struct kevent *eventlist, int nevents, const struct timespec *timeout);
ネットワーク・プログラムでよく使う。複数の入力を監視する。指定された時 間、入力がなければ、システム・コールから復帰する。

参考: システムプログラム/select()による複数のクライアントに対するサービスの同時提供

なにもしない時間切れ。

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

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

コンピュータによって違う。ここでは、PC/AT CPU がx86の標準的なPC) の話。

◆基本的なモデル

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

図? 発振器、カウンタ、比較、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 毎に独立。

◆ACPI (Advanced Configuration and Power Interface)

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

◆TSC (Time Stamp Counter)

◆HPET(High Precision Event Timer)

◆Linux clocksource

Linux カーネルが時間管理のために利用可能なハードウェア。
$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource [←]
tsc acpi_pm
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource [←]
tsc
$ []

■jiffiesとHZ

jiffies は、Linux で、モノトニック時刻を提供する変数。単位は、tick。
$ grep CONFIG_HZ .config [←]
# CONFIG_HZ_PERIODIC is not set [←]
# CONFIG_HZ_100 is not set [←]
# CONFIG_HZ_250 is not set [←]
# CONFIG_HZ_300 is not set [←]
CONFIG_HZ_1000=y
CONFIG_HZ=1000
$ []
利用例
linux-6.12.7/include/asm-generic/param.h
   8:	# define HZ             CONFIG_HZ       /* Internal kernel timer frequency */

linux-6.12.7/include/linux/jiffies.h
  85:	extern u64 __cacheline_aligned_in_smp jiffies_64;
  86:	extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies;

◆tick_periodic()

tick_periodic() は、ハードウェアから独立したtick ごとの処理を行う関数 である。
linux-6.12.7/kernel/time/tick-common.c
  86:	static void tick_periodic(int cpu)
  87:	{
  88:	        if (READ_ONCE(tick_do_timer_cpu) == cpu) {
...
  95:	                do_timer(1);
  98:	                update_wall_time();
  99:	        }
 100:	
 101:	        update_process_times(user_mode(get_irq_regs()));
...
 103:	}

◆do_timer()

linux-6.12.7/kernel/time/timer.c
  61:	__visible u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;

linux-6.12.7/kernel/time/timekeeping.c
2318:	void do_timer(unsigned long ticks)
2319:	{
2320:	        jiffies_64 += ticks;
...
2322:	}

■カレンダー時刻の実装

◆struct timekeeper/xtime

struct timekeeper は、全体でカレンダー時刻を計算するための構造体。次の 3つのフィールドを使って1970年1月1日00:00:00(UTC)からの秒数をナノ秒単位 で保持している。 実際には、xtime_nsec は、そのままでナノ秒を表すのではなく、 xtime_nsec >> shift でナノ秒を表す。
linux-6.12.7/include/linux/timekeeper_internal.h
  89:	struct timekeeper {
  90:	        struct tk_read_base     tkr_mono;
...
  92:	        u64                     xtime_sec;
...
 124:	};

  34:	struct tk_read_base {
  35:	        struct clocksource      *clock;
...
  39:	        u32                     shift;
  40:	        u64                     xtime_nsec;
...
  43:	};

linux-6.12.7/include/linux/time64.h
  13:	struct timespec64 {
  14:	        time64_t        tv_sec;                 /* seconds */
  15:	        long            tv_nsec;                /* nanoseconds */
  16:	};

linux-6.12.7/kernel/time/timekeeping.c
 129:	static inline struct timespec64 tk_xtime(const struct timekeeper *tk)
 130:	{
 131:	        struct timespec64 ts;
 132:	
 133:	        ts.tv_sec = tk->xtime_sec;
 134:	        ts.tv_nsec = (long)(tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift);
 135:	        return ts;
 136:	}

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

linux-6.12.7/kernel/time/time.c
 140:	SYSCALL_DEFINE2(gettimeofday, struct __kernel_old_timeval __user *, tv,
 141:	                struct timezone __user *, tz)
 142:	{
 143:	        if (likely(tv != NULL)) {
 144:	                struct timespec64 ts;
 145:	
 146:	                ktime_get_real_ts64(&ts);
 147:	                if (put_user(ts.tv_sec, &tv->tv_sec) ||
 148:	                    put_user(ts.tv_nsec / 1000, &tv->tv_usec))
 149:	                        return -EFAULT;
 150:	        }
 151:	        if (unlikely(tz != NULL)) {
 152:	                if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))
 153:	                        return -EFAULT;
 154:	        }
 155:	        return 0;
 156:	}

linux-6.12.7/kernel/time/timekeeping.c
 719:	void ktime_get_real_ts64(struct timespec64 *ts)
 720:	{
 721:	        struct timekeeper *tk = &tk_core.timekeeper;
 722:	        unsigned int seq;
 723:	        u64 nsecs;
...
 730:	                ts->tv_sec = tk->xtime_sec;
 731:	                nsecs = timekeeping_get_ns(&tk->tkr_mono);
...
 735:	        ts->tv_nsec = 0;
 736:	        timespec64_add_ns(ts, nsecs);
 737:	}

◆update_wall_time()

update_wall_time() は、 tick_periodic() から呼ばれ、掛け時計の時刻 xtime を更新する。

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

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

◆struct timer_list

linux-6.12.7/include/linux/timer_types.h
   8:	struct timer_list {
...
  14:	        unsigned long           expires;
  15:	        void                    (*function)(struct timer_list *);
...
  21:	};

jiffies が増加して expires に達すれば、(*function)(tl) を呼ぶ。 引数 tl は、struct timer_list *。

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

timer_setup(struct timer_list *timer, void (*callback)(struct timer_list *), unsigned int flags);
timer を初期化する。function は callback で初期化される。
add_timer(struct timer_list *timer)
add_timer_on(struct timer_list *timer, int cpu)
タイマの登録。_on() は、CPU ごと。
mod_timer(struct timer_list *timer, unsigned long expires)
タイマの起動時刻の変更
del_timer(struct timer_list *timer),
del_timer_sync(struct timer_list *timer)
try_to_del_timer_sync(struct timer_list *timer)
タイマのキャンセル。 del_timer_sync() は、もし既に実行中だったら、実行完了を待つ。
(*function)() が呼ばれる時には、struct timer_list *が渡される。 普通、これだけでは不十分で、独自のデータ(構造体へのへポインタ)が必要になる。

(*function)() で独自のデータ(以下の例では struct s1 *)を得るには、次の ように from_timer() マクロか container_of() マクロを用いる。

◆container_of()

container_of() は、内側に定義された構造体へのポインタから外側に定義さ れた構造体のポインタを得るためのマクロ。内側の構造体のオフセットを計算 して、引き算している。
struct s1 {
	...
        struct timer_list s_timer;
	...
        int s_x;
	...
};

void timer_list_handler(struct timer_list *tl) {
	struct s1 *p1 = from_timer(p1, tl, s_timer);
	f( p1->s_x );
}

図? 独自の構造体、struct timer_list timer、from_timer、container_ob
図? timer_list から外側の構造体を求める

linux-6.12.7/include/linux/timer.h
 132:	#define from_timer(var, callback_timer, timer_fieldname) \
 133:	        container_of(callback_timer, typeof(*var), timer_fieldname)

linux-6.12.7/include/linux/container_of.h
  18:	#define container_of(ptr, type, member) ({                              \
  19:	        void *__mptr = (void *)(ptr);                                   \
...
  23:	        ((type *)(__mptr - offsetof(type, member))); })

linux-6.12.7/include/linux/stddef.h
  16:	#define offsetof(TYPE, MEMBER)  __builtin_offsetof(TYPE, MEMBER)

linux-6.12.7/tools/include/linux/kernel.h
  25:	#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

◆schedule_timeout()

引数で指定された時間だけしばらく待つ。 時間の単位は、tick。
linux-6.12.7/kernel/time/timer.c
2534:	struct process_timer {
2535:	        struct timer_list timer;
2536:	        struct task_struct *task;
2537:	};

2577:	signed long __sched schedule_timeout(signed long timeout)
2578:	{
2579:	        struct process_timer timer;
2580:	        unsigned long expire;
2581:	
...
2611:	        expire = timeout + jiffies;
2612:	
2613:	        timer.task = current;
2614:	        timer_setup_on_stack(&timer.timer, process_timeout, 0);
2615:	        __mod_timer(&timer.timer, expire, MOD_TIMER_NOTPENDING);
2616:	        schedule();
2617:	        del_timer_sync(&timer.timer);
2618:	
2619:	        /* Remove the timer from the object tracker */
2620:	        destroy_timer_on_stack(&timer.timer);
2621:	
2622:	        timeout = expire - jiffies;
2623:	
2624:	 out:
2625:	        return timeout < 0 ? 0 : timeout;
2626:	}

2539:	static void process_timeout(struct timer_list *t)
2540:	{
2541:	        struct process_timer *timeout = from_timer(timeout, t, timer);
2542:	
2543:	        wake_up_process(timeout->task);
2544:	}

◆High-resolution kernel timers

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

linux-6.12.7/include/linux/hrtimer.h
  35:	enum hrtimer_mode {
  36:	        HRTIMER_MODE_ABS        = 0x00,
  37:	        HRTIMER_MODE_REL        = 0x01,
...
  56:	};

linux-6.12.7/include/linux/hrtimer_types.h
  13:	enum hrtimer_restart {
  14:	        HRTIMER_NORESTART,      /* Timer is not restarted */
  15:	        HRTIMER_RESTART,        /* Timer must be restarted */
  16:	};

  39:	struct hrtimer {
...
  42:	        enum hrtimer_restart            (*function)(struct hrtimer *);
...
  48:	};
主に次の関数で操作する。
hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode)
struct hrtimer 構造体の初期化。 clock_id としては、モノトニック時刻(CLOCK_MONOTONIC)か、カレンダ時刻 (CLOCK_REALTIME)か選べる。
hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
タイマの開始。tim 秒後(ナノ秒単位)に functioon が呼ばれる。mode は、 絶対指定(HRTIMER_MODE_ABS, absolute)か現在からの相対(HRTIMER_MODE_REL, relative)か指定できる。
hrtimer_cancel(struct hrtimer *timer)
タイマのキャンセル。
schedule_hrtimeout(ktime_t *expires, enum hrtimer_mode mode)
指定した時間だけ現在実行中のプロセスを sleep させる。
ktime_t ktime_get(void)
モノトニック時刻の取得。ktime は、ナノ秒単位。64ビット。
ktime_t ktime_get_real(void)
カレンダ時刻の取得。
ktime_t ktime_set(s64 sec, unsigned long nsec)
sec 秒, nsec ナノ秒の 2 値から ktime へ変換する。
利用例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;
}

■実行の遅延

デバイスドライバでは、遅いデバイスに合わせるためにしばらく待ってから処 理をしたいことが多い。得に割り込みの後半部(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,delay))
    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-6.12.7/include/linux/jiffies.h
 127:	#define time_after(a,b)         \
 128:	        (typecheck(unsigned long, a) && \
 129:	         typecheck(unsigned long, b) && \
 130:	         ((long)((b) - (a)) < 0))
...
 138:	#define time_before(a,b)        time_after(b,a)
...
 147:	#define time_after_eq(a,b)      \
 148:	        (typecheck(unsigned long, a) && \
 149:	         typecheck(unsigned long, b) && \
 150:	         ((long)((a) - (b)) >= 0))
...
 158:	#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,delay))
    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 が使われている。

■スケジューリングの目標

復習(オペレーティングシステム(I)の範囲) 全部は同時には成り立たない。

◆スケジューリング・アルゴリズム

◆優先順位式スケジューリング(OS一般)

◆レディ・キュー(OS一般)

レディ・キューは、レディ状態のプロセスのリスト。優先度の順に並んでいれ ば、CPU はリストの先頭から要素を取り出して実行する。

■プロセス・スケジューリング関連のAPI

◆Unixにおけるプロセスに関するシステム・コール

注意: Linux そのものは、実時間OSではない。 本当に決められた時間内に完了するが求められる処理(ハードリアルタイム)には使えない。 少し遅れても意味がある(ソフトリアルタイム)には、使えることがある。

◆ps lコマンド

ps l の結果のうち、次の属性に注目。
表示 説明
NI Nice。優先度を表す値。
$ /bin/ps l [←]
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1013 20638 20636  20   0 123572  2100 wait   Ss   pts/2      0:00 -bash
0  1013 21139 20638  20   0 155660  5900 poll_s S    pts/2      0:02 xterm -class UXTerm -title uxterm -u8
0  1013 21150 21139  20   0 123552  2144 wait   Ss   pts/3      0:00 bash
0  1013 21560 20638  20   0 267808 22928 poll_s S+   pts/2      0:09 emacs -nw
0  1013 21784 21150  20   0 103748   956 signal T    pts/3      0:00 lv kernel/time/timer.c
0  1013 27031 21150  20   0 108132   980 -      R+   pts/3      0:00 /bin/ps l
$ /bin/nice /bin/ps l [←]
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1013 20638 20636  20   0 123572  2100 wait   Ss   pts/2      0:00 -bash
0  1013 21139 20638  20   0 155660  5900 poll_s S    pts/2      0:02 xterm -class UXTerm -title uxterm -u8
0  1013 21150 21139  20   0 123552  2144 wait   Ss   pts/3      0:00 bash
0  1013 21560 20638  20   0 267808 22928 poll_s S+   pts/2      0:09 emacs -nw
0  1013 21784 21150  20   0 103748   956 signal T    pts/3      0:00 lv kernel/time/timer.c
0  1013 27034 21150  30  10 108136   984 -      RN+  pts/3      0:00 /bin/ps l
$ /bin/nice -19 /bin/ps l [←]
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1013 20638 20636  20   0 123572  2100 wait   Ss   pts/2      0:00 -bash
0  1013 21139 20638  20   0 155660  5900 -      R    pts/2      0:02 xterm -class UXTerm -title uxterm -u8
0  1013 21150 21139  20   0 123552  2144 wait   Ss   pts/3      0:00 bash
0  1013 21560 20638  20   0 267808 22928 poll_s S+   pts/2      0:09 emacs -nw
0  1013 21784 21150  20   0 103748   956 signal T    pts/3      0:00 lv kernel/time/timer.c
0  1013 27035 21150  39  19 108132   984 -      RN+  pts/3      0:00 /bin/ps l
$ []

◆getpriority-pid.c

   1:	/*
   2:	        getpriority-pid.c -- 優先度の表示
   3:	        ~yas/syspro/proc/getpriority-pid.c
   4:	        Created on: 2009/12/14 12:15:11
   5:	*/
   6:	
   7:	#include <stdio.h>              /* stderr, fprintf() */
   8:	#include <sys/time.h>           /* getpriority() */
   9:	#include <sys/resource.h>       /* getpriority() */
  10:	#include <stdlib.h>             /* strtol() */
  11:	#include <limits.h>             /* strtol() */
  12:	
  13:	main( int argc, char *argv[] )
  14:	{
  15:	    int which, who, prio;
  16:	    pid_t pid;
  17:	        if( argc != 2 )
  18:	        {
  19:	            fprintf(stderr,"Usage: %% %s pid\n",argv[0] );
  20:	            exit( 1 );
  21:	        }
  22:	        pid = strtol( argv[1], NULL, 10 );
  23:	        prio = getpriority( PRIO_PROCESS, pid );
  24:	        printf("pid==%d, priority==%d\n", pid, prio);
  25:	}
$ ./getpriority-pid  [←]
Usage: % ./getpriority-pid pid
$ echo $$ [←]
21150
$ ./getpriority-pid  [←]
Usage: % ./getpriority-pid pid
$ ./getpriority-pid $$ [←]
pid==21150, priority==0
$ ./getpriority-pid 0 [←]
pid==0, priority==0
$ /bin/nice -10 ./getpriority-pid 0 [←]
pid==0, priority==10
$ /bin/nice -20 ./getpriority-pid 0 [←]
pid==0, priority==19
$ []

◆nice値の利用法

優先度の調整方法 nice値の厳密な「意味」は、Unix の実装で異なる。 nice値とCPU時管理割当て方の標準はない。 Linux でも、何度か変更されている。

◆Linux(CFS)での通常のプロセス(非実時間プロセス)でのnice値の意味

例1: 次の2つの CPU-bound のプロセスが存在したとする。 AのCPU時間 : BのCPU時間 == 1 : 0.9

◆スケジューリングを行うためのハードウェア

やりたいことの例: 今のプロセスを 50ミリ秒だけ実行して、50ミリ秒後に別の プロセスを実行したい。

Unix では、定期的な割り込み(10ミリ秒から1ミリ秒に1回)を使う方法がよく使われる。 1回の割り込みを tick という。

■Linux task構造体とnice値

Linux のプロセスは、task 構造体で表現されている。

◆task_struct構造体

linux-6.12.7/include/linux/sched.h
 785:	struct task_struct {
...
 793:	        unsigned int                    __state;
...
 833:	        int                             prio;
 834:	        int                             static_prio;
 835:	        int                             normal_prio;
 836:	        unsigned int                    rt_priority;
 837:	
 838:	        struct sched_entity             se;
 839:	        struct sched_rt_entity          rt;
 840:	        struct sched_dl_entity          dl;
...
 845:	        const struct sched_class        *sched_class;
...
 882:	        unsigned int                    policy;
...
1617:	};

 541:	struct sched_entity {
 542:	        /* For load-balancing: */
 543:	        struct load_weight              load;
 544:	        struct rb_node                  run_node;
 545:	        u64                             deadline;
...
 550:	        unsigned char                   on_rq;
...
 556:	        u64                             exec_start;
 557:	        u64                             sum_exec_runtime;
 558:	        u64                             prev_sum_exec_runtime;
 559:	        u64                             vruntime;
 560:	        s64                             vlag;
...
 585:	};

 426:	struct load_weight {
 427:	        unsigned long                   weight;
 428:	        u32                             inv_weight;
 429:	};
struct task_struct の中に、prio 等のフィールドやstruct sched_entity が ある。

struct sched_entity で重要なフィールド。詳しくは後述。

◆policy

Linux では、スケジューリングのポリシーが大きく2種類。通常の時分割と実時間。
linux-6.12.7/include/uapi/linux/sched.h
 114:	#define SCHED_NORMAL            0
 115:	#define SCHED_FIFO              1
 116:	#define SCHED_RR                2
 117:	#define SCHED_BATCH             3
 118:	/* SCHED_ISO: reserved but not implemented yet */
 119:	#define SCHED_IDLE              5
 120:	#define SCHED_DEADLINE          6
 121:	#define SCHED_EXT               7
SCHED_NORMAL
伝統的なプロセス。時分割(time sharing)
SCHED_FIFO (first in first out)
実時間で FIFO
SCHED_RR (round robin)
実時間でラウンドロビン
SCHED_BATCH
パッチ向け。一度CPUを割り当てたら横取りしない。
SCHED_IDLE
nice 19 よりも低い優先度。
SCHED_DEADLINE
実時間で、デッドラインが一番近いもの (Earliest Deadline First (EDF) )
SCHED_EXT
BPF のプログラムで動作を記述できる。

◆prioとstatic_prio

static_prio
nice値を保持する。ただし、次のようなゲタを履かせている (NICE_TO_PRIO()参照)。
prio
スケジューリングに用いる優先度を保持する。
prio を見ると、通常のプロセスか実時間のプロセスかがわかる。 100 以上なら、通常のプロセス。通常のプロセスの prio は、100から140。

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

linux-6.12.7/kernel/sys.c
 297:	SYSCALL_DEFINE2(getpriority, int, which, int, who)
 298:	{
 299:	        struct task_struct *g, *p;
 300:	        struct user_struct *user;
 301:	        const struct cred *cred = current_cred();
 302:	        long niceval, retval = -ESRCH;
 303:	        struct pid *pgrp;
 304:	        kuid_t uid;
...
 310:	        switch (which) {
 311:	        case PRIO_PROCESS:
 312:	                if (who)
 313:	                        p = find_task_by_vpid(who);
 314:	                else
 315:	                        p = current;
 316:	                if (p) {
 317:	                        niceval = nice_to_rlimit(task_nice(p));
 318:	                        if (niceval > retval)
 319:	                                retval = niceval;
 320:	                }
 321:	                break;
 322:	        case PRIO_PGRP:
...
 335:	        case PRIO_USER:
...
 355:	        }
...
 359:	        return retval;
 360:	}

linux-6.12.7/include/linux/sched/prio.h
   5:	#define MAX_NICE        19
   6:	#define MIN_NICE        -20
   7:	#define NICE_WIDTH      (MAX_NICE - MIN_NICE + 1)
...
  16:	#define MAX_RT_PRIO             100
...
  19:	#define MAX_PRIO                (MAX_RT_PRIO + NICE_WIDTH)
...
  27:	#define NICE_TO_PRIO(nice)      ((nice) + DEFAULT_PRIO)
  28:	#define PRIO_TO_NICE(prio)      ((prio) - DEFAULT_PRIO)
...
  33:	static inline long nice_to_rlimit(long nice)
  34:	{
  35:	        return (MAX_NICE - nice + 1);
  36:	}

linux-6.12.7/include/linux/sched.h
1858:	static inline int task_nice(const struct task_struct *p)
1859:	{
1860:	        return PRIO_TO_NICE((p)->static_prio);
1861:	}

glibc-2.35/sysdeps/unix/sysv/linux/getpriority.c
  27:	#define PZERO 20
...
  34:	int
  35:	__getpriority (enum __priority_which which, id_t who)
  36:	{
  37:	  int res;
  38:	
  39:	  res = INLINE_SYSCALL (getpriority, 2, (int) which, who);
  40:	  if (res >= 0)
  41:	    res = PZERO - res;
  42:	  return res;
  43:	}
  44:	libc_hidden_def (__getpriority)
  45:	weak_alias (__getpriority, getpriority)

◆se.load.weight

struct task_struct の static_prio は、struct task_struct の se.load.weight の値を決めるために使われる。
linux-6.12.7/kernel/sched/core.c
9998:	/*
9999:	 * Nice levels are multiplicative, with a gentle 10% change for every
10000:	 * nice level changed. I.e. when a CPU-bound task goes from nice 0 to
10001:	 * nice 1, it will get ~10% less CPU time than another CPU-bound task
10002:	 * that remained on nice 0.
10003:	 *
10004:	 * The "10% effect" is relative and cumulative: from _any_ nice level,
10005:	 * if you go up 1 level, it's -10% CPU usage, if you go down 1 level
10006:	 * it's +10% CPU usage. (to achieve that we use a multiplier of 1.25.
10007:	 * If a task goes up by ~10% and another task goes down by ~10% then
10008:	 * the relative distance between them is ~25%.)
10009:	 */
10010:	const int sched_prio_to_weight[40] = {
10011:	 /* -20 */     88761,     71755,     56483,     46273,     36291,
10012:	 /* -15 */     29154,     23254,     18705,     14949,     11916,
10013:	 /* -10 */      9548,      7620,      6100,      4904,      3906,
10014:	 /*  -5 */      3121,      2501,      1991,      1586,      1277,
10015:	 /*   0 */      1024,       820,       655,       526,       423,
10016:	 /*   5 */       335,       272,       215,       172,       137,
10017:	 /*  10 */       110,        87,        70,        56,        45,
10018:	 /*  15 */        36,        29,        23,        18,        15,
10019:	};

1368:	void set_load_weight(struct task_struct *p, bool update_load)
1369:	{
1370:	        int prio = p->static_prio - MAX_RT_PRIO;
1371:	        struct load_weight lw;
...
1377:	                lw.weight = scale_load(sched_prio_to_weight[prio]);
1378:	                lw.inv_weight = sched_prio_to_wmult[prio];
...
1388:	                p->se.load = lw;
1389:	}

linux-6.12.7/include/linux/sched.h
 419:	# define SCHED_FIXEDPOINT_SHIFT         10
 420:	# define SCHED_FIXEDPOINT_SCALE         (1L << SCHED_FIXEDPOINT_SHIFT)

linux-6.12.7/kernel/sched/sched.h
 151:	#ifdef CONFIG_64BIT
 152:	# define NICE_0_LOAD_SHIFT      (SCHED_FIXEDPOINT_SHIFT + SCHED_FIXEDPOINT_SHIFT)
 153:	# define scale_load(w)          ((w) << SCHED_FIXEDPOINT_SHIFT)
...
 162:	#else
 163:	# define NICE_0_LOAD_SHIFT      (SCHED_FIXEDPOINT_SHIFT)
 164:	# define scale_load(w)          (w)
...
 166:	#endif

■スケジューラとレディ・キュー

◆sched_class

オブジェクト指向のクラスと同じ意味。 スケジューラのための手続きの集合。
sched_classの主要な手続き
名前 説明
enqueue_task プロセスが実行可能(runnable)になった
dequeue_task プロセスが実行可能ではなくなった
yield_task CPUを譲る。dequeueしてenqueue
check_preempt_curr 実行可能になった時にCPUを横取りすべきかをチェック
pick_next_task 次に実行すべきプロセスを選ぶ
set_curr_task スケジューリング・クラスが変更された
task_tick タイマ割込み(tick)の時に呼ばれる
task_new 新しいプロセスが生成された

◆sched_class使い方

プロセスのクラスに応じて enqueue、 dequeue の操作を切り替える。
linux-6.12.7/kernel/sched/sched.h
2387:	struct sched_class {
...
2393:	        void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
2394:	        bool (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
...
2411:	        struct task_struct *(*pick_next_task)(struct rq *rq, struct task_struct *prev);
...
2431:	        void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);
...
2460:	};

linux-6.12.7/kernel/sched/core.c
2015:	void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
2016:	{
...
2020:	        p->sched_class->enqueue_task(rq, p, flags);
...
2034:	}

2039:	inline bool dequeue_task(struct rq *rq, struct task_struct *p, int flags)
2040:	{
...
2057:	        return p->sched_class->dequeue_task(rq, p, flags);
2058:	}

◆Linuxの主要なスケジューリング・クラス

fair_sched_class
公平を目指す。task_struct の policy が SCHED_NORMAL の時に使われる。
rt_sched_class
実時間を目指す。task_struct の policy が SCHED_FIFO と SCHED_RR の時に使われる。
dl_sched_class
実時間を目指す。task_struct の policy が SCHED_DEADLINE の時に使われる。

◆スケジューラ・クラスの設定

linux-6.12.7/kernel/sched/syscalls.c
 526:	int __sched_setscheduler(struct task_struct *p,
 527:	                         const struct sched_attr *attr,
 528:	                         bool user, bool pi)
 529:	{
...
 710:	        next_class = __setscheduler_class(policy, newprio);
...
 723:	                __setscheduler_params(p, attr);
 724:	                p->sched_class = next_class;
 725:	                p->prio = newprio;
...
 766:	        return retval;
 767:	}

 293:	static void __setscheduler_params(struct task_struct *p,
 294:	                const struct sched_attr *attr)
 295:	{
 296:	        int policy = attr->sched_policy;
 297:	
 298:	        if (policy == SETPARAM_POLICY)
 299:	                policy = p->policy;
 300:	
 301:	        p->policy = policy;
 302:	
 303:	        if (dl_policy(policy)) {
...
 305:	        } else if (fair_policy(policy)) {
 306:	                p->static_prio = NICE_TO_PRIO(attr->sched_nice);
...
 316:	        }
...
 332:	        p->normal_prio = normal_prio(p);
 333:	        set_load_weight(p, true);
 334:	}

linux-6.12.7/kernel/sched/core.c
7032:	const struct sched_class *__setscheduler_class(int policy, int prio)
7033:	{
7034:	        if (dl_prio(prio))
7035:	                return &dl_sched_class;
7036:	
7037:	        if (rt_prio(prio))
7038:	                return &rt_sched_class;
7039:	
7040:	#ifdef CONFIG_SCHED_CLASS_EXT
7041:	        if (task_should_scx(policy))
7042:	                return &ext_sched_class;
7043:	#endif
7044:	
7045:	        return &fair_sched_class;
7046:	}

■CFS(Completely Fair Scheduler)

CFS は、Linux のスケジューラの固有名詞。 fair を目指してはいるが、complete に fair かと言われると不明。

CPUバウンドのプロセスが複数存在した時、ある期間を定めて、この期間の間に、 (優先度を考慮して)公平になるようにCPU資源を割り当てる。この期間の間に、 1度はCPUを割り当てられるようにがんばる。この期間は、 kernel.sched_latency_ns で設定されている。以下の例では、15ミリ秒。

$ sysctl kernel.sched_latency_ns [←]
kernel.sched_latency_ns = 15000000
$ []
たとえば、もし優先度が同じプロセスAとプロセスBが存在した時には、15ミリ 秒の間にプロセスAに7.5ミリ秒、プロセスBに7.5ミリ秒のCPU資源を割り当てる ようにがんばる。

Linux CFS は、次の方法でスケジューリングを行なう。

参考:

◆The Earliest Eligible Virtual Deadline First (EEVDF) Scheduler

Linux 6.6 から CFS に代り EEVDF (Earliest Eligible Virtual Deadline First) (Earliest Virtual Deadline First) とい うスケジューラが使えるようになった。

EEVDF スケジューラの目標は CFS と同じ。使用可能なCPU時間をシステム内の すべての実行可能なタスク間で均等に分割すること。 EDF (Earliest Deadline First) は、実時間のスケジューリング方式だが、 EEVDF は、名前は似ているが、fair を目指している。

例:

  1. A, B, C という 3 つの実行可能な CPU-bound のプロセスがある。 どれも実行されていない。 Lag値の合計は0になる。
    プロセス A B C
    Lag値 0 ms 0 ms 0 ms
  2. Lag値が0か正なら、スケジュールされる資格がある(eligibleである)。 スケジューラが資格があるプロセスとして A を選び、30 ミリ秒実行した。 本来なら、各タスクは 10 ミリ秒(30/3)実行すべきであった。 A, B, C の Lag 値に 10 ミリ秒を足す。 ただ、A は、30 ミリ秒実行したので、30 ミリ秒を引く。
    プロセス A B C
    Lag値 -20 ms 10 ms 10 ms
  3. A は、マイナスになり、実行される資格がなくなる(eligibleではない)。 スケジューラは、資格があるものの中から B を選んで、また 30 ミリ秒実行する。 A, B, C の Lag 値に 10 ミリ秒を足す。 ただ、B は、30 ミリ秒実行したので、30 ミリ秒を引く。
    プロセス A B C
    Lag値 -10 ms -10 ms 20 ms
  4. 次にスケジューラは、資格がある C を選んで、30 ミリ秒実行する。 A, B, C の Lag 値に 10 ミリ秒を足す。 ただ、C は、30 ミリ秒実行したので、30 ミリ秒を引く。
    プロセス A B C
    Lag値 0 ms 0 ms 0 ms
参考:

◆runqueues(リスト的な見方)

Linux における ready queue の実装では、tasks_timeline を出発点とする リストと考えて良い。 実際には、struct sched_entity se (struct task_structの途中) をつないでいく。 ノードのキーは、CFS では vruntime、EEVDF では deadline。

runqueue、tasks_timeline、se.vruntime
図? runqueueの構造

linux-6.12.7/kernel/sched/core.c
 120:	DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);

linux-6.12.7/kernel/sched/sched.h
1105:	struct rq {
...
1137:	        struct cfs_rq           cfs;
1138:	        struct rt_rq            rt;
1139:	        struct dl_rq            dl;
1140:	#ifdef CONFIG_SCHED_CLASS_EXT
1141:	        struct scx_rq           scx;
1142:	#endif
...
1311:	};

 651:	struct cfs_rq {
...
 668:	        struct rb_root_cached   tasks_timeline;
...
 674:	        struct sched_entity     *curr;
...
 748:	};

◆赤黒木(red-black tree (rbtree))

赤黒木(red-black tree) は、平衡二分探索木(self-balancing binary search tree)の一種。 節は、赤と黒に分類される。

二分探索木とは、次のような二分木。 平衡木(balanced tree)、または、高さ平衡木(height-balanced tree)は、任意 の節で左右の高さの差が一定以下の木。

Linux では、赤黒木をソートされた要素が並ぶリストを実現するために使っている。

◆Linux red-black treeの基本操作

型定義で、各要素に次の要素を含める。 検索
  1. 現在のノードとキーを比較
  2. 等しいなら見つかった
  3. キーが小さいなら左の枝へ
  4. キーが大きいなら右の枝へ
  5. 枝がなければキーは存在しない
挿入
  1. まず検索する。現在のノードと挿入したいデータのキーを比較する。
  2. キーが小さいなら左の枝を「親」にして検索を続ける。
  3. キーが大きいなら右の枝を「親」にして検索を続ける。
  4. キーが等しいならエラー(エラーにせず、重複を許すこともある)
  5. 子供がいない「親」が見つかる。
  6. 「親」から挿入したいデータへのリンクを作成する(rb_link_node())
  7. 平衡になるようにする(rebalancing, recoloring, rb_insert_color())

◆runqueues(red-black tree)

レディ・キューは、実際にはred-black tree による木構造になっている。 tasks_timeline は、木の根を差す。 ノードのキーは、CFS では vruntime、EEVDF では deadline。

runqueue、tasks_timeline、se.vruntime、red-black tree
図? runqueueの構造(red-black tree)

◆EEVDF/CFSのstruct sched_class

EEVDF も従来の CFS と同じく kernel/sched/fair.c に実装されている。
linux-6.12.7/kernel/sched/sched.h
2508:	#define DEFINE_SCHED_CLASS(name) \
2509:	const struct sched_class name##_sched_class \
2510:	        __aligned(__alignof__(struct sched_class)) \
2511:	        __section("__" #name "_sched_class")

linux-6.12.7/kernel/sched/fair.c
13617:	DEFINE_SCHED_CLASS(fair) = {
13618:	
13619:	        .enqueue_task           = enqueue_task_fair,
13620:	        .dequeue_task           = dequeue_task_fair,
...
13626:	        .pick_task              = pick_task_fair,
...
13643:	        .task_tick              = task_tick_fair,
...
13666:	};

◆__enqueue_entity()

__enqueue_entity()は、CFS で木構造に要素を1個追加する関数である。 関数 __entity_less() でノードを比較して、二分探索木を作る。 __entity_less() は、CFS では vruntime、EEVDF では deadline を比較する。
linux-6.12.7/kernel/sched/fair.c
6990:	static void
6991:	enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
6992:	{
...
7043:	                enqueue_entity(cfs_rq, se, flags);
...
7113:	}

5391:	enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
5392:	{
...
5438:	                __enqueue_entity(cfs_rq, se);
...
5456:	}

 852:	static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
 853:	{
...
 857:	        rb_add_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline,
 858:	                                __entity_less, &min_vruntime_cb);
 859:	}

 861:	static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
 862:	{
 863:	        rb_erase_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline,
 864:	                                  &min_vruntime_cb);
...
 866:	}

 800:	static inline bool __entity_less(struct rb_node *a, const struct rb_node *b)
 801:	{
 802:	        return entity_before(__node_2_se(a), __node_2_se(b));
 803:	}

 544:	static inline bool entity_before(const struct sched_entity *a,
 545:	                                 const struct sched_entity *b)
 546:	{
...
 551:	        return (s64)(a->deadline - b->deadline) < 0;
 552:	}

linux-6.12.7/include/linux/rbtree_augmented.h
  63:	static __always_inline struct rb_node *
  64:	rb_add_augmented_cached(struct rb_node *node, struct rb_root_cached *tree,
  65:	                        bool (*less)(struct rb_node *, const struct rb_node *),
  66:	                        const struct rb_augment_callbacks *augment)
  67:	{
  68:	        struct rb_node **link = &tree->rb_root.rb_node;
  69:	        struct rb_node *parent = NULL;
  70:	        bool leftmost = true;
  71:	
  72:	        while (*link) {
  73:	                parent = *link;
  74:	                if (less(node, parent)) {
  75:	                        link = &parent->rb_left;
  76:	                } else {
  77:	                        link = &parent->rb_right;
  78:	                        leftmost = false;
  79:	                }
  80:	        }
  81:	
  82:	        rb_link_node(node, parent, link);
  83:	        augment->propagate(parent, NULL); /* suboptimal */
  84:	        rb_insert_augmented_cached(node, tree, leftmost, augment);
  85:	
  86:	        return leftmost ? node : NULL;
  87:	}

linux-6.12.7/include/linux/rbtree.h
  59:	static inline void rb_link_node(struct rb_node *node, struct rb_node *parent,
  60:	                                struct rb_node **rb_link)
  61:	{
...
  63:	        node->rb_left = node->rb_right = NULL;
  64:	
  65:	        *rb_link = node;
  66:	}

◆tickごとの仕事

scheduler_tick() は、tick ごとに定期的に呼び出される。 1 tick は、10 ミリ秒(HZ == 100)、4ミリ秒(HZ == 250)、1 ミリ秒(HZ == 1000)が一般的。
linux-6.12.7/kernel/sched/core.c
5585:	void sched_tick(void)
5586:	{
5587:	        int cpu = smp_processor_id();
5588:	        struct rq *rq = cpu_rq(cpu);
5589:	        struct task_struct *curr;
...
5601:	        curr = rq->curr;
...
5607:	        curr->sched_class->task_tick(rq, curr, 0);
...
5631:	}

◆EEVDFのentity_tick()

linux-6.12.7/kernel/sched/sched.h
13096:	static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
13097:	{
13098:	        struct cfs_rq *cfs_rq;
13099:	        struct sched_entity *se = &curr->se;
13100:	
13101:	        for_each_sched_entity(se) {
13102:	                cfs_rq = cfs_rq_of(se);
13103:	                entity_tick(cfs_rq, se, queued);
13104:	        }
...
13113:	}

5691:	static void
5692:	entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
5693:	{
...
5697:	        update_curr(cfs_rq);
...
5721:	}

1214:	static void update_curr(struct cfs_rq *cfs_rq)
1215:	{
1216:	        struct sched_entity *curr = cfs_rq->curr;
1217:	        struct rq *rq = rq_of(cfs_rq);
1218:	        s64 delta_exec;
1219:	        bool resched;
...
1224:	        delta_exec = update_curr_se(rq, curr);
...
1228:	        curr->vruntime += calc_delta_fair(delta_exec, curr);
1229:	        resched = update_deadline(cfs_rq, curr);
1230:	        update_min_vruntime(cfs_rq);
...
1253:	        if (cfs_rq->nr_running == 1)
1254:	                return;
1255:	
1256:	        if (resched || did_preempt_short(cfs_rq, curr)) {
1257:	                resched_curr(rq);
1258:	                clear_buddies(cfs_rq, curr);
1259:	        }
1260:	}

1134:	static s64 update_curr_se(struct rq *rq, struct sched_entity *curr)
1135:	{
1136:	        u64 now = rq_clock_task(rq);
1137:	        s64 delta_exec;
1138:	
1139:	        delta_exec = now - curr->exec_start;
...
1143:	        curr->exec_start = now;
1144:	        curr->sum_exec_runtime += delta_exec;
...
1154:	        return delta_exec;
1155:	}

 289:	static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
 290:	{
 291:	        if (unlikely(se->load.weight != NICE_0_LOAD))
 292:	                delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
 293:	
 294:	        return delta;
 295:	}

1007:	static bool update_deadline(struct cfs_rq *cfs_rq, struct sched_entity *se)
1008:	{
1009:	        if ((s64)(se->vruntime - se->deadline) < 0)
1010:	                return false;
...
1023:	        se->deadline = se->vruntime + calc_delta_fair(se->slice, se);
...
1028:	        return true;
1029:	}

◆[資料] 以前のCFSのentity_tick()

現在(2025年)広く使われている CFS のentity_tick())
linux-6.6.9/kernel/sched/fair.c
12480:	static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
12481:	{
12482:	        struct cfs_rq *cfs_rq;
12483:	        struct sched_entity *se = &curr->se;
12484:	
12485:	        for_each_sched_entity(se) {
12486:	                cfs_rq = cfs_rq_of(se);
12487:	                entity_tick(cfs_rq, se, queued);
12488:	        }
...
12497:	}

5395:	static void
5396:	entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
5397:	{
...
5401:	        update_curr(cfs_rq);
...
5425:	}

1150:	static void update_curr(struct cfs_rq *cfs_rq)
1151:	{
1152:	        struct sched_entity *curr = cfs_rq->curr;
1153:	        u64 now = rq_clock_task(rq_of(cfs_rq));
1154:	        u64 delta_exec;
...
1159:	        delta_exec = now - curr->exec_start;
...
1163:	        curr->exec_start = now;
...
1173:	        curr->sum_exec_runtime += delta_exec;
...
1176:	        curr->vruntime += calc_delta_fair(delta_exec, curr);
...
1189:	}

 311:	static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
 312:	{
 313:	        if (unlikely(se->load.weight != NICE_0_LOAD))
 314:	                delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
 315:	
 316:	        return delta;
 317:	}

◆/proc を使ったスケジューラのパラメタの表示(CSF)

$ cat /proc/sched_debug [←]
Sched Debug Version: v0.11, 4.18.0-477.15.1.el8_8.x86_64 #1
...
jiffies                                 : 8037018588
...
sysctl_sched
  .sysctl_sched_latency                    : 18.000000
  .sysctl_sched_min_granularity            : 10.000000
...

cpu#0, 3092.734 MHz
  .nr_running                    : 0
...
  .curr->pid                     : 0
...
cfs_rq[0]:/system.slice/shibd.service
...
  .se->exec_start                : 3740976147.098853
  .se->vruntime                  : 16904453.116095
  .se->sum_exec_runtime          : 488423.358201

cfs_rq[0]:/system.slice/mariadb.service
...
  .se->exec_start                : 3740976147.098853
  .se->vruntime                  : 16904453.116095
  .se->sum_exec_runtime          : 488423.358201
...

cfs_rq[0]:/system.slice/httpd.service
...
  .se->exec_start                : 3740976409.981736
  .se->vruntime                  : 16904452.748796
  .se->sum_exec_runtime          : 467578.885170
...
runnable tasks:
 S            task   PID         tree-key  switches  prio     wait-time             sum-exec        sum-sleep
-------------------------------------------------------------------------------------------------------------
...
 S           shibd 2029016     36399.620037   1312918   120         0.000000     17147.974128         0.000000 0 0 /system.slice/shibd.service
...
 S          mysqld 3062817    223694.881335      2877   120         0.000000       377.018340         0.000000 0 0 /system.slice/mariadb.service
...
 S           httpd 1887526    304759.136719     29475   120         0.000000      4818.694375         0.000000 0 0 /system.slice/httpd.service
...
>R             cat 3065061  23683938.282852         1   120         0.000000         2.674255         0.000000 0 0 /
...
cpu#1, 3092.734 MHz
..
cpu#2, 3092.734 MHz
...
cpu#3, 3092.734 MHz
...
$ cat /proc/self/sched [←]
cat (3065430, #threads: 1)
-------------------------------------------------------------------
se.exec_start                                :    3736477629.503557
se.vruntime                                  :      24389027.685650
se.sum_exec_runtime                          :             0.463870
...
se.load.weight                               :              1048576
...
policy                                       :                    0
prio                                         :                  120
...
$ []

■課題4 時刻と時間の管理、プロセスのスケジューリング

★問題(401) PIT

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

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

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

★問題(403) struct timer_listの利用

関数f()を実行している時に、次の関数h()を、 40 ミリ秒後に実行したいとする。
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) {
    timer_setup( /*空欄(a)*/, /*空欄(b)*/, 0);
    my_timer.expires  = /*空欄(c)*/;
    /*空欄(d)*/;
}
void my_timer_func(/*省略*/) {
     h( my_arg_a,my_arg_b,my_arg_c );
}

★問題(404) sched_class

struct task_struct のフィールド sched_class は、プロセスのスケジューリ ング・ポリシーに応じていくつかの値がセットされる。ポリシーが SCHED_NORMAL の時、どのような値がセットされるか。この Web ページの資料 の中から選んで答えなさい。

またポリシーがSCHED_NORMAL の時、 sched_class->task_tick() として呼ばれる関数を答えなさい。

★問題(405) 二分探索木によるレディ・キューの実装

以下の図は、4つの要素を持つリストを表している。各要素には、キーがあり、 優先度を表しているものとする。

head、next、next、next
図? 4つの要素を持つリスト構造

このリストを表現した二分探索木を1つ作り、節と枝(矢印)を用いて図示し なさい。ただし、木はバランスをしていなくても良いものとする。

注意: 正しい二分探索木は、複数存在する。


Last updated: 2025/01/29 11:09:32
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>