Linux 进程状态及优先级
进程状态
进程在操作系统中是以双链表的方式连接起来的,而操作系统管理进程本质上是对双链表的增删查改,因此,进程的状态也是由操作系统管理起来的,在操作系统中,
进程的状态转化图如下所示:

这个是在操作系统学科的教材里面对操作系统内进程状态的一般解释,下面我们再看看 Linux 中进程状态转化图:

哇,这个图看着好复杂,但我想说的是,你完全没必要去理解这玩意,把下面这些搞懂,这张图自然就可以看懂了
为了更加深入的理解 Linux 中进程状态,我们先看看 Linux 内核中进程状态结构体的定义:
static const char * const task_state_array[] = {
/* states in TASK_REPORT: */
"R (running)", /* 0x00 */
"S (sleeping)", /* 0x01 */
"D (disk sleep)", /* 0x02 */
"T (stopped)", /* 0x04 */
"t (tracing stop)", /* 0x08 */
"X (dead)", /* 0x10 */
"Z (zombie)", /* 0x20 */
"P (parked)", /* 0x40 */
/* states beyond TASK_REPORT: */
"I (idle)", /* 0x80 */
};
| 状态标志 | 十六进制值 | 说明 |
|---|---|---|
"R (running)" |
0x00 |
运行中 或 可运行:进程正在 CPU 执行,或在运行队列中等待调度。 |
"S (sleeping)" |
0x01 |
可中断睡眠:进程在等待事件(如 I/O 完成、信号),可被信号唤醒。 |
"D (disk sleep)" |
0x02 |
不可中断睡眠:进程在等待不可中断的事件(如磁盘 I/O),不会被信号唤醒。此状态通常由内核关键路径(如文件系统操作)触发,长时间阻塞可能引发系统问题。 |
"T (stopped)" |
0x04 |
暂停状态:进程被信号(如 SIGSTOP、SIGTSTP)暂停,需 SIGCONT 恢复。 |
"t (tracing stop)" |
0x08 |
跟踪暂停:进程被调试器(如 gdb)暂停,类似于 T 但专用于调试场景。 |
"X (dead)" |
0x10 |
死亡状态:进程已终止,等待父进程回收。此状态用户态通常不可见。 |
"Z (zombie)" |
0x20 |
僵尸状态:进程已退出,但父进程尚未通过 wait() 回收其资源。残留的 task_struct 会占用内核资源,过多僵尸进程可能导致 PID 耗尽。 |
"P (parked)" |
0x40 |
停放状态:内核线程主动进入休眠,等待被唤醒(如 kthreadd 管理的线程)。 |
前面说过,操作系统通过队列的方式来管理进程,而进程又有这么写状态存在,因此在单 CPU 架构当中,CPU 内部会针对各种状态实现一个队列用于管理这些进程
运行状态 R
好比说,当一个进程被唤醒后处于运行状态时,操作系统和就会把该进程放入运行队列当中,处在运行队列当中的进程的状态是 R 状态,但是这并不意味着这个
进程会一直处于运行状态直到运行完,因为一个进程运行的时间是不确定的,要是一直让他运行那你让运行队列中的其他进程怎么想,因此运行队列中的每一个 task_struct 都会
对应有一个东西叫做时间片,即每个进程只能运行固定的时间,而后会调用下一个进程,但因为 CPU 运行的速度非常快,所以你很难看出这些进程实际上是串行运行的,他们
被放到运行队列中以轮询的方式被均衡调度,所以你会有一种所有进程在并行运行的感觉
而在多 CPU 的结构里,每个 CPU 会有一个独立的运行队列,而不是使用全局队列,若实例一个全局运行队列就会导致出现 CPU 竞争访问运行队列资源的现象,这是很耗时的,且
安全性也会很差,因此不如个每个 CPU 一个运行队列,这样每个 CPU 各管各的进程,就不存在竞争和冲突了
运行状态如何查看呢?我们当然可以通过 ps 命令查看他:
我们先跑一个死循环的程序,然后通过 ps 查看他的状态:
cpp 程序如下:
#include <iostream>
#include <unistd.h>
int main()
{
while(1) ;
return 0;
}
查看状态如下:
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep run.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1961362 1962323 1962323 1961362 pts/4 1962323 R+ 1003 0:21 ./run.o
1962368 1962465 1962464 1962368 pts/5 1962464 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv run.o
可以看到,run.o 进程处于运行状态(R+),这说明该进程一直都在运行队列上面
休眠状态 S
我们对这个代码进行一个小小的改动:
#include <iostream>
#include <unistd.h>
int main()
{
while(1) sleep(1);
return 0;
}
我们再查看该进程的状态:
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep sleep.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1961362 1963227 1963227 1961362 pts/4 1963227 S+ 1003 0:00 ./sleep.o
1962368 1963288 1963287 1962368 pts/5 1963287 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv sleep.o
可以看到,该进程处于休眠状态,我们再改变一下代码形式:
#include <iostream>
#include <unistd.h>
int main()
{
int a;
while(1) scanf("%d", &a);
return 0;
}
可以看到,进程仍然处于休眠状态
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep sleep.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1961362 1963776 1963776 1961362 pts/4 1963776 S+ 1003 0:00 ./sleep.o
1962368 1963862 1963861 1962368 pts/5 1963861 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv sleep.o
第一个示例本身调用的就是 sleep 函数,就是让系统将自己休眠,在第二个示例中,scanf 操作属于阻塞操作,进程会等待标准输入输入数据,操作系统会将该进程挂载到
阻塞队列当中,在没有被回应前会一直处于休眠状态,而当监测到标准输入时则会被操作系统唤醒,将进程挂载到运行队列当中去
深度休眠状态 D
你肯定会有疑惑,休眠还不够吗,咋还需要深度休眠?
我来举个例子吧,假如说一个进程正在执行磁盘写入操作,而写入的过程中操作系统发现 CPU 快要满了,于是就需要杀掉一些睡眠状态的进程,于是就把这个进程给杀掉了,
等 CPU 空闲的时候操作系统再回头发现这个进程被杀掉了,这就意味着这一次的磁盘写入操作失效了,假如说这个写入操作是一次银行的交易,那就有可能造成3亿资金的泄露,
一切磁盘写入的操作都是不允许丢失的,因此,为了防止 CPU “乱杀无辜”,就有了深度睡眠的概念
对于一些因执行任务比较重要而不可被杀掉的进程在休眠的时候会给深度休眠的状态,深度休眠的进程是不可以被杀掉的,利用这一特性就可以解决这一问题
暂停状态 T
该状态一般是通过信号控制的,关于信号的具体使用等等将会在后面讲解,这里我们需要简单了解一下这个状态的意义所在
举一个很简单的例子,gdb 是一个用来代码调试的工具,他的断点功能是怎么实现的呢?没错,就是通过给执行程序发送暂停信号来控制的,通过给进程发送暂停信号
来完成断点就是暂停状态的一个很好的使用案例
死亡状态 X
死亡状态(EXIT_DEAD 或 TASK_DEAD) 是进程生命周期中的最终状态,表示进程已完全终止且所有资源(包括 task_struct 结构体)已被内核回收。由于其存在时间极短(通常仅几微秒),普通用户无法直接观察到该状态,这里只需要简单理解一下即可
僵尸状态 Z
僵尸状态指的是一个进程死亡后但还没有被父进程回收时所处的状态,若一个进程长期不被父进程回收就会导致进程长期处于僵尸状态,这种子进程死亡确一直占着内存
不放的现象我们称之为内存泄露,这种现象是不被允许发生的
接下来我们制作一个让子进程短时间处于僵尸状态从而让我们可以察觉到的现象:
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
int main()
{
int ret = fork();
// 子进程
if(ret == 0)
{
cout << "I am child process." << endl;
// 休眠5秒
sleep(5);
cout << "I will die." << endl;
exit(0);
}
// 父进程
cout << "I will sleep for 10s." << endl;
sleep(10);
// 回收子进程
cout << "I will recycle the child process." << endl;
waitpid(ret, nullptr, WNOHANG);
cout << "I will exit." << endl;
sleep(5);
return 0;
}
根据这个用例,刚开始我们会查看到两个进程都处于休眠状态(sleep 函数占大头),5s 后子进程退出死亡,但父进程此时还没有回收子进程,因此子进程会等待被回收,进入僵尸状态,
再过5s,父进程回收掉子进程,就只会看到父进程:
# 二者都在休眠状态
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep zombie.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1961362 1972423 1972423 1961362 pts/4 1972423 S+ 1003 0:00 ./zombie.o
1972423 1972424 1972423 1961362 pts/4 1972423 S+ 1003 0:00 ./zombie.o
1962368 1972476 1972475 1962368 pts/5 1972475 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv zombie.o
# 子进程等待父进程回收自己
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep zombie.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1961362 1972423 1972423 1961362 pts/4 1972423 S+ 1003 0:00 ./zombie.o
1972423 1972424 1972423 1961362 pts/4 1972423 Z+ 1003 0:00 [zombie.o] <defunct>
1962368 1972502 1972501 1962368 pts/5 1972501 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv zombie.o
# 子进程被回收
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep zombie.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1961362 1972423 1972423 1961362 pts/4 1972423 S+ 1003 0:00 ./zombie.o
1962368 1972556 1972555 1962368 pts/5 1972555 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv zombie.o
# 父进程退出
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep zombie.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1962368 1972647 1972646 1962368 pts/5 1972646 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv zombie.o
孤儿进程
进程如果提前退出,那么字进程后退出,进⼊Z之后,就会被1 号进程领养,没错,1 号进程就是操作系统本身,而操作系统会定期回收这些子进程,从而避免了
内存泄露等问题
我们将例子改一下,使得父进程先退出:
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
int main()
{
int ret = fork();
// 子进程
if(ret == 0)
{
cout << "I am child process." << endl;
cout << "I will sleep for 10s." << endl;
sleep(10);
cout << "I will die." << endl;
exit(0);
}
// 父进程
cout << "I will sleep for 5s." << endl;
sleep(5);
cout << "I will exit." << endl;
return 0;
}
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep zombie.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1961362 1974897 1974897 1961362 pts/4 1974897 S+ 1003 0:00 ./zombie.o
1974897 1974898 1974897 1961362 pts/4 1974897 S+ 1003 0:00 ./zombie.o
1962368 1974955 1974954 1962368 pts/5 1974954 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv zombie.o
# 父进程退出,子进程 ppid 变成 1,表示被操作系统领养
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep zombie.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 1974898 1974897 1961362 pts/4 1961362 S 1003 0:00 ./zombie.o
1962368 1975026 1975025 1962368 pts/5 1975025 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv zombie.o
# 操作系统定期调用 wait 函数回收子进程
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps axj | head -1 && ps axj | grep zombie.o
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1962368 1975103 1975102 1962368 pts/5 1975102 S+ 1003 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv zombie.o
进程优先级
- CPU资源分配的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的Linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
为了方便看进程的优先级,我们可以利用指令 ps -l 来查看:
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1003 1962368 1962367 0 80 0 - 2884 sigsus pts/5 00:00:01 zsh
0 R 1003 1976685 1962368 0 80 0 - 2772 - pts/5 00:00:00 ps
这里涉及到两个新的概念:
• PRI :代表这个进程可被执⾏的优先级,其值越⼩越早被执⾏
• NI :代表这个进程的nice值
优先级越高的进程被调度得就越频繁,高优先级进程就好比 vip 用户,享用的是 vip 专区服务,而低优先级的进程就是普通用户,必须严格遵循排队等待机制
进程的优先级等于 pri + nice,我们可以通过 top 命令来修改一个进程的 nice 值从而修改一个进程的优先级,需要注意的是
输入 nice 值后进程的优先级等于80 + nice 值,比如说,我们修改一个进程的nice值为10:
# 输入 top 命令,而后输入 r 进入 nice 值修改模式,输入当前进程号
top - 15:22:09 up 65 days, 35 min, 2 users, load average: 0.00, 0.03, 0.06
Tasks: 121 total, 1 running, 120 sleeping, 0 stopped, 0 zombie
%Cpu(s):100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 1839.0 total, 72.2 free, 1405.4 used, 536.7 buff/cache
MiB Swap: 4096.0 total, 3342.4 free, 753.6 used. 433.6 avail Mem
PID to renice [default pid = 1] 1980795
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 168960 9804 7308 S 0.0 0.5 0:58.86 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:01.48 kthreadd
3 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_gp
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_par_gp
# 修改优先级为 10
top - 15:22:09 up 65 days, 35 min, 2 users, load average: 0.00, 0.03, 0.06
Tasks: 121 total, 1 running, 120 sleeping, 0 stopped, 0 zombie
%Cpu(s):100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 1839.0 total, 72.2 free, 1405.4 used, 536.7 buff/cache
MiB Swap: 4096.0 total, 3342.4 free, 753.6 used. 433.6 avail Mem
Renice PID 1980795 to value 10
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 168960 9804 7308 S 0.0 0.5 0:58.86 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:01.48 kthreadd
3 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_gp
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00
再次查看,我们会发现该进程的优先级变成了90:
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1003 1980795 1962368 0 90 10 - 1412 wait_w pts/5 00:00:00 sleep.o
0 R 1003 1983196 1961362 0 80 0 - 2772 - pts/4 00:00:00 ps