我正在学习Linux内核源码(旧版本0.11v)。当我查看关于fork系统调用的信息时,发现有一些用于上下文切换的汇编代码如下:
/*
* switch_to(n) should switch tasks to task nr n, first
* checking that n isn't the current task, in which case it does nothing.
* This also clears the TS-flag if the task we switched to has used
* tha math co-processor latest.
*/
#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,current\n\t" \
"je 1f\n\t" \
"movw %%dx,%1\n\t" \
"xchgl %%ecx,current\n\t" \
"ljmp *%0\n\t" \
"cmpl %%ecx,last_task_used_math\n\t" \
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d" (_TSS(n)),"c" ((long) task[n])); \
}
我认为"ljmp %0\n\t"
可以用于更改TSS和LDT。我知道ljmp
指令需要两个参数,例如ljmp $section, $offset
。我认为ljmp
指令必须使用_TSS(n), xx
。我们不需要提供有意义的偏移值,因为CPU会改变寄存器,包括eip,以进行新任务。
我不知道
ljmp %0
如何像ljmp $section, $offset
那样工作,也不知道为什么这条指令使用%0
。%0
只是__tmp.a
的地址吗?CPU在执行
ljmp
指令时可能会将EIP寄存器保存到旧任务的TSS中。 我是否正确地认为旧任务的EIP值是"cmpl %%ecx,_last_task_used_math\n\t"
的地址?
ljmp %0
将跳转到存储在内存地址%0中的48位地址。因此,它将有效地跳转到存储在内存地址__tmp
中的地址。您将观察到movw %%dx,%1
有效地使用值_TSS(n)
初始化了__tmp.b
。*_TSS(n)将是任务门的段描述符。您会注意到%0
(__tmp.a)未初始化。由于通过任务门时忽略了偏移量(__tmp.a*表示),因此不需要初始化它。实际上,您将执行一个ljmp到segment:offset _TSS(n):garbage。 - Michael Petchcmpl %%ecx,_last_task_used_math
将在新任务的上下文中执行。我没有查看旧内核源代码,但似乎*_last_task_used_math*是最后一个使用数学指令的任务的任务ID。如果它与当前任务ID不同,则避免使用clts
指令。 - Michael Petch