ट्रैक
/
x86-64 Assembly
x86-64 Assembly
/
अभ्यास
/
उद्घाटन रात
उद्घाटन रात

उद्घाटन रात

सीखने का अभ्यास

परिचय

अटॉमिक

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

add qword [rel counter], 5

अंदर से देखें तो प्रोसेसर के पास मेमोरी में सीधे जोड़ने का कोई तरीका नहीं होता। वह इस एक निर्देश को तीन छोटे चरणों में तोड़ देता है, जिन्हें माइक्रो-ऑपरेशन कहते हैं:

  1. मेमोरी से मौजूदा वैल्यू पढ़ना;
  2. रजिस्टर में उस वैल्यू को बदलना, यानी उसमें पाँच जोड़ना;
  3. नतीजे को वापस लिखना।
mov rax, qword [rel counter] ; 1. load the current value
add rax, 5                   ; 2. add five
mov qword [rel counter], rax ; 3. store the result back

यह एक आम पैटर्न है जिसे पढ़ना-बदलना-लिखना (RMW) कहते हैं।

ध्यान दीजिए कि पढ़ना (या लोड) और लिखना (या स्टोर) अलग-अलग घटनाएँ हैं, इसलिए उनके बीच समय का एक अंतराल रहता है। हमें यह आम तौर पर दिखता नहीं, क्योंकि एकल कोर पर हर निर्देश का पूरा असर अगले निर्देश से पहले सुनिश्चित हो जाता है। इसलिए वह अंतराल दिखता नहीं, और add एक ही इकाई की तरह व्यवहार करता है।

लेकिन आधुनिक CPU शायद ही कभी एकल कोर वाले होते हैं, और ऐप्लिकेशन अक्सर एक साथ कई कोरों पर चलते हैं, जिनके बीच कोई तय क्रम नहीं होता। जब कई कोर बिल्कुल एक ही पल चल रहे हों, तो कोई दूसरा कोर उस अंतराल के अंदर, यानी इस कोर के लोड के बाद और उसके स्टोर से पहले, counter को पढ़ या लिख सकता है। दो थ्रेड दोनों वही पुरानी वैल्यू लोड करते हैं, दोनों उसमें पाँच जोड़ते हैं, और दोनों अपना नतीजा स्टोर कर देते हैं। दो बार जोड़ हुआ, लेकिन वैल्यू सिर्फ पाँच ही बढ़ी। एक अपडेट चुपचाप खो गया।

यह एक डेटा रेस है, और मल्टी-थ्रेडेड कोड में यह एक आम समस्या है।

हर वैल्यू इस तरह खतरे में नहीं होती। हर थ्रेड के अपने रजिस्टर और अपना स्टैक होता है, इसलिए रजिस्टर में रखी वैल्यू या थ्रेड के स्टैक पर रखा लोकल वेरिएबल सिर्फ उसी थ्रेड का होता है और रेस में नहीं पड़ सकता। सिर्फ वही मेमोरी सुरक्षा की ज़रूरत रखती है जिसे थ्रेड आपस में साझा करते हैं, जैसे ऊपर counter।

x86-64 इस समस्या को हल करने के लिए कुछ निर्देश देता है, जो किसी निर्देश को अविभाज्य बना देते हैं। वह न सिर्फ उस कोर के लिए एक ही इकाई की तरह व्यवहार करता है जो उसे चला रहा है, बल्कि हर दूसरे कोर के लिए भी। जो काम एक साथ जुड़ा रहता है, जिसे कोई दूसरा कोर बीच से नहीं तोड़ सकता, उसे अटॉमिक कहते हैं।

Note

कई कोरों पर चलने वाले ऐप्लिकेशन को मल्टी-थ्रेडेड कहना आम है। लेकिन थ्रेड और कोर एक चीज़ नहीं हैं। दो थ्रेड एक ही कोर पर एक साथ, बारी-बारी से चल सकते हैं, या अलग-अलग कोरों पर समानांतर में।

अगर पढ़ना-बदलना-लिखना कई निर्देशों में टूटा हो, तो बारी-बारी से चलने वाले थ्रेड उसमें पहले से ही रेस कर सकते हैं, क्योंकि OS किन्हीं भी दो निर्देशों के बीच थ्रेड बदल सकता है। लेकिन एक ही निर्देश के अंदर का अंतराल सिर्फ सचमुच समानांतर चलने वाले कोड में ही सामने आता है। चूँकि OS थ्रेड सिर्फ निर्देशों के बीच बदलता है, किसी एक निर्देश के अंदर कभी नहीं, इसलिए एकल कोर पर एक अकेला निर्देश अपने आप सुरक्षित रहता है।

कई कोरों में सचमुच अटॉमिक होना ही वह है जो नीचे दिए गए निर्देश देते हैं।

अटॉमिक एक्सचेंज

xchg निर्देश दो ऑपरेंड की अदला-बदली करता है। डेस्टिनेशन ऑपरेंड, सोर्स ऑपरेंड में पहले से मौजूद वैल्यू के बराबर हो जाता है, और सोर्स ऑपरेंड, डेस्टिनेशन ऑपरेंड में पहले से मौजूद वैल्यू के बराबर हो जाता है। सोचने में इसे ऐसे समझा जा सकता है मानो दो mov निर्देश एक साथ हो रहे हों।

हमेशा की तरह, इसे दो रजिस्टर ऑपरेंड के साथ, या एक मेमोरी ऑपरेंड और एक रजिस्टर ऑपरेंड के साथ इस्तेमाल किया जा सकता है:

mov  eax, 1
xchg dword [rdi], eax ; [rdi] = 1, eax = the old value of [rdi]
mov ecx, 2
mov edx, 3
xchg edx, ecx         ; edx = 2, ecx = 3

जब इसे मेमोरी ऑपरेंड के साथ इस्तेमाल किया जाता है, तो xchg हमेशा अटॉमिक होता है।

Caution

जब ऑपरेंड में से एक मेमोरी लोकेशन हो, तो xchg अपने आप अटॉमिक होता है। इसका यह भी मतलब है कि ऐसी स्थिति में यह काम बहुत धीमा होता है।

अगर आपको अटॉमिक होने की ज़रूरत नहीं है, तो अदला-बदली किसी खाली रजिस्टर के ज़रिए सादे mov निर्देशों से कीजिए।

लॉक प्रीफिक्स

x86-64 में किसी निर्देश को अटॉमिक बनाने का सबसे आम तरीका है, उसमें lock प्रीफिक्स जोड़ना। यह पढ़ने, बदलने और लिखने को एक अविभाज्य चरण में मिला देता है। इसका मतलब है कि वह कोर पूरी प्रक्रिया के दौरान उस मेमोरी पर अपना एकाधिकार बनाए रखता है, इसलिए बीच में कोई दूसरा कोर उस लोकेशन को पढ़ या लिख नहीं सकता।

lock add qword [rel counter], 5 ; the read, the modify, and the write are one step

lock सिर्फ तब काम करता है जब डेस्टिनेशन मेमोरी हो, और सिर्फ उन्हीं निर्देशों पर जो उस मेमोरी को पढ़ना-बदलना-लिखना करते हैं:

  1. गणितीय काम, जैसे add, sub, inc, dec, neg;
  2. बिटवाइज़ काम, जैसे and, or, xor, not;
  3. बिट वाले निर्देश bts, btr, btc;
  4. कुछ और खास निर्देश, जैसे xadd और cmpxchg, जिनके बारे में नीचे बताया गया है।
Caution

किसी लोकेशन पर एकाधिकार रखना और हर दूसरे कोर को बाहर रोकना मुफ्त नहीं है। lock प्रीफिक्स वाला काम अपने सादे रूप से काफी धीमा होता है, और जब कई कोर एक ही लोकेशन के लिए होड़ करते हैं तो और भी धीमा।

यह प्रीफिक्स सिर्फ उसी मेमोरी के लिए रखिए जिसे एक से अधिक थ्रेड के बदलने की उम्मीद हो। अगर मेमोरी साझा नहीं है या उसे सिर्फ पढ़ा ही जाता है, तो इसका इस्तेमाल न कीजिए।

एक्सचेंज और जोड़ना

सादा lock add मेमोरी को अपडेट तो करता है, लेकिन पुरानी वैल्यू फेंक देता है। अक्सर पुरानी वैल्यू ही चाहिए होती है, मसलन हर थ्रेड को एक अलग टिकट नंबर देने के लिए।

xadd निर्देश (x यानी exchange) जोड़ते हुए पिछली वैल्यू लौटा देता है। यह जोड़ को डेस्टिनेशन में लिख देता है और डेस्टिनेशन की मूल वैल्यू सोर्स रजिस्टर में छोड़ देता है।

mov  rax, 1
lock xadd qword [rdi], rax ; [rdi] = [rdi] + rax = [rdi] + 1
                           ; rax = the old value of [rdi]

lock प्रीफिक्स के साथ यह एक अटॉमिक फेच-एंड-ऐड है। जब कई थ्रेड इसे एक ही काउंटर पर चलाते हैं, तो हर कॉल एक अलग पुरानी वैल्यू लौटाती है।

lock add की तरह, काउंटर ठीक उतनी ही संख्या पर पहुँचता है जितनी बार कॉल हुई। लेकिन lock add से अलग, यहाँ बीच की हर वैल्यू भी लौटाई जाती है, हर कॉलर को एक।

तुलना और एक्सचेंज

xadd जोड़ता है और xchg ऊपर से लिख देता है, लेकिन इनमें से कोई भी नई वैल्यू को मौजूदा वैल्यू पर निर्भर नहीं बना सकता, और न ही उसे सिर्फ तभी लागू कर सकता है जब नीचे कुछ न बदला हो। यही सशर्त अपडेट cmpxchg, यानी तुलना और एक्सचेंज, देता है, और यह इन सबमें सबसे सामान्य प्रिमिटिव है।

cmpxchg dest, src rax को एक अंतर्निहित एक्युमुलेटर की तरह इस्तेमाल करता है और उसकी dest से तुलना करता है:

  • अगर dest == rax, तो dest = src और ZF = 1।
  • अगर dest != rax, तो rax = dest और ZF = 0।

ध्यान दीजिए कि dest तभी अपडेट होता है जब वह उस अपेक्षित वैल्यू के बराबर हो जो पहले rax में लोड की गई थी। यह बराबरी पक्का करती है कि dest में अब भी वही वैल्यू है जिससे नई वैल्यू निकाली गई थी, इसलिए पुरानी पढ़ी गई वैल्यू पर आधारित अपडेट कभी लागू नहीं होता। इसी वजह से cmpxchg अटॉमिक अपडेट की बुनियादी ईंट है, जिसे कंपेयर-एंड-स्वैप (CAS) भी कहते हैं:

    mov rax, qword [rdi]          ; rax = the value we expect to find
.retry:
    lea rcx, [rax + 10]           ; rcx = the new value we want to install
    lock cmpxchg qword [rdi], rcx ; if [rdi] still equals rax, store rcx and set ZF
                                  ; otherwise reload rax with the current value, clear ZF
    jnz  .retry                   ; ZF is cleared, so another thread won the race. Recompute and retry

यह रीट्राई लूप लॉक-फ्री अपडेट का दिल है। पढ़ने और तुलना-और-एक्सचेंज के बीच का अंतराल ठीक वही समय होता है जब कोई दूसरा थ्रेड बीच में आ सकता है। cmpxchg इसे यह करके पकड़ लेता है: वह पुरानी पढ़ी गई वैल्यू से निकाली गई वैल्यू को स्टोर करने से इनकार कर देता है।

मेमोरी ऑर्डरिंग

अब तक हर काम एक ही लोकेशन पर हुआ है। जब थ्रेड एक से अधिक लोकेशन के ज़रिए तालमेल बिठाते हैं, तो एक नया सवाल उठता है: एक थ्रेड की लिखी वैल्यू दूसरे थ्रेड को किस क्रम में दिखती हैं। इस सवाल का जवाब प्रोसेसर की मेमोरी ऑर्डरिंग में है।

ब्रांचलेस-कोड कॉन्सेप्ट में यह बात पहले आ चुकी है कि आधुनिक कोर निर्देशों को एक-एक करके धीरे-धीरे नहीं निपटाता। वह एक साथ कई निर्देश चलता रहता है और जहाँ मौका मिले आगे बढ़ जाता है। इसका मतलब है कि कोई लिखावट दूसरे कोरों को प्रोग्राम के बताए क्रम से देर से दिख सकती है, जबकि उसके बाद के निर्देश पहले ही आगे बढ़ चुके होते हैं।

x86-64 सादे लोड और स्टोर के बीच मज़बूत मेमोरी ऑर्डरिंग बनाए रखता है, जिससे हर कोर पर:

  1. किसी लोड को उसके बाद के लोड के पीछे नहीं सरकाया जाता;
  2. किसी स्टोर को उसके बाद के स्टोर के पीछे नहीं सरकाया जाता;
  3. किसी लोड को उसके बाद के स्टोर के पीछे नहीं सरकाया जाता।

सिर्फ एक ही क्रम-बदलाव संभव है: किसी स्टोर का, किसी अलग पते के बाद वाले लोड के बाद पूरा होता दिखना।

lock प्रीफिक्स वाला निर्देश, या मेमोरी ऑपरेंड वाला xchg, एक पूरा बैरियर है: इसके आर-पार किसी भी दिशा में कुछ खिसकता हुआ नहीं दिखता। इसी वजह से ज़्यादातर हालात में पूरा क्रम सुनिश्चित करने के लिए यही काफी होता है।

स्पिनिंग और pause

जो निर्देश एक फ्लैग सेट करते हुए उसकी पिछली स्थिति भी लौटाते हैं, उन्हें टेस्ट-एंड-सेट कहते हैं। इन्हें एक स्पिनलॉक की बुनियाद के तौर पर इस्तेमाल किया जा सकता है, जो पक्का करता है कि किसी कोर के पास कोड के किसी हिस्से तक सिर्फ उसी की पहुँच हो।

यह पूरा एल्गोरिदम है, जो एक बाइनरी फ्लैग के साथ xchg निर्देश का इस्तेमाल करता है:

  1. शुरू में फ्लैग 0 रहता है।
  2. लॉक पाने के लिए एक कोर फ्लैग में रखी वैल्यू की 1 के साथ अदला-बदली करता है।
  3. अगर लौटाई गई वैल्यू 1 है, तो इसका मतलब है कि लॉक किसी दूसरे कोर के पास है। तब यह कोर इंतज़ार करता है और लॉक दोबारा पाने की कोशिश करता है।
  4. अगर लौटाई गई वैल्यू 0 है, तो इसका मतलब है कि लॉक खाली था। xchg ने अब उसे 1 कर दिया है, और बाकी कोर तब तक इंतज़ार करेंगे जब तक यह कोर उसे छोड़ न दे।
  5. जब यह कोर अपना काम खत्म कर लेता है, तो फ्लैग को 0 कर देना लॉक छोड़ देता है।
acquire:
    mov  eax, 1
    xchg dword [rdi], eax ; try to take the lock; eax = its old value
    test eax, eax
    jnz  .held            ; old value was 1: someone else holds it
    ret                   ; old value was 0: the lock is ours
.held:
    pause                 ; wait before trying again
    jmp  acquire

इंतज़ार वाले लूप में pause निर्देश इससे नहीं बदलता कि कोड क्या निकालता है। यह प्रोसेसर को संकेत देता है कि यह एक स्पिन-वेट है। तब CPU इंतज़ार कर रहे थ्रेड की बिजली की खपत घटा सकता है और उसी कोर पर चल रहे दूसरे थ्रेड को मौका दे सकता है। pause के बिना भी स्पिन लूप सही रहता है, बस फालतू है।

जब थ्रेड अपना काम पूरा कर लेता है, तो वह mov से सादा 0 स्टोर करके लॉक छोड़ सकता है। mov से ज़्यादा कुछ नहीं चाहिए, क्योंकि x86-64 पर लोड और स्टोर को उनके बाद के स्टोर के पीछे नहीं सरकाया जाता। जो स्टोर अपने से पहले के एक्सेसों से कभी आगे नहीं निकलता, उसके बारे में कहा जाता है कि उसमें रिलीज़ ऑर्डरिंग है, और x86 पर हर सादे स्टोर में यह होती है।

Note

कोई भी निर्देश जो किसी मेमोरी लोकेशन को अटॉमिक तरीके से जाँचकर सेट करता है, स्पिनलॉक के लिए इस्तेमाल किया जा सकता है। उदाहरण के लिए, xchg की जगह lock bts इस्तेमाल किया जा सकता है, जो किसी खास बिट को सेट करते हुए यह भी जाँच लेता है कि वह पहले से सेट थी या नहीं।

ध्यान दीजिए कि फ्लैग, जैसे bts द्वारा बदला जाने वाला CF, rflags नाम के रजिस्टर का हिस्सा होते हैं। इसका मतलब है कि वे सिर्फ हर थ्रेड के अपने होते हैं।

निर्देश

Grand Marquee में पहली रात है, और सारे कर्मचारी एक साथ काम कर रहे हैं। भीड़ हर दरवाज़े से अंदर उमड़ रही है, टिकट की कई खिड़कियाँ एक ही टिकट रोल से टिकट बेच रही हैं, प्रीमियर के टिकट लगभग पूरे हो चुके हैं, और ऊपर प्रोजेक्शन बूथ में रीलें चलती रहनी चाहिए।

कर्मचारी जो कुछ भी साझा करते हैं, वह सब एक मेमोरी एड्रेस पर रहता है, और टेस्ट आपके फंक्शन एक ही समय में कई थ्रेड्स से कॉल करते हैं। जो वैल्यू अलग-अलग चरणों में पढ़ी जाती है, बदली जाती है और वापस लिखी जाती है, उसके अपडेट इतने लोड में खो जाएँगे, इसलिए हर फंक्शन को अपना अपडेट एटॉमिक बनाना होगा।

इस अभ्यास में साझा की गई सारी वैल्यू 64-बिट साइन्ड पूर्णांक हैं।

आपके सामने पाँच काम हैं।

Note

इस अभ्यास में अपडेट एटॉमिक निर्देशों से करने चाहिए। टेस्ट आपके फंक्शन एक साथ, कई थ्रेड्स से चलाते हैं और जाँचते हैं कि कोई अपडेट खो न जाए, इसलिए बिना एटॉमिक पढ़ने-बदलने-लिखने वाला कोड उन्हें बहुत संभवतः फेल कर देगा।

1. प्रवेश की गिनती कीजिए

सारे दरवाज़े खुल चुके हैं, और हर दरवाज़े पर एक कर्मचारी टिकट फाड़ रहा है। अंदर जाने दिए गए हर समूह की गिनती एक ही साझा प्रवेश-गिनती में होती है, और जब हर दरवाज़ा एक ही समय गिन रहा हो, तब भी हॉल की कुल गिनती सही निकलनी चाहिए।

admit_group फंक्शन बनाइए, जो किसी समूह के आकार को एटॉमिक तरीके से गिनती में जोड़ता है।

यह फंक्शन इस क्रम में ये आर्गुमेंट लेता है:

  • admissions: साझा प्रवेश-गिनती का मेमोरी एड्रेस, एक 64-बिट साइन्ड पूर्णांक।
  • group_size: समूह में लोगों की संख्या, एक 64-बिट साइन्ड पूर्णांक, जो हमेशा 0 से बड़ा होता है।
admissions = 120, group_size = 4
// admissions is now 124

टेस्ट एक ही समय में कई दरवाज़ों से समूहों को अंदर जाने देते हैं। अंत में गिनती उन सभी समूहों के जोड़ के बराबर होनी चाहिए जिन्हें अंदर जाने दिया गया।

इस फंक्शन का कोई रिटर्न वैल्यू नहीं है। गिनती उसी जगह अपडेट की जानी चाहिए।

2. रील बदलिए

प्रोजेक्टर में एक समय में सिर्फ़ एक रील लगी रहती है। अगली रील लगाना और चल रही रील निकालना एक ही काम होना चाहिए: मशीन पर दो लोग काम कर रहे हों और ये दो अलग-अलग चरण बन जाएँ, तो हो सकता है वही रील दोनों को मिल जाए, या कोई रील फर्श पर गिर जाए।

change_reel फंक्शन बनाइए, जो एटॉमिक तरीके से अगली रील लगाता है और पहले लगी रील को लौटाता है।

यह फंक्शन इस क्रम में ये आर्गुमेंट लेता है:

  • mounted: लगी हुई रील की पहचान संख्या का मेमोरी एड्रेस, एक 64-बिट साइन्ड पूर्णांक।
  • next_reel: लगाने वाली रील की पहचान संख्या, एक 64-बिट साइन्ड पूर्णांक।
mounted = 7, next_reel = 9
// => 7, and mounted is now 9

यह फंक्शन पहले लगी रील की पहचान संख्या एक 64-बिट साइन्ड पूर्णांक के रूप में लौटाता है। टेस्ट एक ही माउंट पर, एक ही समय, कई थ्रेड्स से रीलें बदलवाते हैं और जाँचते हैं कि जो भी रील अंदर दी जाती है, वह ठीक एक बार बाहर निकलती है।

3. टिकट बेचिए

हर खिड़की बिक चुके टिकटों की एक ही साझा गिनती से टिकट बेचती है। यहाँ सिर्फ़ गिनती बढ़ा देना काफी नहीं है, जैसा दरवाज़ों पर चलता है: हर बिक्री को यह भी मालूम होना चाहिए कि उसे कौन-सा नंबर मिला। किन्हीं दो बिक्री का नंबर एक जैसा नहीं हो सकता, और भीड़ खत्म होने के बाद गिनती बिके टिकटों की संख्या के बिल्कुल बराबर होनी चाहिए।

sell_ticket फंक्शन बनाइए, जो एटॉमिक तरीके से गिनती में एक जोड़ता है और अभी बिके टिकट का नंबर लौटाता है। टिकट का नंबर इस बिक्री के बाद की गिनती है: जब गिनती 41 थी, तो यह बिक्री टिकट 42 है।

यह फंक्शन यह आर्गुमेंट लेता है:

  • tickets_sold: बिक चुके टिकटों की साझा गिनती का मेमोरी एड्रेस, एक 64-बिट साइन्ड पूर्णांक।
tickets_sold = 0
// => 1, and tickets_sold is now 1

tickets_sold = 41
// => 42, and tickets_sold is now 42

यह फंक्शन टिकट का नंबर एक 64-बिट साइन्ड पूर्णांक के रूप में लौटाता है। टेस्ट एक ही समय में कई खिड़कियों से टिकट बिकवाते हैं: लौटाया गया हर नंबर अलग होना चाहिए, और अंत में गिनती बिक्री की संख्या के बराबर होनी चाहिए।

4. प्रीमियर भरिए

प्रीमियर में सीटों की संख्या तय है, और हर खिड़की उसी के टिकट बेच रही है। बिक्री तभी हो सकती है जब कोई सीट बची हो, और शो में कभी ज़रूरत से ज़्यादा टिकट नहीं बिकने चाहिए, चाहे हर खिड़की एक ही पल में अंतिम सीट के लिए हाथ बढ़ा रही हो। गिनती जाँचना और फिर उसे बढ़ाना, अगर ये दो अलग-अलग चरण रहें, तो बीच में एक मौका खुला रह जाता है; इसलिए जाँच और अपडेट या तो साथ सफल हों या साथ फेल।

claim_seat फंक्शन बनाइए, जो सीट बची होने पर एटॉमिक तरीके से अगली सीट ले लेता है। सीट मिलने पर यह फंक्शन उस सीट का नंबर लौटाता है, जो इसके बाद भरी हुई सीटों की गिनती होती है। शो हाउसफुल हो जाने पर यह 0 लौटाता है और गिनती को वैसा ही छोड़ देता है।

यह फंक्शन इस क्रम में ये आर्गुमेंट लेता है:

  • seats_taken: भरी जा चुकी सीटों की गिनती का मेमोरी एड्रेस, एक 64-बिट साइन्ड पूर्णांक, जो कभी capacity से ऊपर नहीं होता।
  • capacity: शो में सीटों की संख्या, एक 64-बिट साइन्ड पूर्णांक, जो हमेशा 0 से बड़ा होता है।
seats_taken = 0, capacity = 150
// => 1, and seats_taken is now 1

seats_taken = 82, capacity = 150
// => 83, and seats_taken is now 83

seats_taken = 149, capacity = 150
// => 150, and seats_taken is now 150

seats_taken = 150, capacity = 150
// => 0, and seats_taken stays 150

यह फंक्शन सीट का नंबर लौटाता है, या शो हाउसफुल हो जाने पर 0, एक 64-बिट साइन्ड पूर्णांक के रूप में।

5. बूथ का ध्यान रखिए

प्रोजेक्शन बूथ में एक ही व्यक्ति बैठ सकता है। मेमोरी का एक शब्द बताता है कि बूथ भरा है या खाली: 0 का मतलब खाली, 1 का मतलब भरा हुआ। सारी रात कर्मचारी आते रहते हैं, और हर किसी को अंदर कोई काम करना होता है: लॉगबुक में हस्ताक्षर करना, प्रोजेक्टर में रील डालना, या रील को उसकी जगह रखना। काम कोई भी हो, बूथ का नियम एक ही है, और उसे लागू करना आपका काम है: एक समय में अंदर सिर्फ़ एक व्यक्ति, उसके काम की शुरुआत से लेकर उसके पूरा होने तक।

visit_booth फंक्शन बनाइए, जो:

  1. बूथ खाली होने तक इंतज़ार करता है;
  2. बूथ ले लेता है;
  3. आने वाले का काम चलाता है;
  4. काम पूरा होने के बाद, लौटने से पहले बूथ फिर से खोल देता है।

जब तक बूथ किसी और के पास हो, इसे pause के साथ शालीनता से इंतज़ार करना चाहिए।

यह फंक्शन इस क्रम में ये आर्गुमेंट लेता है:

  • booth: बूथ भरा है या खाली, यह बताने वाले शब्द का मेमोरी एड्रेस, एक 64-बिट साइन्ड पूर्णांक जो या तो 0 होता है या 1।
  • task: आने वाले का काम, एक ऐसा फंक्शन जो कोई पैरामीटर नहीं लेता और जिसकी कोई रिटर्न वैल्यू नहीं होती।

ध्यान रखिए कि टेस्ट ऐसे काम चलाते हैं जो बूथ को और एक साझा लॉगबुक को भी देखते हैं। इसका मतलब है कि पूरे काम के दौरान बूथ पर किसी एक का होना ज़रूरी है, हर मुलाक़ात अपना काम ठीक एक बार चलानी चाहिए, और एक साथ कई लोग आने पर लॉगबुक बिल्कुल सही निकलनी चाहिए।

Caution

आने वाले का काम एक साधारण फंक्शन है जो कॉलिंग कन्वेंशन का पालन करता है:

  • यह किसी भी कॉलर-सेव्ड रजिस्टर पर अपना डेटा लिख सकता है, इसलिए काम के बाद भी जो चीज़ आपको चाहिए, उसे ऐसी जगह रखिए जहाँ यह कॉल पहुँच न सके;
  • और यह अपेक्षा करता है कि कॉल के समय स्टैक 16-बाइट अलाइन्ड हो।

इनमें से किसी भी नियम का उल्लंघन फेल हुए चेक की जगह क्रैश के रूप में सामने आ सकता है।

GitHub के ज़रिए संपादित करें यह लिंक एक नई विंडो या टैब में खुलता है
x86-64 Assembly Exercism

उद्घाटन रात शुरू करने के लिए तैयार हैं?

Exercism पर साइन अप कीजिए और x86-64 Assembly को 22 कॉन्सेप्ट130 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।