2. 可中断睡眠状态(TASK_INTERRUPTIBLE)
3. 不可中断睡眠状态(TASK_UNINTERRUPTIBLE)
进程状态

补充知识:
1,并行和并发
- CPU执行进程代码,不是把进程代码执行完毕才开始下一个,而是给每个进程分配一个时间片,基于时间片,进行调度轮转(单CPU下)并发。
- 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内,让多个进程得以推进,称之为并发。
- 并行:多个进程在CPU下分别,同时进行运行,这称之为并行。
2,时间片
- Linux/windows民用级别操作系统,采用分时操作系统(调度任务追求公平,都要进行)
- 与之对应的实时操作系统:RTOS...(如车载系统:比较需要实时的任务,需要这种操作系统)
3.进程具有独立性 --- Done
4.进程的状态
当进程处于运行队列中时,它处于就绪状态,此时该进程具备了运行的条件,随时可以被 CPU 调度执行,一旦被 CPU 调度执行,就进入运行状态。而当进程需要等待外部设备(例如等待键盘输入)时,它就进入阻塞状态,在阻塞状态下,进程会与目标外部设备连接,此时 CPU 不会对其进行调度。
- 运行状态:进程正在被 CPU 执行,此时进程占用 CPU 资源,其代码正在被执行,相关的计算和操作正在进行中。
- 就绪状态:进程已经准备好运行,它拥有了除 CPU 之外的所有必要资源,只要 CPU 分配给它时间片,它就可以立即执行,进入运行状态。处于就绪状态的进程在就绪队列中等待被调度。
- 阻塞状态:进程由于等待某些事件的发生(如等待 I/O 操作完成、等待信号量等)而暂时无法继续执行,此时进程会放弃 CPU 资源,进入阻塞队列。在阻塞状态下,即使 CPU 有空闲时间,该进程也不能被调度执行,直到其所等待的事件完成,才会转换到就绪状态。
- 挂起状态:挂起状态是为了满足系统管理的需要而引入的。进程可能因为内存紧张等原因被暂时挂起,此时进程被移到外存中,不再参与 CPU 的调度。挂起状态又可以分为就绪挂起和阻塞挂起。就绪挂起状态的进程是处于就绪状态但被挂起,阻塞挂起状态的进程是处于阻塞状态且被挂起。当系统资源充足或者满足特定条件时,挂起的进程可以被激活,重新回到内存中的就绪状态或阻塞状态。
这几种状态的联系如下:进程通常从创建开始,进入就绪状态,然后被 CPU 调度进入运行状态。在运行过程中,可能因为等待某些事件而进入阻塞状态,当事件完成后又回到就绪状态。如果系统资源紧张等原因,进程可能会被挂起,从内存移到外存,根据其原来的状态分为就绪挂起或阻塞挂起,当条件合适时再被激活回到内存相应的状态。而处于运行状态的进程在时间片用完或者被更高优先级的进程抢占等情况下,会回到就绪状态,等待下一次被调度。
linux进程的状态
1. 运行状态(TASK_RUNNING)
- 本质:该状态表示进程要么正在被 CPU 执行,要么已经准备好随时被 CPU 执行。它实际上包含了两种子状态:
- 正在运行:进程正在占用 CPU 资源,其代码正在被 CPU 逐条执行。
- 就绪:进程已经具备了运行的条件,只是由于系统中可能有其他进程正在使用 CPU,所以它在就绪队列中等待 CPU 调度。
- 示例:当你在终端中执行一个简单的
ls命令时,ls进程就会进入运行状态,直到命令执行完毕。
2. 可中断睡眠状态(TASK_INTERRUPTIBLE)
- 本质:进程处于等待某些事件发生的状态,例如等待 I/O 操作完成、等待信号量等。在这种状态下,进程会释放 CPU 资源,并且可以被信号中断。一旦等待的事件发生或者收到了信号,进程就会从可中断睡眠状态转换为就绪状态,等待 CPU 调度。
- 示例:当一个程序调用
read函数从键盘读取输入时,如果没有输入,该进程就会进入可中断睡眠状态,等待用户输入。如果此时用户按下了键盘上的某个键,或者程序收到了一个信号(如SIGTERM),进程就会被唤醒。
3. 不可中断睡眠状态(TASK_UNINTERRUPTIBLE)
- 本质:与可中断睡眠状态类似,进程也是在等待某些事件发生,但不同的是,处于不可中断睡眠状态的进程不会响应任何信号,只能等待所等待的事件完成。这种状态通常用于一些关键的 I/O 操作,以避免在操作过程中被信号中断而导致数据不一致等问题。
- 示例:当进程在进行磁盘 I/O 操作时,为了保证数据的完整性,可能会进入不可中断睡眠状态,直到磁盘 I/O 操作完成。
4. 暂停状态(TASK_STOPPED)
- 本质:进程被暂停执行,通常是由于收到了特定的信号,如
SIGSTOP或SIGTSTP。在暂停状态下,进程不会继续执行,直到收到SIGCONT信号,才会从暂停状态恢复到就绪状态,继续等待 CPU 调度。 - 示例:在终端中,你可以使用
Ctrl + Z组合键向当前正在运行的进程发送SIGTSTP信号,将其暂停。之后,你可以使用fg命令(发送SIGCONT信号)让进程继续执行。
5. 僵尸状态(TASK_ZOMBIE)
- 本质:当一个进程结束运行后,它的大部分资源(如内存、文件描述符等)会被释放,但进程的描述符(
task_struct)仍然保留在系统中,直到其父进程调用wait()或waitpid()等系统调用获取其退出状态信息。处于僵尸状态的进程已经不再执行代码,但它的存在是为了让父进程能够获取其退出状态。 - 示例:如果一个父进程创建了一个子进程,子进程完成任务后退出,但父进程没有及时调用
wait()来获取子进程的退出状态,子进程就会变成僵尸进程。
6. 退出状态(EXIT_DEAD)
- 本质:这是进程生命周期的最后一个状态,表示进程已经完全结束,其所有资源(包括进程描述符)都已经被释放,从系统中彻底消失。
状态转换关系
进程在其生命周期内会在不同状态之间转换,例如:
- 运行状态的进程在时间片用完或者被更高优先级的进程抢占时,会转换到就绪状态。
- 运行状态的进程在等待 I/O 操作时,会转换到可中断睡眠状态或不可中断睡眠状态。
- 处于可中断睡眠状态的进程在收到信号或等待的事件完成时,会转换到就绪状态。
- 处于暂停状态的进程在收到
SIGCONT信号时,会转换到就绪状态。 - 子进程结束运行后会转换到僵尸状态,直到父进程处理其退出状态后,才会进入退出状态。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在 Linux 系统中,使用 ps 命令查看进程信息时,STAT 列显示了进程的状态。
基本状态
运行状态(TASK_RUNNING,R)
- 本质:包含正在被 CPU 执行和就绪等待调度两种子状态。
- 示例:执行
ls、yes命令时进程处于此状态。
- 可中断睡眠状态(TASK_INTERRUPTIBLE,
S)- 本质:等待事件(如 I/O、信号量),可被信号中断,事件完成或接信号转就绪。
- 示例:程序调用
read等等待输入,或网络请求等等待响应时处于此状态。
- 不可中断睡眠状态(TASK_UNINTERRUPTIBLE,
D)- 本质:等待事件但不响应信号,用于关键 I/O 保数据完整。
- 示例:磁盘大量数据写入时进程处于此状态。
- 暂停状态(TASK_STOPPED,
T/t)- 本质:收
SIGSTOP/SIGTSTP暂停,收SIGCONT恢复就绪。t多与终端作业控制相关。 - 示例:
Ctrl + Z暂停top进程,STAT可能显示T或t(进程被追踪。 - 我们的gdb打断点,本质上也就是进程被暂停了。
- 本质:收
- 僵尸状态(TASK_ZOMBIE,
Z)- 本质:进程结束,部分资源释放,等父进程获取退出状态。
- 示例:子进程退出,父进程未及时处理,子进程成僵尸。
- 退出状态(EXIT_DEAD,
X)- 本质:进程完全结束,所有资源释放。
- 说明:难直接观察,一般先成僵尸,父进程处理后消失,特殊情况可直接进入
状态修饰符
除了上述基本状态字符外,STAT 列还可能会出现一些修饰符,常见的有:
- <:表示该进程是高优先级进程。
- N:表示该进程是低优先级进程。
- L:表示该进程有页面被锁定在内存中,通常用于 I/O 操作。
- s:表示该进程是会话首进程。
- l:表示该进程是多线程进程。
- +:表示该进程是前台进程组的成员。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
一个小实验
在 Linux 系统中,当我们编写一个简单的 while 循环程序时,根据是否包含 printf 打印语句,进程会呈现出不同的状态,这主要与进程的执行逻辑、I/O 操作以及 CPU 调度机制有关。

1. while 循环中包含 printf 语句
#include <stdio.h>
int main() {
while (1) {
printf("Hello, World!\n");
}
return 0;
}

当运行上述程序时,进程大多时间处于可中断睡眠状态(S 状态),原因如下:
printf 函数是一个标准库函数,用于格式化输出数据。在执行 printf 时,它并非直接将数据输出到显示器,而是先将数据写入到用户空间的缓冲区。当缓冲区满或者遇到特定的刷新条件(如换行符 \n 、调用 fflush 函数等)时,会触发系统调用将缓冲区中的数据复制到内核空间的缓冲区,最终由内核负责将数据传输到对应的输出设备(如显示器)。
这个过程涉及到大量的 I/O 操作,而 I/O 操作的速度相对于 CPU 的处理速度来说是非常慢的。当进程发起 I/O 请求后,由于外设(如显示器)可能无法立即响应,进程会进入可中断睡眠状态(TASK_INTERRUPTIBLE),释放 CPU 资源,等待 I/O 操作完成。一旦 I/O 操作完成,进程会被唤醒,重新进入就绪队列等待 CPU 调度,进而继续执行后续的代码。因此,在这个程序运行过程中,由于频繁的 I/O 等待,进程大部分时间处于 S 状态。
2. while 循环中注释掉 printf 语句


当运行上述程序时,进程大多时间处于运行状态(R 状态),原因如下:
在注释掉 printf 语句后,while 循环中几乎没有 I/O 操作,主要是 CPU 不断地执行循环判断指令。由于没有 I/O 等待,进程可以持续占用 CPU 资源进行计算,因此会一直处于就绪或正在被 CPU 执行的状态,即 R 状态。在这种情况下,进程会不断地请求 CPU 时间片,并且由于没有阻塞操作,CPU 会持续为该进程分配时间片以执行循环代码。
综上所述,while 循环中是否包含 printf 语句会导致进程状态的不同,主要是因为 printf 函数涉及的 I/O 操作会使进程在等待外设响应时进入可中断睡眠状态,而不包含 printf 时进程主要进行 CPU 计算,因此大多时间处于运行状态。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
进程退出:代码不会执行了,但是不会完全退出,比如我们要保存进程的PCB以来让我们可以查询到这个进程的退出信息。进程=内核数据结构(task_struct)+ 代码和数据,退出时:首先可以立即释放的:代码和数据(程序),进程退出是要有退出信息的---进程的退出码,要保存在自己的task_struct内部。
管理结构的task_struct必须被OS维护起来,方便用户未来进行获取进程的退出信息。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
僵尸进程

另开一个终端:
使用如下命令,每隔一秒就检测进程的信息:状态
![]()
父子同时在运行:

子进程结束,父进程运行
子进程退了之后,状态由S变为了Z:僵尸状态(为了维护自己的task_struct,方便未来自己的父进程读取子进程的退出状态)

父子进程都结束

上述的代码,如果不适用cnt来使子进程有一个退出的结果,而是让父子继承都while(1)死循环,再用kill -9 pid 杀死进程的话,也会出现一样的Z僵尸状态。父进程没有回收子进程。且会一直僵尸。
僵尸进程如果没有被管理(OS回收),那么会一直僵尸下去.......(task_struct会一直维持
一直存在也会一直消耗内存,内存泄漏!!!(后续再介绍解决方案) 一般是需要父进程来读取释放
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
孤儿进程
孤儿进程是指父进程在子进程之前结束运行,导致子进程失去了父进程的控制,从而成为孤儿进程。
形成原因
- 父进程提前退出:当父进程完成了它的任务或者因为某些错误而提前终止时,它的子进程就会变成孤儿进程。例如,父进程执行了
exit系统调用或者收到了一个无法处理的信号而终止。 - 父进程异常终止:父进程可能会因为段错误、非法指令等运行时错误而异常终止,这时子进程也会成为孤儿进程。
处理方式
- init 进程接管:在 Linux 系统中,孤儿进程会被
init进程(进程号为 1)自动收养。init进程会成为孤儿进程的新父进程,负责回收孤儿进程的资源。当孤儿进程终止时,init进程会调用wait系统调用来获取孤儿进程的退出状态,从而避免产生僵尸进程。 - 通过信号处理:可以在父进程中设置信号处理函数,在父进程收到可能导致其终止的信号时,先将子进程的父进程设置为其他进程(如专门的监护进程),或者在信号处理函数中对即将成为孤儿进程的子进程进行一些特殊的处理,如保存相关状态信息等。
- 编写守护进程:如果程序设计中需要避免出现孤儿进程带来的潜在问题,可以考虑将相关进程设计成守护进程。守护进程在后台运行,脱离了终端控制,并且通常会有自己的一套资源管理和进程监控机制,能够更好地处理进程的生命周期,减少孤儿进程出现的可能性。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
思考一下:为啥僵尸进程不被系统领养呢?
主要有以下原因:
- 定义与特性:僵尸进程是指子进程已经结束运行,但其父进程尚未通过
wait或waitpid等系统调用来获取其退出状态信息,此时子进程的进程控制块(PCB)仍会保留在系统中。它处于一种特殊的终止状态,虽然已经不再执行任何代码,但仍占用一定的系统资源,如进程号、PCB 中的一些信息等。与孤儿进程不同,僵尸进程是已经执行完任务准备退出的进程,只是其资源尚未被父进程回收,而不是像孤儿进程那样需要被其他进程接管以继续运行或进行资源管理。 - 系统设计目的:系统设计的原则是让父进程对其子进程的资源回收负责。这样设计的目的是为了让进程之间的关系更加清晰,资源管理更加明确。父进程创建子进程,就有责任在子进程结束后回收其资源,以确保系统资源的有效利用和进程空间的整洁。如果系统随意领养僵尸进程并回收其资源,就会打破这种明确的责任关系,可能导致父进程在编写时不重视资源回收,从而引发更多的资源管理问题。
- 避免混乱:如果系统领养僵尸进程,可能会导致一些混乱。例如,父进程可能还在等待子进程的某些特定信息,或者父进程有自己的逻辑来处理子进程的退出状态。如果系统直接介入并回收僵尸进程,可能会干扰父进程的正常逻辑,导致程序出现错误或异常行为。此外,多个父进程可能产生多个僵尸进程,系统如果统一领养并处理,很难保证按照每个父进程的期望来正确处理子进程的资源和退出状态,容易造成资源管理的混乱。

2311

被折叠的 条评论
为什么被折叠?



