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

أهم المفاهيم التي تنظم العمليات وتعالجها في معمارية الحاسوب الحديثة


Ola Abbas

سنتعرّف في هذا المقال على ثلاثة من أهم المفاهيم التي تنظم العمليات وتعالجها في معمارية الحواسيب الحديثة وهي الجدولة Scheduling والصدَفة Shell والإشارات Signals.

الجدولة Scheduling

يحتوي النظام المُشغَّل على مئات أو حتى أُلوف العمليات، ويُطلَق على جزء النواة Kernel الذي يتعقّب جميع هذه العمليات اسم المجدوِل Scheduler لأنه يجدول أيّ عملية يجب تشغيلها لاحقًا.

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

ينشئ الناس دائمًا خوارزميات جديدةً، كما يمكنك إنشاء خوارزمياتك الخاصة بسهولة إلى حد ما، ولكن هناك عدد من المكونات المختلفة للجدولة.

الجدولة ذات الأولوية Preemptive والجدولة التعاونية Co-operative

يمكن أن تنقسم استراتيجيات الجدولة إلى فئتين:

  1. الجدولة التعاونية Co-operative Scheduling: هي المكان الذي تتخلى فيه العملية المُشغَّلة حاليًا طواعيةً عن التنفيذ للسماح بتشغيل عملية أخرى، والعيب في هذه الاستراتيجية هو أنّ العملية يمكنها اتخاذ قرار بعدم التخلي عن التنفيذ بسبب خطأ تسبَّب في شكل من أشكال الحلقة اللانهائية مثلًا، وبالتالي لا يمكن تشغيل أيّ شيء آخر.
  2. الجدولة الاستباقية Preemptive Scheduling: هي المكان الذي تُقاطَع فيه العملية لإيقافها للسماح بتشغيل عملية أخرى، إذ تحصل كل عملية على شريحة زمنية Time-slice لتعمل فيها، كما سيُعاد ضبط عدّاد الوقت عند كل عملية تبديل سياق Context Switching وستُشغَّل العملية ثم تُقاطَع عند انتهاء الشريحة الزمنية، فتبديل السياق Context Switching هو العملية التي تطبّقها النواة للتبديل من عملية إلى أخرى، في حين يتعامل العتاد مع المقاطعة على أنها مستقلة عن العملية المُشغَّلة، وبالتالي سيعود التحكم إلى نظام التشغيل عند حدوث المقاطعة، كما يمكن أن يقرِّر المجدوِل العملية التالية التي ستُشغَّل، وهذا هو نوع الجدولة الذي تستخدمه جميع أنظمة التشغيل الحديثة.

الوقت الفعلي Realtime

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

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

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

القيمة اللطيفة

تسنِد أنظمة يونيكس لكل عملية قيمةً لطيفة Nice Value، إذ ينظر المجدوِل إلى هذه القيمة ويمكن أن يعطي الأولوية لتلك العمليات التي تتمتع بأعلى قيمة لطيفة.

مجدول لينكس

خضع مجدول لينكس ولا يزال يخضع للعديد من التغييرات، إذ يحاول المطورون الجدد تحسين سلوكه، ويُعرَف المجدول الحالي باسم المجدول O(1)‎ الذي يشير إلى الخاصية التي تمثل أنّ المجدول سيختار العملية التالية لتشغيلها في فترة زمنية ثابتة بغض النظر عن عدد العمليات التي يجب عليه الاختيار من بينها.

تُعَدّ صيغة Big-O طريقةً لوصف الوقت الذي تستغرقه الخوارزمية للتشغيل بالنظر إلى الدخل المتزايد، فإذا استغرقت الخوارزمية ضعف الوقت للتشغيل مع ضعف الدخل، فهذا يؤدي إلى التزايد خطيًا، وإذا استغرقت خوارزمية أخرى أربعة أضعاف الوقت للتشغيل مع ضعف الدخل، فهذا يؤدي إلى تزايد أسي؛ أما إذا استغرق الأمر الوقت نفسه مهما كان مقدار الدخل، فستُشغَّل الخوارزمية في وقت ثابت، ويمكنك رؤية أنه كلما كانت الخوارزمية تنمو بصورة أبطأ مع مزيد من الدخل، كان ذلك أفضل.

استخدمت مجدولات لينكس السابقة مفهوم الجودة Goodness لتحديد العملية التالية لتشغيلها، إذ يُحتفَظ بجميع المهام المُحتمَلة في رتل تشغيل Run Queue، وهو قائمة مترابطة من العمليات التي تعرِف النواة أنها في حالة قابلية للتشغيل، أي لا تنتظر نشاطًا من القرص الصلب أو ليست في حالة سكون.

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

شكل المجدول O(1)‎

المجدول O(1)‎

يستخدِم المجدول O(1)‎ بنية رتل التشغيل الموضح في الشكل السابق، كما يحتوي رتل التشغيل على عدد من الحزم Buckets مرتبةً حسب الأولوية وخارطة نقطية Bitmap تشير إلى الحزم التي تحتوي على عمليات متاحة، إذ يُعَدّ البحث عن العملية التالية لتشغيلها بمثابة قراءة الخارطة النقطية للعثور على حزمة العمليات الأولى، ثم اختيار العملية الأولى من رتل الحزم.

يحتفظ المجدول ببنيتَين هما مصفوفة العمليات النشطة Active التي يمكن تشغيلها ومصفوفة العمليات منتهية الصلاحية Expired التي استخدمت شريحتها الزمنية بالكامل، كما يمكن تبديل هاتين البنيتَين ببساطة من خلال تعديل المؤشرات عندما يكون لجميع العمليات بعض الوقت من وحدة المعالجة المركزية.

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

الصدفة Shell

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

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

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

الإشارات Signals

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

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

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

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

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

لا بد أنك رأيت الأمر kill -9 الذي يأتي من تطبيق الإشارة SIGKILL، إذ تُعرَّف الإشارة SIGKILL على أنها 0x9، لذا ستتوقف العملية المحددة مباشرةً عند تحديدها على أساس وسيط لبرنامج kill، ونظرًا لأنه لا يمكن للعملية اختيار تجاهل هذه الإشارة أو معالجتها، فسيُنظَر إلى هذه الإشارة على أنها الملاذ الأخير، إذ لن يكون لدى البرنامج فرصةً للتنظيف أو الإنهاء بصورة نظيفة.

يُفضَّل إرسال الإشارة SIGTERM -للإنهاء Terminate- إلى العملية أولًا، فإذا تعطلت أو لم تنتهي، فيمكنك اللجوء إلى الإشارة SIGKILL، كما تثبّت معظم البرامج معالجًا للإشارة SIGHUP، أي تعليق Hangup الطرفيات وأجهزة المودِم التسلسلية، إذ سيعيد هذا المعالج تحميل البرنامج لالتقاط التغييرات في ملف الإعداد أو ما شابه ذلك.

إذا سبق لك وبرمجتَ على نظام يونيكس، فستكون على دراية بأخطاء التقطيع segmentation faults عندما تحاول القراءة أو الكتابة في ذاكرة غير مخصَّصة لك، فإذا لاحظت النواة أنك تحاول الوصول إلى ذاكرة ليست مخصَّصة لك، فسترسل لك إشارة خطأ تقطيع segmentation fault signal، ولن تمتلك العملية معالجًا مثبَّتًا لهذه الإشارة، وبالتالي فإنّ الإجراء الافتراضي هو إنهاء البرنامج وتعطيل برنامجك، كما يمكن أن يثبّت البرنامج معالجًا لأخطاء التقطيع في بعض الحالات المحدودة.

لكن يمكنك التساؤل عمّا يحدث بعد تلقي الإشارة، إذ سيُعاد التحكم إلى العملية التي تستأنف عملها من حيث توقفت بمجرد انتهاء معالج الإشارة من عمله، ويقدّم البرنامج البسيط التالي تشغيل بعض الإشارات:

$ cat signal.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigint_handler(int signum)
{
  printf("got SIGINT\n");
}

int main(void)
{
  signal(SIGINT, sigint_handler);
  printf("pid is %d\n", getpid());
  while (1)
    sleep(1);
}
$ gcc -Wall -o signal signal.c
$ ./signal
pid is 2859
got SIGINT # press ctrl-c 
           # press ctrl-z
[1]+  Stopped                 ./signal

$ kill -SIGINT 2859
$ fg
./signal
got SIGINT
Quit # press ctrl-\

$

يعرّف البرنامج البسيط السابق معالجًا للإشارة SIGINT التي تُرسَل عندما يضغط المستخدِم على الاختصار ctrl-c، إذ تُعرَّف جميع إشارات النظام في مكتبة signal.h بما في ذلك الدالة signal التي تسمح لنا بتسجيل دالة المعالجة.

يبقى البرنامج ضمن حلقة لا تفعل شيئًا حتى يتوقف، وحاول الضغط على الاختصار ctrl-c عند بدء البرنامج لإنهائه، إذ يُستدعَى المعالج ونحصل على الخرج المتوقَّع بدلًا من اتخاذ الإجراء الافتراضي، ثم نضغط بعد ذلك على الاختصار ctrl-z الذي يرسل الإشارة SIGSTOP التي تضع العملية افتراضيًا في وضع السكون، أي أنها لم تُوضَع في رتل تشغيل المجدول وبالتالي تُعَدّ خاملةً في النظام.

نستخدم برنامج kill لإرسال الإشارة نفسها من نافذة طرفية أخرى، إذ يمكن تطبيق ذلك فعليًا باستخدام استدعاء النظام kill الذي يأخذ إشارة ومعرّف PID لإرسالها، ويُعَدّ اسم هذه الدالة خاطئًا بعض الشيء، إذ لا تقتل جميعُ الإشارات العمليةَ فعليًا، ولكن تُستخدَم الدالة signal لتسجيل المعالج Handler، كما توضَع الإشارة في رتل خاص بهذه العملية عند توقفها، وبالتالي تأخذ النواة ملاحظةً بالإشارة وتسلّمها في الوقت المناسب.

ننبّه العملية بعد ذلك باستخدام الأمر fg الذي يرسل الإشارة SIGCONT إلى العملية، مما يؤدي إلى تنشيط العملية افتراضيًا، كما تدرك النواة وضع العملية في رتل التشغيل وتمنحها وقتًا من وحدة المعالجة المركزية مرةً أخرى، إذ نرى في هذه المرحلة تسليم الإشارة الموجودة في رتل التشغيل.

نحاول أخيرًا الضغط على الاختصار ctrl-\‎ الذي يرسل الإشارة SIGQUIT -أي إلغاء- إلى العملية، ويأتي خرج الإلغاء Quit من استخدام مزيد من الإشارات بالرغم من إلغاء العملية، وإذا كان لدى الأب عملية ابن ميتة أو منتهية، فسيحصل على الإشارة SIGCHLD، إذ تُعَدّ الصدَفةُ أنها العملية الأب في هذه الحالة، أي أنها ستحصل على الإشارة.

تذكّر أنّ العملية الزومبي Zombie التي يجب حصادها باستخدام الاستدعاء wait للحصول على الشيفرة المُعادة من العملية الابن، ولكن هناك شيء آخر يمنحه الابن للأب وهو رقم الإشارة التي أدّت إلى موت الابن، وهكذا تعرف الصدَفة أنّ العملية الابن قد ماتت أو انتهت بسبب الإشارة SIGABRT وتطبع معلومات أخرى للمستخدِم على أساس خدمة إعلامية، إذ تحدُث العملية نفسها لطباعة خطأ التقطيع Segmentation Fault عندما تموت العملية الابن بسبب الإشارة SIGSEGV.

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

ترجمة -وبتصرُّف- للأقسام Context Switching و Scheduling و The Shell و Signals من الفصل The Process من كتاب Computer Science from the Bottom Up لصاحبه Ian Wienand.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...