Linux进程理解【进程状态】
进程运行时,进程会被CPU
调度,但系统中存在多个进程,进程运行的先后顺序等等该怎么保证呢?OS
将进程分成了运行、睡眠、休眠、暂停、死亡等几种状态来对进程进行管理,下面我将带大家学习进程各种状态的应用场景及作用
1. 阻塞和挂起
==阻塞==
- 阻塞就是进程因为等待某种条件就绪,而导致的一种不推进的状态
- 简单来说就是,进程卡住了,原因是在等待某种资源
- 从数据结构角度来理解,阻塞就是不被调度,这一定是因为进程
task_struct
结构体需要在某种被OS
管理的资源下排队
举个例子,我们在使用到
scanf
函数时,当我们运行程序,光标会一直到控制台闪烁,实际上此时这个进程就是阻塞状态,它是在等待键盘的输入,也就是在等待键盘输入这一资源
为什么要阻塞呢?
- 进程要通过等待的方式,等具体的资源被别人用完之后,再被自己使用,阻塞就是进程等待某种资源就绪的过程
==挂起==
- 挂起状态就是当
CPU
资源不足时,OS
将数据和代码(进程)交换到磁盘中挂起,此时内存中只有PCB
举个例子,我们在下载软件时,下载这个进程就会被加载到内存中,此时如果突然断网了,就会阻塞(缺少网卡资源),由于这里进程暂时不会被运行,资源放在内存中就会浪费空间,OS就会将代码和数据(进程)暂时存放到磁盘中,等到有网了再从磁盘中拿取,这个过程就是挂起状态,可以说挂起是一种特殊的阻塞状态
2. 进程状态
Linux下的进程状态
==进程是什么状态一般是看进程在那个队列排队,也就是在那个硬件资源排队==
kernel源代码定义:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
几种状态的解释
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠)
D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态,在这个状态的进程通常会等待IO的结束
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
2.1 运行状态 R
按照以往的观点,程序一旦运行起来了,那么该程序就是处于运行状态,真的是这样吗?下面我们来看一个例子:
Makefile
文件
myproc:mytest.c
gcc -o myproc mytest.c
.PHONY:monitor
monitor:
ps ajx | head -1 && ps ajx | grep myproc | grep -v grep
.PHONY:clean
clean:
rm -f myproc
这里可以直接将监视指令写入Makefile
文件中,使用起来更加方便
下面我们运行监视起来看看现象
通过监视可以观察到此时的进程状态为S+
,也就是睡眠状态,+
号表示当前进程在前台运行
这是为什么呢,难道程序没有运行吗?
printf
库函数是向外设打印信息,当CPU
执行printf
库函数时,就需要访问外设,但是这里如果时频繁打印的时候,CPU
的运行速度是非常快的,外设和CPU
的速度相差太大,导致外设就不一定是就绪的,那么这里就会产生阻塞,这个进程就会被放在对应的外设的结构体队列中排队 。其实程序运行了,但是我们很难捕捉到,大多数时间,进程都在外设等待队列中排队,导致效果上是休眠状态
这里如果我们去掉printf
这一条需要访问外设的语句,进程就不会发生排队阻塞了,此时就可以观察到是运行状态
注意:R状态
表示进程在运行,但不一定是在CPU上运行,进程在运行队列中排队也算运行状态
2.2 睡眠状态 S
睡眠状态S
的本质就是进程阻塞,表示进程等待某种资源,在上面的测试中已经体现了
进程处于睡眠志状态我们可以通过ctrl + c
来终止进程
当进程是在后台运行时,状态表示没有 +
号,此时ctrl + c
是无法终止进程的,需要使用kill -9 PID
来终止进程
2.3 休眠状态 D
休眠状态D
是一种特殊的睡眠状态,又被称为不可中断睡眠状态,休眠状态下的进程是无法终止的,只能等到拿到所需资源,阻塞结束后,进程才会停止休眠状态
休眠状态比较少见,一般会出现于IO阻塞,这个状态的进程通常会等待IO的结束。系统不能出现D状态,出现D状态说明磁盘被占用空间很大,此时这个进程不能删除,一直等到数据被CPU处理完后这个进程才可中断,此时就表示系统离宕机不远了
2.4 暂停状态 T
可以通过指令使进程处于暂停状态T
kill -19 PID
暂停指定进程kill -18 PID
恢复进程暂停
使用kill -19 PID
暂停进程
使用kill -18 PID
恢复进程暂停
可以看到此时恢复暂停后,进程状态为S
,没有+
表示进入了后台睡眠状态,此时使用ctrl+c
是无法终止进程的,只能使用kill -9 PID
来终止
在 gdb
中调试代码时,打断点本质上就是 使 进程 在指定行暂停运行,此时 进程
处于 t状态
,也就是追踪暂停状态
2.5 死亡状态 X
当进程被终止后,就是处于死亡状态X
,无法在任列表中看到这个状态,死亡状态只是一个返回状态
2.6 僵尸状态 Z
僵尸状态Z
引入
- 在多进程执行任务时,如果一个进程退出了,马上就成为了
死亡X
状态,bash
作为父进程,是没有机会拿到退出结果的 - 为了解决这一问题,
Linux
下当子进程终止后,不会立即退出,而是会维持一个僵尸Z状态
,方便父进程来读取子进程的退出结果
bash
有僵尸进程的回收机制,直接在bash
环境下终止子进程无法观察到僵尸状态,这里我们可以使用上文中讲到的fork()
函数来创建父子进程关系,观察僵尸状态
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <assert.h>
int main()
{
pid_t ret = fork(); //获取返回值
assert(ret != -1); //创建失败的情况
if(ret == 0) //子进程
{
while(1)
{
printf("我是子进程, 我的PID是: %d,PPID是: %d\n", getpid(), getppid());
sleep(1);
}
}
else if(ret > 0) //父进程
{
while(1)
{
printf("我是父进程, 我的PID是: %d,PPID是: %d\n", getpid(), getppid());
sleep(1);
}
}
else
{
printf("进程创建失败!");
}
return 0;
}
运行监视起来观察现象
这时我们终止子进程,由于子进程没有被回收,此时就是处于僵尸状态
僵尸进程的危害:
- 僵尸状态一直不退出,PCB需要一直维护,浪费内存资源
- 僵尸进程如果不被回收,还会导致内存泄漏和标识符占用问题
至于如何避免僵尸进程造成的问题,我会在后续文章详细讲解
3. 孤儿进程
上面了解到了父子进程运行时,当子进程终止后,并不会直接结束,而是处于僵尸状态,方便父进程来读取子进程的结果。
那么如果反过来,父进程先结束,子进程会处于什么状态呢?
下面我们再来启动上面的程序实践一下
然后终止父进程
这里我们观察到,终止父进程后子进程的父进程变成了1号进程
此时的子进程就是孤儿进程
- 当父进程退出后,子进程会被
OS
自动领养 - 子进程的父进程就变成了
1号进程
,即OS
,这个被领养的子进程就是孤儿进程
为什么子进程会被OS领养呢?
- 如果不被领养,子进程后续再退出,就会变成一个无人回收的僵尸进程,会造成内存泄漏的问题
Linux进程理解—进程状态,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!
文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正