vfork(2)

الاسم
vfork - إنشاء عملية طفل وحظر العملية الأم

المكتبة
مكتبة سي المعيارية (libc، -lc)

موجز
#include <unistd.h>

pid_t vfork(void);

    متطلبات ماكروات اختبار الميزات لـ glibc (انظر feature_test_macros(7)):

vfork():
    Since glibc 2.12:
        (_XOPEN_SOURCE >= 500) && ! (_POSIX_C_SOURCE >= 200809L)
            || /* Since glibc 2.19: */ _DEFAULT_SOURCE
            || /* glibc <= 2.19: */ _BSD_SOURCE
    Before glibc 2.12:
        _BSD_SOURCE || _XOPEN_SOURCE >= 500

الوصف

  الوصف القياسي
(من POSIX.1) دالة vfork() لها نفس تأثير fork(2)، باستثناء أن السلوك غير محدد إذا قامت العملية المنشأة بواسطة vfork() إما بتعديل أي بيانات غير متغير من النوع pid_t المستخدم لتخزين القيمة المعادة من vfork()، أو العودة من الدالة التي استُدعيت فيها vfork()، أو استدعاء أي دالة أخرى قبل استدعاء _exit(2) بنجاح أو إحدى دوال عائلة exec(3).

  وصف لينكس
vfork()، تمامًا مثل fork(2)، تنشئ عملية طفل للعملية المستدعية. للتفاصيل والقيمة المعادة والأخطاء، انظر fork(2).

vfork() هي حالة خاصة من clone(2). تُستخدم لإنشاء عمليات جديدة دون نسخ جداول الصفحات للعملية الأم. قد تكون مفيدة في التطبيقات الحساسة للأداء حيث تُنشأ عملية طفل ثم تُصدر فورًا execve(2).

تختلف vfork() عن fork(2) في أن الخيط المستدعي يُعلق حتى تنتهي عملية الطفل (إما بشكل طبيعي، باستدعاء _exit(2)، أو بشكل غير طبيعي، بعد تسليم إشارة قاتلة)، أو تقوم باستدعاء execve(2). حتى تلك النقطة، تشارك الطفل جميع الذاكرة مع أمها، بما في ذلك المكدس. يجب ألا تعود الطفل من الدالة الحالية أو تستدعي exit(3) (والذي سيكون له تأثير استدعاء معالجات الخروج المنشأة بواسطة العملية الأم وتفريغ مخازن stdio(3) للأم)، ولكن قد تستدعي _exit(2).

كما هو الحال مع fork(2)، ترث عملية الطفل المنشأة بواسطة vfork() نسخًا من سمات عملية المستدعي المختلفة (مثل واصفات الملفات، وإعدادات الإشارات، ودليل العمل الحالي)؛ يختلف استدعاء vfork() فقط في معالجة مساحة العنوان الافتراضية، كما هو موصوف أعلاه.

تصل الإشارات المرسلة إلى الأم بعد أن تحرر الطفل ذاكرة الأم (أي بعد أن تنتهي الطفل أو تستدعي execve(2)).

  الوصف التاريخي
تحت لينكس، تُنفذ fork(2) باستخدام صفحات النسخ عند الكتابة، لذا فإن العقوبة الوحيدة التي تتكبدها fork(2) هي الوقت والذاكرة المطلوبين لنسخ جداول صفحات الأم، وإنشاء هيكل مهمة فريد للطفل. ومع ذلك، في الأيام القديمة السيئة، كانت fork(2) تتطلب عمل نسخة كاملة من مساحة بيانات المستدعي، غالبًا دون داع، حيث عادةً ما يتم تنفيذ exec(3) بعدها مباشرة. وبالتالي، لتحقيق كفاءة أكبر، قدمت BSD استدعاء النظام vfork()، والذي لم ينسخ مساحة عنوان العملية الأم بالكامل، بل استعار ذاكرة الأم وخيط التحكم حتى حدوث استدعاء لـ execve(2) أو خروج. كانت العملية الأم معلقة بينما كانت الطفل تستخدم مواردها. كان استخدام vfork() صعبًا: على سبيل المثال، عدم تعديل البيانات في العملية الأم يعتمد على معرفة المتغيرات المحفوظة في سجل.

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

يعتبر البعض دلالات vfork() عيبًا معماريًا، وصفحة الدليل 4.2BSD نصت: “سيتم إزالة استدعاء النظام هذا عند تنفيذ آليات المشاركة المناسبة للنظام. يجب ألا يعتمد المستخدمون على دلالات مشاركة الذاكرة لـ vfork حيث أنها، في تلك الحالة، ستُجعل مرادفة لـ fork.” ومع ذلك، على الرغم من أن أجهزة إدارة الذاكرة الحديثة قللت من فرق الأداء بين fork(2) و vfork()، هناك أسباب متنوعة وراء احتفاظ لينكس والأنظمة الأخرى بـ vfork():

•   تتطلب بعض التطبيقات الحساسة للأداء الميزة الصغيرة في الأداء التي يمنحها vfork().

•   يمكن تنفيذ vfork() على الأنظمة التي تفتقر إلى وحدة إدارة الذاكرة (MMU)، لكن لا يمكن تنفيذ fork(2) على هذه الأنظمة. (أزال POSIX.1-2008 vfork() من المعيار؛ يلاحظ الأساس المنطقي لـ POSIX لدالة posix_spawn(3) أن تلك الدالة، التي توفر وظائف مكافئة لـ fork(2)+ exec(3)، مصممة لتكون قابلة للتنفيذ على الأنظمة التي تفتقر إلى MMU.)

•   على الأنظمة حيث الذاكرة محدودة، يتجنب vfork() الحاجة إلى الالتزام المؤقت بالذاكرة (انظر وصف /proc/sys/vm/overcommit_memory في proc(5)) لتنفيذ برنامج جديد. (يمكن أن يكون هذا مفيدًا بشكل خاص حيث ترغب عملية أم كبيرة في تنفيذ برنامج مساعد صغير في عملية طفل.) على النقيض، استخدام fork(2) في هذا السيناريو يتطلب إما الالتزام بكمية من الذاكرة مساوية لحجم العملية الأم (إذا كان الالتزام الزائد الصارم مفعلًا) أو الالتزام الزائد بالذاكرة مع خطر إنهاء العملية بواسطة قاتل نفاد الذاكرة (OOM).

  ملاحظات لينكس
لا تُستدعى معالجات fork المنشأة باستخدام pthread_atfork(3) عندما يستدعي برنامج متعدد الخيوط يستخدم مكتبة الخيوط NPTL vfork(). تُستدعى معالجات fork في هذه الحالة في برنامج يستخدم مكتبة الخيوط LinuxThreads. (انظر pthreads(7) لوصف مكتبات خيوط لينكس.)

استدعاء vfork() مكافئ لاستدعاء clone(2) مع تحديد flags كالتالي:

     CLONE_VM | CLONE_VFORK | SIGCHLD

المعايير
لا شيء.

التاريخ
4.3BSD; POSIX.1-2001 (لكن مُعلّم كـ OBSOLETE). يزيل POSIX.1-2008 مواصفات vfork().

ظهر استدعاء النظام vfork() في 3.0BSD. في 4.4BSD جُعل مرادفًا لـ fork(2) لكن NetBSD أعادت تقديمه؛ انظر http://www.netbsd.org/Documentation/kernel/vfork.html. في لينكس، كان مكافئًا لـ fork(2) حتى لينكس 2.2.0-pre6 أو نحو ذلك. منذ لينكس 2.2.0-pre9 (على i386، وبعد ذلك بقليل على البنى الأخرى) أصبح استدعاء نظام مستقل. أُضيف الدعم في glibc 2.0.112.

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

عند استدعاء vfork() في عملية متعددة الخيوط، يعلق فقط الخيط المستدعيحتى تنتهي العملية الفرعية أو تنفذ برنامجًا جديدًا. وهذا يعني أن العملية الفرعية تتشارك مساحة العناوين مع شفرات أخرى قيد التشغيل. قد يكون هذا خطيرًا إذا قام مؤشر ترابط آخر في العملية الأم بتغيير بيانات الاعتماد (باستخدام setuid(2) أو ما شابه)، نظرًا لوجود عمليتين الآن بمستويات امتيازات مختلفة تعملان في نفس مساحة العناوين. كمثال على المخاطر، لنفترض أن برنامجًا متعدد الخيوط يعملبصفته الجذر ينشئ عملية فرعية باستخدام vfork(). بعد vfork()، يقوم خيطفي العملية الأم بتسليم العملية إلى مستخدم غير متمتع بامتيازات من أجلتشغيل بعض الأكواد غير الموثوق بها (على سبيل المثال، ربما عبر مكون إضافي تم فتحه باستخدامdlopen(3)). في هذه الحالة، تكون الهجمات ممكنة عندما تستخدم العملية الأم mmap(2) لتعيين رمز سيتم تنفيذه بواسطة العملية الفرعية ذات الامتيازات.

العلل
تفاصيل معالجة الإشارات غير واضحة وتختلف من نظام لآخر. تنص صفحة دليل BSD على ما يلي: ”لتجنب حدوث حالة تعطل محتملة، لا يتم أبدًا إرسال إشارات SIGTTOU أو SIGTTIN إلى العمليات الفرعية التي تكون قيد التشغيل أثناء تنفيذ vfork()؛ بل يُسمح بالإخراج أو عمليات ioctl، بينما تؤدي محاولات الإدخال إلى ظهور إشارة نهاية الملف.“

انظر أيضًا
clone(2)، execve(2)، _exit(2)، fork(2)، _Fork(3)، unshare(2)، wait(2)

ترجمة
تُرجمت هذه الصفحة من الدليل بواسطة زايد السعيدي <zayed.alsaidi@gmail.com>

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 (https://www.gnu.org/licenses/gpl-3.0.html) أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

إذا وجدت أي أخطاء في ترجمة صفحة الدليل هذه، يرجى إرسال بريد إلكتروني إلى قائمة بريد المترجمين: kde-l10n-ar@kde.org.