在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标准文档描述
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,这个指令其实就是读寄存器,没有"先进/后进/先出/后出"等概念了,你想怎么进就怎么进,想怎么出就怎么出