深入理解Linux内核之主调度器(下)

Linux阅码场 2021-07-15 11:17

4.进程上下文切换

接前文:深入理解Linux内核之主调度器(上)


前面选择了一个合适进程作为下一个进程,接下来做重要的上下文切换动作,来保存上一个进程的“上下文”恢复下一个进程的“上下文”,主要包括进程地址空间切换和处理器状态切换

注:这里的上下文实际上是指进程运行时最小寄存器的集合。

如果切换的next进程不是同一个进程,才进行切换:

__schedule
 i  f (likely(prev != next)) {      
        ...
        context_switch  //进程上下文切换
    }

4.1 进程地址空间切换

进程地址空间切换就是切换虚拟地址空间,使得切换之后,当前进程访问的是属于自己的虚拟地址空间(包括用户地址空间和内核地址空间),本质上是切换页表基地址寄存器

进程地址空间切换让进程产生独占系统内存的错觉,因为切换完地址空间后,当前进程可以访问属于它的海量的虚拟地址空间(内核地址空间各个进程共享,用户地址空间各个进程私有),而实际上物理地址空间只有一份。

下面给出源代码分析:

context_switch
->
 /*
 ¦* kernel -> kernel   lazy + transfer active
 ¦*   user -> kernel   lazy + mmgrab() active
 ¦*
 ¦* kernel ->   user   switch + mmdrop() active
 ¦*   user ->   user   switch
 ¦*/
 if (!next->mm) {                                // to kernel
         enter_lazy_tlb(prev->active_mm, next);

         next->active_mm = prev->active_mm;
         if (prev->mm)                           // from user
                 mmgrab(prev->active_mm);
         else
                 prev->active_mm = NULL;
 } else {                                        // to user
        ...
         switch_mm_irqs_off(prev->active_mm, next->mm, next);

         if (!prev->mm) {                        // from kernel
                 /* will mmdrop() in finish_task_switch(). */
                 rq->prev_mm = prev->active_mm;
                 prev->active_mm = NULL;
         }            
 }                    
                     

以上代码是判断是否next进程是内核线程,如果是则不需要进行地址空间切换(实际上指的是用户地址空间),因为内核线程总是运行在内核态访问的是内核地址空间,而内核地址空间是所有的进程共享的。在arm64架构中,内核地址空间是通过ttbr1_el1来访问,而它的主内核页表在内核初始化的时候已经填充好了,也就是我们常说的swapper_pg_dir页表,后面所有对内核地址空间的访问,无论是内核线程也好还是用户任务,统统通过swapper_pg_dir页表来访问,而在内核初始化期间swapper_pg_dir页表地址已经加载到ttbr1_el1中。

需要说明一点的是:这里会做“借用” prev->active_mm的处理,借用的目的是为了避免切换属于同一个进程的地址空间。举例说明:Ua  ->  Ka  ->  Ua   ,Ua表示用户进程,  Ka表示内核线程,当进行这样的切换的时候,Ka 借用Ua地址空间,Ua  ->  Ka不需要做地址空间切换,而Ka  ->  Ua按理来说需要做地址空间切换,但是由于切换的还是Ua 地址空间,所以也不需要真正的切换(判断了Ka->active_mm == Ua->active_mm ),当然还包括切换的是同一个进程的多个线程的情况,这留给大家思考。

下面来看下真正的地址空间切换:

 switch_mm_irqs_off(prev->active_mm, next->mm, next);
 ->switch_mm  //arch/arm64/include/asm/mmu_context.h
    -> if (prev != next) 
         __switch_mm(next);
           ->check_and_switch_context(next)
                -> ...  //asid处理
               -> cpu_switch_mm(mm->pgd, mm)
                   ->cpu_do_switch_mm(virt_to_phys(pgd),mm)
                         -> unsigned long ttbr1 = read_sysreg(ttbr1_el1);  
                             unsigned long asid = ASID(mm);                 
                             unsigned long ttbr0 = phys_to_ttbr(pgd_phys);  
                             ...
                             write_sysreg(ttbr1, ttbr1_el1);   //设置asid到ttbr1_el1
                             isb();                            
                             write_sysreg(ttbr0, ttbr0_el1);   //设置mm->pgd 到ttbr0_el1

上面代码是做真正的地址空间切换,实际的切换很简单,并没有那么复杂和玄乎,仅仅设置页表基地址寄存器即可,当然这里还涉及到了为了防止频繁无效tlb的ASID的设置。

主要做的工作就是设置next进程的ASID到ttbr1_el1, 设置mm->pgd 到ttbr0_el1,仅此而已

需要注意的是:1.写到ttbr0_el1的值是进程pgd页表的物理地址。2.虽然做了这样的切换,但是这个时候并不能访问到next的用户地址空间,因为还处在主调度器上下文中,属于内核态,访问的是内核空间。

而一旦返回了用户态,next进程就能正常访问自己地址空间内容:

  • 访问一个用户空间的虚拟地址va,首先通过va和记录在ttbr1_el1的asid查询tlb,如果找到相应表项则获得pa进行访问。

  • 如果tlb中没有找到,通过ttbr0_el1来遍历自己的多级页表,找到相应表项则获得pa进行访问。

  • 如果发生中断异常等访问内核地址空间,直接通过ttbr1_el1即可完成访问。

  • 访问没有建立页表映射的合法va,发生缺页异常来建立映射关系,填写属于进程自己的各级页表,然后访问。

  • 访问无法地址,发生缺页杀死进程等等。

4.2 处理器状态切换

来切换下一个进程的执行流,上一个进程执行状态保存,让下一个进程恢复执行状态。

处理器状态切换而后者让进程产生独占系统cpu的错觉,使得系统中各个任务能够并发(多个任务在多个cpu上运行)或分时复用(多个任务在一个cpu上运行)cpu资源。

下面给出代码:

context_switch
->(last) = __switch_to((prev), (next))
    -> fpsimd_thread_switch(next) //浮点寄存器切换
        ...
        last = cpu_switch_to(prev, next); 

处理器状态切换会做浮点寄存器等切换,最终调用cpu_switch_to做真正切换。

cpu_switch_to  //arch/arm64/kernel/entry.S
SYM_FUNC_START(cpu_switch_to)
        mov     x10, #THREAD_CPU_CONTEXT
        add     x8, x0, x10
        mov     x9, sp
        stp     x19, x20, [x8], #16             // store callee-saved registers
        stp     x21, x22, [x8], #16
        stp     x23, x24, [x8], #16
        stp     x25, x26, [x8], #16
        stp     x27, x28, [x8], #16
        stp     x29, x9, [x8], #16
        str     lr, [x8]
        add     x8, x1, x10
        ldp     x19, x20, [x8], #16             // restore callee-saved registers
        ldp     x21, x22, [x8], #16
        ldp     x23, x24, [x8], #16
        ldp     x25, x26, [x8], #16
        ldp     x27, x28, [x8], #16
        ldp     x29, x9, [x8], #16
        ldr     lr, [x8]
        mov     sp, x9
        msr     sp_el0, x1
        ptrauth_keys_install_kernel x1, x8, x9, x10
        scs_save x0, x8
        scs_load x1, x8
        ret
SYM_FUNC_END(cpu_switch_to)

这里传递过来的是x0为prev进程的进程描述符(struct task_struct)地址, x1为next的进程描述符地址。会就将prev进程的 x19-x28,fp,sp,lr保存到prev进程的tsk.thread.cpu_context中,next进程的这些寄存器值从next进程的tsk.thread.cpu_context中恢复到相应寄存器。这里还做了sp_el0设置为next进程描述符的操作,为了通过current宏找到当前的任务。

需要注意的是:

  1. mov     sp, x9  做了切换进程内核栈的操作。
  2. ldr     lr, [x8] 设置了链接寄存器,然后ret的时候会将lr恢复到pc从而真正完成了执行流的切换。

4.3 精美图示

这里给出了进程切换的图示(以arm64处理器为例),这里从prev进程切换到next进程。


5.进程再次被调度

当进程重新被调度的时候,从原来的调度现场恢复执行。

5.1 关于lr地址的设置

1)如果切换的next进程是刚fork的进程,它并没有真正的这些调度上下文的存在,那么lr是什么呢?这是在fork的时候设置的:

do_fork
    ...
    copy_thread //arch/arm64/kernel/process.c
    ->memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));
     p->thread.cpu_context.pc = (unsigned long)ret_from_fork;
    p->thread.cpu_context.sp = (unsigned long)childregs;

设置为了ret_from_fork的地址,当然这里也设置了sp等调度上下文(这里将进程切换保存的寄存器称之为调度上下文)。

SYM_CODE_START(ret_from_fork)
        bl      schedule_tail
        cbz     x19, 1f                         // not a kernel thread
        mov     x0, x20
        blr     x19
1:      get_current_task tsk
        b       ret_to_user
SYM_CODE_END(ret_from_fork)

刚fork的进程,从cpu_switch_to的ret指令执行后返回,lr加载到pc。

于是执行到ret_from_fork:这里首先调用schedule_tail对前一个进程做清理工作,然后判断是否为内核线程如果是执行内核线程的执行函数,如果是用户任务通过ret_to_user返回到用户态。

2)如果是之前已经被切换过的进程,lr为cpu_switch_to调用的下一条指令地址(这里实际上是__schedule函数中调用barrier()的指令地址)。

5.2 关于__switch_to的参数和返回值

 
 switch_to(prev, next, prev)
->  ((last) = __switch_to((prev), (next)))
 

这里做处理器状态切换时,传递了两个参数,返回了一个参数:

prev和next很好理解就是 就是前一个进程(当前进程)和下一个进程的 task_struct结构指针,那么last是什么呢?

一句话:返回的last是当前重新被调度的进程的上一个进程的 task_struct结构指针

如:A ->B ->千山万水->D -> A 上面的切换过程:A切换到B 然后经历千山万水再从D -> A,这个时候A重新被调度时,last即为D的 task_struct结构指针。

获得当前重新被调度进程的前一个进程是为了回收前一个进程资源,见后面分析。

5.3 关于finish_task_switch

进程被重新调度时无论是否为刚fork出的进程都会走到finish_task_switch这个函数,下面我们来看它做了什么事情:

主要工作为:检查回收前一个进程资源,为当前进程恢复执行做一些准备工作

finish_task_switch
->finish_lock_switch
    ->raw_spin_unlock_irq   //使能本地中断
->if (mm) 
    mmdrop(mm)  //有借有还  借用的mm现在归还
 ->if (unlikely(prev_state == TASK_DEAD)) {        //前一个进程是死亡状态
            put_task_stack(prev);    //如果内核栈在task_struct中   释放内核栈                                      
           put_task_struct_rcu_user(prev);  //释放前一个进程的task_struct占用内存
   }                                        

可以看到进程被重新调度时首先需要做的主要是:

  • 重新使能本地中断 ,进程被重新调度时,本地cpu中断是被重新打开的!!!

  • 如果有借用mm的情况,现在归还 如果前一个是内核线程,在进程地址空间切换时“借用了”某个进程的mm_struct,现在切换到了下一个进程,理应归还,归还做的是递减借用的mm_struct的引用计数,引用计数为0就会释放mm_struct占用的内存。

  • 对于上一个死亡的进程现在回收最后的资源, 注意这里是递减引用计数,当引用计数为0时才会真正释放。

6. 总结

主调度器可以说Linux内核进程管理中的核心组件,进程管理的其他部分如抢占、唤醒、睡眠等都是围绕它来运作。在原子上下文不能发生调度,说的就是调用主调度器,但是可以设置抢占标志以至于在最近的抢占点发生调度,如中断中唤醒高优先级进程的场景。主调度器所做的工作就是让出cpu,内核很多场景可以直接或间接调用它,而大体上可以分为两种情况:即为主动调度和抢占式调度。主调度器做了两件事情:选择下一个进程和进程进程上下文切换。选择下一个进程解决选择合适高优先级进程的问题。进程进程上下文切换又分为地址空间切换和处理器状态切换,前者让进程产生独自占用系统内存的错觉,而后者让进程产生独自系统cpu的错觉,让系统各个进程有条不紊的共享内存和cpu等资源。


Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 150浏览
  • 在智能网联汽车中,各种通信技术如2G/3G/4G/5G、GNSS(全球导航卫星系统)、V2X(车联网通信)等在行业内被广泛使用。这些技术让汽车能够实现紧急呼叫、在线娱乐、导航等多种功能。EMC测试就是为了确保在复杂电磁环境下,汽车的通信系统仍然可以正常工作,保护驾乘者的安全。参考《QCT-基于LTE-V2X直连通信的车载信息交互系统技术要求及试验方法-1》标准10.5电磁兼容试验方法,下面将会从整车功能层面为大家解读V2X整车电磁兼容试验的过程。测试过程揭秘1. 设备准备为了进行电磁兼容试验,技
    北汇信息 2025-01-09 11:24 50浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 128浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 115浏览
  • 一个真正的质量工程师(QE)必须将一件产品设计的“意图”与系统的可制造性、可服务性以及资源在现实中实现设计和产品的能力结合起来。所以,可以说,这确实是一种工程学科。我们常开玩笑说,质量工程师是工程领域里的「侦探」、「警察」或「律师」,守护神是"墨菲”,信奉的哲学就是「墨菲定律」。(注:墨菲定律是一种启发性原则,常被表述为:任何可能出错的事情最终都会出错。)做质量工程师的,有时会不受欢迎,也会被忽视,甚至可能遭遇主动或被动的阻碍,而一旦出了问题,责任往往就落在质量工程师的头上。虽然质量工程师并不负
    优思学院 2025-01-09 11:48 48浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 92浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 100浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2025-01-09 09:58 33浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 82浏览
  • 在过去十年中,自动驾驶和高级驾驶辅助系统(AD/ADAS)软件与硬件的快速发展对多传感器数据采集的设计需求提出了更高的要求。然而,目前仍缺乏能够高质量集成多传感器数据采集的解决方案。康谋ADTF正是应运而生,它提供了一个广受认可和广泛引用的软件框架,包含模块化的标准化应用程序和工具,旨在为ADAS功能的开发提供一站式体验。一、ADTF的关键之处!无论是奥迪、大众、宝马还是梅赛德斯-奔驰:他们都依赖我们不断发展的ADTF来开发智能驾驶辅助解决方案,直至实现自动驾驶的目标。从新功能的最初构思到批量生
    康谋 2025-01-09 10:04 37浏览
  • 1月7日-10日,2025年国际消费电子产品展览会(CES 2025)盛大举行,广和通发布Fibocom AI Stack,赋智千行百业端侧应用。Fibocom AI Stack提供集高性能模组、AI工具链、高性能推理引擎、海量模型、支持与服务一体化的端侧AI解决方案,帮助智能设备快速实现AI能力商用。为适应不同端侧场景的应用,AI Stack具备海量端侧AI模型及行业端侧模型,基于不同等级算力的芯片平台或模组,Fibocom AI Stack可将TensorFlow、PyTorch、ONNX、
    物吾悟小通 2025-01-08 18:17 37浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 80浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球中空长航时无人机产值达到9009百万美元,2024-2030年期间年复合增长率CAGR为8.0%。 环洋市场咨询机构出版了的【全球中空长航时无人机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球中空长航时无人机总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析中空长航时无人机市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从中空长航时
    GIRtina 2025-01-09 10:35 37浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦