LoginSignup
0
1

More than 5 years have passed since last update.

ioniceの「none: prio 4」の意味を掘り下げる

Posted at

いきなり結論

  • 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.ckernel/block/ioprio.cより、IOPRIO_CLASS_NONEの時はpriorityは0しか設定できない。

c]ionice.c
        case IOPRIO_CLASS_NONE:
            if ((set & 1) && !tolerant)
                warnx(_("ignoring given class data for none class"));
            data = 0;
            break;
ioprio.c
 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()を見ればわかる。

ioprio.c
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より、

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()である。

blk-ioc.c
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になる。

  1. create_task_io_context
  2. generic_make_request_checks
  3. generic_make_request
  4. submit_bio
  5. mpage_bio_submit
  6. mpage_readpages
  7. blkdev_readpages
  8. __do_page_cache_readahead
  9. force_page_cache_readahead
  10. page_cache_sync_readahead
  11. generic_file_read_iter
  12. blkdev_read_iter
  13. new_sync_read
  14. vfs_read
  15. 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()より、

fork.c
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より、

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に変更する修正を出しませんか?

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1