👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍
目录
- 一、进程状态
- 二、Linux操作系统下的进程状态
- 2.1 进程状态在kernel源代码的定义
- 2.2 运行状态 R
- 2.3 浅度睡眠状态 S
- 2.4 磁盘休眠状态 D
- 2.5 停止状态 T
- 2.6 追踪状态 t
- 2.7 死亡状态 X
- 2.8 僵尸状态(僵尸进程) Z
- 三、孤儿进程
- 四、代码
一、进程状态
进程状态通常是通过进程控制块PCB
对象来管理和维护的。它记录了进程当前所处的状态,比如就绪、运行、阻塞等等。当进程状态发生变化时(比如由就绪变为运行),操作系统会更新 PCB 中对应的状态字段。使得操作系统能够有效地管理进程,进行进程调度、资源分配等操作。
以下是六个常见的进程状态:
- 新建态:可以理解为操作系统正在为某个进程创建进程控制块
PCB
对象的过程。(PCB
:操作系统用来管理和跟踪进程信息的数据结构,可以理解为描述进程属性的集合) - 就绪态:进程在就绪队列里已经准备好运行,等待被调度到
CPU
上执行。将其调度到运行态。(调度:从就绪队列中选择一个进程,并将其分配给CPU
执行。调度器的目标是优化系统性能、资源利用率和响应时间,以满足用户需求或系统的指定策略。) - 运行态:进程正在
CPU
上执行指令。但进程的运行状态通常是暂时的,一旦一个进程的时间片段用完,或者发生某些事件(比如等待I/O
操作完成),它可能会被操作系统调度到阻塞状态,等待一定条件满足后重新进入就绪状态。(时间片:在多进程的操作系统中,由于CPU
是有限的。为了公平地分配CPU
时间给各个进程,操作系统采用了时间片轮转调度算法。当时间片用完后,操作系统会剥夺当前进程的CPU
控制权,并将其放回就绪队列,然后选择下一个就绪状态的进程给予CPU
时间片,如此循环执行。) - 阻塞态:进程由于某些原因(比如等待
I/O
操作完成或者等待某个事件发生)而暂时无法继续执行,进入阻塞状态。阻塞态的进程通常会放置在阻塞队列中,等待某些条件满足(比如等待I/O
操作完成)后重新进入就绪态。 - 终止态:进程已经执行完成,或者被操作系统终止。
- 挂起态:当一个进程长时间被阻塞时,如果不再占用
CPU
和其他系统资源时,进程会被挂起,操作系统通常会保留进程的PCB
在内存中,并可能通过交换将部份进程的代码和数据移动到磁盘上的交换空间中,以释放内存。等待相应的事件发生后再次被唤醒执行。
二、Linux操作系统下的进程状态
2.1 进程状态在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)", # 运行状态
"S (sleeping)", # 睡眠状态
"D (disk sleep)", # 磁盘休眠状态
"T (stopped)", # 停止状态
"t (tracing stop)", # 进程正在被追踪
"X (dead)", # 死亡状态
"Z (zombie)", # 僵死状态
};
2.2 运行状态 R
在
Linux
中,运行状态并不意味着进程一定在运行中,只要满足进程是在运行中或者在运行队列里就称此进程时运行状态(运行态 + 就绪态
)。
以下面代码为例,来查看当代码运行起来是否为运行状态
要查看Linux
的进程状态可以使用以下命令
ps ajx | head -1 && ps ajx | grep 'proc' | grep -v 'grep'
其中,+
代表的是该进程是前台运行。
- 当进程以前台方式运行时,它会将输出结果直接发送到终端,并且它的执行会阻塞终端的使用。用户可以通过在终端按下
ctrl + c
或者使用kill
命令可以向进程发送信号9
(SIGKILL
)终止前台运行的进程。kill -9 PID
- 当进程以后台方式运行时,它不会占用终端并且不会阻塞用户的输入输出。用户可以通过在命令末尾添加
&
符号来将进程置于后台运行。但如果想杀掉进程只能用kill
命令。
2.3 浅度睡眠状态 S
浅度睡眠状态本质上就是阻塞状态。
等待I/O
操作是一个经典的阻塞状态
查看进程状态
ps ajx | head -1 && ps ajx | grep 'proc' | grep -v 'grep'
除此之外,bash
命令行也是一个非常经典的浅度睡眠状态(阻塞状态),因为它需要等待用户输入指令与操作系统交互。
-
S
:表示进程处于睡眠状态 -
s
:表示进程是会话的领头进程。 -
Ss
:既处于睡眠状态,又是会话的领头进程(系统第一个用户级别进程)。
2.4 磁盘休眠状态 D
磁盘休眠状态也称深度睡眠状态,它其实也是一种阻塞状态。
比如一个进程需要向磁盘写入数据,由于向磁盘写入数据很慢,那么此时进程必定会进入长时间的等待!而内存的资源是有限的,那么极端情况下,操作系统就可能会将该进程杀死(Linux
操作系统会在内存不足时进行杀进程),那么就会导致数据丢失。因此我们只需让进程在等待磁盘写入完毕期间不被任何人(包括操作系统)杀掉即可,这就是深度睡眠状态(不响应任何请求)。
如果进程长时间处于休眠状态,特别是在等待同一种事件(比如磁盘IO)时,这可能是系统出现问题的迹象之一。这可能意味着系统资源被耗尽,导致无法满足进程的需求,从而导致系统性能下降甚至宕机。
终止休眠进程的一个方法就是切断电源,此时进程是结束了,但整个系统也结束了。
2.5 停止状态 T
顾名思义就是停止当前进程
我们可以通过kill
发送信号来停止进程或者继续运行某个进程。首先通过一下指令列出kill
命令下的所有信号
kill -l
以下面示例代码为例,当代码运行起来时会进入死循环状态
接下来使用以下命令停止进程并查看进程状态
kill -19 PID
我们发现进程确实暂停了,但现在我们又想要让进程运行起来
kill -18 PID
cout
的打印操作可能会被阻塞,因为它需要与显示器设备进行交互,并且显示器设备可能不一定处于可以立即接受写入数据的状态。
- 显示器设备缓冲区已满: 如果显示器设备的缓冲区已满,而
cout
尝试向其写入数据,那么cout
的打印操作可能会被阻塞,直到缓冲区中有足够的空间来接受新的数据。- 显示器设备正在执行其他操作: 如果显示器设备正在执行某些其他操作,例如刷新屏幕、处理其他输入,那么
cout
的打印操作可能会被阻塞,直到设备空闲下来可以接受新的数据。
2.6 追踪状态 t
追踪状态也是暂停状态的一种。
当我们用gdb
调试我们代码时,运行至断点时便处于此种状态
2.7 死亡状态 X
本质上就是终止态:进程已经执行完成,或者被操作系统终止。你不会在进程列表里看到这个状态。
2.8 僵尸状态(僵尸进程) Z
在
Linux
中,一个进程终止了不会立马进入死亡状态。而是会先进入僵尸状态。僵尸状态是指子进程已经终止执行,但其相关的进程控制块PCB
和资源仍然保留在系统中,直到其父进程获取子进程终止状态(获取方式:waitpid
),子进程才能释放资源,变为死亡状态;如果父进程没有及时处理,这个进程就会变成僵尸进程。
以下面代码为例,子进程提前终止,父进程继续执行cout
语句
void exit(int status)
当调用exit
函数时,当前进程会立即停止执行,并返回给其父进程一个终止状态status
,表示进程的退出状态。这个终止状态可以在父进程中通过调用waitpid
来获取。
接下来查看进程状态
ps ajx | head -1 && ps ajx | grep 'proc' | grep -v 'grep'
通过观察我们发现子进程处于僵尸状态,因为父进程一直没有获取子进程的退出状态,于是子进程要一直维持僵尸状态。而一直维持僵尸状态,那么相关的进程控制块PCB
和资源仍然保留在系统,那势必就会引发内存泄漏。
三、孤儿进程
在Linux
当中的进程关系大多数是父子关系,若子进程先退出并且父进程没有对子进程的退出信息进行读取,那么我们称该进程为僵尸进程。
父进程如果提前退出,那么子进程后退出,进入僵尸状态之后,那该如何处理呢?
例如,对于以下代码,fork
函数创建的子进程会一直打印信息,而父进程在打印3
次信息后会退出
运行结果如下:
观察代码运行结果,在父进程未退出时,子进程的PPID就是父进程的PID,而当父进程提前退出后,子进程的PPID
就变成了1
,即子进程被1
号进程(操作系统)领养了。
【总结】
- 若是父进程先退出,那么将来子进程进入僵尸状态时就没有父进程对其进行处理,此时该子进程就称之为孤儿进程。
- 如果父进程一直不获取子进程终止状态,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程会被
1
号进程(操作系统)领养,此后当孤儿进程进入僵尸状态时就由1
号进程自动回收。
四、代码
本篇博客相关代码:点击跳转