Windows मेमोरी की गहराई (भाग 3) — सेक्शन ऑब्जेक्ट और कॉपी-ऑन-राइट: DLL और फ़ाइल मैपिंग वास्तव में क्या हैं

· · Windows, मेमोरी प्रबंधन, साझा मेमोरी, फ़ाइल मैपिंग, कॉपी-ऑन-राइट, DLL, Cache Manager

पिछले लेख «Windows मेमोरी की गहराई (भाग 2) — एक भौतिक पृष्ठ का जीवन» में हमने देखा कि Working Set छोड़ने वाला भौतिक पृष्ठ Modified, Standby, Free और Zeroed से कैसे गुज़रता है। उसी Standby में DLL, EXE, मैप की गई फ़ाइलें और फ़ाइल कैश के पृष्ठ भी रहते हैं।

यहाँ एक प्रश्न उठता है। जब 100 प्रोसेस एक ही kernel32.dll इस्तेमाल करते हैं, क्या Windows RAM में कोड पृष्ठों के 100 सेट रखता है? जब दो प्रोसेस एक ही फ़ाइल को मेमोरी-मैप करते हैं, क्या दूसरा उस पृष्ठ का उपयोग कर सकता है जिसे एक ने पढ़ा?

उत्तर है कई वर्चुअल पतों को एक ही भौतिक पृष्ठ पर मैप करना। उस साझा इकाई का केंद्र सेक्शन ऑब्जेक्ट है, और लिखने के समय ही साझेदारी तोड़ने वाला तंत्र कॉपी-ऑन-राइट (Copy-on-Write, CoW) है।

यह लेख देखता है कि EXE और DLL, डेटा फ़ाइलें, पेजफ़ाइल-समर्थित साझा मेमोरी और फ़ाइल कैश एक ही फ़ाइल स्ट्रीम से कैसे जुड़ते हैं, और प्रसंस्करण पथ कहाँ बँटते हैं। संख्याएँ पढ़ना स्वयं परिचयात्मक लेख «What Does Windows’ “Memory Usage” Actually Mean?» को आधार मानता है।

«Windows मेमोरी की गहराई» — तीनों भाग

  1. भाग 1: वर्चुअल पते और पेज फ़ॉल्ट
    हम उस क्षण का अनुसरण करते हैं जब Commit किया गया वर्चुअल पृष्ठ भौतिक RAM पाता है।
  2. भाग 2: एक भौतिक पृष्ठ का जीवन
    हम Working Set छोड़ने वाले पृष्ठ की अवस्था-परिवर्तनों का अनुसरण करते हैं।
  3. भाग 3 (यह लेख): सेक्शन ऑब्जेक्ट और कॉपी-ऑन-राइट
    हम उस तंत्र का अनुसरण करते हैं जिससे DLL, फ़ाइल मैपिंग और साझा मेमोरी भौतिक पृष्ठ साझा करते हैं।

भाग 3 जिस प्रश्न का उत्तर देता है वह केवल एक है।

कई प्रोसेस एक ही DLL या फ़ाइल को भौतिक पृष्ठों के एक सेट के रूप में क्यों इस्तेमाल कर सकते हैं?

लक्षित पाठक वे डेवलपर और संचालक हैं जो DLL साझेदारी, CreateFileMapping, MapViewOfFile, साझा मेमोरी, CoW और फ़ाइल कैश से संबंध को केवल API उपयोग नहीं बल्कि आंतरिक संरचना से समझना चाहते हैं। पूर्वापेक्षाएँ Windows 10/11 या वर्तमान Windows Server हैं, और आवश्यक पृष्ठभूमि वर्चुअल पते, पेज फ़ॉल्ट और Working Set की बुनियाद है। कठिनाई मध्यम है; हम Control Area और Prototype PTE जैसे आंतरिक शब्द भी इस्तेमाल करते हैं, लेकिन अवलोकन VMMap, Process Explorer और QueryWorkingSetEx से दोहराए जा सकते हैं।

1. पहले निष्कर्ष

शुरुआत में पूरी तस्वीर।

  • सेक्शन ऑब्जेक्ट साझा करने योग्य मेमोरी रेंज दर्शाता है।
    हर प्रोसेस उस सेक्शन का भाग अपने वर्चुअल स्पेस में «व्यू» के रूप में मैप करता है।1
  • एक ही सेक्शन के व्यू एक ही वर्चुअल पते पर होने आवश्यक नहीं।
    प्रोसेस A का 0x000001... और प्रोसेस B का 0x000002... एक ही सेक्शन ऑफ़सेट और एक ही भौतिक पृष्ठ की ओर इशारा कर सकते हैं।2
  • फ़ाइल-समर्थित और पेजफ़ाइल-समर्थित सेक्शन होते हैं।
    पहले वास्तविक फ़ाइल इस्तेमाल करते हैं; दूसरे स्पष्ट फ़ाइल के बिना साझा मेमोरी आदि के लिए।2
  • EXE/DLL लोड करना इमेज सेक्शन है; साधारण फ़ाइल मैपिंग डेटा सेक्शन है।
    SEC_IMAGE के साथ PE के अंदर सेक्शन विशेषताएँ पृष्ठ सुरक्षा तय करती हैं।3
  • पढ़ते समय एक ही भौतिक पृष्ठ साझा हो सकता है।
    जब एक पक्ष CoW पृष्ठ पर लिखता है, केवल वही पृष्ठ कॉपी होता है और लिखने वाले प्रोसेस का PTE बदल जाता है।4
  • एक ही फ़ाइल स्ट्रीम पर भी कैश, डेटा और इमेज पथ अलग होते हैं।
    Cache Manager का कैश्ड I/O SharedCacheMap इस्तेमाल करता है, डेटा मैपिंग DataSectionObject, और EXE/DLL ImageSectionObject। तीनों SECTION_OBJECT_POINTERS से एक ही स्ट्रीम से जुड़ते हैं, लेकिन इमेज फ़ॉल्ट Cache Manager से नहीं जाता।5
  • अकेले Private Bytes यह पुष्टि नहीं कर सकते कि CoW हुआ।
    FILE_MAP_COPY मैप के समय पूरे व्यू का Commit पहले ही लगाता है, इस आशंका से कि बाद में हर पृष्ठ निजी हो जाए। प्रति-पृष्ठ जाँच के लिए QueryWorkingSetEx का Shared बिट इस्तेमाल करें।67

एक वाक्य में: साझा वर्चुअल पता नहीं, बल्कि सेक्शन के अंदर की सामग्री और उस क्षण का संगत भौतिक पृष्ठ है

2. सेक्शन ऑब्जेक्ट और व्यू

Microsoft की परिभाषा में सेक्शन ऑब्जेक्ट साझा करने योग्य मेमोरी क्षेत्र दर्शाता है और वह तंत्र भी है जो फ़ाइल को प्रोसेस के एड्रेस स्पेस में मैप करता है।1

समझने की कुंजी सेक्शन को उस व्यू से अलग करना है जो हर प्रोसेस देखता है।

अवधारणा भूमिका
सेक्शन ऑब्जेक्ट साझा करने की सामग्री, आकार, बैकिंग स्टोर और सुरक्षा की ऊपरी सीमा दर्शाता है
व्यू सेक्शन का भाग किसी प्रोसेस में वर्चुअल पते की रेंज के रूप में दिखाता है
PTE व्यू के हर वर्चुअल पृष्ठ को वर्तमान भौतिक पृष्ठ या अवास्तविक अवस्था से बाँधता है
PFN वह भौतिक पृष्ठ दर्शाता है जो वास्तव में RAM में मौजूद है

Win32 में CreateFileMapping फ़ाइल-मैपिंग ऑब्जेक्ट का हैंडल लौटाता है, और MapViewOfFile प्रोसेस के वर्चुअल स्पेस में व्यू बनाता है।3

HANDLE mapping = CreateFileMappingW(
    file,
    nullptr,
    PAGE_READONLY,
    0,
    0,
    nullptr);

void* view = MapViewOfFile(
    mapping,
    FILE_MAP_READ,
    0,
    0,
    0);

केवल CreateFileMapping बुलाने से प्रोसेस को पढ़ने योग्य पता अभी नहीं मिलता। व्यू बनाना भी हर पृष्ठ तुरंत RAM में नहीं डालता। पहले छुए गए पृष्ठ से पेज फ़ॉल्ट फ़ाइल सामग्री पढ़ता है और PTE से भौतिक पृष्ठ बाँधता है।8 अर्थात् भाग 1 का डिमांड पेजिंग सेक्शन व्यू पर भी लागू होता है।

2.1. एक ही सेक्शन, अलग वर्चुअल पते

भले प्रोसेस A और प्रोसेस B एक ही सेक्शन का एक ही ऑफ़सेट मैप करें, व्यू का आरंभ पता भिन्न हो सकता है।

अलग वर्चुअल पतों को एक ही भौतिक पृष्ठ पर मैप करनाप्रोसेस A और प्रोसेस B प्रत्येक के पास अलग वर्चुअल पते पर व्यू है, लेकिन वे एक ही सेक्शन ऑफ़सेट से एक ही भौतिक पृष्ठ PFN X तक पहुँचते हैंप्रोसेस A: 0x000001A00000 + 0x3000सेक्शन ऑफ़सेट 0x3000प्रोसेस B: 0x000002700000 + 0x3000एक ही भौतिक पृष्ठ PFN X

चित्र 1: साझा सेक्शन के अंदर की सामग्री और भौतिक पृष्ठ है, वर्चुअल पता नहीं।

इसीलिए साझा मेमोरी में कच्चा पॉइंटर न रखें। प्रोसेस A का पॉइंटर मान प्रोसेस B में असंबंधित पता हो सकता है।

साझा संरचना में व्यू के आरंभ से ऑफ़सेट, स्थिर-चौड़ाई पूर्णांक, तथा स्पष्ट संस्करण और संरेखण इस्तेमाल करें। Microsoft का MapViewOfFileEx दस्तावेज़ भी पॉइंटर के बजाय बेस से ऑफ़सेट रखने की सलाह देता है, क्योंकि भविष्य में वही पता उपलब्ध रहे इसकी गारंटी नहीं।6

3. फ़ाइल-समर्थित और पेजफ़ाइल-समर्थित

सेक्शन इस आधार पर दो बड़े प्रकारों में बँटते हैं कि सामग्री कहाँ से पुनर्स्थापित हो सकती है।

फ़ाइल-समर्थित और पेजफ़ाइल-समर्थित कैसे बँटते हैंCreateFileMapping को वास्तविक फ़ाइल देने से फ़ाइल-समर्थित सेक्शन बनता है, और स्वच्छ पृष्ठ मूल फ़ाइल से फिर पढ़ा जा सकता है। INVALID_HANDLE_VALUE देने से पेजफ़ाइल-समर्थित सेक्शन बनता है; पेज फ़ाइल सामग्री थामती है, जो ऑब्जेक्ट नष्ट होने पर गायब हो जाती हैवास्तविक फ़ाइल हैंडल देंINVALID_HANDLE_VALUE देंCreateFileMappingफ़ाइल-समर्थित सेक्शनपेजफ़ाइल-समर्थित सेक्शनस्वच्छ पृष्ठ मूल फ़ाइल से फिर पढ़ा जा सकता हैपेज फ़ाइल सामग्री थामती है, जो नष्ट होने पर गायब होती है

चित्र 2: बैकिंग स्टोर का अंतर तय करता है कि सामग्री कहाँ से लौटे और कितनी देर जिए।

3.1. फ़ाइल-समर्थित सेक्शन

CreateFileMapping को वास्तविक फ़ाइल देने से फ़ाइल-समर्थित सेक्शन बनता है।

  • केवल-पठन व्यू आवश्यक पृष्ठ फ़ाइल से पढ़ता है।
  • पढ़ने/लिखने वाले व्यू के बदलाव उस फ़ाइल के डेटा माने जाते हैं।
  • CoW व्यू के बदलाव मूल फ़ाइल में नहीं लिखे जाते; वे निजी पृष्ठ बन जाते हैं।

यदि फ़ाइल-समर्थित पृष्ठ स्वच्छ है, भौतिक पृष्ठ त्यागा जा सकता है और मूल फ़ाइल से फिर पढ़ा जा सकता है। यही गुण भाग 2 में देखी Standby और फ़ाइल कैश की दक्षता थामता है।

3.2. पेजफ़ाइल-समर्थित सेक्शन

CreateFileMapping के hFile में INVALID_HANDLE_VALUE देना और आकार बताना पेजफ़ाइल-समर्थित सेक्शन बनाता है।

HANDLE mapping = CreateFileMappingW(
    INVALID_HANDLE_VALUE,
    nullptr,
    PAGE_READWRITE,
    0,
    64 * 1024,
    L"Local\\KomuraMemoryDemo");

यह स्पष्ट डेटा फ़ाइल के बिना सेक्शन है, जिसे पेज फ़ाइल थामती है। आरंभिक सामग्री शून्य है, और कई प्रोसेस नाम, हैंडल विरासत, DuplicateHandle आदि से एक ही ऑब्जेक्ट खोल सकते हैं।93 बदलाव उसी साझा पृष्ठ को मैप करने वाले प्रोसेस को दिखते हैं। दूसरी ओर, सेक्शन ऑब्जेक्ट नष्ट होने पर सामग्री नहीं रहती, इसलिए स्थायी फ़ाइल छोड़ने के लिए उपयुक्त नहीं।2

सावधानी: साझा मेमोरी अपने आप पारस्परिक बहिष्कार नहीं लाती। म्यूटेक्स, सेमाफोर, इवेंट, लॉक-फ़्री प्रोटोकॉल आदि अलग से डिज़ाइन करें।9

4. इमेज मैपिंग और डेटा मैपिंग

जब हम कहते हैं «EXE या DLL भी फ़ाइल मैपिंग है», साधारण डेटा फ़ाइल से अंतर फिर भी रखना चाहिए।

मद इमेज मैपिंग डेटा मैपिंग
मुख्य उपयोग EXE या DLL लोड करना साधारण फ़ाइलें, साझा डेटा
निर्माण विशेषता SEC_IMAGE PAGE_READONLY, PAGE_READWRITE आदि
पृष्ठ सुरक्षा PE इमेज के अंदर विशेषताएँ तय करती हैं मैपिंग और व्यू विनिर्देश तय करते हैं
लेखन लिखने योग्य सेक्शन या CoW से निजी किया जा सकता है साझा लेखन या CoW चुना जा सकता है
VirtualQuery Type MEM_IMAGE MEM_MAPPED

SEC_IMAGE के साथ व्यू की पृष्ठ सुरक्षा मुख्यतः निष्पादित इमेज की अपनी सेक्शन विशेषताएँ तय करती हैं, न कि CreateFileMapping को दिए साधारण सुरक्षा मान।3

इस तंत्र से कोड जैसे अपरिवर्तित पृष्ठ कई प्रोसेस में एक ही भौतिक पृष्ठ साझा कर सकते हैं, और केवल प्रोसेस-निजी बदलाव वाले पृष्ठ CoW से शाखा लेते हैं। फिर भी हर DLL पृष्ठ आवश्यक रूप से साझा नहीं होता — ASLR पुनर्स्थापन, लोडर सुधार, हॉटपैच, वास्तविक PE सेक्शन विशेषताएँ आदि के कारण।

महत्वपूर्ण डिज़ाइन है पहले साझा करने योग्य पृष्ठ साझा करें, और केवल बदलाव वाले पृष्ठ देर से कॉपी करें

5. कॉपी-ऑन-राइट शुरू से अंत तक

दो प्रोसेस एक ही CoW पृष्ठ पढ़ रहे हों, उस अवस्था से प्रोसेस A के एक बाइट लिखने तक चलें।

5.1. लिखने से पहले

लिखत होने से पहले दोनों प्रोसेस के PTE अवधारणात्मक रूप से एक ही साझा पृष्ठ तक पहुँचते हैं, और पठन वैसे ही सफल होता है।

कॉपी-ऑन-राइट से पहले की साझा अवस्थालिखत होने से पहले प्रोसेस A का PTE और प्रोसेस B का PTE दोनों एक ही साझा पृष्ठ PFN X तक पहुँचते हैं, और पठन वैसे ही सफल होता हैप्रोसेस A PTEसाझा PFN X(पठन / कॉपी-ऑन-राइट)प्रोसेस B PTE

चित्र 3: लिखने से पहले दोनों प्रोसेस के PTE एक ही भौतिक पृष्ठ की ओर इशारा करते हैं।

5.2. लिखते समय सुरक्षा फ़ॉल्ट

CoW पृष्ठ शुरू से साधारण साझा लिखने योग्य पृष्ठ नहीं होता। जब प्रोसेस A लिखने का प्रयास करता है, CPU सुरक्षा फ़ॉल्ट उठाता है। नियंत्रण पाने वाला मेमोरी मैनेजर तय करता है कि यह अवैध लिखत नहीं बल्कि CoW विशेषता पर लिखत है।

5.3. नया भौतिक पृष्ठ बनाना

उस निर्णय पर Windows यह करता है।

  1. प्रोसेस A के लिए एक भौतिक पृष्ठ प्राप्त करें।
  2. PFN X की सामग्री नए पृष्ठ PFN Y पर कॉपी करें।
  3. प्रोसेस A का PTE PFN Y से बदलें।
  4. प्रोसेस A की सुरक्षा साधारण पढ़ना/लिखना करें।
  5. असफल लिखने के निर्देश को फिर चलाएँ।
कॉपी-ऑन-राइट के बाद की विभाजित अवस्थाप्रोसेस A की लिखत के बाद केवल प्रोसेस A का PTE उस निजी पृष्ठ PFN Y से बदला जाता है जिसे सामग्री की कॉपी मिली, जबकि प्रोसेस B का PTE मूल साझा पृष्ठ PFN X की ओर इशारा करता रहता हैलिखते समय कॉपीप्रोसेस A PTEनिजी PFN Y(R/W, लिखने के बाद)प्रोसेस B PTEसाझा PFN X(मूल)

चित्र 4: केवल लिखने वाले प्रोसेस का PTE नए निजी पृष्ठ से बदला जाता है; दूसरा पक्ष मूल सामग्री पढ़ता रहता है।

प्रोसेस B मूल सामग्री पढ़ता रहता है और प्रोसेस A का बदलाव नहीं देखता। यही Copy-on-Write है। DLL साझेदारी और FILE_MAP_COPY एक ही सिद्धांत इस्तेमाल करते हैं: जब तक लिखें नहीं, कॉपी न करें46

5.4. FILE_MAP_WRITE से अंतर

FILE_MAP_WRITE की साझा लिखत से लिखा पृष्ठ इस तरह डिज़ाइन है कि एक पक्ष का बदलाव उसी फ़ाइल मैपिंग वाले दूसरे व्यू से भी दिखे। FILE_MAP_COPY में इसके विपरीत केवल लिखे गए पृष्ठ प्रोसेस-निजी होते हैं; बदलाव मूल फ़ाइल में वापस नहीं लिखे जाते और व्यू अनमैप होते ही खो जाते हैं।6

क्या आप चाहते हैं «साझा मेमोरी से अद्यतन पहुँचाना», या «हर प्रोसेस साझा आरंभिक डेटा से निजी बदलाव करे»? उद्देश्य के अनुसार सही चुनाव उलटा होता है।

6. CoW के बाद भी MEM_MAPPED / MEM_IMAGE

CoW के बाद पृष्ठ भौतिक रूप से Private हो चुका है। तब लगता है VirtualQuery का Type भी MEM_PRIVATE हो जाएगा, लेकिन वास्तव में डेटा व्यू MEM_MAPPED और निष्पादन योग्य इमेज MEM_IMAGE रहता है। VirtualQuery बताता है कि क्षेत्र किस आरंभिक आवंटन से आया।7

प्रति पृष्ठ यह देखने के लिए कि CoW पहले ही हुआ या नहीं, यह प्रक्रिया इस्तेमाल करें।

  1. लक्ष्य पृष्ठ तक पहुँचें और उसे रेज़िडेंट बनाएँ।
  2. QueryWorkingSetEx से पृष्ठ की Working Set जानकारी लें।
  3. Shared बिट देखें।
  4. यदि Shared == 0 तो वह रेज़िडेंट पृष्ठ Private है।

VMMap में पुष्टि करते समय भी केवल क्षेत्र के Type नहीं, Working Set के Private/Shareable विभाजन देखें।

6.1. Private Bytes बढ़ें नहीं भी सकते

FILE_MAP_COPY में प्रोसेस बाद में व्यू के हर पृष्ठ पर लिख सकता है। इसलिए Windows मैप के समय पूरे व्यू के बराबर Commit शुल्क लेता है।6 परिणामस्वरूप पहला पृष्ठ लिखने पर उसी क्षण Private Bytes 4KiB नहीं बढ़ सकते।

CoW देखते समय ये मेट्रिक प्राथमिकता दें।

  • QueryWorkingSetEx का Shared बिट
  • VMMap का Private WS / Shareable WS
  • RAMMap की भौतिक-पृष्ठ जानकारी
  • पूरक जानकारी के रूप में Private Bytes

यदि «लिखने के क्षण Private Bytes बढ़े या नहीं» को उत्तीर्ण/अनुत्तीर्ण परीक्षा मानें, तो सही काम कर रहे CoW छूट जाएँगे।

7. Cache Manager से संपर्क — तीन पथ अलग रखें

«EXE/DLL लोड, फ़ाइल कैश और साझा मेमोरी सब सेक्शन हैं» कहना पूरी तस्वीर देता है, लेकिन कार्यान्वयन को एक ऑब्जेक्ट में न समेटें।

फ़ाइल स्ट्रीम के पास SECTION_OBJECT_POINTERS होते हैं, जिन्हें मेमोरी मैनेजर और Cache Manager इस्तेमाल करते हैं।

typedef struct _SECTION_OBJECT_POINTERS {
    PVOID DataSectionObject;
    PVOID SharedCacheMap;
    PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS;
  • DataSectionObject: डेटा फ़ाइल की सेक्शन अवस्था
  • SharedCacheMap: Cache Manager द्वारा ट्रैक किया गया कैश व्यू
  • ImageSectionObject: निष्पादन योग्य इमेज की सेक्शन अवस्था

Microsoft दस्तावेज़ बताता है कि यह संरचना फ़ाइल ऑब्जेक्ट को फ़ाइल स्ट्रीम के सेक्शन से बाँधती है और मेमोरी में सामग्री तथा कैश जानकारी ट्रैक करती है।5

यहाँ I/O श्रृंखला और मेमोरी श्रृंखला मिलती हैं। फिर भी तीन पथ अलग समझें।

  • कैश्ड ReadFile / WriteFile Cache Manager के SharedCacheMap और कैश व्यू इस्तेमाल करते हैं।
  • डेटा फ़ाइल पर मैपिंग फ़ॉल्ट मेमोरी मैनेजर DataSectionObject पक्ष पर संभालता है। वह उसी स्ट्रीम के कैश्ड I/O से सहयोग कर सामग्री सुसंगत रखता है।
  • EXE/DLL पर इमेज फ़ॉल्ट मेमोरी मैनेजर ImageSectionObject और पेजिंग I/O से संभालता है। यह Cache Manager के SharedCacheMap से जाने वाला पथ नहीं।
एक ही फ़ाइल स्ट्रीम से जुड़ने वाले तीन पथकैश्ड ReadFile/WriteFile SharedCacheMap इस्तेमाल करते हैं, डेटा-मैपिंग फ़ॉल्ट DataSectionObject, और EXE/DLL इमेज फ़ॉल्ट ImageSectionObject; तीनों SECTION_OBJECT_POINTERS से एक ही फ़ाइल स्ट्रीम से जुड़ते हैंकैश्ड ReadFile / WriteFileSharedCacheMapडेटा-मैपिंग फ़ॉल्टDataSectionObjectEXE/DLL इमेज फ़ॉल्टImageSectionObjectएक ही फ़ाइल स्ट्रीम(SECTION_OBJECT_POINTERS)

चित्र 5: तीन पथ अलग संसाधित होते हैं, लेकिन एक ही फ़ाइल स्ट्रीम से जुड़ते हैं।

तीनों में समान यह नहीं कि «सब Cache Manager में जाता है», बल्कि यह कि एक ही फ़ाइल स्ट्रीम कैश, डेटा सेक्शन और इमेज सेक्शन जैसी अलग अवस्थाएँ SECTION_OBJECT_POINTERS से बाँधती है।5 कैश पठन-लेखन, Lazy Writer, तथा Cc और Mm का संबंध «The Depths of Windows I/O (Part 4) — Cache Manager: When Does Your WriteFile Actually Reach the Disk?» में है।

ध्यान दें कि मेमोरी-मैप व्यू को ReadFile/WriteFile से मिलाएँ तो हमेशा एक ही क्षण की सामग्री दिखने की गारंटी नहीं। डिज़ाइन में सिंक्रनाइज़ेशन, फ्लश और फ़ाइल-साझा मोड होना चाहिए।36

8. ऑब्जेक्ट और व्यू का जीवनकाल

केवल CreateFileMapping हैंडल बंद करने से मौजूदा व्यू नष्ट नहीं होता। व्यू सेक्शन का आंतरिक संदर्भ रखता है, और हर व्यू के UnmapViewOfFile तथा हर हैंडल के CloseHandle के बाद ही ऑब्जेक्ट नष्ट योग्य होता है।3

UnmapViewOfFile(view);
CloseHandle(mapping);
CloseHandle(file);

जीवनकाल का यह अलगाव «फ़ाइल बंद कर दी, फिर भी उपयोग में है» घटना का कारण है। फ़ाइल हैंडल बंद करने के बाद भी यदि इमेज सेक्शन या डेटा व्यू फ़ाइल स्ट्रीम का संदर्भ रखे, फ़ाइल का अंतिम बंद बाद में आता है।

I/O पक्ष की सफ़ाई/बंद से संबंध «The Depths of Windows I/O (Part 1)» में है, और कार्यान्वयन जाल «Shared Memory Pitfalls and Practical Best Practices» में।

9. स्वयं देखें

9.1. दो प्रोसेस से एक ही DLL देखना

पहले मौजूदा प्रोसेस से DLL साझेदारी पुष्टि करें।

  1. व्यवस्थापक के रूप में Process Explorer शुरू करें।
  2. दो cmd.exe प्रोसेस शुरू करें।
  3. View > Lower Pane View > DLLs चुनें।
  4. दोनों प्रोसेस में एक ही DLL का पथ और मैपिंग पुष्टि करें।
  5. VMMap में प्रत्येक cmd.exe खोलें और Images Working Set, Private तथा Shareable तुलना करें।

Process Explorer में एक ही DLL दिखना प्रमाण है कि दोनों ने एक ही इमेज मैप की। अकेले इससे यह सिद्ध नहीं होता कि हर पृष्ठ का PFN मेल खाता है। प्रति-पृष्ठ साझेदारी पुष्टि के लिए VMMap का Shareable विभाजन, RAMMap और QueryWorkingSetEx मिलाएँ। Process Explorer और VMMap Sysinternals देते हैं।1011

9.2. दो प्रोसेस से FILE_MAP_COPY देखना

निम्न प्रोग्राम एक ही फ़ाइल को CoW व्यू के रूप में मैप करता है और QueryWorkingSetEx का Shared बिट दिखाता है। फ़ाइल-मैपिंग ऑब्जेक्ट PAGE_READONLY से बनता है, लेकिन वह सुरक्षा FILE_MAP_COPY व्यू से संगत है, और व्यू पक्ष की पहली लिखत CoW पैदा करती है।3

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.h>

#include <cstdio>
#include <cwchar>

#pragma comment(lib, "Psapi.lib")

void PrintPage(const char* stage, void* address)
{
    MEMORY_BASIC_INFORMATION mbi{};
    if (!VirtualQuery(address, &mbi, sizeof(mbi))) {
        std::printf("VirtualQuery failed: %lu\n", GetLastError());
        return;
    }

    for (int attempt = 0; attempt < 3; ++attempt) {
        // The page may have been trimmed while the user was waiting.
        // Touch it immediately before querying the working-set attributes.
        volatile unsigned char resident =
            *static_cast<volatile unsigned char*>(address);
        (void)resident;

        PSAPI_WORKING_SET_EX_INFORMATION ws{};
        ws.VirtualAddress = address;
        if (!QueryWorkingSetEx(GetCurrentProcess(), &ws, sizeof(ws))) {
            std::printf("QueryWorkingSetEx failed: %lu\n", GetLastError());
            return;
        }

        if (!ws.VirtualAttributes.Valid) {
            Sleep(0);
            continue;
        }

        std::printf(
            "%s: Type=0x%lx Valid=1 Shared=%llu ShareCount=%llu\n",
            stage,
            static_cast<unsigned long>(mbi.Type),
            static_cast<unsigned long long>(ws.VirtualAttributes.Shared),
            static_cast<unsigned long long>(ws.VirtualAttributes.ShareCount));
        return;
    }

    std::printf(
        "%s: page is not resident; Shared/ShareCount were not interpreted\n",
        stage);
}

int wmain(int argc, wchar_t** argv)
{
    if (argc != 3) {
        std::fwprintf(stderr, L"usage: cow_demo <file> <read|write>\n");
        return 2;
    }

    HANDLE file = CreateFileW(
        argv[1], GENERIC_READ, FILE_SHARE_READ,
        nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (file == INVALID_HANDLE_VALUE) return 3;

    HANDLE mapping = CreateFileMappingW(
        file, nullptr, PAGE_READONLY, 0, 0, nullptr);
    if (!mapping) {
        CloseHandle(file);
        return 4;
    }

    auto* view = static_cast<unsigned char*>(
        MapViewOfFile(mapping, FILE_MAP_COPY, 0, 0, 0));
    if (!view) {
        CloseHandle(mapping);
        CloseHandle(file);
        return 5;
    }

    volatile unsigned char value = view[0];
    (void)value;

    std::puts("Start the other process. When both are waiting, press Enter...");
    (void)std::getchar();
    PrintPage("before", view);

    if (std::wcscmp(argv[2], L"write") == 0) {
        std::puts("Press Enter to trigger copy-on-write...");
        (void)std::getchar();
        view[0] ^= 0x5a;
        PrintPage("after write", view);
    } else {
        std::puts("After the writer changes its page, press Enter...");
        (void)std::getchar();
        PrintPage("reader after peer write", view);
    }

    std::puts("Press Enter to exit...");
    (void)std::getchar();

    UnmapViewOfFile(view);
    CloseHandle(mapping);
    CloseHandle(file);
}

बनाएँ और तैयार करें।

cl /std:c++20 /EHsc /W4 cow_demo.cpp

$path = "$env:TEMP\\cow-demo.bin"
[IO.File]::WriteAllBytes($path, [byte[]]::new(65536))

फिर दो कंसोल से एक ही फ़ाइल खोलें।

.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" read
.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" write

दोनों शुरू करने के बाद पहले पठन पक्ष फिर लेखन पक्ष पर Enter दबाएँ, और पुष्टि करें कि दोनों के before में Shared सेट है। लेखन पक्ष पर फिर Enter दबाएँ तो उस प्रोसेस का पृष्ठ Shared 0 हो जाता है। फिर पठन पक्ष पर Enter दबाएँ और पुष्टि करें कि पाठक मूल पृष्ठ पढ़ता रहता है। लिखने के बाद भी VirtualQuery का Type MEM_MAPPED रहता है।

ध्यान दें कि PrintPage पूछने से ठीक पहले लक्ष्य पृष्ठ को फिर छूता है, और Valid == 0 हो तो Shared तथा ShareCount की व्याख्या नहीं करता और तीन बार तक पुनः प्रयास करता है। यदि पृष्ठ अभी भी रेज़िडेंट नहीं तो परिणाम नहीं देता और यह बताता है। ShareCount समय और मेमोरी दबाव से बदल सकता है, इसलिए Valid == 1 पर पुष्ट Shared बिट का बदलाव देखें, कोई स्थिर मान नहीं।

10. व्यवहार में बचने योग्य पाँच गलत पाठ

10.1. «साझा मेमोरी एक ही वर्चुअल पते पर पहुँचती है»

साझा सेक्शन और भौतिक पृष्ठ है। व्यू का वर्चुअल पता प्रोसेस के अनुसार भिन्न हो सकता है, इसलिए कच्चे पॉइंटर के बजाय ऑफ़सेट रखें।

10.2. «यदि वही DLL है तो हर पृष्ठ आवश्यक रूप से साझा है»

स्वच्छ कोड पृष्ठ साझा करना आसान है, जबकि पुनर्स्थापन, लिखने योग्य सेक्शन, CoW और माप के क्षण की रेज़िडेंसी भी Private पृष्ठ पैदा करते हैं।

10.3. «CoW के बाद यह MEM_PRIVATE हो जाता है»

VirtualQuery का Type MEM_MAPPED या MEM_IMAGE रहता है। वास्तविक साझेदारी QueryWorkingSetEx से पुष्टि करें।7

10.4. «यदि Private Bytes नहीं बढ़े तो CoW नहीं हुआ»

FILE_MAP_COPY पूरे व्यू का Commit पहले लगाता है। Private WS और Shared बिट प्राथमिकता दें।6

10.5. «यदि पृष्ठ साझा है तो सिंक्रनाइज़ेशन अनावश्यक है»

एक ही भौतिक पृष्ठ देखना और कई CPU कोर से सुरक्षित अद्यतन करना अलग समस्याएँ हैं। परमाणुकता, मेमोरी क्रम, पारस्परिक बहिष्कार, क्रैश के समय मध्य अवस्था, और संस्करण संगतता डिज़ाइन करें।

संदर्भ और जीवनकाल को अलग-अलग अनुसरण करने का विचार Excel COM इंटरऑप के बाद बचे प्रोसेस की समस्या पर भी लागू होता है। देखें «Why EXCEL.EXE Processes Remain After C# Excel COM Automation — Reference Release Patterns and the Replacement Decision»।

11. सार

  • सेक्शन ऑब्जेक्ट साझा करने योग्य मेमोरी रेंज दर्शाता है, और हर प्रोसेस उसे अपने वर्चुअल स्पेस में व्यू के रूप में मैप करता है।1
  • एक ही सेक्शन का एक ही ऑफ़सेट अलग वर्चुअल पतों से एक ही भौतिक पृष्ठ पर मैप होता है।2
  • फ़ाइल-समर्थित सेक्शन वास्तविक फ़ाइल थामता है; पेजफ़ाइल-समर्थित सेक्शन नामित साझा मेमोरी आदि थामता है।9
  • EXE/DLL इमेज सेक्शन और साधारण फ़ाइल डेटा सेक्शन मानी जाती है; सुरक्षा और वापस लिखने का गंतव्य भिन्न हैं।3
  • CoW पठन के दौरान भौतिक पृष्ठ साझा करता है और पहली लिखत पर केवल वही पृष्ठ कॉपी कर PTE बदलता है।4
  • CoW के बाद भी VirtualQuery MEM_MAPPED/MEM_IMAGE लौटाता है, इसलिए QueryWorkingSetEx के Shared बिट से पुष्टि करें।7
  • FILE_MAP_COPY में पूरे व्यू का Commit पहले लगता है, इसलिए अकेले Private Bytes से CoW नहीं आँका जा सकता।6
  • Cache Manager का कैश्ड I/O, डेटा मैपिंग और इमेज मैपिंग एक ही फ़ाइल स्ट्रीम से अलग पथों के रूप में जुड़ते हैं जो क्रमशः SharedCacheMap, DataSectionObject और ImageSectionObject इस्तेमाल करते हैं।5
  • साझा मेमोरी तभी सुरक्षित होती है जब व्यू और हैंडल का जीवनकाल, सिंक्रनाइज़ेशन, ACL और ऑफ़सेट डिज़ाइन शामिल हों।

इससे «Windows मेमोरी की गहराई» के तीनों भाग पूरे होते हैं। आप वर्चुअल पता Reserve/Commit करते हैं, पेज फ़ॉल्ट से भौतिक पृष्ठ पाते हैं, पृष्ठ को Working Set से पृष्ठ सूची पर ले जाते हैं, सेक्शन से साझा करते हैं, और केवल लिखे पृष्ठ CoW से बाँटते हैं — Windows मेमोरी प्रबंधन इसी एक प्रवाह से जुड़ा है।

संबंधित लेख

संबंधित परामर्श क्षेत्र

KomuraSoft LLC Windows अनुप्रयोगों की साझा मेमोरी, फ़ाइल मैपिंग, DLL लोडिंग, फ़ाइल लॉकिंग, अंतर-प्रोसेस संचार और मेमोरी उपयोग की दोष जाँच संभालती है।

संदर्भ लिंक

  1. Microsoft Learn, Section Objects and Views. सेक्शन ऑब्जेक्ट के साझा करने योग्य मेमोरी रेंज दर्शाने, और हर प्रोसेस के सेक्शन का भाग व्यू के रूप में मैप करने पर।  2 3

  2. Microsoft Learn, File-Backed and Page-File-Backed Sections. फ़ाइल-समर्थित और पेजफ़ाइल-समर्थित सेक्शन, CoW, तथा अलग प्रोसेस के वर्चुअल पतों से एक ही भौतिक मेमोरी साझा करने पर।  2 3 4

  3. Microsoft Learn, CreateFileMappingW function. फ़ाइल-मैपिंग ऑब्जेक्ट, पेजफ़ाइल-समर्थित सेक्शन, SEC_IMAGE, व्यू और हैंडल का जीवनकाल, तथा एक ही फ़ाइल थामने वाले व्यू की सुसंगति पर।  2 3 4 5 6 7 8

  4. Microsoft Learn, Memory Protection. कई प्रोसेस के एक ही DLL के भौतिक पृष्ठ साझा करने, और एक पक्ष के लिखने पर CoW के नए भौतिक पृष्ठ पर कॉपी कर PTE अद्यतन करने पर।  2 3

  5. Microsoft Learn, SECTION_OBJECT_POINTERS structure. DataSectionObject, SharedCacheMap और ImageSectionObject के फ़ाइल स्ट्रीम मैपिंग तथा कैश जानकारी को मेमोरी मैनेजर / Cache Manager से बाँधने पर।  2 3 4

  6. Microsoft Learn, MapViewOfFileEx function. FILE_MAP_COPY के साथ CoW; पेज फ़ाइल से समर्थित निजी पृष्ठ; पूरे व्यू का Commit शुल्क; तथा वर्चुअल पते के बजाय ऑफ़सेट रखने पर।  2 3 4 5 6 7 8

  7. Microsoft Learn, VirtualQuery function. CoW के बाद Type के MEM_MAPPED/MEM_IMAGE रहने, तथा QueryWorkingSetEx के Shared बिट से निजीकरण पुष्टि पर।  2 3 4

  8. Microsoft Learn, Managing Memory Sections. व्यू तक पहुँचने तक भौतिक मेमोरी न दिए जाने, और पहली पहुँच के पेज फ़ॉल्ट के फ़ाइल सामग्री पढ़ने पर। 

  9. Microsoft Learn, Sharing Files and Memory. नाम या हैंडल से एक ही फ़ाइल-मैपिंग ऑब्जेक्ट साझा करने; INVALID_HANDLE_VALUE से पेजफ़ाइल-समर्थित साझा मेमोरी बनाने; तथा सिंक्रनाइज़ेशन अलग आवश्यक होने पर।  2 3

  10. Microsoft Learn, Process Explorer - Sysinternals. Process Explorer के प्रोसेस हैंडल तथा लोड की गई DLL / मेमोरी-मैप फ़ाइलें दिखाने पर। 

  11. Microsoft Learn, VMMap - Sysinternals. VMMap के प्रोसेस वर्चुअल मेमोरी को Image, Mapped File, Private आदि में बाँटने, तथा Working Set का Private/Shareable विभाजन दिखाने पर। 

निकटवर्ती विषयों में गहराई से जाने के लिए समान टैग वाले नवीनतम लेख।

DllMain और Loader Lock — DLL आरंभीकरण में "कुछ न करें" कहे जाने का वास्तविक कारण

DllMain से LoadLibrary क्यों न बुलाएँ या दूसरे थ्रेड से सिंक्रोनाइज़ न करें। प्राथमिक स्रोतों से यह लेख समझाता है कि लोडर लॉक हर DLL सूचन...

Windows मेमोरी की गहराई (भाग 2) — भौतिक पेज का जीवन: पाँच सूचियाँ और पेज फ़ाइल की सच्चाई

यह लेख PFN डेटाबेस, Standby, Modified, मेमोरी संपीड़न और पेज फ़ाइल को जोड़कर बताता है कि Working Set छोड़ने के बाद भौतिक पेज कहाँ जाता है।

Windows मेमोरी की गहराई (भाग 1) — वह क्षण जब आभासी पता भौतिक RAM बन जाता है: शुरू से अंत तक page fault

यह लेख VirtualAlloc, VAD, पेज टेबल, TLB, demand-zero और hard fault को जोड़कर उस क्षण को समझाता है जब किसी आभासी पते को भौतिक RAM मिलती है।

Windows वर्चुअलाइज़ेशन की गहराई (भाग 3) — सेकंडों में बूट होने वाली वर्चुअल मशीनें: WSL2, Windows Sandbox और कंटेनर इतने हल्के क्यों हैं

WSL2 और Windows Sandbox सेकंडों में शुरू होकर इतने हल्के क्यों लगते हैं? यह लेख डायनामिक बेस इमेज और डायरेक्ट मैप से डायनामिक मेमोरी आवंट...

Windows वर्चुअलाइज़ेशन की गहराई (भाग 2) — वह मेमोरी जिसे कर्नेल भी नहीं देख सकता: VBS, HVCI और Credential Guard कैसे काम करते हैं

संगत हार्डवेयर पर क्लीन इंस्टॉल पर VBS डिफ़ॉल्ट से सक्षम होता है और हाइपरवाइज़र तथा SLAT से कर्नेल से मज़बूत अलगाव बनाता है। यह लेख VTL, ...

ये पृष्ठ विषय को सेवाओं और निर्णयों के व्यापक संदर्भ में रखते हैं।

यह लेख निम्नलिखित सेवाओं से सीधे जुड़ा है।

अक्सर पूछे जाने वाले प्रश्न

इस लेख के विषय पर परामर्श में अक्सर पूछे जाने वाले प्रश्न।

क्या एक ही DLL इस्तेमाल करने वाला हर प्रोसेस RAM में उस DLL की पूरी कॉपी पाता है?
आमतौर पर नहीं। उसी इमेज के अपरिवर्तित पृष्ठ हर प्रोसेस के अलग वर्चुअल पते से एक ही भौतिक पृष्ठ पर मैप होते हैं। केवल वे पृष्ठ जिन्हें लिखना हो, कॉपी-ऑन-राइट आदि से प्रोसेस-निजी भौतिक पृष्ठ बनते हैं।
क्या CreateFileMapping उसी क्षण प्रोसेस को मेमोरी आवंटित करता है?
CreateFileMapping फ़ाइल-मैपिंग ऑब्जेक्ट बनाता है, लेकिन उसे प्रोसेस के वर्चुअल स्पेस में दिखाने वाला MapViewOfFile है। इसके अलावा व्यू के भौतिक पृष्ठ आमतौर पर पहली बार छुए गए पृष्ठ से पेज फ़ॉल्ट द्वारा साकार होते हैं।
FILE_MAP_WRITE और FILE_MAP_COPY में क्या अंतर है?
FILE_MAP_WRITE के ज़रिए बदलाव साझा फ़ाइल-डेटा पक्ष पर दिखने वाली लिखत है। FILE_MAP_COPY आरंभिक पृष्ठ साझा करता है, लेकिन केवल लिखे गए पृष्ठ प्रोसेस-निजी कॉपी बनते हैं; बदलाव मूल फ़ाइल में वापस नहीं लिखे जाते और व्यू अनमैप होते ही खो जाते हैं।
कॉपी-ऑन-राइट के बाद क्या VirtualQuery MEM_PRIVATE लौटाता है?
नहीं। डेटा व्यू MEM_MAPPED और इमेज व्यू MEM_IMAGE रहता है। यह देखने के लिए कि पृष्ठ वास्तव में निजी हुआ या नहीं, पृष्ठ को रेज़िडेंट बनाएँ और QueryWorkingSetEx का Shared बिट देखें।
क्या साझा मेमोरी में कच्चा पॉइंटर रखना चाहिए?
आमतौर पर नहीं। एक ही सेक्शन होने पर भी कोई गारंटी नहीं कि हर प्रोसेस का व्यू एक ही वर्चुअल पते पर होगा। साझा संरचना में बेस से ऑफ़सेट, स्थिर-चौड़ाई पूर्णांक, तथा स्पष्ट लेआउट और सिंक्रनाइज़ेशन योजना इस्तेमाल करें।

लेखक की प्रोफ़ाइल

लेख के लेखक का परिचय पृष्ठ।

Go Komura

KomuraSoft LLC के प्रतिनिधि

Windows सॉफ़्टवेयर विकास, तकनीकी परामर्श और बग जाँच में विशेषज्ञ, विशेष रूप से मौजूदा सिस्टम वाली परियोजनाओं और पुनरुत्पादन में कठिन बग में।

सार्वजनिक लिंक

ब्लॉग पर लौटें