المزالق وأفضل الممارسات عند استخدام shared memory - تنظيم مسبق للتزامن، الرؤية، العمر، ABI، والأمان

· · Shared Memory, IPC, Concurrency, C++, C#, تطوير Windows

frames للصور، نتائج الفحص، سجلّات السلاسل الزمنيّة، بيانات الـ board، buffers ضخمة. حين نريد تبادل بيانات كبيرة بزمن استجابة منخفض داخل الجهاز نفسه، يبدو shared memory جذّاباً جدّاً.

غير أنّ ما هو خطر قليلاً هنا، هو أنّ shared memory يتقدّم نحونا بوجه «IPC سريع». في الواقع، shared memory هو «IPC يُتيح تقليل النسخ، لكن في المقابل يدفع مسؤوليّة الاتّساق إلى جهة التطبيق».

  • سريع
  • مرن
  • لكنّ الـ protocol من صنعك أنت
  • وحين تقع الأعطال تكون الأعراض صاخبة

عادةً ما تأتي هذه الأربع معاً.

في هذا المقال، نُنظّم - مع الأخذ في الاعتبار file mapping على Windows و shm_open / mmap على POSIX - النقاط التي تنحشر عند استخدام shared memory في الميدان، والتصميم الذي يخفض معدّل الأعطال. سواء في C/C++ أو في MemoryMappedFile على C#، الجوهر هو نفسه تقريباً.1

1. أوّلاً الخلاصة (في كلمة)

إن قلناها بصورة خشنة جدّاً لكنّها مفيدة في الميدان، فالأمر هكذا.

  • shared memory هو آليّة تجعل سلسلة البايتات نفسها مرئيّةً من عمليّات متعدّدة، وليس التزامن نفسه23
  • السرعة تظهر مع البيانات الكبيرة عند تبادلها داخل الجهاز نفسه. أمّا حين تكون الرسائل الصغيرة للتحكّم فقط، فإنّ pipe / socket / named pipe / queue غالباً ما تكون أيسر بكثير
  • في shared memory، أن يكون مرئيّاً و أن يكون قابلاً للقراءة بأمان مسألتان منفصلتان
  • لا تجعل volatile أساس التصميم. فإنّ الذرّيّة (atomicity)، والترتيب، والانتظار تُعالج كلٌّ على حدة45
  • إذا وضعت raw pointer، HANDLE، file descriptor، std::string، std::vector، std::mutex كما هي، فستبكي لاحقاً غالباً
  • البيانات التي تُوضع في shared memory، الأسلم أن تُمال نحو fixed-width integers + layout صريحة + header مع version
  • مجرّد وضع magic / version / size / state / generation / heartbeat في الـ header المتقدّم يُغيّر كثيراً سهولة التحقيق في الأعطال
  • إنّ معضلات shared memory ليست السرعة، بل التهيئة، العمر، الاستعادة، الصلاحيّات، ABI
  • في Windows، الهيكل هو CreateFileMapping / OpenFileMapping / MapViewOfFile، وفي POSIX shm_open / ftruncate / mmap63
  • الأقلّ عرضةً للأعطال هو البدء بـ ring buffer من نوع SPSC (single-producer single-consumer) أو double buffer

باختصار، shared memory سريع، لكنّه إن استُخدم على نحو خشن أصاب صاحبه بـ «داء توهّم أنّ التزامن يحدث وحده». تجنّب هذا هو معركة البداية.

2. ما الذي يُشاركه shared memory، وما الذي لا يُشاركه

shared memory، إن قلناها بصورة عامّة، هو آليّة لتمرير الصفحات الفيزيائيّة نفسها إلى فضاءات العناوين الافتراضيّة لعمليّات متعدّدة. في Windows نستخدم file mapping object و view، وفي POSIX نُجري mmap على shared memory object.273

ما هو مهمّ هنا نقطتان.

  1. ما يُشترك هو سلسلة البايتات في المحتوى، وليس العنوان الافتراضيّ نفسه
  2. أن يكون coherent و أن يكون متزامناً مسألتان مختلفتان

في توثيق Windows أيضاً، يُذكر أنّ الـ views المُشتقّة من نفس file mapping object تكون coherent عند نفس النقطة الزمنيّة. لكنّ ذلك لا يعني أنّ القارئ يستطيع دوماً قراءة سجلّ مُحدَّث ومتّسق.8

مثلاً،

  • يقصد writer كتابة length
  • ثمّ payload
  • ثمّ ready flag

بهذا الترتيب. لكنّ القارئ إن قرأ بدون أيّ تزامن، فقد يرى length جديداً مع payload قديم مدمَجَين. shared memory لا يُصلح هذا تلقائيّاً.

أيّ، ما يُشاركه shared memory هو البايتات. وما لا يُشاركه هو المعنى، الترتيب، إخطار الإكمال، سياسة الاستعادة. هذه كلّها تحتاج إلى تصميم من جهتنا.

3. مواقف يلائمها shared memory / لا يلائمها

الموقف يلائم / لا يلائم السبب
تمرير frames أو buffers كبيرة داخل الجهاز نفسه يلائم يَسهُل تقليل عدد مرّات النسخ
قيم sensors عالية التواتر، صور، صوت، بيانات board وما شابه يلائم يَسهُل استهداف زمن استجابة منخفض و throughput مرتفع
تبادل أوامر أو ردود صغيرة فقط لا يلائم كثيراً تكلفة التزامن من أجل التحكّم تكون نسبيّاً ثقيلة
التبادل مع جهاز آخر لا يلائم shared memory مبنيّ أساساً على افتراض الجهاز نفسه
تعايش طويل المدى للغات أو إصدارات مختلفة صعب يستلزم تصميم ABI و versioning
الحاجة للـ persistence أيضاً حسب الهدف file-backed mapping خيار قويّ، لكن مسؤوليّات الـ persistence و الـ IPC تختلط بسهولة

في الميدان، الفصل «التحكّم عبر نظام الرسائل، وجسم البيانات في shared memory» قويّ جدّاً. مثلاً،

  • إخطار من عمليّة الـ UI إلى عمليّة الـ worker بـ «استخدِم الـ frame التالي» يكون عبر event / pipe / socket
  • جسم الـ frame الفعليّ في shared memory

هكذا التركيب. وهو هادئ نسبيّاً.

4. أربعة أمور يجب تحديدها أوّلاً

عند تصميم shared memory، أوّل ما يجب تحديده هو هذه الأربعة.

4.1 افصل بين control plane و data plane

حدِّد أوّلاً ماذا تضع في الـ shared memory.

  • data plane: صور، صوت، سلاسل سجلّات، بيانات بالكتلة
  • control plane: البدء، الإيقاف، الأخطاء، إعادة الاتّصال، إعادة التهيئة، الإخطار

مجرّد فصل هذين يُبسّط بشدّة تصميم جانب الـ shared memory.

4.2 ضيِّق نموذج التزامن

  • SPSC: 1 producer / 1 consumer
  • MPSC: عدّة writers / 1 consumer
  • SPMC: 1 writer / عدّة readers
  • MPMC: عدّة writers / عدّة readers

تتصاعد الصعوبة عموماً بهذا الترتيب. الذهاب إلى MPMC من البداية شجاع جدّاً. وغالباً ما يظهر شبح ترتيب الذاكرة لاحقاً.

4.3 حدِّد المالك والعمر

  • من يُنشئ
  • من يُهيّئ
  • من يحذف
  • من يُستعيد إن سقط أحد المشاركين في الأثناء

إن غمض هذا، تَلوَّث الجوّ مع كلّ ترتيب إقلاع أو إعادة تشغيل.

4.4 حدِّد ABI و version

  • التصميم layout
  • أحجام الأنواع
  • alignment
  • منطقة reserved
  • version / feature flags
  • وجود توافق أو عدمه

shared memory ليست قصّة API، بل قصّة ABI (binary interface). الإهمال هنا يُؤدّي إلى عطل مزعج: التوافق المصدريّ موجود، لكنّ الانهيار يحدث وقت التشغيل فقط.

5. مزالق شائعة

5.1 لا تُجري تزامناً

الأكثر شيوعاً.

«طالما ننظر إلى الذاكرة نفسها، فإذا كتبتُ يستطيع الآخر القراءة».

نعم، قد تستطيع القراءة. لكنّ ذلك لا يعني أنّك تستطيع القراءة في التوقيت الصحيح، وبالوحدة الصحيحة، وبالترتيب الصحيح.

سواء على Windows أو POSIX، فإنّ الوصول إلى shared memory مبنيّ على افتراض دمجه مع وسائل تزامن أخرى. في توثيق Windows أيضاً، يُذكر أنّ الوصول إلى الـ views المشتركة يتطلّب التنسيق عبر mutex / semaphore / event وما شابه.2 في توثيق POSIX أيضاً، الوصول إلى shared memory يستلزم تزامناً.9

5.2 محاولة حلّ كلّ شيء بـ volatile

volatile ليست سحراً يُنقذ تصميم shared memory. على الأقلّ، atomicity و mutual exclusion مسألتان منفصلتان.45

مثلاً، تصميم يَضع volatile bool ready; ويدور في busy loop،

  • يُهدر CPU
  • يُصبح ضمان ترتيب payload و ready مبهماً
  • ليس portable
  • يَسهُل التقاط حالات وسيطة

أيّ، لا خير فيه عادةً.

علاوةً على ذلك، فإنّ WaitOnAddress على Windows مُخصَّصة للـ threads داخل العمليّة نفسها. الأسلم ألّا تُعتبر آليّة انتظار cross-process.10

5.3 السماح بقراءة الحالات الوسيطة

عند وقوع حادث في shared memory، يبدو الأمر طبيعيّاً جدّاً.

  • الـ header وحده جديد
  • الـ payload وحده قديم
  • الطول وحده مُحدَّث
  • مجموعة حقلَين متهالكة

إن كان المطلوب فقط تحديث scalar واحد بصورة atomic، فالقصّة بسيطة نسبيّاً. لكن إن نشرت سجلّاً مكوَّناً من حقول متعدّدة، فلا بدّ من إجراء commit.

نموذجيّاً، أحد الخيارات الآتية.

  • حماية الكلّ بـ mutex
  • استخدام double buffer والتبديل في النهاية إلى «رقم الـ buffer الفعّال الآن»
  • استخدام ring buffer بحيث يحمل كلّ slot state / sequence
  • إن كان 1 writer / عدّة readers، أخذ snapshot عبر sequence counter

حتّى مجرّد «رفع ready flag في النهاية» لا يكفي ما لم تُحدِّد بأيّ ترتيب memory order تكتب الـ flag وتقرأها. في shared memory، توقيت النشر نفسه هو الـ protocol.

5.4 وضع pointers أو كائنات معقّدة كما هي

هذا متكرّر جدّاً.

  • raw pointer
  • HANDLE
  • file descriptor
  • std::string
  • std::vector
  • std::unordered_map
  • std::mutex
  • CRITICAL_SECTION

من يضع هذه في shared memory ويحاول استخدامها من عمليّة أخرى. عادةً ما يبدأ جحيم صغير.

السبب بسيط، العنوان الافتراضيّ والموارد process-local لها معنى فقط في سياق تلك العمليّة. في Windows أيضاً، حتّى إن أجريت map للـ mapping نفسه من عمليّة أخرى، فالعنوان الافتراضيّ ليس بالضرورة متطابقاً.711

لذا، إن احتجت إلى مرجع، فالقاعدة هي حمله كـ offset من العنوان الأساسيّ.

typedef struct ShmRef {
    uint64_t offset;   // الموقع النسبيّ من بداية الـ segment
    uint32_t length;
    uint32_t kind;
} ShmRef;

هكذا تستطيع كلّ عمليّة تحويله إلى عنوانها الخاصّ بـ base + offset.

5.5 انكسار الـ ABI

shared memory ليست كود مصدر، بل اتّفاق على المستوى الثنائيّ. أيّ، الفروقات الآتية كلّها مؤثّرة.

  • حجم int / long
  • تمثيل bool
  • underlying type لـ enum
  • حجم wchar_t
  • الفرق بين 32bit / 64bit
  • #pragma pack
  • اختلاف الـ compiler / language
  • alignment / padding
  • little-endian / big-endian

داخل الجهاز نفسه، الـ endianness غالباً موحَّدة، لكن مجرّد دخول دعم ARM64 أو mixed toolchain يكفي لانكسار الأمور بصورة طبيعيّة.

لذا، أنصح بشدّة بأن يكون الهيكل المُوضَع في shared memory:

  • fixed-width integers مثل uint32_t / uint64_t
  • padding / reserved صريحة
  • في الـ header: version, header_size, record_size, total_size
  • عند الحاجة، static_assert(sizeof(...))
  • عدم وضع كائنات non-trivial

5.6 سباق التهيئة

shared memory سهل الكسر بافتراض «المُنشئ هو من هيّأ بالتأكيد».

في Windows، إن أصاب CreateFileMapping اسماً موجوداً، يُعيد الكائن الموجود، ويُكشف الأمر عبر GetLastError() كـ ERROR_ALREADY_EXISTS. الصفحات الأوليّة لـ pagefile-backed mapping تبدأ بـ 0.8 في POSIX، الـ shared memory object الجديد يكون في البداية بطول 0، ويُحدَّد حجمه بـ ftruncate. البايتات المُخصَّصة حديثاً تُهيَّأ بـ 0. والإنشاء عبر O_CREAT | O_EXCL ذرّيّ.3

من دون معرفة هذا الفرق، إن:

  • استَخدمتَ مباشرةً بعد الـ open
  • لم يكن هناك flag إكمال تهيئة
  • هيّأ المشاركون بصورة متزامنة
  • لم تتفقّد version mismatch

فستحدث أعطال بحسب ترتيب الإقلاع.

في الحدّ الأدنى، الأفضل وضع الحالات الآتية في الـ header المتقدّم.

  • INITIALIZING
  • READY
  • BROKEN

والتهيئة تكون من قِبل الـ creator وحده، أمّا الـ joiner فينتظر READY. هذا الأسلوب وحده يُهدِّئ العالم كثيراً.

5.7 عدم التفكير في الاستعادة عند الأعطال

ماذا يحدث إن سقط الـ writer أثناء تحديث البيانات المشتركة. إن أُطلق الإصدار للإنتاج وهذا مُهمَل، تتجهّم الوجوه فجأةً عند الحوادث.

mutex على Windows يصبح abandoned إن انتهى thread المالك دون release، ويستقبل جانب الـ wait WAIT_ABANDONED. وهذا يعني أنّ المورد المشترك قد يكون في حالة غير مُعرَّفة.12 في robust mutex على POSIX أيضاً، حين يموت الـ owner، يُعاد EOWNERDEAD، ويأتي تدفّق استدعاء pthread_mutex_consistent() بعد الإصلاح.1314

المهمّ هنا ألّا «تُكمل دون توقّف». الاستعادة تتطلّب على الأقلّ أحد الآتي.

  • رقم generation
  • آخر sequence مُلتزَم به
  • heartbeat
  • dirty / clean flag
  • 2-stage commit بأسلوب journal
  • إجراء إعادة تهيئة كاملة عند التلف

5.8 false sharing وتنازع cache line

يُقال إنّ shared memory سريع. لكن إن كانت العدّادات الـ hot مُكتظّة في cache line نفسه، تتنقّل الـ line بين الـ CPUs ذهاباً وإياباً، وتزداد البطء بصورة مُلحوظة.

المثال النموذجيّ:

  • producer يُحدِّث write_index
  • consumer يُحدِّث read_index
  • كلاهما على cache line نفسه

في هذه الحالة:

  • فصل الـ hot fields في cache lines مختلفة
  • فصل الـ fields ذات تواتر التحديث المرتفع عن المنخفض
  • مراعاة 1 writer لـ 1 cache line

يُحدث فرقاً كبيراً. كثيراً ما يُذكر التماثل مع 64 bytes، لكن 64 bytes قيمة شائعة في كثير من الـ CPUs لكنّها ليست قانوناً مطلقاً، فلتنظر إليها بهذا المزاج.

5.9 الاستهانة بالاسم والصلاحيّات والأمان

named shared memory مريح، لكن إن أَهملت الاسم والصلاحيّات وقعت الأعطال.

في Windows:

  • يوجد namespace هو Global\ و Local\
  • إنشاء جديد لـ file mapping تحت Global\ من خارج session 0 يستلزم SeCreateGlobalPrivilege
  • اسم الـ object يُشارك namespace مع event / semaphore / mutex / waitable timer / job

هذه عاداتها.1582

أيّ:

  • جعلتُه "Global\\MyApp" فيبدو أنّ المشاركة بين الـ service و desktop app ممكنة
  • لكنّه يفشل بسبب الصلاحيّات
  • وعلى رأس ذلك، أُنشئ mutex بنفس الاسم سابقاً فيقع ERROR_INVALID_HANDLE

طين Windows-y جدّاً يظهر.

في جانب POSIX أيضاً، الاستهانة بـ mode أو umask لـ shm_open تجعل الأمر يبدو أوسع ممّا يجب، أو على العكس يَتَعذَّر فتحه.3

shared memory ليست آمنة لمجرّد كونها ذاكرة. من العمليّات التي لها صلاحيّة قراءة، يبدو الأمر مفتوحاً جدّاً. عند وضع معلومات سرّيّة، يجب التفكير - مثل الذاكرة العاديّة - في سياق paging / swap / dump / الصلاحيّات.

5.10 إجراء تغيير الحجم والترقية بصورة خشنة

«أريد توسيع shared memory قليلاً لاحقاً» طلب خطير نسبيّاً.

  • mapping object على Windows له حجم محدَّد عند الإنشاء8
  • في POSIX أيضاً، إن لم تُراعِ اتّساق ftruncate و mmap، فلن يتطابق طول الـ map عند المشاركين316

في الميدان، الأسلم أن يكون الحجم ثابتاً ضمن جيله. عند الحاجة إلى التوسيع:

  1. أنشئ segment بـ version / name / generation جديدة
  2. حوِّل المشاركين
  3. أغلق الـ segment القديمة

هكذا يقلّ معدّل الأعطال.

5.11 حشر حتّى الإخطار في shared memory

شائع:

  • وضع ready = 1 في shared memory
  • والطرف الآخر while (!ready) Sleep(1);

هذا يعمل في البداية. لكنّه لاحقاً يعود في صورة:

  • إهدار CPU
  • اهتزاز زمن الاستجابة بسبب Sleep(1)
  • صعوبة ملاحظة الفقدان
  • صعوبة كتابة timeout أو إخطار الإنهاء بصورة نظيفة

الأفضل إمالة shared memory نحو جانب البيانات، وإلقاء الإخطار إلى primitive قابل للانتظار.

  • Windows: event / semaphore / mutex / named pipe وما شابه217
  • POSIX: semaphore / process-shared mutex + condvar وما شابه1819

5.12 الاعتقاد بأنّك «بهذا تستطيع المشاركة مع جهاز آخر»

تأتي لحظة يَنتاب فيها المرء شعور بأنّه إن استخدم file-backed mapping وأجرى map لملفّ مشترك عبر الشبكة، فبإمكانه التصرّف معه كـ shared memory مع جهاز آخر.

هذا خطر.

في توثيق CreateFileMapping على Windows أيضاً، يُذكر أنّ الـ coherence لا تُضمن لـ remote files. إن أُجري map لنفس الصفحة على جهازَين بصورة writable، فإنّ كلّاً يرى كتاباته فقط، ولا يحدث merge عند تحديث القرص.8

shared memory آليّة مبنيّة أساساً داخل الجهاز نفسه. عند تجاوز الأجهزة، الأسلم اختيار socket / RPC / message broker بصورة مباشرة، فهو أسهل في الحفاظ على العقل.

6. أفضل الممارسات

6.1 افصل بين control plane و data plane

ضع في shared memory البيانات بالكتلة فقط، وأَلقِ الإخطارات وانتقالات الحالة في قناة منفصلة.

  • shared memory: frame, sample, batch, snapshot
  • event / semaphore / pipe / socket: ready, consumed, stop, error, reconnect

هذا الفصل يُحسِّن - قبل الأداء - وضوح التصميم.

6.2 ضع header ثابتاً في المقدّمة

أنصح بشدّة على الأقلّ بوضع header كهذا في المقدّمة.

typedef struct SharedHeader {
    uint32_t magic;
    uint16_t abi_version;
    uint16_t header_size;

    uint32_t state;          // 0=initializing, 1=ready, 2=broken
    uint32_t flags;

    uint64_t total_size;
    uint64_t generation;
    uint64_t heartbeat_ns;

    uint64_t payload_offset;
    uint64_t payload_size;

    uint64_t write_seq;
    uint64_t read_seq;

    uint8_t  reserved[64];
} SharedHeader;

النقاط:

  • صدّ المختلف وغير المُهيَّأ بـ magic
  • صدّ اختلاف الـ layout بـ abi_version و header_size
  • صدّ مرحلة التهيئة بـ state
  • اكتشاف إعادة الإنشاء بـ generation
  • تتبّع الحياة بـ heartbeat
  • ترك مهرب لتوسعات مستقبليّة بـ reserved

ما يصعب في shared memory هو «صعوبة رؤية ما يحدث». لذا، نُحمّله metadata رصد من البداية.

6.3 اجعل المرجع offset

اجعل المرجع - بدل الـ pointer - offset.

  • يُحَلّ بـ base + offset
  • ضع تحقّقاً من نطاق offset + length
  • حدِّد sentinel للقيم غير الصالحة

هذا وحده يقلّ بشدّة الأعطال من نوع address mismatch.

6.4 ضيِّق نموذج التزامن

shared memory يصعب فجأةً مع زيادة الـ writers. لذا في البداية، الأقوى أحد هذين.

  • SPSC ring buffer
  • snapshot من 1 writer / عدّة readers

إن لزم تعدّد الـ writers:

  • اجعل enqueue فقط lock-free / atomic
  • اجمع تحديث البيانات الفعليّ في consumer واحد

هكذا، تقليل نقاط مسؤوليّة الاتّساق يَصلُح غالباً.

6.5 وضِّح commit protocol

تصميم لا يستطيع شرحه نصّيّاً «من أيّ لحظة يجوز القراءة» تصميم خطر.

مثلاً، في double buffer:

  1. اكتب إلى الـ buffer غير المنشور
  2. أكِّد checksum أو الطول
  3. بدّل active buffer index بـ release
  4. يقرأ الـ reader الـ active index بـ acquire
  5. بعد الانتهاء من القراءة، تحقّق من عدم تغيّر الـ index

هكذا تُحدِّد طقوس النشر.

6.6 ثبِّت الحجم لكلّ generation

بدل resize in place:

  • name = MyShm.v3
  • abi_version = 3
  • generation = 42

تقطيع الأجيال على هذا النحو أسهل في الصيانة.

shared memory لا تُجري - مثل API - «فحص نوع عند الاستدعاء». لذا، عدم كسر ABI متّفق عليه أمر مهمّ.

6.7 أَدخل قابليّة الرصد

ما يُفيد وجوده في الحدّ الأدنى:

  • آخر وقت تحديث
  • آخر sequence ناجح
  • عدد الـ drop / overwrite
  • عدد version mismatch
  • عدد attach / detach
  • last error code
  • heartbeat

عندما تنكسر shared memory، عادةً ما تكون السجلّات شحيحة. وضع counters ذاتيّة يجعل الاستجابة للحوادث أيسر بكثير.

6.8 اصنع اختبارات الحالات الشاذّة أوّلاً

الحالات الطبيعيّة فقط لا تكفي. على الأقلّ راقب الآتي.

  • إنهاء قسريّ أثناء تحديث الـ writer
  • فيضان الـ ring بسبب تأخّر الـ reader
  • اتّصال مع version mismatch
  • تعايش 32bit / 64bit
  • فتح يعبر sessions
  • نقص صلاحيّات
  • إعادة تشغيل وعمليّة سابقة تحمل generation قديمة
  • تأثير cache miss / NUMA عند نقل بيانات ضخمة متتابعة

في shared memory، اختبارات أساليب الكسر أكثر قيمة من الحالات الطبيعيّة.

7. نقاط للنظر بين Windows و POSIX

المنظور Windows POSIX
الإنشاء / الـ open CreateFileMapping / OpenFileMapping / MapViewOfFile6 shm_open / ftruncate / mmap3
المشاركة دون ربط بالقرص pagefile-backed mapping بتمرير INVALID_HANDLE_VALUE68 POSIX shared memory object + mmap3
القيمة الابتدائيّة صفحات pagefile-backed مُهيَّأة بـ 08 الـ object الجديد بطول 0. البايتات المُخصَّصة حديثاً مُهيَّأة بـ 03
التزامن mutex / semaphore / event / interlocked وما شابه25 process-shared mutex / condvar / semaphore2018
ما يجب عدم استخدامه cross-process CRITICAL_SECTION, WaitOnAddress2110 mutex / condvar مع PTHREAD_PROCESS_PRIVATE كما هو2019
موت المالك WAIT_ABANDONED12 robust mutex + EOWNERDEAD / pthread_mutex_consistent()1314
حذف الاسم يُحذف بتحرير آخر handle / view28 حذف الاسم بـ shm_unlink. إن بقيت مراجع، يبقى الكيان حتّى آخرها2223
namespace / صلاحيّات Global\ / Local\، ACL، SeCreateGlobalPrivilege1524 mode، umask، namespace، O_CREAT|O_EXCL3

MemoryMappedFile في C# هو في جوهره غلاف لـ file mapping على Windows. لذا:

  • الـ open بنفس الاسم
  • استخدام mutex / event منفصلين
  • القراءة من الـ view بـ layout صريحة
  • عدم وضع مراجع كائنات كما هي

هذه الأساسيّات لا تتغيّر.1

8. checklist يُنظَر إليها أوّلاً

  • هل shared memory ضروريّة فعلاً. هل هي بيانات كبيرة في الجهاز نفسه
  • هل فُصل بين control plane و data plane
  • هل يمكن إنزال نموذج التزامن إلى SPSC / 1 writer عدّة readers
  • هل يحتوي الـ header المتقدّم على magic / version / size / state / generation / heartbeat
  • هل لا يُوضع pointer / HANDLE / fd / STL object / std::mutex
  • هل يوجد commit protocol يمنع رؤية الـ reader للحالات الوسيطة
  • هل المُهيِّئ شخص واحد محدَّد
  • هل يوجد إجراء استعادة عند الإنهاء غير الطبيعيّ
  • هل الأسماء والصلاحيّات صريحة
  • هل Global\ ضروريّة فعلاً
  • هل لا تَفترض resize in place
  • هل اختبرت writer kill / reader stall / version mismatch / نقص الصلاحيّات

9. خلاصة

shared memory، إن استُخدمت جيّداً، قويّة جدّاً. خاصّةً مع:

  • صور
  • صوت
  • سلاسل sensors
  • batches كبيرة
  • snapshots عالية التواتر

أيّ بيانات كبيرة داخل الجهاز نفسه، تكون فعّالة فعلاً.

غير أنّ جوهر shared memory هو - أكثر من «السرعة» - نقل المسؤوليّة. بدلاً من تقليل النسخ والمراسلة عبر kernel، تتولّى أنت:

  • التزامن
  • الرؤية
  • التهيئة
  • ABI
  • الاستعادة
  • الصلاحيّات
  • قابليّة الرصد

لذا، الأسلم في المرّة الأولى أن يكون الشكل هكذا.

  • SPSC ring buffer أو double buffer
  • header ثابت في المقدّمة
  • مرجع offset
  • إخطار عبر قناة منفصلة
  • مع version / generation / heartbeat
  • مع اختبارات للحالات الشاذّة

البدء من هذا الشكل يجعل shared memory أداةً سَلِسة جدّاً. على العكس، التعامل معها فجأةً كـ «ذاكرة مشتركة سريعة يُمكن وضع أيّ شيء فيها» يجعلها مع الزمن - ليست تطبيقاً بل علم آثار.

10. مراجع

  • Windows: أساسيّات file mapping و named shared memory682
  • Windows: namespace / security / synchronization1524512
  • POSIX: shm_open, shm_unlink, mmap, process-shared / robust synchronization322162013
  • .NET: نظرة عامّة على MemoryMappedFile1
  1. Microsoft Learn, “Memory-Mapped Files” https://learn.microsoft.com/en-us/dotnet/standard/io/memory-mapped-files / Microsoft Learn, “MemoryMappedFile Class” https://learn.microsoft.com/en-us/dotnet/api/system.io.memorymappedfiles.memorymappedfile?view=net-10.0  2 3

  2. Microsoft Learn, “Sharing Files and Memory” https://learn.microsoft.com/en-us/windows/win32/memory/sharing-files-and-memory  2 3 4 5 6 7 8

  3. man7.org, “shm_open(3)” https://man7.org/linux/man-pages/man3/shm_open.3.html  2 3 4 5 6 7 8 9 10 11

  4. Microsoft Learn, “/volatile (volatile Keyword Interpretation)” https://learn.microsoft.com/en-us/cpp/build/reference/volatile-volatile-keyword-interpretation?view=msvc-170 / Microsoft Learn, “volatile (C++)” https://learn.microsoft.com/en-us/cpp/cpp/volatile-cpp?view=msvc-170  2

  5. Microsoft Learn, “Interlocked Variable Access” https://learn.microsoft.com/en-us/windows/win32/sync/interlocked-variable-access / Microsoft Learn, “MemoryBarrier function” https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-memorybarrier  2 3 4

  6. Microsoft Learn, “Creating Named Shared Memory” https://learn.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory  2 3 4

  7. Microsoft Learn, “Scope of Allocated Memory” https://learn.microsoft.com/en-us/windows/win32/memory/scope-of-allocated-memory  2

  8. Microsoft Learn, “CreateFileMappingA function” https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga  2 3 4 5 6 7 8 9

  9. man7.org, “POSIX Shared Memory” training slides https://man7.org/training/download/ipc_pshm_slides-mkerrisk-man7.org.pdf 

  10. Microsoft Learn, “WaitOnAddress function” https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitonaddress  2

  11. Microsoft Learn, “MapViewOfFileEx function” https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffileex / Microsoft Learn, “MapViewOfFile function” https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile 

  12. Microsoft Learn, “Mutex Objects” https://learn.microsoft.com/en-us/windows/win32/sync/mutex-objects  2 3

  13. man7.org, “pthread_mutex_lock(3p)” https://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html / man7.org, “pthread_mutexattr_setrobust(3)” https://man7.org/linux/man-pages/man3/pthread_mutexattr_setrobust.3.html  2 3

  14. man7.org, “pthread_mutex_consistent(3)” https://man7.org/linux/man-pages/man3/pthread_mutex_consistent.3.html / man7.org, “pthread_mutex_consistent(3p)” https://man7.org/linux/man-pages/man3/pthread_mutex_consistent.3p.html  2

  15. Microsoft Learn, “Kernel object namespaces” https://learn.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces  2 3

  16. man7.org, “mmap(2)” https://man7.org/linux/man-pages/man2/mmap.2.html  2

  17. Microsoft Learn, “Using Mutex Objects” https://learn.microsoft.com/en-us/windows/win32/sync/using-mutex-objects 

  18. man7.org, “sem_init(3)” https://man7.org/linux/man-pages/man3/sem_init.3.html / man7.org, “sem_init(3p)” https://man7.org/linux/man-pages/man3/sem_init.3p.html  2

  19. man7.org, “pthread_condattr_setpshared(3p)” https://man7.org/linux/man-pages/man3/pthread_condattr_setpshared.3p.html / man7.org, “pthread_condattr_getpshared(3p)” https://man7.org/linux/man-pages/man3/pthread_condattr_getpshared.3p.html  2

  20. man7.org, “pthread_mutexattr_getpshared(3)” https://man7.org/linux/man-pages/man3/pthread_mutexattr_getpshared.3.html / man7.org, “pthread_mutexattr_getpshared(3p)” https://man7.org/linux/man-pages/man3/pthread_mutexattr_getpshared.3p.html  2 3

  21. Microsoft Learn, “Critical Section Objects” https://learn.microsoft.com/en-us/windows/win32/sync/critical-section-objects 

  22. Microsoft Learn, “File Mapping Security and Access Rights” https://learn.microsoft.com/en-us/windows/win32/memory/file-mapping-security-and-access-rights  2

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

روابط عامة

العودة إلى المدونة