Skip to content

寻找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结构体中的状态:

[[email protected] ~]# 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.
[[email protected] ~]# 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

Avatar

专业Linux/Unix/Windows系统管理员,开源技术爱好者。对操作系统底层技术,TCP/IP协议栈以及信息系统安全有强烈兴趣。电脑技术之外,则喜欢书法,古典诗词,数码摄影和背包行。

Sidebar