我正在研究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"的地址是否正确?
  • 最佳答案

    这个语法甚至是什么意思?

    这个难以理解的困惑是GCC的Extended ASM,其一般格式为

     asm [volatile] ( AssemblerTemplate
                    : OutputOperands
                  [ : InputOperands
                  [ : Clobbers ] ] )
    

    在这种情况下,__asm__语句仅包含AssemblerTemplateInputOperands。输入操作数部分解释%0%1的含义,以及ecxedx如何获得其值:
  • 第一个输入操作数是"m" (*&__tmp.a),因此%0成为__tmp.a m emory地址(老实说,我不确定为什么这里需要*&)。
  • 第二个输入操作数是"m" (*&__tmp.b),因此%1成为__tmp.b Emory地址。
  • 第三个输入操作数是"d" (_TSS(n)),因此当此代码启动时, D X寄存器将包含_TSS(n)
  • 第四个输入操作数是"c" ((long) task[n]),因此当此代码启动时,E C X寄存器将包含task[n]

  • 清理后,代码可以解释如下
        cmpl %ecx, _current
        je 1f
    
        movw %dx, __tmp.b          ;; the address of __tmp.b
        xchgl %ecx, _current
        ljmp __tmp.a               ;; the address of __tmp.a
    
        cmpl %ecx, _last_task_used_math
        jne 1f
        clts
    1:
    
    ljmp %0甚至如何工作?

    请注意,ljmp(也称为jmpf)指令有两种形式。您知道的一个(opcode EA)需要两个立即参数:一个用于段,一个用于偏移量。这里使用的一个(操作码FF /5)是不同的:segment和address参数不在代码流中,而是在内存中的某个地方,并且指令指向该地址。

    在这种情况下,ljmp的参数指向__tmp结构的开头。前四个字节(__tmp.a)包含偏移量,其后的两个字节(__tmp.b的下半部分)包含段。

    间接ljmp __tmp.a等效于ljmp [__tmp.b]:[__tmp.a],除了ljmp segment:offset只能接受立即参数。如果要切换到任意的TSS而无需自行修改代码(这是一个糟糕的主意),则可以使用间接指令。

    另请注意,__tmp.a从未初始化。我们可以假设_TSS(n)指的是任务门(因为这是您使用TSS进行上下文切换的方式),并且“通过”任务门的跳转偏移量将被忽略。

    旧指令指针在哪里?

    这段代码没有在TSS中存储旧的EIP。

    (我在此之后进行猜测,但是我认为这种猜测是合理的。)

    旧的EIP存储在与旧任务相对应的内核空间堆栈中。

    Linux 0.11为每个任务分配一个ring 0堆栈(即内核的堆栈)(请参阅copy_process中的fork.c函数,该函数初始化TSS)。当任务A期间发生中断时,旧的EIP将保存在内核空间堆栈中,而不是用户空间堆栈中。如果内核决定切换到任务B,则也将切换内核空间堆栈。当内核最终切换回任务A时,该堆栈又切换回去,并且通过iret我们可以返回到任务A所在的位置。

    关于assembly - ljmp指令在linux内核fork系统调用中起什么作用?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/33783692/

    10-08 23:18