linux 进程切换(1) - 基本框架

  • 了解进程切换流程;

1.context_switch代码分析

  context_switch函数用来完成具体的进程切换。代码调用流程:

kernel/sched/core.c:
schedule()
	->__schedule(false);
		->context_switch

  context_switch函数分析:

static inline struct rq * context_switch(struct rq *rq, struct task_struct *prev, 
           struct task_struct *next)------------------(1) 
{ 
    struct mm_struct *mm, *oldmm;

    mm = next->mm; 
    oldmm = prev->active_mm;-------------------(2)

	/*  如果next是内核线程,则线程使用prev所使用的地址空间
     *  schedule( )函数把该线程设置为懒惰TLB模式
     *  内核线程并不拥有自己的页表集(task_struct->mm = NULL)
     *  它使用一个普通进程的页表集
     *  不过,没有必要使一个用户态线性地址对应的TLB表项无效
     *  因为内核线程不访问用户态地址空间。
    */
    if (!mm) {---------------------------(3) 
     	/*  内核线程的active_mm为上一个进程的mm
         *  注意此时如果prev也是内核线程,
         *  则oldmm为NULL, 即next->active_mm也为NULL  */
        next->active_mm = oldmm;   
        
        atomic_inc(&oldmm->mm_count);   /*  增加mm的引用计数  */
        
        /*  通知底层体系结构不需要切换虚拟地址空间的用户部分
         *  这种加速上下文切换的技术称为惰性TBL  */
        enter_lazy_tlb(oldmm, next);-----------------(4) 
    } else 
   	 /*  不是内核线程, 则需要切切换虚拟地址空间  */
        switch_mm(oldmm, mm, next); ---------------(5)

	/*  如果prev是内核线程或正在退出的进程
     *  就重新设置prev->active_mm
     *  然后把指向prev内存描述符的指针保存到运行队列的prev_mm字段中
     */
    if (!prev->mm) {------------------------(6) 
        prev->active_mm = NULL; 
        rq->prev_mm = oldmm; 
    }

    switch_to(prev, next, prev);------------------(7) 
    
     /*  switch_to之后的代码只有在
     *  当前进程再次被选择运行(恢复执行)时才会运行
     *  而此时当前进程恢复执行时的上一个进程可能跟参数传入时的prev不同
     *  甚至可能是系统中任意一个随机的进程
     *  因此switch_to通过第三个参数将此进程返回
     */
     
	 /*  路障同步, 一般用编译器指令实现
     *  确保了switch_to和finish_task_switch的执行顺序
     *  不会因为任何可能的优化而改变  */
    barrier();

	/*  进程切换之后的处理工作  */
    return finish_task_switch(prev); 
}
  • (1) 一旦调度器算法确定了pre task和next task,那么就可以调用context_switch函数实际执行进行切换的工作了,参数传递情况:

    • rq:在多核系统中,进程切换总是发生在各个cpu core上,参数rq指向本次切换发生的那个cpu对应的run queue
    • prev:将要被剥夺执行权利的那个进程
    • next:被选择在该cpu上执行的那个进程
  • (2) next是马上就要被切入的进程(简称B进程),prev是马上就要被剥夺执行权利的进程(简称A进程)。mm变量指向B进程的地址空间描述符,oldmm变量指向A进程的当前正在使用的地址空间描述符(active_mm)。

    • 对于normal进程,其task_struct的mm和active_mm相同,都是指向其进程地址空间。
    • 对于内核线程而言,其task_struct的mm成员为NULL(内核线程没有进程地址空间),但是,内核线程被调度执行的时候,总是需要一个进程地址空间,而active_mm就是指向它借用的那个进程地址空间。
  • (3)如果mm为空,说明B进程是内核线程,这时只能借用A进程当前正在使用的那个地址空间(prev->active_mm)。注意:这里不能借用A进程的地址空间(prev->mm),因为A进程也可能是一个内核线程,不拥有自己的地址空间描述符。

  • (4)如果要切入的B进程是内核线程,那么调用体系结构相关的代码enter_lazy_tlb,标识该cpu进入lazy tlb mode。如果要切入的进程实际上是内核线程,那么暂时不需要flush TLB,因为内核线程不会访问usersapce,所以那些无效的TLB entry也不会影响内核线程的执行。在这种情况下,为了性能,会进入lazy tlb mode。

  • (5)如果要切入的B进程是内核线程,那么由于是借用当前正在使用的地址空间,因此没有必要调用switch_mm进行地址空间切换,只有要切入的B进程是一个普通进程的情况下(有自己的地址空间)才会调用switch_mm,真正执行地址空间切换。

  • (6)如果切出的A进程是内核线程,那么其借用的那个地址空间(active_mm)已经不需要继续使用了(内核线程A被挂起了,根本不需要地址空间了)。除此之外,还设定了run queue上一次使用的mm struct(rq->prev_mm)为oldmm。

  • (7)一次进程切换,表面上看起来涉及两个进程,实际上涉及到了三个进程。switch_to和一般的调用函数不同,当A进程在CPUa调用它切换到B进程的时候,switch_to一去不回,直到在某个cpu上(称之CPUx)完成从X进程(就是last进程)到A进程切换的时候,switch_to返回到A进程的现场。switch_to完成了具体prev到next进程的切换,当switch_to返回的时候,说明A进程再次被调度执行了。

2.ARM64的进程地址空间切换

  对于ARM64这个cpu arch,每一个cpu core都有两个寄存器来指示当前运行在该CPU core上的进程(线程)实体的地址空间。这两个寄存器分别是ttbr0_el1(用户地址空间)和ttbr1_el1(内核地址空间)。由于所有的进程共享内核地址空间,因此所谓地址空间切换也就是切换ttbr0_el1而已。每一个进程都有自己独立的一组用于翻译用户空间虚拟地址的Translation table,这些信息保存在内存描述符中,具体位于struct mm_struct中的pgd成员中。以pgd为起点,可以遍历该内存描述符的所有用户地址空间的Translation table。具体代码如下:

static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, 
      struct task_struct *tsk)----------------(1) 
{ 
    unsigned int cpu = smp_processor_id();

    if (prev == next)--------------------(2) 
        return;

    if (next == &init_mm) {-----------------(3) 
        cpu_set_reserved_ttbr0(); 
        return; 
    }

    check_and_switch_context(next, cpu); 
}
  • (1)prev是要切出的地址空间,next是要切入的地址空间,tsk是将要切入的进程。

  • (2)要切出的地址空间和要切入的地址空间是一个地址空间的话,那么切换地址空间也就没有什么意义了。

  • (3)在ARM64中,地址空间的切换主要是切换ttbr0_el1,对于swapper进程的地址空间,其用户空间没有任何的mapping,而如果要切入的地址空间就是swapper进程的地址空间的时候,将(设定ttbr0_el1指向empty_zero_page)。

  • (4)check_and_switch_context中有很多TLB、ASID相关的操作,最终该函数会调用arch/arm64/mm/proc.S文件中的cpu_do_switch_mm将要切入进程的L0 Translation table物理地址(保存在内存描述符的pgd成员)写入ttbr0_el1。

refer to

  • https://blog.csdn.net/gatieme/article/details/51872659
  • http://www.wowotech.net/process_management/context-switch-arch.html
  • http://www.wowotech.net/process_management/context-switch-tlb.html
发布了161 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41028621/article/details/104575515
今日推荐