switch_to

switch_to()负责从上一个进程的处理器状态切换到新进程的处理器状态

15   #define  switch_to(prev,next,last) do {                                  
16          unsigned  long  esi,edi;                                          
17          asm  volatile ( " pushfl "                                        
18                        " pushl %%ebp "                                   
19                        " movl %%esp,%0 "          /* save ESP */           
20                        " movl %5,%%esp "          /* restore ESP */        
21                        " movl $1f,%1 "            /* save EIP */           
22                        " pushl %6 "               /* restore EIP */        
23                        " jmp __switch_to "                                 
24                        " 1: "                                              
25                          " popl %%ebp "                                    
26                        " popfl "                                             
27                       : "=m" (prev->thread.esp),"=m" (prev-> thread.eip),  
28                         "=a" (last),"=S" (esi),"=D"  (edi)                 
29                       : "m" (next->thread.esp),"m" (next-> thread.eip),    
30                         "2" (prev), "d"  (next));                          
31  }  while  ( 0 )

  • 输出部分有5个参数,表明这段程序执行后有5项数据会有改变。其中%0和%1都在内存中,分别为prev->thread.esp和prev->thread.eip,而%2则与寄存器EAX结合,对应于参数中的last. [输出的参数为5个,分别为prev->thraad.esp(%0), prev->thread.eip(%1), last(%2), esi(%3), edi(%4) ]
  • 输入部分有4个参数。其中%5和%6在内存中,分别为next->thread.esp和next->thread.eip;%7与寄存器EAX结合,对应于prev;%8与寄存器EDX结合,对应于next [输入的参数为4个,分别为next->thread.esp(%5), next->thread.eip(%6), prev(%2), next(%7) ]
  • "2" (prev), "d" (next),在寄存器%%eax和%%edx中分别保存prev和next的值
  • 第一步,pushfl, pushl %%ebp, 将进程prev的eflags和ebp保存到该进程的内核堆栈中
  • 第二步,movl %%esp, %0(prev->thread.esp),将当前进程prev的系统空间堆栈指针存入该进程的进程描述符结构体中(prev->thread.esp).
  • 第三步,movl %5(next->thread.esp), %%esp 将下一个待执行进程next的系统空间堆栈指针切换进来.由于硬件相关的进程上下文信息都跟内核堆栈相关,所以第二步和第三步可以说完成了进程切换的大部分工作.(从21行开始就在使用进程next的堆栈了。换言之,从21开始,“当前进程”已经是next而不是prev了)
  • 第四步,movl $1f, %1(prev->thread.eip), 将25行的地址保存在prev->thread.eip中
  • 第五步,pushl %6(next->thread.eip), jmp __switch_to 这是整个宏的玄机所在,首先将next->thread.eip压栈, 然后跳转到__switch_to()函数中去执行,这里要注意的是,C函数执行完成后,其返回地址在栈顶位置,又由于现在栈顶的值是next->thread.eip, 这样,next->thread.eip(即上一次它被调离时25行的地址)即为这个函数的返回地址,这样,函数返回后,将跳转到25行这个地址去执行.
  • 第六步,popl %%ebp, popfl 恢复新进程的相关寄存器的值.由于在第三步中,进程栈切换已经完成,所以,这里是对新的进程栈的恢复操作.

注:
  • current与ESP的关系
在内核代码中当需要访问当前进程的task_struct结构时使用的指针current实际上是个宏定义,它是根据当前进程的堆栈指针ESP计算出来的
  • eip是下条要执行的指令地址
  • ebp指向内核堆栈基地址
  • esp指向内核堆栈栈顶


第一步
pushfl
pushl %%ebp
                   进程prev的内核堆栈

                   |           |
                   |           |
                   |-----------|
                   |  eflags   |
                   |  ebp      |
           %%esp-->|           |

第二步
movl %%esp,%0  //prev->thraad.esp(%0)
                   进程prev的内核堆栈
                   |           |
                   |           |
                   |-----------|
                   |  eflags   |
        (%%esp)    |  ebp      |
prev->thread.esp-->|           |


第三步
movl %5,%%esp   //next->thread.esp(%5)

                进程next的内核堆栈

                |           |
                |           |
                |           |
                | eflags    |
                | ebp       |
        %%esp-->|           |
 (next->thread.esp)


第四步
movl $1f,%1     //prev->thread.eip(%1)

prev->thread.eip = (第25行的地址)
作为进程prev下一次被调度运行时的“返回地址”


第五步
pushl %6        //next->thread.eip(%6)

             进程next的内核堆栈
           |                |
           |                |
           |                |
           |eflags          |
           |ebp             |
           |next->thread.eip|
   %%esp-->|                |
将next->thread.eip压入堆栈(next的内核堆栈?):这里的next->thread.eip正是
进程next上一次被调离时在第21行中保存的。它也执行这里的标号"1", 即25行的popl指令






你可能感兴趣的:(Process,Scheduling)