目录
一、进程状态概念
Linux进程状态是指Linux系统中进程所处的不同执行阶段或条件,状态决定了一个进程的后续动作
在Linux中,状态本质上是进程taskt_struct结构体中的一个整形变量
体现在Linux内核源码为:
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 */ };
二、Linux中的进程状态:
就绪状态(Ready):进程已经准备好执行但是还没有被处理器调度执行的状态
运行状态(Running):进程正在被处理器执行的状态
阻塞状态(Blocked):进程因为等待某些事件(如等待I/O操作完成、等待信号量等)而被阻塞的状态
等待状态(Waiting):进程因为等待某些事件(如I/O操作完成、信号等)而被阻塞的状态
暂停状态(Paused):进程被暂停执行,可以自行恢复执行
挂起状态(Suspended):进程被操作系统挂起执行,资源被释放,进程状态被保存到磁盘上。挂起状态可以分为内存挂起状态和磁盘挂起状态两种。
终止状态(Terminated):进程执行完毕或者因为某些原因被终止的状态
1.就绪状态、运行状态(R)
一个进程已经准备就绪,可以随时被系统调度此时就是运行状态
相当于合并了就绪和运行,统称运行状态
注意:不要认为被调度执行后才算运行状态
1.运行队列(进程排队)
我们知道,在等待运行时,进程需要排队等待被调度,就有一个就绪队列
但当内存中存在许多进程时就会使得CPU中的资源不够同时分配给许多进程,于是从就绪队列调度后仍然存在一个队列–运行队列
需要特别注意的是,在Linux中进程的任何排队操作,都是进程的PCB在排队,把所有的进程PCB用数据结构组织起来,就形成了我们的运行队列
这些存在运行队列中的进程都被称作运行状态
- 不过一个进程的PCB可能不仅仅在一个队列中排队,在每个task_struct结构体中都会有很多的structural_listnode n,真正去排队的是这些东西,而不是之前的简化说法PCB
2.时间片
一个进程被拿到cup中执行时,并不是等这个进程执行完毕才切换下一个进程
而是这多个进程在一个时间段内所有代码都会被执行 —— 这就叫做【并发执行】
每一个进程执行一个时间片的时间后就会从cpu上拿下来,然后放上下一个进程。这个动作就称作为【进程切换】。
一个时间片通常是10ms左右
3.进程的前后台运行
1.前台进程
- 指在终端中直接运行的命令或程序,它会占据当前终端,并将执行过程中产生的相关信息显示在终端上
- 前台进程与用户进行交互,需要较高的响应速度,因此其优先级通常较高
- 如果前台进程没有结束,用户则不能在当前终端中进行其他的操作
在Shell命令行中输入并执行某条命令,默认情况下会启动一个前台进程
2.后台进程
- 也称为守护进程(Daemon),是运行在后台的一种特殊进程
- 后台进程基本上不和用户交互,其优先级别相对较低
- 后台进程不受终端控制,即使终端关闭,它也不会随之消失,而是继续运行
比如我写了一个死循环,上面的状态显示的是R+
- 这里的R+代表的就是这个进程是在前台运行的,所以我们在输入任何指令后不会对其造成任何的影响
- 如果想要运行在后台,需要在执行时在你生成的可执行文件的后面加上了一个&,那么其状态变成了R,此代表的意思就是这个进程它是运行在了后台
2.挂起状态
挂起状态是一个重要的概念,它指的是将当前处于运行状态的数据保存在内存中,并让进程或系统等待某个事件的到来再继续执行,这相当于使计算机或进程进入一种睡眠状态
挂起状态分为系统挂起和进程挂起
1.系统挂起
- 整个计算机系统进入挂起状态,此时CPU停止工作,内存中的数据被保存到磁盘中,以便在系统唤醒时能够快速恢复。
- 系统挂起通常通过特定的命令或系统设置来实现,如使用rtcwake命令设置系统挂起时间。
2.进程挂起
- 单个进程被挂起,暂停其所有活动并释放CPU时间片,但进程的状态信息和资源(如内存中的数据)仍然被保留。
- 进程挂起可以由用户主动发起,也可能是操作系统因为资源不足或其他原因而自动进行。
- 进程挂起可以通过发送特定的信号(如SIGSTOP)来实现,也可以通过其他机制(如调试器的暂停功能)来触发
3.挂起的场景与特点
- 在挂起状态下,系统或进程占用的资源(如CPU、内存等)非常低,有助于释放系统资源给其他任务使用
- 在资源有限的情况下,可以通过挂起非关键进程来释放资源给更重要的任务使用。
一般计算机处于挂起状态的前提是计算机的资源已经比较吃紧了,不得不将一些数据换到磁盘中(磁盘中的swap分区)
3.阻塞状态(S、D)
阻塞状态是进程在等待某个事件或资源(如I/O操作完成、信号量释放等)时所处的状态。
在这种状态下,进程无法继续执行,因为它需要等待的资源或事件尚未发生。
此时进程不会占用CPU资源,操作系统会调度其他可执行的进程
例如我们平时的命令行等待我们输入命令时就是一个阻塞状态
1.可中断的睡眠状态(S浅度睡眠状态)
S(sleeping)在本质上也是阻塞状态的一种,可以说是阻塞的一种分支
S状态是Linux中可中断的睡眠状态,表示进程正在等待某个事件或资源
S状态下的进程可以被信号或中断唤醒并继续执行。
将进程运行起来我们可以看到其是S+的状态,因为命令行此时正在等待用户的输入,遇到I/O操作。
2.不可中断的睡眠状态(D深度睡眠状态)
D状态是Linux中不可中断的睡眠状态,也称为磁盘睡眠状态。在这种状态下,进程正在等待磁盘I/O操作或其他无法被中断的硬件操作完成。
进程无法响应任何信号或中断。
进程只能等待操作完成后才能继续执行。
3.关于S浅度睡眠状态与D深度睡眠状态的理解
学了挂起状态,我们可以知道,在内存不足时,会通过挂起状态来释放我们的内存
但是如果在极端情况下,即使挂起状态释放内存也不够呢?这时操作系统就没办法了,只能亲自动手开始杀进程了
但是这时用户有一个特别重要的信息要通过进程写入磁盘,但这个进程又刚好被操作系统杀了呢?这可就麻烦了,信息丢失可是个大问题
于是为了保证某些正在干重要事情的进程不被杀掉,给这些进程加了一块“免死金牌” D状态,操作系统在杀进程时,如果进程只是一个普通的‘S’,操作系统可以杀,但如果是‘D’,那么因为有免死金牌,操作系统不能杀
这个免死金牌具体来说就是给该进程的状态设置为深度睡眠状态。那么这个进程就不会被杀死了,当磁盘写完数据后告知进程,那么它就可以将自己放入【运行队列】里去运行,此时它的状态就变成R了
那如果D状态的进程特别多呢?
一般系统中D状态的进程是很少见的,如果你看到了D状态的进程,那就说明你的系统里挂不远了,洗洗睡吧
4.停止状态(T)、进程跟踪状态(t)
1.停止状态(T)
- 停止状态(T)表示进程已经被停止执行。这通常是因为进程接收到了某种信号(如SIGSTOP)而主动或被动地停止了运行
- 在停止状态下,进程不会占用CPU资源,也不会执行任何操作,直到它接收到继续执行的信号(如SIGCONT)或系统对其进行其他操作。
这种状态通常发生在以下情况下:
当进程读取一个设备,但是被设备拒绝了,而操作系统又不想杀掉你,此时,为了减少你的非法操作,就会把该进程置为停止状态。
当用户使用Ctrl+Z组合键将一个正在前台运行的进程移至后台时,该进程会进入停止状态
- 在停止状态下,进程仍然存在于系统中,但不再活动。可以使用fg命令将一个停止的进程移回前台继续执行,或者使用bg命令将其放入后台执行。
- 系统管理员可以使用kill命令的-STOP选项,或者kill -s SIGSTOP命令,将一个进程暂停
- 暂停进程:kill -19 PID
- 启动进程:kill -18 PID
处于暂停状态的进程,不仅仅是等待硬件资源,也可能是等待软件信号等,所以,他也可以被看作是一个阻塞状态的分支
2.进程跟踪状态(t)
- 进程跟踪状态(t)是进程在被调试器跟踪时所处的状态。当使用调试器(如gdb)对进程进行调试时,如果调试器暂停了进程的执行(例如,在断点处),则进程会进入跟踪状态。
- 在跟踪状态下,进程同样不会执行任何操作,但它与停止状态(T)的区别在于,跟踪状态是由调试器主动控制的,而停止状态则可能是由信号或其他外部因素触发的。
- 比如调试器暂停了进程的执行。这通常发生在调试器设置了断点,并且进程执行到该断点时,或者调试器执行了其他导致进程暂停的操作,如单步执行等
T停止状态和S睡眠状态的主要区别在于:
- T状态是进程被显式停止执行的状态,需要通过发送信号来恢复;
- S状态是进程在等待某个事件或资源完成的状态,当等待条件满足时进程会自动恢复执行
5.死亡状态(X)
死亡状态就是所有资源释放进程的任务结束了,使这个进程退出,同时把这个进程的资源释放(通过父进程回收)
(就如一个人过世,处理好他的后事)
杀进程的方法:
1.发送9信号
kill -9 PID
2.通过进程名、
killall 进程名
6.僵死状态(Z)
1.僵死状态的概念
僵死状态形象点说有点像是没死透的死亡状态
进程在死亡后需要将进程的资源给父进程回收
简单说就是父进程既然创建了子进程后,定然会给子进程布置了任务,子进程完成任务后需要将任务的完成情况报备给父进程,否则父进程怎么知道你有没有好好完成任务呢?
因此,进程在退出后,却没有把进程的资源给父进程回收,此时的进程就处于僵死状态,此时的进程又被称作僵尸进程
2.进程PID与退出码
在系统中每个进程在创建时都会给进程分配一个PID,子进程的PID是父进程用来识别和操作子进程的唯一标识符
在退出时会返回给父进程的一个值,用于表示子进程的退出状态或执行结果。父进程通过读取子进程的退出码来获取子进程的结束信息
父进程回收子进程时会用wait函数回收子进程的PID,并将子进程的退出状态保存在通过第二个参数传递的地址中(如果提供了该参数)
3.僵尸进程举例
#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> #include<sys/wait.h> int main() { pid_t pid=fork();//创建父子进程 if(pid==0) //子进程进 { //....... printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid()); exit(0);//返回退出码 //子进程结束 } //父进程 sleep(15); int status; pid_t p=wait(&status); printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid()); }
在sleep(15)前,子进程结束,但是还没有被父进程回收,显示的是Z+僵死状态
休眠15秒过后,父进程对子进程进行回收,子进程的僵死状态结束
4.僵尸进程的危害
如果一个父进程一直不去不去回收已经退出的子进程的信息,就会造成内存泄露
进程一般退出的时候,它的代码和相关数据会被释放,但是,它的task_struct对象依然还在内存中,仍然占用内存,占用进程表,这些僵尸进程会累积起来,占用大量的进程表项。
进程表是有限资源,当进程表被填满后,系统可能无法再创建新的进程,导致系统资源泄露和性能下降。
5.僵尸进程的解决方案
- 首先自然就是在子进程结束后让父进程用wait、waitpid回收
- 让父进程忽略 SIGCHLD信号,这样子进程结束后会被自动回收,不会变成僵尸进程
- 杀死父进程,让子进程成为孤儿进程,由 init 进程接管并回收
7.孤儿进程
1.概念
前面我们知道,僵尸进程是能够恢复的,只要被父进程回收就行
可万一,子进程好好地,父进程出事了呢?
父进程如果提前退出,子进程后退出,变成僵尸进程之后,那该如何处理呢?这种情况下的进程就称作孤儿进程
- 孤儿进程不再拥有原来的父进程,因为父进程已经终止
2.特点
但其实这种情况比僵尸进程要好的多,因为在Linux系统中,存在init进程
- 孤儿进程会被init进程收养。这意味着这些进程将以init进程作为它们的父进程继续运行
- 当孤儿进程结束时,其占用的资源(如内存、文件描述符等)会被正常回收,就像任何其他进程一样
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t id = fork(); if (id < 0) { perror("fork"); return 1; } else if (id == 0) {//child printf("I am child, pid : %d\n", getpid()); sleep(10); } else {//parent printf("I am parent, pid: %d\n", getpid()); sleep(3); exit(0); } return 0; }
3.孤儿进程解决僵尸进程
甚至于如果出现了僵尸进程,我们还可以将僵尸进程的父进程杀掉,这样僵尸进程就会变成孤儿进程,这样就能正常被init进程回收
孤儿进程的存在通常不是一个问题,因为系统能够自动处理它们。然而,在某些情况下,如果系统中产生了大量的孤儿进程,并且这些进程长时间运行而不结束,那么它们可能会占用系统资源,影响系统性能