procfsのtask statは何を示すのか
Advent calender2015
今年も書かせていただくことにしました。比較的まともに?記事を書くことのできるトリガーとして助かります。
Linux kernel vanilla v4.2
本記事では、著者の技量の都合により語彙が適当では無いことがあります。指摘いただけますと助かります。
procfsで見えるもの
稼働しているシステムで動いているプロセスを調べるために、psコマンドを使っていますよね。busybox版のソースコードを見ると判りやすいのですが、/proc配下のファイルを参照して情報を集めているのです。/procは、procfsがマウントされており、物理記憶装置上のファイルシステムではなく、Linux kernelとユーザランドとをつないでいるインタフェースでもあります。
タスク情報
ユーザランドでは、プロセス、スレッドと区別していますが、kernelではいずれも"task struct"で管理されます。スレッドは、libc実装ではプロセス空間を共有するtaskと考えるとよいでしょう。forkするときは注意が必要なのですが、これはまた別の機会があればそちらで。。。
本題のプロセス情報、以下のディレクトリ配下にあるファイルから取得ができます。
/proc/<id>/
このファイルを通して読み書きされるコードは、procfsのところでも触れたとおり、kernelのフレームワークを利用すると簡潔なコードになります。とりあえずソースコードを見てみましょう:
FILE: fs/proc/base.c
static const struct pid_entry tid_base_stuff[] = { ONE("stat", S_IRUGO, proc_tid_stat), ONE("statm", S_IRUGO, proc_pid_statm), REG("maps", S_IRUGO, proc_tid_maps_operations),
statにpsで表示するための情報がたくさん含まれているようです(busybox版psコマンドソースコードより):
int proc_tid_stat(struct seq_file *m, struct pid_namespace *ns, struct pid *pid, struct task_struct *task) { return do_task_stat(m, ns, pid, task, 0); }
FILE: fs/proc/array.c
static int do_task_stat(struct seq_file *m, struct pid_namespace *ns, struct pid *pid, struct task_struct *task, int whole)
ここをざーっと眺めていくと、なにを表示しているのかがわかりますね!(ブン投げ
/proc/PID/statの表示コード(変数はこの手前に代入されている)
seq_printf(m, "%d (%s) %c", pid_nr_ns(pid, ns), tcomm, state); seq_put_decimal_ll(m, ' ', ppid); seq_put_decimal_ll(m, ' ', pgid); seq_put_decimal_ll(m, ' ', sid); seq_put_decimal_ll(m, ' ', tty_nr); seq_put_decimal_ll(m, ' ', tty_pgrp); seq_put_decimal_ull(m, ' ', task->flags); seq_put_decimal_ull(m, ' ', min_flt); seq_put_decimal_ull(m, ' ', cmin_flt); seq_put_decimal_ull(m, ' ', maj_flt); seq_put_decimal_ull(m, ' ', cmaj_flt); seq_put_decimal_ull(m, ' ', cputime_to_clock_t(utime)); seq_put_decimal_ull(m, ' ', cputime_to_clock_t(stime)); seq_put_decimal_ll(m, ' ', cputime_to_clock_t(cutime)); seq_put_decimal_ll(m, ' ', cputime_to_clock_t(cstime)); seq_put_decimal_ll(m, ' ', priority); seq_put_decimal_ll(m, ' ', nice); seq_put_decimal_ll(m, ' ', num_threads); seq_put_decimal_ull(m, ' ', 0); seq_put_decimal_ull(m, ' ', start_time); seq_put_decimal_ull(m, ' ', vsize); seq_put_decimal_ull(m, ' ', mm ? get_mm_rss(mm) : 0); seq_put_decimal_ull(m, ' ', rsslim); seq_put_decimal_ull(m, ' ', mm ? (permitted ? mm->start_code : 1) : 0); seq_put_decimal_ull(m, ' ', mm ? (permitted ? mm->end_code : 1) : 0); seq_put_decimal_ull(m, ' ', (permitted && mm) ? mm->start_stack : 0); seq_put_decimal_ull(m, ' ', esp); seq_put_decimal_ull(m, ' ', eip); /* The signal information here is obsolete. * It must be decimal for Linux 2.0 compatibility. * Use /proc/#/status for real-time signals. */ seq_put_decimal_ull(m, ' ', task->pending.signal.sig[0] & 0x7fffffffUL); seq_put_decimal_ull(m, ' ', task->blocked.sig[0] & 0x7fffffffUL); seq_put_decimal_ull(m, ' ', sigign.sig[0] & 0x7fffffffUL); seq_put_decimal_ull(m, ' ', sigcatch.sig[0] & 0x7fffffffUL); seq_put_decimal_ull(m, ' ', wchan); seq_put_decimal_ull(m, ' ', 0); seq_put_decimal_ull(m, ' ', 0); seq_put_decimal_ll(m, ' ', task->exit_signal); seq_put_decimal_ll(m, ' ', task_cpu(task)); seq_put_decimal_ull(m, ' ', task->rt_priority); seq_put_decimal_ull(m, ' ', task->policy); seq_put_decimal_ull(m, ' ', delayacct_blkio_ticks(task)); seq_put_decimal_ull(m, ' ', cputime_to_clock_t(gtime)); seq_put_decimal_ll(m, ' ', cputime_to_clock_t(cgtime)); if (mm && permitted) { seq_put_decimal_ull(m, ' ', mm->start_data); seq_put_decimal_ull(m, ' ', mm->end_data); seq_put_decimal_ull(m, ' ', mm->start_brk); seq_put_decimal_ull(m, ' ', mm->arg_start); seq_put_decimal_ull(m, ' ', mm->arg_end); seq_put_decimal_ull(m, ' ', mm->env_start); seq_put_decimal_ull(m, ' ', mm->env_end); } else seq_printf(m, " 0 0 0 0 0 0 0"); if (permitted) seq_put_decimal_ll(m, ' ', task->exit_code); else seq_put_decimal_ll(m, ' ', 0); seq_putc(m, '\n'); if (mm) mmput(mm);
pid_nr_ns()では、namespaceで管理しているpidに変換する関数のようです。namespaceも、不勉強なため説明できるような理解はできていません。
ぼちぼち調べながら見ていきましょう... schedulerが参照するようなcpu timeとかあるので、ここだけ見ても意味は理解できない雰囲気ですね。
format | vars | description | In the kernel space |
---|---|---|---|
%d | pid | プロセスID | pid_nr_ns(pid, ns) |
%s | tcomm | COMM名 | (task_struct*)->comm |
%c | state | 状態フラグのうち最も若いフラグの立っているところ([RSDTtXz]:左が若い) | get_task_state()の1文字目 |
(ll) | ppid | 親プロセスID | task_tgid_nr_ns(task->real_parent, ns) |
(ll) | pgid | プロセスグループID | task_pgrp_nr_ns(task, ns) |
(ll) | sid | グループリーダID(?) | task_session_nr_ns(task, ns), task->group_leaderのpid |
(ll) | tty_nr | ttyに属していれば、tty drviceのidを入れる.なければ0 | ((minor & ~0xFF)<<12) or (major << 8) or ((minor&0xFF) |
(ll) | tty_pgrp | ttyのプロセスグループ | - |
(ull) | task->flags | プロセス状態を示すビットマップ(後述) | - |
(ull) | min_flt | minor faultカウントの総和 | (struct task_struct*)->min_flt |
(ull) | cmin_flt | signal.minor faultカウントの総和 | (struct signal_struct)->cmin_flt |
(ull) | maj_flt | major faultカウントの総和 | (struct task_struct*)->maj_flt |
(ull) | cmaj_flt | signal.major faultカウントの総和 | (struct signal_struct)->cmaj_flt |
(ull) | utime | ユーザランドに費やしたcpu time | cputime_to_clock_t(utime) |
(ull) | stime | システムに費やしたcpu time | cputime_to_clock_t(stime) |
(ll) | cutime | 亡くなった子プロセスを含めたユーザランドに費やしたcpu time | cputime_to_clock_t((struct signal_struct)->cutime) |
(ll) | cstime | 亡くなった子プロセスを含めたシステムに費やしたcpu time | cputime_to_clock_t((struct signal_struct)->cstime) |
(ll) | priority | task priority() | (->prio - 100) |
(ll) | nice | nice値 | (->static_prio - 120(DEFAULT_PRIO)) |
(ll) | num_threads | スレッド数 | tsk->signal->nr_threads |
(ull) | 0 (fixed.) | - | - |
(ull) | start_time | boot based time in ticks, copy_proces()された時 | nsec_to_clock_t(task->real_start_time) |
(ull) | vsize | マップされたサイズ[Bytes] | PAGE_SIZE * mm->total_vm |
(ull) | mm->rss_stat.count[ MM_FILEPAGES,MM_ANONPAGES]の和 | プロセスに割り当てられている物理ページ数 | (mm ? get_mm_rss(mm) : 0) |
(ull) | rsslim | max resident set size | sig->rlim[RLIMIT_RSS].rlim_cur |
(ull) | このプロセスのコード領域の先頭アドレス | mm ? (permitted ? mm->start_code : 1) : 0 | |
(ull) | このプロセスのコード領域の終了アドレス | mm ? (permitted ? mm->end_code : 1) : 0 | |
(ull) | スタックアドレスの先頭(スタックの底?) | (permitted && mm) ? mm->start_stack : 0 | |
(ull) | esp | stack pointer | -(arch依存) |
(ull) | eip | index pointer? | -(arch依存) |
(ull) | ペンディングされているシグナルのビットマップ | task->pending.signal.sig[0] & 0x7fffffffUL | |
(ull) | (確認中) | task->blocked.sig[0] & 0x7fffffffUL | |
(ull) | (確認中) | sigign.sig[0] & 0x7fffffffUL | |
(ull) | (確認中) | sigcatch.sig[0] & 0x7fffffffUL | |
(ull) | wchan | 待ち状態に入った関数名(名前なし設定だとアドレス) | get_wchan(task) |
(ull) | 0 (fixed) | - | - |
(ull) | 0 (fixed) | - | - |
(ll) | task->exit_signal | (確認中) | - |
(ll) | task_cpu(task) | (確認中) | - |
(ull) | task->rt_priority | (確認中) | - |
(ull) | task->policy | (確認中) | - |
(ull) | delayacct_blkio_ticks(task) | (確認中) | - |
(ull) | cputime_to_clock_t(gtime) | (確認中) | - |
(ll) | cputime_to_clock_t(cgtime) | (確認中) | - |
(ull) | mm->start_data | (確認中) | - |
(ull) | mm->end_data | (確認中) | - |
(ull) | mm->start_brk | (確認中) | - |
(ull) | mm->arg_start | (確認中) | - |
(ull) | mm->arg_end | (確認中) | - |
(ull) | mm->env_start | (確認中) | - |
(ull) | mm->env_end | (確認中) | - |
(ll) | task->exit_code | 0 if !permitted | (確認中) |
コメントも面白いですね。RT-signalは statusを見ろって書いてます。
/* The signal information here is obsolete. * It must be decimal for Linux 2.0 compatibility. * Use /proc/#/status for real-time signals. */
※per proces flags
/* * Per process flags */ #define PF_EXITING 0x00000004 /* getting shut down */ #define PF_EXITPIDONE 0x00000008 /* pi exit done on shut down */ #define PF_VCPU 0x00000010 /* I'm a virtual CPU */ #define PF_WQ_WORKER 0x00000020 /* I'm a workqueue worker */ #define PF_FORKNOEXEC 0x00000040 /* forked but didn't exec */ #define PF_MCE_PROCESS 0x00000080 /* process policy on mce errors */ #define PF_SUPERPRIV 0x00000100 /* used super-user privileges */ #define PF_DUMPCORE 0x00000200 /* dumped core */ #define PF_SIGNALED 0x00000400 /* killed by a signal */ #define PF_MEMALLOC 0x00000800 /* Allocating memory */ #define PF_NPROC_EXCEEDED 0x00001000 /* set_user noticed that RLIMIT_NPROC was exceeded */ #define PF_USED_MATH 0x00002000 /* if unset the fpu must be initialized before use */ #define PF_USED_ASYNC 0x00004000 /* used async_schedule*(), used by module init */ #define PF_NOFREEZE 0x00008000 /* this thread should not be frozen */ #define PF_FROZEN 0x00010000 /* frozen for system suspend */ #define PF_FSTRANS 0x00020000 /* inside a filesystem transaction */ #define PF_KSWAPD 0x00040000 /* I am kswapd */ #define PF_MEMALLOC_NOIO 0x00080000 /* Allocating memory without IO involved */ #define PF_LESS_THROTTLE 0x00100000 /* Throttle me less: I clean memory */ #define PF_KTHREAD 0x00200000 /* I am a kernel thread */ #define PF_RANDOMIZE 0x00400000 /* randomize virtual address space */ #define PF_SWAPWRITE 0x00800000 /* Allowed to write to swap */ #define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_allowed */ #define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */ #define PF_MUTEX_TESTER 0x20000000 /* Thread belongs to the rt mutex tester */ #define PF_FREEZER_SKIP 0x40000000 /* Freezer should not count it as freezable */ #define PF_SUSPEND_TASK 0x80000000 /* this thread called freeze_processes and should not be frozen */
cmin_fltとかの説明。統計情報ですね...
/* * The resource counters for the group leader are in its * own task_struct. Those for dead threads in the group * are in its signal_struct, as are those for the child * processes it has previously reaped. All these * accumulate in the parent's signal_struct c* fields. * * We don't bother to take a lock here to protect these * p->signal fields because the whole thread group is dead * and nobody can change them. * * psig->stats_lock also protects us from our sub-theads * which can reap other children at the same time. Until * we change k_getrusage()-like users to rely on this lock * we have to take ->siglock as well. * * We use thread_group_cputime_adjusted() to get times for * the thread group, which consolidates times for all threads * in the group including the group leader. */
prio : RT[0..99], user [100..140], 120がnice=0, user-nice = -20..+19 => 100..139
とりあえずここまで。。。本当はpsコマンドでタスク状態をモニタして、不具合や性能問題の追跡例を書いてみようかと思っていました。それよりも、ユーザランドからプロセスの状態を見る方法を示したほうが応用範囲が広がるだろうと思われます。
一部未確認がありますが、年内には更新を終わらせたいと思います。。。