2010-10-15 4 views
37

यूनिक्स सिस्टम में हम जानते हैं कि malloc() एक गैर-पुनर्वित्त समारोह (सिस्टम कॉल) है। ऐसा क्यों है?मॉलोक() और printf() को गैर-पुनर्वित्त के रूप में क्यों कहा जाता है?

इसी प्रकार, printf() को गैर-पुनर्वित्त कहा जाता है; क्यूं कर?

मुझे फिर से प्रवेश की परिभाषा पता है, लेकिन मैं जानना चाहता था कि यह इन कार्यों पर क्यों लागू होता है। क्या उन्हें गारंटीकृत गारंटी प्राप्त करने से रोकता है?

+1

@ रिपुनजय-त्रिपाठी: printf, अगर यह सामान्य संसाधन को प्रिंट कर रहा है, उदा। stdio। malloc क्योंकि यह ताले पर भरोसा करते हैं। याद रखें कि पुनर्वित्तक और थ्रेड सुरक्षा के बीच एक अंतर है। जहां मॉलोक थ्रेड-सुरक्षित है। इस पोस्ट को देखो। http://stackoverflow.com/questions/855763/malloc-thread-safe। – yadab

+0

आपको "कैननिकल कार्यान्वयन" कहां मिला? यदि डेवलपर्स कहते हैं कि वे हैं तो कार्य पुनर्वित्तक हैं। glibc डेवलपर्स नहीं कहते हैं कि malloc या printf पुनर्वित्तक हैं: तो वे नहीं हैं। – pmg

+0

@pmg, "कैननिकल कार्यान्वयन" के बारे में शब्द मेरे द्वारा जोड़े गए थे। मेरा मतलब यही है। यह स्पष्ट है कि पुनर्वितरण एक कार्यान्वयन की संपत्ति है, इंटरफ़ेस की नहीं। हालांकि, उदाहरण के लिए, POSIX 'malloc' और 'printf' को पुनर्वित्त कार्यों के रूप में सूचीबद्ध नहीं करता है, और यह कारण के लिए है। इस quesiton में, ओपी जानना चाहता था कि कारण क्या है। –

उत्तर

48

malloc और printf आमतौर पर वैश्विक संरचनाओं का उपयोग करते हैं, और आंतरिक रूप से लॉक-आधारित सिंक्रनाइज़ेशन का उपयोग करते हैं। यही कारण है कि वे पुनर्वित्त नहीं कर रहे हैं।

malloc फ़ंक्शन या तो थ्रेड-सुरक्षित या थ्रेड-असुरक्षित हो सकता है। दोनों रैत्रांत नहीं हैं:

  1. Malloc एक वैश्विक ढेर पर चल रही है, और यह संभव है कि एक ही समय में हो malloc के दो विभिन्न आमंत्रण, एक ही स्मृति ब्लॉक वापस जाएँ।(दूसरा मॉलोक कॉल चंक के पते से पहले होना चाहिए, लेकिन खंड को अनुपलब्ध के रूप में चिह्नित नहीं किया गया है)। यह malloc के पोस्टकंडिशन का उल्लंघन करता है, इसलिए यह कार्यान्वयन फिर से प्रवेश नहीं किया जाएगा।

  2. इस प्रभाव को रोकने के लिए, malloc का थ्रेड-सुरक्षित कार्यान्वयन लॉक-आधारित सिंक्रनाइज़ेशन का उपयोग करेगा। हालांकि, malloc संकेत हैंडलर से कहा जाता है, तो निम्न स्थिति हो सकता है:

    malloc();   //initial call 
        lock(memory_lock); //acquire lock inside malloc implementation 
    signal_handler(); //interrupt and process signal 
    malloc();   //call malloc() inside signal handler 
        lock(memory_lock); //try to acquire lock in malloc implementation 
        // DEADLOCK! We wait for release of memory_lock, but 
        // it won't be released because the original malloc call is interrupted 
    

    यह स्थिति जब malloc बस अलग धागे से कहा जाता है नहीं होगा। दरअसल, पुनर्वित्त अवधारणा थ्रेड-सुरक्षा से परे है और इसके लिए कार्यों को ठीक से काम करने की आवश्यकता है भले ही इसका कोई भी आमंत्रण को समाप्त न करे। यह मूल रूप से तर्क है कि ताले के साथ कोई भी कार्य फिर से प्रवेश नहीं करेगा।

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

+0

अंत में, धन्यवाद। मैं उतना ही कम लिखने वाला था, लेकिन आपका जवाब ठीक उसी तरह दिखाई दिया जब मैं अपना टाइप करना शुरू कर रहा था। +1। – janneb

+1

"ताले के साथ कोई भी फ़ंक्शन पुन: प्रवेश नहीं करेगा"। मैंने एक सिस्टम पर काम किया जहां आप म्यूटेक्स पर इंतजार करते समय म्यूटेक्स को ध्वजांकित कर सकते थे, या तो म्यूटेक्स पर इंतजार करते समय, या पूरे समय म्यूटेक्स आयोजित किया जाता था। जाहिर है, यह गलत उपयोग करना आसान था, और आपको यह गारंटी देना होगा कि फ़ंक्शन वापस आ जाएगा, लेकिन इसका उपयोग पुनर्वित्त कार्यों (आमतौर पर कर्नेल में) से ग्लोबल्स तक पहुंचने के लिए किया जाता था। मैं प्रमाण के बिना मानता हूं कि अन्य कर्नेल के बराबर तंत्र हैं, लेकिन यह भी कि मानक ऐसे शेंगेनियों की आवश्यकता नहीं लेना चाहता जहां टिकाऊ हो। –

+0

@Steve: उदाहरण के लिए, लिनक्स कर्नेल स्पिनलॉक्स में आमतौर पर लॉक होने पर इंटरप्ट अक्षम कर देते हैं। "सामान्य" सोते हुए म्यूटेक्स ओटीओएच इंटरप्ट्स सक्षम के साथ चलाते हैं। – janneb

1

अधिकतर संभावना है क्योंकि आप आउटपुट लिखना शुरू नहीं कर सकते हैं जबकि printf के लिए एक और कॉल अभी भी स्वयं को प्रिंट कर रहा है। स्मृति आवंटन और deallocation के लिए भी यही है।

+1

यह बताता है कि "पुनः प्रवेश" क्या है, लेकिन क्यों नहीं ये फ़ंक्शन गैर-प्रवेशकर्ता नहीं हैं। – ChrisF

+0

ओह महान तो। मैं printf के बारे में पूछने के लिए मूर्ख था। धन्यवाद। लेकिन क्या हम malloc() को दो अलग-अलग धागे से एक साथ नहीं बुला सकते? –

+0

* "आप आउटपुट लिखना शुरू नहीं कर सकते हैं जबकि प्रिंटफ के लिए एक और कॉल अभी भी इसे स्वयं प्रिंट कर रहा है।" * क्यों? 'Printf' का क्या कारण है? यह स्पष्ट नहीं है। हो सकता है कि परिणाम 'हैलो, woSOMETHINGELSErld!', लेकिन आपसे पूछा गया कि फिर भी मुद्रित किया जाएगा? –

-2

ऐसा इसलिए है क्योंकि दोनों वैश्विक संसाधनों के साथ काम करते हैं: ढेर मेमोरी संरचनाएं और कंसोल।

संपादित करें: ढेर एक तरह से जुड़ी सूची संरचना से कुछ और नहीं है। प्रत्येक malloc या free इसे संशोधित करता है, इसलिए लेखन के साथ एक ही समय में कई धागे होने से इसकी स्थिरता कम हो जाएगी।

EDIT2: एक और विवरण: उन्हें म्यूटेक्स का उपयोग करके डिफ़ॉल्ट रूप से पुनर्वित्त किया जा सकता है। लेकिन यह दृष्टिकोण महंगा है, और कोई गारंटी नहीं है कि उनका हमेशा एमटी पर्यावरण में उपयोग किया जाएगा।

तो दो समाधान हैं: 2 लाइब्रेरी फ़ंक्शंस बनाने के लिए, एक पुनर्विक्रेता और कोई उपयोगकर्ता को म्यूटेक्स भाग नहीं छोड़ता या छोड़ देता है। उन्होंने दूसरा चुना है।

इसके अलावा, ऐसा इसलिए हो सकता है क्योंकि इन कार्यों के मूल संस्करण गैर-पुनर्वित्तक थे, इसलिए संगतता के लिए इसे घोषित किया गया है।

+0

कोई अच्छा जवाब नहीं है, यह स्पष्ट है। –

+1

तो आप दावा करते हैं कि 'malloc' थ्रेड-सुरक्षित नहीं है? दिलचस्प ... (-1) और आप यह भी दावा करते हैं कि म्यूटेक्स सहित फ़ंक्शन पुनर्वित्तक बनाता है ... और भी दिलचस्प! (-2) –

+0

@ पावेलशवेड - चाहे मॉलोक थ्रेड-सुरक्षित है या नहीं *** [बहस योग्य बिंदु] (http://stackoverflow.com/q/855763/645128) ***। कम से कम इस मुद्दे को दूर करने में मदद करने के लिए एक संदर्भ प्रदान करें। *** [अपनी पोस्ट से] (http://stackoverflow.com/a/3941563/645128) *** क्या आप यह नहीं कहते कि यह संभव है कि मॉलोक थ्रेड हो सकता है *** un *** सुरक्षित? – ryyker

-4

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

+0

ठीक है, लेकिन यह कहां विफल हो सकता है? अगर मुझे कुछ उदाहरण मिल जाए तो अच्छा होगा। –

+0

सी विनिर्देशन में यह अनिर्धारित व्यवहार है, यह कहां असफल हो सकता है इसका उदाहरण नहीं है, उस समय सी विनिर्देश लिखते समय यह एक गैर मुद्दा था (बिल्कुल कोई थ्रेड नहीं था)। यह कुछ कार्यान्वयन पर काम कर सकता है, न कि दूसरों पर बिल्कुल, जबकि दोनों कार्यान्वयन विनिर्देशों का पालन कर सकते हैं। – UnixShadow

+0

@RIPUNJAY: ऐसा हो सकता है कि मॉलोक को दोनों कॉल एक ही सूचक को वापस कर दें, क्योंकि दोनों मॉलोक इनवोकेशंस ने यह निर्धारित किया कि ब्लॉक उपलब्ध होना चाहिए (पहला आवेदक ब्लॉक को नि: शुल्क निर्धारित करने और इसे आवंटित करने के बीच बाधित किया जाएगा)। –

10

चलिए समझते हैं कि re-entrant द्वारा हमारा क्या मतलब है। पिछले आमंत्रण समाप्त होने से पहले एक पुन: प्रवेश समारोह लागू किया जा सकता है। यह एक संकेत है कि समारोह के निष्पादन के दौरान उठाया गया था

  • एक समारोह रिकर्सिवली कहा जाता है
  • के लिए (यूनिक्स कुछ व्यवधान हैंडलर की तुलना में अधिक आम तौर पर या) एक समारोह एक संकेत हैंडलर में कहा जाता है यदि

    • हो सकता है

      malloc फिर से प्रवेश नहीं कर रहा है क्योंकि यह कई वैश्विक डेटा संरचनाओं का प्रबंधन कर रहा है जो निःशुल्क मेमोरी ब्लॉक ट्रैक करते हैं।

      printf फिर से प्रवेश नहीं है क्योंकि यह एक वैश्विक चर यानी FILE * स्टउट की सामग्री को संशोधित करता है।

    3

    यहां कम से कम तीन अवधारणाएं हैं, जिनमें से सभी बोलचाल भाषा में उलझन में हैं, जो कि आप उलझन में थे।

    • धागा सुरक्षित
    • महत्वपूर्ण अनुभाग
    • फिर से प्रवेशी

    सबसे आसान एक पहले लेने के लिए: दोनों malloc और printfthread-safe हैं। उन्हें 2001 से मानक सी में थ्रेड-सुरक्षित होने की गारंटी दी गई है, 2001 से पॉज़िक्स में, और इससे पहले से ही अभ्यास में। इसका मतलब है कि निम्नलिखित कार्यक्रम दुर्घटना या प्रदर्शन करने के लिए नहीं बुरा व्यवहार की गारंटी है है:

    #include <pthread.h> 
    #include <stdio.h> 
    
    void *printme(void *msg) { 
        while (1) 
        printf("%s\r", (char*)msg); 
    } 
    
    int main() { 
        pthread_t thr; 
        pthread_create(&thr, NULL, printme, "hello");   
        pthread_create(&thr, NULL, printme, "goodbye");   
        pthread_join(thr, NULL); 
    } 
    

    एक समारोह जो थ्रेड-सुरक्षित नहींstrtok है का एक उदाहरण। यदि आप strtok को दो अलग-अलग धागे से एक साथ कॉल करते हैं, तो परिणाम अपरिभाषित व्यवहार है - क्योंकि strtok आंतरिक रूप से अपने राज्य का ट्रैक रखने के लिए एक स्थिर बफर का उपयोग करता है। glibc इस समस्या को हल करने के लिए strtok_r जोड़ता है, और सी 11 ने एक ही चीज़ को जोड़ा (लेकिन वैकल्पिक रूप से और एक अलग नाम के तहत, क्योंकि यहां खोज नहीं किया गया) strtok_s के रूप में।

    ठीक है, लेकिन printf अपने आउटपुट का निर्माण करने के लिए वैश्विक संसाधनों का भी उपयोग नहीं करता है? वास्तव में, यह का मतलब क्या होगा दो धागे से stdout पर प्रिंट करने के लिए? जो हमें अगले विषय पर लाता है। जाहिर है printf इसका उपयोग करने वाले किसी भी प्रोग्राम में critical section होने जा रहा है। एक बार में महत्वपूर्ण अनुभाग के अंदर निष्पादन का केवल एक थ्रेड होने की अनुमति है।

    कम से कम POSIX अनुरूप प्रणालियों में, इस printfflockfile(stdout) के लिए एक कॉल के साथ शुरू और funlockfile(stdout) के लिए एक कॉल है, जो एक वैश्विक stdout के साथ जुड़े म्युटेक्स लेने की तरह मूल रूप से है के साथ समाप्त होने से हासिल की है।

    हालांकि, कार्यक्रम में प्रत्येक विशिष्ट FILE को अपना स्वयं का म्यूटेक्स रखने की अनुमति है। इसका मतलब है कि एक थ्रेड fprintf(f1,...) पर कॉल कर सकता है, उसी समय एक दूसरा थ्रेड fprintf(f2,...) पर कॉल के बीच में होता है। यहां कोई दौड़ की स्थिति नहीं है। (क्या आपके libc वास्तव में समानांतर में उन दो कॉल चलाता है एक QoI मुद्दा है। मैं वास्तव में नहीं पता है glibc क्या करता है।)

    इसी तरह, malloc किसी भी आधुनिक प्रणाली में एक महत्वपूर्ण अनुभाग होने की संभावना नहीं है, क्योंकि आधुनिक प्रणालियों रहे हैं smart enough to keep one pool of memory for each thread in the system, सभी एन धागे एक पूल पर लड़ने के बजाय। (sbrk सिस्टम कॉल अभी भी शायद एक महत्वपूर्ण अनुभाग होगा, लेकिन mallocsbrk में अपने समय की बहुत कम खर्च करता है। या mmap, या जो कुछ भी शांत बच्चों को इन दिनों का उपयोग कर रहे हैं।)

    ठीक है, तो क्या re-entrancy वास्तव में करता है क्या मतलब है? असल में, इसका मतलब है कि फ़ंक्शन को सुरक्षित रूप से रिकर्सली कहा जा सकता है - वर्तमान आमंत्रण "होल्ड ऑन होल्ड" होता है जबकि दूसरा आमंत्रण चलता है, और फिर पहला आविष्कार अभी भी "इसे छोड़कर कहां उठा सकता है" चुनने में सक्षम है। (तकनीकी रूप से यह एक रिकर्सिव कॉल के कारण नहीं हो सकता है: पहला आविष्कार थ्रेड ए में हो सकता है, जो थ्रेड बी द्वारा मध्य में बाधित हो जाता है, जो दूसरा आविष्कार करता है। लेकिन यह परिदृश्य धागा का एक विशेष मामला है -सुरक्षा, इसलिए हम इसके बारे में इस अनुच्छेद में

    न तो printf है और न ही malloc भूल सकता है।) संभवतः हो सकता है किसी एकल थ्रेड द्वारा रिकर्सिवली कहा जाता है, क्योंकि वे पत्ती कार्य हैं (वे खुद को कॉल नहीं करते और न ही कॉल किसी भी उपयोगकर्ता द्वारा नियंत्रित कोड के लिए जो संभवतः एक रिकर्सिव कॉल कर सकता है)। और, जैसा कि हमने ऊपर देखा है, वे 2001 से * बहु-* थ्रेडेड पुनः-प्रवेश कॉल के खिलाफ थ्रेड-सुरक्षित रहे हैं (ताले का उपयोग करके)।

    तो, जो भी आपको बताता है कि printf और malloc गैर-पुनर्विक्रेता गलत था; उनका कहना है कि उनके दोनों कार्यक्रमों में शायद महत्वपूर्ण वर्ग होने की संभावना है - बाधाएं जहां एक ही समय में केवल एक धागा हो सकता है।


    पंडिताऊ ध्यान दें: glibc एक विस्तार है जिसके द्वारा printf ही फिर से बुला सहित, मनमाने ढंग से उपयोगकर्ता कोड कॉल करने के लिए बनाया जा सकता है प्रदान करता है। यह अपने सभी क्रमपरिवर्तनों में पूरी तरह से सुरक्षित है - कम से कम जहां तक ​​थ्रेड-सुरक्षा का संबंध है। (जाहिर है यह बिल्कुल पागल प्रारूप स्ट्रिंग कमजोरियों के लिए दरवाजा खुल जाता है।) दो वेरिएंट हैं: register_printf_function (जो दस्तावेज और यथोचित समझदार है, लेकिन आधिकारिक तौर पर "पदावनत") और register_printf_specifier (जो लगभग एक अतिरिक्त को छोड़ कर एक है अनियंत्रित पैरामीटर और total lack of user-facing documentation)। मैं उनमें से किसी एक की सिफारिश नहीं करता, और यहां केवल एक दिलचस्प तरफ के रूप में उनका उल्लेख करता हूं।

    #include <stdio.h> 
    #include <printf.h> // glibc extension 
    
    int widget(FILE *fp, const struct printf_info *info, const void *const *args) { 
        static int count = 5; 
        int w = *((const int *) args[0]); 
        printf("boo!"); // direct recursive call 
        return fprintf(fp, --count ? "<%W>" : "<%d>", w); // indirect recursive call 
    } 
    int widget_arginfo(const struct printf_info *info, size_t n, int *argtypes) { 
        argtypes[0] = PA_INT; 
        return 1; 
    } 
    int main() { 
        register_printf_function('W', widget, widget_arginfo); 
        printf("|%W|\n", 42); 
    } 
    
    +4

    यह उत्तर _almost_ सही है; लेकिन इसमें POSIX, _async सिग्नल सुरक्षा_ से एक महत्वपूर्ण अवधारणा गुम है। एसिंक्रोनस सिग्नल के परिणामस्वरूप या तो 'printf' या' malloc' पर कॉल के बीच में कोड कोड _can_ निष्पादित करें। न तो फ़ंक्शन को एसिंक-सिग्नल-सुरक्षित होने की आवश्यकता होती है, इसलिए एसिंक्रोनस सिग्नल_ के लिए हैंडलर से उन्हें कॉल करना असुरक्षित है। POSIX सिस्टम प्रोग्रामर का यही अर्थ है जब वे कहते हैं कि 'printf' और' malloc' "पुनर्वित्त नहीं हैं"। – zwol

    +0

    "तो, जो भी आपको बताता है कि printf और malloc गैर-पुनर्विक्रेता गलत था" सिग्नल हैंडलर के बारे में क्या? धागे के साथ मूल धागा अंततः सीपीयू को वापस ले जाएगा और जारी रहेगा लेकिन सिग्नल हैंडलर के साथ सिग्नल हैंडलर लौटने तक कुछ भी नहीं होता है ... –

    +0

    जैरी: जब मैंने यह जवाब दिया, तो मैं इस धारणा के तहत था कि "पुनर्वित्त" और "एसिंक-सिग्नल -साफ "समानार्थक नहीं थे, और वह malloc/printf" पुनर्वित्तक हैं लेकिन async-signal-safe नहीं "। दरअसल, मैं अभी भी उस छाप के नीचे हूं; लेकिन @ ज़्वोल की टिप्पणी से पता चलता है कि आबादी का एक महत्वपूर्ण हिस्सा है जो मानता है कि वे * समानार्थक हैं, और इस प्रकार कोई भी कार्य जो एसिंक-सिग्नल-सुरक्षित नहीं है, परिभाषा के अनुसार * संभावित रूप से * पुनर्वित्त नहीं हो सकता है। जैरी की तरह लगता है कि शिविर में भी है। मुझे लगता है कि नैतिक है: शब्दकोष का उपयोग करते समय, किसी को अपने दर्शकों और उनकी धारणाओं को जानना चाहिए। :) – Quuxplusone

    संबंधित मुद्दे