المزالق وأفضل الممارسات عند استخدام 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، وفي POSIXshm_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
ما هو مهمّ هنا نقطتان.
- ما يُشترك هو سلسلة البايتات في المحتوى، وليس العنوان الافتراضيّ نفسه
- أن يكون 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::stringstd::vectorstd::unordered_mapstd::mutexCRITICAL_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 المتقدّم.
INITIALIZINGREADYBROKEN
والتهيئة تكون من قِبل الـ 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
أيّ:
- جعلتُه
"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
في الميدان، الأسلم أن يكون الحجم ثابتاً ضمن جيله. عند الحاجة إلى التوسيع:
- أنشئ segment بـ version / name / generation جديدة
- حوِّل المشاركين
- أغلق الـ 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:
- اكتب إلى الـ buffer غير المنشور
- أكِّد checksum أو الطول
- بدّل active buffer index بـ release
- يقرأ الـ reader الـ active index بـ acquire
- بعد الانتهاء من القراءة، تحقّق من عدم تغيّر الـ index
هكذا تُحدِّد طقوس النشر.
6.6 ثبِّت الحجم لكلّ generation
بدل resize in place:
name = MyShm.v3abi_version = 3generation = 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
-
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
-
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
-
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
-
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
-
Microsoft Learn, “Creating Named Shared Memory” https://learn.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, “Scope of Allocated Memory” https://learn.microsoft.com/en-us/windows/win32/memory/scope-of-allocated-memory ↩ ↩2
-
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
-
man7.org, “POSIX Shared Memory” training slides https://man7.org/training/download/ipc_pshm_slides-mkerrisk-man7.org.pdf ↩
-
Microsoft Learn, “WaitOnAddress function” https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitonaddress ↩ ↩2
-
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 ↩
-
Microsoft Learn, “Mutex Objects” https://learn.microsoft.com/en-us/windows/win32/sync/mutex-objects ↩ ↩2 ↩3
-
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
-
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
-
Microsoft Learn, “Kernel object namespaces” https://learn.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces ↩ ↩2 ↩3
-
man7.org, “mmap(2)” https://man7.org/linux/man-pages/man2/mmap.2.html ↩ ↩2
-
Microsoft Learn, “Using Mutex Objects” https://learn.microsoft.com/en-us/windows/win32/sync/using-mutex-objects ↩
-
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
-
Microsoft Learn, “Critical Section Objects” https://learn.microsoft.com/en-us/windows/win32/sync/critical-section-objects ↩
-
man7.org, “shm_unlink(3p)” https://man7.org/linux/man-pages/man3/shm_unlink.3p.html ↩ ↩2
-
man7.org, “shm_open(3)” (shm_unlink semantics) https://man7.org/linux/man-pages/man3/shm_open.3.html ↩
-
Microsoft Learn, “File Mapping Security and Access Rights” https://learn.microsoft.com/en-us/windows/win32/memory/file-mapping-security-and-access-rights ↩ ↩2
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
قائمة تحقّق للتعامل الآمن مع child processes في تطبيقات Windows - Job Objects ونشر الـ exit وstdio وتصميم الـ watchdog
دليل تصميم متكامل للتعامل الآمن مع child processes في تطبيقات Windows عبر Job Objects ونشر الـ exit وتصريف stdio ووضع الـ watchdog خارج ا...
كيف نُحوِّل C# إلى native DLL باستخدام Native AOT - استدعاء exports من نوع UnmanagedCallersOnly من C/C++
يوضِّح هذا المقال كيف نُصدر مكتبة C# بوصفها native DLL عبر Native AOT، ونكشف نقاط دخول UnmanagedCallersOnly تُستدعى مباشرةً من C أو C++ ب...
مزالق تطبيقات serial communication - framing وtimeouts وflow control وreconnects ومحوّلات USB وتجمّد الـ UI
ملخّص عمليّ لمزالق تطبيقات serial communication على Windows: framing وtimeouts وflow control وreconnects ومحوّلات USB-to-serial وتجمّد ال...
أيّها نختار من بين Windows Forms وWPF وWinUI - جدول قرار للتطوير الجديد، الأصول القائمة، التوزيع، والتعبير عن الـ UI
هذا المقال يُنظّم اختيار WinForms أو WPF أو WinUI من زاوية الأصول القائمة والتوزيع وقدرة التعبير في الـ UI، ويُقدّم جدول قرار عمليّاً يُس...
كيف تقيس وتقارن سرعة لغات البرمجة المختلفة - دليل عمليّ لمقارنة C# / C++ / Java / Go تحت الشروط نفسها
إطار عمليّ لمقارنة سرعة C# و C++ و Java و Go بإنصاف: تحديد ما تقيسه، توحيد بيئة التشغيل، التعامل مع warm-up و JIT و GC، وقراءة الإحصاءات ...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.
روابط عامة