はじめに
Linuxでいわゆるリアルタイムシステムを指向するときはsched(7)のpriority値を意識することになるが、個人的にいつもpriorityの値を相対的にしか見られず、絶対的にかつどっちが上・下かについてわからなくなってしまうので、そんな自分のためにメモ書きしておくことにした・・・つもりが、なにか途中から趣旨を間違えた記事になってしまった。本当は「priority一覧表」がほしかっただけだったのに...
なお、Linux-4.10くらい、procps-ng-3.3.12くらい、util-linux-2.29.2くらいを見ています。
schedのpolicyとpriority値
Linuxでは、ユーザプログラム・kernelスレッド問わず、タスクにsched policyを設定できる。下記の上3つがいわゆるリアルタイムスケジューリング、下3つがいわゆるタイムシェアスケジューリングになる。
- SCHED_DEADLINE, SCHED_FIFO, SCHED_RR
- SCHED_OTHER, SCHED_BATCH, SCHED_IDLE
SCHED_DEADLINEは自動的に最高優先度になるので置いておき、残りのリアルタイムスケジューリングのSCHED_FIFO, SCHED_RRはpriority値を設定できる。これがtask_structのrt_priorityとなり、必ずpriority値の順にスケジューリングされるようになる。
これに対し、タイムシェアスケジューリングはpriorityを設定できない。代わりにnice値を設定できる(SCHED_IDLEを除く)。
なおkernel内でSCHED_OTHERとSCHED_NORMALを混同したコードが見られるため注意が必要。どちらも同じ意味。(というかkernel内はSCHED_NORMALを積極的に使いたい?)
nice値
SCHED_OTHER, SCHED_BATCHでは、nice値によりタイムシェアの割り当て時間がスケールする。寝ていた期間などによりボーナスやpreemtpは働くものの、時間はおおむねnice値が1違うと1.25倍変わるようになる。参考サイト、
- CFSのnice値について : 革命の日々 その2
- 帰ってきたCon Kolivas、大論争を呼ぶの巻(3/3) - @IT
指数的に効くため、大きくnice値が異なると相対的にリアルタイムスケジューリングっぽくなるものの、preempt保障があるわけではないので、リアルタイムスケジューリングと異なりlatency問題は残る。sched_latency_ns使いチューニングできるものの、短くしすぎると今度はcontext swtichのオーバヘッドが問題になる。
nice値のコード上の効き方
1.25倍の根拠は、kernel/kernel/sched/core.cより、
8825 /*
8826 * Nice levels are multiplicative, with a gentle 10% change for every
8827 * nice level changed. I.e. when a CPU-bound task goes from nice 0 to
8828 * nice 1, it will get ~10% less CPU time than another CPU-bound task
8829 * that remained on nice 0.
8830 *
8831 * The "10% effect" is relative and cumulative: from _any_ nice level,
8832 * if you go up 1 level, it's -10% CPU usage, if you go down 1 level
8833 * it's +10% CPU usage. (to achieve that we use a multiplier of 1.25.
8834 * If a task goes up by ~10% and another task goes down by ~10% then
8835 * the relative distance between them is ~25%.)
8836 */
8837 const int sched_prio_to_weight[40] = {
8838 /* -20 */ 88761, 71755, 56483, 46273, 36291,
8839 /* -15 */ 29154, 23254, 18705, 14949, 11916,
8840 /* -10 */ 9548, 7620, 6100, 4904, 3906,
8841 /* -5 */ 3121, 2501, 1991, 1586, 1277,
8842 /* 0 */ 1024, 820, 655, 526, 423,
8843 /* 5 */ 335, 272, 215, 172, 137,
8844 /* 10 */ 110, 87, 70, 56, 45,
8845 /* 15 */ 36, 29, 23, 18, 15,
8846 };
8847
8848 /*
8849 * Inverse (2^32/x) values of the sched_prio_to_weight[] array, precalculated.
8850 *
8851 * In cases where the weight does not change often, we can use the
8852 * precalculated inverse to speed up arithmetics by turning divisions
8853 * into multiplications:
8854 */
8855 const u32 sched_prio_to_wmult[40] = {
8856 /* -20 */ 48388, 59856, 76040, 92818, 118348,
8857 /* -15 */ 147320, 184698, 229616, 287308, 360437,
8858 /* -10 */ 449829, 563644, 704093, 875809, 1099582,
8859 /* -5 */ 1376151, 1717300, 2157191, 2708050, 3363326,
8860 /* 0 */ 4194304, 5237765, 6557202, 8165337, 10153587,
8861 /* 5 */ 12820798, 15790321, 19976592, 24970740, 31350126,
8862 /* 10 */ 39045157, 49367440, 61356676, 76695844, 95443717,
8863 /* 15 */ 119304647, 148102320, 186737708, 238609294, 286331153,
8864 };
計算してもらうとわかるとおり1.25倍になって...いないぞ...結構ぶれていた。下記は小数点3桁までで四捨五入している。
nice値 | sched_prio to_weight |
1.25倍? | sched_prio to_wmult |
1.25倍? |
---|---|---|---|---|
-19 | 88761 | 1.237 | 48388 | 1.237 |
-18 | 71755 | 1.270 | 59856 | 1.270 |
-17 | 56483 | 1.221 | 76040 | 1.221 |
-16 | 46273 | 1.275 | 92818 | 1.275 |
-15 | 36291 | 1.245 | 118348 | 1.245 |
-14 | 29154 | 1.254 | 147320 | 1.254 |
-13 | 23254 | 1.243 | 184698 | 1.243 |
-12 | 18705 | 1.251 | 229616 | 1.251 |
-11 | 14949 | 1.255 | 287308 | 1.255 |
-10 | 11916 | 1.248 | 360437 | 1.248 |
-9 | 9548 | 1.253 | 449829 | 1.253 |
-8 | 7620 | 1.249 | 563644 | 1.249 |
-7 | 6100 | 1.244 | 704093 | 1.244 |
-6 | 4904 | 1.256 | 875809 | 1.256 |
-5 | 3906 | 1.252 | 1099582 | 1.252 |
-4 | 3121 | 1.248 | 1376151 | 1.248 |
-3 | 2501 | 1.256 | 1717300 | 1.256 |
-2 | 1991 | 1.255 | 2157191 | 1.255 |
-1 | 1586 | 1.242 | 2708050 | 1.242 |
0 | 1277 | 1.247 | 3363326 | 1.247 |
1 | 1024 | 1.249 | 4194304 | 1.249 |
2 | 820 | 1.252 | 5237765 | 1.252 |
3 | 655 | 1.245 | 6557202 | 1.245 |
4 | 526 | 1.243 | 8165337 | 1.243 |
5 | 423 | 1.263 | 10153587 | 1.263 |
6 | 335 | 1.232 | 12820798 | 1.232 |
7 | 272 | 1.265 | 15790321 | 1.265 |
8 | 215 | 1.250 | 19976592 | 1.250 |
9 | 172 | 1.255 | 24970740 | 1.255 |
10 | 137 | 1.245 | 31350126 | 1.245 |
11 | 110 | 1.264 | 39045157 | 1.264 |
12 | 87 | 1.243 | 49367440 | 1.243 |
13 | 70 | 1.250 | 61356676 | 1.250 |
14 | 56 | 1.244 | 76695844 | 1.244 |
15 | 45 | 1.250 | 95443717 | 1.250 |
16 | 36 | 1.241 | 119304647 | 1.241 |
17 | 29 | 1.261 | 148102320 | 1.261 |
18 | 23 | 1.278 | 186737708 | 1.278 |
19 | 18 | 1.200 | 238609294 | 1.200 |
20 | 15 | hoge | 286331153 | hoge |
nice値のその他細かい話
SCHED_IDLEは実装上はタイムシェアであるものの、割り当て時間を極端に短くして、SCHED_BATCH, SCHED_IDLEに負け続けるという実装のように読める。
autogroup(/proc/sys/kernel/sched_autogroup_enabled)やcgroupの機能により時間のスライスがタスクごとではなくてグループごとに行われるため、たくさんのタスクがわさわさ動くと実際の動きは予想しづらくなる。
nice値のことをドキュメントやsyscallの関数名で「priority」と表現していることがあるが、紛らわしいので、私はnice値に統一して呼ぶようにしている。
古い記事によっては「nice値がpriority値のベースとなりそこから動的に-5から+5される」という趣旨の記述が見られるが、コードを追う限り裏付けできなかった。Linux-2.6.23(9th October, 2007)からCFS(Completely Fair Scheduler)に置き換わったが、変わる前のO(1)スケジューラのころの記述なのではないかと考えている。nice値はあくまでPOSIX仕様でどう動くかは実装次第であり、またLinuxでも古くからあるため適当に書かれた記事が多い。英語記事ではあるがLinux Scheduler – CFS and Nice | oakbytesが比較的まとまっているように見える。
ioprio値との関係
schedのpriority値とioprio値は直接の関係はない。IOPRIO_CLASS_NONEだとsched設定がioprioに派生する。このパターンについては先のCFQとクラスの記事にまとめている。
schedのpolicyとclass
タスクに設定されたschedのpolicyは、コード上はsched classに対応づけられる。kernel/kernel/sched/sched.hより、
1306 extern const struct sched_class stop_sched_class;
1307 extern const struct sched_class dl_sched_class;
1308 extern const struct sched_class rt_sched_class;
1309 extern const struct sched_class fair_sched_class;
1310 extern const struct sched_class idle_sched_class;
stop_sched_class
stop_sched_classは、migration/%uのkernelスレッドにのみ使われる(sched_set_stop_task()あたり)。policyで何を指定しても使うことはできない。ユーザランドから観測するとSCHED_FIFOのprilrity値99のように見えるが、classが違い、これが最優先に動く。このスレッドはcpu migrationをしていて、タスクをこれまでとは異なるCPUで動かす時の処理を行う。
dl_sched_class
dl_sched_classは、SCHED_DEADLINEを指定したときに使われる。stop_sched_classの次の優先度で動く。
rt_sched_class
rt_sched_classは、SCHED_FIFO, SCHED_RRを指定したときに使われる。dl_sched_classの次の優先度で動く。rt_sched_class同士の場合は設定したpriority値の順で動く。
fair_sched_class
fair_sched_classは、SCHED_OTHER, SCHED_BATCH, SCHED_IDLEを指定したときに使われる。SCHED_IDLEだけは特殊で、先の通り常に不遇を受けて、fair_sched_classの中で最低の優先度で動く。それ以外はnice値の箇所で説明したとおり。
idle_sched_class
idle_sched_classは、swapper/%d(idle_task)の時にのみ選ばれる。これは、何もすることがないときに割り当てられるタスク。このあたりに詳しい 〆(.. )カリカリッ!! Linuxのidleスレッドめも - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ
なお、linux/kernel/sched/idle_task.cの先頭に注意書きがあるとおり、SCHED_IDLEのidleとidle-taskのidleは別物である。
3 /*
4 * idle-task scheduling class.
5 *
6 * (NOTE: these are not related to SCHED_IDLE tasks which are
7 * handled in sched/fair.c)
8 */
priority一覧表
priority値はツールや使用する場面によって表現が大きく変わってしまう。基本はkernel内のstruct task_structのメンバprio, static_prio, normal_prio(それぞれの意味要確認)に入る値を正と考えるべきだが、確認時は各種ツールを解することになるので、より注意が必要なところ。パクリもとこの記事を書こうと思うきっかけになったプロセスの優先度@CetnOS 5.5 | Mazn.netに感謝したい。
kernel prio |
chrt | top PRI |
ps rtprio |
ps pri |
ps priority |
捕捉 |
---|---|---|---|---|---|---|
-1 | 0 | rt | 0 | 140 | -101 | SCHED_DEADLINEと一部のタスク |
0 | 99 | rt | 99 | 139 | -100 | SCHED_FIFO,SCHED_RRの最高 |
1 | 98 | -99 | 98 | 138 | -99 | ↑ |
2 | 97 | -98 | 97 | 137 | -98 | ↑ |
... | ... | ... | ... | ... | ... | リアルタイムスケジューリング |
97 | 2 | -3 | 2 | 42 | -3 | ↓ |
98 | 1 | -2 | 1 | 41 | -2 | SCHED_FIFO,SCHED_RRの最低 |
100 | 0 | 0 | - | 39 | 0 | SCHED_OTHER,SCHED_BATCHの最高 |
101 | 0 | 1 | - | 38 | 1 | ↑ |
102 | 0 | 2 | - | 37 | 2 | ↑ |
... | ... | ... | ... | ... | ... | タイムシェアスケジューリング |
119 | 0 | 19 | - | 20 | 19 | ↑ |
120 | 0 | 20 | - | 19 | 20 | SCHED_OTHERデフォルト値 |
121 | 0 | 21 | - | 18 | 21 | ↓ |
... | ... | ... | ... | ... | ... | タイムシェアスケジューリング |
138 | 0 | 38 | - | 1 | 38 | ↓ |
139 | 0 | 39 | - | 0 | 39 | SCHED_OTHER,SCHED_BATCHの最低 |
なお、priorityとsched policyによりどのようにスケジューリングされるか(CFS)を追った、ムチャしやがった意欲的な記事がこちら。Linux スケジューラーのコア実装とシステムコール - Qiita
sched programming interface
schedに絡むシステムコールはたくさんあるが、歴史的経緯からか、interfaceの一貫性が怪しい。下記に早見表をまとめる。実際に使うときはman等参照されたい。
sched_getattr(2), sched_setattr(2)はglibcヘルパー関数がないので自分でsyscall(2)しないといけない。
...テーブルの縦の結合ってどう書くの?...
priority値の定義
値の定義
kernel/include/linux/sched/prio.hにだいたいが定義されている。・・・というかもう全部引用する。
4 #define MAX_NICE 19
5 #define MIN_NICE -20
6 #define NICE_WIDTH (MAX_NICE - MIN_NICE + 1)
7
8 /*
9 * Priority of a process goes from 0..MAX_PRIO-1, valid RT
10 * priority is 0..MAX_RT_PRIO-1, and SCHED_NORMAL/SCHED_BATCH
11 * tasks are in the range MAX_RT_PRIO..MAX_PRIO-1. Priority
12 * values are inverted: lower p->prio value means higher priority.
13 *
14 * The MAX_USER_RT_PRIO value allows the actual maximum
15 * RT priority to be separate from the value exported to
16 * user-space. This allows kernel threads to set their
17 * priority to a value higher than any user task. Note:
18 * MAX_RT_PRIO must not be smaller than MAX_USER_RT_PRIO.
19 */
20
21 #define MAX_USER_RT_PRIO 100
22 #define MAX_RT_PRIO MAX_USER_RT_PRIO
23
24 #define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH)
25 #define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2)
26
27 /*
28 * Convert user-nice values [ -20 ... 0 ... 19 ]
29 * to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ],
30 * and back.
31 */
32 #define NICE_TO_PRIO(nice) ((nice) + DEFAULT_PRIO)
33 #define PRIO_TO_NICE(prio) ((prio) - DEFAULT_PRIO)
34
35 /*
36 * 'User priority' is the nice value converted to something we
37 * can work with better when scaling various scheduler parameters,
38 * it's a [ 0 ... 39 ] range.
39 */
40 #define USER_PRIO(p) ((p)-MAX_RT_PRIO)
41 #define TASK_USER_PRIO(p) USER_PRIO((p)->static_prio)
42 #define MAX_USER_PRIO (USER_PRIO(MAX_PRIO))
43
44 /*
45 * Convert nice value [19,-20] to rlimit style value [1,40].
46 */
47 static inline long nice_to_rlimit(long nice)
48 {
49 return (MAX_NICE - nice + 1);
50 }
51
52 /*
53 * Convert rlimit style value [1,40] to nice value [-20, 19].
54 */
55 static inline long rlimit_to_nice(long prio)
56 {
57 return (MAX_NICE - prio + 1);
58 }
よって、NICE_WIDTHは40、MAX_PRIOは140、DEFAULT_PRIOは120 になる。
最大値の定義
linux/kernel/sched/core.c:sched_get_priority_max()が最大値を与える。
5085 /**
5086 * sys_sched_get_priority_max - return maximum RT priority.
5087 * @policy: scheduling class.
5088 *
5089 * Return: On success, this syscall returns the maximum
5090 * rt_priority that can be used by a given scheduling class.
5091 * On failure, a negative error code is returned.
5092 */
5093 SYSCALL_DEFINE1(sched_get_priority_max, int, policy)
5094 {
5095 int ret = -EINVAL;
5096
5097 switch (policy) {
5098 case SCHED_FIFO:
5099 case SCHED_RR:
5100 ret = MAX_USER_RT_PRIO-1;
5101 break;
5102 case SCHED_DEADLINE:
5103 case SCHED_NORMAL:
5104 case SCHED_BATCH:
5105 case SCHED_IDLE:
5106 ret = 0;
5107 break;
5108 }
5109 return ret;
5110 }
MAX_USER_RT_PRIOは先に見たとおり100なので、最大値はrt_priorityでいうところの99となる。
最小値の定義
linux/kernel/sched/core.c:sched_get_priority_min()が最小値を与える。
5112 /**
5113 * sys_sched_get_priority_min - return minimum RT priority.
5114 * @policy: scheduling class.
5115 *
5116 * Return: On success, this syscall returns the minimum
5117 * rt_priority that can be used by a given scheduling class.
5118 * On failure, a negative error code is returned.
5119 */
5120 SYSCALL_DEFINE1(sched_get_priority_min, int, policy)
5121 {
5122 int ret = -EINVAL;
5123
5124 switch (policy) {
5125 case SCHED_FIFO:
5126 case SCHED_RR:
5127 ret = 1;
5128 break;
5129 case SCHED_DEADLINE:
5130 case SCHED_NORMAL:
5131 case SCHED_BATCH:
5132 case SCHED_IDLE:
5133 ret = 0;
5134 }
5135 return ret;
5136 }
より、最小値はrt_priorityでいうところの1となる。
chrt
util-linux/schedutils/chrt.c:245 show_sched_pid_info()より、
if (sched_getparam(pid, &sp) != 0)
err(EXIT_FAILURE, _("failed to get pid %d's attributes"), pid);
else
prio = sp.sched_priority;
なのでsched_getparam(2)を使っている。これはlinux/kernel/core.c:4584のsys_sched_getparam()より、
4584 if (task_has_rt_policy(p))
4585 lp.sched_priority = p->rt_priority;
で、task_has_rt_policy(p)(==SCHED_FIFO,SCHED_RR)の時はrt_priorityが、そうでない場合は0が返る。
top(PRI)
procps-ng/top/top.c:5430のtask_show()のEU_PRIより、
case EU_PRI:
if (-99 > p->priority || 999 < p->priority) {
cp = make_str("rt", W, Jn, AUTOX_NO);
} else
cp = make_num(p->priority, W, Jn, AUTOX_NO, 0);
break;
p->priorityはlibprocpsで取得される値で、/proc/[PID]/statの「(18) priority %ld」のこと。-99より小さいまたは999より大きい場合は"rt"を表示し、それ以外はそのまま表示している。
ps(rtprio)
procps-ng/ps/output.c:702のpr_rtprio()より、
// Based on "type", FreeBSD would do:
// REALTIME "real:%u", prio
// NORMAL "normal"
// IDLE "idle:%u", prio
// default "%u:%u", type, prio
// We just print the priority, and have other keywords for type.
static int pr_rtprio(char *restrict const outbuf, const proc_t *restrict const pp){
if(pp->sched==0 || pp->sched==(unsigned long)-1) return snprintf(outbuf, COLWID, "-");
return snprintf(outbuf, COLWID, "%ld", pp->rtprio);
}
pp->rtprioはlibprocpsで取得される値で、/proc/[PID]/statの「(40) rt_priority %u」のこと。同様に、pp->schedは「(41) policy %u」のこと。
pp->schedが0とはSCHED_NORMAL(==SCHED_OTHER)のことなので、リアルタイム系policyじゃない時は"-"を、どうでないときはrt_priorityをそのまま表示している
ps(pri)
procps-ng/ps/output.c:655のpr_pri()より、
// not legal as UNIX "PRI"
// "pri" (was 20..60, now 0..139)
static int pr_pri(char *restrict const outbuf, const proc_t *restrict const pp){ /* 20..60 */
return snprintf(outbuf, COLWID, "%ld", 39 - pp->priority);
}
p->priorityはlibprocpsで取得される値で、/proc/[PID]/statの「(18) priority %ld」のこと。39オフセットから反転させた値にしている。
ps(priority)
procps-ng/ps/output.c:624のpr_pri()より、
// legal as UNIX "PRI"
// "priority" (was -20..20, now -100..39)
static int pr_priority(char *restrict const outbuf, const proc_t *restrict const pp){ /* -20..20 */
return snprintf(outbuf, COLWID, "%ld", pp->priority);
}
p->priorityはlibprocpsで取得される値で、/proc/[PID]/statの「(18) priority %ld」のこと。ここはそのまま表示している。
/proc/[PID]/stat
linux/fs/proc/array.c:389 do_task_stat()より、
481 /* scale priority and nice values from timeslices to -20..20 */
482 /* to make it look like a "normal" Unix priority/nice value */
483 priority = task_prio(task);
(----------snip----------)
504 seq_put_decimal_ll(m, " ", priority);
(----------snip----------)
542 seq_put_decimal_ull(m, " ", task->rt_priority);
task_prio()はlinux/kernel/sched/core.c:3838で、
3838 /**
3839 * task_prio - return the priority value of a given task.
3840 * @p: the task in question.
3841 *
3842 * Return: The priority value as seen by users in /proc.
3843 * RT tasks are offset by -200. Normal tasks are centered
3844 * around 0, value goes from -16 to +15.
3845 */
3846 int task_prio(const struct task_struct *p)
3847 {
3848 return p->prio - MAX_RT_PRIO;
3849 }
となっている。なのでまとめると、
- (18) priority %ld は、p->prioからMAX_RT_PRIO(==100)を引いたもの
- (40) rt_priority %u は、task->rt_priorityそのまま
p->prioはおおむね、linux/kernel/sched/core.c:871のnormal_prio()ということでよい。
871 /*
872 * __normal_prio - return the priority that is based on the static prio
873 */
874 static inline int __normal_prio(struct task_struct *p)
875 {
876 return p->static_prio;
877 }
878
879 /*
880 * Calculate the expected normal priority: i.e. priority
881 * without taking RT-inheritance into account. Might be
882 * boosted by interactivity modifiers. Changes upon fork,
883 * setprio syscalls, and whenever the interactivity
884 * estimator recalculates.
885 */
886 static inline int normal_prio(struct task_struct *p)
887 {
888 int prio;
889
890 if (task_has_dl_policy(p))
891 prio = MAX_DL_PRIO-1;
892 else if (task_has_rt_policy(p))
893 prio = MAX_RT_PRIO-1 - p->rt_priority;
894 else
895 prio = __normal_prio(p);
896 return prio;
897 }
- task_has_dl_policy()(==SCHED_DEADLINE)の時は-1(MAX_DL_PRIOは0(include/linux/sched/deadline.h))
- task_has_rt_policy()(==SCHED_FIFO,SCHED_RR)の時は0から98(MAX_RT_PRIOは100)
- それ以外はp->static_prio
Real-Time Linux patch
LinuxをよりリアルタイムOSっぽく扱えるようにするためのパッチ群がReal-Time Linux Wikiにて公開されている。きちんと中身を確認はできていないが、おおむね下記の点に変更が入っている。
- 割り込みコンテキストを割り込みスレッド(irq/xxx)に変える。SCHED_FIFOのpriority==-50のスレッド
- softirqをすべてksoftirqdで処理する
- spin_lock_irqsave()などで割り込み禁止にしなくなる
などのことをして、レイテンシが小さくなるようにしている。
ポエム: Linuxにおけるリアルタイムシステムとは
Linuxで完全なリアルタイム性を保障しようと思うとものすごく難しい。保障しようと思ったとたん抜けがあるといけないわけで、行儀の悪いユーザプログラムがSCHED_FIFOになっているのはもってのほか、行儀の悪いドライバ1つ混ざるだけで保障なんてできなくなってしまう。SMPやVCPUで以前に比べればネックになりにくくはなったものの、L1/L2キャッシュ状態・分岐予測・CPUの動的周波数変更・HyperVisor介入、などのOSに見えにくいところの予測不能点も増えた。まとまりないもののずらずらと書き下してみる。
sched priority
SCHED_FIFOのpriorityは使いこなしの基本。CFS(Completely Fair Scheduler)で多くが改善されたとはいえ、SCHED_OTHER(タイムシェア)のままではリアルタイムは厳しい。
なおSCHED_FIFOにするだけではCPUを100%使うことができない問題があるので、sched_rt_runtime_usの確認も忘れないこと。
mlock
せっかくSCHED_FIFOにしても、初回アクセスでページフォルト起こしたりスワップアウトしたりしていては話にならない。仮想アドレス割り当てだけでなく物理メモリもきちんと割り当てるために、mlock(2), mlockall(2)を使いリアルタイム処理中にアクセスするアドレスを固定しておく必要がある。なおL1,L2キャッシュミスヒットはそれでも起こるが、ページフォルト起こしてディスクから読むのに比べればオーダが異なるので、まだ許容範囲(?)となる。
CPU binding
context switchや割り込みやL1キャッシュ乱れの影響を減らすため、リアルタイム性がほしいタスクとそうでないタスクとで割り当てるCPUを分ける方法がある。HyperVisorでさらにVCPUを分けておく方法もあるが、いずれにしても複数の(論理)CPUを使える前提でないと使えない方法である。幸いにもCPU bindingする方法はそこそこQiitaに書かれているので詳細はそちらを。例えばプロセス・スレッドを特定のCPUコアでのみ動作させる方法。システムコールはsched_setaffinity(2)、コマンドラインからはtaskset(1)で。
Interrupt
Interrupt(割り込み)はその名のごとく何かの処理中に別の処理が割り込むので、割り込みハンドラとそこに登録する関数は注意深く精査する必要がある。割り込み頻度や割り込み優先度・ハンドラで処理する絶対量は常に意識しなければならない。
最近は、割り込みとはいえ直接ハンドラを呼ぶのではなく、割り込みから特定タスクを起こすことだけ行い、実際の処理を特定のタスクに任せるという割り込みスレッドを使った実装が流行っている。割り込みスレッドへcontext switchするコストがかかるものの、割り込みスレッドよりもさらに優先度したいタスクがある場合に、priorityベースでタスクの順を考えるようにすることができる。
割り込み/プリエンプト禁止
割り込み禁止(spin_lock_irqsave()系)やプリエンプト禁止(preempt_disable()系)を長い期間行うとレイテンシに響く。最低限のリソースへのアクセスの期間にとどめるのがよい。
L1/L2キャッシュ状態・分岐予測・CPUの動的周波数変更・HyperVisor
最近はソフト制御の外のハードのレベルで行われるので、ソフト(OS)レベルでは影響を測ることが難しくなっている。結果、昔ながらの命令数を数えて処理時間を見積もるのが現実的でなくなってしまっている。L2キャッシュのway数をvcpuで割り振ったり、vcpuでなくて物理CPUレベルでHyperVisorがVMに割り当てたり、といったパーティショニングをするくらいしか今のところはないのかなぁ。
あとがき
priorityの確認方法を書くだけのつもりだったのに、なにかリアルタイムシステム入門っぽくなってしまった。HPCも含め世の中は一般的にレイテンシよりもスループットを重視するため、意外とリアルタイム処理の視点で書かれている記事は多くない。nice値とpriority値を混同したようなレベルのものも未だに多い。
逆に、Linuxなんかではリアルタイム保障はムリという主張も多い(まぁ間違ってはいないけど)。「保障」のレベルにもよるんだろうけど、0,1で決めるんではなくて「おおむね保障する」レベルで許されるのなら、Linuxを使ったリアルタイム指向の設計という選択肢もある。
ARM CPUのマルチコアも当たり前になってきたので、これからこういう話がもっと必要になってくる・・・のかな?
参考サイト
Documentation
- sched(7)
- kernel/Documentation/scheduler/sched-bwc.txt
- kernel/Documentation/scheduler/sched-deadline.txt
- kernel/Documentation/scheduler/sched-design-CFS.txt
- kernel/Documentation/scheduler/sched-rt-group.txt
sched関係
- Linux スケジューラーのコア実装とシステムコール - Qiita
- スケジューラについての調査 - オペレーティングシステム授業発表
- Overview of the Linux Scheduler Framework
- Introduction to Linux Scheduler
- ...Androidもそうだけど、ここ10年くらいはkernel分野のソフトは韓国・台湾強い...
priority関係
- プロセスの優先度@CetnOS 5.5 | Mazn.net
- 〆(.. )カリカリッ!! Linuxのidleスレッドめも - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ
- CFSのnice値について : 革命の日々 その2
- 帰ってきたCon Kolivas、大論争を呼ぶの巻(3/3) - @IT