linux内核—进程调度(核心)
目录
核心函数__schedule()
处理过程
1、选择下一个进程
2、切换线程
1)切换进程的虚拟地址空间
2)切换寄存器
3)执行清理工作
核心函数__schedule()
主要的调度程序
进入次函数的主要方法是:
1、显示阻塞:互斥、信号量、等待队列等;
2、在中断和用户空间返回时检查TIF_NED_RESCHED标志。参考arch/arm64/entry_64.s
为了在任务中抢占,调度器在计时器中设置标志中断处理程序schedule_tick
3、唤醒不会真正导致进入schedule,他们添加任务到任务队列而已。
如果添加到运行队列的新任务抢占了当前任务,则唤醒设置TIF_NED_RESCHED,schedule获取在最近的情况下调用。
如果内核是可抢占的(CONFIG_PREEMPTION=y)
在syscall或异常中断上下文中,位于下一个最外层preempt_enable。
一个IRQ上下文中,从中断处理程序返回到可抢占上下文。
如果内核是非抢占的
则在下一个
cond_sched()调用,
explict_schedule()调用
从syscall或异常返回到用户空间
从中断处理程序返回到用户空间
必须在禁用抢占的情况下使用
static void __sched notrace __schedule(bool preempt)
preempt表示是否抢占调度,true抢占,强制剥夺当前进程对处理器的使用权,false表示主动调度,把当前进程让出处理器。
处理过程
1)调用pick_next_task以选择下一个进程。
2)调用context_switch以切换进程
1、选择下一个进程
/** Pick up the highest-prio task:*/
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{const struct sched_class *class;struct task_struct *p;//优化,如果所有进程属于限期或公平调度类,可以直接使用公平调度类的pick_next_task方法if (likely((prev->sched_class == &idle_sched_class ||prev->sched_class == &fair_sched_class) &&rq->nr_running == rq->cfs.h_nr_running)) {p = pick_next_task_fair(rq, prev, rf);if (unlikely(p == RETRY_TASK))goto restart;/* Assumes fair_sched_class->next == idle_sched_class */if (!p) {put_prev_task(rq, prev);p = pick_next_task_idle(rq);}return p;}restart:put_prev_task(rq, prev);for_each_class(class) {p = class->pick_next_task(rq);if (p)return p;}
}
如果当前进程属于空闲调度类或公平调度类,并且所有可运行的进程属于公平调度类,那么直接调用公平调度类的pick_next_task方法。如果公平调度类没有选择下一个进程,那么从空闲调度类选择下一个进程。
一般情况,从优先级最高的调度类开始,调用调度类的pick_next_task方法来选择下一个进程,如果选中了下一个进程,就调度这个进程。否则从优先级低的调度类选择下一个进程。
优先级从高到低依次是 停机、限期、实时、公平和空闲
1)停机调度类:pick_next_task_stop,如果运行队列中的成员stop指向某个线程,那么这个进程在运行队列中,那么返回成员stop指向的进程,否则反馈空指针。
2)限期调度类:pick_next_task_dl,从限期运行队列中选择截止期限最小的进程。(就是红黑树最左边的进程)。限期进程不支持任务组。
3)实时调度类:pick_next_task_rt,
如果实时运行队列没有加入运行队列,返回空指针。
从根任务组在当前上的实时运行队列开始,选择优先级最高的调度实体
如果选择的调度实体是任务组,继续从这个任务组在当前处理器上的实时运行队列中选择优先级最高的调度实体。重复这个步骤,知道选中的调度实体为进程为止。
4)公平调度类:pick_next_task_fair
从根任务组在当前处理器上的公平队列中,选择虚拟运行时间最小的调度实体,就是红黑树中最左边的调度实体。
如果选中的调度实体是任务组,继续从这个任务组在当前处理器上的公平运行队列中选择虚拟运行时间最小的调度实体。重复这个步骤,直到找到调度实体为进程为止。
5)空闲调度类:pick_next_task_fair
返回运行队列的成员idle指向的空闲线程。
2、切换线程
context_switch 工作主要如下
1)switch_mm_irq_off 负责切换进程的用户虚拟地址空间。
2)switch_to 负责切换处理器的寄存器
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,struct task_struct *next, struct rq_flags *rf)
{prepare_task_switch(rq, prev, next);//执行进程切换的准备工作,每种处理器加过必须定义的函数prepare_arch_switch。arch_start_context_switch(prev);//开启上下文切换//内核线程,内核线程没有用户虚拟地址空间,需要借用上一个进程的用户虚拟地址空间,把借来的用户虚拟地址空间保存在成员active_mm中,//内核线程在借用的虚拟地址空间上执行if (!next->mm) { // to kernel//通知处理器架构不需要切换用户虚拟地址空间,这种加速进程切换计数称为惰性TLBenter_lazy_tlb(prev->active_mm, next);next->active_mm = prev->active_mm;//if (prev->mm) // from usermmgrab(prev->active_mm);elseprev->active_mm = NULL;} else { // to usermembarrier_switch_mm(rq, prev->active_mm, next->mm);//切换进程的虚拟地址空间switch_mm_irqs_off(prev->active_mm, next->mm, next);//switch_mmif (!prev->mm) { // from kernel 如果上一个进程是内核线程/* will mmdrop() in finish_task_switch(). */rq->prev_mm = prev->active_mm;prev->active_mm = NULL;//设置空指针,断开它和借用用户虚拟地址空间的联系,把借用的用户虚拟地址空间保存在运行队列pre_mm中}}rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);prepare_lock_switch(rq, next, rf);/* Here we just switch the register state and the stack. */switch_to(prev, next, prev);//切换寄存器状态和栈barrier();//内存屏障return finish_task_switch(prev);//负责在进程切换后执行清理工作
}
执行进程切换的准备工作,每种处理器加过必须定义的函数prepare_arch_switch。
开启上下文切换
切换类型以及所执行的过程。
/** kernel -> kernel lazy + transfer active* user -> kernel lazy + mmgrab() active** kernel -> user switch + mmdrop() active* user -> user switch*/
1)切换进程的虚拟地址空间
switch_mm_irqs_off->switch_mm (架构相关)
static inline void
switch_mm(struct mm_struct *prev, struct mm_struct *next,struct task_struct *tsk)
{if (prev != next)__switch_mm(next);update_saved_ttbr0(tsk, next);
}
如果prev不等于next,进程空间不同,执行__switch_mm切换用户虚拟地址空间
static inline void __switch_mm(struct mm_struct *next)
{unsigned int cpu = smp_processor_id();if (next == &init_mm) {cpu_set_reserved_ttbr0();return;}check_and_switch_context(next, cpu);
}
- 如果切换到内核的内存描述符init_mm,把寄存器TTBR0_EL1设置为保留的地址空间描述符0和保留的零页empty_zero_page的物理地址。
- check_and_switch_context为进程分配地址空间标识符
2)切换寄存器
__notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,struct task_struct *next)
{struct task_struct *last;fpsimd_thread_switch(next);//切换浮点寄存器tls_thread_switch(next);//切换本地存储相关的寄存器hw_breakpoint_thread_switch(next);//切换调试寄存器contextidr_thread_switch(next);//把上下文标识符寄存器CONTEXTDIR_EL1 设置为下一个进程的进程号entry_task_switch(next);//使用当前处理器的每处理器变量__entry_task记录下一个进程的进程描述符地址。uao_thread_switch(next);//根据下一个进程可访问的虚拟地址空间上限,恢复用户访问覆盖(UAO)状态ptrauth_thread_switch(next);ssbs_thread_switch(next);dsb(ish);//数据同步屏障,确保前面的缓存维护操作和页表缓存为何操作执行完/* the actual thread switch */last = cpu_switch_to(prev, next);//切换通用寄存器return last;
}
cpu_switch_to 通用寄存器的切换
arch/arm64/kernel/entry.S
ENTRY(cpu_switch_to)mov x10, #THREAD_CPU_CONTEXTadd x8, x0, x10mov x9, spstp x19, x20, [x8], #16 // store callee-saved registersstp x21, x22, [x8], #16stp x23, x24, [x8], #16stp x25, x26, [x8], #16stp x27, x28, [x8], #16stp x29, x9, [x8], #16str lr, [x8]add x8, x1, x10ldp x19, x20, [x8], #16 // restore callee-saved registersldp x21, x22, [x8], #16ldp x23, x24, [x8], #16ldp x25, x26, [x8], #16ldp x27, x28, [x8], #16ldp x29, x9, [x8], #16ldr lr, [x8]mov sp, x9msr sp_el0, x1ret
ENDPROC(cpu_switch_to)
NOKPROBE(cpu_switch_to)
由被调用函数负责保存寄存器x19~x28.
寄存器x29 ,帧指针(Frame Pointer) 寄存器
栈指针(Stack Pointer ,SP)寄存器
寄存器x30,链接寄存器(Link Register,LR) ,存放函数返回地址
用户栈指针寄存器SP_EL0,内核使用它存放当前进程的进程描述符的第一个成员thread_info的地址。
cpu_switch_to 两个参数,x0存放上一个进程的进程描述符的地址,x1存放下一个进程的进程描述符地址。
寄存器x10,存放进程描述符的成员thread.cpu_context的偏移。
寄存器x8,存放上一个进程的进程描述符的成员thread.cpu_context地址
寄存器x9保存栈指针
返回值时寄存器x0的值,上一个进程的进程描述符的地址。
3)执行清理工作
finish_task_switch
static struct rq *finish_task_switch(struct task_struct *prev)__releases(rq->lock)
{struct rq *rq = this_rq();//运行队列struct mm_struct *mm = rq->prev_mm;long prev_state;if (WARN_ONCE(preempt_count() != 2*PREEMPT_DISABLE_OFFSET,"corrupted preempt_count: %s/%d/0x%x\n",current->comm, current->pid, preempt_count()))preempt_count_set(FORK_PREEMPT_COUNT);//如果prev是内核线程,那么rq->prev_mm存放它借用的内存描述符,置为空指针。rq->prev_mm = NULL;prev_state = prev->state;vtime_task_switch(prev);//计算进程prev的时间统计perf_event_task_sched_in(prev, current);finish_task(prev);finish_lock_switch(rq);//把prev->on_cpu设置为0,表示进程prev没有在处理器上运行;然后释放运行队列的锁,开启硬中断finish_arch_post_lock_switch();kcov_finish_switch(current);fire_sched_in_preempt_notifiers(current);if (mm) {membarrier_mm_sync_core_before_usermode(mm);mmdrop(mm);}//主动退出或者被终止if (unlikely(prev_state == TASK_DEAD)) {if (prev->sched_class->task_dead)prev->sched_class->task_dead(prev);kprobe_flush_task(prev);/* Task is done with its stack. *///释放进程的内核栈put_task_stack(prev);//释放进程描述符put_task_struct_rcu_user(prev);}tick_nohz_task_switch();return rq;
}
参考
https://course.0voice.com/v1/course/intro?courseId=2&agentId=0
相关文章:
linux内核—进程调度(核心)
目录 核心函数__schedule() 处理过程 1、选择下一个进程 2、切换线程 1)切换进程的虚拟地址空间 2)切换寄存器 3)执行清理工作 核心函数__schedule() 主要的调度程序 进入次函数的主要方法是: 1、显示阻塞:互…...
【STM32笔记】__WFI();进入不了休眠的可能原因(系统定时器SysTick一直产生中断)
【STM32笔记】__WFI();进入不了休眠的可能原因(系统定时器SysTick一直产生中断) 【STM32笔记】低功耗模式配置及避坑汇总 前文: blog.csdn.net/weixin_53403301/article/details/128216064 【STM32笔记】HAL库低功耗模式配置&am…...
【期末复习】例题讲解Dijkstra算法
使用场景Dijkstra算法用于解决单源点最短路径问题,即给一个顶点作为源点,依次求它到图中其他n-1个顶点的最短距离。例题讲解Dijkstra算法将图中所有顶点分成两部分,第一部分是已知到源点最短距离的顶点Known(K),第二部分是不知道到…...
Pytorch 基础之张量索引
本次将介绍一下 Tensor 张量常用的索引与切片的方法: 1. index 索引 index 索引值表示相应维度值的对应索引 a torch.rand(4, 3, 28, 28) print(a[0].shape) # 返回维度一的第 0 索引 tensor print(a[0, 0].shape) # 返回维度一 0 索引位置…...
JVM系统优化实践(1):JVM概览
您好,我是湘王,这是我的CSDN博客,欢迎您来,欢迎您再来~这是多年之前做过的学习笔记,今天再翻出来,觉得仍然是记忆犹新。「独乐乐不如众乐乐」,就拿出来分享给「众乐乐」吧。目前大多…...
优秀!19年后,它再次成为TIOBE年度编程语言
新年伊始,TIOBE发布了2022年度编程语言,C时隔19年再度登顶,成为2022年最受欢迎的编程语言。TIOBE在2003年首次统计编程语言的流行指数时,C便成为年度编程语言。2022年,C获得了最高的人气4.62%,紧随其后的是…...
剑指 Offer 26. 树的子结构
摘要 剑指 Offer 26. 树的子结构 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构),B是A的子结构, 即 A中有出现和B相同的结构和节点值。 一、子树解析 思路解析:若树B是树A的子结构,则…...
他是00年的,我们卷不过他...
现在的小年轻真的卷得过分了。前段时间我们公司来了个00年的,工作没两年,跳槽到我们公司起薪18K,都快接近我了。后来才知道人家是个卷王,从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天,原来这位小老弟家里条…...
C#开发的OpenRA的OpenGL创建纹理流程
C#开发的OpenRA的OpenGL创建纹理流程 由于OpenRA采用的是OpenGL来显示游戏画面, 那么它就必然采用纹理来显示了。 并且由于它是2D的游戏,所以3D的模型是没有的,只要使用纹理贴图,就可以完全实现了游戏的功能。 OpenGL的纹理要起作用,需要经过一系列的动作。 先要使用glGen…...
3D目标检测(一)—— 基于Point-Based方法的PointNet系列
3D目标检测(一)—— PointNet,PointNet,PointNeXt, PointMLP 目录 3D目标检测(一)—— PointNet,PointNet,PointNeXt, PointMLP 前言 零、网络使用算法 …...
《设计模式》策略模式
策略模式 前言 先了解一下设计模式的几种类似: 行为型设计模式(Behavioral Design Pattern)是指一组设计模式,它们关注的是对象之间的通信和协作。行为型设计模式描述了对象之间的职责分配和算法的封装,以及如何在运…...
【离散数学】1. 数理逻辑
1.数理逻辑 2. 集合论 3. 代数系统 4. 图论 离散数学:研究离散量结构及相互关系的学科 数理逻辑集合论代数系统图论 逻辑:研究推理的科学 数学方法:引进一套符号系统的方法 数理逻辑是用数学方法研究形式逻辑的科学,即使用符号化…...
Java8新特性学习
Java8新特性学习为啥使用Lambda表达式Lambda表达式的基础语法无参无返回有参无返回一个参数多参单个语句体类型推断四大内置核心函数式接口其他接口方法引用与构造器引用Stream简介什么是StreamStream操作步骤创建Stream中间操作终止操作(终端操作)归约与收集并行流…...
SPARK outputDeterministicLevel的作用--任务全部重试或者部分重试
背景 目前spark的repartition()方法是随机分配数据到下游,这会导致一个问题,有时候如果我们用repartition方法的时候,如果任务发生了重试,就有可能导致任务的数据不准确,那这个时候改怎么解决这个问题呢? …...
图数据库中的 OLTP 与 OLAP 融合实践
在一些图计算的场景下,我们会遇到同时需要处理 OLTP 和 OLAP 的问题。而本文就给了一个 OLTP 与 OLAP 融合实践的指导思路,希望给你带来一点启发。 Dag Controller 介绍 Dag Controller 是 NebulaGraph 企业版的图系统,经过反复测试无误后已…...
Shader Graph简介
使用着色器(shader)和材质(material),我们能够创造出非常多有趣的效果。除了Unity自带的shader外,还可以自己编写shader或使用其他人所编写的shader。编写shader通常需要我们了解shader编程语言的语法和相关…...
kubectl
目录 一、陈述式资源管理方法 二、基本信息查看 2.1 基本信息查看格式 2.2 查看master节点组件状态 2.3 查看命名空间 2.4 创建/查看命名空间 2.5 删除(重启)命名空间/pod 2.6 查看资源的详细信息 2.7 创建副本控制器来启动Pod 2.8 查看指定命…...
实验室设计SICOLAB第三方检测中心实验室设计
第三方检测中心实验室怎么设计?详细设计内容有哪些?功能区域有哪些?仪器有哪些?要多少面积?第三方检测中心实验室是一种独立的实验室,为客户提供各种测试和分析服务。以下是一个第三方检测中心实验室的详细…...
GPS经纬度转距离
function [pN, pE] distance_gps(lon1, lon2, lat1, lat2)d2r pi/180; % deg转radR 6371000.0; % 地球半径pN (lat2 - lat1) * d2r * R;pE (lon2 - lon1) * d2r * R * cos(lat2 * d2r); end...
7-周赛333总结
7-周赛333总结 还是只过了前两题,第三题又写了好久好久,然后也不知道错在了哪里,只过了部分题解,也许是思考不全面吧。下次也许先做第四题更好…第四题今天花了点时间 做出来了个大概 开心 :happy: 合并两个二维数组 - 求和法【…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)
cd /home 进入home盘 安装虚拟环境: 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境: virtualenv myenv 3、激活虚拟环境(激活环境可以在当前环境下安装包) source myenv/bin/activate 此时,终端…...
