内核版本3.19, 架构 x86_64
switch_to() 是在 arch/x86/include/asm/switch_to.h中, 内容如下:
-
#define switch_to(prev, next, last) \
-
do { \
-
/* \
-
* Context-switching clobbers all registers, so we clobber \
-
* them explicitly, via unused output variables. \
-
* (EAX and EBP is not listed because EBP is saved/restored \
-
* explicitly for wchan access and EAX is the return value of \
-
* __switch_to()) \
-
*/ \
-
unsigned long ebx, ecx, edx, esi, edi; \
-
\
-
asm volatile("pushfl\n\t" /* save flags */ \
-
"pushl %%ebp\n\t" /* save EBP */ \
-
"movl %%esp,%[prev_sp]\n\t" /* save ESP */ \
-
"movl %[next_sp],%%esp\n\t" /* restore ESP */ \
-
"movl $1f,%[prev_ip]\n\t" /* save EIP */ \
-
"pushl %[next_ip]\n\t" /* restore EIP */ \
-
__switch_canary \
-
"jmp __switch_to\n" /* regparm call */ \
-
"1:\t" \
-
"popl %%ebp\n\t" /* restore EBP */ \
-
"popfl\n" /* restore flags */ \
-
\
-
/* output parameters */ \
-
: [prev_sp] "=m" (prev->thread.sp), \
-
[prev_ip] "=m" (prev->thread.ip), \
-
"=a" (last), \
-
\
-
/* clobbered output registers: */ \
-
"=b" (ebx), "=c" (ecx), "=d" (edx), \
-
"=S" (esi), "=D" (edi) \
-
\
-
__switch_canary_oparam \
-
\
-
/* input parameters: */ \
-
: [next_sp] "m" (next->thread.sp), \
-
[next_ip] "m" (next->thread.ip), \
-
\
-
/* regparm parameters for __switch_to(): */ \
-
[prev] "a" (prev), \
-
[next] "d" (next) \
-
\
-
__switch_canary_iparam \
-
\
-
: /* reloaded segment registers */ \
-
"memory"); \
-
} while (0)
进程的切换, 最明显的就是栈的切换和内核控制路径的切换, 具体就是对%ebp, %esp 和%eip 的切换. 在 switch_to 中, 显式完成的只有%esp 的切换(movl %[next_sp], %%esp). %ebp 通过切换%esp 之后使用 popl %%ebp 完成, %eip的切换则在 __switch_to()返回执行 return 时隐式完成.
switch_to 中做的事情依次是:
1. 将 flags 压栈
2. 将 prev 的%ebp 压栈
3. 将 prev 的%esp 保存到 prev->thread.sp
4. 置%esp 的值为 next->thread.sp, 这样内核栈就切到了 next 的内核栈, 以后的操作都是在 next 的内核栈上做的.
5. 将语句 1: 处的地址保存到 prev->thread.ip
6. 将 next->thread.ip 压栈(注意这里已经是在 next 的栈上操作)
此时 next 的栈上是这样的:
…
___
%ebp(上次从 next 切换出去时保存的)
___
%eip(刚刚压入) <-%esp 指向这里
而 prev 的栈是这样的:
…
___
flags
__
%ebp (保存在 prev->thread.sp 中的地址指向这里)
7. 调用 __switch_canary 对 next 的栈做检查
8. 跳转到 __switch_to() 执行. 这里的 trick 是使用 jmp 而不是 call, 避免机器把当前%eip 压栈
9. 在 __switch_to() 执行到 ret 时, 从把栈顶值弹出到%eip 中, 这就完成了内核控制路径的切换.
10. 栈 pop, 弹出值作为%ebp. 这里栈中的值不是这次 switch_to 中保存的, 而是上一次 B 作为 switch_to 的 prev 时保存的.
11. 从栈中恢复 flags.
还有一些细节:
1. 汇编中 output parameters 中有 "=a" (last). 也就是把%eax 的值放入 last 中. __switch_to() 的最后一条语句是 return prev_p; 是将 pre 指针放到%eax 中. 这样就将 next 的 last 指针设为了 prev.
2. 为什么不在 __switch_to() 之前就把%ebp 弹出来? 因为%ebp 没有在 thread_struct 结构中保存, 它只能保存在栈上. %ebp 被保存在栈上的原因是使用 get_wchan() 将会对栈上的%ebp 进行追踪, 找到这个睡眠进程的内核控制流在哪个函数内.(我感觉还是没回答这个问题, 哪天改了试试)
3. __switch_to()中大概做的是切换段寄存器, gdt 之类的事情.
4. 若 next 进程之前没进过 switch_to, 栈上%eip 的位置会是 ret_from_fork().
阅读(690) | 评论(0) | 转发(0) |