Linux:进程状态

目录

1.Linux内核关于进程状态的源代码

[2. 运行状态](#2. 运行状态)

[3. 阻塞状态](#3. 阻塞状态)

[4. 挂起](#4. 挂起)

5.Linux中的进程状态

[5.1 睡眠状态](#5.1 睡眠状态)

[5.2 暂停状态](#5.2 暂停状态)

[5.3 僵尸进程与孤儿进程](#5.3 僵尸进程与孤儿进程)


我们在学习进程状态时,老师只是简单的让我们记住下面这张图

1.教材中进程操作系统的进程状态

那么这些进程状态到底是什么意思呢?本篇文章将具体讲解上面教材中提到的进程状态在linux中的体现。

1.Linux内核关于进程状态的源代码

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。

下面的状态在kernel源代码里定义:

cpp 复制代码
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
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 */
};

简单介绍:

  • R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态,这个进程即将被销毁。

进程状态,就是PCB中的一个字段,就是结构体中的一个变量,这些进程状态类型就是通过#define 出的宏常量

所谓状态变化,就是在PCB(Linux叫 task_struct)中修改代表进程状态变量的值,然后将PCB链入不同的队列中,比如运行队列,等待对列等。所以说所有的过程,都只与进程的PCB有关,与代码和数据无关。

2. 运行状态

创建一个进程就是将代码和数据拷贝进内存,然后为这个进程创建PCB,PCB内有指针指向要执行的代码。所以进程 = 内核创建的PCB + 代码和数据。

每个CPU都有一个运行队列,通过双向链表将就绪的PCB连接起来,只是这个进程的PCB在运行队列中,或者正在被CPU执行,都是运行状态。

教材中的就绪和执行在Linux中统称为运行状态:R运行状态(running)。

查看运行状态:

这里写一个死循环让代码一直运行:

通过 ps axj 来查看所有的进程通过grep筛选名称,head -1显示第一行:

可以看到STAT就是进程状态,R+。下面那个进程是grep指令的进程。+号的意思是前台进程,这时命令行不会执行其他命令,可以输入ctrl+c终止程序,后台进程在 ./文件名 后加&即可。

后台进程不能通过ctrl+c 关闭,前台可以执行其他命令,只能通过kill -9 pid 杀掉这个进程。

3. 阻塞状态

我们代码中一定会或多或少访问系统中的软件和硬件资源,比如磁盘,键盘,网卡,如果程序要求我们输入,但是我们键盘上不输入,也就是要访问的资源没有就绪,所以代码就无法向后进行,这时就会出行阻塞状态。

我们知道操作系统并不是直接管理软件和硬件,而是管理的它们的信息,通过先描述,创建关于软件(进程)和硬件的信息的结构体,通过链表组织起来,即先描述,再组织,可以更好的管理相关的设备。

当出现访问设备出现阻塞时,我们就需要把进程的PCB链接到要访问设备的等待队列中,这里要注意的是:不是PCB不是仅仅存在一个队列中,他可能同时存在多个队列中,这是因为PCB的内部存在指针结构体,它是指向下一个和上一个PCB内部的指针结构体,而这样的结构体有多个。

注意:

操作系统中,会存在非常多的队列,运行队列,等待硬件的设备等待队列等。一个PCB可以存在多个对类中,所有系统内的进程是用双链表链接起来的

查看阻塞状态:

通过scanf 让进程访问键盘:

我们不输入数据,这个进程就会一直处于阻塞状态:

Linux下显示为S+,+表示前台进程。

4. 挂起

如果一个进程当前被阻塞了,注定了,这个进程在它所等待的资源没有就绪的时候,该进程是无法被调度的。如果此时,恰好OS内的内存资源已经严重不足了,怎么办??操作系统OS会将内存数据进行置换到外设磁盘上,针对所有阻塞进程,全部都由OS自动执行!这是进程状态就是挂起。

不用担心慢的问题,因为这个是必然的,现在主要关心的是让OS继续执行下去。

磁盘中会有一块swap分区,OS内的数据会被交换到这里,如果当进程被OS调度,曾经被置换出去的进程代码和数据,又要重新被加载到内存中。

这个状态一般是不容易看到的,要OS内存资源不足时才能看到,这里就不做展示了。

5.Linux中的进程状态

5.1 睡眠状态

  • S睡眠状态(sleeping):休眠状态,浅度睡眠,可以被终止,会对外界信号做出反应。
  • D磁盘休眠状态(Disk sleep):休眠状态,深度睡眠

上面两个都是阻塞状态。S状态上面已经讲过了,这里讲一下D状态。

**D磁盘休眠状态是专门为磁盘设计的一种状态。**我们要知道,如果在内存资源不足的情况下,操作系统会通过杀掉进程,节约资源的。当一个进程的进程状态处在D状态时,表示这个进程不可以被杀掉,不管是通过kill命令还是OS,都是不行的。

这是为什么呢?因为当进程要向磁盘中写入数据时,会将数据交给磁盘,让磁盘写入,然后等待磁盘返回是否写入成功的信息,如果这时进程被杀掉,且磁盘中途写入数据失败,磁盘这时要把错误信息返回给进程,但找不到进程,磁盘会将数据丢掉,会造成数据丢失。为了防止这种状况出现,专门设计出了D状态,进程不能被杀掉,只能等待D状态结束。

我们用户一般是看不到D状态的,如果我们看到D状态,几乎计算机快要挂掉,因为还会有其他的进程等待访问磁盘。

5.2 暂停状态

  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。

我们在Linux状态中看到两个T,T/t,其实内核中两个都是T,那它们两个有什么区别呢?

我们先来了解一下kill 命令的显示信号,使用kill -l 查看

我们现在用到是下面三个:

  • kill -9 是杀掉一个进程
  • kill -18是让一个进程继续
  • kill -19是让进程暂停

我们写一个while(1);死循环然后运行

**这里T是普通的stopping 状态,使用kill -19强制进程暂停。**可以发现kill -18 后进程成为后台运行状态,结束的话要用kill -9 杀掉进程。

当我们使用gdp对进程调试追踪,进程遇到断点暂停时,这种T是(tracing stop)追踪暂停。它们都会显示T。

以上S,D,T状态在教材中统称为阻塞状态。

5.3 僵尸进程与孤儿进程

  • Z (zombie)僵尸进程,这个进程已经执行完任务,但PCB还没有被释放。

进程=内核PCB+进程的代码和数据,都要占据内存空间,进程退出的核心工作之一就是将PCB和自己的代码和数据释放掉!

为什么我们要创建一个进程??一定是因为父进程要子进程要完成某种任务!父进程怎么知道进程把任务完成的怎么样?所以进程在退出的时候,要有一些退出信息,表明自己把任务完成的怎么样!

当一个进程在退出的时候,退出信息会由OS写入到当前退出进程的PCB中可以允许进程的代码和数据空间被释放,**但是不能允许进程的PCB被立即释放!!**要让OS或者父进程,读取进程的PCB中的退出信息,得知子进程退出的原因!

进程退出了,但是还没有被父进程或者OS读取,OS必须维护这个退出进程的PCB结构!!此时,这个进程基本上已经算退出了,此时进程状态为:Z,僵尸状态!

如果一个进程Z状态了,但是父进程就是不回收它,PCB就要一直存在,父进程或者OS读取之后,PCB状态先被改成X状态,表示可以释放了,才会被OS释放。

如果我们一直不会回收,会造成内存泄漏,PCB一直占用系统资源。

僵尸进程查看:

cpp 复制代码
int main()    
{    
    pid_t id = fork();    
    if(id<0)    
        return 1;    
    if(id==0)    
    {    
        //子进程    
        int cnt = 5;    
        while(cnt)    
        {    
            printf("I am child,run times:%d\n",cnt--);    
            sleep(1);    
        }    
        printf("I am child, dead:%d\n",cnt--);    
        exit(2);//让一个正常的进程退出    
    }    
    else     
    {    
        //父进程    
        while(1)    
        {    
            printf("I am father,running any time!\n");    
            sleep(1);    
        }    
        //没有回收子进程,子进程会一直为Z状态    
        //回收操作以后文章讲解
    }                                                                      
    return 0;    
}     

因为父进程是死循环,不能停下,不能回收子进程,子进程就会出现Z 僵尸状态,称为僵尸进程。

孤儿进程

上面程序是子进程先运行结束,而父进程先运行结束后出现什么呢?

子进程的父进程先退出了,子进程要被领养,变成孤儿进程。

cpp 复制代码
int main()                                                           
{    
    pid_t id = fork();    
    if(id<0)    
        return 1;    
    if(id==0)    
    {    
        //子进程    
        while(1)    
        {    
            printf("I am child...\n");    
            sleep(1);    
        }    
    }    
    else    
    {    
        //父进程    
        int cnt = 5;    
        while(cnt)    
        {    
            printf("I am father,run times:%d\n",cnt--);    
            sleep(1);    
        }    
        printf("I am father, dead:%d\n",cnt--);    
        //父进程先退出,子进程还在运行    
        //bash会回收掉父进程    
        //不会管孙子进程    
    }    
    return 0;    
}   

可以发现子进程的PPID变成了1,被1进程领养,变成孤儿进程。那1号进程是什么呢?

我们可以使用top指令正在运行的进程的相关信息:

1号进程 systemd 就是操作系统。为什么要被领养呢,因为要防止孤儿进程没有进程接收返回值,导致PCB无法释放,造成内存泄漏。

本篇结束!

相关推荐
软件技术员29 分钟前
Let‘s Encrypt SSL证书:acmessl.cn申请免费3个月证书
服务器·网络协议·ssl
哎呦喂-ll40 分钟前
Linux进阶:环境变量
linux
耗同学一米八42 分钟前
2024 年河北省职业院校技能大赛网络建设与运维赛项样题四
运维·网络
Rverdoser42 分钟前
Linux环境开启MongoDB的安全认证
linux·安全·mongodb
PigeonGuan1 小时前
【jupyter】linux服务器怎么使用jupyter
linux·ide·jupyter
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
东华果汁哥1 小时前
【linux 免密登录】快速设置kafka01、kafka02、kafka03 三台机器免密登录
linux·运维·服务器
咖喱鱼蛋2 小时前
Ubuntu安装Electron环境
linux·ubuntu·electron
ac.char2 小时前
在 Ubuntu 系统上安装 npm 环境以及 nvm(Node Version Manager)
linux·ubuntu·npm