membarrier(2)

الاسم
membarrier - إصدار حواجز ذاكرة على مجموعة من الخيوط

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

موجز

#include <linux/membarrier.h> /* تعريف ثوابت MEMBARRIER_* */
#include <sys/syscall.h>      /* تعريف ثوابت SYS_* */
#include <unistd.h>

int syscall(SYS_membarrier, int cmd, unsigned int flags, int cpu_id);

ملاحظة: لا توفر glibc غلافًا لـ membarrier()، مما يستلزم استخدام syscall(2).

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

يجب أن يتم استخدام حواجز الذاكرة مع مراعاة أن حاجز الذاكرة يحتاج دائمًا إما إلى أن يكون متطابقًا مع نظائره من حواجز الذاكرة، أو أن نموذج ذاكرة البنية لا يتطلب الحواجز المتطابقة.

هناك حالات حيث يتم تنفيذ أحد جانبي الحواجز المتطابقة (والذي سنشير إليه باسم "الجانب السريع") بشكل متكرر أكثر بكثير من الآخر (والذي سنشير إليه باسم "الجانب البطيء"). هذا هدف رئيسي لاستخدام membarrier(). الفكرة الأساسية هي استبدال، لهذه الحواجز المتطابقة، حواجز الذاكرة في الجانب السريع بحواجز مترجم بسيطة، على سبيل المثال:

    asm volatile ("" : : : "memory")

واستبدال حواجز الذاكرة في الجانب البطيء باستدعاءات لـ membarrier().

سيضيف هذا حملًا زائدًا إلى الجانب البطيء، ويزيل الحمل الزائد من الجانب السريع، مما يؤدي إلى زيادة في الأداء الكلي طالما أن الجانب البطيء غير متكرر بما يكفي بحيث لا يفوق الحمل الزائد لاستدعاءات membarrier() مكسب الأداء في الجانب السريع.

وسيطة cmd هي واحدة مما يلي:

MEMBARRIER_CMD_QUERY (منذ Linux 4.3)
    الاستعلام عن مجموعة الأوامر المدعومة. قيمة إرجاع الاستدعاء هي قناع بت للأوامر المدعومة. MEMBARRIER_CMD_QUERY، الذي له القيمة 0، ليس مدرجًا بحد ذاته في قناع البت هذا. هذا الأمر مدعوم دائمًا (على النوى حيث يتم توفير membarrier()).

MEMBARRIER_CMD_GLOBAL (منذ Linux 4.16)
    ضمان أن جميع الخيوط من جميع العمليات على النظام تمر عبر حالة حيث تتطابق جميع وصولات الذاكرة إلى عناوين مساحة المستخدم مع ترتيب البرنامج بين الدخول إلى والعودة من استدعاء النظام membarrier(). جميع الخيوط على النظام مستهدفة بهذا الأمر.

MEMBARRIER_CMD_GLOBAL_EXPEDITED (منذ Linux 4.16)
    تنفيذ حاجز ذاكرة على جميع الخيوط الجارية لجميع العمليات التي سجلت سابقًا مع MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED.

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

    نظرًا لأن التسجيل يتعلق بالقصد من تلقي الحواجز، فمن الصحيح استدعاء MEMBARRIER_CMD_GLOBAL_EXPEDITED من عملية لم تستخدم MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED.

    الأوامر "المعجلة" تكتمل بشكل أسرع من الأوامر غير المعجلة؛ فهي لا تمنع أبدًا، ولكن لها الجانب السلبي المتمثل في التسبب في حمل زائد إضافي.

MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED (منذ Linux 4.16)
    تسجيل نية العملية لتلقي حواجز ذاكرة MEMBARRIER_CMD_GLOBAL_EXPEDITED.

MEMBARRIER_CMD_PRIVATE_EXPEDITED (منذ Linux 4.14)
    تنفيذ حاجز ذاكرة على كل خيط جارٍ ينتمي إلى نفس عملية الخيط المستدعي.

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

    الأوامر "المعجلة" تكتمل بشكل أسرع من الأوامر غير المعجلة؛ فهي لا تمنع أبدًا، ولكن لها الجانب السلبي المتمثل في التسبب في حمل زائد إضافي.

    يجب على العملية تسجيل نيتها لاستخدام الأمر المعجل الخاص قبل استخدامه.

MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED (منذ Linux 4.14)
    تسجيل نية العملية لاستخدام MEMBARRIER_CMD_PRIVATE_EXPEDITED.

MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE (منذ Linux 4.16)
    بالإضافة إلى توفير ضمانات ترتيب الذاكرة الموصوفة في MEMBARRIER_CMD_PRIVATE_EXPEDITED، عند العودة من استدعاء النظام، يكون لدى الخيط المستدعي ضمان بأن جميع أشقائه الخيوط الجارية قد نفذوا تعليمة تسلسل أساسية. يُقدم هذا الضمان فقط للخيوط في نفس العملية التي ينتمي إليها الخيط المستدعي.

    الأوامر "المعجلة" تكتمل أسرع من الأوامر غير المعجلة، ولا تتعطل أبدًا، ولكن لها عيب التسبب في عبء إضافي.

    يجب على العملية تسجيل نيتها لاستخدام أمر النواة المتزامن المعجل الخاص قبل استخدامه.

MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE (منذ لينكس 4.16)
    سجل نية العملية لاستخدام MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE.

MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ (منذ لينكس 5.10)
    تأكد من أن الخيط المستدعي، عند العودة من استدعاء النظام، أن جميع أشقائه الخيوط الجارية قد أعيد تشغيل أي أقسام حرجة rseq قيد التشغيل حاليًا إذا كانت معلمة flags تساوي 0؛ إذا كانت معلمة flags هي MEMBARRIER_CMD_FLAG_CPU، فسيتم تنفيذ هذه العملية فقط على وحدة المعالجة المركزية المشار إليها بواسطة cpu_id. يُقدم هذا الضمان فقط للخيوط في نفس العملية التي ينتمي إليها الخيط المستدعي.

    حاجز الذاكرة RSEQ متاح فقط في الشكل "المعجل الخاص".

    يجب على العملية تسجيل نيتها لاستخدام أمر rseq المعجل الخاص قبل استخدامه.

MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ (منذ لينكس 5.10)
    سجل نية العملية لاستخدام MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ.

MEMBARRIER_CMD_SHARED (منذ لينكس 4.3)
    هذا اسم مستعار لـ MEMBARRIER_CMD_GLOBAL موجود للتوافق العكسي مع الرأس.

يجب تحديد وسيطة flags كـ 0 ما لم يكن الأمر هو MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ، وفي هذه الحالة يمكن أن تكون flags إما 0 أو MEMBARRIER_CMD_FLAG_CPU.

يتم تجاهل وسيطة cpu_id ما لم تكن flags هي MEMBARRIER_CMD_FLAG_CPU، وفي هذه الحالة يجب أن تحدد وحدة المعالجة المركزية المستهدفة بواسطة أمر حاجز الذاكرة هذا.

جميع عمليات الوصول إلى الذاكرة التي يتم إجراؤها بترتيب البرنامج من كل خيط مستهدف مضمونة أن تكون مرتبة فيما يتعلق بـ membarrier().

إذا استخدمنا الدلالة barrier() لتمثيل حاجز مترجم يجبر عمليات الوصول إلى الذاكرة على التنفيذ بترتيب البرنامج عبر الحاجز، و smp_mb() لتمثيل حواجز الذاكرة الصريحة التي تجبر ترتيب الذاكرة الكامل عبر الحاجز، فلدينا جدول الترتيب التالي لكل زوج من barrier() و membarrier() و smp_mb(). يتم تفصيل ترتيب الزوج على النحو التالي (O: مرتب، X: غير مرتب):

     | barrier() | smp_mb() | membarrier()
    barrier() | X | X | O
    smp_mb() | X | O | O
    membarrier() | O | O | O

قيمة الإرجاع
عند النجاح، تُرجع عملية MEMBARRIER_CMD_QUERY قناع بت للأوامر المدعومة، وتُرجع عمليات MEMBARRIER_CMD_GLOBAL و MEMBARRIER_CMD_GLOBAL_EXPEDITED و MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED و MEMBARRIER_CMD_PRIVATE_EXPEDITED و MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED و MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE و MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE صفرًا. عند الخطأ، يُرجع -1، ويتم تعيين errno للإشارة إلى الخطأ.

لأمر معين، مع ضبط flags على 0، يُضمن أن استدعاء النظام هذا سيعيد دائمًا نفس القيمة حتى إعادة التشغيل. ستؤدي الاستدعاءات الإضافية بنفس الوسائط إلى نفس النتيجة. لذلك، مع ضبط flags على 0، تكون معالجة الأخطاء مطلوبة فقط لأول استدعاء لـ membarrier().

الأخطاء

EINVAL
    cmd غير صالح، أو flags غير صفرية، أو تم تعطيل أمر MEMBARRIER_CMD_GLOBAL لأنه تم تعيين معلمة وحدة المعالجة المركزية nohz_full، أو أن أوامر MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE و MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE غير منفذة بواسطة البنية.

ENOSYS
    استدعاء النظام membarrier() غير منفذ بواسطة هذه النواة.

EPERM
    لم يتم تسجيل العملية الحالية قبل استخدام الأوامر المعجلة الخاصة.

المعايير
لينكس.

التاريخ
لينكس 4.3.

قبل لينكس 5.10، كان النموذج الأولي:

    int membarrier(int cmd, int flags);

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

ترتيب البرنامج هو الترتيب الذي يتم به ترتيب التعليمات في كود التجميع للبرنامج.

تشمل الأمثلة حيث يمكن أن يكون membarrier() مفيدًا تطبيقات مكتبات القراءة-النسخ-التحديث ومجمّعي القمامة.

أمثلة
بافتراض تطبيق متعدد الخيوط حيث يُنفّذ "fast_path()" بشكل متكرر جدًا، وحيث يُنفّذ "slow_path()" بشكل نادر، يمكن تحويل الكود التالي (x86) باستخدام membarrier():

    #include <stdlib.h>

    static volatile int a, b;

    static void
    fast_path(int *read_b)
    {
        a = 1;
        asm volatile ("mfence" : : : "memory");
        *read_b = b;
    }

    static void
    slow_path(int *read_a)
    {
        b = 1;
        asm volatile ("mfence" : : : "memory");
        *read_a = a;
    }

    int
    main(void)
    {
        int read_a, read_b;

        /*
         * Real applications would call fast_path() and slow_path()
         * from different threads.  Call those from main() to keep
         * this example short.
         */

        slow_path(&read_a);
        fast_path(&read_b);

        /*
         * read_b == 0 implies read_a == 1 and
         * read_a == 0 implies read_b == 1.
         */

        if (read_b == 0 && read_a == 0)
            abort();

        exit(EXIT_SUCCESS);
    }

الكود أعلاه بعد تحويله لاستخدام membarrier() يصبح:

    #define _GNU_SOURCE
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/syscall.h>
    #include <linux/membarrier.h>

    static volatile int a, b;

    static int
    membarrier(int cmd, unsigned int flags, int cpu_id)
    {
        return syscall(__NR_membarrier, cmd, flags, cpu_id);
    }

    static int
    init_membarrier(void)
    {
        int ret;

        /* Check that membarrier() is supported.  */

        ret = membarrier(MEMBARRIER_CMD_QUERY, 0, 0);
        if (ret < 0) {
            perror("membarrier");
            return -1;
        }

        if (!(ret & MEMBARRIER_CMD_GLOBAL)) {
            fprintf(stderr,
                "membarrier does not support MEMBARRIER_CMD_GLOBAL\n");
            return -1;
        }

        return 0;
    }

    static void
    fast_path(int *read_b)
    {
        a = 1;
        asm volatile ("" : : : "memory");
        *read_b = b;
    }

    static void
    slow_path(int *read_a)
    {
        b = 1;
        membarrier(MEMBARRIER_CMD_GLOBAL, 0, 0);
        *read_a = a;
    }

    int
    main(int argc, char *argv[])
    {
        int read_a, read_b;

        if (init_membarrier())
            exit(EXIT_FAILURE);

        /*
         * Real applications would call fast_path() and slow_path()
         * from different threads.  Call those from main() to keep
         * this example short.
         */

        slow_path(&read_a);
        fast_path(&read_b);

        /*
         * read_b == 0 implies read_a == 1 and
         * read_a == 0 implies read_b == 1.
         */

        if (read_b == 0 && read_a == 0)
            abort();

        exit(EXIT_SUCCESS);
    }

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

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

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