【发布时间】:2016-02-20 09:40:37
【问题描述】:
我正在研究linux内核源码(旧版本0.11v)。 当我检查 fork 系统调用时,有一些用于上下文切换的 asm 代码,如下所示:
/*
* 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 会更改 cpu 的寄存器,包括新任务的 eip。
我不知道
ljmp %0是如何像ljmp $section, $offset一样工作的,以及为什么这条指令使用%0。%0只是__tmp.a的地址吗?在执行
ljmp指令时,CPU 可能会将 EIP 寄存器保存到旧任务的 TSS 中。旧任务的 EIP 值是"cmpl %%ecx,_last_task_used_math\n\t"地址,我说的对吗?
【问题讨论】:
-
这很难读,Linus 的一些 cmets 可能还不错。
ljmp %0将跳转到内存地址 %0 中包含的 48 位地址。如此有效地将ljmp指向内存地址__tmp中包含的地址。您将观察到movw %%dx,%1有效地将__tmp.b初始化为_TSS(n)。 _TSS(n) 将是任务门的段描述符。您会注意到%0(__tmp.a) 未初始化。不需要,因为当您通过任务门 ljmp 时,偏移量(__tmp.a 表示)会被忽略。实际上,您执行 ljmp 以分段:偏移 _TSS(n):garbage。 -
ljmp _TSS(n):garbage 当_TSS(n)代表一个任务门选择器时会根据任务选择器切换任务,忽略偏移量(所以不需要设置任何东西) ,并在新任务上下文中继续执行 ljmp 之后的指令。
-
cmpl %%ecx,_last_task_used_math将在 ljmp 之后的新任务的上下文中执行。我没有看过旧的内核源代码,但似乎 _last_task_used_math 是最后一个使用数学指令的任务的任务 ID。如果它与当前 taskid 不同,则避免使用clts指令。 -
另请注意,TSS 任务切换在 linux 中已被放弃,因此仅具有历史意义。
-
是的@Jester,我只是在评论那个旧内核,因为这似乎是 OP 感兴趣的内容。Linux 自 0.11 以来已经走过了漫长的道路;)
标签: assembly linux-kernel x86 gnu-assembler att