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