procfsのtask statは何を示すのか

2015/12/12linuximport

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
%dpidプロセスIDpid_nr_ns(pid, ns)
%stcommCOMM名(task_struct*)->comm
%cstate状態フラグのうち最も若いフラグの立っているところ([RSDTtXz]:左が若い)get_task_state()の1文字目
(ll)ppid親プロセスIDtask_tgid_nr_ns(task->real_parent, ns)
(ll)pgidプロセスグループIDtask_pgrp_nr_ns(task, ns)
(ll)sidグループリーダID(?)task_session_nr_ns(task, ns), task->group_leaderのpid
(ll)tty_nrttyに属していれば、tty drviceのidを入れる.なければ0((minor & ~0xFF)<<12) or (major << 8) or ((minor&0xFF)
(ll)tty_pgrpttyのプロセスグループ-
(ull)task->flagsプロセス状態を示すビットマップ(後述)-
(ull)min_fltminor faultカウントの総和(struct task_struct*)->min_flt
(ull)cmin_fltsignal.minor faultカウントの総和(struct signal_struct)->cmin_flt
(ull)maj_fltmajor faultカウントの総和(struct task_struct*)->maj_flt
(ull)cmaj_fltsignal.major faultカウントの総和(struct signal_struct)->cmaj_flt
(ull)utimeユーザランドに費やしたcpu timecputime_to_clock_t(utime)
(ull)stimeシステムに費やしたcpu timecputime_to_clock_t(stime)
(ll)cutime亡くなった子プロセスを含めたユーザランドに費やしたcpu timecputime_to_clock_t((struct signal_struct)->cutime)
(ll)cstime亡くなった子プロセスを含めたシステムに費やしたcpu timecputime_to_clock_t((struct signal_struct)->cstime)
(ll)prioritytask priority()(->prio - 100)
(ll)nicenice値(->static_prio - 120(DEFAULT_PRIO))
(ll)num_threadsスレッド数tsk->signal->nr_threads
(ull)0 (fixed.)--
(ull)start_timeboot 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)rsslimmax resident set sizesig->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)espstack pointer-(arch依存)
(ull)eipindex 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_code0 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コマンドでタスク状態をモニタして、不具合や性能問題の追跡例を書いてみようかと思っていました。それよりも、ユーザランドからプロセスの状態を見る方法を示したほうが応用範囲が広がるだろうと思われます。

一部未確認がありますが、年内には更新を終わらせたいと思います。。。