当前位置: 首页 > news >正文

进程状态+进程优先级+进程上下文切换解读

一、进程状态

什么是进程状态?

进程状态指的是在操作系统中进程在生命周期中所处的不同阶段。

进程状态有哪些呢?
在这里插入图片描述

我们可以看到上述图片
进程状态分为:创建状态、就绪状态、运行状态、阻塞状态和终止状态

所有的操作系统在实现进程状态变化的时候都要符合上面的理论。
进程在运行的时候是需要资源的,也就是说进程之间是有竞争的,它们竞争的资源分为两类:外设资源和CPU资源(输入输出设备统称为外设资源,运算器和控制器统称为CPU资源)

大部分的操作系统内部都要给每个CPU设置一个调度队列多个CPU就要多个调度队列,操作系统对调度队列进行的管理方式就是先描述在组织

综上所述,我们就知道了在操作系统中就要有一个struct runqueue

struct runqueue
{//队列属性int num; //队列个数struct task_stuct* head;
}

所以说CPU的调度队列是怎么样拿到该进程的呢?

struct task_struct
{//所有的属性struct list_head tasks;
}
struct list_head
{struct list_head* next;struct list_head* prev;
}

看上述的代码,在struct task_struct里面有一个struct list_head tasks的变量,struct list_head里面包含两个指针分别是next和prev指针,它是通过struct list_head来实现双链表的。所以综上所述我们就可以知道,只要我们得到struct task_struct结构体中的struct list_head tasks变量的地址,我们就可以利用偏移量来得到task_struct的起始地址。

既然我们知道task_struct的起始地址 = struct list_head变量的地址 - 偏移量了,那偏移量该怎么得到呢?

假设在地址为0处有一个struct task_struct变量,去访问该变量中的struct list_head变量并进行取地址:&((struct task_struct*)0->tasks)。我们要知道的是,对于每一个进程来书,struct task_struct结构体的大小是固定的,所以说struct list_head在struct task_struct中的偏移量也是固定的。那么struct task_struct* start = &stract list_head task - &((struct task_struct*)0->tasks).

所以说在linux中PCB实现双链表的这种方式就再也和类型没有关系了,struct task_struct既可以属于链表,同时也可以属于其他数据结构。

在linux中是没有就绪状态的,所以我们认为就绪状态和运行状态不分家。在有些系统中,认为PCB处在运行队列中但是未分配CPU资源叫做就绪状态,正在CPU中执行的进程叫做运行状态。

每个进程都要竞争CPU资源,所以就得把进程链入到调度队列中。我们把上述这个行为叫做进程在CPU的调度队列当中进行排队。进行排队时,该进程的状态就叫做(r)运行状态

运行状态:该进程的PCB必须处在CPU的调度队列中,只要在调度队列中,进程就叫做运行状态,随时等待CPU的调度执行

阻塞状态:当进程无法继续执行,需要等待某些非CPU资源就绪时,就会进入阻塞状态(进程进入阻塞状态,PCB会被移除运行队列,进入等待队列)

挂起状态:当内存空间严重不足时,操作系统会将进程的代码,数据,堆栈等内存资源移出内存,换出磁盘的交换区里,但进程的PCB仍然保留在内存中,这就叫挂起状态

新建状态:操作系统为进程分配必要的资源,并为进程分配唯一的标识符

下面的状态在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 */
};

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

看下面这个代码,代码运行时我们发现它的状态怎么会是S(睡眠状态)状态呢?不应该是R(运行状态)吗?
在这里插入图片描述
在这里插入图片描述
接下来我们再改一下代码在这里插入图片描述
很奇怪的就是为什么将while中的printf屏蔽了就变成了R状态呢?

在这里插入图片描述
原因就是printf是向显示器打印内容的,访问的是外设资源,外设的速度很慢,很大概率是无法就绪的,所以就是S状态。但是有少数的概率会显示R状态

其实S这个状态是一种浅度睡眠的状态,我们可以使用ctrl+c来杀掉浅度睡眠,而D这种状态则是深度睡眠,其不对任何事件进行响应,连操作系统都不能杀掉,除非是说进程自己醒过来。

僵尸进程

模拟实现僵尸进程
在这里插入图片描述
运行代码得到的结果如下:

在这里插入图片描述
在这里插入图片描述
我们看上述代码会发现子进程比父进程先退出,父进程就会先处于一个僵尸状态代码、数据释放掉,但是会保留struct task_struct,PCB会自动记录进程退出时的退出信息,方便父进程读取退出码。

假设父进程一直不处理,一直处于僵尸进程,PCB一直存在于内存中,这就会导致内存的泄露。

那如果我们是父进程先退出呢?子进程会怎么样?

孤儿进程

模拟实现孤儿进程
在这里插入图片描述
运行结果如下:
在这里插入图片描述
在这里插入图片描述
我们查看进程状态发现一个很奇怪的东西,就是这个1号进程是什么呢?

子进程被操作系统一号领养了,一号是一个叫做init的进程

在linu中init进程是什么?

它是系统启动后运行的第一个用户态进程,通常具有进程 ID(PID)为 1。init 进程的主要作用是初始化系统环境、启动其他关键服务和守护进程,并管理系统的运行级别。

为什么要领养子进程呢?

因为孤儿进程也会退出,但是父进程早早就没了,所以会被系统领养,本质上是为了回收孤儿进程防止内存泄漏。

进程退出不应该会变成僵尸进程吗?为什么没有看到父进程的僵尸状态呢?

子进程的父进程的父进程是bash(外壳程序),父进程一旦退出,就会被bash回收,所以看不到父进程的僵尸状态,具体过程看下面两张图。

在这里插入图片描述

在这里插入图片描述

二、进程的优先级

什么是进程的优先级?

在操作系统中,进程的优先级是用于决定进程调度顺序的一个重要参数。优先级越高,进程获得 CPU 时间片的机会就越大。总而言之就是进程得到某种资源的先后顺序

在这里插入图片描述
我们使用ps -al命令查看进程的优先级

UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值

PRI越小代表该进程优先级越高,它就是一个整数,存在于task_struct中,而NI是一个修正数据:PRI(new) = PRI(old) + nice 在linux中nice的取值范围是-20~19,一共40个级别。

使用top来改变PRI,步骤如下:

top指令后,-r进入pid,接着输入pid
在这里插入图片描述
在这里插入图片描述

补充进程的性质:

竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发: 多个进程在⼀个CPU下采用进程切换的方式,在⼀段时间之内,让多个进程都得以推进,称之为并发

三、进程切换

在这里插入图片描述
CPU内有很多寄存器,这些寄存器就是一套储存空间,寄存器只有一套,属于CPU本身,但是寄存器内部的数据可以有很多。进程在被切换的时候,进程PCB会保存该进程的上下文数据,以便进程再被调度的时候可以恢复数据。

看上述图片中红色括号中的queue数组,这个是什么呢?

其实queue数组类型是一个struct list_head类型,简单的理解就是认为它是一个struct task_struct*类型,它可以使用struct list_head中的节点指针对进程进行关联,这个进程的大小是140,下标[0,99]我们不需要理会,[100,139]刚好是40个,然而我们进程范围是[60,99]刚刚好也是40个,所以我们知道操作系统根据进程的优先级将进程挂接在对应的下标上。

在linux中对进程调度的时候,用到一个bitmap[5],利用位图的方式对queue数组进行遍历,这样能提高效率。但是为什么是5呢?因为一个整型4个byte,32个bit,那么5个就是160个bit,刚刚好对应下标140.

对应的比特位的位置表示的是数组中第几个队列比特位的内容表示的是队列是否为空。

我们再看向上面的图片会发现有两个指针分别是:struct prio_array*,struct prio_array* expired,这两个指针分别是活跃队列过期队列
CPU调度的时候直接从
active指针找到对应的queue[140],新增进程或者时间片到的进程,从CPU上剥离下来,被剥离下来的进程只能重新入队列,入过期队列。

既然如此为什么要有nice值呢?

如果我们要改PRI值的时候,我们能直接改吗?答案是不能的,因为我们直接改PRI值那么进程就会被立即迁移到对应的队列上,而有了nice值的话就不会影响active队列中进程的调度,在expired队列中对应的进程的优先级做出调整。

等到CPU调度完所有进程,所有进程都会跑到过期队列。一旦active没有进程了,这时只需要交换active,expired指针内容就可以了。

http://www.dtcms.com/a/324359.html

相关文章:

  • 不同hash加密类型的hashfile.txt文件
  • C# 中常用集合以及使用场景
  • 强制类型转换
  • AI 对齐:让人工智能与人类价值同频共振
  • JavaWeb——maven高级(5/5)-私服(私服的概念与作用、Maven 依赖的查找顺序、上传资源到私服的配置步骤、下载依赖配置条件)
  • 单链表专题---暴力算法美学(2)(有视频演示)
  • actuary notes[2]
  • 单调栈——数位删减
  • Go语言中切片(Slice)的拷贝
  • 自创论述类文本阅读:论温泉
  • PWM波的频谱分析及matlab 验证[电路原理]
  • 【Linux】使用静态 BusyBox 解决操作系统“塌方”问题
  • Premiere准备工作
  • AQS的详细讲解
  • Java对接支付宝,回调验签失败
  • 活动策划(展会、年会),在线工具能快速出邀请函不?
  • [创业之路-537]:经营分析会 - 销售目标以及支撑、关键策略、主要行动措施、资源保障、人才储备
  • 在 JDK 17 上完整观察 synchronized 锁升级过
  • 嵌入式第二十四课!!linux应用软件编程与文件操作!!!
  • Java 基础编程案例:斐波拉契数与从输入交互到逻辑处理
  • NodeJs学习日志(4):路由合并_环境配置_常用文件目录
  • HarmonyOS之module.json5功能详解
  • AI测试助手如何让Bug无处可藏
  • 湖南(源点咨询)市场调研 如何在行业研究中快速有效介入 中篇
  • 深入浅出DBSCAN:基于密度的聚类算法详解与Python实战
  • github上传文件
  • Navicat 无限适用
  • Tesseract训练个人字库操提高准确率操作全流程(详细)
  • 新手向:Python制作简易音乐播放器
  • Python中的 __name__