【Linux进程状态】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、直接谈论Linux的进程状态

看看Linux内核源代码怎么说

1.1、R状态 -----> 进程运行的状态

1.2、S状态 -----> 休眠状态(进程在等待“资源”就绪)

1.3、T状态 -----> 暂停状态

1.4、t状态 ------> 当前的进程因为被追踪而暂停了

1.5、D状态

1.6、僵尸状态(Z)

1.7、死亡状态(X)

二、僵尸进程

下面我们创建一个进程来看一看

僵尸进程危害

三、孤儿进程

下面我们创建一个进程来看一看

四、运行状态

五、阻塞状态

六、挂起状态

七、进程切换的话题

总结



前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

一、直接谈论Linux的进程状态

看看Linux内核源代码怎么说

  • 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)。
  • 下面的状态在kernel源代码里定义:
 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 */
};

操作系统要更改一个进程的状态,它的原理其实就是更改 task_struct{}内部的状态属性(status)。

1.1、R状态 -----> 进程运行的状态

vim Makefile
testStatus:testStatus.cgcc -o $@ $^ -g
.PHONY:clean
clean:rm -f testStatus

这儿有一个替换方式:

:%s/myprocess/testStatus/  
//将 myprocess替换成 testStatus的可执行程序
vim testStatus.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{while(1){printf("i am a process,pid:%d\n",getpid());}return 0;
}

我们还可以用shell命令来一直刷新观察

while :; do ps ajx | head -1 && ps ajx | grep testStatus | grep -v grep; sleep 1; done

它其实一直处于一种休眠状态。

我们将打印的代码注释掉,再来看一下程序:

vim testStatus.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{while(1){//printf("i am a process,pid:%d\n",getpid());}return 0;
}

此时的代码就是一个非常纯粹的循环代码,没有打印。

重新 make 编译一下:

那么为什么我们加上 printf 的代码,就看到了大量的 S(休眠状态)呢?

printf的本质是往显示器上打印;而程序的运行是在千里之外的云服务器上跑;根据冯诺依曼体系结构,显示器是一个外设,所以CPU在跑当前的程序时,把数据写入到我们当前的内存当中,打印数据的顺序:先写入到内存里,再刷新到外设里。可是我们无法保证每次打印的时候,显示器的状态都是就绪的,因为程序是CPU跑的,CPU的运算速度要比显示器本身的速度要快的多,所以进程在被调度的时候,要访问显示器的资源,因为资源要一直在显示器上打,所以大部分时间,相比较CPU来讲,大部分时间,我们对应的进程都在等待我们的设备资源是否就绪。就比如:代码执行到 printf 的时候,CPU是几纳秒,而数据刷新到显示器的时间是几毫秒,其余大部分时间都在等待中,而这等待的时间,就是(S)休眠状态。

1.2、S状态 -----> 休眠状态(进程在等待“资源”就绪)

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{while(1){sleep(10);//printf("i am a process,pid:%d\n",getpid());}return 0;
}

可中断睡眠:处于S睡眠状态,依旧可以随时被外部信息打断,比如:ctrl + c,就醒来了。

进程在运行时 + &(取地址符);那么进程就默认在后端运行。

此时 R 后面就没有 + 了,+ 代表进程是在前端运行的,还是在后端运行的。

1.3、T状态 -----> 暂停状态

T/t :让进程暂停,等待被进一步唤醒。

1.4、t状态 ------> 当前的进程因为被追踪而暂停了

1.5、D状态

  • D:Linux系统比较特有的一种进程状态。
  • 当A进程需要大量的数据写入磁盘的时候,等待磁盘写入数据,此时A进程的状态就是休眠状态,当磁盘写入数据完成或者写入失败的话,都要向A进程汇报情况。
  • 但是有一个场景:系统中内存严重不足时,操作系统有一个特权,Linux操作系统有权力杀掉进程来释放空间。那么此时你的A进程被操作系统杀掉了,用来释放空间,过来3、4秒之后,磁盘已经写入了1GB的数据了,还想要再次写入A进程的数据,但是失败了,失败之后,要汇报情况,可是A进程已经被操作系统杀掉了;此时B进程也要向磁盘写入数据,那么磁盘就去照顾B进程去了,那么已经写入A进程的那1GB数据就丢失了(假如那1GB的数据是转账记录的话,那事故是很严重的),那该怎么办呢?
  • 为了避免这种情况,就可以把等待磁盘数据写入后,需要拿到磁盘返回结果的进程状态,设为D状态。D:不可被杀,深度睡眠,不可中断睡眠。

消除D状态有两种情况:

1、让进程自己醒来;

2、重启,不行的话,就得断电了。

1.6、僵尸状态(Z)

僵尸状态也叫僵死状态,它是在进程在死亡状态之前的状态。当一个进程运行完毕、出现问题或者被杀掉以后,它所占用的内存资源和退出状态没有被它的父进程回收,此时这个进程的状态就称为僵尸状态。

1.7、死亡状态(X)

当一个进程执行结束或者是被操作系统杀掉,它的PCB被操作系统删除,并且对应加载到磁盘上的二进制代码也被删除,此时这个进程就处于死亡状态了。

当一个进程占有内存的所有资源被回收以后,这个进程就处于死亡状态。进程的死亡状态是看不到的,因为只有在回收完成的那一刻才会出现,PCB不存在也就搜索不到这个进程,所以也无法演示。

二、僵尸进程

  • 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
  • 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。

Linux中的进程退出的时候,会将自己的退出信息保留在自己的PCB当中,如果没有人读取PCB中进程退出的消息,那么进程就会一直不释放;一般会把进程的代码和数据结果释放掉,但是PCB的数据结构不会释放,直到将来对进程进行等待,如果不等待,那么进程会一直处于僵尸状态。如果把进程的退出信息读取了和等待了,那么这个进程才会变成X状态,从而将进程的所有资源全部释放。

一个进程退出的时候,它不会立即退出,而是会现处于一种僵尸状态,如果父进程不会对我们这个进程进行回收或者等待的话,那么对应的进程会一直处于僵尸状态。

下面我们创建一个进程来看一看

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{pid_t id = fork();if(id == 0){// childint cnt = 5;while(cnt){printf("I am child, cnt: %d, pid: %d\n", cnt, getpid());sleep(1);cnt--;}}else{// parentwhile(1){printf("I am parent, running always!, pid: %d\n", getpid());sleep(1);}}return 0;
}

子进程已经运行完毕,但是需要维持自己的退出信息,在自己的进程task_struct会记录自己的退出信息,未来让父进程进行读取。

如果没有父进程读取,僵尸进程会一直存在(内核数据结构task_struct会一直存在),进程的代码和数据会释放。

将来子进程被 waitpid(系统调用接口) 等待方式读取了,那么这个子进程会由Z状态变为X状态,之后由OS来释放。

僵尸进程危害

  • 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
  • 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
  • 内存泄漏?是的!
  • 如何避免?后面讲。

三、孤儿进程

  • 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  • 父进程先退出,子进程就称之为“孤儿进程”。
  • 孤儿进程被1号init进程领养,当然要有init进程回收喽。

下面我们创建一个进程来看一看

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{pid_t id = fork();if(id == 0){// childint cnt = 5;while(cnt){printf("I am child, cnt: %d, pid: %d\n", cnt, getpid());sleep(1);//cnt--;}}else{// parentint cnt = 5while(cnt){printf("I am parent, running always!, pid: %d\n", getpid());sleep(1);cnt--;}}return 0;
}

我们已经启动的所有的进程,我们怎么从来没有关心过僵尸进程(内存泄漏)呢?

直接在命令行中启动的进程,它的父进程是bash,bash会自动回收新进程的Z(僵尸进程)。

四、运行状态

操作系统为了合理分配CPU以及各种硬件资源,保证各个进程的正常运行。操作系统会为CPU创建一个进程队列,也为每一个硬件都创建一个等待队列。

而某一个进程处于运行状态本质上就是操作系统将该进程对应的PCB放入CPU的运行队列中,然后再将PCB中维护进程状态的变量修改为相应的值。

PCB里面有进程的各种属性值,以及对应的代码的地址。所以CPU从运行队列中找到PCB取出数据后,可以根据数据得到进程的各种属性值和指令,然后执行相应代码。进程处于运行状态并不意味着该进程此时一定正在被运行,只要该进程处于CPU的运行队列中即可。

注意:CPU是处理数据的速度在纳秒级,运算速度非常快,所以只要进程处于CPU的运行队列中,我们就可以认为该进程正在被运行。

一个进程一旦持有CPU,会一直运行到这个进程结束吗?

不会。CPU基于时间片进行轮转调度的。(Linux不是这样调度的,这只是OS教材调度方法之一)

让多个进程以切换的方式进行调度,在一个时间段内同时得以推进代码,就叫做并发。

每一个CPU都要有一个自己的运行队列,有两个CPU的话,会存在真正意义上的有两个调度队列。任何时刻,都同时有多个进程在真的同时运行,我们叫做并行。

五、阻塞状态

CPU处理数据的速度极快,是我们计算机中的各种硬件处理数据的万倍,比如:一个磁盘或者一个网卡同时只能为一个进程服务,但是在计算机中需要使用这些硬件资源(磁盘等)的进程会有很多。

如果在硬件为一个进程服务时,有其他运行中的进程也需要使用该硬件资源,操作系统就会将该进程的PCB放入硬件的等待队列中,进程会等待硬件来为自己提供服务。

不是只有CPU才有运行队列,各种硬件设备也有自己的等待队列。

由于多个进程需要访问某种硬件,进程PCB在硬件等待队列中等待硬件服务自己的状态就被称为阻塞状态。阻塞状态在本质上就是将进程的PCB从CPU的运行队列中,放入硬件的等待队列中,然后将PCB中维护进程状态的变量也会修改为相应的值。当该进程获得对应的硬件资源服务时,再将该进程放回CPU的运行队列中。

注意:并不是只有等待硬件资源的进程才处于阻塞状态,一个进程等待另一个进程就绪、一个进程等待软件资源就绪等也是阻塞状态。

六、挂起状态

上面我们学习了阻塞状态,处于阻塞状态的进程由于需要等待某种资源,所以它对应的代码和数据在短期内不会被处理(这里的短期指的是对于操作系统而言)。但它们的数据仍储存在内存中,占用存储空间但是不执行,相当于浪费了内存资源。而如果当前操作系统处于高IO(大量向内存输入和向外部设备输出数据)的情况下,可用的内存空间不足,操作系统就会选择性的将这些处于阻塞状态的进程对应的代码和数据转移到磁盘中,从而节省出内存空间。

这种由于内存空间不足,操作系统将在等待资源的进程对应的代码和数据放到磁盘中以节省内存空间的状态就被称为挂起状态。挂起状态不会移动进程的PCB,只会移动进程对应的代码和数据。

挂起并不是释放进程,因为对应的PCB仍然在硬件的等待队列中,当该进程获得对应的资源服务以后,操作系统仍然可以将该进程对应的代码和数据从磁盘加载到内存中来继续运行,其本质是对内存数据的唤入唤出。

七、进程切换的话题

A进程在CPU运行队列中运行的时间片到了之后,A进程会脱离CPU的运行进程;B进程会进入CPU的运行队列中运行,那么比如:A进程在CPU中已经运行到了第50行代码后,时间片到了,退出CPU,当A进程再次进入CPU的时候,是从新开始运行呢?还是接上上回第50行的代码继续运行呢?

CPU里面有一套寄存器,会保留A进程运行的数据,当A进程退出CPU的时候,A进程在CPU中已经运行的数据,将由 task_struct 这个结构体来保存,当A进程再次进入CPU时,数据还会回到寄存器原来的位置,所以A进程会接着第50行继续运行。

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

CPU内的寄存器:

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

寄存器 != 寄存器的内容


总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/543735.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java代码基础算法练习-判断字符串是否为回文-2024.03.16

任务描述&#xff1a; 回文串是指一个正读和反读都一样的字符串&#xff0c;比如“level”或者“noon”等。要求输入 一个字符串&#xff0c;判断此字符串是否为回文。&#xff08;注&#xff1a;设字符串长度小于20&#xff09; 任务要求&#xff1a; package suanfa;import…

学python新手如何安装pycharm;python小白如何安装pycharm

首先找到官网&#xff1a; Download PyCharm: The Python IDE for data science and web development by JetBrains 打开后选择下载&#xff0c;下图标红部分 点击exe程序&#xff0c;点击下一步&#xff01; 选择安装路径&#xff0c;下一步 弹出界面全选 选择默认 然后直接…

第三门课:结构化机器学习项目-机器学习策略

文章目录 1 机器学习策略一1.1 为什么是ML策略&#xff1f;1.2 正交化1.3 单一数字评估指标1.4 满足和优化指标1.5 训练、开发及测试集划分1.6 开发集和测试集的大小1.7 什么时候改变开发、测试集和指标&#xff1f;1.8 为什么是人的表现&#xff1f;1.9 可避免偏差1.10 理解人…

IDEA创建Sping项目只能勾选17和21,没有Java8?

解决办法: 替换创建项目的源 我们只知道IDEA页面创建Spring项目&#xff0c;其实是访问spring initializr去创建项目。故我们可以通过阿里云国服去间接创建Spring项目。将https://start.spring.io/或者http://start.springboot.io/替换为 https://start.aliyun.com/

python的小技巧一

文章目录 python的小技巧系列1、变量相关变量交换三元运算符一个数值的范围比较有的场景下使用 try...exception 代替if...else 2、字符串相关格式化连接字符串的分割字符串的连接 3、生成器4、列表相关取最后一个元素判断列表是否为空列表合并去除列表中的重复值判断某个值是包…

python知识点总结(一)

这里写目录标题 一、什么是WSGI,uwsgi,uWSGI1、WSGI2、uWSGI3、uwsgi 二、python中为什么没有函数重载&#xff1f;三、Python中如何跨模块共享全局变量?四、内存泄露是什么?如何避免?五、谈谈lambda函数作用?六、写一个函数实现字符串反转&#xff0c;尽可能写出你知道的所…

lua脚本的基础内容

官方地址&#xff1a;http://luajit.org/ 官方wiki地址&#xff1a;http://wiki.luajit.org/Home 推荐书籍&#xff1a; OpenResty 最佳实践&#xff1a;https://moonbingbing.gitbooks.io/openresty-best-practices/content/ lua基础文档&#xff1a;https://www.runoob.com/l…

SpringBoot Servlet容器启动解析

介绍 容器架构 容器处理请求 容器启动全局流程解析 启动前准备 WebServer创建入口 WebServer创建 Servlet启动 Web容器工厂类加载解析 Web容器个性化配置 属性注入 工厂类初始化 BeanPostProcessor方法实现 定制化流程 面试题 请描述下Servlet容器启动流程&#xff1f;介绍下…

深入理解TCP:序列号、确认号和自动ACK的艺术

深入理解TCP&#xff1a;序列号、确认号和自动ACK的艺术 在计算机网络的世界里&#xff0c;TCP&#xff08;传输控制协议&#xff09;扮演着至关重要的角色。它确保了数据在不可靠的网络环境中可靠地、按顺序地传输。TCP的设计充满智慧&#xff0c;其中序列号&#xff08;Seq&a…

uniapp 写安卓app,运行到手机端 调试

手机 设置》关于手机》点击版本号 4-5次&#xff0c;弹出手机锁屏页面&#xff0c;输入手机锁屏密码 2.手机 设置中 》搜索 开发人员选项 》 调试》打开USB调试 同页面 找到 选择USB配置》选择 MIDIhbuilder 编辑器 点击 》运行》运行到手机或模拟器》运行到Android App基座 》…

[Windows] Win11 常用快捷键

文章目录 &#x1f680; [Windows] Win11 常用快捷键&#x1f310; Windows 操作系统&#x1f525; Windows 11 &#x1f310; Windows 11 快捷键概览&#x1f525; 基本快捷键&#x1f525; 窗口快捷键&#x1f525; 功能快捷键 &#x1f4dd; 小结 &#x1f680; [Windows] W…

Java手写简易数据库--持续更新中

MYDB 0. 项目结构0.1 引用计数缓存框架为什么不使用LRU引用计数缓存缓存框架实现 0.2 共享内存数组 1. 事务管理器--TM1.1 XID 文件XID 规则XID 文件结构读取方式事务状态 1.2 代码实现 2. 数据管理器--DM2.1 页面缓存页面结构页面缓存数据页管理第一页普通页 2.2 日志文件 3. …