🌼🌼前言:在操作系统中,进程是最基本的资源管理单位,而操作系统通过精确管理这些进程的状态来确保系统能够高效运行。进程的状态不仅仅是操作系统设计的一部分,它对系统的性能、稳定性以及资源的分配起着至关重要的作用。本文将带你全面了解Linux操作系统中的常见进程状态,并结合实际场景探讨每种状态的危害与好处。我们还将分享一些个人思考,帮助大家更好地理解操作系统在管理这些状态时的决策过程。
进程
只有被OS管理好了,才能发挥它的全部功效,而系统中存在多个 进程
,OS无法做到面面俱到,因此为了更好的管理进程
,OS把 进程
分成了几种状态:阻塞、挂起、运行、休眠等等,至于 每种状态的应用场景是什么、有什么用?本文将会带着大家认识的各种 进程
状态 。
🖋️ 运行状态(R)
应用场景:
运行状态是进程最基本的状态之一,表示进程正被操作系统的CPU调度并正在执行。任何我们在日常操作中看到的、正在执行的程序,如浏览器、文本编辑器、音乐播放器等,其背后的进程都在运行状态。
当你在编辑一个文档时,你的文本编辑器进程就是处于运行状态;而当你在浏览器中浏览网页时,浏览器的进程也是在运行状态。尽管这些进程很可能会同时执行多个任务,但它们都在等待CPU时间片来完成自己的任务。
好处:
-
系统高效响应:当进程处于运行状态时,操作系统正在将资源分配给它,意味着任务正在进行中,系统的响应也最为及时。
-
保证系统稳定性:处于运行状态的进程是系统最为关键的部分之一,确保系统能够按预期完成其工作任务。
下面的代码可以验证这一点:
cpp
#include<iostream>
using namespace std;
#include<unistd.h>
#include<sys/types.h>
int main()
{
while(1)
{
cout << "I'm a process, my PID is:" << getpid() << endl;
sleep(1);
}
return 0;
}

- 只要你的程序出现,输出,输入(只要有输入输出(I/O)操作,你通常不会看到进程处于 R+ 状态) 因为很多程序都要用(I/O) ,所以大家要排队。打印一句输出语句对于CPU就几微秒,CPU在这个时间里又去运行其他的进程了。等到时间片轮转下一次在运行到当前的程序时,中间的时间, 进程 都在外设等待队列中 排队。
具体流程:
-
I/O 操作 :比如打印一行信息到屏幕,这个操作本身可能非常快速,几微秒内就完成。然而,打印输出到屏幕的过程并不仅仅是 CPU 计算,它还涉及显示器的响应和系统的输出操作。进程需要等待 I/O 操作完成。
-
时间片轮转 :进程通常是分时执行的 ,也就是说,操作系统会将 CPU 的时间片分配给多个进程运行。当当前进程在进行 I/O 操作时(比如输出到屏幕),它可能会被挂起,等待 I/O 完成,而操作系统会将 CPU 时间片分配给其他进程 。因此,当前进程短暂地失去 CPU 的控制权 ,被放到 等待队列中,等待 I/O 完成。
-
进入等待队列 :进程进行 I/O 操作时,通常会进入 S 状态,也就是"睡眠状态",表示它在等待 I/O 资源(如等待数据从磁盘读取、等待网络包到达等)。一旦操作系统发现进程需要等待资源,它就会将该进程从 CPU 上移除,换其他进程执行。
-
R+ 状态的消失 :只有当进程不需要等待外部资源,并且能够持续占用 CPU 执行计算任务时,它才会维持 R+ 状态 。一旦进程进行 I/O 操作,它就会进入 S 状态 ,因此你通常看不到 进程持续处于 R+ 状态,除非它真的不涉及 I/O 操作。
当我们将打印语句和睡眠语句屏蔽后,进程 不用在等待队列中 排队, CPU 就一直在处理死循环,此时可以观察到 运行 R 状态。
🖋️ 睡眠状态(S)
应用场景:
进程进入睡眠状态通常是因为等待某些外部资源的到来。这些资源包括I/O设备、网络连接、文件读取等。当一个进程发起了I/O请求后,通常会进入睡眠状态,等待设备响应。会从CPU上面拿下来。
比如,你在Linux系统上执行一个程序,它需要从磁盘读取数据。在数据被读取之前,进程会进入睡眠状态,直到磁盘I/O操作完成,进程才会被唤醒并继续执行。
好处:
-
资源高效利用:在等待某些资源时,进程会进入睡眠状态,这样可以释放CPU,让其他有任务的进程能够继续运行。这使得CPU的使用更加高效,避免无谓的空转。
-
降低系统负载:进程在睡眠状态时不会占用CPU,能够降低系统的负载,并确保其他进程能够顺利执行。
操作系统通过异步I/O处理机制(例如,使用事件驱动框架)能够更好地管理进程的睡眠状态,减少不必要的等待时间,提高系统的并发性。进一步的优化可以帮助减少I/O等待的影响,提高系统响应能力。
🖋️ 休眠状态(D)
应用场景:
休眠状态是指进程因等待不可中断的资源而进入的一种特殊状态。通常,进程会进入休眠状态是因为它需要进行I/O操作或等待硬件设备的响应。这种状态与睡眠状态(S可中断休眠)不同,休眠状态下的进程无法通过常规信号中断。Kill -9 指令无法中断这种状态。
例如,操作系统在访问磁盘时,如果磁盘I/O操作没有完成,进程就会进入休眠状态,直到硬件设备准备好或I/O操作完成。
好处:
-
避免浪费CPU资源:在进程等待硬件设备响应时,进入休眠状态可以避免不必要的CPU消耗。这有助于系统优化资源使用,避免过度的CPU占用。
-
保证I/O操作稳定性:休眠状态通常出现在I/O操作期间,操作系统通过这种方式确保I/O操作能够顺利完成,而不至于受到其他进程的影响。
危害:
-
进程卡住 :一旦进程进入休眠状态,如果长时间没有硬件设备响应,可能会导致进程"卡住",从而拖慢整个系统的效率。
-
系统响应缓慢 :如果系统中大量进程都进入休眠状态,可能会导致整体系统的响应变慢,用户可能会感觉到系统的不流畅。
-
可能导致死锁或系统不响应 :如果多个进程因为等待同一资源而进入D状态,可能会导致死锁,这时候系统可能会出现不响应的现象,特别是在没有正确的资源管理机制时。
休眠状态虽然能够减少CPU资源的浪费,但它也可能成为系统性能瓶颈的一部分。随着硬件的进步(如更快的SSD、改进的网络硬件等),操作系统可以通过更高效的硬件支持来减少进程进入休眠状态的时间,从而提升整体系统响应速度。
典型场景:
S态:
等待磁盘文件读取时,进程处于S态,操作系统允许进程响应信号和中断。这意味着进程在等待磁盘I/O的同时,操作系统可以调度其他进程执行,甚至可能会唤醒该进程继续执行。
进程等待用户输入时,进程会进入S态 ,此时等待可以被外部中断,例如用户可以按
Ctrl+C
中断操作。场景示例 :当你运行
cat
命令读取一个文件时,进程会进入 S态,直到操作系统完成磁盘I/O。D态:
等待硬盘设备I/O操作完成时,进程可能进入D态。例如,当程序等待硬盘读取数据时,操作系统无法中断这一过程,直到硬盘完成数据传输。
系统内核的某些操作(如访问硬件、等待驱动程序响应等)可能会使进程进入D态 。此时,即使你发送
kill
信号,进程也无法响应,只能等到硬件操作完成。场景示例 :进程在进行磁盘的深度读取(如文件系统操作)时,可能会进入D态,如果硬盘在执行操作时非常慢,进程可能会长时间处于此状态。
🖋️ 进程暂停 T 状态
进程的**暂停状态(T状态)**是通过操作系统发出的暂停信号让进程暂时停止运行的状态,通常是出于调试或者需要临时停止进程的目的。
如何进入T状态:
你提到的命令:
-
kill -19 PID
发送 SIGSTOP 信号,该信号将使进程进入暂停状态(T状态)。 -
kill -18 PID
发送 SIGCONT 信号,该信号会让暂停的进程恢复执行。
T 状态的工作原理:
-
进入 T 状态:
- 当我们使用
kill -19 PID
命令时,实际上是向指定进程发送了 SIGSTOP 信号,告诉操作系统将该进程暂停 。操作系统会将该进程的状态从运行中(R,R看不到一般是S+)转换为暂停(T)。暂停状态的进程不会占用 CPU 资源,但它的所有资源(内存、文件描述符等)依然保持,直到它被恢复。
- 当我们使用
-
恢复进程:
- 使用
kill -18 PID
命令发送 SIGCONT 信号,可以让暂停的进程恢复执行。操作系统会将暂停状态的进程重新调度,使其继续从中断的地方执行。恢复后,进程的状态通常会变为运行状态(R)。
- 使用
我们可以通过 kill -18 PID
使 进程
恢复运行,恢复后的 进程
在后台运行 注意:
进程
在后台运行时,是无法通过 ctrl+c
指令终止的,只能通过 kill -9 PID
终止。
S+是在前台运行的,可以用**ctrl+c,而S在后台运行只能用Kill -9 PID 杀死。
**
使用场景:
-
调试: 在调试程序时,可能需要暂停进程的执行,以便检查当前的程序状态或修改某些变量。开发人员可以通过
kill -19
暂停进程,然后使用调试工具(如 GDB)查看程序的当前堆栈、内存、变量等状态。 -
系统资源管理: 在系统负载过高时,管理员可能会选择暂停某些不重要的进程,以释放 CPU 时间给高优先级任务。这种情况下,暂停某些进程后可以恢复它们,避免系统出现性能瓶颈。
-
临时停止某些进程: 有时候我们需要临时停止某些进程(例如长时间运行的服务、后台任务等),但又不想完全终止它们,避免以后再重启或者重新创建时浪费时间。暂停进程会节省系统资源,直到恢复时它们会继续运行。
T 状态的影响和风险:
-
优点:
-
灵活性: 进程暂停并不意味着进程完全终止,所有资源(如内存)仍然保留。这样做可以避免进程的重新初始化或重启,提高系统运行效率。
-
调试便利: 在调试期间,我们可以暂停进程,检查程序的状态,然后继续执行程序,这对于排查问题或错误非常有帮助。
-
-
缺点:
-
无响应: 一旦进程进入暂停状态,它就不再响应任何输入或执行任何任务,可能会导致系统的某些功能暂停或失效。如果有多个进程被暂停,会影响到整个系统的响应速度。
-
资源占用: 虽然暂停状态的进程不消耗 CPU,但它仍然占用系统的其他资源(如内存、文件句柄等)。如果大量进程都处于暂停状态,可能会导致系统资源紧张。
-
可能影响用户体验: 如果你暂停的是一个用户交互的进程(比如服务程序、后台处理任务等),可能会造成用户无法获得实时反馈,影响用户体验。
-
举个例子:
假设你正在运行一个计算密集型的程序,而你的服务器的CPU使用率已经很高,你希望暂时让它停止,释放CPU给其他任务处理。此时,你可以通过 kill -19 <pid>
命令暂停它,等到CPU负载下降后,再通过 kill -18 <pid>
恢复该进程。
另一种情况是在调试一个程序时,你可能希望在某个特定的地方暂停执行(例如某个函数或某个循环),而通过暂停和恢复进程,你可以检查程序状态或变量,进行故障排查
🖋️ 僵尸进程(Zombie)
应用场景:
僵尸进程是在进程终止后,父进程尚未回收其退出状态而留下的进程。僵尸进程没有实际的执行任务,它们只是占用了系统的PID和资源,等待父进程读取它们的退出状态并回收资源。
当一个子进程终止时,操作系统会将它的退出状态保留在进程表中,直到父进程通过
wait()
或waitpid()
来读取该状态。如果父进程没有及时回收,子进程就会成为僵尸进程。
我们可以利用 fork()
函数自己创建 父子进程
关系,观察到这一现象:
cpp
#include<iostream>
using namespace std;
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t ret = fork();
if(ret == 0)
{
while(1)
{
cout << "I'm son process, my PID: " << getpid() << " PPID: " << getppid() << endl;
sleep(1);
}
}
else if(ret > 0)
{
while(1)
{
cout << "I'm father process, my PID: " << getpid() << " PPID: " << getppid() << endl;
sleep(1);
}
}
else
{
while(1)
{
cout << "Make son process fail!" << endl;
sleep(1);
}
}
return 0;
}

此时输入指令 kill -9 PID
即 kill -9 28315
终止 子进程
再次查看进程状态: 危害:
-
资源占用:尽管僵尸进程不再执行任务,但它们仍然占据着PID等资源。如果系统中存在大量僵尸进程,它们会导致进程表的资源被占满,影响系统的正常运行。
-
内存泄漏:未及时回收的僵尸进程会导致内存泄漏,增加系统的负担,影响系统稳定性。
个人思考:
操作系统通过在进程终止时自动回收资源或通过父进程的 wait()
系统调用回收僵尸进程,能够有效地减少僵尸进程的出现。然而,如果父进程长时间不回收子进程的退出状态,就会导致资源的浪费。因此,设计合理的进程管理机制可以帮助系统避免僵尸进程的积累。
🖋️孤儿进程
孤儿进程 是操作系统中的一种特殊进程状态,指的是父进程在子进程运行期间退出或终止,导致该子进程没有父进程的状态。此时,操作系统会自动将这个子进程"领养",并将它的父进程 ID(PID)修改为 1
,即 init
进程(在 Linux 中为系统的第一个进程)。
孤儿进程的形成:
孤儿进程的形成通常是在以下两种情况发生时:
-
父进程提前终止:父进程在子进程还在运行时结束,导致子进程失去了原本的父进程。
-
父进程不再管理子进程 :父进程虽然未终止,但可能退出了某些工作范围,或者未能处理子进程的退出,导致系统将子进程重新分配给
init
(PID = 1)进程。
孤儿进程的处理方式:
-
OS的处理 :操作系统会通过将孤儿进程的父进程设为
init
进程来"领养"它。这是因为init
进程是操作系统的根进程,负责管理所有的子进程。init
进程负责回收孤儿进程,确保它们最终能够被清理,避免内存泄漏。 -
子进程的状态 :一旦父进程退出,子进程进入孤儿状态,操作系统会重新安排该进程的父进程为
init
进程。即便没有原父进程,孤儿进程依然可以正常运行,直到结束。
孤儿进程的例子:
假设有以下的程序逻辑:
-
父进程创建一个子进程。
-
父进程在子进程运行过程中退出。
-
子进程继续执行,但由于父进程已经结束,它就变成了孤儿进程,父进程 ID 被设置为
1
(即init
)。cpp#include <iostream> #include <unistd.h> int main() { pid_t pid = fork(); if (pid > 0) { // 父进程 std::cout << "Parent process is terminating. PID: " << getpid() << std::endl; sleep(2); // 等待子进程 std::cout << "Parent process exited." << std::endl; } else if (pid == 0) { // 子进程 std::cout << "Child process is running. PID: " << getpid() << " Parent PID: " << getppid() << std::endl; sleep(5); // 假装子进程在做某些操作 std::cout << "Child process finished. PID: " << getpid() << " Parent PID: " << getppid() << std::endl; } else { std::cout << "Fork failed!" << std::endl; } return 0; }
孤儿进程的危害:
孤儿进程本身并不会直接造成资源泄漏,因为它会被
init
进程领养并最终清理。但如果系统中频繁产生孤儿进程且没有及时回收,它们可能会成为系统中僵尸进程的来源,导致系统的 PID 空间被占满,从而影响新进程的创建。
🖋️ 守护进程(Daemon)
应用场景:
守护进程是那些在后台运行,通常不与用户直接交互的进程。它们通常负责处理长期运行的任务,如定时任务、日志记录、系统监控等 。例如,cron
用于执行定时任务,syslog
负责系统日志管理。
守护进程会在系统启动时启动,并会一直保持运行,直到系统关闭或进程被显式终止。它们不会被终端关闭或控制,因此常常在后台悄无声息地工作。
好处:
-
后台服务支持:守护进程能够确保后台任务的持续运行,保证系统正常运行。例如,定时备份、系统日志收集、监控任务等都依赖守护进程。
-
提高系统稳定性:守护进程通常负责关键任务的执行,确保系统资源的持续管理和服务的正常提供。
危害:
-
资源消耗:守护进程通常会持续运行,占用系统资源。如果守护进程设计不当,可能会导致资源浪费或系统负载过高。
-
调试困难:守护进程通常没有用户界面,难以进行调试。如果守护进程出现问题,可能需要通过日志或其他监控工具才能发现。
个人思考:
守护进程在系统中扮演着极其重要的角色,尤其是在服务型操作系统中。合理设计和管理守护进程的资源消耗、性能监控,以及日志管理至关重要。在实际应用中,开发者需要确保守护进程的资源利用高效,避免出现无限循环、内存泄漏等问题。
1. 阻塞(Blocked)状态:
进程在阻塞状态下,通常是因为它正在等待某些资源的就绪,比如等待 I/O 操作完成、等待文件锁定、等待网络响应等。进程在这个状态下并不会占用 CPU 时间,而是进入等待队列,直到所需的资源变得可用,才会被操作系统重新调度执行。包括(S,D)状态。
阻塞的原因:
-
I/O 操作:例如,读取文件、网络请求、硬盘访问等。
-
资源等待:例如,等待 CPU 时间片、内存、锁等。
-
同步等待:例如,等待其他进程的信号或消息。
阻塞状态的表现:
-
阻塞状态的进程不能继续执行,直到它所等待的资源或事件发生。
-
阻塞的进程进入操作系统的等待队列,并不占用 CPU 资源。
-
操作系统根据不同的等待类型(I/O、锁等)将进程排入不同的队列。
例子:
假设你在使用某个应用程序,它需要从网络获取数据。在等待数据从服务器返回之前,进程就会进入阻塞状态。在此期间,它不会占用 CPU,而是等待数据返回。
2. 挂起(Suspended)状态:
挂起状态(也称为交换至磁盘状态)**是指进程暂时被移出内存,**挂起等待恢复。这种状态通常发生在系统内存紧张时,操作系统会选择将某些进程的状态保存在磁盘上,而非继续占用内存资源。挂起的进程处于"非活动"状态,它没有被调度执行,直到操作系统将其从磁盘中恢复到内存中,才会重新进入就绪状态。
挂起的原因:
-
内存不足:当系统内存不足时,操作系统可能会将某些进程挂起,转移到磁盘中保存,从而释放内存空间给其他进程使用。
-
操作系统的调度策略:操作系统可能根据优先级等因素选择挂起某些进程。
挂起状态的表现:
-
挂起状态的进程暂时被从内存中移除,进程的上下文信息被保存到磁盘中,直到它再次被调度。
-
挂起的进程不会占用 CPU 和内存资源。
-
与阻塞状态不同,挂起状态通常由操作系统主动控制,而不是进程由于等待资源而进入这种状态。
例子:
假设操作系统内存资源紧张,操作系统选择将一些低优先级的进程挂起,保存到磁盘中,直到有足够的内存空间,再将它们恢复到内存中继续执行。
cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
int main() {
int fd = open("example_file.txt", O_RDONLY);
if (fd == -1) {
std::cerr << "Failed to open file, blocking..." << std::endl;
// 阻塞状态:等待文件打开
sleep(10); // 假设等待文件就绪
}
close(fd);
return 0;
}
阻塞与挂起的区别:
虽然 阻塞 和 挂起 都意味着进程不会执行,但它们有以下几点不同:
区别 | 阻塞(Blocked) | 挂起(Suspended) |
---|---|---|
触发原因 | 进程等待某些资源(如 I/O 操作、信号等) | 进程主动或被动从内存移到磁盘,通常是由于内存不足 |
操作系统行为 | 操作系统将进程放入等待队列,等待资源 | 操作系统主动将进程状态保存到磁盘,节省内存 |
资源占用 | 进程不占用 CPU 资源,但仍占用内存资源 | 进程不占用 CPU 和内存资源 |
恢复条件 | 进程等待的资源可用时会恢复执行 | 进程从磁盘恢复到内存时才会恢复执行 |
总结:
-
阻塞状态通常是进程等待某些资源(如 I/O)的结果,这种状态下进程不会占用 CPU 资源。
-
挂起状态通常发生在内存不足时,操作系统将进程的状态保存在磁盘上,以便稍后恢复。挂起进程不会占用内存和 CPU 资源。