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