اذهب إلى المحتوى

مفهوم الملف هو تجريد abstraction مناسب إما كحوض للبيانات أو مصدر لها، وبالتالي هو تجريد ممتاز لجميع الأجهزة التي قد يوصلها المرء بالحاسوب. هذا الإدراك هو سر القوة العظيمة لنظام التشغيل يونيكس ويتجلى في مجمَل تصميم كامل المنصة. ويُعَدّ توفير تجريد الأجهزة هذا للمبرمج من الأدوار الرئيسية لنظام التشغيل.

كل شيء عبارة عن ملف

تُعَدّ مقولة كل شيء عبارة عن ملف مبدأً يُستمَد غالبًا من أنظمة يونكس Unix وما يشابهها مثل لينكس linux وبي إس دي BSD.

لنتخيل ملفًا في إطار مألوف مثل معالج النصوص، إذ تكون العمليتان الأساسيتان اللتان نستطيع تنفيذهما على ملف في معالج النصوص التخيلي هذا كما يلي:

  1. قراءته، أي قراءة معالج النصوص البيانات الحالية المحفوظة.
  2. الكتابة ضمنه، أي كتابة المستخدِم بيانات جديدةً.

لنستعرض بعض الطرفيات الشائعة الموصولة بالحاسوب، وما هو ارتباطها بالعمليات الأساسية على الملفات:

  1. الشاشة.
  2. لوحة المفاتيح.
  3. الطابعة.
  4. القرص المدمَج CD-ROM.

تشبه كل من الشاشة والطابعة ملفًا للكتابة فقط، إذ تُعرَض المعلومات نقاطًا على الشاشة أو خطوطًا على الصفحة بدلًا من تخزينه على هيئة بِتّات على القرص؛ أما لوحة المفاتيح، فتُعَدّ مثل ملف للقراءة فقط، إذ ترد البيانات من ضغطات المستخدِم على المفاتيح، وكذلك الأمر بالنسبة للقرص المضغوط CD-ROM مثلًا، لكن تخزَّن البيانات مباشرةً على القرص بدلًا من أن يدخلها المستخدِم عشوائيًا.

وبالتالي فإن مفهوم الملف هو تجريد abstraction مناسب إما لحوض البيانات أو مصدرها، لذا فهو تجريد ممتاز لجميع الأجهزة التي قد يوصلها المرء بالحاسوب، ويُعَدّ هذا الإدراك هو سر القوة العظيمة لنظام التشغيل يونيكس ويتجلى في مجمَل تصميم كامل المنصة، كما يُعَدّ توفير تجريد الأجهزة هذا للمبرمج من الأدوار الرئيسية لنظام التشغيل.

ربما لا نبالغ عندما نقول أنّ التجريد هو المفهوم الأساسي الذي يدعم جميع أشكال الحوسبة الحديثة، إذ لا يمكن لشخص واحد فهم كل الأمور من تصميم واجهة مستخدِم حديثةً إلى العمليات الداخلية لوحدة المعالجة المركزية CPU الحديثة، ناهيك عن بنائها بكاملها بأنفسهم؛ أما بالنسبة للمبرمجين، فالتجريد هو اللغة المشتركة التي تتيح لنا التعاون والابتكار.

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

001abstraction.png

(صورة توضح مفهوم التجريد)

تطبيق التجريد

يُطبَّق التجريد عمومًا بما يسمى واجهة برمجة التطبيق API، ويُعَدّ API مصطلحًا مبهمًا نوعًا ما، إذ يشير إلى أمور مختلفة حسب سياقات الأعمال البرمجية المتنوعة،يصمم المبرمج في الأساس مجموعة دوال functions، ويُوثِّق واجهتها ووظيفتها حسب مبدأ أن التنفيذ الفعلي الذي يزوده بواجهة API يكون مخفيًا.

تقدِّم العديد من تطبيقات الويب على سبيل المثال واجهة API يمكن الوصول إليها عن طريق بروتوكول HTTP، ويطلق الوصول إلى البيانات بهذه الطريقة عدة سلاسل معقدة من استدعاءات الإجراءات البعيدة remote procedure calls واستعلامات قاعدة البيانات database queries وعمليات نقل البيانات data transfers، وتكون جميعها غير مرئية بالنسبة للمستخدِم النهائي الذي يتلقى البيانات المقتضبة ببساطة.

سيألف الذين هم على دراية باللغات البرمجية كائنية التوجه object-oriented مثل جافا Java أو بايثون Python أو ++C مفهوم التجريد في الأصناف classes، إذ تزِّود التوابع methods الصنف بالواجهة لكنها تجرِّد التنفيذ.

تطبيق التجريد بلغة البرمجة C

تُعَدّ مؤشرات الدالة function pointers منهجيةً شائعةً تُستخدَم في نواة نظام تشغيل لينكس وغيرها من الشيفرات البرمجية الأساسية المكتوبة بلغة C والتي لا يكون مفهوم كائنية التوجه مدمجًا فيها، كما يُعَدّ فهم هذا المصطلح أمرًا رئيسيًا لقراءة معظم الشيفرات البرمجية الأساسية المكتوبة بلغة C، إذ يمكّنك فهم طريقة قراءة التجريدات الموجودة ضمن الشيفرة البرمجية من تكوين فكرة عن تصاميم واجهات API الداخلية.

#include <stdio.h>

/* الواجهة البرمجية التي سننفذها */
struct greet_api
{
    int (*say_hello)(char *name);
    int (*say_goodbye)(void);
};

/* تطبيق دالة hello */
int say_hello_fn(char *name)
{
    printf("Hello %s\n", name);
    return 0;
}

/* تطبيق دالة goodbye */
int say_goodbye_fn(void)
{
    printf("Goodbye\n");
    return 0;
}

/* بنية لتنفيذ الواجهة البرمجية*/
struct greet_api greet_api =
{
    .say_hello = say_hello_fn,
    .say_goodbye = say_goodbye_fn
};

/* لا تحتاج الدالة main()‎ معرفة أيّ شيء عن آلية عمل
‫* say_hello/goodbye، فهي لا تعلم إلا أنها تعمل*/
int main(int argc, char *argv[])
{
    greet_api.say_hello(argv[1]);
    greet_api.say_goodbye();

    printf("%p, %p, %p\n", greet_api.say_hello, say_hello_fn, &say_hello_fn);

    exit(0);
}

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

نبدأ بالبنية التي تحدِّد الواجهة البرمجية struct greet_api، فالدوال التي أحيطت أسمائها بأقواس مع محدد المؤشر pointer marker تصف مؤشر الدالة، إذ يصف مؤشر الدالة النموذج الأولي للدالة التي يجب أن يشير إليها، كما سيؤدي توجيهه إلى دالة دون إضافة النوع المُعاد return type الصحيح أو المعاملات الصحيحة على الأقل إلى توليد تحذير من المصرِّف، وإذا تركته في الشيفرة البرمجية، فيحتمل أن يؤدي إلى تنفيذ عملية خاطئة أو أعطال، لذلك إذ ستجد غالبًا أنّ أسماء المعامِلات parameters قد حُذفت ولم يُحدَّد إلا نوع المعامِل، ويتيح هذا للمنفذ تحديد أسماء المعامِلات لتجنب ورود تحذيرات من المصرِّف.

سنتناول الآن تنفيذ الواجهة البرمجية، إذ ستجد عادةً في الدوال الأعقد مصطلحًا يدل على أنّ دوال تنفيذ الواجهة البرمجية هي عبارة عن غلاف حول الدوال الأخرى التي تكون عادةً مسبوقةً بشرطة سفلية أو اثنتين، إذ ستستدعي الدالة ()say_hello_fn دالةً أخرى ()say_hello_function_ على سبيل المثال، ولهذه عدة استخدامات، إذ نستخدمها عمومًا لنحظى بأجزاء أبسط وأصغر من الواجهة API -في تنظيم الوسائط arguments أو التحقق منها مثلًا- منفصلةً عن عملية التنفيذ الأعقد، ويسهّل هذا غالبًا المسار إلى تحقيق تغييرات ملموسة في العمليات الداخلية مع ضمان بقاء الواجهة ثابتة، إلا أنّ عملية التنفيذ هنا بسيطة جدًا ولا تحتاج حتى إلى دوال داعمة خاصة بها، كما يختلف مدلول بادئات الدالة التي تكون شرطة سفلية واحدة _ أو مزدوجة __ أو حتى ثلاثية ___ باختلاف المشاريع، لكنها عمومًا تُعَدّ تحذيرًا مرئيًا بأنه لا يُفترَض استدعاء الدالة مباشرةً من خارج الواجهة.

ملاحظة: قد يُشار إلى دالة الشرطة السفلية المزدوجة ‎__‎foo‎ في المحادثات بالتابع السحري dunder foo وتكون فو foo مثل س أو ص في الجبر.

نملأ مؤشرات الدالة في المرحلة ما قبل الأخيرة في struct greet_api greet_api، إذ يُعَدّ اسم الدالة مؤشرًا، لذا لا حاجة لأخذ عنوان الدالة مثل say_hello_fn&، وأخيرًا يمكننا استدعاء دوال واجهة API ضمن بنية main.

ستلاحظ هذا المصطلح باستمرار عند تصفحك الشيفرة المصدرية source code،ـ ويمكن أن نوضح ذلك في هذا المثال البسيط الذي اجتزأناه من الملف include/linux/virtio.h في الشيفرة المصدرية لنواة نظام لينكس:

 /**
 * virtio_driver - operations for a virtio I/O driver
 * @driver: underlying device driver (populate name and owner).
 * @id_table: the ids serviced by this driver.
 * @feature_table: an array of feature numbers supported by this driver.
 * @feature_table_size: number of entries in the feature table array.
 * @probe: the function to call when a device is found.  Returns 0 or -errno.
 * @remove: the function to call when a device is removed.
 * @config_changed: optional function to call when the device configuration
 *    changes; may be called in interrupt context.
 */
struct virtio_driver {
        struct device_driver driver;
        const struct virtio_device_id *id_table;
        const unsigned int *feature_table;
        unsigned int feature_table_size;
        int (*probe)(struct virtio_device *dev);
        void (*scan)(struct virtio_device *dev);
        void (*remove)(struct virtio_device *dev);
        void (*config_changed)(struct virtio_device *dev);
#ifdef CONFIG_PM
        int (*freeze)(struct virtio_device *dev);
        int (*restore)(struct virtio_device *dev);
#endif
};

كل المطلوب هو أن نفهم فهمًا سطحيًا أنّ هذه البنية هي وصف لجهاز الإدخال والإخراج I/O الافتراضي، ونلاحظ أن المتوقَّع من مستخدِم واجهة API هذه -أي كاتب تعريف الجهاز device driver- هو تقديم عدد من الدوال التي ستُستدعَى في شروط مختلفة أثناء تشغيل النظام، أي عند تقصّي عتاد جديد hardware أو عند إزالة عتاد ما، …إلخ على سبيل المثال، كما يحتوي على مجموعة بيانات، وهي البُنى التي يجب تعبئتها بالبيانات المرتبطة بها، كما يُعَدّ البدء بعناصر توصيف مثل هذه أسهل طريقة لبدء فهم الطبقات المختلفة لشيفرة النواة البرمجية.

المكتبات

تؤدي المكتبات دورَين يوضحان التجريد، هما:

  • تتيح للمبرمجين إعادة استخدام الشيفرة البرمجية المتاح الوصول إليها عمومًا.
  • تؤدي دور الصندوق الأسود في تنفيذ الخصائص الوظيفية عن المبرمج.

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

يشار إلى المكتبة القياسية في منصة يونكس باسم libc عمومًا، ومهمتها توفير الواجهة الأساسية للنظام، والاستدعاءات الأساسية مثل ()read و ()write و ()printf، كما توصَف واجهة API هذه بمجملها بتوصيف يسمى بوزيكس POSIX، وهي متاحة مجانًا على الإنترنت وتصف العديد من الاستدعاءات التي تؤلف واجهة API القياسية في نظام يونكس.

تتبع معظم منصات يونكس عمومًا معايير بوزيكس، مع وجود بعض الفروقات الطفيفة التي تكون مهمةً أحيانًا (وهذا ما يفسر تعقيد أنظمة بناء غنو Gnu autotools المختلفة، التي تحاول دومًا إخفاء هذه الفروقات عنك). يحتوي نظام لينوكس على العديد من الواجهات التي لا تتبع معايير بوزيكس، لذا فإن بناء تطبيقات تستخدم هذه الواجهات دون غيرها لن يجعل تطبيقك محمولًا portable بما يكفي.

تُعّدّ المكتبات تجريدًا أساسيًا يضم الكثير من التفاصيل، وسنتناول في فصول لاحقة آلية عمل المكتبات بالتفصيل.

ترجمة -وبتصرّف- للقسم Everything is a file!‎ والقسم Implementing abstraction من الفصل Chapter 1. General Unix and Advanced C من كتاب Computer Science from the Bottom Up.

اقرأ أيضًا


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

أفضل التعليقات

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



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...