التفرع (استدعاء النظام)

في مجال الحوسبة ، تُعرف عملية fork بأنها عملية يقوم فيها برنامج بإنشاء نسخة من نفسه. وعادةً ما تُنفذ كغلاف برمجي بلغة C القياسية لوظائف fork وclone وغيرها من استدعاءات النظام في نواة النظام . لسنوات عديدة، كانت fork هي الطريقة الأساسية لإنشاء العمليات في أنظمة يونكس والأنظمة الشبيهة بيونكس ، ولا تزال واجهةً أساسيةً للامتثال لمعيار POSIX . مع ذلك، فقدت شعبيتها في السنوات الأخيرة نتيجةً لعيوبها، بما في ذلك ضعف الأداء، وانعدام أمان الخيوط ، وكونها مصدرًا شائعًا للثغرات الأمنية . [ 1 ]

ملخص

في أنظمة التشغيل متعددة المهام ، تحتاج العمليات (البرامج قيد التشغيل) إلى طريقة لإنشاء عمليات جديدة، مثلاً لتشغيل برامج أخرى. عادةً ما تكون عملية التفرع (Fork) ومشتقاتها هي الطريقة الوحيدة للقيام بذلك في الأنظمة الشبيهة بنظام يونكس. لكي تبدأ عملية ما بتنفيذ برنامج مختلف، تقوم أولاً بالتفرع لإنشاء نسخة من نفسها. ثم، تقوم النسخة، المسماة " العملية الفرعية "، بإجراء أي تغييرات بيئية تحتاجها، ثم تستدعي دالة النظام exec لاستبدال نفسها بالبرنامج الجديد: فتتوقف عن تنفيذ برنامجها السابق لصالح البرنامج الجديد. (أو، في حالات نادرة، تتخلى العملية الفرعية عن استدعاء exec وتستمر في تنفيذ بعض وظائف البرنامج الأصلي كعملية منفصلة).

تُنشئ عملية التفرع (fork) مساحة عنوان منفصلة للعملية الفرعية. تمتلك العملية الفرعية نسخة طبق الأصل من جميع قطاعات ذاكرة العملية الأصلية. في إصدارات يونكس الحديثة التي تتبع نموذج الذاكرة الافتراضية بدءًا من SunOS 4.0، يتم تطبيق دلالات النسخ عند الكتابة ، ولا يلزم نسخ الذاكرة الفعلية. بدلاً من ذلك، يمكن لصفحات الذاكرة الافتراضية في كلتا العمليتين الإشارة إلى نفس صفحات الذاكرة الفعلية حتى تقوم إحداهما بالكتابة إلى إحدى هذه الصفحات، وعندها يتم نسخها. يُعد هذا التحسين مهمًا في الحالة الشائعة التي تُستخدم فيها عملية التفرع (fork) مع الأمر exec لتنفيذ برنامج جديد: عادةً، تُنفذ العملية الفرعية مجموعة صغيرة من الإجراءات قبل أن تتوقف عن تنفيذ برنامجها لصالح البرنامج المراد تشغيله، ولا تحتاج إلا إلى عدد قليل جدًا، إن وُجد، من هياكل بيانات العملية الأصلية .

عندما تستدعي عملية ما دالة fork، تُعتبر هي العملية الأصلية ، وتُعتبر العملية المُنشأة حديثًا هي العملية الفرعية. بعد استدعاء fork، لا تُشغّل العمليتان البرنامج نفسه فحسب، بل تستأنفان التنفيذ كما لو كانتا قد استدعتا دالة النظام. عندئذٍ، يمكنهما فحص القيمة المُعادة من الدالة لتحديد حالتهما، سواء كانت أصلية أو فرعية، والتصرف وفقًا لذلك.

يعود تاريخ مفهوم التفرع (fork) إلى ستينيات القرن العشرين، حيث ظهرت إحدى أقدم الإشارات إليه في كتاب " تصميم نظام متعدد المعالجات" (A Multiprocessor System Design) لميلفن كونواي ، الذي نُشر عام ١٩٦٢. [ ٢ ] وقد حفّزت ورقة كونواي ل. بيتر دويتش على تطبيق مفهوم التفرع في نظام المشاركة الزمنية جيني (GENIE) ، حيث استعاره كين تومسون لأول مرة [ ٣ ] في نظام يونكس البحثي (Research Unix) . [ ٤ ] [ ٥ ] وأصبح التفرع لاحقًا واجهة قياسية في نظام بوزيكس (POSIX) . [ ٦ ]

اِصطِلاحِيّ

مثال

يوضح المثال التالي لبرنامج "Hello, World!" آلية عمل استدعاء النظام fork في لغة البرمجة C. يقوم البرنامج بتقسيم البرنامج إلى عمليتين، حيث تحدد كل منهما الوظيفة التي ستؤديها بناءً على القيمة المُعادة من استدعاء النظام fork. تم حذف التعليمات البرمجية النمطية مثل تضمين ملفات الرأس .

#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main ( void ) { pid_t pid = fork ();إذا كان ( pid == -1 ) { perror ( "فشل إنشاء العملية الفرعية" ); return EXIT_FAILURE ; } else if ( pid == 0 ) { printf ( "مرحباً من العملية الفرعية! \n " ); return EXIT_SUCCESS ; } else { int status ; waitpid ( pid , & status , 0 ); } return EXIT_SUCCESS ; }

فيما يلي تحليل تفصيلي لهذا البرنامج.

pid_t pid = fork ();

يستدعي البيان الأول في الدالة الرئيسية استدعاء النظام fork لتقسيم التنفيذ إلى عمليتين. تُسجل القيمة المُعادة من fork في متغير من النوع pid_t ، وهو نوع POSIX لمعرفات العمليات (PIDs).

إذا كان ( pid == -1 ) { perror ( "فشل التفرع" ); return EXIT_FAILURE ; }

يشير الرقم ناقص واحد إلى وجود خطأ في عملية التفرع : لم يتم إنشاء عملية جديدة، لذلك يتم طباعة رسالة خطأ.

إذا نجحت عملية التفرع (fork) ، فسيصبح لدينا عمليتان، كلتاهما تُنفذان الدالة الرئيسية من النقطة التي انتهت عندها العملية . ولجعل العمليتين تُنفذان مهامًا مختلفة، يجب على البرنامج أن يعتمد على القيمة المُعادة من العملية لتحديد ما إذا كان يُنفذ كعملية فرعية أم كعملية أصلية .

وإلا إذا كان ( pid == 0 ) { printf ( "مرحباً من العملية الفرعية! \n " ); return EXIT_SUCCESS ; }

في العملية الفرعية، تظهر القيمة المُعادة كصفر (وهو مُعرّف عملية غير صالح ). تقوم العملية الفرعية بطباعة رسالة الترحيب المطلوبة، ثم تُنهي عملها. (لأسباب تقنية، يجب استخدام دالة الخروج POSIX _exit هنا بدلاً من دالة الخروج القياسية في لغة C ).

وإلا { int status ; waitpid ( pid , & status , 0 ); }

تستقبل العملية الأخرى، وهي العملية الأصلية، من عملية التفرع (fork) مُعرّف العملية الفرعية، وهو دائمًا عدد موجب. تُمرّر العملية الأصلية هذا المُعرّف إلى استدعاء النظام waitpid لتعليق التنفيذ حتى تنتهي العملية الفرعية. عندئذٍ، تستأنف العملية الأصلية التنفيذ وتخرج باستخدام عبارة return .

تواصل

تبدأ العملية الفرعية بنسخة من مُعرّفات ملفات العملية الأصلية . [ 6 ] للتواصل بين العمليات وتبادل البيانات ومزامنتها بين العملية الأصلية والعملية الفرعية، يمكن استخدام الأنابيب . [ 7 ] غالبًا ما تُنشئ العملية الأصلية أنبوبًا واحدًا أو عدة أنابيب، ثم بعد التفرع، تُغلق العمليات الفرعية نهايات الأنابيب التي لا تحتاجها. [ 7 ] لمشاركة نفس المتغير بين العملية الأصلية والعملية الفرعية، يجب استخدام تعيين الذاكرة أو الذاكرة المشتركة . [ 8 ]

المتغيرات

Vfork

Vfork هو شكلٌ مُعدَّل من fork، له نفس اصطلاح الاستدعاء ونفس الدلالات تقريبًا، ولكنه يُستخدم فقط في حالاتٍ مُحدَّدة. نشأ في إصدار 3BSD من يونكس، [ 9 ] [ 10 ] [ 11 ] وهو أول إصدار من يونكس يدعم الذاكرة الافتراضية. تم توحيده من قِبل POSIX، مما سمح لـ vfork بأن يكون له نفس سلوك fork تمامًا، ولكن تم تصنيفه على أنه قديم في إصدار 2004 [ 12 ] واستُبدل بـ posix_spawn () (الذي يُنفَّذ عادةً عبر vfork) في الإصدارات اللاحقة.

عند استدعاء دالة النظام vfork، يتم تعليق العملية الأصلية حتى تُكمل العملية الفرعية تنفيذها أو يتم استبدالها بصورة تنفيذية جديدة عبر إحدى دوال النظام " exec ". تستعير العملية الفرعية وحدة إدارة الذاكرة المُهيأة من العملية الأصلية، وتُشارك صفحات الذاكرة بين العمليتين دون نسخ، وبالأخص دون أي دلالات نسخ عند الكتابة ؛ [ 12 ] وبالتالي، إذا أجرت العملية الفرعية تعديلًا على أي من الصفحات المشتركة، فلن يتم إنشاء صفحة جديدة، وستكون الصفحات المُعدلة مرئية للعملية الأصلية أيضًا. نظرًا لعدم وجود أي نسخ للصفحات (مما يستهلك ذاكرة إضافية)، تُعد هذه التقنية مُحسّنة مقارنةً بـ fork العادي في بيئات النسخ الكامل عند استخدامها مع exec. في POSIX، يؤدي استخدام vfork لأي غرض باستثناء كونه تمهيدًا لاستدعاء فوري لدالة من عائلة exec (وعدد قليل من العمليات الأخرى) إلى سلوك غير مُحدد . [ 12 ] كما هو الحال مع vfork، تستعير العملية الفرعية هياكل البيانات بدلًا من نسخها. لا يزال vfork أسرع من fork الذي يستخدم دلالات النسخ عند الكتابة.

لم يكن نظام System V يدعم استدعاء هذه الوظيفة قبل طرح نظام System VR4، لأن مشاركة الذاكرة التي يسببها عرضة للأخطاء:

لا يقوم Vfork بنسخ جداول الصفحات، لذا فهو أسرع من تنفيذ fork في نظام System V. لكن العملية الفرعية تُنفَّذ في نفس مساحة العناوين الفيزيائية للعملية الأصلية (حتى تنفيذ exec أو exit )، وبالتالي يمكنها الكتابة فوق بيانات العملية الأصلية ومكدسها. قد ينشأ وضع خطير إذا استخدم المبرمج vfork بشكل خاطئ، لذا تقع مسؤولية استدعاء vfork على عاتق المبرمج. يكمن الاختلاف بين نهج System V ونهج BSD في المبدأ: هل ينبغي لنواة النظام إخفاء خصائص تنفيذها عن المستخدمين، أم ينبغي لها أن تتيح للمستخدمين المتقدمين فرصة الاستفادة من التنفيذ لأداء وظيفة منطقية بكفاءة أكبر؟

موريس ج. باخ [ 13 ]

وبالمثل، فإن صفحة دليل لينكس الخاصة بـ vfork تثني بشدة عن استخدامها: [ 9 ]

من المؤسف أن نظام لينكس أعاد إحياء هذا الشبح من الماضي. تنص صفحة دليل BSD على ما يلي: "سيتم حذف استدعاء النظام هذا عند تطبيق آليات مشاركة النظام المناسبة. يجب على المستخدمين عدم الاعتماد على دلالات مشاركة الذاكرة الخاصة بـ vfork()، حيث ستصبح في هذه الحالة مرادفة لـ fork(2)."

تشمل المشاكل الأخرى المتعلقة بـ vfork حالات الجمود التي قد تحدث في البرامج متعددة الخيوط نتيجةً للتفاعلات مع الربط الديناميكي . [ 14 ] وكبديل لواجهة vfork ، قدم معيار POSIX عائلة دوال posix_spawn التي تجمع بين وظائف fork و exec. يمكن تنفيذ هذه الدوال كإجراءات مكتبية باستخدام fork ، كما هو الحال في Linux، [ 14 ] أو باستخدام vfork لتحسين الأداء، كما هو الحال في Solaris، [ 14 ] [ 15 ] ولكن مواصفات POSIX تشير إلى أنها "صُممت كعمليات نواة "، خاصةً لأنظمة التشغيل التي تعمل على أجهزة ذات موارد محدودة وأنظمة الوقت الحقيقي . [ 16 ]

على الرغم من أن إصدار 4.4BSD قد تخلص من تطبيق vfork، مما جعل vfork يتصرف بنفس طريقة fork، إلا أنه أعيد لاحقًا في نظام التشغيل NetBSD لأسباب تتعلق بالأداء. [ 10 ]

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

Rfork

يتضمن نظام التشغيل Plan 9 ، الذي ابتكره مصممو نظام Unix، وظيفة fork كنوع من وظيفة جديدة تُسمى "rfork" تسمح بمشاركة دقيقة للموارد بين العمليات الأصلية والفرعية، بما في ذلك مساحة العناوين (باستثناء جزء من المكدس ، وهو فريد لكل عملية)، ومتغيرات البيئة ، ومساحة أسماء نظام الملفات؛ [ 17 ] وهذا ما يجعله واجهة موحدة لإنشاء كل من العمليات والخيوط داخلها. [ 18 ] وقد اعتمد كل من FreeBSD [ 19 ] و IRIX استدعاء النظام rfork من Plan 9، حيث أعاد الأخير تسميته إلى "sproc". [ 20 ]

استنساخ

cloneهي استدعاء نظام في نواة لينكس يُنشئ عملية فرعية قد تتشارك أجزاءً من سياق تنفيذها مع العملية الأصلية. وكما هو الحال مع rfork في FreeBSD وsproc في IRIX، استُلهمت clone في لينكس من rfork في Plan 9، ويمكن استخدامها لتنفيذ الخيوط (مع أن مبرمجي التطبيقات عادةً ما يستخدمون واجهة ذات مستوى أعلى مثل pthreads ، المُنفذة فوق clone). وقد حُذفت ميزة "المكدسات المنفصلة" من Plan 9 وIRIX لأنها (بحسب لينوس تورفالدز ) تُسبب عبئًا زائدًا كبيرًا. [ 20 ]

التفرع في أنظمة تشغيل أخرى

في التصميم الأصلي لنظام التشغيل VMS (1977)، اعتُبرت عملية النسخ مع ما يتبعها من تغيير في محتوى بعض العناوين المحددة للعملية الجديدة، كما في عملية التفرع، محفوفة بالمخاطر. إذ قد تُنسخ الأخطاء في حالة العملية الحالية إلى عملية فرعية. هنا، يُستخدم استعارة إنشاء العمليات: حيث يُعاد بناء كل مكون من مكونات تخطيط ذاكرة العملية الجديدة من الصفر. وقد اعتُمدت استعارة إنشاء العمليات لاحقًا في أنظمة تشغيل مايكروسوفت (1993).

يُوفّر مُكوّن التوافق مع معيار POSIX في نظام إدارة الوسائط الافتراضية (OpenExtensions) تطبيقًا محدودًا جدًا لوظيفة fork، حيث يتم تعليق العملية الأصلية أثناء تنفيذ العملية الفرعية، وتتشارك العملية الفرعية والأصلية نفس مساحة العناوين. [ 21 ] وهذا في جوهره عملية vfork مُصنّفة على أنها fork . (ينطبق هذا على نظام التشغيل الضيف CMS فقط؛ أما أنظمة التشغيل الضيف الأخرى في نظام إدارة الوسائط الافتراضية، مثل Linux، فتُوفّر وظائف fork قياسية).

انظر أيضاً

مراجع

  1. باومان، أندرو؛ أبافو، جوناثان؛ كريجر، أوران؛ روسكو، تيموثي (13-15 مايو 2019). مفترق طرق . ورشة عمل حول المواضيع الساخنة في أنظمة التشغيل (HotOS '19). بيرتينورو، إيطاليا: ACM. doi : 10.1145/3317550.3321435 .
  2. نيمان، لينوس (25 أغسطس 2016). "ملاحظات حول تاريخ التفرع والدمج". حوليات IEEE لتاريخ الحوسبة . 38 (3): 84-87 . doi : 10.1109/MAHC.2016.34 .
  3. "s3.s من بحث يونكس" . جيت هاب . 1970.
  4. كين تومسون ودينيس ريتشي (3 نوفمبر 1971). "SYS FORK (II)" (ملف PDF) . دليل مبرمج يونكس . مختبرات بيل .
  5. ريتشي، دينيس م .؛ طومسون، كين (يوليو 1978). "نظام يونكس لتقاسم الوقت" (ملف PDF) . مجلة بيل سيستم التقنية ، 57 (6). AT&T: 1905-1929 . doi : 10.1002/j.1538-7305.1978.tb02136.x . تاريخ الاسترجاع: 22 أبريل 2014 .
  6. 1 2مرجع واجهات النظام، مواصفات يونكس الموحدة ، الإصدار 5 من مجموعة Open Groupfork  
  7. 1 2مرجع واجهات النظام، مواصفات يونكس الموحدة ، الإصدار 5 من مجموعة Open Grouppipe  
  8. "shmat()" . صفحة دليل لينكس . تم الاطلاع عليها بتاريخ 12-07-2025 .
  9. 1 2دليل مبرمج لينكس – استدعاءات النظام من Manned.orgvfork(2)  
  10. 1 2 "وثائق NetBSD: لماذا يتم تنفيذ vfork() التقليدية؟" . مشروع NetBSD . تم الاطلاع عليه بتاريخ 16 أكتوبر 2013 .
  11. "vfork(2)". دليل مبرمج يونكس، إصدار VAX-11 الافتراضي . جامعة كاليفورنيا، بيركلي. ديسمبر 1979.
  12. 1 2 3مرجع واجهات النظام، مواصفات يونكس الموحدة ، الإصدار 3 من مجموعة Open Groupvfork  
  13. باخ، موريس ج. (1986). تصميم نظام التشغيل يونكس . برنتيس هول. ص 291-292 . Bibcode : 1986duos.book.....B . 
  14. 1 2 3 ناخيموفسكي، جريج (مايو 2006). "تقليل استخدام الذاكرة لإنشاء العمليات الفرعية للتطبيق" . شبكة أوراكل التقنية . شركة أوراكل . مؤرشف من الأصل في 22 سبتمبر 2019.
  15. تطبيق OpenSolaris posix_spawn()
  16. مرجع واجهات النظام، مواصفات يونكس الموحدة ، الإصدار 5 من مجموعة Open Groupposix_spawn  
  17. دليل مبرمج الخطة 9 ، المجلد 1fork(2)  
  18. دليل مبرمج الخطة 9 ، المجلد 1intro(2)  
  19. دليل استدعاءات نظام FreeBSDrfork(2)  
  20. 1 2 تورفالدز، لينوس (1999). "حافة لينكس" . المصادر المفتوحة: أصوات من ثورة المصادر المفتوحة . أورايلي. ISBN 978-1-56592-582-3.
  21. "z/VM > z/VM 6.2.0 > برمجة التطبيقات > وثيقة توافق z/VM V6R2 OpenExtensions POSIX > وثيقة توافق POSIX.1 > القسم 3. العمليات الأساسية > 3.1 إنشاء العمليات وتنفيذها > 3.1.1 إنشاء العمليات" . IBM . تم الاطلاع عليه في 21 أبريل 2015 .