前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Linux】进程状态&&僵尸进程和孤儿进程&&阻塞、挂起和运行

【Linux】进程状态&&僵尸进程和孤儿进程&&阻塞、挂起和运行

作者头像
zxctscl
发布2024-04-10 09:57:57
2590
发布2024-04-10 09:57:57
举报
文章被收录于专栏:zxctscl个人专栏zxctscl个人专栏

1. 前言

上一篇博客中提到 【Linux】进程初步理解,这次继续来分享与进程有关的知识。

2. Linux的进程状态

Linux的进程状态就是struct task_struct内部的一个属性。 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。 下面的状态在kernel源代码里定义:

代码语言:javascript
复制
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):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

2.1 S状态

检测一下这些状态: 先写一个测试代码:

代码语言:javascript
复制
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4
  5 int main()
  6 {
  7
  8   while(1)
  9   {
 10     printf("I am a process,pid:%d\n",getpid());
 11   }
 12   return 0;
 13 }

运行代码:发现进程都是出于s状态:

printf在显示器上打的时候,根据冯诺依曼特体系结构,显示器是一个外设,所以CPU在跑当前程序时,要把数据写到内存中,然后刷新到外设上,但是不能保证,每一次打印的时候显示器都是就绪的。相比较CPU来讲,大部分时间这个进程都在等待设备资源是否就绪,如果资源不就绪,当前进程就一直出于S状态。

把代码在printf之前先休眠10秒:

此时发现进程一直出于S状态,可以直接ctrl+c把处于S状态的进程终止掉:

把S的这种状态叫做可中断睡眠,就是处于睡眠状态,依旧可以被外部信息随时打断。

2.2 R状态

那么把代码里面的printf给注释了:

此时进程都是R状态:

2.3 T/t状态

在kill命令中的19号命令,让进程暂停:

直接使用:

代码语言:javascript
复制
kill -19 pid

此时进程就处于T状态

要想让暂停的进程继续运行起来就用18号信号

此时进程又重新运行起来:

但是此时是在后台运行的,要想终止进程,只能使用kill -9。 暂停在之间调试的时候就已经用到了。

打开Makefile加-g选项

代码语言:javascript
复制
  1 testStatus:testStatus.c
  2   gcc -o $@ $^ -g
  3 .PHONY:clean
  4 clean:
  5   rm -f testStatus

在10行打一个断点,然后查看一下进程的运行状态:

遇到断点,进程就暂停下来。

2.4 D状态

D状态是Linux系统比较特有的状态。

D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。

操作系统杀进程的时候时毫无类别的去杀,在写入关键数据的进程是不能被杀掉的,所以操作系统规定,凡是进程在就行数据IO,在等待外设,像磁盘资源时,把状态设为D状态。 D状态不可被杀,深度睡眠,不可中断睡眠,就是一种sleep状态。

消除D状态:1. 让进程自己醒来;2. 重启–断点

3. 僵尸进程和孤儿进程

3.1 僵尸进程

Linux中一个进程的退出,它会将自己的退出信息保留在自己的PCB中。如果不读取PCB中的进程退出消息,那么进程就一直不释放,一般会释放掉代码和数据,但PCB的内核数据结构是一直存在的,直到将来对进程进行等待;如果不等待,那么进程就一直出于僵尸状态。如果读取了这个进程的退出信息或者等待了,那么这个进程才会变成X,进而将进程的信息全部释放。

为了测试重新写一个父子进程代码:

代码语言:javascript
复制
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4
  5 int main()
  6 {
  7
  8   pid_t id=fork();
  9   if(id==0)
 10   {
 11     //child
 12     int cnt=5;
 13     while(cnt)
 14     {
 15       printf("I am a child,cnt:%d,pid:%d\n",cnt,getpid());
 16       sleep(1);
 17       cnt--;
 18     }
 19
 20   }
 21   else{
 22     //parent
 23     while(1)
 24     { printf("I am parent,running always,pid:%d\n",getpid());
 25       sleep(1);
 26       }
 27   }
 28   return 0;
 29 }

来看一下这个状态:

Z状态:已经运行完毕,但是需要维护自己的退出信息,在自己的进程task_struct会记录自己的退出信息,未来让父进程读取。 如果没有父进程读取,僵尸进程就会一直存在。 如果对僵尸进程一直不回收,就会引起内存泄漏问题,操作系统会调用waitpid来进行进程状态的改变,变为X,再由操作系统进行释放。

一个进程已经出于僵尸了,就不能kill,无法杀掉已经死掉的进程。

3.2 孤儿进程

如果一个进程在运行的时候,它的父进程先退出了,那么这个进程就是孤儿进程。

来代码看看:

代码语言:javascript
复制
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4
  5 int main()
  6 {
  7
  8   pid_t id=fork();
  9   if(id==0)
 10   {
 11     //child
 12     int cnt=5;
 13     while(cnt)
 14     {
 15       printf("I am a child,cnt:%d,pid:%d\n",cnt,getpid());
 16       sleep(1);
 17 
 18     }
 19 
 20   }
 21   else{
 22     //parent
 23     int cnt=5;
 24     while(cnt)
 25     { printf("I am parent,running always,pid:%d\n",getpid());
 26       sleep(1);
 27       cnt--;
 28     }
 29   }
 30  // while(1)
 31  // {
 32  //   sleep(1);
 33 //    printf("I am a process,pid:%d\n",getpid());
 34 //  }
 35   return 0;
 36 }

来看看现象:

父进程如果先退出,子进程就会变成孤儿进程, 而孤儿进程一般会被1号进程(OS本身)进行领养。 为了保证孤儿进程正常被回收,孤儿进程会被操作系统领养。

可以直接kill掉孤儿进程:

在之前在Linux上写的代码,怎么出来没有关系过僵尸呢?或者内存泄漏? 因为直接在命令行中启动的进程,它的父进程是bash,bash会自动回收新进程的Z。

4. 进程的阻塞、挂起和运行

在网上找的一张进程状态图:

终止状态就等价于Z状态和X状态。

4.1 运行

进程运行一般在CPU上运行。 进程=task_struct+进程的代码和数据

每一个进程都有task_struct,为了对当前所有的进程进行管理,用链表将它们链接起来。而每一个CPU都会有一个运行队列struct runqueue,要运行进程,就得将进程放入运行队列struct runqueue中。从此CPU要运行已经进程,就在运行队列的头部取出一个进程,然后把相关的代码和数据拿到CPU寄存器中,进而就可以调度这个进程了。 一般而言一个进程被放到CPU上这个进程状态就是R,但是大部分教材中说的是进程在运行队列中,该进程的状态就是R状态,这里意思就是进程已经准备好了,可以随时被调度。

一个进程一旦持有CPU,会一直只运行这个进程吗? 不会,进程基于时间片进行轮转调度的。(而Linux中并不是以这种方法调度的,在之后的博客中会提到,请多多关注。) 让多个进程以切换的方式进程调度,在一个时间段内同时得以推进代码,就叫做并发。

把任何时刻,都有多个进程在真的同时运行,叫做并行。

4.2 阻塞状态

在C语言中用过一个scanf,如果不往里面输入数据,会一直处于什么状态?

来看看代码:

代码语言:javascript
复制
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 
  5 int main()
  6 {
  7
  8    int a=0;
  9
 10    scanf("%d",&a);
 11
 12    printf("a=%d\n",a);

阻塞状态就相当于S状态或者是D状态。

等待:等待键盘资源是否就绪,键盘上有没有被用户按下按键,按键数据交给进程:

操作系统是软硬件资源的管理者。 进程本身就是软件。 堆硬件的管理也是先描述在组织。

操作系统里面有一个结构体对设备就行管理,每一个结构体里面包含设备的状态,种类,还有指向其他设备的指针。

在等待说明进程并没有被调度,说明进程并不在运行队列中。

如果还有进程要等待键盘资源,就把对应的进程从运行队列放在等待队列里面。拿到对应的资源后,就回到运行队列里面,这个过程一般叫唤醒。 不是只有CPU才有运行队列,各种设备都有自己的等待队列。

阻塞和运行的状态变化,往往伴随进程PCB被连入到不同的队列中。

4.3 挂起

操作系统在运行进程的时候内存时比较吃紧的,一旦进程出于阻塞状态,那么就意味着当前进程不会被调度,这个进程的代码和数据就不会被访问,此时就会把这个进程的代码和数据唤出到磁盘上。那么曾经代码和数据占用的空间就空出来了,一旦获取到相应的资源,又会被唤起。

这个进程的PCB还在内存中,只是它的代码和数据在磁盘的swap分区,此时把这种状态叫做阻塞挂起。 这样操作系统就会更合理使用内存资源。

在用户层是感知不到的。 频繁的换入换出,会导致效率问题。这个是牺牲效率换取空间。

一般swap分区的大小不会太大,为了减少操作系统频繁的使用swap带来效率问题。

4.4 进程切换

CPU内部会有很多寄存器。 函数调用返回的临时变量,就用到了寄存器。

当一个进程被CPU调度的时候,CPU的寄存器中会保存当前进程的临时数据。如果这个进程的时间片到了,那么就会把这个进程从CPU上剥离下来,把下一个进程放上去。如果想要把这个进程恢复,就得保存上下文,然后CPU运行到这个进程又继续执行。

CPU内部的所有寄存器中的临时数据,叫做进程的上下文。

进程在切换,最重要的一件事就是:上下文数据的保护和恢复。

CPU内的寄存器:寄存器本身是硬件,具有数据的存储能力,CPU的寄存器硬件只有一套。 CPU内部的数据,可以有多套,有几个进程,就有几套和该进程对应的上下文数据。 所以寄存器!=寄存器内容

有问题请指出,大家一起进步!!!

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
  • 2. Linux的进程状态
    • 2.1 S状态
      • 2.2 R状态
        • 2.3 T/t状态
          • 2.4 D状态
          • 3. 僵尸进程和孤儿进程
            • 3.1 僵尸进程
              • 3.2 孤儿进程
              • 4. 进程的阻塞、挂起和运行
                • 4.1 运行
                  • 4.2 阻塞状态
                    • 4.3 挂起
                      • 4.4 进程切换
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                      http://www.vxiaotou.com