-
[schedule()>switch_to()]
-
15 #define switch_to(prev,next,last) do { \
-
16 asm volatile("pushl %%esi\n\t" \
-
17 "pushl %%edi\n\t" \
-
18 "pushl %%ebp\n\t" \
-
19 "movl %%esp,%0\n\t"/* save ESP */ \
-
20 "movl %3,%%esp\n\t"/* restore ESP */ \
-
21 "movl $1f,%1\n\t" /* save EIP */ \
-
22 "pushl %4\n\t" /* restore EIP */ \
-
23 "jmp __switch_to\n" \
-
24 "1:\t" \
-
25 "popl %%ebp\n\t" \
-
26 "popl %%edi\n\t" \
-
27 "popl %%esi\n\t" \
-
28 :"=m" (prev->thread.esp),"=m" (prev->thread.eip), \
-
29 "=b" (last) \
-
30 :"m" (next->thread.esp),"m" (next->thread.eip), \
-
31 "a" (prev), "d" (next), \
-
32 "b" (prev)); \
-
33 } while (0)
《情景分析》里在这段代码上费了不小的篇幅,已经算得上很合格的解读了,但没有书中惯有的锋利。所以补上一些“锋利”的看法:
1, 第22行和23行
push %4
jmp __switch_to
这两句的可读性并不好,改写成:
call __switch_to
jmp dword [next->thread.eip] //我没用at&t语法
效果是一样的。内核中像这样的push b, jmp a还不止一处,大概是为了效率。而且,对汇编熟的人,可能反而喜爱这种写法,这样写,就像是一个“双连跳”。就仿佛是jmp的目标不是函数,而是一段普通的代码,在其末尾,还有一个臆想的"jmp b"。push b是伏笔。
这种模型抽象还是很清晰的,估计自己以后也会试着用。
2,还是这两行
知道这两行的功能,可能立即又陷入一个误区:jmp __switch_to,jmp next->thraed.eip,看样子,是跳向了新的代码了。嗯,也是也是,肯定是执行next进程的指令去了。
其实不是的。这里的jmp next->thread.eip,几乎总是跳向这段代码里的"1f"标签。没有跳走。
像switch_to这种,它的代码是公用的,不属于某个进程。
那为什么要跳呢?直接写成call __switch_to,等__switch_to返回,自然而然的走到"1f"标签多好。
前面说的是“通常”,如果next是一个新进程,它没有被调度过,即使我们预先把它的thread.eip设置成,"$1f",他的内核中没有上下文,1f标签处的那些pop..pop..对它是无从谈起的。
对一个新进程,还是让它干干净净的restore_all最好。
所以,jmp next->thread.eip,也就是那个push next->thread.eip,是为了代码的普适性。如果只考虑一般情况,那这两句精简成一句“call __switch_to”是没问题的。
3,为什么只保存esi,edi,ebp这三个寄存器呢?
阅读(1531) | 评论(0) | 转发(0) |