いきなり結論
- Q. ioniceの結果のこの2つて違うの?「none: prio 4」「none: prio 0」
- A. 同じです。kernelマニア以外はそっとページを閉じてもらって問題ないです。
はじめに
Linuxで**ionice(1)**しまくると、noneなのにprioの値が2種類あることに気づく。
[rarul@tina ~]$ cd /proc
[rarul@tina proc]$ ionice -p *[0-9]* |sort |uniq
best-effort: prio 3
none: prio 0
none: prio 4
noneは先の記事の通りIOPRIO_CLASS_NONEであることを表す。IOPRIO_CLASS_NONEは何も設定していないときのデフォルトのクラス。ただ、util-linux/schedutils/ionice.cやkernel/block/ioprio.cより、IOPRIO_CLASS_NONEの時はpriorityは0しか設定できない。
case IOPRIO_CLASS_NONE:
if ((set & 1) && !tolerant)
warnx(_("ignoring given class data for none class"));
data = 0;
break;
61 SYSCALL_DEFINE3(ioprio_set, int, which, int, who, int, ioprio)
62 {
63 int class = IOPRIO_PRIO_CLASS(ioprio);
64 int data = IOPRIO_PRIO_DATA(ioprio);
(----------snip----------)
83 case IOPRIO_CLASS_NONE:
84 if (data)
85 return -EINVAL;
86 break;
じゃioniceが表示する「none: prio 4」っていったいなんなんだ?という疑問がわいてくる。これを読み解くという趣旨の雑記です。なお、Linux-4.10くらいとutil-linux-2.29.2くらいを見ています。
いつこうなるのか「none: prio 4」
いつ「none: prio 4」になるのかはものすごく単純で、kernel/ioprio.cのget_task_ioprio()を見ればわかる。
145 static int get_task_ioprio(struct task_struct *p)
146 {
147 int ret;
148
149 ret = security_task_getioprio(p);
150 if (ret)
151 goto out;
152 ret = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_NONE, IOPRIO_NORM);
153 task_lock(p);
154 if (p->io_context)
155 ret = p->io_context->ioprio;
156 task_unlock(p);
157 out:
158 return ret;
159 }
p->io_contextがNULLなら**IOPRIO_PRIO_VALUE(IOPRIO_CLASS_NONE, IOPRIO_NORM)**がioprio_get(2)の値として返ってくる。IOPRIO_NORMはkernel/include/linux/ioprio.hより、
43 /*
44 * Fallback BE priority
45 */
46 #define IOPRIO_NORM (4)
となる。つまり、io_contextがNULLの時になぜか、0ではなく、IOPRIO_CLASS_BEのデフォルト値であるIOPRIO_NORMを選んでいる。どう見てもただのバグです。本当にありがとうございました。
・・・いや、io_contextがNULLってどういうこと?そもそもいつインスタンスが入るの?
io_contextが初期化されるタイミング
CLONE_IOを使って共用する特殊ケースを除くと、io_contextを初期化する関数はkernel/block/blk-ioc.c:create_task_io_context()である。
260 task_lock(task);
261 if (!task->io_context &&
262 (task == current || !(task->flags & PF_EXITING)))
263 task->io_context = ioc;
264 else
265 kmem_cache_free(iocontext_cachep, ioc);
266
267 ret = task->io_context ? 0 : -EBUSY;
268
269 task_unlock(task);
create_task_io_context()を呼ぶ系は、大きく3つある。
1. requestを作るとき
ioを動かすためにrequestを作るときにcreate_io_context()からcreate_task_io_context()を呼ぶ。blk-core.cあたり。ただし遅延writeはkworkerやjbd2/sda1のcontextで行われるので、来るのは、readやO_SYNCつきwriteや明示的にsync(2)した場合などとなる。このときおおむね下記のようなbacktraceになる。
- create_task_io_context
- generic_make_request_checks
- generic_make_request
- submit_bio
- mpage_bio_submit
- mpage_readpages
- blkdev_readpages
- __do_page_cache_readahead
- force_page_cache_readahead
- page_cache_sync_readahead
- generic_file_read_iter
- blkdev_read_iter
- new_sync_read
- vfs_read
- SyS_read
2. ioprio_set()するとき
ioprio_set(2)すると、set_task_ioprio()->get_task_io_context()->create_task_io_context()と呼ばれる。このときioprio_setしたいioprio値に設定される。
3. clone()するとき
copy_io()->get_task_io_context()->create_task_io_context()と呼ばれる。kernel/kernel/fork.c:copy_io()より、
1251 static int copy_io(unsigned long clone_flags, struct task_struct *tsk)
1252 {
1253 #ifdef CONFIG_BLOCK
1254 struct io_context *ioc = current->io_context;
1255 struct io_context *new_ioc;
1256
1257 if (!ioc)
1258 return 0;
1259 /*
1260 * Share io context with parent, if CLONE_IO is set
1261 */
1262 if (clone_flags & CLONE_IO) {
1263 ioc_task_link(ioc);
1264 tsk->io_context = ioc;
1265 } else if (ioprio_valid(ioc->ioprio)) {
1266 new_ioc = get_task_io_context(tsk, GFP_KERNEL, NUMA_NO_NODE);
1267 if (unlikely(!new_ioc))
1268 return -ENOMEM;
1269
1270 new_ioc->ioprio = ioc->ioprio;
1271 put_io_context(new_ioc);
1272 }
1273 #endif
1274 return 0;
1275 }
ここで注意が必要で、**ioprio_valid(ioc->ioprio)**が0なら新しいtskはio_contextが初期化されずNULLのままとなる。**ioprio_valid()**はkernel/include/linux/ioprio.hより、
17 #define ioprio_valid(mask) (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE)
となる。つまり、clone()時に、親がio_context==NULLのままもしくはIOPRIO_CLASS_NONEなら、子はio_context==NULLとなる。(CLONE_IOの時は親と子がio_contextを共用する...けど親がio_context==NULLのままだと共用せず、親も子も初回作ろうとしたときにそれぞれ作るな、これもバグか。)
まとめ
- 「none: prio 4」はio_contextが初期化されていない状態を表す。
- 「none: prio 0」はio_contextが初期化されIOPRIO_CLASS_NONEに設定されている状態を表す。
- 「none: prio 0」な人がfork()やpthread_create()すると子が「none: prio 4」になる
- どちらもユーザランドから見た動きの差はない (io_contextがNULLなら初回request出す時にIOPRIO_CLASS_NONEで初期化されるため)
あとがき
先のI/OスケジューラのクラスとCFQの記事に絡み、ioprio_get(2)しまくるテストをしているときに気づいた問題から出た疑問をまとめた。なにか問題が起こってioprio値が不正規にでもなってしまったかと思い真剣に調べてしまったが、ioprioの管理の中だけに閉じた特に実害のないしょうもないバグが原因だった。
ちなみに、sched(7)の各種設定値は/proc/[pid]/statから取れるが、ioprioの値はここからは取れず、現時点ではioprio_get(2)のsyscallをしないといけない。/proc/[0-9]+を見ながらioprio_get(2)しまくるツールという形で久々にプログラムを書いた気がする。
kernelへのパッチの出し方よく知らないので、代わりに誰か、IOPRIO_NORMを0に変更する修正を出しませんか?