寻找Linux上等待CPU的进程
前几天碰到一个(面试)问题:
在Linux上,你如何找到哪些进程处于等待CPU的状态?
第一个想到的工具是PS。但看了一下PS的手册,它的状态栏中’R’可以表示正在运行,或者已经准备好了但正在等待运行。
然后尝试了grep State /proc/$pid/status,还是同样的问题。继续查找相关资料,发现Linux的进程管理中,进程的状态是RUNNING(正在运行)还是 Ready to run (等待运行)是不加区分的。跟PS中的状态是一样的。但对于排错来讲,这两种状态差别就大了。或许Linux的调度机制可以确保处于Ready to run状态的进程一定可以分配到CPU?
再换个角度考虑,进程的状态是一个动态的东西,应该是在不同的状态之间不停的切换的。确实,如果一个进程处于Ready to run的状态,那么它确实应该很快就能获得CPU资源,变成真正意义上的RUNNING的状态。至少理论上是如此,如果一个进程处于Ready to run的状态却又很长时间都得不到CPU,那么可以认为这个系统出了问题,或者是负载严重过载。
因此,更准确的说,一个进程如果处于等待的状态,那么它应该不是等待CPU,而是应该等待其它东西(I/O)。所以说,这个”等待CPU“的说法是不太合适的,或者说这是及其少见的。更常见的是处于等待状态的进程,也即PS中显示为D (uninterruptible sleep (usually IO))或S (interruptible sleep (waiting for an event to complete))状态的进程。
也就是这个问题有一定的误导性。估计也不是问题设计者的本意。
那么,我们究竟能否找到在某个特定时间点上处于Ready to run状态的进程呢?
2019.11.01更新:
过了一年多再重新看这个问题,我想到的是:
1. 如果有进程处于可运行状态却无法得到CPU时间,那说明系统处于忙碌状态,很可能连命令输入都不会响应
2. 如果系统能分出一些CPU时间,而某个进程却还是无法进入运行状态,那要么是它并非在等待CPU,要么是自己的运行优先级太低,有CPU时间也自动分给了高优先级的进程。
3. 查了一下linux的相关源码,在和进程状态相关的task_struct结构体中,进程的状态 TASK_RUNNING 就表示进程正在运行中或可以运行
,也就是说,在源码级别这两个状态就是通用的,不加以区分。因此即使追踪内核中的进程状态,也无法区分这两周情况。
不管怎么说,我们可以把这个问题转化为:如何查看进程的当前状态?
2019/11/04更新
最直接的方式:用PS命令:ps -eo state,user,pid,comm –sort state
S USER PID COMMAND
R root 113166 ps
S root 1 systemd
S root 2 kthreadd
S root 4 kworker/0:0H
S root 6 ksoftirqd/0
S root 7 migration/0
S root 8 rcu_bh
S root 9 rcu_sched
S root 10 lru-add-drain
......
或者是atop命令,对应的是‘S’栏,表示进程的状态。此命令输出太长,我就不贴在这里了。
再者,也可以查看/proc/${pid}/status
文件中State对应的状态。下面是一个for循环,查看/proc/
下所有pid的State信息:
[hostname]$ for i in /proc/[0-9]*; do egrep "Name|State" $i/status; doneName: systemd
State: S (sleeping)
Name: lru-add-drain
State: S (sleeping)
Name: migration/18
State: S (sleeping)
Name: kworker/9:2
State: S (sleeping)
Name: kworker/17:2
State: S (sleeping)
Name: kworker/21:2
State: S (sleeping)
Name: kworker/25:0
...
下面是task_struct中定义的一个进程所有可能的状态(4.9 kernel):
/*
* Task state bitmask. NOTE! These bits are also
* encoded in fs/proc/array.c: get_task_state().
*
* We have two separate sets of flags: task->state
* is about runnability, while task->exit_state are
* about the task exiting. Confusing, but this way
* modifying one set can't modify the other one by
* mistake.
*/
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_DEAD 16
#define EXIT_ZOMBIE 32
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_PARKED 512
#define TASK_NOLOAD 1024
#define TASK_NEW 2048
#define TASK_STATE_MAX 4096
#define TASK_STATE_TO_CHAR_STR "RSDTtXZxKWPNn"
其中几个比较重要的的状态(摘自jeancheng的博客):
状态 | 描述 |
---|---|
TASK_RUNNING | 表示进程要么正在执行,要么正要准备执行(已经就绪),正在等待cpu时间片的调度 |
TASK_INTERRUPTIBLE | 进程因为等待一些条件而被挂起(阻塞)而所处的状态。这些条件主要包括:硬中断、资源、一些信号……,一旦等待的条件成立,进程就会从该状态(阻塞)迅速转化成为就绪状态TASK_RUNNING |
TASK_UNINTERRUPTIBLE | 意义与TASK_INTERRUPTIBLE类似,除了不能通过接受一个信号来唤醒以外,对于处于TASK_UNINTERRUPIBLE状态的进程,哪怕我们传递一个信号或者有一个外部中断都不能唤醒他们。只有它所等待的资源可用的时候,他才会被唤醒。这个标志很少用,但是并不代表没有任何用处,其实他的作用非常大,特别是对于驱动刺探相关的硬件过程很重要,这个刺探过程不能被一些其他的东西给中断,否则就会让进城进入不可预测的状态 |
TASK_STOPPED | 进程被停止执行,当进程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信号之后就会进入该状态 |
TASK_TRACED | 表示进程被debugger等进程监视,进程执行被调试程序所停止,当一个进程被另外的进程所监视,每一个信号都会让进城进入该状态 |
and if you’re feeling like geeky, 可以使用systemtap直接追踪某一个进程的task_struct结构体中的状态:
[root@pdc-sc-perf18 ~]# stap -g -v pstate.stp -x 6704
Pass 1: parsed user script and 474 library scripts using 271960virt/69260res/3476shr/65932data kb, in 490usr/90sys/586real ms.
Pass 2: analyzed script: 1 probe, 12 functions, 4 embeds, 0 globals using 398328virt/196660res/4520shr/192300data kb, in 750usr/1590sys/2352real ms.
Pass 3: using cached /root/.systemtap/cache/75/stap_7569d6a0832621d7346d359b41ff72d6_4950.c
Pass 4: using cached /root/.systemtap/cache/75/stap_7569d6a0832621d7346d359b41ff72d6_4950.ko
Pass 5: starting run.
6704: p4d
task state: 1
Pass 5: run completed in 10usr/180sys/519real ms.
[root@pdc-sc-perf18 ~]# cat pstate.stp
%{
#ifdef CONFIG_PREEMPT_RT_FULL
#error "pfiles.stp not supported on CONFIG_PREEMPT_RT_FULL kernels"
#endif
%}
function find_task_state(task:long,pid) {
printf("task state: %d\n",task_state(task))
}
probe begin {
%( $# < 1
%? pid = target()
%: pid = $1
%)
task = pid2task(pid)
if (task == 0) error (sprintf("Process-id %d is invalid, please provide valid PID as $1 or -x PID", pid))
printf("%6d: %s\n", pid, pid2execname(pid))
find_task_state(task,pid)
exit()
}
Understanding Linux Process States Author: Yogesh Babar
https://access.redhat.com/sites/default/files/attachments/processstates_20120831.pdf