लघु संस्करण: calloc()
इसके बजाय हमेशा उपयोग करें malloc()+memset()
। ज्यादातर मामलों में, वे एक ही होंगे। कुछ मामलों में, calloc()
कम काम करेगा क्योंकि यह memset()
पूरी तरह से छोड़ सकता है । अन्य मामलों में, calloc()
धोखा भी दे सकते हैं और किसी भी स्मृति को आवंटित नहीं कर सकते हैं! हालांकि, malloc()+memset()
काम की पूरी राशि हमेशा करेंगे।
इसे समझने के लिए मेमोरी सिस्टम के छोटे दौरे की आवश्यकता होती है।
स्मृति का त्वरित दौरा
यहाँ चार मुख्य भाग हैं: आपका प्रोग्राम, मानक पुस्तकालय, कर्नेल और पेज टेबल। आप पहले से ही अपना कार्यक्रम जानते हैं, इसलिए ...
स्मृति आवंटनकर्ता पसंद करते हैं malloc()
और calloc()
अधिकतर छोटे आवंटन (कुछ भी 1 बाइट से 100 केबी तक) लेते हैं और उन्हें मेमोरी के बड़े पूल में समूहित करते हैं। उदाहरण के लिए, यदि आप 16 बाइट्स आवंटित करते हैं, तो malloc()
पहले अपने एक पूल से 16 बाइट्स प्राप्त करने का प्रयास करेंगे, और फिर पूल के सूखने पर कर्नेल से अधिक मेमोरी के लिए कहें। हालाँकि, चूंकि आप जिस प्रोग्राम के बारे में पूछ रहे हैं, वह एक बार में बड़ी मात्रा में मेमोरी के लिए आवंटित हो रहा है, malloc()
और calloc()
कर्नेल से सीधे उस मेमोरी के लिए पूछेगा। इस व्यवहार के लिए सीमा आपके सिस्टम पर निर्भर करती है, लेकिन मैंने 1 MiB को सीमा के रूप में उपयोग किया है।
कर्नेल प्रत्येक प्रक्रिया को वास्तविक रैम आवंटित करने और यह सुनिश्चित करने के लिए ज़िम्मेदार है कि प्रक्रियाएं अन्य प्रक्रियाओं की स्मृति में हस्तक्षेप नहीं करती हैं। इसे मेमोरी प्रोटेक्शन कहा जाता है , यह 1990 के दशक से आम गंदगी है, और यही कारण है कि एक कार्यक्रम पूरे सिस्टम को नीचे लाए बिना क्रैश कर सकता है। इसलिए जब किसी प्रोग्राम को अधिक मेमोरी की आवश्यकता होती है, तो यह सिर्फ मेमोरी नहीं ले सकता है, बल्कि यह कर्नेल से सिस्टम कॉल mmap()
या जैसे मेमोरी का उपयोग करता है sbrk()
। कर्नेल पृष्ठ तालिका को संशोधित करके प्रत्येक प्रक्रिया को RAM देगा।
पृष्ठ तालिका वास्तविक भौतिक RAM में मेमोरी पते को मैप करती है। आपकी प्रक्रिया के पते, 0x00000000 से 0xFFFFFFFF एक 32-बिट सिस्टम पर, वास्तविक मेमोरी नहीं हैं, बल्कि वर्चुअल मेमोरी में पते हैं । प्रोसेसर इन पतों को 4 KiB पेजों में विभाजित करता है, और प्रत्येक पेज को पेज टेबल को संशोधित करके भौतिक RAM के एक अलग टुकड़े को सौंपा जा सकता है। पृष्ठ तालिका को संशोधित करने के लिए केवल कर्नेल की अनुमति है।
यह कैसे काम नहीं करता है
यहाँ कैसे आवंटन 256 MiB करता है नहीं काम करते हैं:
आपका प्रोसेस कॉल करता है calloc()
और 256 MiB मांगता है।
मानक पुस्तकालय कॉल करता है mmap()
और 256 MiB के लिए पूछता है।
कर्नेल अनुपयोगी RAM के 256 MiB पाता है और पृष्ठ तालिका को संशोधित करके आपकी प्रक्रिया को देता है।
मानक पुस्तकालय रैम को शून्य करता है memset()
और इससे लौटता है calloc()
।
आपकी प्रक्रिया अंततः बाहर निकल जाती है, और कर्नेल रैम को पुनः प्राप्त करता है ताकि इसे किसी अन्य प्रक्रिया द्वारा उपयोग किया जा सके।
यह वास्तव में कैसे काम करता है
उपरोक्त प्रक्रिया काम करेगी, लेकिन यह इस तरह से नहीं होता है। तीन प्रमुख अंतर हैं।
जब आपकी प्रक्रिया को कर्नेल से नई मेमोरी मिलती है, तो संभवतः उस मेमोरी का उपयोग किसी अन्य प्रक्रिया द्वारा पहले किया गया था। यह एक सुरक्षा जोखिम है। क्या होगा अगर उस मेमोरी में पासवर्ड, एन्क्रिप्शन कुंजियाँ या गुप्त साल्सा व्यंजनों हैं? लीक से संवेदनशील डेटा रखने के लिए, कर्नेल हमेशा एक प्रक्रिया को देने से पहले मेमोरी को स्क्रब करता है। हम मेमोरी को शून्य करने के साथ ही स्क्रब भी कर सकते हैं, और यदि नई मेमोरी को शून्य किया जाता है तो हम इसकी गारंटी भी दे सकते हैं, इसलिए mmap()
गारंटी देता है कि यह जो नई मेमोरी देता है वह हमेशा शून्य है।
वहाँ बहुत सारे कार्यक्रम हैं जो स्मृति को आवंटित करते हैं लेकिन स्मृति का तुरंत उपयोग नहीं करते हैं। कुछ समय के लिए मेमोरी आवंटित की जाती है, लेकिन इसका उपयोग कभी नहीं किया जाता है। कर्नेल यह जानता है और आलसी है। जब आप नई मेमोरी आवंटित करते हैं, तो कर्नेल पृष्ठ तालिका को बिल्कुल भी स्पर्श नहीं करता है और आपकी प्रक्रिया को कोई RAM नहीं देता है। इसके बजाय, यह आपकी प्रक्रिया में कुछ पता स्थान पाता है, वहाँ जाने के लिए क्या माना जाता है का एक नोट बनाता है, और एक वादा करता है कि यह रैम को वहां रखेगा यदि आपका प्रोग्राम कभी भी इसका उपयोग करता है। जब आपका प्रोग्राम उन पतों से पढ़ने या लिखने की कोशिश करता है, तो प्रोसेसर पृष्ठ दोष और उन पतों पर रैम में कर्नेल चरणों को चलाता है और आपके प्रोग्राम को फिर से शुरू करता है। यदि आप मेमोरी का उपयोग कभी नहीं करते हैं, तो पेज फॉल्ट कभी नहीं होता है और आपके प्रोग्राम को वास्तव में रैम नहीं मिलती है।
कुछ प्रक्रियाएं मेमोरी को आवंटित करती हैं और फिर इसे संशोधित किए बिना इसे पढ़ा जाता है। इसका मतलब है कि विभिन्न प्रक्रियाओं में स्मृति में बहुत सारे पृष्ठ प्राचीन ज़ीरो से भरे हो सकते हैं mmap()
। चूँकि ये पृष्ठ सभी समान हैं, कर्नेल इन सभी आभासी पतों को जीरो से भरे हुए स्मृति के एक साझा 4 KiB पृष्ठ को इंगित करता है। यदि आप उस मेमोरी को लिखने की कोशिश करते हैं, तो प्रोसेसर आपको एक और पेज फाल्ट और कर्नेल स्टेप्स को ट्रिगर करता है, ताकि आप किसी और प्रोग्राम के साथ साझा नहीं किया गया ज़ीरो का एक ताज़ा पेज दे सकें।
अंतिम प्रक्रिया इस तरह दिखाई देती है:
आपका प्रोसेस कॉल करता है calloc()
और 256 MiB मांगता है।
मानक पुस्तकालय कॉल करता है mmap()
और 256 MiB के लिए पूछता है।
कर्नेल अप्रयुक्त पता स्थान के 256 MiB पाता है, इस बारे में एक नोट बनाता है कि अब उस पता स्थान का उपयोग किस लिए किया जाता है, और रिटर्न।
मानक पुस्तकालय जानता है कि का परिणाम mmap()
हमेशा शून्य से भर जाता है (या हो जाएगा एक बार यह वास्तव में कुछ रैम हो जाता है), तो यह स्मृति स्पर्श नहीं करता है, इसलिए कोई पृष्ठ दोष है, और राम अपनी प्रक्रिया को कभी नहीं दिया गया है ।
आपकी प्रक्रिया अंततः बाहर निकल जाती है, और कर्नेल को रैम को पुनः प्राप्त करने की आवश्यकता नहीं होती है क्योंकि यह पहली बार में आवंटित नहीं किया गया था।
यदि आप memset()
पृष्ठ को शून्य करने के लिए उपयोग करते हैं , memset()
तो पृष्ठ दोष को ट्रिगर किया जाएगा, रैम को आवंटित करने का कारण होगा, और फिर इसे शून्य होगा भले ही यह पहले से ही शून्य से भरा हो। यह अतिरिक्त काम की एक बड़ी मात्रा है, और बताते हैं कि क्यों और calloc()
से तेज है । तो अंत वैसे भी स्मृति का उपयोग कर, की तुलना में तेजी अब भी है और लेकिन अंतर काफी इतना हास्यास्पद नहीं है।malloc()
memset()
calloc()
malloc()
memset()
यह हमेशा काम नहीं करता है
सभी प्रणालियों में वर्चुअल मेमोरी नहीं होती है, इसलिए सभी सिस्टम इन अनुकूलन का उपयोग नहीं कर सकते हैं। यह बहुत पुराने प्रोसेसर पर लागू होता है जैसे 80286 और साथ ही एम्बेडेड प्रोसेसर जो कि परिष्कृत स्मृति प्रबंधन इकाई के लिए बहुत छोटा है।
यह भी हमेशा छोटे आवंटन के साथ काम नहीं करेगा। छोटे आवंटन के साथ, calloc()
कर्नेल पर सीधे जाने के बजाय एक साझा पूल से मेमोरी प्राप्त करता है। सामान्य तौर पर, साझा किए गए पूल में पुरानी मेमोरी से इसमें जमा किया गया डेटा हो सकता है जिसे इस्तेमाल किया गया था और इसके साथ मुक्त किया गया था free()
, इसलिए calloc()
उस मेमोरी को ले सकते हैं और memset()
इसे खाली करने के लिए कॉल कर सकते हैं। सामान्य कार्यान्वयन ट्रैक करेंगे कि साझा पूल के कौन से हिस्से प्राचीन हैं और फिर भी जीरो से भरे हुए हैं, लेकिन सभी कार्यान्वयन ऐसा नहीं करते हैं।
कुछ गलत उत्तरों को खारिज करना
ऑपरेटिंग सिस्टम के आधार पर, कर्नेल अपने खाली समय में मेमोरी को शून्य कर सकता है या नहीं कर सकता है, यदि आपको बाद में कुछ शून्य मेमोरी प्राप्त करने की आवश्यकता है। लिनक्स समय से पहले मेमोरी को शून्य नहीं करता है, और ड्रैगनफली बीएसडी ने हाल ही में इस सुविधा को अपने कर्नेल से हटा दिया है । कुछ अन्य गुठली समय से पहले शून्य स्मृति करते हैं, हालांकि। पृष्ठों को निष्क्रिय करना निष्क्रिय आइडल वैसे भी बड़े प्रदर्शन अंतरों की व्याख्या करने के लिए पर्याप्त नहीं है।
calloc()
समारोह की कुछ विशेष स्मृति गठबंधन संस्करण का उपयोग नहीं कर रहा है memset()
, और यह है कि यह बहुत तेजी से वैसे भी नहीं होगा। memset()
आधुनिक प्रोसेसर के लिए अधिकांश कार्यान्वयन इस तरह दिखते हैं:
function memset(dest, c, len)
// one byte at a time, until the dest is aligned...
while (len > 0 && ((unsigned int)dest & 15))
*dest++ = c
len -= 1
// now write big chunks at a time (processor-specific)...
// block size might not be 16, it's just pseudocode
while (len >= 16)
// some optimized vector code goes here
// glibc uses SSE2 when available
dest += 16
len -= 16
// the end is not aligned, so one byte at a time
while (len > 0)
*dest++ = c
len -= 1
तो आप देख सकते हैं, memset()
बहुत तेज़ है और आप वास्तव में मेमोरी के बड़े ब्लॉक के लिए कुछ भी बेहतर नहीं कर सकते हैं ।
तथ्य यह memset()
है कि शून्य मेमोरी जो पहले से ही शून्य है, इसका मतलब यह है कि मेमोरी दो बार शून्य हो जाती है, लेकिन यह केवल 2x प्रदर्शन अंतर की व्याख्या करता है। यहां प्रदर्शन का अंतर बहुत बड़ा है (मैंने अपनी प्रणाली के बीच परिमाण के तीन से अधिक क्रमों को मापा malloc()+memset()
और calloc()
)।
पार्टी की चाल
10 बार लूपिंग करने के बजाय, एक प्रोग्राम लिखें जो मेमोरी को तब तक आवंटित करता है जब तक कि malloc()
या calloc()
NULL वापस नहीं करता है।
यदि आप जोड़ते हैं तो क्या होता है memset()
?