Linux 64 位上下文切换
Linux 64 bit context switch
在32位模式下的switch_to宏中,在调用__switch_to函数之前执行了以下代码:
asm volatile("pushfl\n\t" /* save flags */ \
"pushl %%ebp\n\t" /* save EBP */ \
"movl %%esp,%[prev_sp]\n\t" /* save ESP */ \
"movl %[next_sp],%%esp\n\t" /* restore ESP */ \
"movl f,%[prev_ip]\n\t" /* save EIP */ \
"pushl %[next_ip]\n\t" /* restore EIP */ \
__switch_canary \
"jmp __switch_to\n" /* regparm call */
EIP入栈(恢复EIP)。当 __switch_to 完成时,有一个 ret which returns 到那个位置。
这是相应的 64 位代码:
asm volatile(SAVE_CONTEXT \
"movq %%rsp,%P[threadrsp](%[prev])\n\t" /* save RSP */ \
"movq %P[threadrsp](%[next]),%%rsp\n\t" /* restore RSP */ \
"call __switch_to\n\t"
那里,只有rsp被保存和恢复。我认为 RIP 已经在
堆栈的顶部。但是我找不到完成该操作的说明。
64 位上下文切换,尤其是 RIP 寄存器,实际上是如何完成的?
提前致谢!
在 32 位内核中,thread.ip
可能是以下之一:
switch_to
中的 1
标签
ret_from_fork
ret_from_kernel_thread
通过使用 push
+ jmp
对模拟 call
来确保 return 到正确的位置。
在64位内核中,thread.ip
不是这样使用的。执行总是在 call
之后继续(在 32 位情况下它曾经是 1
标签)。这样就不用模拟call
了,可以正常完成。在 __switch_to
return 秒后使用条件跳转发送到 ret_from_fork
(您省略了这部分):
#define switch_to(prev, next, last) \
asm volatile(SAVE_CONTEXT \
"movq %%rsp,%P[threadrsp](%[prev])\n\t" /* save RSP */ \
"movq %P[threadrsp](%[next]),%%rsp\n\t" /* restore RSP */ \
"call __switch_to\n\t" \
"movq "__percpu_arg([current_task])",%%rsi\n\t" \
__switch_canary \
"movq %P[thread_info](%%rsi),%%r8\n\t" \
"movq %%rax,%%rdi\n\t" \
"testl %[_tif_fork],%P[ti_flags](%%r8)\n\t" \
"jnz ret_from_fork\n\t" \
RESTORE_CONTEXT \
ret_from_kernel_thread
被合并到 ret_from_fork
路径中,使用 entry_64.S
中的另一个条件跳转:
ENTRY(ret_from_fork)
DEFAULT_FRAME
LOCK ; btr $TIF_FORK,TI_flags(%r8)
pushq_cfi [=11=]x0002
popfq_cfi # reset kernel eflags
call schedule_tail # rdi: 'prev' task parameter
GET_THREAD_INFO(%rcx)
RESTORE_REST
testl , CS-ARGOFFSET(%rsp) # from kernel_thread?
jz 1f
在32位模式下的switch_to宏中,在调用__switch_to函数之前执行了以下代码:
asm volatile("pushfl\n\t" /* save flags */ \
"pushl %%ebp\n\t" /* save EBP */ \
"movl %%esp,%[prev_sp]\n\t" /* save ESP */ \
"movl %[next_sp],%%esp\n\t" /* restore ESP */ \
"movl f,%[prev_ip]\n\t" /* save EIP */ \
"pushl %[next_ip]\n\t" /* restore EIP */ \
__switch_canary \
"jmp __switch_to\n" /* regparm call */
EIP入栈(恢复EIP)。当 __switch_to 完成时,有一个 ret which returns 到那个位置。 这是相应的 64 位代码:
asm volatile(SAVE_CONTEXT \
"movq %%rsp,%P[threadrsp](%[prev])\n\t" /* save RSP */ \
"movq %P[threadrsp](%[next]),%%rsp\n\t" /* restore RSP */ \
"call __switch_to\n\t"
那里,只有rsp被保存和恢复。我认为 RIP 已经在 堆栈的顶部。但是我找不到完成该操作的说明。 64 位上下文切换,尤其是 RIP 寄存器,实际上是如何完成的?
提前致谢!
在 32 位内核中,thread.ip
可能是以下之一:
switch_to
中的 ret_from_fork
ret_from_kernel_thread
1
标签
通过使用 push
+ jmp
对模拟 call
来确保 return 到正确的位置。
在64位内核中,thread.ip
不是这样使用的。执行总是在 call
之后继续(在 32 位情况下它曾经是 1
标签)。这样就不用模拟call
了,可以正常完成。在 __switch_to
return 秒后使用条件跳转发送到 ret_from_fork
(您省略了这部分):
#define switch_to(prev, next, last) \
asm volatile(SAVE_CONTEXT \
"movq %%rsp,%P[threadrsp](%[prev])\n\t" /* save RSP */ \
"movq %P[threadrsp](%[next]),%%rsp\n\t" /* restore RSP */ \
"call __switch_to\n\t" \
"movq "__percpu_arg([current_task])",%%rsi\n\t" \
__switch_canary \
"movq %P[thread_info](%%rsi),%%r8\n\t" \
"movq %%rax,%%rdi\n\t" \
"testl %[_tif_fork],%P[ti_flags](%%r8)\n\t" \
"jnz ret_from_fork\n\t" \
RESTORE_CONTEXT \
ret_from_kernel_thread
被合并到 ret_from_fork
路径中,使用 entry_64.S
中的另一个条件跳转:
ENTRY(ret_from_fork)
DEFAULT_FRAME
LOCK ; btr $TIF_FORK,TI_flags(%r8)
pushq_cfi [=11=]x0002
popfq_cfi # reset kernel eflags
call schedule_tail # rdi: 'prev' task parameter
GET_THREAD_INFO(%rcx)
RESTORE_REST
testl , CS-ARGOFFSET(%rsp) # from kernel_thread?
jz 1f