مدخل إلى أنظمة التشغيل الفصل الرابع: فهم الملفات Files وأنظمة الملفات file systems


Ola Abbas

تُفقََد البيانات المخزَّنةٌ في الذاكرة الرئيسية لعمليةٍ ما عندما تكمل هذه العملية عملها أو تتعطل لسببٍ ما، ويُطلَق على البيانات المخزّنة في القرص الصلب (hard disk drive واختصاره HDD) والبيانات المخزّنة على أقراص التخزين ذات الحالة الثابتة (solid state drive وتختصر إلى SSD) ببياناتٍ دائمة (persistent)، أي أنها لا تُفقََد بعد اكتمال العملية حتى لو أغلِق الحاسوب. القرص الصلب (Hard disk drive) معقد، حيث تخزَّن البيانات ضمن كتل (blocks) التي تتواجد ضمن قطاعات (sectors)، وتشكّل القطاعاتُ مساراتٍ (tracks) ثم تنظَّم المسارات في دوائر متحدة المركز على أطباق (platters) القرص الصلب. أما أقراص التخزين ذات الحالة الثابتة (Solid state drives) فهي أبسط إلى حدٍ ما لأنّ الكتل مرقّمة تسلسليًا، ولكنها تثير تعقيدًا مختلفًا فيمكن أن تُكتَب كل كتلة عددًا محدودًا من المرات قبل أن تصبح غير موثوقة للاستخدام مرةً أخرى. والمبرمج غير مجبرٍ للتعامل مع تلك التعقيدات ولكن ما يحتاجه حقًا هو تجريدٌ مناسب لعتاد التخزين الدائم (persistent storage hardware)، وتجريد التخزين الدائم الأكثر شيوعًا هو نظام الملفات (file system)، فيمكن القول بتجريد أن:

  • نظام الملفات ما هو إلا ربط (mapping) بين اسم الملف ومحتوياته، فإذا عُدَّت أسماء الملفات مفاتيحًا (keys) ومحتويات الملف قيمًا (values) فإن نظام الملفات مشابه لقاعدة البيانات ذات النوع مفتاح-قيمة (key-value database)
  • الملف هو سلسلة من البايتات

تكون أسماء الملفات عادةً من النوع سلسلة (string) وتكون بنظام هرمي (hierarchical) حيث يكون اسم الملف عبارة عن مسار يبدأ من المجلد الأعلى مستوى (top-level directory or folder) مرورًا بمجلدات فرعية حتى الوصول إلى الملف المطلوب. الاختلاف الرئيسي بين الآلية الأساسية (underlying mechanism) والتي هي التخزين الدائم وتجريدها والذي هو نظام الملفات هو أن الملفات تعمل على أساس البايت (byte-based) أما التخزين الدائم يعمل على أساس الكتلة (block-based). يترجم نظام التشغيل عمليات الملف ذات الأساس البايتي في مكتبة C إلى عمليات ذات أساس كتلي على أجهزة التخزين الدائم، ويتراوح حجم الكتلة بين 1 و 8 كيبي بايت (KiB التي تساوي 210 أو 1024 بايت). تفتح الشيفرة التالية ملفًا وتقرأ أول بايت:

FILE *fp = fopen("/home/downey/file.txt", "r");
char c = fgetc(fp);
fclose(fp);

يحدث ما يلي عند تشغيل الشيفرة السابقة:

  1. تستخدم الدالة fopen اسم الملف لإيجاد المجلد الأعلى مستوىً (top-level directory) وهو / ثم المجلد الفرعي home ثم المجلد الفرعي المتواجد ضمن home وهو downey.
  2. لتجد بعد ذلك ملفًا اسمه file.txt وتفتحه للقراءة، وهذا يعني أن fopen تنشئ بنية بيانات (data structure) تمثل الملف المقروء، حيث تتتبّع بينةُ البيانات الكميةَ المقروءة من الملف، وتدعى هذه الكمية المقروءة بموضع الملف (file position). وتدعى بنية البيانات تلك بكتلة تحكم الملف (File Control Block) في DOS، ولكنني أريد، يقول الكاتب، تجنّب ذلك المصطلح لأن له معنىً آخر في UNIX، وبالتالي لا يوجد اسمٌ جيد لبنية البيانات تلك في UNIX، وبما أنها مدخلة في جدول الملف المفتوح لذلك سأسميها، يقول الكاتب، بمدخلة جدول الملف المفتوح (OpenFileTableEntry).
  3. يتحقق نظام التشغيل من وجود الحرف التالي من الملف مسبقًا في الذاكرة عند استدعاء الدالة fgetc، إذا كان موجود فإن الدالة fgetc تقرأ الحرف التالي وتقدّم موضع الملف إلى الحرف الذي بعده ثم تعيد النتيجة.
  4. أما إذا لم يوجد الحرف التالي في الذاكرة فيصدر نظام التشغيل طلب إدخال/إخراج (I/O request) للحصول على الكتلة التالية. القرص الصلب بطيء لذلك تُقاطَع العملية التي تنتظر وصول بياناتٍ من القرص الصلب عادةً وتشغَّل عملية أخرى ريثما تصل تلك البيانات.
  5. تُخزَّن الكتلة الجديدة من البيانات في الذاكرة عند اكتمال عملية الإدخال/الإخراج (I/O operation) ثم تستأنف العملية عملها، حيث يُقرأ أول حرف ويُخزَّن كمتغير محلي.
  6. يكمل نظام التشغيل أية عملية معلّقة (pending operations) أو يلغيها ثم يزيل البيانات المخزنة في الذاكرة ويحرر مدخلة جدول الملف المفتوح (OpenFileTableEntry) عندما تغلق العمليةُ الملف.

عملية الكتابة في ملف مشابهة لعملية القراءة من ملف ولكن مع وجود خطوات إضافية، حيث يفتح المثال التالي ملفًا للكتابة ويغير أول حرف من الملف:

FILE *fp = fopen("/home/downey/file.txt", "w");
fputc('b', fp);
fclose(fp);

يحدث ما يلي عند تشغيل الشيفرة السابقة:

  1. تستخدم الدالة fopen اسم الملف لإيجاده، فإذا كان الملف غير موجود مسبقًا فتنشئ الدالة fopen ملفًا جديدًا وتضيف مدخلةً في المجلد الأب home/downey/.
  2. ينشئ نظام التشغيل مدخلة جدول الملف المفتوح (OpenFileTableEntry) التي تحدد أن الملف مفتوح للكتابة وتهيئ موضع الملف بالقيمة 0.
  3. تحاول الدالة fputc كتابة أو إعادة كتابة البايت الأول من الملف، حيث إذا كان الملف موجودًا مسبقًا فيجب على نظام التشغيل تحميل الكتلة الأولى من الملف إلى الذاكرة، وإذا لم يوجد الملف فسيخصّص نظام التشغيل مكانًا للكتلة الجديدة في الذاكرة ويطلب كتلة جديدة من القرص الصلب.
  4. يمكن ألّا تُنسَخ الكتلة المعدّلة في الذاكرة إلى القرص الصلب بعد تعديلها مباشرةً، حيث تُخزَّن البيانات المكتوبة في الملف تخزينًا مؤقتًا (buffered) أي أنها تُخزَّن في الذاكرة، ولكنها لا تُكتَب في القرص الصلب إلّا عند وجود كتلة واحدة على الأقل لكتابتها.
  5. تُكتَب البيانات المخزنة تخزينًا مؤقتًا في القرص الصلب وتُحرَّر مدخلة جدول الملف المفتوح عند إغلاق الملف.

باختصار توفر مكتبة C تجريدًا هو نظام الملفات الذي يربط أسماء الملفات بمجرىً من البايتات، ويُبنى هذا التجريد على أجهزة التخزين الدائم التي تنظَّم ضمن كتل.

أداء القرص الصلب (Disk performance)

الأقراص الصلبة بطيئة حيث إن الوقت الوسطي لقراءة كتلة من القرص الصلب إلى الذاكرة يتراوح بين 5 و 25 ميلي ثانية على الأقراص الصلبة الحالية HDDs (تعرّف على خصائص أداء القرص الصلب). أما SSDs فهي أسرع من HDDs، حيث تستغرق قراءة كتلة حجمها 4 كيبي بايت 25 ميكرو ثانية وتستغرق كتابتها 250 ميكرو ثانية (تعرّف على متحكم قرص التخزين ذات الحالة الثابتة). وإذا وازنت الأرقام السابقة مع دورة ساعة المعالج (clock cycle of the CPU)، حيث إن المعالج الذي يملك معدل ساعة (clock rate) مقداره 2 جيجا هرتز يكمل دورةَ ساعةٍ كل 0.5 نانو ثانية، والوقت اللازم لجلب بايت من الذاكرة إلى المعالج هو حوالي 100 نانو ثانية، وبالتالي إذا أكمل المعالج تعليمةً واحدةً في كل دورة ساعة (التي مقدارها 0.5 نانو ثانية) فإنه سيكمل 200 تعليمة خلال انتظاره وصول بايت من الذاكرة إليه (100/0.5=200). وسيكمل المعالج 2000 تعليمة بدورة ساعة 1 ميكرو ثانية، وبالتالي يكمل المعالج 50,000 تعليمة خلال وقت انتظار جلب بايت من SSD والذي يقدر ب 25 ميكرو ثانية. ويستطيع المعالج إكمال 2,000,000 تعليمة خلال ميلي ثانية وبذلك يستطيع إكمال 40 مليون تعليمة خلال وقت انتظار جلب بايت من القرص الصلب HDD والمقدّر ب 20 ميلي ثانية. إذا لم يكن لدى المعالج أي عملٍ للقيام به خلال عملية انتظار جلب بيانات من القرص الصلب فإنه يبقى خاملًا بلا عمل، لذلك ينتقل المعالج لتنفيذ عملية أخرى ريثما تصل البيانات من القرص الصلب.

أحد أهم التحديات التي تواجه عملية تصميم نظام التشغيل هو الفجوة في الأداء بين الذاكرة الرئيسية والتخزين الدائم، لذلك توفر أنظمة التشغيل والعتاد مجموعة خاصيات الهدف منها سد هذه الفجوة وهذه الخاصيات هي:

  • تحويلات الكتلة (Block transfers): يتراوح الوقت اللازم لتحميل بايت واحد من القرص الصلب بين 5 و 25 ميلي ثانية، بالمقابل فإن الوقت الإضافي لتحميل كتلة حجمها 8 كيبي بايت (KiB) هو وقت مهمل، لذلك تحاول أنظمة التشغيل قراءة كتل كبيرة الحجم في كل عملية وصول إلى القرص الصلب.
  • الجلب المسبق (Prefetching): يستطيع نظام التشغيل في بعض الأحيان توقّعَ أن العملية ستقرأ كتلةً ما ثم يبدأ بتحميل تلك الكتلة من القرص الصلب قبل أن تُطلَب. فإذا فتحتَ ملفًا وقرأت أول كتلة على سبيل المثال فهذا يؤدي إلى وجود احتمال كبير أنك ستقرأ الكتلة الثانية، لذلك سيحمّل نظام التشغيل كتل إضافية قبل أن تُطلَب.
  • التخزين المؤقت (Buffering): يخزّن نظام التشغيل بيانات الكتابة في ملفٍ ما في الذاكرة ولا يكتبها في القرص الصلب مباشرةً، لذلك إذا عدّلت الكتلة مراتٍ متعددة عند وجودها في الذاكرة فلن يكتبها نظام التشغيل في القرص الصلب إلّا مرةً واحدة.
  • التخبئة (Caching): إذا استخدمت عمليةٌ كتلةً ما مؤخرًا فإنها ستستخدمها مرة أخرى قريبًا، وإذا احتفظ نظام التشغيل بنسخة من هذه الكتلة في الذاكرة فإنه سيتعامل مع الطلبات المستقبلية لهذه الكتلة بسرعة الذاكرة.

تُطبَّق بعض الخاصيات السابقة عن طريق العتاد أيضًا، حيث توفر بعض الأقراص الصلبة ذاكرةً مخبئيةً على سبيل المثال وتُخزَّن فيها الكتل المستخدمة مؤخرًا، وتقرأ العديد من الأقراص الصلبة عدة كتل في نفس الوقت على الرغم من وجود كتلة واحدة مطلوبة فقط.

تحسّن الآليات السابقة من أداء البرامج ولكنها لا تغير شيئًا من سلوكها، ولا يتوجب على المبرمجين معرفة الكثير عن تلك الآليات باستثناء حالتين هما:

  1. إذا أصبح أداء البرنامج سيئًا بشكل غير متوقع فيجب عليك معرفة هذه الآليات لتشخيص المشكلة.
  2. يصبح تنقيح أخطاء (debug) البرنامج أصعب عندما تُخزَّن البيانات تخزينًا مؤقتًا (buffered)، فإذا طبع البرنامج قيمة ثم تعطّل البرنامج على سبيل المثال، فإن تلك القيمة لن تظهر لأنها يمكن أن تكون في مخزَن مؤقت (buffer). وإذا كتب برنامجٌ ما بياناتٍ في القرص الصلب ثم أُغلق الحاسوب فجأةً قبل كتابة البيانات في القرص الصلب فيمكن أن تُفقَد تلك البيانات إذا كانت في الذاكرة المخبئية (cache) ولم تنقَل بعد إلى القرص الصلب.

بيانات القرص الصلب الوصفية (Disk metadata)

تكون الكتل التي تشكّل الملف منظمةً في القرص الصلب بحيث قد تكون هذه الكتل مجاورةً لبعضها البعض وبذلك يكون أداء نظام الملفات أفضل، ولكن قد لا تحوي معظم أنظمة التشغيل تخصيصًا متجاورًا (contiguous allocation) للكتل، حيث يكون لأنظمة التشغيل كامل الحرية بوضع كتلةٍ ما في أي مكان تريده على القرص الصلب وتستخدم بنى بيانات مختلفة لتتبع تلك الكتل. تُدعى بنية البيانات في العديد من أنظمة ملفات UNIX ب inode والتي ترمز إلى عقدة دليل (index node). تدعى معلومات الملفات مثل موقع كتل هذه الملفات بالبيانات الوصفية (metadata)، فمحتوى الملف هو بيانات (data) ومعلومات الملف بيانات أيضًا ولكنها بيانات توصف بيانات أخرى لذلك تدعى وصفية (meta). بما أن inodes تتوضع على القرص الصلب مع بقية البيانات لذلك فهي مصممة لتتوافق مع كتل القرص الصلب بدقة. تتضمن inode لنظام UNIX معلومات عن الملف وهذه المعلومات هي معرف (ID) المستخدم مالك الملف، ورايات الأذونات (permission flags) والتي تحدد مَن المسموح له قراءة أو كتابة أو تنفيذ الملف، والعلامات الزمنية (timestamps) التي تحدد آخر تعديل وآخر دخول إلى الملف، وتتضمن أيضًا أرقام أول 12 كتلة من الكتل المشكّلة للملف. فإذا كان حجم الكتلة الواحدة 8 كيبي بايت (KiB) فإن حجم أول 12 كتلة من الملف هو 96 كيبي بايت وهذا الرقم كبير كفاية لأغلبية حجوم الملفات ولكنه ليس بكافٍ لجميع الملفات بالتأكيد، لذلك تحتوي inode مؤشرًا إلى كتلة غير موجهة (indirection block) التي تتضمن مؤشّرات إلى كتلٍ أخرى فقط. يعتمد عدد المؤشرات في كتلةٍ غير موجهةٍ على أحجام الكتل وعددها وهو 1024 كتلةً عادةً، حيث تسطيع كتلةٌ غير موجهة عنونة 8 ميبي بايت (MiB التي تساوي 220 بايتًا) باستخدام 1024 كتلة وبحجم 8 كيبي بايت لكل كتلة، وهذا رقمٌ كافٍ لجميع الملفات باستثناء الملفات الكبيرة أي أنه لا يزال غير كافٍ لجميع الملفات، لذلك تتضمن inode أيضًا مؤشرًا إلى كتلة غير موجهة مضاعفة (double indirection block) التي تتضمن بدورها مؤشرات إلى كتل غير موجهة، وبالتالي يمكننا عنونة 8 جيبي بايت (GiB التي تساوي 230 بايتًا) باستخدام 1024 كتلة غير موجهة. وإذا لم يكن ذلك كافيًا أيضًا فتوجد كتلة غير موجهة ثلاثية (triple indirection block) أخيرًا، والتي تتضمن مؤشرات إلى كتل غير موجهة مضاعفة وبذلك تكون كافية لملف حجمه 8 تيبي بايت (TiB التي تساوي 240 بايتًا) كحدٍ أعلى. بدا ذلك كافيًا لمدة طويلة عندما صُمِّمت inodes لنظام UNIX، ولكنها أضحت قديمةً الآن. تستخدم بعض أنظمة الملفات مثل FAT جدول تخصيص الملف (File Allocation Table) كبديل عن الكتل غير الموجهة، حيث يتضمن جدول التخصيص مدخلةً لكل كتلة وتدعى الكتلة هنا بعنقود (cluster)، ويتضمن المجلد الجذر (root directory) مؤشرًا لأول عنقود في كل ملف، وتشير مدخلة جدول FAT والتي تمثل عنقودًا إلى العنقود التالي في الملف بشكل مشابه للائحة المترابطة (linked list).

تخصيص الكتلة (Block allocation)

ينبغي على أنظمة الملفات تتبّع الكتل التابعة لكل ملف وتتبّع الكتل المتاحة للاستخدام أيضًا، حيث يجد نظام الملفات كتلةً متوفرة لملفٍ ما ثم يخصصها له عندما يُنشَأ هذا الملف، ويجعل نظام الملفات كتل ملفٍ ما متاحةً لإعادة التخصيص عندما يُحذَف ذلك الملف. حيث أهداف تخصيص الكتلة هي ما يلي:

  • السرعة (Speed): يجب أن يكون تخصيص الكتل وتحريرها سريعًا.
  • الحد الأدنى من استهلاك المساحة (Minimal space overhead): يجب أن تكون بنى البيانات التي يستخدمها المخصّص (allocator) صغيرة الحجم بحيث تترك أكبر قدر ممكن من المساحة للبيانات.
  • الحد الأدنى من التجزئة (Minimal fragmentation): إذا وُجدت كتل غير مستخدمة نهائيًا أو مستخدمة جزئيًا فإن هذه المساحة غير المستخدمة تدعى تجزئة (fragmentation).
  • الحد الأعلى من التجاور (Maximum contiguity): يجب أن تكون البيانات التي تُستخدم في الوقت ذاته مجاورة لبعضها البعض فيزيائيًا إذا كان ممكنًا وذلك لتحسين الأداء.

تصميم نظام ملفات يحقق هذه الأهداف أمرٌ صعب خاصةً أن أداء نظام الملفات معتمدٌ على خواص الحِمل (workload characteristics) مثل حجم الملفات وأنماط الوصول (access patterns) وغير ذلك، فنظام الملفات المتماشي جيدًا مع نوع حِمل قد لا يكون أداؤه جيدًا مع نوع حِملٍ آخر، ولهذا السبب تدعم معظم أنظمة التشغيل أنواعًا متعددة من أنظمة الملفات. يُعَد تصميم نظام الملفات مجالًا نشطًا للبحث والتطوير، حيث انتقلت أنظمة تشغيل Linux خلال العشر سنوات الماضية من ext2 والذي كان نظام ملفات UNIX التقليدي إلى ext3 والذي هو نظام ملفات مزودٌ بسجل (journaling) ومُعَد لتحسين السرعة (speed) والتجاور (contiguity)، ثم انتقلت بعد ذلك إلى ext4 الذي يستطيع التعامل مع ملفات وأنظمة ملفات أكبر، وقد يكون هناك انتقال (migration) آخر إلى نظام ملفات B-tree واختصاره Btrfs خلال السنوات القليلة القادمة.

هل كل شيء هو ملف؟

إن تجريد الملف حقيقةً هو تجريدٌ لمجرىً من البايتات (stream of bytes) والذي اتضح أنه مفيدٌ لكثيرٍ من الأشياء وليس لأنظمة الملفات فقط، أنبوب (pipe) نظام UNIX هو مثالٌ على ذلك والذي هو نموذجٌ بسيط للاتصالات بين العمليات (inter-process communication)، فتكون العمليات مُعدّةً بحيث يكون خرج عمليةٍ ما دخلًا لعمليةٍ أخرى. يتصرف الأنبوب على أساس أن أول عملية هي ملفٌ مفتوحٌ للكتابة بالتالي يمكنه أن يستخدم دوال مكتبة C مثل fputs و fprintf وأن العملية الأخرى ملفٌ مفتوحٌ للقراءة أي يمكنه استخدام الدوال fgets و fscanf. وتستخدم شبكة الاتصالات تجريد مجرى البايتات أيضًا، فمِقبس (socket) نظام UNIX هو بنية بيانات تمثل قناة اتصال بين العمليات الموجودة على حواسيب مختلفة عادةً، حيث تستطيع العمليات قراءة بيانات وكتابتها في مِقبس باستخدام دوال تتعامل مع الملفات. تجعل إعادة استخدام تجريد الملف الأمور أسهل بالنسبة للمبرمجين، حيث إنهم غير ملزمين إلا بتعلم واجهة برمجة تطبيقات واحدة (application program interface واختصارها API). كما أنها تجعل البرامج متعددة الاستعمال بما أن البرنامج المجهّز ليعمل مع الملفات قادرٌ على العمل مع بيانات قادمة من الأنابيب (pipes) ومصادر أخرى أيضًا.

ترجمة -وبتصرّف- للفصل Files and file systems من كتاب Think OS A Brief Introduction to Operating Systems





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن