本文主要描述主动调度过程,如用户态代码调用sched_yield。
1:通过系统调用,进入内核态,在内核系统调用处理入口(entry_SYSCALL_64),先保存部分寄存器信息到当前进程对应的内核栈上(task_struct->thread.sp)
pushq $__USER_DS /*pt_regs->ss */
pushq PER_CPU_VAR(rsp_scratch) /*pt_regs->sp */
pushq %r11 /*pt_regs->flags */
pushq $__USER_CS /*pt_regs->cs */
pushq %rcx /*pt_regs->ip */
pushq %rax /*pt_regs->orig_ax */
pushq %rdi /*pt_regs->di */
pushq %rsi /*pt_regs->si */
pushq %rdx /*pt_regs->dx */
pushq %rcx /*pt_regs->cx */
pushq $-ENOSYS /*pt_regs->ax */
pushq %r8 /*pt_regs->r8 */
pushq %r9 /*pt_regs->r9 */
pushq %r10 /*pt_regs->r10 */
pushq %r11 /*pt_regs->r11 */
sub $(6*8),%rsp /*pt_regs->bp, bx, r12-15 not saved */
2:执行系统调用处理函数,
call *sys_call_table(, %rax, 8)
3:在系统调用处理函数里,调用schedule函数
4:schedule通过pick_next_task选取下一个要执行的task
5:进入switch_mm_irqs_off,切换页表信息(load_cr3(next->pgd))
6:switch_to切换上下文
如果是x86平台,最终调用__switch_to_asm
ENTRY(__switch_to_asm)
/*
* Save callee-saved registers
* This must match the order ininactive_task_frame
*/
pushq %rbp
pushq %rbx
pushq %r12
pushq %r13
pushq %r14
pushq %r15
/*switch stack */
movq %rsp,TASK_threadsp(%rdi) // 将当前的内核栈信息保存到task1结构体里
movq TASK_threadsp(%rsi),%rsp // 从next_task结构体的thread.sp获取内核栈信息
#ifdefCONFIG_CC_STACKPROTECTOR
movq TASK_stack_canary(%rsi),%rbx
movq %rbx,PER_CPU_VAR(irq_stack_union)+stack_canary_offset
#endif
/*restore callee-saved registers */
popq %r15
popq %r14
popq %r13
popq %r12
popq %rbx
popq %rbp
jmp __switch_to
END(__switch_to_asm)
7:到这一步,当前的rsp已经表示为next_task对应的内核栈信息了,接下来,从内核态返回到用户态,并拿着rsp恢复出所有的寄存器信息,这样执行iret返回用户态时就回到了next_task的用户态现场。