2013-02-22 59 views
7

linux/arch/x86/include/asm/switch_to.h में, वहाँ मैक्रो switch_to की परिभाषा, कुंजी लाइनों जो वास्तविक धागा स्विच चमत्कार इस तरह पढ़ कर (लिनक्स 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 समारोह के अंत में, जो वास्तव में एक "नकली" कॉल अनुदेश एक असली ret के साथ मिलान किया है पर एक ret अनुदेश के साथ निर्देश, और प्रभावी ढंग से अगले धागे के वापसी बिंदु next->eip बनाते हैं।

क्या मुझे समझ नहीं आता है, क्यों हैक? क्यों सिर्फ call __switch_to नहीं है, तो यह बाद ret, jmpnext->eip को, जो और अधिक स्वच्छ और पाठक के अनुकूल है।

उत्तर

5

वहाँ यह इस तरह से करने के लिए दो कारणों से है।

एक संकार्य की पूरी लचीलेपन की अनुमति के लिए/[next_ip] के लिए रजिस्टर आवंटन है। आप ऐसा करने में सक्षम होना चाहते हैं jmp %[next_ip] के बाद call __switch_to तो यह %[next_ip] एक nonvolatile रजिस्टर करने के लिए (यानी एक जब एक समारोह कॉल करने कि, एबीआई परिभाषाओं से, अपने मूल्य रख सकेंगे आवंटित राशि के लिए आवश्यक है)।

यह संकलक के अनुकूलन की क्षमता में एक प्रतिबंध प्रस्तुत करता है, और परिणामस्वरूप कोड context_switch() ('कॉलर' - जहां switch_to() का उपयोग किया जाता है) हो सकता है जितना अच्छा हो सकता है। लेकिन किस लाभ के लिए?

खैर - कि जहां दूसरा कारण में आता है, कोई नहीं, है, वास्तव में, क्योंकि call __switch_to बराबर होगा:

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

यानी यह वापसी पता धक्का; आप एक अनुक्रम push/jmp (== call)/ret/jmp के साथ समाप्त हो जाएंगे, जबकि यदि आप इस स्थान पर वापस नहीं लौटना चाहते हैं (और यह कोड नहीं है), तो आप कॉल को "कॉलिंग" करके कोड शाखाओं पर सहेजते हैं क्योंकि आप आपको केवल push/jmp/ret करना होगा। कोड स्वयं पूंछ रिकर्सिव बनाता है।

हां, यह एक छोटा सा अनुकूलन है, लेकिन शाखा से बचने से विलंबता कम हो जाती है और विलंबता संदर्भ स्विच के लिए महत्वपूर्ण होती है।

+2

लेकिन यह प्रभावी ढंग से वापसी-भविष्यवाणी ढेर मार नहीं होगा? – harold

+0

हां - लेकिन लक्ष्य पंजीकृत करने के लिए 'jmp' भी करता है, जैसा कि आप _never_' switch_to() 'पर वापस लौटना चाहते हैं, वास्तव में (अगले संदर्भ स्विच तक)। जहां तक ​​दोनों के बीच कोई अंतर नहीं है। –

+0

@harold: अच्छा बिंदु; रिटर्न-एड्रेस पूर्वानुमानकर्ता स्टैक की वर्तमान सामग्री 'context_switch()' ([कर्नेल/शेड/कोर.c' में] से वापसी के संदर्भ संदर्भ के बाद मूल्यवान है (http://elixir.free-electrons.com /linux/v4.6/source/kernel/sched/core.c#L2752)), और हो सकता है कि कुछ स्तर शेड्यूलर में कॉल स्टैक का बैक अप लें (जब तक उनके बैकट्रैस अलग नहीं हो जाते)।लेकिन यह केवल तभी सच है जब '% [next_ip]' हमेशा/आमतौर पर switch_to के अंदर होता है; यह उस तरह से 'prev_ip' सेट करता है, लेकिन हो सकता है कि यह सबसे आम मूल्य नहीं है (कर्नेल प्री-एम्प्शन शायद इसे कहीं और छोड़ सकता है?) –