الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

[مقالة] استعمال الملفات المعنونة في الذاكرة Memory-Mapped Files (جديد دوت نت 4)

بدأه عبد العظيم بخاري في 8 نوفمبر 2010 · 0 رد · 380 مشاهدة · في Microsoft Visual C#.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1

محتويات المقالة:

- الجديد في الكلاسات والnamespaces لMMF

- القراءة من ملف معنون في الذاكرة .

- مشاركة الذاكرة بين الكائنات والprocesses .

- انشاء الملفات وتوسعتها وتقطيعها.

فلنفرتض أنك تريد معالجة ملفات كبيرة يصل حجمها لعدة GB's وتريد قراءة معلومات منها وكتابة معلومات اليها . فاحد احد الخيارات المتوفرة اليك هو عن طريق sequential stream والذي يكون مناسب في حال اردت الوصول لمحتويات الملف من اوله حتى اخره . لكن اذا اردت الوصول لمنطقة ما داخله فإنك ستواجه صعوبة في ذلك حيث ستلاحظ بطئ كبير في برنامجك لذلك ليس هذا الحل المثالي لهذا الأمر.

اذا كنت تمتلك خبرة في البرمجة بWindows API فابمكانك استعمال التقنية القديمة التي تسمى memory-mapped files أو ما نختصرها عادة ب MMF . فكرة استعمال الملفات المعنونة MMF تقوم على تحميل الملف في الذاكرة فيظهر داخلها ككتل متصلة مع بعضها البعض في الaddress space لبرنامجك . وبالتالي تكون عملية القراءة والكتابة لهذا الملف امر سهل عن طريق الوصول للموقع المناسب داخل الكتل في الذاكرة . ففي الفواقع حينما يقوم نظام التشغيل بجلب ملفات exe و dll الخاصة ببرنامجك من اجل تنفيذها فإنه يتم استعمال طريقة الملفات المعنونة في الذاكرة لهذا الأمر في الخلفية .

استعمال الملفات المعنونة في الذاكرة MMF ليس امراً جديداً بحد ذاته على تطبيقات اطار عمل الدوت نت , فقد كان بالامكان استعمال الAPI الخاص بنظام التشغيل عن طريق (P/Invoke) منذ الإصدار الأول للدوت نت . لكن الجديد الان هو أن استعمال memory-mapped files اصبح متوفراً داخل اطار عمل ال.NET دون الحاجة للتعامل بشكل مباشر مع Windows APIs .

اريد أن اشير إلى أنه ليس فقط الملفات كبيرة الحجم يمكن التعامل معها بMMF كما يظن الكثير من المبرمجين بل وحتى الملفات الصغيرة ايضاً تنفع لذلك بل وتعطي اداءً افضل لأن حجم الكاش في نظام الملفات يكون صغير بمثل احجامهم .لذلك استعمال طريقة الملفات المعنونة في الذاكرة يعطينا طريقة اسرع واسهل للتعامل مع كافة احجام الملفات .

الجديد في الكلاسات والnamespaces

جاء لنا اطار عمل الدوت نت 4 بnamespace جديدة وهي : System.IO.MemoryMappedFiles والموجودة داخل الاسمبلي System.Core.dll .

تحتوي هذه الnamespace على اربعة كلاسات وعلى عدة enumerations تساعد جميعها للوصول بسهولة وبشكل محمي لمخطط الملفات المعنونة في الذاكرة.

اهم كلاس بالنسبة لمبرمجين هو كلاس MemoryMappedFile والذي يسمح لنا انشاء object للعنونة في الذاكرة والذي نستطيع منه انشاء كائن view accessor والذي نستعمله لمعالجة الmemory block المعنون من الملف بشكل مباشر . ونعني بالمعالجة استعمال الميثود Read و Write من اجل كتابة معلومات لتلك المنطقة بالذاكرة أو مجرد القراءة منها وبالتالي نتعامل بسهولة مع اي ملف في الدوت نت .

لو اردنا برمجة هذا الأمر باستعمال الطريقة القديمة بالnative (كما نفعل عند استعمال Windows API) فاننا ببساطة ننشأ pointer يؤشر على بداية البلوك في الذاكرة . لكن view accessor هي الطريقة الأفضل بعالم الmanaged code .

حتى نحصل ملف معنون في الذاكرة ونجري عمليات عليه فإننا بحاجة لكائن accessor والذي يمكن الحصول عليه عن طريق ثلاث خطوات :

1- تحتاج لobject من نوع file stream يؤشر على ملف معين موجود في القرص لديك .

2- تنشأ كائن MemoryMappedFile من الobject السابق .

3- تنشأ كائن accessor .

كما في الكود التالي

FileStream myfile = new FileStream(@"C:\TestFile.dat", FileMode.Open);
MemoryMappedFile myFirstMMF = MemoryMappedFile.CreateFromFile(myfile);
MemoryMappedViewAccessor myFirstAccessor = myFirstMMF.CreateViewAccessor();

يقوم الكود السابق بفتح ملف معين باستعمال كلاس System.IO.FileStream ومن ثم نمرر الinstance object الناتج إلى ميثود CreateFromFile في كلاس MemoryMappedFile .

ثم الخطوة الثالثة استدعاء الميثود CreateViewAccessor من كلاس MemoryMappedFile .

عدم وجود أي باراميتر لميثود CreateViewAccessor يعني أن العنونة تبدأ من بداية الملف (offset رقم صفر) وتنتهي عند البايت الأخير من الملف . وطبعاً يمكنك تحديد منطقة معينة في الملف وليس كله من اجل عنونتها فمثلاً اذا كان حجم الملف 2 جيجابايت فيمكنك اخذ منطقة لعنونتها أو ما نسميها بview عند اول ميجا من الملف وبحجم 12000 بايت عن طريق الكود التالي:

MemoryMappedViewAccessor myFirstAccessor =
myFirstMMF.CreateViewAccessor(1024 * 1024, 12000);

سترى خيارات متقدمة اكثر لكيفية اختيار المناطق التي تريد عنونتها بالذاكرة , لكن اولا سنرى كيفية القراءة من الملف المعنون.

القراءة من ملف معنون في الذاكرة

فلنفترض أننا نريد قراءة 20 بايت من بداية المنطقة المعنونة فميثود ReadByte كالاتي :

....
 MemoryMappedViewAccessor myFirstAccessor = myFirstMMF.CreateViewAccessor();
 byte[] Filebuffer = new byte[20];
 for (int i= 0; i< Filebuffer.Length; i++)
 {
Filebuffer = myFirstAccessor.ReadByte(i);
 }

يمكننا ايضاً استعمال الmethods الذين يسمون Read<T> و ReadArray<T> , فلنفترض مثلاً أن لدينا نوع Sample معرف كstructure (وهو افتراضي للتوضيح فقط ) فستلاحظ أن استدعاء كل ميثود يعطينا نفس الناتج كالاتي :

// الميثود الاولى
 byte[] Filebuffer = new byte[20];
myFirstMMF.ReadArray(0, Filebuffer, 0, Filebuffer.Length);
 Sample sample1 = new Sample(Filebuffer);

 MessageBox.Show(sample1.ToString());

 // الميثود التانية
 Sample sample2 = new Sample();
 myFirstMMF.Read(0, out sample2);

 MessageBox.Show(sample2.ToString());

لاحظ أنه يجب عليك تحديد موقع بداية القراءة لكلتا الmethods . والoffset هو صفري الاساس يكون بشكل نسبي للview المعنونة وليس الملف الأصلي . فعند قيامك بانشاء كائن memory mapped جديد فعليك تحديد المنطقة التي تريد عنونتها من ذلك الملف كما وضحنا سابقاً واذا لم تقم بتحديد ذلك فالview ستكون من بداية الملف .

تذكر دائماً أن الoffset الأصلي للملف يكون الoffset الحالي مجموع مع الoffset للview .

تذكر أيضاً أن الobjects للعنونة في الذاكرة والملفات التحتية لها handlers في نظام التشغيل .لذلك من المهم أن تتذكر أن تعمل dispose لكائنات العنونة في الذاكرة بعد الإنتهاء منها واذا لم تقم بذلك فسيبقون مفتوحين لوقت غير محدد حتى يجدهم الgarbage collector . لذلك ننصح باستعمال جمل try-finally أو استعمال جمل using في السي شارب.

اذا كنت تريد العمل مع الsequential stream فبامكانك عمل ذلك عن طريق ميثود جميلة اسمها CreateViewStream في كلاس MemoryMappedFile والتي ترجع كائن من نوع MemoryMappedViewStream والذي يسمح بعمل وصول متسلسل للمنطقة المعنونة بطريقة مختلفة عن الaccessros والتي تصل بطريقة محددة دون الحاجة للمرور على جميع الكتل في الذاكرة .

مشاركة الذاكرة بين الكائنات والprocesses

لقد رأينا سابقاً كيفية استعمال الملفات المعنونة في الذاكرة لتسهيل التعامل مع محتويات الملفات وتسريع ذلك باستخدام عمليات بسيطة في الذاكرة . اما الان سنتعلم كيفية استعمال هذه التقنية من اجل مشاركة الذاكرة داخل البرنامج وايضاً بين الproccesses .

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

لاحظ الشكل التالي:

86333269.png

يمكننا باستعمال هذا الأمر السماح لعدة threads بالوصول لمحتويات الملف دون القلق على الconcurrency أو الlocking . الذي عليك فقط هو أن تعرف كيفية قراءة بيانات وكتابتها للمنطقة في الذاكرة وقد رأينا كيفية عمل ذلك باستعمال كلاس MemoryMappedViewAccessor .

يوضح المثال التالي كيفية عمل عنونة لملف باكثر من accessor واحد وطبعاً يجب أن يكون اسم الملف نفسه ولا يتغير عند كل مرة تنشأ فيها view accessor .

...
 MemoryMappedViewAccessor myFirstAccessor = myFirstMMF.CreateViewAccessor();
 MemoryMappedViewAccessor mySecondAccessor = myFirstMMF.CreateViewAccessor();

 // الكتابة
 byte ch1 = Encoding.ASCII.GetBytes("B")[0];
myFirstAccessor.Write(0, ch1);

 // القراءة
 byte ch2 = mySecondAccessor.ReadByte(0);
 string IsSame = (ch2 == ch1) ? "Same" : "Not same!";
 MessageBox.Show(IsSame);

يجب أن تعلم أنه في لحظة انتهاء الكتابة للmemory block للview فإن محتويات الملف تكون قد تغيرت . ومع أن نظام التشغيل لا يصدر البيانات للقرص مباشرة لكن بشكل لحظي تقريباً دون الحاجة لأي عمليات تحويل أو تصديروهذا من الأمور التي تمتاز بها طريقة الملفات المعنونة في الذاكرة .

يجب علينا اعطاء الview اسم مناسب حتى نستطيع مشاركة الملف (الذي عملنا له عنونة في الذاكرة) بين الprocesses . وهذا الاسم يسمح لك بفتح synchronized view متزامنة بين اكثر من process واحدة ووطبعاً يجب أن يكون الاسم مناسب ومختلف عن اسماء بقية الobjects .

فلنفترض اننا نريد ارسال string ما من process إلى اخرى وذلك عن طريق الكود التالي الذي يفتح view لها اسم للعنونة في الذاركة ويكتب عليها string بسيط.

لا يحتوي الكود السابق على أي مكان حقيقي (كالملفات) لتخزين البيانات لذلك تحتاج لتحديد باراميتر السعة عند استدعاء الميثود CreateOrOpen وقد قمنا بوضع السعة في كودنا السابق ل2000 بايت .

هذه السعة تعرف حجم الmemory block . على أي حال بالعودة للمثال السابق فالخطوة التالية هي استعمال نفس اسم الview من قبل process اخرى لقراءة الstring الذي كتبناه.

MemoryMappedFile myFirstMMF = MemoryMappedFile.CreateOrOpen("myFirstMMF-name", 2000);
 MemoryMappedViewAccessor myFirstAccessor = myFirstMMF.CreateViewAccessor();

 string MyMsg= "مرحباً بكم جميعا في عالم الملفات المعنونة في الذاكرة";
 byte[] MSGBytes = Encoding.ASCII.GetBytes(MyMsg);

 myFirstAccessor.WriteArray(0, MSGBytes, 0, MSGBytes.Length);
 MessageBox.Show("تم كتابة النص بنجاح.");

تقوم الprocess الثانية بفتح نفس الview المعنونة على الذاكرة عن طريق استعمال ميثود CreateOrOpen لكلاس MemoryMappedFile . وبعدها عن قمنا بانشاء الobject المسمى myFirstAccessor بنفس الطريقة السابقة وقمنا بقراءة البيانات بايت بعد الاخر حتى نصل للبايت الاخير . بعدنا قمنا بمعالجة النص وطباعته على الشاشة .

يعطينا هذا المثال لمحة سرعة عن مدى سهولة عمل اتصال بين الprocesses والذي نسميه بinter-process communication أو IPC

انشاء الملفات وتوسعتها وتقطيعها

يمكننا انشاء ملفات من الملفات المعنونة في الذاكرة اضافة إلى امكانية توسعتها وتقطيعها . فمثلاً اذا اردت انشاء ملف جديد من اجل وضع المنطقة المعنونة في الذاكرة عليه فعليك تنفيذ مثل هذا الكود :

FileStream myfile = new FileStream(
  @"C:\NewTestFile.dat", FileMode.CreateNew);
 MemoryMappedFile myFirstMMF =
  MemoryMappedFile.CreateFromFile(myfile, null, 2500);
 MemoryMappedViewAccessor myFirstAccessor =
  myFirstMMF.CreateViewAccessor();

لاحظ أنه يتم انشاء ملف جديد عبر الوضعية CreateNew اثناء استدعاء الconstuctor لFileStream , وطبعاً يكون حجمه صفر بايت لأنه يكون فارغ , وحتى نقوم بتعبئته بقيم الview الموجودة في الذاكرة فيجب استعمال الميثود CreateFromFile الذي يحتوي على باراميتر السعة .

ففي المثال السابق سوف يكون حجم الملف 2500 بايت وفي حال لم يتبقى شيئ في الذاكرة لكتابته على الملف فسوف يحتوي الملف على قيم صفرية مثل احرف null . اما اذا كان المتبقي اكبر من حجم السعة فلن يقوم الدوت نت بتمديد الملف وسوف تفشل العملية بعكس الstreams التي تقبل أن تمدد الملف . لذلك يجب الحذر عند وضع السعة .

يجب أن تعلم أن نوع باراميتر السعة هو long والذي يعني أنه قيمة ذات 64 بت مع الاشارة (System.Int64) وبالتالي لست مقيد ب2 من الviews في ان واحد فيمكنك استعمال حجم اكبر للviews .

لكن في واقع الأمر هناك قيود حجم الvirtual address space في البرنامج فاذا كان لديك ويندوز 64 بت وتترجم برنامجك بوضعية 64 بت في الفيجوال ستديو فيسمح لك بحجم يقارب 8 تيرابايت . اما اذا كنت تستعمل نظام ويندوز 32 بت العادي فالسعة القصوى عادةً تكون اقل من 2 جيجا اعتماداً على تنصيب النظام والذاكرة المتوفرة .

الحالة الثالثة والاخيرة وهي تقطيع الملفات فإنها تتم بشكل يختلف قليلاً عن انشاء الملفات وتمديدها حيث أن التقطيع يتم عادة في مستوى الfile level فاذا حاولت اختيار باراميتر السعة بحجم اقل من حجم الملف الأصلي على القرص فسوف تحصل على خطأ بذلك لذلك يجب عليك استعمال امر اخر , فمثلاً يمكنك استعمال ميثود SetLength في كلاس FileStream . ومن اجل الحصول على حجم الملف يمكنك استعمال الproperty التي تسمى Length لنفس الكلاس أو لكلاس System.IO.FileInfo .

خلاصة المقالة

لقد تعلمنا في هذه المقالة عن الملفات المعنونة في الذاكرة Memory-Mapped files وتعرفنا على كلاساتها الmanaged المدعومة في اطار عمل الدوت نت 4.

تقنية MMF هي تقنية مفيدة تقدم لنا امكانية قراءة وكتابة الملفات ببساطة دون الحاجة لstream ودون الاهتمام بحجم الملف حتى لو كان اكبر من الذاكرة لأنه يمكنك عمل منطقة معينة به ليتم عمل عنونة لها في الذاكرة .

عمل عنونة لملفات الذاكرة مفيد في مشاركة البيانات بين الthreads التابعة للبرنامج وحتى بين الproccess الي تعمل على نفس الsystem . فكل ما عليك فعله هو اعطاء الobject للmapped view اسم مناسب فاذا كان هذا الاسم نفسه في الproccess الاخرى سيتم عمل مشاركة اوتوماتيكية للبيانات .

يمكننا ببساطة التعامل مع قراءة الملفات والكتابة عليها عن طريق accessor معين واستعمال عدة methods رائعات .

استعمالنا للMemory-Mapped files يعطينا بديل رائع ومميز عن الطرق التقليدية الاخرى للتعامل مع الملفات .

لمعلومات اخرى عن هذه التقنية راجع الروابط التالي :

http://msdn.microsoft.com/en-us/library/system.io.memorymappedfiles(VS.100).aspx

http://msdn.microsoft.com/en-us/library/system.io.memorymappedfiles.memorymappedviewaccessor(VS.100).aspx

عبد العظيم بخاري

http://www.el-bukhari.com/2010/11/memory-mapped-files-4.html

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…