【Linux】进程3——进程状态

我们先不讲Linux的进程状态,我们先了解一下我们在学校里学习的操作系统课程里的进程状态 

1.操作系统课程里的进程状态

1.1.运行态

        当多个进程需要运行时,它们会竞争CPU资源。在极端情况下,如果只有一个CPU,那么众多进程就必须通过调度器来合理使用CPU资源,确保资源的均衡使用。为了管理这些进程,每个CPU都会维护一个自己的运行队列struct runqueue。这个运行队列是一个重要的数据结构,用于存储当前CPU上待运行的进程。如下图。

        每个进程都有一个对应的task_struct结构体,这个结构体包含了进程的各种属性。而运行队列中最重要的属性是head和tail指针,它们分别指向队列的头部和尾部。这意味着排队的不是进程的代码,而是进程的PCB在排队。

     当多个进程处于运行队列中时,它们所处的状态被称为运行状态(r状态)。这意味着这些进程已经准备好,可以随时被调度执行。 

在运行队列里的进程都是r状态——运行态 

   那么问题来了

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

  • 不是,因为每个进程都有一个时间片的概念,每个进程一次性在CPU上最多执行时间片大小的时间
  • 一旦一个进程的执行时间超过了它的时间片,操作系统会强制将其从CPU上移除,并将其放回运行队列的尾部等待再次调度。
  • 大量的把进程从CPU上放下去,拿上来的动作——进程切换 

所以CPU上一定会存在大量的把进程拿上去又放下来的情况 

      时间片的引入确保了每个进程都有机会在一段时间内得到执行,从而实现了多个进程的并发执行。并发执行是指在同一时间段内,多个进程的代码都会被执行。通过合理分配时间片,操作系统可以确保所有进程都得到公平调度和执行,从而提高了系统的整体效率和响应速度。

1.2.阻塞态

我们知道,每台计算机都有很多外部设备

操作系统如何对硬件做管理?——先描述,再组织

我们把每种设备描述成下面这样 

struct dev
{
int type;
int status;
task_struct* head;
....
}; 

现在有一个进程,他要等待I/O设备的输入

因为没有输入数据,它不能放到运行队列里面去

那怎么办?

把它放到键盘的那个等待队列去即可

同理,下一个这样子的进程也是放到这个等待队列里 

        正如上面所说我们每个外设都有一个等待队列,每个使用设备的进程,如果想要使用某个设备,就需要等待该设备变为就绪状态。

        如果设备当前不可用,即处于不可读状态,那么进程会自动将其PCB(进程控制块)加入到该设备的等待队列中。

        一旦设备变为可用状态,即处于可读状态,进程就可以从等待队列中取出PCB,进入就绪状态,等待CPU调度。

如果设备仍然没有准备好,进程就会在等待队列中等待,这时我们称进程处于阻塞状态。

等待队列是每个设备都有的,所以有很多个等待队列,这个和运行队列可不同,运行队列是有多少个CPU就有多少个运行队列

        因此,当我们说让进程去某个资源中等待时,实际上是将进程加入到该资源的等待队列中,也就是让进程进入阻塞状态。

        这个可以类比进程的运行态,但是与进程的运行态不同的是,运行态等待的是CPU资源,而阻塞态是由于等待一些非CPU资源而无法运行的状态同样也会维护相应的等待队列,这个等待队列因等待的资源不同而有所差异,比如常见等待的资源:磁盘,显卡,网卡,打印机,因此这些资源都会维护对应的等待队列,比如:磁盘等待队列,网卡等待队列,显卡等待队列等,此时进程在内存中。

这个状态的特点是:

  • ① 有时也叫作 “等待态、封锁态、睡眠态” 。
  • ② 当前进程因等待某事件的发生而暂时不能运行的状态。
  • ③ 即使这时CPU空闲,该进程也不能运行。

此过程会发生的事情:

  1.  当前进程的PCB会从runqueue中移除
  2. 当前进程的PCB会被加入到对应等待资源的等待队列中
  3. 对应进程的PCB中的进程的状态会被改变 

1.3.挂起态

这个是比较奇怪的状态

如果有很多进程都在等一个设备,比如磁盘,但磁盘一直没准备好,这些进程就只能在内存里等着,也不占用CPU。

我们知道每个进程都需要占用一些内存。

但是这个时候偏偏操作系统的内存严重不足了,操作系统就得想办法省内存。

因为如果一个进程只是等着,没有被CPU调度,那它的代码和数据其实在内存里是空闲的。

操作系统就可以把等待进程的代码和数据移到磁盘里去,只留下一个PCB在内存里排队。

这样,当设备准备好了,再把代码和数据从磁盘移回内存。

这个过程叫做换出和换入。

当代码和数据不在内存里时,我们称这个进程为挂起状态。

这样操作系统就可以空出一些内存给其他进程用。

这个挂起状态适用于所有等待的进程。

左边是阻塞态,右边是挂起状态 

        挂起态也属于一种等待状态,和阻塞态稍微有点类似,

        不过,挂起态的本质原因是操作系统中的内存资源不够,进程的数量太多,导致系统的压力非常大,因此此时操作系统为了释放压力,必须将一些暂时不需要运行的进程中的相关的代码和数据先置换到磁盘上,一般情况下,磁盘都会预先留出一部分空间来完成这个过程:swap分区,此时进程不在内存中,而在磁盘上。

挂起状态对用户是不可见的,这是操作系统的一种行为。

2.Linux内核源代码里的运行状态

了解了操作系统里的进程状态,我们现在可以来看看具体的例子——Linux的进程状态

一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)。

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

/*
* 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 */
};

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

2.1. R(running):运行态

R状态就是Linux里典型的运行状态

我们来看看一个例子

我们编译运行,换另外一个账号来

我们发现不是r状态???

这个是为什么?不急

我们修改一下代码

我们编译运行

我们再用别的账号看看

都是R状态了

我们看到的现象是程序不断地输出,但是为啥是处于S状态呢??
        原因很简单,主要是因为我们在程序的源代码中调用printf函数,这个函数是向终端输出内容的,是一种向慢设备输出的一个过程,因此,因为我们知道处理机的速度是非常快的,慢设备的速度是非常慢的,

        所以,这个过程中大部分时间都是处于等待的过程,因此此时进程所属的状态是S状态

 S就对应我们的阻塞状态了

2.2. S(sleeping):阻塞状态——浅度睡眠

这种状态是一种阻塞状态,表示的是等待外设的就绪

话不多说,我们看例子

这个是因为大部分时间在等待,极小部分在输出,所以是阻塞状态

我们再看一个例子

这个和上面我讲的阻塞状态是不是差不多啊 

我们使用top命令看一下我们的系统

为什么这么多S状态,其实他们都是在等我们输入指令,等指令进去了,不就运行起来了吗?? 

S状态是一种浅度睡眠状态,可以随时被中断

当然我们也可以采用kill -9 进程的pid 命令来杀掉该进程

上面举过例子了,我们就不说了

2.3. D(disk sleeping):阻塞状态——深度睡眠

阻塞状态其实有两种,一种是S状态,另外一种是D状态

D状态属于一种深度睡眠的状态,这个状态一旦发生,就不容易被中断,一般情况下只能通过关机或者断电才能够中断,其发生本质原因是等待磁盘资源的分配,磁盘的资源压力过大

我们可以通过一个故事来理解D状态 

        有一个进程要完成I/O操作,此时进程呢他那朝着远端的磁盘大喊了一声磁盘啊,我这有1GB的数据我把数据存到你存到磁盘的某个位置上,你帮我做一下吧。

        然后呢,当磁盘听到这句话,磁盘慢慢悠悠的探出来个脑袋,就给进程说好的,那你把数据给我吧,我去帮你写,于是磁盘就抱着进程的数据去做写入了。

        磁盘进行写入时,这个进程它翘着二郎腿,悠哉悠哉的在这里等待,此时操作系统它从旁边路过,操作系统现在压力很大,不知道什么原因,反正整个计算机里,进程变得非常多了,而且每一个地方都特别吃资源,内存资源严重不足,当前操作系统火急火燎的在想办法,当他从这个进程旁边路过的时候,他瞥了一眼这个进程,觉得他很不爽,他把他能做的全做了,包括进程所对应的代码和数据能置换全置换了,所以操作系统一气之下啊,就对着这个进程说,你这个进程这么没眼色,你没看到当前内存已经被撑得快爆了吗?我已经马上快挂掉了,你还在这里翘着二郎腿,你还在等着呢,不要等了,所以操作系统直接把这个进程干掉了。(如果系统压力已经在内存辗转腾挪已经解决不了时操作系统就要开始动手杀进程了,所以操作系统是会杀掉处于某一些他自己判定不太重要的进程)

         删掉了之后,此时这个刚刚在写入数据的磁盘也遇到了问题,当前他在写入时发现写到一半时突然发现磁盘空间不够了,这个磁盘就转过头,探出脑袋就对这个进程说进程啊,不好意思,我写入失败了,唉,进程呢,进程怎么不见了?那我数据没有写成功该怎么办呢我到底是再尝试一次呢还是我再等一等,我该怎么做决策呢,(有的硬件直接丢掉的有的给你再试着写一下,大部分都是掉直接丢掉),此时数据就被丢失了,丢失的数据非常重要。

        于是呢,法官就把操作系统,进程和磁盘一并带上了法庭,法官先审问操作系统说你怎么能杀掉进程呢?操作系统却说:“请问用户有没有赋予我管理他软件资源的权利?请问我杀掉进程是不是在特别极端的情况下杀掉了?请问我有没有履行我操作系统的职责?我的职责是保证系统不挂数据丢失和我有什么关系,我就是在做我操作系统该做的事情,如果你判我有罪了,那么请问下次如果再碰到这样的极端情况,那到底我还做不做,我如果不杀最后导致操作系统挂掉,第一,它的数据该丢还是会丢,第二,可能还会影响其他进程,那么这个责任谁来承担。”法官说:“唉,这话说的还挺有道理啊,是的,他只是履行了他的义务,而且他确实是在极端情况下做的这个事情”于是法官又把目光转移到了磁盘身上,对磁盘说:“你怎么能把人家数据丢掉呢?”此时磁盘就说:“法官大人不要怪我,在这件事情上我就是个跑腿的人家让我干啥就干啥我在写入的时候就已经告诉了对方,我可能会失败,我让他去等的我要给他结果的,我的工作模式从来向来都是这样,从我出生的时候就是这样,其他磁盘也是这样,如果你认为我有罪的话,那是不是我的兄弟磁盘,我的朋友磁盘也有问题,那是不是我们把储存的逻辑全部都改下,我就是个跑腿的,我怎么能决定这个数据写入失败该怎么办,数据被丢失是因为我还有其他事情要做,因为有其他进程也要让我写入”,法官听了之后觉得也有道理,那么就把视角转向了受害人进程的身上,反正操作系统没错,磁盘也没错,那就是你的问题了,在法官刚看向进程的时候,进程扑通一下就跪下来了对法官说:“法官我可是受害人啊,我是被杀掉的,我就是静静的坐在那里,人在家中坐,锅从天上来,我是被杀掉的,我怎么能有错呢啊”。法官此时一想,唉,他们三个好像说的都挺有道理,难道是我错了吗? (凡是有争议的地方,那么一定是因为制度设计不合理) 法官最后说算了算了,你们三个都回去吧,我完了去改改操作系统,法官就把进程的状态加了一个状态,那么这个状态就是只要这个进程当前正在有写入任务交给了磁盘,如果磁盘没有办法立马响应的话,需要进程等待这个进程绝对不能以浅度睡眠的方式运行即S状态,必须把自己设为D状态,从我们的源代码方式规定,D状态进程任何人都不能杀掉,包括操作系统 所以故事又来了,这个进程呢,又喊出来磁盘啊,磁盘说怎么又是你,进程说没关系,这次写吧,这次我不怪你了,所以呢,磁盘抱着数据就去进行写入了,那么当前这个进程翘着二郎腿在这里啊,嗑着瓜子,在这里等,一个操作系统路过了操作系统说你这个进程话还没说完,进程就亮出了一个免死金牌,一边看去,不要影响我,我还忙着呢,你现在是没资格杀我啊,所以操作系统一看行啊,有这回事就行反正我尽力了,人家不让杀他就不让杀我去那么去杀别的进程,反正对我来我做了我的工作,你没有被杀死那后果自己承担,所以当前进程的此时就处了一个状态叫D状态,当它进行等待时进程不可被杀死,这样的话就不会存在刚刚我们的这种问题了所以当磁盘把数据写完之后,返回来时告诉进程进程啊,你的数据写完了这个时候得到了结果之后,进程再把自己的状态由D状态恢复成R状态。

我们最后再来总结一下这个小故事:
        在这个故事中,一个进程请求磁盘保存1GB数据。磁盘接受了任务,进程则进了等待队列。由于系统内存紧张,操作系统决定杀掉该进程以释放资源。磁盘在写入过程中发现空间不足,但进程已被杀死,数据丢失。法官审问后,决定给当前的进程状态进行修改——增加D状态,保证以后正在进行I/O操作的进程不会被杀死。这样,即使操作系统再次路过,也无法杀掉处于D状态的进程,避免了数据丢失。

D状态的进程不接受任何请求 

系统里一旦出现一个D状态,就说明这个系统块崩溃了

2. 4. Z和X状态:死亡与僵尸

  • X状态:表示进程已经死亡,处于终止态
  • Z状态:表示进程处于僵尸状态,已经不用上处理机运行的状态

我们来讲一个故事,

        某一天,大街上突然一个人倒下路边死了,路人走上前一看,立马拨打了120和110,120来了以后说这个没救了,就走了,110来了之后就把现场围起来,查死因,查明白了才会叫别人来收尸,操作系统也是类似的,操作系统需要确认进程是不是处于X状态,就把在查询的这个时间将这个进程设置为Z状态 

        我们需要知道,在Linux中,一个进程退出时不会马上进入X态,而是会进入Z状态,那么这是为什么呢?
        一个进程被创建就是为了完成某项任务,因此,当进程结束的时候,需要将进程的结束信息(是否完成任务等)告诉给父进程或者操作系统,也就是说:操作系统在一个进程退出后,这个进程不会马上处于X态,而是会为其维护一个称为僵尸态的状态,维护的目的是:就是维护进程的退出信息,以便将这个信息告知给操作系统或者父进程

如何模拟僵尸进程?
        一般情况下,我们都会创建进程,这个进程会有相应的父进程,当子进程退出而父进程没有退出的时候,此时子进程所处的状态就是僵尸状态(Z状态)

我们来模拟一个30s的僵尸状态

 我们让父进程一直执行,子进程只运行5秒,且父进程没有针对子进程干任何事情

 while :; do ps aux | grep test | grep ‘mytest'; sleep 1 ;echo "###############" ; done

我们看看刚开始的 

我们看看执行了一段时间后 

此时可以看出,该进程处于由S状态变成了Z状态

我们看到了<defunct>了,这个就是Z状态的标志

进程一般退出时,如果父进程没有主动回收子进程的信息,子进程会让自己一直处于Z状态,进程相关资源尤其是struct task_struct不能被释放

2.4.1.僵尸进程的危害

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

因此,当出现僵尸进程的时候,我们必须合理地解决好僵尸进程的问题

2.5.孤儿进程

上面子进程先退出,子进程就变僵尸状态,那父进程先退出的情况呢?

当一个进程的父进程提前退出的时候,子进程还在运行,此时子进程会被父进程的父进程领养,即会被bash(1号进程)领养

我们来模拟一下

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	pid_t id = fork();
	if (id < 0) {
		perror("fork");
		return 1;
	}
	else if (id == 0) {//child
		printf("我是子进程,我的pid是:%d,我的ppid是:%d\n", getpid(), getppid());
		sleep(100);
	}
	else {//parent
		printf("我是父进程,我的pid是:%d,我的ppid是:%d\n", getpid(), getppid());
		sleep(3);
		exit(0);
	}
	return 0;
			

}

 此时我们会发现,这个进程的状态相比于上述的进程来说是没有+的,这种进程状态没有+,是不能被Ctrl+C杀掉的,此时必须使用kill -9 进程的pid命令才能够杀掉进程

查看1号进程:使用top命令

从上图中我们可以看出,1号进程本质就是root,名为:systemd

系统为什么要领养这个子进程?

        因为这个子进程未来退出的时候需要被系统回收——这个工作必须要父进程来做

2.5. T(Stopping):暂停状态

我们看个例子

这个进程的PID是 107338

当进程被kill -19 pid 暂停的时候,此时进程会处于暂停状态

 想要将进程进行恢复的话可以采用kill -18 pid进行恢复

S状态和T状态的区别是什么? 这个大家去思考

2.6. t(tracing stopping):追踪暂停状态

一般是在调试的过程中遇到断点的状态

我们看个例子

 

我们现在打断点,然后运行起来

 

 我们换个账号来看看

2.7.T和t的区别:

在进程状态中,“T”代表停止状态或常规暂停,而“t”代表追踪停止。

二者的主要区别在于,

  • “T”状态通常可以通过发送SIGSTOP信号给进程来使其停止,而这个被暂停的进程可以通过发送SIGCONT信号来继续运行。
  • “t”状态主要发生在进程被调试过程中遇到断点时,此时进程会进入追踪停止状态。
  • 39
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值