个人主页:chian-ocean
文章专栏:Linux
前言:
在现代操作系统中,进程是资源分配和任务调度的基本单位。作为一个多任务操作系统,Linux 必须在多个进程之间进行有效的调度和管理,这就需要对每个进程进行状态标记,从而明确进程当前的行为和系统资源的使用情况。
进程状态概念
进程是操作系统中程序执行的基本单位,它是一个正在运行的程序的实例。一个进程不仅包含程序代码,还包括其运行时的状态、资源(如内存、文件描述符)和执行信息。
为什么存在进程状态
进程状态的管理是操作系统实现多任务处理的关键。不同的进程状态能够帮助操作系统了解每个进程当前的执行状态,以便做出合理的资源调度和管理决策。简短来说,进程状态的存在有以下几个原因:
- 资源管理:不同的进程状态帮助操作系统高效管理CPU、内存、I/O设备等资源。比如,处于"就绪"状态的进程等待CPU时间片,而"睡眠"状态的进程等待I/O资源,可以避免无效的资源占用。
- 进程调度:操作系统根据进程的不同状态(如就绪、运行、阻塞等)来决定进程的执行顺序,实现公平且高效的调度。
- 并发控制:多任务操作系统需要有效地管理多个进程的并发执行。通过跟踪进程状态,操作系统可以协调不同进程的执行,避免冲突或竞争。
- 状态恢复与调度:进程在执行时可能会被挂起、等待或终止。进程状态帮助操作系统在合适的时机恢复、暂停或终结进程,确保系统正常运行。
linux几种进程状态
-
R - Running(运行中):该进程正在使用CPU,或者在就绪队列中等待CPU调度。
-
S - Sleeping(睡眠中):进程处于睡眠状态,通常表示它正在等待某些事件的完成或资源的释放,例如等待I/O操作(磁盘读写、网络数据、用户输入等)。
-
D - Uninterruptible-Sleep(不可中断的睡眠状态):进程在等待某些硬件事件,无法被信号中断。
-
X - Dead(死亡):这个状态在某些版本的
ps
工具中表示进程已经死亡(已被终止)。 -
T - Stopped(停止):进程已被停止,通常是通过信号(如
SIGSTOP
)暂停或挂起的状态。 -
Z - Zombie(僵尸):进程已经结束执行,但仍然保留在进程表中,等待父进程读取其退出状态。
进程状态
R状态
R状态 表示进程的"运行"状态,具体来说,是指进程正在运行或者准备运行的状态。这个状态包括了两种可能的情况:进程正在执行(在CPU上运行),或者进程处于就绪状态,等待操作系统调度器分配CPU资源。
R状态的详细解释:
含义:进程被操作系统的调度器选中并分配了CPU时间片,进程正在执行其指令。
常见场景:
- 进程获得了CPU时间片,正在执行其代码。
- 该状态下的进程正在占用CPU资源,进行计算任务、处理数据或执行其他操作
时间片(Time Slice)是操作系统在进行进程调度时所分配给每个进程的执行时间。每个时间片是一个固定的时间段,操作系统通过轮流分配时间片给各个进程,从而实现进程的多任务并发执行。
demo:
cpp
# 执行代码
#include<iostream>
#include<unistd.h>
#include <sys/types.h>
using namespace std;
int main()
{
while(true)
{
cout<<" I am a prrocess" <<"pid: " <<getpid()<<endl;
sleep(1);
}
return 0;
}
利用ps
查看进程
bash
ps ajx | head -1 ; ps ajx | grep process
在进程状态属性为什么是S(睡眠状态),我们在执行进程的时候,为什么不是R(运行状态)
- S状态表示进程正在等待某些外部事件的发生,通常是I/O操作。
执行流程:
-
等输入流写入到缓冲区。
-
操作系统将数据从缓冲区写入终端设备。
-
在写入过程中,
cout
命令可能会进入S
状态,等待I/O操作(比如将数据从内存写入终端屏幕)。 -
当I/O操作完成后,
cout
命令恢复执行并退出。
**简而言之:**由于CPU运行速度太快,一直在等硬件设备,所以是S状态
cpp
//执行代码
#include<iostream>
#include<unistd.h>
#include <sys/types.h>
using namespace std;
int main()
{
while(true);
return 0;
}
再次利用ps
查看进程
- 这次没有IO操作就不存在等待的IO的过程,所以进程就状态就是R(运行状态)
S状态
在Linux操作系统中,进程的S状态 代表进程处于睡眠状态(Sleep) ,通常也称为可中断睡眠状态(Interruptible Sleep) 。处于S状态的进程正在等待某个事件或资源的完成,例如等待I/O操作、等待信号或者等待锁释放等。该状态下的进程不会占用CPU资源,直到它的等待条件得到满足,并被唤醒。
S状态的详细解释:
含义:
- S 表示进程正在等待某个事件或资源的完成,进入了睡眠状态。这是一个可中断的睡眠状态,意味着进程可以被外部信号中断。
- 在S状态下,进程会进入阻塞队列并挂起,直到所等待的条件满足(如I/O操作完成、资源可用等)或进程接收到信号(例如终止信号、暂停信号等)而被唤醒。
- S状态通常发生在进程发起I/O操作(如读取文件、网络数据等)时,或者等待其他进程、信号或资源
常见场景:
- 等待I/O操作(磁盘I/O、网络I/O等)
- 等待外部信号、事件或进程间通信
- 等待资源(如内存、锁、硬件设备等)
- 等待系统调用完成(如
read()
、write()
等)
demo:
cpp
// 执行代码
#include<iostream>
#include<unistd.h>
#include <sys/types.h>
using namespace std;
int main()
{
while(true)
{
cout<<" I am a prrocess" <<"pid: " <<getpid()<<endl;
sleep(1);
}
return 0;
}
- 这本质上就是一种等待IO的行为。
执行代码,ps
监控
bash
ps ajx | head -1 ; ps ajx | grep process
等待资源:
cpp
// 执行代码
#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include<string>
using namespace std;
int main()
{
string s;
while(true)
{
cin >> s;
}
return 0;
}
执行代码,ps
监控
bash
ps ajx | head -1 ; ps ajx | grep process
- 在此处依旧是S状态,无论是等待系统资源还是外部资源都是等待某种资源。
- CPU在执行进程的时候由于缺少资源,让进程在wait_queue(等待队列)中等待的这个过程就是一种(S)睡眠状态。
D状态
在Linux操作系统中,D状态 (不可中断睡眠状态,Uninterruptible Sleep)表示进程处于阻塞状态 ,但与S状态不同,进程处于D状态时不能被外部信号中断。这通常发生在进程等待某些硬件资源或进行阻塞I/O操作时。进程会一直处于D状态,直到资源或事件准备就绪为止。
**含义:**是进程在等待某些特定资源(通常是硬件资源或底层I/O设备)时的状态。
D状态的特点
- 不可中断 :与S状态(可中断睡眠状态)不同,D状态下的进程不能被信号中断,无法通过外部操作(如发送
SIGKILL
)来强制终止。 - 通常是硬件等待:进程通常会在等待底层硬件设备(如磁盘、网络、文件系统等)响应时进入D状态。例如,进程可能正在等待磁盘设备返回数据,或者在等待硬件设备准备好进行某个操作。
- 阻塞I/O操作:进程在执行一些可能导致长时间等待的I/O操作时,可能会进入D状态,直到I/O操作完成。例如,读取磁盘上的大文件,或等待网络硬件返回数据。
3. 进程进入D状态的常见场景
- 等待硬件I/O操作完成:
- 磁盘I/O:进程在进行磁盘读写时,如果操作系统没有足够的缓冲区或磁盘设备忙碌,进程可能会进入D状态,直到磁盘操作完成。
- 网络I/O:例如,进程等待从网络接口接收到数据,或者等待将数据发送到网络。在某些情况下,进程会在等待网络设备准备好进行数据传输时进入D状态。
Z状态
Z状态也叫做僵尸进程
- 僵尸进程 是子进程已完成执行(死亡)但其父进程尚未读取子进程的退出状态。
- 在
ps
输出中,僵尸进程的状态标识为Z
。 - 僵尸进程不会占用系统资源(如 CPU、内存),但仍保留进程 ID(PID)。
Z状态的特点
内存占用
- 僵尸进程不占用内存或 CPU,因为它已经结束运行。
- 它只占用一个 进程 ID (PID) 和少量的系统资源来记录进程退出信息。
进程表中保留
- Z 状态进程的条目会保留在进程表中,直到其父进程通过
wait()
系列系统调用清理它。 - 如果父进程没有处理子进程的退出状态,僵尸进程将一直存在。
进程进入Z状态的常见场景
cpp
#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include<string>
using namespace std;
int main()
{
pid_t id = fork();
while(true)
{
if(id == 0)
{
cout<<"我是子进程"<<getpid()<<endl;
sleep(3);
}
else
{
cout<<"我是父进程"<<getpid()<<endl;
sleep(3);
}
}
return 0;
}
上面的进程执行后会创建两个进程,然后杀死子进程
cpp
kill -9 <pid> // 杀死进程
执行ps
指令, 进程进程查看
bash
ps ajx | head -1 ; ps ajx | grep process
- 观察上面的进程状态变为了Z状态。
如何造成僵尸进程
-
子进程创建: 父进程通过调用
fork()
创建一个子进程。 -
子进程执行: 子进程完成自己的任务并退出,进入
EXIT_ZOMBIE
状态。 -
系统通知父进程: 子进程退出时,操作系统会向父进程发送
SIGCHLD
信号,告知其子进程已终止。 -
父进程未处理:
- 如果父进程未对
SIGCHLD
信号做出响应,也未调用wait()
或waitpid()
,子进程的资源无法被完全释放。 - 子进程的状态信息仍保留在进程表中,导致该进程成为僵尸进程。
僵尸进程的危害
- 占用系统资源
- 系统不稳定
- 潜在的安全隐患
- 干扰父进程逻辑
孤儿进程
有了僵尸进程,就存在孤儿进程
**孤儿进程(Orphan Process)**是指一个进程的父进程在其运行期间终止,但子进程仍在继续运行。孤儿进程由操作系统的 init
进程(PID 为 1)接管,init
会成为孤儿进程的新父进程,负责清理它在结束后的资源。
孤儿进程的特点
- 父进程已终止: 孤儿进程的直接父进程已经退出。
- 被
init
进程接管: 系统会自动将孤儿进程的父进程指向init
,由其负责清理子进程的资源。 - 进程状态正常: 孤儿进程仍在运行,不会进入僵尸状态。
进程孤儿状态的场景
cpp
#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include<string>
using namespace std;
int main()
{
pid_t id = fork();
while(true)
{
if(id == 0)
{
cout<<"我是子进程"<<getpid()<<endl;
sleep(3);
}
else
{
cout<<"我是父进程"<<getpid()<<endl;
sleep(3);
}
}
return 0;
}
杀死父进程
bash
kill -9 <pid> // 杀死进程
执行ps
指令, 进程进程查看
bash
ps ajx | head -1 ; ps ajx | grep process
- 查看到子进程
PPID
是1.
孤儿进程的产生原因
- 父进程崩溃或异常退出: 父进程因错误、信号或其他原因终止,导致其子进程成为孤儿进程。
- 父进程有意终止: 某些设计模式下,父进程会主动退出,将子进程交给
init
进程接管。例如:- 一些守护进程的实现会通过
fork()
两次来确保子进程独立运行。
死父进程**
- 一些守护进程的实现会通过
bash
kill -9 <pid> // 杀死进程
执行ps
指令, 进程进程查看
bash
ps ajx | head -1 ; ps ajx | grep process
[外链图片转存中...(img-t7Y5ceQx-1734353587109)]
- 查看到子进程
PPID
是1.
孤儿进程的产生原因
- 父进程崩溃或异常退出: 父进程因错误、信号或其他原因终止,导致其子进程成为孤儿进程。
- 父进程有意终止: 某些设计模式下,父进程会主动退出,将子进程交给
init
进程接管。例如:- 一些守护进程的实现会通过
fork()
两次来确保子进程独立运行。
- 一些守护进程的实现会通过