التطبيق، العمليات والخيوط
هذا الدرس مبني على الكتاب الممتاز Operating Systems: Three Easy Pieces لـ Remzi H. Arpaci-Dusseau و Andrea C. Arpaci-Dusseau.
التطبيق والعمليات
مصطلح التطبيق هو مفهوم عام يشير إلى برنامج يتم تشغيله بواسطة المستخدم. إنه ليس مصطلحًا تقنيًا دقيقًا، ولكنه غالبًا ما يُستخدم للإشارة إلى برنامج يتفاعل مع المستخدم، مثل محرر النصوص، متصفح الويب أو لعبة.
العملية هي نسخة من برنامج قيد التشغيل. تمثل بيئة تنفيذ التطبيق، بما في ذلك موارده وحالته. يتم إنشاء عملية عند تشغيل تطبيق ويمكن إنهاؤها عند إغلاق التطبيق. يمكن تشغيل نفس البرنامج عدة مرات، مما يخلق عدة عمليات منفصلة.
بشكل أكثر رسمية، في البداية يكون البرنامج مجرد ملف ثنائي قابل للتنفيذ مخزن على القرص. يحتوي هذا الملف الثنائي على:
- كود الآلة للتطبيق، وهو الكود القابل للتنفيذ بواسطة المعالج.
- البيانات الثابتة، وهي المتغيرات العالمية والثوابت المستخدمة بواسطة التطبيق.
- الموارد اللازمة لتنفيذ التطبيق، مثل المكتبات المشتركة وملفات التكوين.
يتم إنشاء عملية عندما يقوم نظام التشغيل بتحميل الملف الثنائي في الذاكرة وتهيئة بيئة التنفيذ. لذا، بشكل مجازي، العملية هي نسخة من البرنامج في الذاكرة، مع مساحة العنوان الخاصة بها، ومواردها الخاصة وحالتها الخاصة.
بيئة التنفيذ
تشمل بيئة تنفيذ العملية:
- معرف العملية (PID)، وهو رقم فريد يُخصص بواسطة نظام التشغيل لتحديد العملية.
- مساحة العنوان، وهي الذاكرة المخصصة للعملية لتخزين كودها وبياناتها ومكدس تنفيذها.
- واصفات الملفات، وهي مراجع للملفات المفتوحة بواسطة العملية.
- متغيرات البيئة، وهي أزواج مفتاح-قيمة تُستخدم لتكوين سلوك العملية.
- الإشارات، وهي إشعارات تُرسل إلى العملية لإعلامها بحدوث حدث، مثل انقطاع أو خطأ.
- الخيوط، وهي وحدات تنفيذ خفيفة داخل عملية. يمكن أن تحتوي العملية على عدة خيوط تشترك في نفس مساحة العنوان والموارد، ولكنها تُنفذ بشكل مستقل عن بعضها البعض.
مساحة العنوان
عند إنشاء عملية، يقوم نظام التشغيل بتخصيص مساحة عنوان لها، وهي نطاق من عناوين الذاكرة التي يمكن للعملية استخدامها لتخزين كودها وبياناتها ومكدس تنفيذها. هذه المساحة معزولة عن العمليات الأخرى، مما يعني أن العملية لا يمكنها الوصول مباشرة إلى ذاكرة عملية أخرى.
في الواقع، من وجهة نظر البرنامج، يبدو أن الذاكرة كلها تحت تصرفه، دون القلق بشأن العمليات الأخرى. تُقسم الذاكرة إلى بعض القطاعات الكبيرة:
- قطاع الكود، الذي يحتوي على الكود القابل للتنفيذ للبرنامج.
- قطاع البيانات، الذي يحتوي على المتغيرات العالمية وهياكل البيانات الثابتة.
- قطاع المكدس، الذي يُستخدم لتخزين المتغيرات المحلية ومعلمات الوظائف.
- قطاع الكومة، الذي يُستخدم لتخصيص الذاكرة الديناميكي (على سبيل المثال، باستخدام
mallocفي C أوnewفي C++).
مشاركة وحدة المعالجة المركزية
من المحتمل جدًا أن يكون لديك عدد أكبر بكثير من العمليات النشطة مقارنة بعدد نوى المعالج على جهازك. لذلك، يجب إيجاد وسيلة لمشاركة وحدة المعالجة المركزية بين العمليات المختلفة بطريقة شفافة للمستخدم.
الفكرة الرئيسية هي جعل كل عملية تعتقد أن لديها وحدة المعالجة المركزية بالكامل لنفسها. لتحقيق ذلك، يستخدم نظام التشغيل آلية تُسمى التخطيط (أو scheduling بالإنجليزية). يتضمن التخطيط تخصيص شرائح زمنية (أو quanta) لكل عملية، بحيث يمكن لكل عملية أن تُنفذ لفترة معينة قبل أن تُقطع وتفسح المجال لعملية أخرى.
عندما تُقطع عملية، يتم حفظ حالتها في نظام التشغيل، بحيث يمكن استئناف تنفيذها لاحقًا من نفس النقطة. يستخدم نظام التشغيل خوارزمية تخطيط لتحديد أي عملية يجب أن تُنفذ في لحظة معينة. إذا كنت قد سمعت من قبل عن مصطلح "تبديل السياق"، فهذا هو بالضبط ما يعنيه: يقوم نظام التشغيل بحفظ حالة العملية الحالية واستعادتها لاحقًا لاستئناف تنفيذها.
كما أن نظام التشغيل هو الذي يدير أي عملية تُنفذ على أي نواة معالج. يمكنه نقل عملية من نواة إلى أخرى لتحسين استخدام موارد النظام. يتيح ذلك توزيع عبء العمل بين النوى المختلفة وتحسين أداء النظام.
كمستخدم، لا تحتاج إلى القلق بشأن تخطيط العمليات، حيث يتولى نظام التشغيل ذلك تلقائيًا. ومع ذلك، يمكنك التأثير على التخطيط عن طريق تعديل أولوية العمليات أو تقاربها (أي النواة التي يجب أن تُنفذ عليها).
على نظام Windows، يمكنك استخدام مدير المهام (علامة التبويب التفاصيل) لتعديل أولوية عملية أو تقاربها.

العمليات والخيوط
يعمل نظام التشغيل بجد لعزل العمليات عن بعضها البعض، ولكن هناك حالات يكون من المرغوب فيها أن يتمكن نفس البرنامج من تنفيذ مهام متعددة بالتوازي. كما أنه من الأسهل أحيانًا أن تشترك بعض المهام في الموارد، مثل الذاكرة، بدلاً من تكرار هذه الموارد لكل مهمة. هنا يأتي دور الخيوط.
الخيط هو وحدة تنفيذ خفيفة داخل عملية. يمكن أن تحتوي العملية على عدة خيوط تشترك في نفس مساحة العنوان (الذاكرة) باستثناء المكدس، ولكنها تُنفذ بشكل مستقل عن بعضها البعض. لذا إذا كان لديك عملية تحتوي على عدة خيوط، يمكن لهذه الخيوط أن تُنفذ بالتوازي على عدة نوى معالج، مما يُحسن أداء البرنامج. حتى بدون وجود عدة نوى معالج، تُمكن الخيوط من استخدام وحدة المعالجة المركزية بشكل أفضل عن طريق تجنب أوقات الانتظار غير الضرورية، مثل انتظار استجابة الشبكة أو تنفيذ عمليات الإدخال/الإخراج.
لذلك يمكن اعتبار الخيط كعملية خفيفة تشترك في ذاكرة العملية الأم. كما أنه مدعوم من قبل تخطيط نظام التشغيل. لذا، مثل العمليات، من الصعب عادةً التنبؤ بالضبط متى سيتم تنفيذ خيط، لأن ذلك يعتمد على تخطيط نظام التشغيل وحمل العمل الحالي للنظام.
خيوط النواة وخيوط المستخدم
بطريقة مربكة قليلاً، هناك مفهوم آخر للخيوط. في الواقع، يمكن التمييز بين الخيوط التي يديرها نظام التشغيل (تُسمى خيوط النواة) والخيوط التي يديرها التطبيق (تُسمى خيوط المستخدم). تُدار خيوط النواة بواسطة نظام التشغيل وتُخطط بواسطة النواة. تتوافق مع الوصف أعلاه. يمكنها أن تُنفذ بالتوازي على عدة نوى معالج وتكون معزولة عن بعضها البعض (باستثناء الذاكرة).
تُدار خيوط المستخدم (الخيوط الخضراء، coroutines في Go، الألياف في Rust، إلخ) بواسطة التطبيق وتُخطط بواسطة التطبيق نفسه. تكون عادةً أخف من خيوط النواة، لأنها لا تتطلب إدارة من قبل نظام التشغيل. ومع ذلك، لا يمكنها أن تُنفذ بالتوازي على عدة نوى معالج، لأنها تُخطط بواسطة التطبيق وليس بواسطة نظام التشغيل. إنه كما لو أن العملية تقسم وقتها بنفسها وتقوم بتخطيطها الخاص. ولكن كل ذلك داخل عملية واحدة أو خيط واحد.
بعض اللغات مثل Go تخلط بين المفهومين. تسمح بوجود M coroutines (خيوط المستخدم) التي تُنفذ على N خيوط (خيوط النواة). يدير بيئة تشغيل Go تخطيط coroutines على الخيوط. يتيح ذلك إدارة دقيقة للموارد مع الاستفادة من تخطيط نظام التشغيل والتوازي متعدد النوى.