switch_to()的代码在arch/x86/include/asm/system.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 */ \
-
"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) \
-
\
-
/* 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) \
-
\
-
: /* reloaded segment registers */ \
-
"memory"); \
-
} while (0)
根据ABI约定和内联汇编,ebx, ecx, edx, esi, edi这几个寄存器是由编译器自动保存和恢复的。这一点可能不太好理解,举个例子,看下面的代码中的ecx:
-
#include <stdio.h>
-
-
void modify_ecx(void) {
-
unsigned long ecx;
-
-
__asm__ (
-
"movl $1, %%ecx\n\t"
-
:"=c"(ecx)
-
:
-
);
-
}
-
-
void test(void) {
-
unsigned long ecx;
-
-
__asm__ volatile(
-
"nop\n\t"
-
"call modify_ecx\n\t"
-
: "=c" (ecx)
-
:
-
: "memory"
-
);
-
}
-
-
int main(void) {
-
test();
-
return 0;
-
}
这里的test()就相当于内核源代码中“调用”switch_to()的context_switch(),我们来查看其对应的汇编代码(注意要加-O0):
main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ecx subl $8, %esp call test movl $0, %eax addl $8, %esp popl %ecx leal -4(%ecx), %esp ret
可见,在调用test()之前,编译器已经自动完成了保存ecx的操作,而调用之后它又会恢复ecx的值。
而ebp,eflags是手工压入堆栈,并在switch回来后恢复出来的。esp和eip保存在相应的task_struct结构体里。
需要额外说明的是那个jmp,因为这里的参数传递是通过寄存器完成的,具体说是用了eax和edx这个两个寄存器,所以再jmp其实就和call一样了,不过call是要把ebp入栈的,而jmp不需要,这里也不需要。
另外一个可能的问题是:为什么switch_to()有三个参数?我们切换的进程明明是两个啊!这里问题的所在是进程切换时堆栈的切换,如果不使用三个参数,切换的堆栈中仍然保存的是切换前的参数,再切换回来时prev很可能不对了,所以需要一个参数来修正,这个参数又正好是__switch_to的返回值。这样问题就解决了。