सिग्नल पकड़े जाने पर सिस्टम कॉल में रुकावट


29

मैन पेज को पढ़ने read()और write()कॉल करने से यह प्रतीत होता है कि ये कॉल सिग्नल द्वारा बाधित हो जाते हैं, भले ही उन्हें ब्लॉक करना है या नहीं।

विशेष रूप से, मान लें

  • एक प्रक्रिया कुछ संकेत के लिए एक हैंडलर स्थापित करती है।
  • एक डिवाइस खोला जाता है (कहते हैं, एक टर्मिनल) सेट के साथ O_NONBLOCK नहीं (यानी अवरुद्ध मोड में परिचालन)
  • प्रक्रिया तब read()डिवाइस से पढ़ने के लिए एक सिस्टम कॉल करती है और परिणामस्वरूप कर्नेल-स्पेस में कर्नेल कंट्रोल पथ निष्पादित करता है।
  • जब प्राइसेज read()कर्नेल-स्पेस में अपने निष्पादन को अंजाम दे रहा है, तो जिस सिग्नल के लिए हैंडलर पहले स्थापित किया गया था, उस प्रक्रिया तक पहुंचाया गया है और उसके सिग्नल हैंडलर को लागू किया गया है।

SUSv3 'सिस्टम इंटरफेस वॉल्यूम (XSH)' में मैन पेज और उपयुक्त अनुभागों को पढ़ना , एक को पता चलता है कि:

मैं। यदि read()किसी डेटा को पढ़ने से पहले उसे सिग्नल द्वारा बाधित किया जाता है (अर्थात कोई डेटा उपलब्ध नहीं होने के कारण उसे ब्लॉक करना पड़ता है), तो यह -1 errno[EINTR] पर सेट हो जाता है।

ii। यदि read()किसी डेटा को सफलतापूर्वक पढ़ने के बाद किसी सिग्नल से बाधित किया जाता है (यानी अनुरोध को तुरंत सर्विस करना शुरू करना संभव था), तो यह बाइट्स की संख्या को पढ़ता है।

प्रश्न ए): क्या मैं यह मानने के लिए सही हूं कि या तो मामले में (ब्लॉक / कोई ब्लॉक) सिग्नल की डिलीवरी और हैंडलिंग पूरी तरह से पारदर्शी नहीं है read()?

केस i। यह समझने में आसान लगता है क्योंकि अवरुद्ध करने read()की प्रक्रिया सामान्य रूप से TASK_INTERRUPTIBLEराज्य में होती है ताकि जब कोई सिग्नल दिया जाए, तो कर्नेल प्रक्रिया को TASK_RUNNINGराज्य में रखता है ।

हालाँकि जब read()ब्लॉक (केस ii) को ब्लॉक करने की आवश्यकता नहीं है और कर्नेल-स्पेस में अनुरोध को संसाधित कर रहा है, तो मैंने सोचा होगा कि सिग्नल का आगमन और इसकी हैंडलिंग किसी HW के आगमन और उचित हैंडलिंग की तरह पारदर्शी होगी व्यवधान होगा। विशेष रूप से मैंने यह माना होगा कि सिग्नल के वितरण पर, प्रक्रिया को अपने सिग्नल हैंडलर को निष्पादित करने के लिए अस्थायी रूप से उपयोगकर्ता मोड में रखा जाएगा , जहां से यह अंत में बाधित read()(कर्नेल-स्पेस) में प्रसंस्करण को पूरा करने के लिए वापस आ जाएगा ताकि read()इसके रन पूरा होने के बाद, जिसके बाद यह प्रक्रिया read()(उपयोगकर्ता-स्थान में) कॉल के ठीक बाद बिंदु पर वापस आती है , जिसके परिणामस्वरूप उपलब्ध सभी बाइट्स पढ़े जाते हैं।

लेकिन ii। लगता है कि read()यह बाधित है, क्योंकि डेटा तुरंत उपलब्ध है, लेकिन यह केवल कुछ डेटा (सभी के बजाय) देता है।

यह मुझे मेरे दूसरे (और अंतिम) प्रश्न पर लाता है:

प्रश्न बी): यदि ए के तहत मेरी धारणा सही है, read()तो बाधा क्यों आती है, भले ही इसे ब्लॉक करने की आवश्यकता न हो क्योंकि अनुरोध को तुरंत पूरा करने के लिए डेटा उपलब्ध है? दूसरे शब्दों में, read()सिग्नल हैंडलर को निष्पादित करने के बाद फिर से शुरू क्यों नहीं किया जाता है , जिसके परिणामस्वरूप सभी उपलब्ध डेटा (जो सभी के बाद उपलब्ध था) को वापस किया जाना है?

जवाबों:


29

सारांश: आप सही हैं कि एक संकेत प्राप्त करना पारदर्शी नहीं है, न तो मामले में मैं (कुछ भी पढ़े बिना बाधित) और न ही मामले में ii (आंशिक पढ़ने के बाद बाधित)। अन्यथा ऐसा करने के लिए मुझे ऑपरेटिंग सिस्टम की वास्तुकला और अनुप्रयोगों की वास्तुकला दोनों में मूलभूत परिवर्तन करने की आवश्यकता होगी।

OS कार्यान्वयन दृश्य

विचार करें कि क्या होता है यदि सिस्टम कॉल सिग्नल से बाधित होता है। सिग्नल हैंडलर उपयोगकर्ता-मोड कोड निष्पादित करेगा। लेकिन syscall हैंडलर कर्नेल कोड है और किसी भी उपयोगकर्ता-मोड कोड पर भरोसा नहीं करता है। तो आइए syscall हैंडलर के विकल्पों को देखें:

  • सिस्टम कॉल को समाप्त करें; रिपोर्ट करें कि उपयोगकर्ता कोड कितना किया गया था। यदि वांछित है, तो सिस्टम कॉल को किसी तरह से पुनः आरंभ करने के लिए यह एप्लिकेशन कोड तक है। इस तरह यूनिक्स काम करता है।
  • सिस्टम कॉल की स्थिति को सहेजें, और उपयोगकर्ता कोड को कॉल फिर से शुरू करने की अनुमति दें। यह कई कारणों से समस्याग्रस्त है:
    • जब उपयोगकर्ता कोड चल रहा होता है, तो सहेजे गए स्टेट को अमान्य करने के लिए कुछ हो सकता है। उदाहरण के लिए, यदि किसी फ़ाइल से पढ़ा जाता है, तो फ़ाइल को छोटा किया जा सकता है। तो इन मामलों को संभालने के लिए कर्नेल कोड को बहुत सारे तर्क की आवश्यकता होगी।
    • सहेजे गए राज्य को किसी भी लॉक को रखने की अनुमति नहीं दी जा सकती है, क्योंकि इस बात की कोई गारंटी नहीं है कि उपयोगकर्ता कोड कभी भी syscall को फिर से शुरू करेगा, और फिर लॉक हमेशा के लिए आयोजित किया जाएगा।
    • कर्नेल को सामान्य इंटरफ़ेस के अलावा, syscall शुरू करने के लिए नए इंटरफ़ेस को फिर से शुरू करने या रद्द करने के लिए नए इंटरफ़ेस को उजागर करना चाहिए। यह एक दुर्लभ मामले के लिए बहुत अधिक जटिलता है।
    • सहेजे गए राज्य को संसाधनों (मेमोरी, कम से कम) का उपयोग करने की आवश्यकता होगी; उन संसाधनों को कर्नेल द्वारा आवंटित और धारण करने की आवश्यकता होगी लेकिन प्रक्रिया के आवंटन के खिलाफ गिना जाएगा। यह अचूक नहीं है, लेकिन यह एक जटिलता है।
      • ध्यान दें कि सिग्नल हैंडलर सिस्टम कॉल कर सकता है जो स्वयं बाधित हो जाता है; तो आप बस एक स्थिर संसाधन आबंटन नहीं कर सकते हैं जो सभी संभावित syscalls को कवर करता है।
      • और क्या होगा अगर संसाधनों का आवंटन नहीं किया जा सकता है? फिर syscall को वैसे भी विफल होना होगा। जिसका अर्थ है कि इस मामले को संभालने के लिए एप्लिकेशन को कोड की आवश्यकता होगी, इसलिए यह डिज़ाइन एप्लिकेशन कोड को सरल नहीं करेगा।
  • प्रगति में रहें (लेकिन निलंबित), सिग्नल हैंडलर के लिए एक नया धागा बनाएं। यह, फिर से, समस्याग्रस्त है:
    • प्रारंभिक यूनिक्स कार्यान्वयन में प्रति प्रक्रिया एक ही धागा था।
    • सिग्नल हैंडलर syscall के जूतों पर ओवरस्टेपिंग का जोखिम उठाएगा। यह वैसे भी एक मुद्दा है, लेकिन वर्तमान यूनिक्स डिजाइन में, यह निहित है।
    • नए धागे के लिए संसाधनों को आवंटित करने की आवश्यकता होगी; ऊपर देखो।

एक रुकावट के साथ मुख्य अंतर यह है कि बाधा कोड विश्वसनीय है, और अत्यधिक विवश है। यह आमतौर पर संसाधनों को आवंटित करने, या हमेशा के लिए चलाने, या ताले लेने और उन्हें जारी नहीं करने, या किसी अन्य प्रकार की गंदी चीजों को करने की अनुमति नहीं है; चूंकि बाधा हैंडलर ओएस कार्यान्वयनकर्ता द्वारा स्वयं लिखा गया है, वह जानता है कि यह कुछ भी बुरा नहीं करेगा। दूसरी ओर, एप्लिकेशन कोड कुछ भी कर सकता है।

एप्लिकेशन डिज़ाइन दृश्य

जब एक सिस्टम कॉल के बीच में कोई एप्लिकेशन बाधित होता है, तो क्या सिसकॉल को पूरा करना जारी रखना चाहिए? हर बार नहीं। उदाहरण के लिए, एक प्रोग्राम जैसे शेल पर विचार करें जो टर्मिनल से एक लाइन पढ़ रहा है, और उपयोगकर्ता प्रेस करता है Ctrl+C, जो SIGINT को ट्रिगर करता है। रीड पूरा नहीं होना चाहिए, यही संकेत सभी के बारे में है। ध्यान दें कि इस उदाहरण से पता चलता है कि readयदि कोई बाइट अभी तक नहीं पढ़ी गई है तो भी syscall को बाधित होना चाहिए।

इसलिए एप्लिकेशन को सिस्टम कॉल को रद्द करने के लिए कर्नेल को बताने का एक तरीका होना चाहिए। यूनिक्स डिजाइन के तहत, यह स्वचालित रूप से होता है: संकेत syscall रिटर्न बनाता है। अन्य डिजाइनों को आवेदन को अपनी अनुमति पर syscall को फिर से शुरू करने या रद्द करने के लिए एक मार्ग की आवश्यकता होगी।

readसिस्टम कॉल यह है, क्योंकि यह आदिम है कि समझ में आता है है, ऑपरेटिंग सिस्टम के सामान्य डिजाइन दी तरीका है। इसका क्या अर्थ है, मोटे तौर पर, "जितना हो सके उतना पढ़ें, एक सीमा तक (बफर आकार), लेकिन अगर कुछ होता है तो रोकें"। वास्तव में पढ़ने के लिए एक पूर्ण बफर readमें एक लूप में चलना शामिल है जब तक कि संभव के रूप में कई बाइट्स नहीं पढ़े गए हैं; इस, एक उच्च स्तरीय समारोह है fread(3)। इसके विपरीत read(2)जो एक सिस्टम कॉल है, freadएक लाइब्रेरी फ़ंक्शन है, जिसे उपयोगकर्ता के शीर्ष पर लागू किया जाता है read। यह उस एप्लिकेशन के लिए उपयुक्त है जो किसी फ़ाइल के लिए पढ़ता है या कोशिश कर मर जाता है; यह एक कमांड लाइन दुभाषिया के लिए या नेटवर्क प्रोग्राम के लिए उपयुक्त नहीं है जो कि कनेक्शन को साफ-सुथरा करना चाहिए, न ही ऐसे नेटवर्क प्रोग्राम के लिए जिसमें समवर्ती कनेक्शन हों और जो थ्रेड का उपयोग न करें।

लूप में पढ़ने का उदाहरण रॉबर्ट लव के लिनक्स सिस्टम प्रोग्रामिंग में दिया गया है:

ssize_t ret;
while (len != 0 && (ret = read (fd, buf, len)) != 0) {
  if (ret == -1) {
    if (errno == EINTR)
      continue;
    perror ("read");
    break;
  }
  len -= ret;
  buf += ret;
}

यह और कुछ case iऔर ध्यान रखता है case ii


बहुत संक्षिप्त और स्पष्ट उत्तर के लिए बहुत बहुत धन्यवाद, जो समान विचारों को पुष्टि करता है जो UNIX डिजाइन दर्शन पर एक लेख में सामने रखा गया है। मुझे लगता है कि syscall रुकावट व्यवहार तकनीकी बाधाओं या बाधाओं के बजाय UNIX डिजाइन फिलोसोफी के साथ क्या करना है
darbehdar

@darbehdar यह तीनों हैं: यूनिक्स डिजाइन दर्शन (यहां मुख्य रूप से प्रक्रियाएं कर्नेल की तुलना में कम विश्वसनीय हैं और मनमाना कोड चला सकती हैं, यह भी कि प्रक्रियाएं और धागे अंतर्निहित नहीं हैं), तकनीकी बाधाएं (संसाधन आवंटन पर), और एप्लिकेशन डिजाइन (वहां ऐसे मामले हैं जब सिग्नल को सिसकल को रद्द करना होगा)।
गिल्स एसओ- बुराई को रोकना '

2

प्रश्न A का उत्तर देने के लिए :

हां, सिग्नल की डिलीवरी और हैंडलिंग पूरी तरह से पारदर्शी नहीं है read()

read()चल आधे रास्ते कुछ संसाधनों पर कब्जा किया जा सकता है, जबकि यह संकेत से बाधित है। और सिग्नल के सिग्नल हैंडलर read()किसी अन्य (या किसी अन्य async- सिग्नल सुरक्षित syscalls ) को भी कॉल कर सकते हैं । तो read()सिग्नल द्वारा बाधित होने वाले संसाधनों को जारी करने के लिए सबसे पहले रोका जाना चाहिए, अन्यथा read()सिग्नल हैंडलर से कॉल करने पर वे उसी संसाधनों तक पहुंच बनाएंगे और रीजेंट मुद्दों का कारण बनेंगे।

क्योंकि read()सिग्नल हैंडलर से सिस्टम कॉल को कॉल किया जा सकता है और वे संसाधनों के समान सेट पर भी कब्जा कर सकते हैं read()। उपरोक्त रीक्रेंट मुद्दों से बचने के लिए, सबसे सरल, सबसे सुरक्षित डिज़ाइन read()हर बार बाधित होने से रोकने के लिए होता है जब इसके रन के दौरान एक सिग्नल होता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.