Linux 64位上下文切换

时间:2015-03-16 09:04:28

标签: linux assembly linux-kernel x86 x86-64

在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 $1f,%[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。 这是相应的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寄存器?

提前致谢!

1 个答案:

答案 0 :(得分:3)

在32位内核中,thread.ip可能是以下之一:

  • 1
  • 中的switch_to标签
  • ret_from_fork
  • ret_from_kernel_thread

通过使用call + push对模拟jmp来确保返回正确的位置。

在64位内核中,thread.ip不会像这样使用。执行始终在call之后继续(在32位情况下曾经是1标签)。因此,不需要模拟call,它可以正常完成。在ret_from_fork返回后使用条件跳转调度到__switch_to(您已省略此部分):

#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 $0x0002
        popfq_cfi                               # reset kernel eflags

        call schedule_tail                      # rdi: 'prev' task parameter

        GET_THREAD_INFO(%rcx)

        RESTORE_REST

        testl $3, CS-ARGOFFSET(%rsp)            # from kernel_thread?
        jz   1f