Linux教程

linux kernel进程切换(寄存器保存与恢复)

本文主要是介绍linux kernel进程切换(寄存器保存与恢复),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

在threadA执行时,被调度了执行了threadB, 那么在threadB执行完之后是如何恢复threadA进程能够继续执行的呢?

我们从代码中去寻找答案, 系统调度是schedule()函数,那么我们就从schedule()函数看起
schedule() --> __schedule(true) --> context_switch(rq, prev,next) --> switch_to(prev, next, prev) --> __switch_to((prev), (next))

/*
 * Thread switching.
 */
struct task_struct *__switch_to(struct task_struct *prev,
				struct task_struct *next)
{
	struct task_struct *last;

	fpsimd_thread_switch(next);
	tls_thread_switch(next);
	hw_breakpoint_thread_switch(next);
	contextidr_thread_switch(next);
#ifdef CONFIG_THREAD_INFO_IN_TASK
	entry_task_switch(next);
#endif
	uao_thread_switch(next);

	/*
	 * Complete any pending TLB or cache maintenance on this CPU in case
	 * the thread migrates to a different CPU.
	 */
	dsb(ish);

	/* the actual thread switch */
	last = cpu_switch_to(prev, next);

	return last;
}

1、通用寄存器的保存
schedule() --> __schedule(true) --> context_switch(rq, prev,next) --> switch_to(prev, next, prev) --> __switch_to((prev), (next)) --> cpu_switch_to(prev, next)

/*
 * Register switch for AArch64. The callee-saved registers need to be saved
 * and restored. On entry:
 *   x0 = previous task_struct (must be preserved across the switch)
 *   x1 = next task_struct
 * Previous and next are guaranteed not to be the same.
 *
 */
ENTRY(cpu_switch_to)
	mov	x10, #THREAD_CPU_CONTEXT
	add	x8, x0, x10		// previous task_struct + THREAD_CPU_CONTEXT,X8为前一个进程的struct cpu_context结构体指针
	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]       // lr压栈
	add	x8, x1, x10    // next task_struct + THREAD_CPU_CONTEXT,X8为后一个进程的struct cpu_context结构体指针
	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]        lr出栈
	mov	sp, x9
#ifdef CONFIG_THREAD_INFO_IN_TASK
	msr	sp_el0, x1
#else
	and	x9, x9, #~(THREAD_SIZE - 1)
	msr	sp_el0, x9
#endif
	ret
ENDPROC(cpu_switch_to)

根据ARM标准文档描述<IHI0056C_beta_aaelf64.pdf>, x19~x28是属于
callee-saved registers,需要保存,此外sp,lr,pc也需要保存

THREAD_CPU_CONTEXT为thread.cpu_context在struct task_struct结构体中的偏移

 DEFINE(THREAD_CPU_CONTEXT,	offsetof(struct task_struct, thread.cpu_context));
 struct task_struct {
	......
	struct thread_struct thread;
 };
 struct thread_struct {
	struct cpu_context	cpu_context;	/* cpu context */
	unsigned long		tp_value;	/* TLS register */
#ifdef CONFIG_COMPAT
	unsigned long		tp2_value;
#endif
	struct fpsimd_state	fpsimd_state;
	unsigned long		fault_address;	/* fault info */
	unsigned long		fault_code;	/* ESR_EL1 value */
	struct debug_info	debug;		/* debugging */
}
struct cpu_context {
	unsigned long x19;
	unsigned long x20;
	unsigned long x21;
	unsigned long x22;
	unsigned long x23;
	unsigned long x24;
	unsigned long x25;
	unsigned long x26;
	unsigned long x27;
	unsigned long x28;
	unsigned long fp;
	unsigned long sp;
	unsigned long pc;
};

问与答:
关于前面的一段汇编,上面stp的一段是压栈,将前一个进程的寄存器保存起来。
后面ldp的一段是出栈,将后一个进程的寄存器恢复出来。
关于栈,不都是先进后出吗? 为何这里是先进先出、后进后出?

在arm32上,是PUSH,POP等指令,是先进后出
到了arm64上,没有了PUSH POP指令,取而代之的是STP LDP,这个指令其实就是读寄存器,没有"先进/后进/先出/后出"等概念了,你想怎么进就怎么进,想怎么出就怎么出

这篇关于linux kernel进程切换(寄存器保存与恢复)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!