lab5:深入理解进程切换

进程切换过程

根据调度算法策略,选取要执行的下一个进程        schedule()

进程上下文切换   ->                                                 context_switch

                             进程地址空间切换                       switch_mm_irqs_off

                             内核堆栈和硬件上下文切换         switch_to

Linux的进程切换由context_switch函数完成,函数位于源码目录的 kernel/sched/core.c 

/*
 * context_switch - switch to the new MM and the new thread's register state.
 */
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);

	/*
	 * For paravirt, this is coupled with an exit in switch_to to
	 * combine the page table reload and the switch backend into
	 * one hypercall.
	 */
	arch_start_context_switch(prev);

	/*
	 * 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
		membarrier_switch_mm(rq, prev->active_mm, next->mm);
		/*
		 * sys_membarrier() requires an smp_mb() between setting
		 * rq->curr / membarrier_switch_mm() and returning to userspace.
		 *
		 * The below provides this either through switch_mm(), or in
		 * case 'prev->active_mm == next->mm' through
		 * finish_task_switch()'s mmdrop().
		 */
		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;
		}
	}

	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);
}

分析:prev 是进程切换前执行的进程
        next 是进程切换后要执行的进程
        next->mm 指向切换后要执行的进程的地址空间描述符
        prev->mm 指向切换之前进程的当前正在使用的地址空间描述符active_mm ,如果该进程是内核线程,则mm为空,内核线程没有自己的用户空间上下文,因此需要借用其他进程正在使用的地址空间,即active_mm。          

        如果next进程是内核线程那么调用体系结构相关的代码enter_lazy_tlb,标识该cpu进入lazy tlb mode。因为要切入的进程实际上是内核线程,那么我们也暂时不需要更新TLB,因为内核线程不会访问用户空间,所以那些无效的TLB也不会影响内核线程的执行。在这种情况下,为了性能,会进入lazy tlb mode。由于next进程是内核线程,会借用当前正在使用的地址空间,因此没有必要调用switch_mm进行地址空间切换。如果prev进程是内核线程,那么其借用的那个地址空间(active_mm)已经不需要继续使用了。如果prev进程是用户进程,则将被借用的mm_struct的引用计数器加1,使得即使用户进程退出,地址空间也不会被马上销毁(需要等到引用计数器为0才能销毁)。如果next进程是一个用户进程,则调用switch_mm_irqs_off进行用户地址空间切换,包括页表切换和刷新TLB。之后判断如果prev进程是内核线程,则停止借用地址空间并处理引用计数器。

switch_to函数分析

ARM64:

ENTRY(cpu_switch_to)             // x0,x1用作参数传递分别保存prev和next
	mov	x10, #THREAD_CPU_CONTEXT // 寄存器x10存放THREAD_CPU_CONTEXT的偏移
	add	x8, x0, x10             // x0与偏移量相加后存入x8,获取旧进程cpu_context的地址
	mov	x9, sp                 // 将栈顶指针sp存入x9寄存器

	stp	x19, x20, [x8], #16		// 将寄存器x19~x29的值保存,每条指令执行完成后X8寄存器的值会自动加16.保存现场
	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]               //保存寄存器LR,该寄存器存放了cpu_switch_to函数的返回地址
	
	add	x8, x1, x10             // 获取访问next进程的cpu_context的指针
	ldp	x19, x20, [x8], #16		// 恢复next进程的现场
	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
	
	ret                         // 此时ret将next进程的lr寄存器的值加载到PC,进程切换完毕
ENDPROC(cpu_switch_to)
NOKPROBE(cpu_switch_to)

                                 

 x86:

ENTRY(__switch_to_asm)
  UNWIND_HINT_FUNC
  /*
   * Save callee-saved registers
   * This must match the order in inactive_task_frame
   */
  pushq  %rbp
  pushq  %rbx
  pushq  %r12
  pushq  %r13
  pushq  %r14
  pushq  %r15

  /* switch stack */
  movq  %rsp, TASK_threadsp(%rdi) // 保存旧进程的栈顶
  movq  TASK_threadsp(%rsi), %rsp // 恢复新进程的栈顶

  /* restore callee-saved registers */
  popq  %r15
  popq  %r14
  popq  %r13
  popq  %r12
  popq  %rbx
  popq  %rbp

  jmp  __switch_to
END(__switch_to_asm)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值