switch_to中的汇编
switch_to()的代码在arch/x86/include/asm/system.h中,如下:
[c]
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("pushflnt" /* save flags */
"pushl %%ebpnt" /* save EBP */
"movl %%esp,%[prev_sp]nt" /* save ESP */
"movl %[next_sp],%%espnt" /* restore ESP */
"movl $1f,%[prev_ip]nt" /* save EIP */
"pushl %[next_ip]nt" /* restore EIP */
"jmp __switch_ton" /* regparm call */
"1:t"
"popl %%ebpnt" /* restore EBP */
"popfln" /* 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)
[/c]
根据ABI约定和内联汇编,ebx, ecx, edx, esi, edi这几个寄存器是由编译器自动保存和恢复的。这一点可能不太好理解,举个例子,看下面的代码中的ecx:
[c]
include
void modify_ecx(void) {
unsigned long ecx;
asm (
“movl $1, %%ecxnt”
:”=c”(ecx)
:
);
}
void test(void) {
unsigned long ecx;
asm volatile(
“nopnt”
“call modify_ecxnt”
: “=c” (ecx)
:
: “memory”
);
printf(“ecx=%ldn”, ecx);
}
int main(void) {
test();
return 0;
}
[/c]
这里的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的返回值。这样问题就解决了。