软讯网络 > 操作系统 > Linux > switch_to
【标 题】:switch_to
【关键字】:
switch_to
【来 源】:http://blog.csdn.net/kernel_details/archive/2007/01/10/1478954.aspx
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 恢复新进程的相关寄存器的值.由于在第三步中,进程栈切换已经完成,所以,这里是对新的进程栈的恢复操作.
注: 在内核代码中当需要访问当前进程的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指令