怎么查看网站提交百度的度,新手怎么做网站推广,十堰网站建设联系电话,阿里云服务器618文章目录 一、前言二、操作系统学科下的进程状态1、运行状态2、阻塞状态3、挂起状态 三、Linux下的7种进程状态1、运行状态R2、浅度睡眠状态S3、深度睡眠状态D一场有趣的官司 4、停止状态T5、进程跟踪状态t6、死亡状态X7、僵死状态Z —— 两个特殊进程① 僵尸进程② 孤儿进程 四… 文章目录 一、前言二、操作系统学科下的进程状态1、运行状态2、阻塞状态3、挂起状态 三、Linux下的7种进程状态1、运行状态R2、浅度睡眠状态S3、深度睡眠状态D一场有趣的官司 4、停止状态T5、进程跟踪状态t6、死亡状态X7、僵死状态Z —— 两个特殊进程① 僵尸进程② 孤儿进程 四、总结与提炼 一、前言 Hello大家好本文我们所要介绍的是有关Linux下的进程状态 在上一文中我们重点介绍了有关 Linux下进程的基本概念了解了什么是进程、怎么去描述并组织进程、创建一个进程。在本文中我们将先通过了解操作系统学科下的进程状态对进程的状态有一个基本的概念然后呢再去学习Linux下的7种进程状态学习这PCB结构体中的第二个成员变量
task_ struct内容分类 标示符: 描述本进程的唯一标示符用来区别其他进程。状态: 任务状态退出代码退出信号等。优先级: 相对于其他进程的优先级。程序计数器: 程序中即将被执行的下一条指令的地址。内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针上下文数据: 进程执行时处理器的寄存器中的数据[休学例子要加图CPU寄存器]。IO状态信息: 包括显示的I/O请求,分配给进程的IO设备和被进程使用的文件列表。记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。其他信息 二、操作系统学科下的进程状态
对于进程而言呢它是操作系统中的概念如果有学习过《操作系统》这门学科的话应该可以很清楚对于进程而言的话是存在着许许多多的状态如果一开始刚刚接触的小伙伴一定会感觉这么多状态要怎么区分呀 其实那么多的状态真正主要的也就那么几个所以接下去我会中重点讲解以下几种进程的状态
1、运行状态 首先我们要谈到的是【运行状态】这个状态是最普遍的 首先对于一个进程而言我们知道它是由 内核数据结构 所对应的代码和数据 所组成的所以当系统中存在多个进程的时候就势必会存在多个结构体当然我们需要将这些进程给链接组织起来那么这些进程就相当于是在处在一个运行队列中我们如果要找到这个队列中的某个进程的话只需要找到这个进程的头部即可那我们就可以对应地去调度某个进程把这个进程所对应的代码和数据放到CPU上去执行 因为每个进程是需要去竞争CPU资源的但是呢CPU不可能同时给这么多进程分配资源 所以每一个CPU都会去维护一个运行队列里面的队头指针head所指向就是第一个进程所对应的【task_struct】队尾指针tail所指向就是最后一个所对应的【task_struct】。所以我们要运行某一个进程只需要将 head 所指向的那个进程放到CPU上去运行即可 但是CPU无法直接去找到某个需要调度的进程来运行此时呢就需要一种东西叫做【调度器】CPU通过找到运行队列运行队列找到调度器然后调度器再去一一调度所需要运行的进程那么此时所被调度的、处于运行队列里的这些进程所处的状态我们称之为 运行状态R 提问一个进程只要把自己放到CPU上开始运行了是不是一直要到执行完毕才把自己放下来 不是每一个进程都有一个叫做时间片的概念 其时间大概是在10 ms左右。所以并不是一个进程一直在执行而是这多个进程在一个时间段内所有代码都会被执行 —— 这就叫做【并发执行】 所以呢这就一定会存在大量的进程被CPU放上去、拿下来的动作 —— 这就叫做【进程切换】 所以呢我们不要拿自己的时间感受去衡量CPU其运行一遍速度是非常快的你根本感受不到这种进程切换的效果
2、阻塞状态 在介绍完【运行状态】后我们再来讲讲【阻塞状态】 之前我们在讲 操作系统基本概念 的时候有说到过操作系统要去管理底层的硬件的话需要进行【先描述再组织】的动作那就是将这一个个生冷的硬件抽象成为结构体类型内部的type代表这是一个什么类型的硬件、status代表这个硬件当前在操作系统中所处的状态是什么样的wait_queue代表的则是等待队列的指针 那我们继续去谈起操作系统中的【进程】现在呢有一个进程想要去读取键盘中的数据那么它就需要与我们刚才所描述的结构体相互勾连起来因为操作系统根据这个结构体顺着体系结构去访问到最底层的硬件需要实现所以这个进程便需要一直处于等待状态我们可以将其链入【等待队列】中即我们刚才所讲的wait_queue 那有同学可可能会疑惑这为什么叫做【等待队列】呢明显只有一个进程鸭 一个进程当然不能算是队列若此时又有一个进程也要来读取键盘中数据的话我们就需要将其链入到这个等待队列中。此时这两个进程所处的状态即为 阻塞状态 那么当键盘里一旦有数据了我们只需要把这个进程放到我们在上面所讲的【运行队列】中即可那么CPU在调度的时候就可以自动地到底层设备里去读取了如果当键盘里没有输入的时候最终我们的进程就只能在每一个各自的等待队列里去等待把这种处于等待队列的进程称之为 阻塞状态 3、挂起状态 最后我们再来讲讲一种状态叫做【挂起状态】 对于操作系统而言我们知道它要为当前的所有进程分配内存资源在上面我只画了一个等待队列而且只有两个进程若是一个等待队列中存在多个进程并且通过有多个等待队列在排队的话操作系统中的内存资源就会出现【消耗殆尽】的问题那么操作系统就得保证在正常情况下省出来内存资源 要怎么去省呢 只要一个进程没有被调度运行那么当前这个进程的 代码和数据 就是空闲的、没有被使用的。于是操作系统就想办法把这个进程内核的PCB给保留然后将其 代码和数据 重新交换到外设当中【换出】那么此时只有一个PCB在这里排队我们把上面这种状态就称之为 挂起状态 当这个进程就绪了把这个进程放到运行队列时此时再考虑把 代码和数据 重新放回进程中【换入】 那有的同学说为什么要这样去做呢这样做有什么意义 答当一个进程所对应的代码和数据交换到外设上时操作系统内部就腾出了相应的空间此时OS就可以将这块多出来的空间重新给到其他需要的进程使用从而达到了循环利用系统资源的目的节省了系统开销。
三、Linux下的7种进程状态 在介绍完操作系统学科下的三种最主要进程状态后我们对进程的状态有了基本的概念接下去就让我们正式地来学习一下Linux系统下7种进程状态 先来小结并回顾一下上面所学
如果当前是【运行状态】那么接下来就需要被调度运行如果当前是【阻塞状态】那就等条件就绪等设备准备好就把当前进程投递到运行队列里然后再被CPU调度运行如果当前是【挂起状态】要做的就是把当时换出的代码和数据重新换入然后再把所对应的进程列入到运行队列中
以下就是关于进程的所有状态
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 */
};1、运行状态R 首先我们要来聊的是【运行状态R】 来看下下面的这段代码是一个死循环去printf打印循环语句 1 #include stdio.h2 #include unistd.h3 4 int main(void)5 {6 while(1); 7 {8 printf(hello bit\n);9 }10 11 return 0;12 }然后我们将下面的代码给运行起来观察这个进程的状态时看到其显示为S不过呢读者想看到的应该是R才对 接下去呢我们把代码修改一下再来看看不使用printf打印语句了而且直接使用一个while(1)去做循环 1 #include stdio.h2 #include unistd.h3 4 int main(void)5 {6 while(1); 7 //{8 // printf(hello bit\n);9 //}10 11 return 0;12 }然后我们看到此时再运行起来时这个进程的状态就改变了变成了R这才是我们想要的【进程状态】 那有读者就要问了为什么把printf打印语句给去掉之后就变成这样了呢 原因就在于printf打印语句它是属于IO流的一种第一次因为是循环的缘故它一直在等IO设备就绪所以其进程状态就一直为S对应的即是在操作系统中的【阻塞状态】但是当我们去掉printf这种IO流之后呢它就是在纯纯运行没有IO那也就变成了R状态 这里再补充说明一下这个S和R后面的 这里的R代表的就是这个进程是在前台运行的所以我们在输入任何指令后不会对其造成 任何的影响 那若是我们不以正常的方式去启动这个进程的话其进程的状态就会不一样了可以看到我在./myproc的后面加上了一个那么其状态变成了R此代表的意思就是这个进程它是运行在了【后台】的 2、浅度睡眠状态S 接下去我们再来介绍一下Linux下的睡眠状态S 下面我们要运行的代码是每隔一秒打印一句内容 1 #include stdio.h2 #include unistd.h3 4 int main(void)5 {6 while(1)7 {8 sleep(1); 9 printf(hello bit\n);10 }11 12 return 0;13 }运行起来后可以看到这个进程所处的状态即为S睡眠状态 那有同学就很疑惑这个进程不是在运行吗为什么不是R状态呢 我们可以通过动图来观察看看每一次去查看这个进程的状态时都是处于S这个睡眠状态。其实呢这和外设与CPU的速度之差是有关系的CPU那个运行速度是极其快速的 虽然我们肉眼可见右侧的进程是一直在每隔一秒运行但是呢在我们查进程状态的时候看到的却只能是S因为有99.99%的时间都在进行等待而只有0.01%的时间在运行它们之间简直可以说是数量级别的差别 不过呢上面这样还无法看出一个进程是否真正地处于睡眠状态我们再通过一段代码来看看 1 #include stdio.h2 #include unistd.h3 4 int main(void)5 {6 int a 0;7 printf(Enter# );8 scanf(%d, a);9 10 printf(echo : %d\n, a);11 return 0; 12 } 将该进程运行起来我们可以看到其是出于S的状态因为【shell】此时正在等待用户的输入这个就对应到了我们上面所讲到的 阻塞状态 不仅如此像我们一直在使用的bash也可以算是一种【阻塞状态】一直等待着我们去输入命令行一旦有了的话它就进行读取 3、深度睡眠状态D 除了【浅度睡眠】之外呢还有一种叫做【深度睡眠】它们俩呢都是 阻塞状态 对于浅度睡眠来说之所以称为 “浅度”是有原因的也就是处于这种状态的进程容易被唤醒。例如说我们在上面所讲到的这个处于阻塞状态的进程我们使用kill -9 8664向这个进程发送【9号信号】那么这个进程就被杀死了你也可以认为被唤醒了 好接下去呢我就通过一个故事来描述一下这个【深度睡眠】到底是怎样一种状态 现在在操作系统里有一个进程它呢想要把数据写到磁盘上去磁盘这个时候听到了它的请求就开始写数据不过我们知道外设尤其是像磁盘这种设备的速度都是比较慢的所以呢这个时候进程就会一直地等待下去 但是呢光这么无休止地等待下去可不行。因为当前这个时候操作系统中的已经有非常多的进程了那么OS它看不下去了路过的时候看到当前的这个进程正处于等待状态也不运行于是二话不说把它终止了 所以呢同学们我们要明白这么一个道理操作系统认为当前系统能行的话就直接让用户去用就可以了如果扛不住了就置换页表实在不行了就去会杀进程
那么当这个进程被杀掉的时候磁盘说我在写数据的时候出了问题。它这个时候就回去找那个调度它的进程但是发现这个进程没了所以磁盘就特别尴尬我要怎么办呢写好的这个数据该不该还放在这里【一般的设备都会选择丢失】如果这份数据是普通的那还好但如果其为银行当中那种非常重要的数据呢该怎么办 一场有趣的官司 那因为随着这份重要数据的丢失呢就引发了一场官司 到了法庭上法官开始问话审讯【操作系统】、【进程】、【磁盘】这三个嫌疑人读者认为是谁的问题的锅在谁呢 以下呢是三个人的辩词 【操作系统】 请问有没有赋予我管理它软硬件资源的权利请问我杀掉进程是不是在特别极端的情况下杀掉的请问我有没有履行我操作系统的职责我的职责是保证系统不挂数据丢失和我有什么关系 我就是在做我操作系统该做的事如果你判我有罪了那么请问我下次再遇到类似的情况时到底我还做不做我如果不杀的话最终导致操作系统挂掉① 数据该丢还是得丢② 可能还会影响其他进程。这些责任谁来承担呢 操作系统听了这番说辞后心里想了想确实是。于是呢这个时候便把茅头指向了丢掉数据的磁盘✒ 发问道你为什么要丢数据呢 【磁盘】 法官大人啊这你不能怪我╮(╯▽╰)╭ 在这件事情上我就是个跑腿的人家让我干啥就干啥我在写入的时候就已经告诉了对方我会失败我让他去等的我要给它结果的我的工作模式向来都是这样其他磁盘也是这样 如果你认为我有罪的话是不是其他的磁盘也有问题呢我丢失也是有原因的其他进程也要叫我写入数据那我也去写了并且汇报回去了那其他磁盘怎么没出问题呢 操作系统一听诶这货说的好像确实没什么问题。那就就把视角转向了受害人即【进程】这一方。反正操作系统没错、磁盘没错那就是你的问题喽 【进程】 法官我可是受害人啊/(ㄒoㄒ)/~~ 我就是静静地坐在那里“人在家中坐锅从天上来呀”。我是被杀掉的我怎么能有错呢 那这个时候法官一想它们三个说的似乎都挺有道理难道是我错了吗 其实上面三者都没错操作系统履行了它的职责在关键时刻杀掉了进程而磁盘呢则是起到了它本能的义务丢失了没人要的数据进程呢则因为在等候期间无意中被操作系统给杀掉了当这个进程在等磁盘写入数据这么关键的时候应该要让其受到保护不可以被任何人给杀掉才对 那我们在上面有提到过处于【浅度睡眠】的进程是可以被kill掉的那么我们就要让这个进程的状态变为【深度睡眠】才对即[D] 那既然这个进程的状态被设置为D后当它在等待这个磁盘写入的时候操作系统路过看到这个进程没有跑起来空等着于是在想把他杀掉的时候就被这块 《秒死金牌》给吓到了 那么这个进程就不会被杀死了当磁盘写完数据后告知进程那么它就可以将自己放入【运行队列】里去运行此时它的状态就变成R了 那这个时候就又有同学问了D状态这么强大吗那如果一个操作系统里有很多的D状态这怎么办呢 这个同学你问得很好确实这个D状态的话操作系统是没有办法将其杀掉的而是要等到磁盘写入完毕或者什么事情执行完毕后其才会去自动结束这个进程或者是在外部断电、直接拔掉电源即可 一般一个系统里面如果存在D状态的话那这个系统中的磁盘压力已经非常大了如果存在多个D状态的话则表示这个系统已经是非常危险了最好马上重装一下系统 不过呢这个[D]就没办法在这里给读者演示了因为D状态的进程只有处于高IO的情况才可以演示。有兴趣的可以去研究一下Linux下的命令dd
4、停止状态T 好接下去呢我们来讲讲【停止状态T】 首先我们要通过下面这句命令来查看一下对应的进程信号
kill -l此处我们要使用到的是18、19号信号 接下去我们就来试一试如何让这个进程暂停之后又重新启动会是怎样的 所以我们来总结一下 暂停进程 kill -19 PID启动进程 kill -18 PID所以呢如果我们要将一个进程给终止的话发送19号信号即可要让其继续启动起来则发起18号信号 那我现在要问了这个T停止状态和S睡眠状态有什么不同呢 stopped状态进程 完全暂停了, 其不会再接收任何信号了一个进程通过stopped状态可以控制另一个S和D一定是在等待某种资源而T状态可能在等待某种资源也可能被其他进程控制
5、进程跟踪状态t 接下去呢我们再来说说进程的跟踪状态t还记得我们在基础篇中所学习的 GDB调试 吗 首先我们在正常状态下查看这个进程的状态其为S睡眠状态。接下去呢我进行了【gdb】调试在行内打上断点后输入r运行起来后我们再去查看这个进程的状态时就发现其状态变成了t原因就在于这个进程在调试的时候停了下来 6、死亡状态X 对于【死亡状态X】来说呢这个状态只是一个返回状态你不会在任务列表里看到这个状态 那其实就是我们在上面所讲到的这个 杀进程
kill -9 PID7、僵死状态Z —— 两个特殊进程 接下去我们来介绍一种状态叫做【僵死状态Z】对于这个状态我们要涉及到两个特殊的进程叫做 僵尸进程 与 孤儿进程 ① 僵尸进程 首先我们要来介绍的是僵尸进程这里呢通过一个故事来进行引入
你呢很喜欢晨跑这一天早晨6点又起来跑步了当你路过一个公园的时候遇到了一个晨练的老大爷此时突然他就倒了下来。那你此时就非常担惊受怕了马上拨打了110和120的电话然后守在他的身边等待救援来到周边的人看到也都纷纷围了过来其中不免有一些人会紧急救援。不过等了一会这个人就没了呼吸 过了十几分钟后救护车和警车都来了警察先封锁了让法医过来检验一下其状况就说“这个人已经没救了赶紧通知家属准备后事吧~。” 好我们回归正题来说说这个【僵尸进程】 因为在救护车来之前这个人其实就已经死亡了但是其状态还没有被检测当前并不知道它的死因所以我们操作系统就可能会维护当前的这个状态这个状态即为Z状态即[僵死状态] 那我现在想问了有一个进程暂时退出了它要将它的状态暂时维持一段时间问题是它维持给谁看呢 答父进程当一个进程退出的时候那最关心它的便是【父进程】。因为这个父进程费了很大的劲才将这个子进程fork出来此时呢它突然挂掉了那么此时父进程就必须去关心一下对应的子进程退出时的原因 不过光凭我们现在所学习的知识还无法去查看这个等我们后面讲到wait()系统调用的时候再去做一定的介绍 就上面这样生冷的文字来叙述还不太行接下去我们通过实际的案例来观察一下 1 #include stdio.h2 #include stdlib.h3 #include unistd.h4 5 int main(void)6 {7 pid_t id fork();8 if(id 0)9 {10 // child11 int cnt 5;12 while(cnt)13 {14 printf(I am child, pid: %d, ppid: %d, cnt: %d\n, getpid(), getppid(), cnt);15 sleep(1);16 17 cnt--;18 } 19 exit(0);20 }21 else22 {23 // father 24 while(1)25 {26 printf(I am father, pid: %d, ppid: %d\n, getpid(), getppid());27 sleep(1);28 }29 }30 return 0;31 }运行起来可以看到在一开始的 5s 内父子进程还是同时在跑的但是当子进程在循环结束后退出了。不过此时呢父进程还是运行并没有退出。所以我们在右侧查看这两个进程的状态时间就发生了一定的变化 仔细地来看一下这个状态的变化其中【PID】为5486其父进程为5485一开始它们均为S睡眠状态但是呢到了后面子进程的状态就变成了Z僵死状态。也就意味着此时子进程已经死亡了但是呢父进程还不知道这里还可以通过右侧的这个defunct这个来看它表示【失效的、不再存在的】 所以我们总结一下 进程一般退出的时候如果父进程没有主动回收子进程信息子进程会一直让自己处于Z状态进程的相关资源尤其是 task_struct 结构体不能被释放 那对于上面的这种子进程我们就将其称作为是【僵尸进程】不过呢这种进程是存在一定危害的 那一个父进程创建了很多子进程就是不回收是不是就会造成内存资源的浪费是的因为数据结构对象本身就要占用内存想想C中定义一个结构体变量对象是要在内存的某个位置进行开辟空间 那么对于上面的这种危害我们就称作为是【内存泄漏】要如何去进行避免呢之后的文章会做讲解~
② 孤儿进程
上面我们讲到当一个子进程突然退出但是父进程并没有去主动回收的话那么此时这个子进程就会变成【僵尸进程】那看到下面我们将这个进程突然终止之后父子进程都不见了 那此时我想问这个父进程突然之间退出了但是呢它的父进程并不知晓那为何这个父进程没有处于【僵尸状态Z】呢 因为当前这个进程是bash的子进程当其一退出之后bash就将其回收了。可是这个子进程为什么也没了呢这个的话我们就要聊聊【孤儿进程】了 接下去我们来将上面测试僵尸进程的代码做个修改让父进程先于子进程退出 1 #include stdio.h2 #include stdlib.h3 #include unistd.h4 5 int main(void)6 {7 pid_t id fork();8 if(id 0)9 {10 // child11 int cnt 500;12 while(cnt)13 {14 printf(I am child, pid: %d, ppid: %d, cnt: %d\n, getpid(), getppid(), cnt);15 sleep(1);16 17 cnt--;18 }19 exit(0);20 }21 else22 {23 int cnt 5;24 // father 25 while(cnt--) 26 {27 printf(I am father, pid: %d, ppid: %d\n, getpid(), getppid());28 sleep(1);29 }30 }31 return 0;32 }然后通过进程状态追踪来看看我们观察到一开始两个父进程和子进程是同步在走的但是呢过了一会父进程终止了只有子进程还在跑此时我们需要留意的不是其进程状态而是这个子进程的PPID即父进程的PID它由原先的【19956】变成了【1】这是为什么呢 这里的话就要给读者普及一下了对于PID的值为1的进程我们一般将其称作为是【系统进程】。我们可以使用下面这句指令去查找一下
ps ajx | head -1 ps ajx | grep systemd然后我们看到这个【系统进程】的PID即为1 那为何会出现上面这种现象呢是这个子进程突然换父进程了吗 对于父子进程来说如果父进程先于子进程结束的话那么这个子进程就会被称作为是【孤儿进程】对于孤儿进程来说呢它会有1号进程即【系统进程】所领养因为此时这个进程没有了父进程了所以需要有人去带领它 但是这个孤儿进程为什么要被领养呢 其实很简单既然它是一个进程的话最终都是要退出的但是没了父亲的话就只能由系统进程来进行维护了 那其实我们就可以解释上面的事情了 当这个父进程退出的时候它是被bash给回收了那么对于父进程的子进程来说我们刚才谈到了如果一个进程的父进程先于子进程结束的话那么在这一刻这个子进程立马变成了【孤儿进程】被系统领养并回收了所以我们就看不到了 四、总结与提炼 最后来总结一下本文所学习的内容 在本文中我们主要讲解了两个大点一个是在操作系统中所常见的一些进程状态它们分别为运行状态、阻塞状态、挂起状态。 所被调度的、处于运行队列里的这些进程所处的状态我们称之为 运行状态R处于等待队列中同时在等待外设相应的进程所处的状态我们称之为 阻塞状态R当把一个进程的 数据和代码都重新交换到外设当中进程所处的状态我们称之为 挂起状态R 接下去我们又介绍了在Linux系统下的七种进程状态分别是运行状态R、浅度睡眠状态S、深度睡眠状态D、停止状态T、进程跟踪状态t、死亡状态X、僵死状态Z 当一个进程没有在等待IO流的时候其就会处于 运行状态R使用sleep()函数可以很好地使一个进程处于浅度睡眠状态S如果不想让一个进程在等待磁盘操作的时候被操作系统杀掉则可让其处于 深度睡眠状态D可以向一个进程发送【19】号信号使其暂停处于停止状态T继续发送【18】号信号的话则可以使其重新启动在【gdb】的环境下去运行一个断点的话则可以使其处于进程跟踪状态t使用kill -9 PID就可以杀掉一个进程使其处于死亡状态X如果让一个子进程在父进程不知晓的时候退出可那么其就会处于僵死状态Z变为【僵尸进程】若是在父子进程中父进程先于子进程退出的话那么这个子进程就会变成【孤儿进程】 以上就是本文要介绍的所有内容感谢您的阅读