8

にはlinux/arch/x86/include/asm/switch_to.h、マクロの定義がありswitch_to、実際のスレッド切り替えの奇跡を行う主要な行は次のようになっています (Linux 4.7 で変更されるまで):

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  */ \
              "1:\t"                        \
              "popl %%ebp\n\t"      /* restore EBP   */ \
              "popfl\n"         /* restore flags */ \

名前付きオペランドには、 のようなメモリ制約があります[prev_sp] "=m" (prev->thread.sp)__switch_canaryが定義されていない限り、何も定義されていませんCONFIG_CC_STACKPROTECTOR(その後、 を使用してロードおよびストアされます%ebx)。

カーネルスタックポインターのバックアップ/復元のように、それがどのように機能するか、push next->eipおよび実際の命令と一致する「偽の」呼び出し命令である関数の最後の命令でjmp __switch_toどのように機能するかを理解し、効果的に戻ります次スレのポイント。retretnext->eip

私が理解できないのは、なぜハッキングなのかということです。だけではなくcall __switch_to、その後に、retよりクリーンで読みやすいです。jmpnext->eip

4

1 に答える 1

6

このようにする理由は2つあります。

1つは、のオペランド/レジスタ割り当ての完全な柔軟性を可能にすることです[next_ip]jmp %[next_ip] その後を実行できるようにする場合は、不揮発性レジスタ(つまり、ABI定義により、関数呼び出しを行うときにその値を保持するレジスタ)に割り当てるcall __switch_to必要があります。%[next_ip]

これにより、コンパイラーの最適化機能に制限が生じ、結果として得られるcontext_switch()('呼び出し元'-switch_to()使用される場所)のコードは、可能な限り適切ではない可能性があります。しかし、どのような利益のために?

ええと、それが2番目の理由が出てくるところですが、実際には、次のcall __switch_toようになります。

pushl 1f
jmp __switch_to
1: jmp %[next_ip]

つまり、差出人住所をプッシュします。最終的にシーケンスpush/ jmp== call)/ ret/jmpになりますが、この場所に戻りたくない場合(このコードは戻りません)、呼び出しを「偽造」することでコードブランチを節約できます。するpush// 。jmp_ retここでは、コード自体が末尾再帰になります。

はい、これは小さな最適化ですが、ブランチを回避するとレイテンシーが減少し、レイテンシーはコンテキストスイッチにとって重要です。

于 2013-02-22T12:32:00.400 に答える