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的返回值。这样问题就解决了。