精华内容
下载资源
问答
  • 深入理解Linux内核进程上下文切换

    千次阅读 多人点赞 2020-09-28 08:39:19
    本文主要关注进程管理的一个切入点,那就是进程的上下文切换,来理解linux内核是如何进程进程上下文切换的,从而揭开上下文切换的神秘面纱。 (注意:本文以linux-5.0内核源码讲解,采用arm64架构) 本文目录: 1....

    作者简介

    韩传华,就职于南京大鱼半导体有限公司,主要从事linux相关系统软件开发工作,负责Soc芯片BringUp及系统软件开发,乐于分享喜欢学习,喜欢专研Linux内核源代码。

    我都知道操作系统的一个重要功能就是进行进程管理,而进程管理就是在合适的时机选择合适的进程来执行,在单个cpu运行队列上各个进程宏观并行微观串行执行,多个cpu运行队列上的各个进程之间完全的并行执行。进程管理是个复杂的过程,例如进程的描述、创建和销毁、生命周期管理、进程切换、进程抢占、调度策略、负载均衡等等。本文主要关注进程管理的一个切入点,那就是进程的上下文切换,来理解linux内核是如何进程进程上下文切换的,从而揭开上下文切换的神秘面纱。
    (注意:本文以linux-5.0内核源码讲解,采用arm64架构)

    本文目录:
    1.进程上下文的概念
    2.上下文切换详细过程
     2.1 进程地址空间切换
     2.2 处理器状态(硬件上下文)切换
    3.ASID机制
    4.普通用户进程、普通用户线程、内核线程切换的差别
    5.进程切换全景视图
    6.总结

    1.进程上下文的概念

    进程上下文是进程执行活动全过程的静态描述。我们把已执行过的进程指令和数据在相关寄存器与堆栈中的内容称为进程上文,把正在执行的指令和数据在寄存器与堆栈中的内容称为进程正文,把待执行的指令和数据在寄存器与堆栈中的内容称为进程下文。

    实际上linux内核中,进程上下文包括进程的虚拟地址空间和硬件上下文。

    进程硬件上下文包含了当前cpu的一组寄存器的集合,arm64中使用task_struct结构的thread成员的cpu_context成员来描述,包括x19-x28,sp, pc等。

    如下为硬件上下文存放示例图:

    2.上下文切换详细过程

    进程上下文切换主要涉及到两部分主要过程:进程地址空间切换和处理器状态切换。地址空间切换主要是针对用户进程而言,而处理器状态切换对应于所有的调度单位。下面我们分别看下这两个过程:

    __schedule   // kernel/sched/core.c
    ->context_switch
      ->switch_mm_irqs_off   //进程地址空间切换
      ->switch_to //处理器状态切换
    

    2.1 进程地址空间切换

    进程地址空间指的是进程所拥有的虚拟地址空间,而这个地址空间是假的,是linux内核通过数据结构来描述出来的,从而使得每一个进程都感觉到自己拥有整个内存的假象,cpu访问的指令和数据最终会落实到实际的物理地址,对用进程而言通过缺页异常来分配和建立页表映射。进程地址空间内有进程运行的指令和数据,因此到调度器从其他进程重新切换到我的时候,为了保证当前进程访问的虚拟地址是自己的必须切换地址空间。
    实际上,进程地址空间使用mm_struct结构体来描述,这个结构体被嵌入到进程描述符(我们通常所说的进程控制块PCB)task_struct中,mm_struct结构体将各个vma组织起来进行管理,其中有一个成员pgd至关重要,地址空间切换中最重要的是pgd的设置。

    pgd中保存的是进程的页全局目录的虚拟地址(本文会涉及到页表相关的一些概念,在此不是重点,不清楚的可以查阅相关资料,后期有机会会讲解进程页表),记住保存的是虚拟地址,那么pgd的值是何时被设置的呢?答案是fork的时候,如果是创建进程,需要分配设置mm_struct,其中会分配进程页全局目录所在的页,然后将首地址赋值给pgd。

    我们来看看进程地址空间究竟是如何切换的,结果会让你大吃一惊(这里暂且不考虑asid机制,后面有机会会在其他文章中讲解):
    代码路径如下:

    context_switch  // kernel/sched/core.c
    ->switch_mm_irqs_off
      ->switch_mm
      ->__switch_mm
        ->check_and_switch_context
        ->cpu_switch_mm
          ->cpu_do_switch_mm(virt_to_phys(pgd),mm) //arch/arm64/include/asm/mmu_context.h
        
    arch/arm64/mm/proc.S
    158 /*
    159  *      cpu_do_switch_mm(pgd_phys, tsk)
    160  *
    161  *      Set the translation table base pointer to be pgd_phys.
    162  *
    163  *      - pgd_phys - physical address of new TTB
    164  */
    165 ENTRY(cpu_do_switch_mm)
    166         mrs     x2, ttbr1_el1
    167         mmid    x1, x1                          // get mm->context.id
    168         phys_to_ttbr x3, x0
    169
    170 alternative_if ARM64_HAS_CNP
    171         cbz     x1, 1f                          // skip CNP for reserved ASID
    172         orr     x3, x3, #TTBR_CNP_BIT
    173 1:
    174 alternative_else_nop_endif
    175 #ifdef CONFIG_ARM64_SW_TTBR0_PAN
    176         bfi     x3, x1, #48, #16                // set the ASID field in TTBR0
    177 #endif
    178         bfi     x2, x1, #48, #16                // set the ASID
    179         msr     ttbr1_el1, x2                   // in TTBR1 (since TCR.A1 is set)
    180         isb
    181         msr     ttbr0_el1, x3                   // now update TTBR0
    182         isb
    183         b       post_ttbr_update_workaround     // Back to C code...
    184 ENDPROC(cpu_do_switch_mm)
    

    代码中最核心的为181行,最终将进程的pgd虚拟地址转化为物理地址存放在ttbr0_el1中,这是用户空间的页表基址寄存器,当访问用户空间地址的时候mmu会通过这个寄存器来做遍历页表获得物理地址(ttbr1_el1是内核空间的页表基址寄存器,访问内核空间地址时使用,所有进程共享,不需要切换)。完成了这一步,也就完成了进程的地址空间切换,确切的说是进程的虚拟地址空间切换。

    内核处理的是不是很简单,很优雅,别看只是设置了页表基址寄存器,也就是将即将执行的进程的页全局目录的物理地址设置到页表基址寄存器,他却完成了地址空间切换的壮举,有的小伙伴可能不明白为啥这就完成了地址空间切换?试想如果进程想要访问一个用户空间虚拟地址,cpu的mmu所做的工作,就是从页表基址寄存器拿到页全局目录的物理基地址,然后和虚拟地址配合来查查找页表,最终找到物理地址进行访问(当然如果tlb命中就不需要遍历页表),每次用户虚拟地址访问的时候(内核空间共享不考虑),由于页表基地址寄存器内存放的是当前执行进程的页全局目录的物理地址,所以访问自己的一套页表,拿到的是属于自己的物理地址(实际上,进程是访问虚拟地址空间的指令数据的时候不断发生缺页异常,然后缺页异常处理程序为进程分配实际的物理页,然后将页帧号和页表属性填入自己的页表条目中),就不会访问其他进程的指令和数据,这也是为何多个进程可以访问相同的虚拟地址而不会出现差错的原因,而且做到的各个地址空间的隔离互不影响(共享内存除外)。

    其实,地址空间切换过程中,还会清空tlb,防止当前进程虚拟地址转化过程中命中上一个进程的tlb表项,一般会将所有的tlb无效,但是这会导致很大的性能损失,因为新进程被切换进来的时候面对的是全新的空的tlb,造成很大概率的tlb miss,需要重新遍历多级页表,所以arm64在tlb表项中增加了非全局(nG)位区分内核和进程的页表项,使用ASID区分不同进程的页表项,来保证可以在切换地址空间的时候可以不刷tlb,后面会主要讲解ASID技术。

    还需要注意的是仅仅切换用户地址空间,内核地址空间由于是共享的不需要切换,也就是为何切换到内核线程不需要也没有地址空间的原因。

    如下为进程地址空间切换示例图:

    2.2 处理器状态(硬件上下文)切换

    前面进行了地址空间切换,只是保证了进程访问指令数据时访问的是自己地址空间(当然上下文切换的时候处于内核空间,执行的是内核地址数据,当返回用户空间的时候才有机会执行用户空间指令数据**,地址空间切换为进程访问自己用户空间做好了准备**),但是进程执行的内核栈还是前一个进程的,当前执行流也还是前一个进程的,需要做切换。

    arm64中切换代码如下:

    switch_to
    ->__switch_to
      ... //浮点寄存器等的切换
      ->cpu_switch_to(prev, next)
    
    
    arch/arm64/kernel/entry.S:
    1032 /*
    1033  * Register switch for AArch64. The callee-saved registers need to be saved
    1034  * and restored. On entry:
    1035  *   x0 = previous task_struct (must be preserved across the switch)
    1036  *   x1 = next task_struct
    1037  * Previous and next are guaranteed not to be the same.
    1038  *
    1039  */
    1040 ENTRY(cpu_switch_to)
    1041         mov     x10, #THREAD_CPU_CONTEXT
    1042         add     x8, x0, x10
    1043         mov     x9, sp
    1044         stp     x19, x20, [x8], #16             // store callee-saved registers
    1045         stp     x21, x22, [x8], #16
    1046         stp     x23, x24, [x8], #16
    1047         stp     x25, x26, [x8], #16
    1048         stp     x27, x28, [x8], #16
    1049         stp     x29, x9, [x8], #16
    1050         str     lr, [x8]
    1051         add     x8, x1, x10
    1052         ldp     x19, x20, [x8], #16             // restore callee-saved registers
    1053         ldp     x21, x22, [x8], #16
    1054         ldp     x23, x24, [x8], #16
    1055         ldp     x25, x26, [x8], #16
    1056         ldp     x27, x28, [x8], #16
    1057         ldp     x29, x9, [x8], #16
    1058         ldr     lr, [x8]
    1059         mov     sp, x9
    1060         msr     sp_el0, x1
    1061         ret
    1062 ENDPROC(cpu_switch_to)
    

    其中x19-x28是arm64 架构规定需要调用保存的寄存器,可以看到处理器状态切换的时候将前一个进程(prev)的x19-x28,fp,sp,pc保存到了进程描述符的cpu_contex中,然后将即将执行的进程(next)描述符的cpu_contex的x19-x28,fp,sp,pc恢复到相应寄存器中,而且将next进程的进程描述符task_struct地址存放在sp_el0中,用于通过current找到当前进程,这样就完成了处理器的状态切换。

    实际上,处理器状态切换就是将前一个进程的sp,pc等寄存器的值保存到一块内存上,然后将即将执行的进程的sp,pc等寄存器的值从另一块内存中恢复到相应寄存器中,恢复sp完成了进程内核栈的切换,恢复pc完成了指令执行流的切换。其中保存/恢复所用到的那块内存需要被进程所标识,这块内存这就是cpu_contex这个结构的位置(进程切换都是在内核空间完成)。

    由于用户空间通过异常/中断进入内核空间的时候都需要保存现场,也就是保存发生异常/中断时的所有通用寄存器的值,内核会把“现场”保存到每个进程特有的进程内核栈中,并用pt_regs结构来描述,当异常/中断处理完成之后会返回用户空间,返回之前会恢复之前保存的“现场”,用户程序继续执行。
    所以当进程切换的时候,当前进程被时钟中断打断,将发生中断时的现场保存到进程内核栈(如:sp, lr等),然后会切换到下一个进程,当再次回切换回来的时候,返回用户空间的时候会恢复之前的现场,进程就可以继续执行(执行之前被中断打断的下一条指令,继续使用自己用户态sp),这对于用户进程来说是透明的。

    如下为硬件上下文切换示例图:

    3.ASID机制

    前面讲过,进程切换的时候,由于tlb中存放的可能是其他进程的tlb表项,所有才需要在进程切换的时候进行tlb的清空工作(清空即是使得所有的tlb表项无效,地址转换需要遍历多级页表,找到页表项,然后重新加载页表项到tlb),有了ASID机制之后,命中tlb表项,由虚拟地址和ASID共同决定(当然还有nG位),可以减小进程切换中tlb被清空的机会。

    下面我们讲解ASID机制,ASID(Address Space Identifer 地址空间标识符),用于区别不同进程的页表项,arm64中,可以选择两种ASID长度8位或者16位,这里以8位来讲解。

    如果ASID长度为8位,那么ASID有256个值,但是由于0是保留的,所有可以分配的ASID范围就为1-255,那么可以标识255个进程,当超出255个进程的时候,会出现两个进程的ASID相同的情况,因此内核使用了ASID版本号。
    内核中处理如下(参考arch/arm64/mm/context.c):

    1)内核为每个进程分配一个64位的软件ASID,其中低8位为硬件ASID,高56位为ASID版本号,这个软件ASID存放放在进程的mm_struct结构的context结构的id中,进程创建的时候会初始化为0。
    2)内核中有一个64位的全局变量asid_generation,同样它的高56位为ASID版本号,用于标识当前ASID分配的批次。
    3)当进程调度,由prev进程切换到next进程的时候,如果不是内核线程则进行地址空间切换调用check_and_switch_context,此函数会判断next进程的ASID版本号是否和全局的ASID版本号相同(是否处于同一批次),如果相同则不需要为next进程分配ASID,不相同则需要分配ASID。
    4)内核使用asid_map位图来管理硬件ASID的分配,asid_bits记录使用的ASID的长度,每处理器变量active_asids保存当前分配的硬件ASID,每处理器变量reserved_asids存放保留的ASID,tlb_flush_pending位图记录需要清空tlb的cpu集合。
    硬件ASID分配策略如下:
    (1)如果进程的ASID版本号和当前全局的ASID版本号相同(同批次情况),则不需要重新分配ASID。
    (2)如果进程的ASID版本号和当前全局的ASID版本号不相同(不同批次情况),且进程原本的硬件ASID已经被分配,则重新分配新的硬件ASID,并将当前全局的ASID版本号组合新分配的硬件ASID写到进程的软件ASID中。
    (3)如果进程的ASID版本号和当前全局的ASID版本号不相同(不同批次情况),且进程原本的硬件ASID还没有被分配,则不需要重新分配新的硬件ASID,只需要更新进程软件ASID版本号,并将当前全局的ASID版本号组合进程原来的硬件ASID写到进程的软件ASID中。
    (4)如果进程的ASID版本号和当前全局的ASID版本号不相同(不同批次情况),需要分配硬件ASID时,发现硬件ASID已经被其他进程分配完(asid_map位图中查找,发现位图全1),则这个时候需要递增全局的ASID版本号, 清空所有cpu的tlb, 清空asid_map位图,然后分配硬件ASID,并将当前全局的ASID版本号组合新分配的硬件ASID写到进程的软件ASID中。

    下面我们以实例来看ASID的分配过程:
    如下图:

    我们假设图中从A进程到D进程,有255个进程,刚好分配完了asid, ,从A到D的切换过程中使用的都是同一批次的asid版本号。

    则这个过程中,有进程会创建的时候被切换到,假设不超出255个进程,在切换过程中会为新进程分配硬件的ASID,分配完后下次切换到他时由于他的ASID版本号和当前的全局的ASID版本号相同,所以不需要再次分配ASID,当然也不需要清空tlb。

    注:这里说的ASID即为硬件ASID区别于ASID版本号。

    情况1-ASID版本号不变 属于策略(1):从C进程到D进程切换,内核判断D进程的ASID版本号和当前的全局的ASID版本号相同,所以不需要为他分配ASID(执行快速路径switch_mm_fastpath去设置ttbrx_el1))。

    情况2 -硬件ASID全部分配完 属于策略(4):假设到达D进程时,asid已经全部分配完(系统中有255个进程都分配到了硬件asid号),这个时候新创建的进程E被调度器选中,切换到E,由于新创建的进程的软件ASID被初始化为0,所以和当前的全局的ASID版本号不同(不在同一批次),则这个时候会执行new_context为进程分配ASID,但是由于没有可以分配的ASID,所以会将全局的ASID版本号加1(发生ASID回绕),这个时候全局的ASID为801,然后清空asid_map,置位tlb_flush_pending所有bit用于清空所有cpu的tlb,然后再次去分配硬件ASID给E进程,这个时候分配到了1给他(将ASID版本号)。

    情况3-ASID版本号发生变化,进程的硬件ASID可以再次使用 属于策略(3):假设从E切换到了B进程,而B进程之前已经在全局的ASID版本号为800的批次上分配了编号为5的硬件ASID,但是B进程的ASID版本号800和现在全局的ASID版本号801不相同,所有需要new_context为进程分配ASID,分配的时候发现asid_map中编号为5没有被置位,也就是没有其他进程分配了5这个ASID,所有可以继续使用原来分配的硬件ASID 5。

    情况4 - ASID版本号发生变化,有其他进程已经分配了相同的硬件ASID 属于策略(2): 假设从B进程切换到A进程,而B进程之前已经在全局的ASID版本号为800的批次上分配了编号为1的硬件ASID,但是B进程的ASID版本号800和现在全局的ASID版本号801不相同,所有需要new_context为进程分配ASID,分配的时候发现asid_map中编号为1已经被置位,也就是其他进程已经分配了1这个ASID,需要从asid_map寻找下一个空闲的ASID,则分配了新的ASID为6。

    假设从A到E,由于E的ASID版本号和全局的ASID版本号(同一批次),和情况1相同,不需要分配ASID。但是之前原来处于800这个ASID版本号批次的进程都需要重新分配ASID,有的可以使用原来的硬件ASID,有的重新分配硬件ASID,但是都将ASID版本号修改为了现在全局的ASID版本号801。但是,随着硬件ASID的不断分配,最终处于801这一批次的硬件ASID也会分配完,这个时候就是上面的情况2,要情况所有cpu的tlb。

    我可以看到有了ASID机制之后,由于只有当硬件ASID被分配完了(如被255个进程使用),发生回绕的时候才会清空所有cpu的tlb,大大提高了系统的性能(没有ASID机制的情况下每次进程切换需要地址空间切换的时候都需要清空tlb)。

    4. 普通用户进程、普通用户线程、内核线程切换的差别

    内核地址空间切换的时候有一下原则:看的是进程描述符的mm_struct结构,即是成员mm:
    1)如果mm为NULL,则表示即将切换的是内核线程,不需要切换地址空间(所有任务共享内核地址空间)。
    2)内核线程会借用前一个用户进程的mm,赋值到自己的active_mm(本身的mm为空),进程切换的时候就会比较前一个进程的active_mm和当前进程的mm。
    3)如果前一个任务的和即将切换的任务,具有相同的mm成员,也就是共享地址空间的线程则也不需要切换地址空间。

    ->所有的进程线程之间进行切换都需要切换处理器状态。
    ->对于普通的用户进程之间进行切换需要切换地址空间。
    ->同一个线程组中的线程之间切换不需要切换地址空间,因为他们共享相同的地址空间。
    -> 内核线程在上下文切换的时候不需要切换地址空间,仅仅是借用上一个进程mm_struct结构。

    有一下场景:
    约定:我们将进程/线程统称为任务,其中U表示用户任务(进程/线程),K表示内核线程,带有数字表示同一个线程组中的线程。
    有以下任务:Ua1 Ua2 Ub Uc Ka Kb (eg:Ua1为用户进程, Ua2为和Ua1在同一线程组的用户进程,Ub普通的用户进程,Ka普通的内核线程 )。

    如果调度顺序如下:

    Uc -> Ua1 -> Ua2 -> Ub -> Ka -> Kb -> Ub

    从Uc -> Ua1 由于是不同的进程,需要切换地址空间。
    从 Ua1 -> Ua2 由于是相同线程组中的不同线程,共享地址空间,在切换到Ua1的时候已经切换了地址空间,所有不需要切换地址空间。
    从 Ua2 -> Ub 由于是不同的进程,需要切换地址空间。
    从 Ub -> Ka 由于切换到内核线程,所以不需要切换地址空间。
    从Ka -> Kb 俩内核线程之前切换,不需要切换地址空间。
    从Kb -> Ub 从内核线程切换到用户进程,由于Ka和Kb都是借用Ub的active_mm,而Ub的active_mm 等于Ub的mm,所以这个时候 Kb的active_mm和 Ub的mm相同,所有也不会切换地址空间。

    如下为多任务地址空间切换示例图:

    5. 进程切换全景视图

    我们以下场景为例:
    A,B两个进程都是普通的用户进程,从进程A切换到进程B,简单起见我们在这里不考虑其他的抢占时机,我们假设A,B进程只是循环进行一些基本的运算操作,从来不调用任何系统调用,只考虑被时钟中断,返回用户空间之前被抢占的情况。

    下面给出进程切换的全景视图:

    视图中已经讲解很清楚,需要强调3个关键点:

    1.发生中断时的保存现场,将发生中断时的所有通用寄存器保存到进程的内核栈,使用struct pt_regs结构。

    2.地址空间切换将进程自己的页全局目录的基地址pgd保存在ttbr0_le1中,用于mmu的页表遍历的起始点。

    3.硬件上下文切换的时候,将此时的调用保存寄存器和pc, sp保存到struct cpu_context结构中。做好了这几个保存工作,当进程再次被调度回来的时候,通过cpu_context中保存的pc回到了cpu_switch_to的下一条指令继续执行,而由于cpu_context中保存的sp导致当前进程回到自己的内核栈,经过一系列的内核栈的出栈处理,最后将原来保存在pt_regs中的通用寄存器的值恢复到了通用寄存器,这样进程回到用户空间就可以继续沿着被中断打断的下一条指令开始执行,用户栈也回到了被打断之前的位置,而进程访问的指令数据做地址转化(VA到PA)也都是从自己的pgd开始进行,一切对用户来说就好像没有发生一样,简直天衣无缝。

    6. 总结

    进程管理中最重要的一步要进行进程上下文切换,其中主要有两大步骤:地址空间切换和处理器状态切换(硬件上下文切换),前者保证了进程回到用户空间之后能够访问到自己的指令和数据(其中包括减小tlb清空的ASID机制),后者保证了进程内核栈和执行流的切换,会将当前进程的硬件上下文保存在进程所管理的一块内存,然后将即将执行的进程的硬件上下文从内存中恢复到寄存器,有了这两步的切换过程保证了进程运行的有条不紊,当然切换的过程是在内核空间完成,这对于进程来说是透明的。

    (END)

    Linux阅码场原创精华文章汇总

    更多精彩,尽在"Linux阅码场",扫描下方二维码关注

    别忘了分享、点赞或者在看哦~

    展开全文
  • Linux内核进程上下文切换深入理解

    千次阅读 2020-09-28 16:01:42
    本文主要关注进程管理的一个切入点,那就是进程的上下文切换,来理解linux内核是如何进程进程上下文切换的,从而揭开上下文切换的神秘面纱。 (注意:本文以linux-5.0内核源码讲解,采用arm64架构)

    我都知道操作系统的一个重要功能就是进行进程管理,而进程管理就是在合适的时机选择合适的进程来执行,在单个cpu运行队列上各个进程宏观并行微观串行执行,多个cpu运行队列上的各个进程之间完全的并行执行。进程管理是个复杂的过程,例如进程的描述、创建和销毁、生命周期管理、进程切换、进程抢占、调度策略、负载均衡等等。本文主要关注进程管理的一个切入点,那就是进程的上下文切换,来理解linux内核是如何进程进程上下文切换的,从而揭开上下文切换的神秘面纱。
    (注意:本文以linux-5.0内核源码讲解,采用arm64架构)

    本文目录:
    1.进程上下文的概念
    2.上下文切换详细过程
     2.1 进程地址空间切换
     2.2 处理器状态(硬件上下文)切换
    3.ASID机制
    4.普通用户进程、普通用户线程、内核线程切换的差别
    5.进程切换全景视图
    6.总结

    1.进程上下文的概念

    进程上下文是进程执行活动全过程的静态描述。我们把已执行过的进程指令和数据在相关寄存器与堆栈中的内容称为进程上文,把正在执行的指令和数据在寄存器与堆栈中的内容称为进程正文,把待执行的指令和数据在寄存器与堆栈中的内容称为进程下文。

    实际上linux内核中,进程上下文包括进程的虚拟地址空间和硬件上下文。

    进程硬件上下文包含了当前cpu的一组寄存器的集合,arm64中使用task_struct结构的thread成员的cpu_context成员来描述,包括x19-x28,sp, pc等。

    如下为硬件上下文存放示例图:

    2.上下文切换详细过程

    进程上下文切换主要涉及到两部分主要过程:进程地址空间切换和处理器状态切换。地址空间切换主要是针对用户进程而言,而处理器状态切换对应于所有的调度单位。下面我们分别看下这两个过程:

    __schedule   // kernel/sched/core.c->context_switch  ->switch_mm_irqs_off   //进程地址空间切换  ->switch_to //处理器状态切换

    2.1 进程地址空间切换

    进程地址空间指的是进程所拥有的虚拟地址空间,而这个地址空间是假的,是linux内核通过数据结构来描述出来的,从而使得每一个进程都感觉到自己拥有整个内存的假象,cpu访问的指令和数据最终会落实到实际的物理地址,对用进程而言通过缺页异常来分配和建立页表映射。进程地址空间内有进程运行的指令和数据,因此到调度器从其他进程重新切换到我的时候,为了保证当前进程访问的虚拟地址是自己的必须切换地址空间。
    实际上,进程地址空间使用mm_struct结构体来描述,这个结构体被嵌入到进程描述符(我们通常所说的进程控制块PCB)task_struct中,mm_struct结构体将各个vma组织起来进行管理,其中有一个成员pgd至关重要,地址空间切换中最重要的是pgd的设置。

    pgd中保存的是进程的页全局目录的虚拟地址(本文会涉及到页表相关的一些概念,在此不是重点,不清楚的可以查阅相关资料,后期有机会会讲解进程页表),记住保存的是虚拟地址,那么pgd的值是何时被设置的呢?答案是fork的时候,如果是创建进程,需要分配设置mm_struct,其中会分配进程页全局目录所在的页,然后将首地址赋值给pgd。

    我们来看看进程地址空间究竟是如何切换的,结果会让你大吃一惊(这里暂且不考虑asid机制,后面有机会会在其他文章中讲解):
    代码路径如下:

    context_switch  // kernel/sched/core.c->switch_mm_irqs_off  ->switch_mm  ->__switch_mm    ->check_and_switch_context    ->cpu_switch_mm      ->cpu_do_switch_mm(virt_to_phys(pgd),mm) //arch/arm64/include/asm/mmu_context.h    arch/arm64/mm/proc.S158 /*159  *      cpu_do_switch_mm(pgd_phys, tsk)160  *161  *      Set the translation table base pointer to be pgd_phys.162  *163  *      - pgd_phys - physical address of new TTB164  */165 ENTRY(cpu_do_switch_mm)166         mrs     x2, ttbr1_el1167         mmid    x1, x1                          // get mm->context.id168         phys_to_ttbr x3, x0169170 alternative_if ARM64_HAS_CNP171         cbz     x1, 1f                          // skip CNP for reserved ASID172         orr     x3, x3, #TTBR_CNP_BIT173 1:174 alternative_else_nop_endif175 #ifdef CONFIG_ARM64_SW_TTBR0_PAN176         bfi     x3, x1, #48, #16                // set the ASID field in TTBR0177 #endif178         bfi     x2, x1, #48, #16                // set the ASID179         msr     ttbr1_el1, x2                   // in TTBR1 (since TCR.A1 is set)180         isb181         msr     ttbr0_el1, x3                   // now update TTBR0182         isb183         b       post_ttbr_update_workaround     // Back to C code...184 ENDPROC(cpu_do_switch_mm)

    代码中最核心的为181行,最终将进程的pgd虚拟地址转化为物理地址存放在ttbr0_el1中,这是用户空间的页表基址寄存器,当访问用户空间地址的时候mmu会通过这个寄存器来做遍历页表获得物理地址(ttbr1_el1是内核空间的页表基址寄存器,访问内核空间地址时使用,所有进程共享,不需要切换)。完成了这一步,也就完成了进程的地址空间切换,确切的说是进程的虚拟地址空间切换。

    内核处理的是不是很简单,很优雅,别看只是设置了页表基址寄存器,也就是将即将执行的进程的页全局目录的物理地址设置到页表基址寄存器,他却完成了地址空间切换的壮举,有的小伙伴可能不明白为啥这就完成了地址空间切换?试想如果进程想要访问一个用户空间虚拟地址,cpu的mmu所做的工作,就是从页表基址寄存器拿到页全局目录的物理基地址,然后和虚拟地址配合来查查找页表,最终找到物理地址进行访问(当然如果tlb命中就不需要遍历页表),每次用户虚拟地址访问的时候(内核空间共享不考虑),由于页表基地址寄存器内存放的是当前执行进程的页全局目录的物理地址,所以访问自己的一套页表,拿到的是属于自己的物理地址(实际上,进程是访问虚拟地址空间的指令数据的时候不断发生缺页异常,然后缺页异常处理程序为进程分配实际的物理页,然后将页帧号和页表属性填入自己的页表条目中),就不会访问其他进程的指令和数据,这也是为何多个进程可以访问相同的虚拟地址而不会出现差错的原因,而且做到的各个地址空间的隔离互不影响(共享内存除外)。

    其实,地址空间切换过程中,还会清空tlb,防止当前进程虚拟地址转化过程中命中上一个进程的tlb表项,一般会将所有的tlb无效,但是这会导致很大的性能损失,因为新进程被切换进来的时候面对的是全新的空的tlb,造成很大概率的tlb miss,需要重新遍历多级页表,所以arm64在tlb表项中增加了非全局(nG)位区分内核和进程的页表项,使用ASID区分不同进程的页表项,来保证可以在切换地址空间的时候可以不刷tlb,后面会主要讲解ASID技术。

    还需要注意的是仅仅切换用户地址空间,内核地址空间由于是共享的不需要切换,也就是为何切换到内核线程不需要也没有地址空间的原因。

    如下为进程地址空间切换示例图:

    2.2 处理器状态(硬件上下文)切换

    前面进行了地址空间切换,只是保证了进程访问指令数据时访问的是自己地址空间(当然上下文切换的时候处于内核空间,执行的是内核地址数据,当返回用户空间的时候才有机会执行用户空间指令数据**,地址空间切换为进程访问自己用户空间做好了准备**),但是进程执行的内核栈还是前一个进程的,当前执行流也还是前一个进程的,需要做切换。

    arm64中切换代码如下:

    switch_to->__switch_to  ... //浮点寄存器等的切换  ->cpu_switch_to(prev, next)
    arch/arm64/kernel/entry.S:1032 /*1033  * Register switch for AArch64. The callee-saved registers need to be saved1034  * and restored. On entry:1035  *   x0 = previous task_struct (must be preserved across the switch)1036  *   x1 = next task_struct1037  * Previous and next are guaranteed not to be the same.1038  *1039  */1040 ENTRY(cpu_switch_to)1041         mov     x10, #THREAD_CPU_CONTEXT1042         add     x8, x0, x101043         mov     x9, sp1044         stp     x19, x20, [x8], #16             // store callee-saved registers1045         stp     x21, x22, [x8], #161046         stp     x23, x24, [x8], #161047         stp     x25, x26, [x8], #161048         stp     x27, x28, [x8], #161049         stp     x29, x9, [x8], #161050         str     lr, [x8]1051         add     x8, x1, x101052         ldp     x19, x20, [x8], #16             // restore callee-saved registers1053         ldp     x21, x22, [x8], #161054         ldp     x23, x24, [x8], #161055         ldp     x25, x26, [x8], #161056         ldp     x27, x28, [x8], #161057         ldp     x29, x9, [x8], #161058         ldr     lr, [x8]1059         mov     sp, x91060         msr     sp_el0, x11061         ret1062 ENDPROC(cpu_switch_to)

    其中x19-x28是arm64 架构规定需要调用保存的寄存器,可以看到处理器状态切换的时候将前一个进程(prev)的x19-x28,fp,sp,pc保存到了进程描述符的cpu_contex中,然后将即将执行的进程(next)描述符的cpu_contex的x19-x28,fp,sp,pc恢复到相应寄存器中,而且将next进程的进程描述符task_struct地址存放在sp_el0中,用于通过current找到当前进程,这样就完成了处理器的状态切换。

    实际上,处理器状态切换就是将前一个进程的sp,pc等寄存器的值保存到一块内存上,然后将即将执行的进程的sp,pc等寄存器的值从另一块内存中恢复到相应寄存器中,恢复sp完成了进程内核栈的切换,恢复pc完成了指令执行流的切换。其中保存/恢复所用到的那块内存需要被进程所标识,这块内存这就是cpu_contex这个结构的位置(进程切换都是在内核空间完成)。

    由于用户空间通过异常/中断进入内核空间的时候都需要保存现场,也就是保存发生异常/中断时的所有通用寄存器的值,内核会把“现场”保存到每个进程特有的进程内核栈中,并用pt_regs结构来描述,当异常/中断处理完成之后会返回用户空间,返回之前会恢复之前保存的“现场”,用户程序继续执行。
    所以当进程切换的时候,当前进程被时钟中断打断,将发生中断时的现场保存到进程内核栈(如:sp, lr等),然后会切换到下一个进程,当再次回切换回来的时候,返回用户空间的时候会恢复之前的现场,进程就可以继续执行(执行之前被中断打断的下一条指令,继续使用自己用户态sp),这对于用户进程来说是透明的。

    如下为硬件上下文切换示例图:

     

    3.ASID机制

    前面讲过,进程切换的时候,由于tlb中存放的可能是其他进程的tlb表项,所有才需要在进程切换的时候进行tlb的清空工作(清空即是使得所有的tlb表项无效,地址转换需要遍历多级页表,找到页表项,然后重新加载页表项到tlb),有了ASID机制之后,命中tlb表项,由虚拟地址和ASID共同决定(当然还有nG位),可以减小进程切换中tlb被清空的机会。

    下面我们讲解ASID机制,ASID(Address Space Identifer 地址空间标识符),用于区别不同进程的页表项,arm64中,可以选择两种ASID长度8位或者16位,这里以8位来讲解。

    如果ASID长度为8位,那么ASID有256个值,但是由于0是保留的,所有可以分配的ASID范围就为1-255,那么可以标识255个进程,当超出255个进程的时候,会出现两个进程的ASID相同的情况,因此内核使用了ASID版本号。
    内核中处理如下(参考arch/arm64/mm/context.c):

    1)内核为每个进程分配一个64位的软件ASID,其中低8位为硬件ASID,高56位为ASID版本号,这个软件ASID存放放在进程的mm_struct结构的context结构的id中,进程创建的时候会初始化为0。
    2)内核中有一个64位的全局变量asid_generation,同样它的高56位为ASID版本号,用于标识当前ASID分配的批次。
    3)当进程调度,由prev进程切换到next进程的时候,如果不是内核线程则进行地址空间切换调用check_and_switch_context,此函数会判断next进程的ASID版本号是否和全局的ASID版本号相同(是否处于同一批次),如果相同则不需要为next进程分配ASID,不相同则需要分配ASID。
    4)内核使用asid_map位图来管理硬件ASID的分配,asid_bits记录使用的ASID的长度,每处理器变量active_asids保存当前分配的硬件ASID,每处理器变量reserved_asids存放保留的ASID,tlb_flush_pending位图记录需要清空tlb的cpu集合。
    硬件ASID分配策略如下:
    (1)如果进程的ASID版本号和当前全局的ASID版本号相同(同批次情况),则不需要重新分配ASID。
    (2)如果进程的ASID版本号和当前全局的ASID版本号不相同(不同批次情况),且进程原本的硬件ASID已经被分配,则重新分配新的硬件ASID,并将当前全局的ASID版本号组合新分配的硬件ASID写到进程的软件ASID中。
    (3)如果进程的ASID版本号和当前全局的ASID版本号不相同(不同批次情况),且进程原本的硬件ASID还没有被分配,则不需要重新分配新的硬件ASID,只需要更新进程软件ASID版本号,并将当前全局的ASID版本号组合进程原来的硬件ASID写到进程的软件ASID中。
    (4)如果进程的ASID版本号和当前全局的ASID版本号不相同(不同批次情况),需要分配硬件ASID时,发现硬件ASID已经被其他进程分配完(asid_map位图中查找,发现位图全1),则这个时候需要递增全局的ASID版本号, 清空所有cpu的tlb, 清空asid_map位图,然后分配硬件ASID,并将当前全局的ASID版本号组合新分配的硬件ASID写到进程的软件ASID中。

    下面我们以实例来看ASID的分配过程:
    如下图:

    我们假设图中从A进程到D进程,有255个进程,刚好分配完了asid, ,从A到D的切换过程中使用的都是同一批次的asid版本号。

    则这个过程中,有进程会创建的时候被切换到,假设不超出255个进程,在切换过程中会为新进程分配硬件的ASID,分配完后下次切换到他时由于他的ASID版本号和当前的全局的ASID版本号相同,所以不需要再次分配ASID,当然也不需要清空tlb。

    注:这里说的ASID即为硬件ASID区别于ASID版本号。

    情况1-ASID版本号不变 属于策略(1):从C进程到D进程切换,内核判断D进程的ASID版本号和当前的全局的ASID版本号相同,所以不需要为他分配ASID(执行快速路径switch_mm_fastpath去设置ttbrx_el1))。

    情况2 -硬件ASID全部分配完 属于策略(4):假设到达D进程时,asid已经全部分配完(系统中有255个进程都分配到了硬件asid号),这个时候新创建的进程E被调度器选中,切换到E,由于新创建的进程的软件ASID被初始化为0,所以和当前的全局的ASID版本号不同(不在同一批次),则这个时候会执行new_context为进程分配ASID,但是由于没有可以分配的ASID,所以会将全局的ASID版本号加1(发生ASID回绕),这个时候全局的ASID为801,然后清空asid_map,置位tlb_flush_pending所有bit用于清空所有cpu的tlb,然后再次去分配硬件ASID给E进程,这个时候分配到了1给他(将ASID版本号)。

    情况3-ASID版本号发生变化,进程的硬件ASID可以再次使用 属于策略(3):假设从E切换到了B进程,而B进程之前已经在全局的ASID版本号为800的批次上分配了编号为5的硬件ASID,但是B进程的ASID版本号800和现在全局的ASID版本号801不相同,所有需要new_context为进程分配ASID,分配的时候发现asid_map中编号为5没有被置位,也就是没有其他进程分配了5这个ASID,所有可以继续使用原来分配的硬件ASID 5。

    情况4 - ASID版本号发生变化,有其他进程已经分配了相同的硬件ASID 属于策略(2): 假设从B进程切换到A进程,而B进程之前已经在全局的ASID版本号为800的批次上分配了编号为1的硬件ASID,但是B进程的ASID版本号800和现在全局的ASID版本号801不相同,所有需要new_context为进程分配ASID,分配的时候发现asid_map中编号为1已经被置位,也就是其他进程已经分配了1这个ASID,需要从asid_map寻找下一个空闲的ASID,则分配了新的ASID为6。

    假设从A到E,由于E的ASID版本号和全局的ASID版本号(同一批次),和情况1相同,不需要分配ASID。但是之前原来处于800这个ASID版本号批次的进程都需要重新分配ASID,有的可以使用原来的硬件ASID,有的重新分配硬件ASID,但是都将ASID版本号修改为了现在全局的ASID版本号801。但是,随着硬件ASID的不断分配,最终处于801这一批次的硬件ASID也会分配完,这个时候就是上面的情况2,要情况所有cpu的tlb。

    我可以看到有了ASID机制之后,由于只有当硬件ASID被分配完了(如被255个进程使用),发生回绕的时候才会清空所有cpu的tlb,大大提高了系统的性能(没有ASID机制的情况下每次进程切换需要地址空间切换的时候都需要清空tlb)。

    4. 普通用户进程、普通用户线程、内核线程切换的差别

    内核地址空间切换的时候有一下原则:看的是进程描述符的mm_struct结构,即是成员mm:
    1)如果mm为NULL,则表示即将切换的是内核线程,不需要切换地址空间(所有任务共享内核地址空间)。
    2)内核线程会借用前一个用户进程的mm,赋值到自己的active_mm(本身的mm为空),进程切换的时候就会比较前一个进程的active_mm和当前进程的mm。
    3)如果前一个任务的和即将切换的任务,具有相同的mm成员,也就是共享地址空间的线程则也不需要切换地址空间。

    ->所有的进程线程之间进行切换都需要切换处理器状态。
    ->对于普通的用户进程之间进行切换需要切换地址空间。
    ->同一个线程组中的线程之间切换不需要切换地址空间,因为他们共享相同的地址空间。
    -> 内核线程在上下文切换的时候不需要切换地址空间,仅仅是借用上一个进程mm_struct结构。

    有一下场景:
    约定:我们将进程/线程统称为任务,其中U表示用户任务(进程/线程),K表示内核线程,带有数字表示同一个线程组中的线程。
    有以下任务:Ua1 Ua2 Ub Uc Ka Kb (eg:Ua1为用户进程, Ua2为和Ua1在同一线程组的用户进程,Ub普通的用户进程,Ka普通的内核线程 )。

    如果调度顺序如下:

    Uc -> Ua1 -> Ua2 -> Ub -> Ka -> Kb -> Ub

    从Uc -> Ua1 由于是不同的进程,需要切换地址空间。
    从 Ua1 -> Ua2 由于是相同线程组中的不同线程,共享地址空间,在切换到Ua1的时候已经切换了地址空间,所有不需要切换地址空间。
    从 Ua2 -> Ub 由于是不同的进程,需要切换地址空间。
    从 Ub -> Ka 由于切换到内核线程,所以不需要切换地址空间。
    从Ka -> Kb 俩内核线程之前切换,不需要切换地址空间。
    从Kb -> Ub 从内核线程切换到用户进程,由于Ka和Kb都是借用Ub的active_mm,而Ub的active_mm 等于Ub的mm,所以这个时候 Kb的active_mm和 Ub的mm相同,所有也不会切换地址空间。

    如下为多任务地址空间切换示例图:

     

    5. 进程切换全景视图

    我们以下场景为例:
    A,B两个进程都是普通的用户进程,从进程A切换到进程B,简单起见我们在这里不考虑其他的抢占时机,我们假设A,B进程只是循环进行一些基本的运算操作,从来不调用任何系统调用,只考虑被时钟中断,返回用户空间之前被抢占的情况。

    下面给出进程切换的全景视图:

    视图中已经讲解很清楚,需要强调3个关键点:

    1.发生中断时的保存现场,将发生中断时的所有通用寄存器保存到进程的内核栈,使用struct pt_regs结构。

    2.地址空间切换将进程自己的页全局目录的基地址pgd保存在ttbr0_le1中,用于mmu的页表遍历的起始点。

    3.硬件上下文切换的时候,将此时的调用保存寄存器和pc, sp保存到struct cpu_context结构中。做好了这几个保存工作,当进程再次被调度回来的时候,通过cpu_context中保存的pc回到了cpu_switch_to的下一条指令继续执行,而由于cpu_context中保存的sp导致当前进程回到自己的内核栈,经过一系列的内核栈的出栈处理,最后将原来保存在pt_regs中的通用寄存器的值恢复到了通用寄存器,这样进程回到用户空间就可以继续沿着被中断打断的下一条指令开始执行,用户栈也回到了被打断之前的位置,而进程访问的指令数据做地址转化(VA到PA)也都是从自己的pgd开始进行,一切对用户来说就好像没有发生一样,简直天衣无缝。

    6. 总结

    进程管理中最重要的一步要进行进程上下文切换,其中主要有两大步骤:地址空间切换和处理器状态切换(硬件上下文切换),前者保证了进程回到用户空间之后能够访问到自己的指令和数据(其中包括减小tlb清空的ASID机制),后者保证了进程内核栈和执行流的切换,会将当前进程的硬件上下文保存在进程所管理的一块内存,然后将即将执行的进程的硬件上下文从内存中恢复到寄存器,有了这两步的切换过程保证了进程运行的有条不紊,当然切换的过程是在内核空间完成,这对于进程来说是透明的。

     

     

    展开全文
  • 1.首先,需要搞明白什么是上下文切换上下文切换就是从当前执行任务切换到另一个任务执行的过程。但是,为了确保下次能从正确的位置继续执行,在切换之前,会保存上一个任务的状态。 2. 然后,需要明白进程与线程...

    1.首先,需要搞明白什么是上下文切换?
    上下文切换就是从当前执行任务切换到另一个任务执行的过程。但是,为了确保下次能从正确的位置继续执行,在切换之前,会保存上一个任务的状态。

    2. 然后,需要明白进程与线程的区别?(网上很多,这里简单说明) 
    1).线程是进程的一部分。进程是表示资源分配的基本单位,又是调度运行的基本单位,是程序执行的一个实例; 线程是进程中执行运算的最小单位,即执行处理机调度的基本单位,是进程中的一个执行流。 
    2).内存空间不同。每一个进程拥有自己独立的内存空间,而线程共享进程的内存空间。

    3. 所以,进程上下文切换与线程上下文切换最主要的区别就是线程的切换虚拟空间内存是相同的(因为都是属于自己的进程),但是,进程切换的虚拟空间内存则是不同的。

    同时,这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。

    线程上下文切换比进程上下文切换快的多。

    补充:多线程是如何实现的? 
    主要是CPU通过给每个线程分配CPU时间片来实现多线程的。即使是单核处理器(CPU)也可以执行多线程处理。

    时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。

    4. 在进行上下文切换时,CPU通过时间片分配算法来循环执行任务,Java中的时间片分配算法有哪些? 
    最简单最常用的就是基于时间片轮转调度算法。时间片轮转调度算法是非常公平的处理机分配方式,可以使就绪队列的每个进程每次仅运行一个时间片。 
    原理:在时间片轮转调度算法中,系统根据先来先服务的原则,将所有的就绪进程排成一个就绪队列,并且每隔一段时间产生一次中断,激活系统中的进程调度程序,完成一次处理机调度,把处理机分配给就绪队列队首进程,让其执行指令。当时间片结束或进程执行结束,系统再次将CPU分配给队首进程。

    展开全文
  • 进程上下文切换与线程上下文切换

    千次阅读 2020-04-06 22:45:33
    进程上下文切换 进程上下文包含了进程执行所需要的所有信息。 用户地址空间:包括程序代码,数据,用户堆栈等; 控制信息:进程描述符,内核栈等; 硬件上下文:进程恢复前,必须装入寄存器的数据统称为硬件上下文...

    进程上下文切换

    进程上下文包含了进程执行所需要的所有信息。
    
    用户地址空间:包括程序代码,数据,用户堆栈等;
    控制信息:进程描述符,内核栈等;
    硬件上下文:进程恢复前,必须装入寄存器的数据统称为硬件上下文。
    
    进程切换分3步
    
    a.切换页目录以使用新的地址空间
    b.切换内核栈
    c.切换硬件上下文
    4、刷新TLB
    5、系统调度器的代码执行
    
    

    线程上下文切换

      对于linux来说,线程和进程的最大区别就在于地址空间。
      对于线程切换,第1步是不需要做的,第2和3步是进程和线程切换都要做的。所以明显是进程切换代价大
    
      1.  线程上下文切换和进程上下文切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,
        但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。
        内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
    
      2.  另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。
        简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。
        还有一个显著的区别是当你改变虚拟内存空间的时候,
        处理的页表缓冲(processor’s Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,
        这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。
    

    总结

    最后来个图总结 更清晰

    在这里插入图片描述

    展开全文
  • 1、首先,需要搞明白什么是上下文切换?(面试题) 上下文切换就是从当前执行任务切换到另一个任务执行的过程。但是,为了确保下次能从正确的位置继续执行,在切换之前,会保存上一个任务的状态。 2、 然后,需要...
  • 首先,需要搞明白什么是上下文切换上下文切换就是从当前执行任务切换到另一个任务执行的过程。但是,为了确保下次能从正确的位置继续执行,在切换之前,会保存上一个任务的状态。 然后,需要明白进程与线程的...
  • 文章目录1 进程上下文与中断上下文2 进程上下文切换2.1 概念2.2 步骤3 处理器模式切换3.1 概念3.2 步骤4系统任务与用户进程 1 进程上下文与中断上下文 进程上下文 所谓的“进程上下文”,就是一个进程在执行...
  • 1.进程上下文切换过程: @1.由中断、异常、系统调用等触发中断,将cpu切换为内核模式,将eip,esp,eflages压入内核堆栈; @2.保存硬件未来得及保存的现场信息; @3.调用中断服务程序; @4.检查need_resched标志...
  • 都知道操作系统的一个重要功能就是...本文主要关注进程管理的一个切入点,那就是进程的上下文切换,来理解linux内核是如何进程进程上下文切换的,从而揭开上下文切换的神秘面纱。进程地址空间切换和处理器状态切换。
  • 进程切换分两步 1.切换页目录以使用新的地址空间 ...线程上下文切换进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通
  • 对于服务器的优化,很多人都有自己的经验和见解,但就我观察,有两点常常会被人忽视 – 上下文切换 和 Cache Line同步 问题,人们往往都会习惯性地把视线集中在尽力减少内存拷贝,减少IO次数这样的问题上,不可否认...
  • 进程上下文切换分析

    2018-07-15 20:59:00
    我今天分析一下进程上下文切换,也就是进程调度时,怎么由当前进程切换到另一个进程的。 1、概述  进程调度的时机,也就是进程是在啥时候切换,触发因数是什么。  中断发生时,进入中断处理中断服务程序——...
  • 进程上下文切换

    2015-11-06 21:38:00
    以前老是看到进程上下文切换,只知道是时一些信息从内存和cpu里换出,概念很模糊,今天翻了下书,记录下 上下文切换:操作系统内核用一种称为上下文切换的较高形式的异常控制流来实现多任务 内核为每一个进程维护...
  • 线程上下文切换与进程上下文切换

    千次阅读 2017-09-27 18:29:24
    进程上下文包含了进程执行所需要的所有信息。 用户地址空间:包括程序代码,数据,用户堆栈等; 控制信息:进程描述符,内核栈等; 硬件上下文:(注意中断也要保存硬件上下文只是保存的方法不同)。 2、进程...
  • Linux进程上下文切换过程context_switch详解 日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.6 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度 前面我们了解了linux进程调度器的...
  • ##################################### 作者:张卓 原创作品转载请注明出处:《Linux操作系统分析》MOOC课程 ...##################################### 1. 前言 一篇我们详细地分析一
  • 进程上下文切换

    2021-07-09 21:12:32
    名词解释:因为不同进程之间共享CPU的资源,所以在不同进程切换到CPU里面被执行时,我们称一个进切换到另一个进程运行为:进程上下文切换 这里涉及到CPU寄存器和程序计数器:CPU寄存器是容量很小的速度非常快的...
  • (2)进程切换过程中的上下文信息保存与恢复,必须在内核态吗?(3)信号量和管程机制是否可以相互实现,有没有确定的答案和证明?首先理解以下概念:一、进程控制块进程控制块:PCB是操作系统管理控制进程运行所有...
  • 进程、线程上下文切换

    千次阅读 2020-05-24 22:12:02
    上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动:(1)挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处,(2)在内存中检索下一个进程的上下文并将...
  • 进程、线程上下文切换的开销

    千次阅读 2020-09-10 11:21:07
    进程、线程上下文切换的开销 虚拟内存与地址空间映射关系 虚拟内存是操作系统为每个进程提供的一种抽象,每个进程都有属于自己的、私有的、地址连续的虚拟内存,当然我们知道最终进程的数据及代码必然要放到物理...
  • 各个进程之间是共享 CPU 资源的,在不同的时候进程之间需要切换,让不同的进程可以在 CPU 执行,那么这个一个进程切换到另一个进程运行,称为进程上下文切换进程是由内核管理和调度的,所以进程的切换只能发生...
  • 上下文切换详解 首先谈谈上下文切换的概念。...上下文切换只能发生在内核态中,上下文切换过程中的信息被保存在进程控制块(PCB)中。 上下文切换的信息会一直被保存在CPU的内存中,直到被再次使用。 ...
  • 进程切换过程之一:上下文切换

    千次阅读 2020-01-26 22:32:25
    进程切换过程之一:上下文切换 注:下面给出的源码均出自https://github.com/mit-pdos/xv6-public 在进程进行切换时,需要进行上下文切换,也就是寄存器的值的保存。 对于上下文的切换,有两种情况:硬件自动...
  • 先在主机通过top、free、ps、iostat 等常用工具分析了下主机的CPU、内存、IO使用情况,发现三者都不高。通过vmstat 1 查看的结果如下:   从vmstat的输出结果来看,io项的block in 和block ...
  • 一、正常控制流 (1)按顺序取下一条指令执行...③进程上下文切换(操作系统层) 2.程序和进程 程序 按某种方式形成的代码和数据的集合,静态的概念。 进程 程序关于某个数据集合的一次运行活动。动态的含义。不.
  • 这个程序是模仿一个Linux内核(不停的循环运行),有多个进程(模拟)运行在内核中,然后内核进行切换。这个内核不是凭空构想出来的,而是在真实Linux内核源码的基础精简修改而来。所以是十分值得学习的。废话不多...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 112,991
精华内容 45,196
关键字:

进程上下文切换