Linux 进程管理之进程调度与切换( 四 )

switch_to 把寄存器中的值比如esp等存放到进程thread结构中,保存现场一边后续恢复,同时调用 __switch_to 完成了堆栈的切换 。
在进程的 task_struct 结构中有个重要的成分 thread,它本身是一个数据结构 thread_struct, 里面记录着进程在切换时的(系统空间)堆栈指针,取指令地址(也就是“返回地址”)等关键性的信息 。
/*__switch_to 处理的主要逻辑是TSS,其核心就是load_esp0将TSS中的内核空间(0级)堆栈指针换成next->esp0. 这是因为cpu在穿越中断门或者陷阱门时要根据新的运行级别从TSS中取得进程在系统空间的堆栈指针,其次,段寄存器fs和gs的内容也做了相应的切换 。同时cpu中为debug而设计的一些寄存器以及说明进程I/O 操作权限的位图 。*/struct task_struct fastcall * __switch_to(struct task_struct *prev_p, struct task_struct *next_p){struct thread_struct *prev = &prev_p->thread,*next = &next_p->thread;int cpu = smp_processor_id();struct tss_struct *tss = &per_cpu(init_tss, cpu);.../* 将TSS 中的内核级(0 级)堆栈指针换成next->esp0,这就是next 进程在内核栈的指针*/load_esp0(tss, next);//为prev保存gssavesegment(gs, prev->gs);//从next的tls_array缓存中加载线程的Thread-Local Storage描述符load_TLS(next, cpu);/*若当前特权级别是0且prev->iopl != next->iopl,则恢复IOPL设置set_iopl_mask*/if (get_kernel_rpl() && unlikely(prev->iopl != next->iopl))set_iopl_mask(next->iopl);/*根据thread_info的TIF标志_TIF_WORK_CTXSW_PREV和 _TIF_WORK_CTXSW_NEXT 判断是否需要处理debug寄存器和IO位图*/if (unlikely(task_thread_info(prev_p)->flags & _TIF_WORK_CTXSW_PREV ||task_thread_info(next_p)->flags & _TIF_WORK_CTXSW_NEXT))__switch_to_xtra(prev_p, next_p, tss);//设置cpu的lazy模式arch_leave_lazy_cpu_mode();//若fpu_counter > 5 则恢复next_p 的FPU寄存器if (next_p->fpu_counter > 5)math_state_restore();//若需要,恢复gs寄存器if (prev->gs | next->gs)loadsegment(gs, next->gs);x86_write_percpu(current_task, next_p);return prev_p;}关于__switch_to 的工作就是处理 TSS (任务状态段) 。
TSS 全称task state segment,是指在操作系统进程管理的过程中,任务(进程)切换时的任务现场信息 。
linux 为每一个 CPU 提供一个 TSS 段,并且在 TR 寄存器中保存该段 。
linux 中之所以为每一个 CPU 提供一个 TSS 段,而不是为每个进程提供一个TSS 段,主要原因是 TR 寄存器永远指向它,在任务切换的适合不必切换 TR 寄存器,从而减小开销 。
在从用户态切换到内核态时,可以通过获取 TSS 段中的 esp0 来获取当前进程的内核栈 栈顶指针,从而可以保存用户态的 cs,esp,eip 等上下文 。
TSS 在任务切换过程中起着重要作用,通过它实现任务的挂起和恢复 。所谓任务切换是指,挂起当前正在执行的任务,恢复或启动另一任务的执行 。
在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到 TR(任务寄存器)所指定的任务的 TSS 中;然后,下一任务的 TSS 被装入 TR;最后,从 TR 所指定的 TSS 中取出各寄存器的值送到处理器的各寄存器中 。由此可见,通过在 TSS 中保存任务现场各寄存器状态的完整映象,实现任务的切换 。
因此,__switch_to 核心内容就是将 TSS 中的内核空间(0级)堆栈指针换成 next->esp0 。这是因为 CPU 在穿越中断门或者陷阱门时要根据新的运行级别从TSS中取得进程在系统空间的堆栈指针 。
thread_struct.esp0 指向进程的系统空间堆栈的顶端 。当一个进程被调度运行时,内核会将这个变量写入 TSS 的 esp0 字段,表示这个进程进入0级运行时其堆栈的位置 。换句话说,进程的 thread_struct 结构中的 esp0 保存着其系统空间堆栈指针 。当进程穿过中断门、陷阱门或者调用门进入系统空间时,处理器会从这里恢复期系统空间栈 。

Linux 进程管理之进程调度与切换

文章插图
由于栈中变量的访问依赖的是段、页、和 esp、ebp 等这些寄存器,所以当段、页、寄存器切换完以后,栈中的变量就可以被访问了 。
因此 switch_to 完成了进程堆栈的切换,由于被切进的进程各个寄存器的信息已完成切换,因此 next 进程得以执行指令运行 。
由于 A 进程在调用 switch_to 完成了与 B 进程堆栈的切换,也即是寄存器中的值都是 B 的,所以 A 进程在 switch_to 执行完后,A停止运行,B开始运行,当过一段时间又把 A 进程切进去后,A 开始从 switch_to 后面的代码开始执行 。
schedule 的调用流程如下:
Linux 进程管理之进程调度与切换


推荐阅读