【Linux进行时】进程状态

【Linux进行时】进程状态_第1张图片

进程状态:

❓假设我们在上课,在B站上上课,请问我们的B站是不是一直运行呢?不是的!

❓假设我们同时打开了B站和PDF阅读器时,是怎么运行的呢?

每一个进程在CPU跑一会,再从CPU拿下来放上另外一个上去,周而复始,这种叫做分时操作系统

❓那就有一个问题,假设先用B站,为什么先将B站这个进程放上去,而不是PDF阅读器呢?

这就取决于进程状态!

思维导图:

【Linux进行时】进程状态_第2张图片

1.进程的状态

1.1三种基本运行状态

  • 运行状态

R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里

❓一个进程只要把自己放到CPU上开始运行了,是不是一直要到执行完毕,才把自己放下来?

不是,每一个进程都有一个叫做:时间片的概念! 其时间大概是在10 ms左右。所以并不是一个进程一直在执行,而是这多个进程在一个时间段内所有代码都会被执行 —— 这就叫做【并发执行】

所以呢这就一定会存在大量的进程被CPU放上去、拿下来的动作 —— 这就叫做【进程切换】

  • 并发and并行

并行:指的是两个或者多个事件同一时刻发生(可以类比为一个公路,三台车不同道同向行驶)

并发:指的是一段时间内有多个程序同时运行

  • 就绪状态

指的是进程已经处于准备好执行的状态,但是CPU没有空闲的,

  • 阻塞状态和挂起状态

有两个概念:阻塞和挂起!

阻塞:进程因为等待某种条件就绪 ,而导致的一种不推进的状态——进程卡住了——阻塞一定在等待某种资源

就相当于进程太多了,卡着一个进程,在这个进程来看就是在等待CPU调度(某种资源)

阻塞:进程等待某种资源就绪的过程

❓为什么阻塞呢?

进程要通过等待的方式,等具体的资源被别人是用使用完之后,再被自己使用。

❓什么是资源呢?资源=磁盘或者网卡显卡等各种外设(这是硬件,软件也有)

因为操作系统是通过先描述,后组织的方式来进行管理,假设用struct来代表这些外设用链表连接起来,

❓可以存在大量的进程吗?可以的❓要管理吗?先描述再组织

假设我们有一个进程task_struct要加载到cpu中,它在等待一个网卡资源

【Linux进行时】进程状态_第3张图片

CPU说你不能在我上面跑了,所以task_struct就添加到这个队列的尾部等待网卡这个资源

【Linux进行时】进程状态_第4张图片

也就是我们的task_struct在我们的列入到某种资源的队列当中,它就不会被CPU调度了吗,这不就是卡住了吗,不就是阻塞了吗!!!!

pcb可以被维护在不同的队列中的!

阻塞:阻塞就是不被调度——一定是因为当前进程需要等待某种资源就绪——一定是进程task_struct结构体需要再某种被OS管理的资源下排队

挂起:

【Linux进行时】进程状态_第5张图片

例子

假设task_struct是个下载任务,这里突然没网了,CPU说这个task_struct就不能跑了,task_struct就链接在网卡这个队列的中,这里就是阻塞了

操作系统通过自己的一套算法,把占有内存的,不被调度的进程的数据和代码交换到磁盘当中

磁盘那边放了一份交换后的代码和数据,这里的内存的代码和数据被释放,

在被再次调度前再把代码和数据换回来就可以了

【Linux进行时】进程状态_第6张图片

其中代码和数据由操作系统暂时将我们的从内存换到磁盘的过程中,此时这个进程就是挂起状态,全称叫做阻塞挂起状态

2.Linux进程状态

task_struct是一个结构体,内部会包含各种属性,包括状态(status)

为了研究 Linux 进程状态,我们把源码先拿出来看看:

/*
* 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):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

用0表示R,用1表示S,用2表示D,用4表示T,用8表示t,用16表示X,用32表示Z

  • 运行状态

❓进程只要是在R状态,就一定是在CPU上运行吗?不一定!

【Linux进行时】进程状态_第7张图片

所有运行的进程只要在运行的队列中排队就可以了,所以CPU调度进程只要去我们的队列中去挑进程就可以了

❓进程是什么状态?一般也看这个进程在哪里排队

进程状态查看我们可以用ps

这里我们也能拿 ps 指令来看进程的状态,我们开小窗输入:

ps axj | head -1 && ps axj | grep process | grep -v grep

【Linux进行时】进程状态_第8张图片

显然这里S是休眠状态

首先需要声明一点,状态后面跟加号,表示是一个 **前台进程,**你只需要知道的是,能够在键盘上 Ctrl+c 暂停的都可以叫前台进程。

其实我们CPU的运行速度都很快,我们看到这里它一直在打印,其实是显示器(外设)一直在打印,CPU一瞬间就完成了,所以状态是S

我们将test.c中printf注释掉,再试一下

【Linux进行时】进程状态_第9张图片

image-20230810165055831

我们发现这里变成了R状态了

所以这个进程不访问任何资源,只等你 CPU,只要你被运行期间不访问外设,就不会被阻塞。

不访问外设,那么死也会在等待队列里,一直在等待队列中,这就让 process 达成 R状态!

这里printf的本质就是向外设(网卡、显示器)打印消息,进程只要是R状态,并不直接代表进程正在运行,而代表该进程在运行队列中排队

【Linux进行时】进程状态_第10张图片

队列由操作系统自己维护

  • 睡眠状态

S状态——休眠状态,可中断休眠,它一直在等待某种资源,其实就是阻塞状态

image-20230915093710034

我们一般把 状态叫作 浅度睡眠,也叫做 可中断睡眠

  • 顾名思义,当进程处于 S 状态,它可以随时被唤醒。
  • 不仅仅是操作系统可以唤醒,你也可以唤醒,甚至你想杀掉它都行。
$ kill -9 [pid]

我们用另外一个例子来更直观感受S状态

#include 
 #include 
 int main(void)
 {
   int a=0;
  printf("输入a的值:");
  scanf("%d",&a);
  printf("a=%d\n",a);                                        
  return 0;                                                                          
 }   

【Linux进行时】进程状态_第11张图片

将该进程运行起来我们可以看到其是出于S+的状态,因为【shell】此时正在等待用户的输入,这个就对应到了我们上面所讲到的 阻塞状态

不仅如此,像我们一直在使用的bash,也可以算是一种【阻塞状态】,一直等待着我们去输入命令行,一旦有了的话它就进行读取

【Linux进行时】进程状态_第12张图片

  • 深度睡眠模式

这也是一种阻塞模式

"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */

这里D模式比S模式多一个disk,磁盘?那肯定跟磁盘有关系

一般而言,在 Linux 中,如果我们等待的是磁盘资源,我们进程阻塞所处的状态就是 D 状态。

与S状态类似,进程处于睡眠状态,但是此刻进程是不可中断的。不可中断,指的并不是CPU不响应外部硬件的中断,而是指进程不响应异步信号。
绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号的。否则你将惊奇的发现,kill -9竟然杀不死一个正在睡眠的进程了!于是我们也很好理解,为什么ps命令看到的进程几乎不会出现D状态,而总是S状态。

而TASK_UNINTERRUPTIBLE状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。

  • 暂停状态

T状态,暂停状态

我们先通过这个指令来了解一下,我们要使用的就是下面的18和19信号

kill -l

【Linux进行时】进程状态_第13张图片

进程暂停与进程休眠(阻塞) 没有关系,只是单纯不想让这个进程跑了。

比如有些进程在执行任务时,用户想让这个进程暂停一下,这其实很好理解。

比如看视频,听音乐,下载,都会有暂停。当你点击暂停的时候下载对应的代码就不跑了,此时这个进程你就可以认为是暂停状态。

kill -19 进程pid    暂停该进程
kill -18 进程pid    解除暂停

【Linux进行时】进程状态_第14张图片

kill -18 pid解除一下

  • 死亡状态

X状态,死亡状态

kill -9 PID

dead 代表死亡,所以 X状态对应的就是 死亡状态

这个没有什么好说的,X 状态的进程就代表死亡了,可以随时等待 OS 来收尸了。

  • 僵尸状态

当一个 Linux 中的进程退出的时候,一般不会直接进入X状态(死亡,资源可以立马被回收),而是进入Z状态

❓当进程退出后不直接变成X状态而是变成Z状态这是为什么呢?

当一个进程退出的时候,那最关心它的便是【父进程】。因为这个父进程费了很大的劲才将这个子进程fork出来,此时呢它突然挂掉了,那么此时父进程就必须去关心一下对应的子进程退出时的原因

在这个退出过程中,进程占有的所有资源将被回收,除了task_struct结构(以及少数资源)以外。于是进程就只剩下task_struct这么个空壳,故称为僵尸。
之所以保留task_struct,是因为task_struc t里面保存了进程的退出码、以及一些统计信息。而其父进程很可能会关心这些信息。

我们来造一个僵尸出来看看:

#include 
#include 
#include 
 
int main(void) {
    pid_t id = fork();
    if (id == 0) {
        // child
        int cnt = 5;
        while (cnt) {
            printf("我是子进程,我还剩下 %ds\n", cnt--);
            sleep(1);
        }
        printf("我是子进程,我已经变僵尸了,等待被检测\n");
        exit(0);
    }
    else {
        // father
        while (1) {
            sleep(1);
        }
    }
}

【Linux进行时】进程状态_第15张图片

❓如果长时间处于僵尸状态,会有什么结果呢?

如果没有人收尸,该状态会一直维护,该进程的相关资源 (task_struct) 不会被释放!内存泄露!一般必须要求父进程进行回收,如何回收的问题我们会在进程控制章节讲解。

❓为什么要main()的返回值呢?return 0;这个是进程退出码

如果-个进程退出了,立马X状态,立马退出,你作为父进程,有没有机会拿到退出结果呢? ? ? Linux当进程退出的时候,- -般进程不会立即彻底退出,而是要维持一个状态叫做Z, 也叫做僵尸状态— 方便后续父进程(OS)读取该子进程退出的退出结果!

只要父进程不退出,这个僵尸状态的子进程就一直存在。那么如果父进程退出了呢,谁又来给子进程“收尸”?
当进程退出的时候,会将它的所有子进程都托管给别的进程(使之成为别的进程的子进程)。托管给谁呢?可能是退出进程所在进程组的下一个进程(如果存在的话),或者是1号进程。所以每个进程、每时每刻都有父进程存在。除非它是1号进程。

1号进程,pid为1的进程,又称init进程。
linux系统启动后,第一个被创建的用户态进程就是init进程。它有两项使命:
1、执行系统初始化脚本,创建一系列的进程(它们都是init进程的子孙);
2、在一个死循环中等待其子进程的退出事件,并调用waitid系统调用来完成“收尸”工作;
init进程不会被暂停、也不会被杀死(这是由内核来保证的)。它在等待子进程退出的过程中处于S状态,“收尸”过程中则处于R状态。

  • 孤儿进程

如果父进程 一直存在,子进程退出了,父进程对子进程不管不顾,子进程退出后就要进入僵尸状态

如果有父子两个进程,因为一些问题父进程不存在了,子进程还没有退出呢?

myproc.c

 1 #include
    2 #include
    3 int main()
    4 {
    5   pid_t id=fork();
    6   if(id==0)
    7   {
    8     //child
    9     while(1)
   10     {
   11       printf("我是子进程:pid:%d,ppid:%d\n",getpid(),getppid());
   12       sleep(1);
   13     }
   14   }
   15   else
   16   {
   17     //parent
   18     int cnt=10;
   19     while(1)
   20     {
   21        printf("我是父进程:pid:%d,ppid:%d\n",getpid(),getppid());
   22        sleep(1);
   23        if(cnt--<=0)break;                                                                            
   24     }
   25   }
   26   return 0;
   27 }
              

make一下,没有问题

【Linux进行时】进程状态_第16张图片

这里的$@

和$^

$@代表冒号左侧的,红色的,目标文件

$^代表冒号右侧的,蓝色的,依赖文件

【Linux进行时】进程状态_第17张图片

while :;do ps ajx | head -1&&ps ajx |grep myproc | grep -v grep;sleep 1;echo "----------";done

【Linux进行时】进程状态_第18张图片

我们在旁边把程序运行起来

我们发现我们的父进程是5388 ppid 7666

子进程 5389 ppid5388

【Linux进行时】进程状态_第19张图片

后面父进程中止了,只剩下子进程

子进程是5389 ppid变成了1

【Linux进行时】进程状态_第20张图片

父进程这时候退出了那是不是应该处于僵尸状态呢?

这里我们为什么没有看到父进程处于僵尸状态呢?

我们在命令行中启动一个进程时,它的父进程是bash

父进程退出之后,它的父进程是bash(命令行解释器),所以这个父进程退出的时候,它的父亲帮他回收了

相当于是我死了,我爹bash帮我回收了

我们发现这个子进程的父进程死掉后,它给自己又找了个爹(1号进程),1好进程可以叫为操作系统

image-20230814152258685

image-20230814152355978

如果不领养会发生什么呢?

如果有一天这个子进程挂了,并且他没有父进程,他就会被操作系统遗忘,就一直处于一种占用操作系统内存空间的情况,也就是内存泄漏

在这里插入图片描述

你可能感兴趣的:(操作系统,Linux,linux,网络,网络协议)