欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > 【Linux】进程状态和优先级

【Linux】进程状态和优先级

2025/2/4 16:51:10 来源:https://blog.csdn.net/s_little_monster/article/details/145359601  浏览:    关键词:【Linux】进程状态和优先级

在这里插入图片描述
个人主页~


进程状态和优先级

  • 一、进程状态
    • 1、操作系统进程状态
      • (一)运行态
      • (二)阻塞态
      • (三)挂起态
    • 2、Linux进程状态
      • (一)R-运行状态
        • 并发执行
      • (二)S-浅度睡眠状态
      • (三)D-磁盘休眠状态(深度睡眠)
      • (四)Z-僵尸状态
    • 3、孤儿进程
  • 二、进程优先级
    • 1、基本概念
    • 2、优先级的计算
  • 三、Linux的调度

一、进程状态

1、操作系统进程状态

操作系统的进程属性状态一共有三种模型,分别是三态、五态、七态模型,我们下图给出的是五态模型,七态模型多出的两态就是两种类型的挂起态,所以我们可以说操作系统进程一共有六种状态

其中新建态和退出态顾名思义就是新建进程的状态和结束进程的状态就绪态就是进程加载完成后等待调度的状态,下面我们着重来强调一下运行态、阻塞态和挂起态
在这里插入图片描述

(一)运行态

运行态是指进程正在处理器上运行,它所对应的程序代码正在被 CPU 执行的状态,处于运行态的进程能够占用 CPU 资源,并按照程序的逻辑顺序依次执行指令进行数据处理与其他进程或系统进行交互等操作,是进程真正处于活动执行的状态

(二)阻塞态

阻塞态是指进程因等待某种事件的发生而暂时无法继续执行的状态,处于阻塞态的进程不能占用 CPU 资源,此时进程会暂停执行,直到所等待的事件完成或条件满足,才有可能重新进入就绪态或运行态

(三)挂起态

挂起态是指进程暂时被操作系统从内存中转移到外存(如磁盘)上,或者虽然仍在内存中但被标记为暂停执行的一种状态,处于挂起态的进程不再参与 CPU 的调度,其运行被暂停,直到满足特定条件后才可能恢复到其他可执行状态

2、Linux进程状态

操作系统的进程状态分有运行态-R、睡眠态(浅度睡眠)-S、磁盘休眠态(深度睡眠)-D、停止态-T(t)、死亡态-X、僵尸态-Z
其中睡眠态和停止态是有区别的,睡眠态是在进程等待的过程中可能的一种状态,停止态是无论进程的状态如何,都都可以进行的一种状态
死亡态就是进程结束后资源全部回收的一种状态,不再对系统产生负担
在这里插入图片描述

//kernel源代码里关于Linux进程状态的定义: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-运行状态

Linux内核在运行状态时,除了我们上面操作系统都有的行为以外,它是一个双向链表的结构,在Linux中存在一个结构体叫做运行队列,它负责维护两个 task_struct* 类型的指针:head、tail,分别指向task_struct链表的头和尾,这样我们就可以通过它对双向链表中的节点进行移动

因为一个进程一般都是死循环的状态,即我们之前谈到过的while(1)这样的,那么如果我们把进程放到CPU上它就一直跑下去吗?显而易见的是不会的,原因是每一个进程都有一个叫做时间片的概念,在CPU上执行一段时间它自己就要下来,让其他进程执行,这时,head = head -> next , tail = tail -> next,让下一个进程进到CPU中执行,由于这个CPU的速度是非常快的,所以我们是感知不到进程切换的,这样就可以做到在一个时间段内,所有进程代码都会被执行,这个过程叫做并发执行,而在这个过程中,把大量的进程不断放到CPU拿出CPU的动作,叫做进程切换

并发执行

多个进程在一个CPU下采用进程切换的方式,在一段时间内,让多个进程都得以推进,称之为并发,进程在CPU被来回切换,我们要做到每个进程在下次来的时候还能从上次结束的位置继续进行,这里就需要我们进程对上下文进行保存和恢复,这部分信息存在于哪里呢?答案是寄存器,CPU寄存器里面保存的是进程的上下文,因为寄存器是最快的存储结构,能够大大提高效率

(二)S-浅度睡眠状态

处于 S 状态的进程正在等待某个事件的发生或资源的可用,此时进程会暂时停止执行,进入睡眠状态,它可以被某些信号中断唤醒,进程在等待期间,仍然占用着一些系统资源,如内存空间等,用于维持其进程上下文和相关的数据结构,以便在事件发生或资源可用时能够快速恢复执行

例如一些IO操作,进程需要从磁盘读取数据、等待网络数据的接收或者等待用户在终端输入数据等情况,由于 I/O 操作相对较慢,进程会进入可中断睡眠状态,直到 I/O 操作完成,我们可以通过kill命令杀死进程
在这里插入图片描述

在这里插入图片描述
这里的sleep是因为我们使用了sleep函数,如果我们在程序当中不使用sleep函数,我们多次查看这里还是S状态,原因是我们在先前提到过,Linux下一切皆文件,屏幕打印也是一个文件IO的过程,所以这个进程大部分时间都在等待文件的IO,所以大部分时间都是S状态
在这里插入图片描述
下图可见这个进程是可以被杀死的
在这里插入图片描述

(三)D-磁盘休眠状态(深度睡眠)

深度睡眠状态对应的也是上面的阻塞状态,分工与浅度睡眠状态不同,相当与是将阻塞状态细分为两种状态,处于 D 状态的进程不会被信号中断唤醒,即使是像 kill 这样的强制终止信号也无法使其醒来,它只能在等待的事件完成后才会被唤醒,这是为了确保进程在执行一些关键操作时不会被意外打断,保证操作的完整性和稳定性

这里就不演示D状态了,只是如果有一个进程一直占用CPU的话,对计算机来说是一种负担

(四)Z-僵尸状态

僵尸状态其实也是死亡态的一种,只不过它并没有完全对系统资源进行回收,是当进程退出并且父进程没有读取到子进程退出的代码时产生的一种进程,叫做僵尸进程,僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码,所以只要子进程退出,父进程还在运行,且父进程没有读取子进程状态,子进程就进入了僵尸状态
在这里插入图片描述

在这里插入图片描述
我们可以通过while :; do ps aux | grep 16621; sleep 1; done来每秒循环查看16621进程的进程状态
在这里插入图片描述
僵尸进程一直不退出,就要一直被PCB维护,相当于不回收的一段空间,如果一直不回收,最终会导致内存泄漏

3、孤儿进程

上面提到的是子进程退出,父进程仍在运行且不读取子进程退出的状态,那么子进程就会成为一个僵尸进程,这里的孤儿进程是父进程先退出,子进程后退出,父进程退出后到子进程退出前这段时间子进程被称为孤儿进程,但没有父进程就不能进行资源释放了,这时,该进程会被系统领养也就是1号进程称为它的养父,它的父进程变为1号进程,我们称作孤儿进程被1号进程领养,最后退出时被1号进程回收
在这里插入图片描述
这里我们可以通过while :; do ps axj | grep process2; sleep 1; done来在右侧每隔一秒循环打印程序process2中的各个进程,我们发现在五秒后子进程的父进程变为1号进程

在这里插入图片描述

二、进程优先级

1、基本概念

大部分计算机只有一个CPU,而进程是有很多个的,这样就会产生多个进程去抢夺一个CPU的情况,这时就需要有优先级的存在去判定谁先来到CPU
CPU资源分配的先后顺序就是进程优先级,优先级高的先执行,通过ps -l可以查看:UID-执行者身份,也就是用户名,PID-该进程ID,PPID-该进程父进程ID,PRI-该进程优先级,NI-该进程nice值,其中PRI和NI就是和进程优先级密切相关的两个量
在这里插入图片描述

2、优先级的计算

PRI就是进程优先级,值越小,优先级越高,一般我们创建的进程PRI都是80,NI值可以修正优先级,PRI(NEW) = PRI(OLD) + NI,NI范围是-20~19,也就是我们所创建的进程的优先级一共有40级,需要注意的是,PRI(OLD)是一直不变的量,从创建出一个进程开始他就是这个量,只能通过后天NI值的改变修正影响PRI

三、Linux的调度

简单串联一下今天的内容,并且将Linux对于运行队列的调度通过一个简单的模型来解释

我们通过一个结构体来解释进程运行逻辑,首先我们需要有一个指针数组running,其中数组的每个元素序号都代表着优先级大小,其中,0~99这一百个位置是给其他种类的进程用的,我们所创建的进程只用100-139这四十个位置,刚好对应上前面的优先级范围,也是四十级,优先级范围是60-99,最终就会转化为100-139,每个元素就是一个指针,指向该优先级的运行队列

运行开始,先从优先级高的开始运行,再进行进程切换的过程中,优先级相同的进程运行完后,将running指针给到waiting,waiting可以看做是一个指针都指向空的指针数组,例如,running[100]所指向的进程都执行过时间片时长了,waiting[100] = running[100] ,然后running[100] = NULL,然后继续往下执行,直到所有的进程都执行过时间片时长一次,然后再将run和wait的值互换,此时,run指向waiting,wait指向running,内部是通过这个二阶指针决定哪个是运行队列哪个是等待队列的,waiting成为了运行队列,running成为了等待队列,然后按照同样的方法进行值的交换

在以上的过程中,需要有一个量来精确得知running或waiting啥时候为空,这就是isempty,这个bitmap是需要我们自己定义的,我们定义一个char*[5]类型的bitmap,刚好可以存储40个比特位,0-39对应running中的100-139,然后其中有数据该比特位就为1,没有数据就为0,在我们进行数据交互的时候改变bitmap的值,当这40个比特位都为0,即isempty为0,此时运行队列为空

struct runqueue
{bitmap isempty;//位图task_struct** run;//指向runningtask_struct** wait;//指向waitingtask_struct* running[140];//维护一个运行队列task_struct* waiting[140];//维护一个等待队列
}

在这里插入图片描述
我们是可以通过前后指针来访问该进程的全部内容的,在task_struct的双向链表中,其实指向前后的两个指针是放在一个结构体的,我们把这个结构体叫node

struct node
{struct node* next;struct node* prev;
}
struct task_struct
{//其他属性//...struct node* start;struct node link;
} 

start指向link,link中有指向前后进程的指针,该进程就可以写作(task_struct*)(start - &(task_struct*)0->link),下面是该写法的解释:

  1. (task_struct*)0
    这是将整数 0 强制转换为 task_struct* 类型的指针,也就是让这个指针指向地址 0,这里只是为了构建一个概念上的 task_struct 结构体,实际上并不会真正访问地址 0,只是利用它来获取结构体成员相对于结构体起始地址的偏移量
  2. ((task_struct*)0)->link
    这一步是通过前面得到的指向地址 0 的 task_struct 指针来访问 link 成员,由于指针指向的是地址 0,所以 ((task_struct*)0)->link 得到的就是 link 成员相对于结构体起始地址的偏移量,因为在内存中,结构体成员的地址是相对于结构体起始地址的偏移,而这里起始地址为 0,所以 ((task_struct*)0)->link 的值就是 link 成员在 task_struct 结构体中的偏移字节数
  3. &((task_struct*)0)->link
    对 ((task_struct*)0)->link 取地址,得到的就是 link 成员相对于结构体起始地址的偏移量的地址,实际上就是偏移量的值本身,例如,如果 link 成员在 task_struct 结构体中是从第 20 个字节开始的,那么 &((task_struct*)0)->link 的值就是 20
  4. start - &((task_struct*)0)->link
    start 是 link 成员的实际内存地址。用 start 减去 link 成员相对于结构体起始地址的偏移量,就可以得到包含 link 成员的 task_struct 结构体的起始地址,这是基于内存地址的计算原理,因为结构体成员的地址等于结构体起始地址加上该成员相对于起始地址的偏移量,反过来,结构体起始地址就等于成员地址减去偏移量
  5. (task_struct*)
    最后将计算得到的地址强制转换为 task_struct* 类型的指针,这样就得到了指向包含 link 成员的 task_struct 结构体的指针

今日分享就到这里了~
在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com