Linux 下进程的状态

发布于:2024-11-28 ⋅ 阅读:(13) ⋅ 点赞:(0)

操作系统中常见进程状态

在操作系统中有六种常见进程状态:

  1. 新建状态: 进程正在被创建. 此时操作系统会为进程分配资源, 如: 内存空间等, 进行初始化
  2. 就绪状态: 进程已经准备好运行了, 只需要等待被调度, 获取 CPU 资源就可以执行了, 操作系统中可能同时存在多个进程处于就绪状态, 都在排队等待获取 CPU 资源
  3. 运行状态: 进程获得了 CPU 资源, 正在执行指令
  4. 阻塞状态: 进程因为需要等待某个条件而进行暂停执行
  5. 终止状态: 进程已经完成了任务或者因为一些错误结束了运行, 操作系统会回收进程占用的资源.
  6. 挂起状态: 挂起是一种特殊操作, 当系统内存资源紧张时, 一些暂时没有运行的进程会被暂时交换到外存中挂起.

运行状态

CPU 会维护一个运行队列.
CPU 会从这个队列中寻找数据来进行处理.

当一个进程被放在了运行队列中, 那么这个进程的状态就是运行状态.
无论这个基础是否正在被处理.

阻塞状态

进程因为需要等待某个条件而进行暂停执行

进程在运行过程中或多或少都会去访问一些操作系统的资源: 如硬盘里的数据, 网卡接受的数据 ...

最常见的: 我们在学习 C 或 C++ 时, 都写过从键盘上获取两个数据, 然后打印出这两个数据之和.
当我们这个程序运行起来之后, 就会在控制台中要我们输入数据, 如果我们一直不输入数据, 那么这个程序就不会向后执行. 这就是一种阻塞状态, 进程在一直在等待键盘的资源.

那么操作系统是怎么知道, 哪个进程需要键盘的数据的呢?

我们之前了解到: 操作系统是管理软硬件资源的, 并且是通过软硬件的信息来了解软硬件对应的状态, 从而管理. 

键盘也是一个硬件资源, 所以操作系统也会收集键盘的信息, 存放在一个 struct 结构体中.

像键盘, 硬盘这样的设备的 结构体中, 都会维护一个等待队列

需要这个设备资源的进程都会被链入这个队列中,

这样当资源来临时, 就可以将资源给到对应的进程

 

那么进程状态从 运行状态 到 阻塞状态, 都经历了什么:
将进程的 PCB 从运行队列移动到设备的等待队列, 将状态修改为阻塞状态. 自此状态的修改完成.

进程状态变化本质: 

  1. 更改进程 PCB 中的 status 字段
  2. 将进程 PCB 链入到等待队列中

挂起状态

挂起又分为:

  • 就绪挂起
  • 阻塞挂起

这两种状态之前的区别就是: 进程在挂起之前的状态.

当相同的内存严重不足的时候: 
就绪状态 和 阻塞状态的 进程此时因为资源没有就绪, 所以都还没有调度.

与其放在内存中继续占用内存, 不如先将这些进程的代码和数据弄到硬盘中,

为操作系统腾出一部分空间.

在硬盘中, 这部分用来存储挂起进程代码和数据的空间, 称为 swap分区.
swap分区通常很小, 这个部分专门用与这种情况.

问题: 频繁的和硬盘这种外设交互, 那么操作系统的效率不就变得很低? (相较于 CPU 访问内存, 访问硬盘这样的外设, 速度是非常慢的)

此时的操作系统正处于内存严重不足的情况, 如果再不腾出一部分空间, 可能下一秒就会直接宕机了.

此时, 操作系统更关心的是能否存活下去, 而不是效率, 如果操作系统直接宕机, 那么所有的进程都会直接结束, 哪还谈什么效率.

以上说的是通用的状态, 在 Linux 中进程的状态又有些不同.

 Linux中六种常见状态

  1. R 运行状态 (running)
  2. S 睡眠状态 (sleeping), 也称为可中断睡眠状态. 意味着进程在等待事件的完成
  3. D 磁盘休眠状态 (Disk sleep), 也可称为不可中断睡眠状态.
  4. T 停止状态 (stopped) 通过发送 SIGSTOP 信号给进程来停止 (T) 进程, 这个被暂停的进程可以通过接收 SIGCONT 信号, 让进程运行起来
  5. X 死亡状态 (dead): 表示一个进程已经完成其执行并即将被销毁. 这个状态只是一个返回状态,意味着进程已经结束其生命周期,但还没有被完全清除出系统. 这个状态下的进程不会在任务列表中出现. 而且这个状态非常短暂, 几乎不可能通过 ps 命令捕捉到.
  6. z 僵尸状态: 僵尸状态(Zombie State)是一种特殊的进程状态,它表示一个进程已经完成执行,但其父进程尚未回收其终止状态

 R 和 S状态

我们运行下面这段程序

#include <iostream>
#include <unistd.h>
int main()
{
    while(1)
    {
        std::cout << "hello world" << std::endl;
    }
    return 0;
}

 使用 ps命令观察 ./a.out 这个进程.

 S+: 中的 + 号和前后台进程有关. 这个在文章后面来讲解.

我们会发现: 这个正在运行的进程没有处于 R 状态, 而是处于 S 状态. 为什么?

上面说过, 访问外设的速度非常慢, printf 会将内容打印到屏幕上,

屏幕就是一种外设, 与屏幕交互的速度很慢, 程序的大部分时间内,

都处于 阻塞状态, 在等待外设将内容显示到屏幕上.

真正处于运行的事件很少.

当然, 我们只需要将 cout 语句去掉, 一直循环, 那么就能观察到, 程序一直处于 R 状态

S 和 D状态

S 和 D 都是睡眠状态, 这两个睡眠状态有什么差别?
S: 可以被比作浅度睡眠
D: 可以被比作深度睡眠

当我们从 QQ 上下载一个文件时, 这个文件是需要被拷贝到硬盘中的

如果这个文件非常的大, 而此时操作系统的内存又不足了, 需要杀死一些进程,

腾出一些空间, 如果此时 QQ 就被杀死了, 那么文件的下载也就失败了,

这些数据可能是非常重要的, 而操作系统杀死了进程, 导致我的文件丢失了,

这是很麻烦的事情.

所以, 操作系统就提供了一个 D 状态, 当进程处于 D 状态时, 操作系统就不会杀死这个进程.
因为 D 代表此时进程正在和磁盘交互, 为了不如数据丢失, 不要杀掉这个进程.

结论: 

S (sleeping): 浅度睡眠, 可以被终止

D (disk sleep): 深度睡眠, 防止像硬盘写入数据时被杀掉而专门创立的状态

 

Z 僵尸状态

僵尸状态的出现和父子进程有关.

僵尸状态: 

僵尸状态是一种特殊的进程状态, 它表示一个进程已经完成执行, 但其父进程尚未回收其终止状态.

我们知道创建子进程, 是为了去完成任务, 那么这个任务是否完成, 完成的结果如何等信息, 都应该要告诉父进程. 

当子进程结束后, 而父进程没有没有接收子进程返回的信息, 那么子进程就不会被操作系统回收, 这时子进程就加入了僵尸状态.

前台/后台进程

在上面观察进程的 R / S 状态时, 我们可以看到进程的状态是 R+ / S+.
这个 "+" 是什么意思?

这个 "+" 代表这个进程是在前台运行的.
那什么叫前台进程, 什么叫后台进程?

前台进程: 可以在终端直接交互
后台进程: 不会接收终端的输入, 要结束后台进程通常使用 kill 命令.

代码:

#include <iostream>
#include <unistd.h>
int main()
{
    while(1)
    {
        sleep(1);
        std::cout << "hello world" << std::endl;
    }
    return 0;
}

前台进程: 运行时无法使用bash外壳的指令并且可以被CTRL C强制终止掉

可以看到, 当进程在前台运行时, 我向终端输入 ls 命令, 并没有打印出当前目录下的文件.
此时我们只能和这个前台进程进行交互, 无法向命令行解释器发送指令.

后台进程: 运行时可输入指令,不能被CTRL C掉, 只能使用kill指令来杀掉进程.

在命令后面加上一个 &

./a.out &

可以看到, 我输入 ls 指令依然可以查询当前目录下的文件. Ctrl C 也无法终止这个进程