操作系统实验 实验报告-2

1. 实验目的

在本实验中学习 Linux 操作系统的进程状态,并通过编写一些简单代码来观察各种情况下,Linux 进程的状态,进一步理解进程的状态及其转换机制。

2. 实验内容

2.1 Linux 进程状态及其相互转换

Linux 系统中的进程主要有以下六种状态:

  • TASK_RUNNING(可运行状态):

正在运行的进程或在可运行进程队列(run_queue)中等待运行的进程处于该状态。它实际上包含一般操作系统原理教材中所谓进程三种基本状态中的运行态和就绪两种状态。当 CPU 空闲时,进程调度程序只在处于该状态的进程中选择优先级最高的进程运行。Linux 中运行态的进程可以进一步细分为 3 种:内核运行态、用户运行态和就绪态。

  • TASK_INTERRUPTIBLE(可中断阻塞状态):

处于可中断阻塞状态的进程排成一个可中断阻塞状态进程队列,该队列中的阻塞进程在资源有效时,能被信号或中断唤醒进入到运行态队列。

  • TASK_UNINTERRUPTIBLE(不可中断阻塞状态):

不可中断指的是进程不响应信号。处于不可中断阻塞状态的进程排成一个不可中断阻塞状态进程队列。该队列中的阻塞进程,不可被其他进程唤醒,只有被使用 wake_up() 函数明确唤醒时才能转换到可运行的就绪状态。

  • TASK_STOP / TASK_TRACED(暂停状态):

当进程收到信号 SIGSTOP、SIGTSTP、SIGTTIN 或 SIGTTOU 时就会进入暂停状态。可向其发送 SIGCONT 信号,让进程转换到可运行状态。

  • TASK_DEAD - EXIT_ZOMBIE(僵死状态):

表示进程停止但尚未消亡的一种状态。此时进程已经结束运行并释放掉大部分资源,但父进程尚未收回其 PCB。在进程退出时,将状态设为 TASK_ZOMBIE,然后发送信号给父进程,由父进程再统计其中的一些数据后,释放它的 task_struct 结构。处于该状态的进程已经终止运行,但是父进程还没有询问其状态。

  • TASK_DEAD - EXIT_DEAD(退出状态):

处于此状态的进程即将被销毁,EXIT_DEAD 非常短暂,几乎不可能通过 ps 命令捕捉到。

Linux 中进程的状态转换过程如下图所示:

进程的状态转换过程

可以使用 ps 命令查看进程在系统中的状态。在 ps 命令的显示结果中,5 种字符分别代表 5 种不同的进程状态:

  • R(TASK_RUNNING):可执行状态或运行状态
  • S(TASK_INTERRUPTIBLE):可中断阻塞状态,可响应中断、接收信号(如 SIGKILL)
  • D(TASK_UNINTERRUPTIBLE):不可中断阻塞状态,只能响应中断
  • T(TASK_STOPPED / TASK_TRACED):暂停状态或跟踪状态
  • Z(TASK_DEAD / EXIT_ZOMBIE):退出状态,进程成为僵尸进程

注:在状态字符后面如果带+(如 S+),表示进程是前台运行,否则是后台运行。

2.2 观察进程状态

  1. 查看“运行”状态(R)

创建一个 C 程序,2-1.cpp,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <unistd.h>

int main()
{
int k = 0;
for (int i = 0; i < 1000000; i++) {
for (int j = 0; j < 1000000; j++) {
k++;
k--;
}
}
}

编译链接为 a 文件,后台运行该程序(后接 & 符号),并使用 ps 命令查看,命令如下:

1
2
3
gcc 2-1.cpp -o a
./a &
ps ax | grep ./a
  1. 查看“暂停”状态(T)

运行 ./a 进程,其进入 R 状态。使用 kill 命令,向 ./a 进程发送 SIGSTOP 信号,并使用 ps 命令观察其状态(进入了 T 状态)。

1
2
3
4
5
6
7
8
./a &
ps ax | grep ./a

kill -SIGSTOP [pid]
ps ax | grep ./a

kill -SIGCONT [pid]
ps ax | grep ./a
  1. 查看“可中断阻塞”状态(S)

创建一个 C 程序,2-2.cpp,代码如下:

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <unistd.h>

int main()
{
sleep(30);
return 0;
}

编译链接为 a 文件,后台运行该程序(后接 & 符号),并使用 ps 命令查看,命令如下:

1
2
3
gcc 2-2.cpp -o a
./a &
ps ax | grep ./a
  1. 查看“不可中断阻塞”状态(D)

创建一个 C 程序,2-3.cpp,代码如下:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <unistd.h>

int main()
{
if (vfork() == 0) {
sleep(30);
return 0;
}
}

编译链接为 a 文件,后台运行该程序(后接 & 符号),并使用 ps 命令查看,命令如下:

1
2
3
gcc 2-3.cpp -o a
./a &
ps ax | grep ./a
  1. 查看“僵尸”进程(Z)

创建一个 C 程序,2-4.cpp,在其中创建一个子进程,并让子进程迅速结束,而父进程陷入阻塞。即父进程进入 S 状态,子进程进入 Z 状态。代码如下:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <unistd.h>

int main()
{
if (vfork()) {
sleep(30);
}
}

编译链接为 a 文件,后台运行该程序(后接 & 符号),并使用 ps 命令查看(30 秒内),命令如下:

1
2
3
gcc 2-4.cpp -o a
./a &
ps ax | grep ./a

3. 分析与思考

分析为什么出现以上现象,并对其进行总结:什么时候出现运行状态、暂停状态、可中断阻塞状态、不可中断阻塞状态、僵尸状态?

出现不同状态原因:

  1. 可运行状态 R:

进程当前正在运行,或者正在运行队列中等待调度,当创建一个新进程,系统调用创建原语,该进程为就绪状态,或者进程获得 CPU 正在运行,处于执行状态。

  1. 暂停状态 T:

当进程收到信号 SIGSTOP、SIGTSTP、SIGTTIN 或 SIGTTOU 时就会进入暂停状态。可向其发送 SIGCONT 信号,进程转换到可运行状态。

  1. 可中断阻塞状态 S:

进程调用阻塞原语把状态改为阻塞状态,在资源有效时,能被信号或中断唤醒进入到运行态队列,进程处于阻塞状态,正在等待某些事件发生或能够占用某些资源。接收到信号唤醒呼叫,进程将转变为运行态,继续排队等待调度。

  1. 不可中断阻塞状态 D:

进程调用阻塞原语把状态改为阻塞状态,不可被其他进程唤醒,只有在其等待的事件(如 I/O 操作)完成时,进程才被显式地唤醒,转变为运行态,继续排队等待调度。

  1. 僵死状态 Z:

表示进程停止但尚未消亡的一种状态,进程已经结束但未释放进程控制块(PCB),处于该状态下的子进程没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,此时进程已经结束运行并释放掉大部分资源。


操作系统实验 实验报告-2
https://flowerdown.org/posts/20230522-205859
作者
Unrealfeathers
发布于
2023年5月22日
许可协议