lab5:深入理解Linux进程切换

Linux的进程切换过程包括如下两大部分:

一、选择哪个进程作为下一个进程;

二、完成进程上下文切换:

        1、用户地址空间

        2、控制信息

        3、进程CPU上下文,寄存器的值

以linux-5.4.34为例分析进程切换:

schedule()函数位于kernel/sched/core.c中,在__schedule()中调用context_switch选择一个新的进程来运行,并进行上下文的切换。context_switch首先调用switch_mm切换CR3,然后调用宏switch_to来进行CPU上下文切换。

asmlinkage __visible void __sched schedule(void)
{
	struct task_struct *tsk = current;

	sched_submit_work(tsk);
	do {
		preempt_disable();
		__schedule(false);
		sched_preempt_enable_no_resched();
	} while (need_resched());
	sched_update_worker(tsk);
}
EXPORT_SYMBOL(schedule);

在__schedule()中,通过next = pick_next_task(rq, prev)找到下一个进程,context_switch(rq, prev, next),进行进程上下文的切换。

其中mm为进程描述符,对于用户进程来说,mm指向虚拟地址空间的用户空间部分,而对于内核线程,mm为NULL。如果切换进来的新进程为内核进程,新进程的active_mm设置为旧进程的用户地址空间,并将旧进程的mm_count+1。

static __always_inline struct rq *context_switch(struct rq *rq,
						 struct task_struct *prev,
						 struct task_struct *next,
						 struct rq_flags *rf)
{
    ...
	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 { 
		...
    /* 这里切换寄存器状态和栈 */  
    switch_to(prev, next, prev);  
    ...
}

通过一个宏替换执行switch_to,不同的体系结构所定义的宏各不相同,位于arch/{archname}/include/asm/switch_to.h中,x86定义如下:

#define switch_to(prev, next, last)					\
do {									\
	prepare_switch_to(next);					\
									\
	((last) = __switch_to_asm((prev), (next)));			\
} while (0)

中间的两句movq表示的即为堆栈切换的过程,之后只需要弹出栈中保存的各个寄存器的值即可恢复寄存器状态。但上面似乎并没有显式地保存、修改指针寄存器 rip 的值,rip的值是如何切换的呢?

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)

这里需要对函数调用堆栈框架的深入理解才能发现端倪,注意__switch_to_asm是在C代码中调用的,也就是使用call指令,而这段汇编的结尾是jmp __switch_to,__switch_to函数是C代码最后有个return,也就是ret指令。将__switch_to_asm和__switch_to结合起来,正好是call指令和ret指令的配对出现。call指令压栈RIP寄存器到进程切换前的prev进程内核堆栈;而ret指令出栈存入RIP寄存器的是进程切换之后的next进程的内核堆栈栈顶数据。

可以继续分析arm下的进程切换,switch_to在arm下定义:

#define switch_to(prev,next,last)					\
do {									\
	__complete_pending_tlbi();					\
	last = __switch_to(prev,task_thread_info(prev), task_thread_info(next));	\
} while (0)

task_thread_info返回task->thread_info结构体的地址

static inline struct thread_info *task_thread_info(struct task_struct *task)
{
	return &task->thread_info;
}

__switch_to如下:根据ATPCS规则,r0-r3传递参数,r4-r11保存局部变量,r12为ip,r13为sp,r14为lr,r15为pc。

ENTRY(__switch_to)
 UNWIND(.fnstart	)
 UNWIND(.cantunwind	)
	add	ip, r1, #TI_CPU_SAVE
 ARM(	stmia	ip!, {r4 - sl, fp, sp, lr} )	@ Store most regs on stack
 THUMB(	stmia	ip!, {r4 - sl, fp}	   )	@ Store most regs on stack
 THUMB(	str	sp, [ip], #4		   )
 THUMB(	str	lr, [ip], #4		   )
	ldr	r4, [r2, #TI_TP_VALUE]
	ldr	r5, [r2, #TI_TP_VALUE + 4]
#ifdef CONFIG_CPU_USE_DOMAINS
	mrc	p15, 0, r6, c3, c0, 0		@ Get domain register
	str	r6, [r1, #TI_CPU_DOMAIN]	@ Save old domain register
	ldr	r6, [r2, #TI_CPU_DOMAIN]
#endif
	switch_tls r1, r4, r5, r3, r7
#if defined(CONFIG_STACKPROTECTOR) && !defined(CONFIG_SMP)
	ldr	r7, [r2, #TI_TASK]
	ldr	r8, =__stack_chk_guard
	.if (TSK_STACK_CANARY > IMM12_MASK)
	add	r7, r7, #TSK_STACK_CANARY & ~IMM12_MASK
	.endif
	ldr	r7, [r7, #TSK_STACK_CANARY & IMM12_MASK]
#endif
#ifdef CONFIG_CPU_USE_DOMAINS
	mcr	p15, 0, r6, c3, c0, 0		@ Set domain register
#endif
	mov	r5, r0
	add	r4, r2, #TI_CPU_SAVE
	ldr	r0, =thread_notify_head
	mov	r1, #THREAD_NOTIFY_SWITCH
	bl	atomic_notifier_call_chain
#if defined(CONFIG_STACKPROTECTOR) && !defined(CONFIG_SMP)
	str	r7, [r8]
#endif
 THUMB(	mov	ip, r4			   )
	mov	r0, r5
 ARM(	ldmia	r4, {r4 - sl, fp, sp, pc}  )	@ Load all regs saved previously
 THUMB(	ldmia	ip!, {r4 - sl, fp}	   )	@ Load all regs saved previously
 THUMB(	ldr	sp, [ip], #4		   )
 THUMB(	ldr	pc, [ip]		   )
 UNWIND(.fnend		)
ENDPROC(__switch_to)

add ip, r1, #TI_CPU_SAVE  将IP寄存器赋值为r1+ TI_CPU_SAVE,r1即为prev->thread_info,TI_CPU_SAVE是cpu_context成员在thread_info中的偏移。因此IP寄存器保存了prev->thread_info->cpu_context的地址。

ARM( stmia ip!, {r4 - sl, fp, sp, lr}) 将r4 - sl, fp, sp, lr寄存器中的内容保存到IP寄存器所指向的内存地址,即prev->thread_info->cpu_context,这相当于保存了prev进程运行时的寄存器上下文。

ARM( ldmia r4, {r4 - sl, fp, sp, pc}) 将next->thread_info->cpu_context的数据加载到r4 - sl, fp, sp, lr,pc寄存器中,next->thread_info->cpu_context->sp存入寄存器SP相当于内核栈切换完成,next->thread_info->cpu_context->pc存入寄存器PC相当于跳转到next进程运行。即切换到next进程运行时的寄存器上下文。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值