memfd_create(2)

الاسم
memfd_create - إنشاء ملف مجهول

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

موجز
#define _GNU_SOURCE         /* انظر feature_test_macros(7) */
#include <sys/mman.h>

int memfd_create(const char *name, unsigned int flags);

الوصف
تنشئ memfd_create() ملفًا مجهولًا وتعيد واصف ملف يشير إليه. يتصرف الملف كملف عادي، وبالتالي يمكن تعديله، واقتطاعه، وتعيينه في الذاكرة، وما إلى ذلك. ومع ذلك، على عكس الملف العادي، فهو موجود في ذاكرة الوصول العشوائي (RAM) وله تخزين دعم متطاير. بمجرد إسقاط جميع المراجع إلى الملف، يتم تحريره آليًا. تُستخدم الذاكرة المجهولة لجميع صفحات الدعم للملف. لذلك، فإن الملفات التي تم إنشاؤها بواسطة memfd_create() لها نفس دلالات تخصيصات الذاكرة المجهولة الأخرى مثل تلك المخصصة باستخدام mmap(2) مع العلم MAP_ANONYMOUS.

يُضبط الحجم الأولي للملف على 0. بعد الاستدعاء، يجب ضبط حجم الملف باستخدام ftruncate(2). (بدلاً من ذلك، يمكن ملء الملف باستدعاءات write(2) أو ما شابه ذلك.)

يُستخدم الاسم المقدم في name كاسم ملف وسيُعرض كهدف للارتباط الرمزي المقابل في الدليل /proc/self/fd/. يُسبق الاسم المعروض دائمًا بـ memfd: ويخدم أغراض التصحيح فقط. لا تؤثر الأسماء على سلوك واصف الملف، وبالتالي يمكن أن تحمل ملفات متعددة نفس الاسم دون أي آثار جانبية.

يمكن إجراء عملية OR بتية على القيم التالية في flags لتغيير سلوك memfd_create():

MFD_CLOEXEC
    اضبط علامة الإغلاق عند التنفيذ (FD_CLOEXEC) على واصف الملف الجديد. انظر وصف العلامة O_CLOEXEC في open(2) للأسباب التي تجعل هذا مفيدًا.

MFD_ALLOW_SEALING
    السماح بعمليات الختم على هذا الملف. انظر مناقشة عمليات F_ADD_SEALS و F_GET_SEALS في fcntl(2)، وأيضًا الملاحظات أدناه. المجموعة الأولية من الأختام فارغة. إذا لم يتم تعيين هذا العلم، ستكون المجموعة الأولية من الأختام هي F_SEAL_SEAL، مما يعني أنه لا يمكن تعيين أختام أخرى على الملف.

MFD_HUGETLB (منذ Linux 4.14)
    سيتم إنشاء الملف المجهول في نظام الملفات hugetlbfs باستخدام الصفحات الضخمة. راجع ملف مصدر نواة Linux Documentation/admin-guide/mm/hugetlbpage.rst لمزيد من المعلومات حول hugetlbfs. يُدعم تحديد كل من MFD_HUGETLB و MFD_ALLOW_SEALING في flags منذ Linux 4.16.

MFD_HUGE_2MB
MFD_HUGE_1GB
...
    يُستخدم بالتزامن مع MFD_HUGETLB لتحديد أحجام صفحات hugetlb بديلة (على التوالي، 2 ميجابايت، 1 جيجابايت، ...) على الأنظمة التي تدعم أحجام صفحات hugetlb متعددة. تُضمن تعريفات أحجام الصفحات الضخمة المعروفة في ملف الرأس <linux/memfd.h>.

    للحصول على تفاصيل حول ترميز أحجام الصفحات الضخمة غير المضمنة في ملف الرأس، راجع مناقشة الثوابت المماثلة في التسمية في mmap(2).

يجب أن تكون البتات غير المستخدمة في flags صفرًا.

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

فيما يتعلق بـ fork(2) و execve(2)، تنطبق الدلالات المعتادة على واصف الملف الذي تم إنشاؤه بواسطة memfd_create(). ترث العملية التابعة الناتجة عن fork(2) نسخة من واصف الملف وتشير إلى نفس الملف. يُحتفظ بواصف الملف عبر execve(2)، ما لم يتم تعيين علم الإغلاق عند التنفيذ.

قيمة الإرجاع
عند النجاح، تُعيد memfd_create() واصف ملف جديد. عند الخطأ، تُعيد -1 ويُضبط errno للإشارة إلى الخطأ.

الأخطاء

EFAULT
    يشير العنوان في name إلى ذاكرة غير صالحة.

EINVAL
    تضمن flags بتات غير معروفة.

EINVAL
    كان name طويلًا جدًا. (الحد هو 249 بايت، باستثناء البايت الصفري الختامي.)

EINVAL
    تم تحديد كل من MFD_HUGETLB و MFD_ALLOW_SEALING في flags.

EMFILE
    وُصل إلى الحد الأقصى لواصفات الملفات المفتوحة لكل عملية.

ENFILE
    وُصل إلى الحد الأقصى لإجمالي عدد الملفات المفتوحة على مستوى النظام.

ENOMEM
    لم تكن الذاكرة كافية لإنشاء ملف مجهول جديد.

EPERM
    تم تحديد العلم MFD_HUGETLB، لكن المتصل لم يكن متميزًا (لم يكن لديه القدرة CAP_IPC_LOCK) وليس عضوًا في مجموعة hugetlb_shm_group؛ راجع وصف /proc/sys/vm/hugetlb_shm_group في proc_sys_vm(5).

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

التاريخ
Linux 3.17، glibc 2.27.

ملاحظات
يوفر استدعاء النظام memfd_create() بديلاً بسيطًا لتركيب نظام الملفات tmpfs(5) يدويًا وإنشاء وفتح ملف في ذلك النظام. الغرض الرئيسي من memfd_create() هو إنشاء ملفات وواصفات الملفات المرتبطة بها المستخدمة مع واجهات برمجة تطبيقات ختم الملفات المقدمة بواسطة fcntl(2).

للاستدعاء النظام memfd_create() استخدامات أيضًا بدون ختم الملف (ولهذا السبب يتم تعطيل ختم الملف، ما لم يُطلب صراحةً باستخدام العلم MFD_ALLOW_SEALING). على وجه الخصوص، يمكن استخدامه كبديل لإنشاء ملفات في tmp أو كبديل لاستخدام open(2) O_TMPFILE في الحالات التي لا توجد فيها نية لربط الملف الناتج فعليًا بنظام الملفات.

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

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

مثال على استخدام آلية الختم كما يلي:

(1) تنشئ العملية الأولى ملف tmpfs(5) باستخدام memfd_create(). تُرجع الاستدعاء واصف ملف يُستخدم في الخطوات اللاحقة.

(2) تحدد العملية الأولى حجم الملف الذي أُنشئ في الخطوة السابقة باستخدام ftruncate(2)، وتُخططه باستخدام mmap(2)، وتملأ الذاكرة المشتركة بالبيانات المطلوبة.

(3) تستخدم العملية الأولى عملية fcntl(2) F_ADD_SEALS لوضع ختم واحد أو أكثر على الملف، لتقييد التعديلات الإضافية على الملف. (إذا تم وضع الختم F_SEAL_WRITE، فسيكون من الضروري أولاً إلغاء تخطيط التعيين المشترك القابل للكتابة الذي أُنشئ في الخطوة السابقة. وإلا، يمكن تحقيق سلوك مشابه لـ F_SEAL_WRITE باستخدام F_SEAL_FUTURE_WRITE، الذي يمنع عمليات الكتابة المستقبلية عبر mmap(2) و write(2) من النجاح مع الاحتفاظ بالتعيينات المشتركة القابلة للكتابة الحالية).

(4) تحصل عملية ثانية على واصف ملف لملف tmpfs(5) وتُخططه. من بين الطرق الممكنة لحدوث ذلك ما يلي:

    •   يمكن للعملية التي استدعت memfd_create() نقل واصف الملف الناتج إلى العملية الثانية عبر مقبس نطاق UNIX (انظر unix(7) و cmsg(3)). ثم تُخطط العملية الثانية الملف باستخدام mmap(2).

    •   تُنشأ العملية الثانية عبر fork(2) وبالتالي ترث آلياً واصف الملف والتعيين. (لاحظ أنه في هذه الحالة والحالة التالية، توجد علاقة ثقة طبيعية بين العمليتين، حيث تعملان تحت نفس معرف المستخدم. لذلك، لن يكون ختم الملف ضرورياً عادةً.)

    •   تفتح العملية الثانية الملف /proc/pid/fd/fd، حيث <pid> هو PID العملية الأولى (التي استدعت memfd_create())، و <fd> هو رقم واصف الملف الذي أرجعته استدعاء memfd_create() في تلك العملية. ثم تُخطط العملية الثانية الملف باستخدام mmap(2).

(5) تستخدم العملية الثانية عملية fcntl(2) F_GET_SEALS لاسترجاع قناع البت للأختام التي طُبقت على الملف. يمكن فحص قناع البت هذا لتحديد أنواع القيود التي فُرضت على تعديلات الملف. إذا رغبت، يمكن للعملية الثانية تطبيق أختام إضافية لفرض قيود إضافية (طالما لم يُطبق ختم F_SEAL_SEAL بعد).

أمثلة
فيما يلي برنامجان مثالان يوضحان استخدام memfd_create() وواجهة برمجة تطبيقات ختم الملف.

البرنامج الأول، t_memfd_create.c، ينشئ ملف tmpfs(5) باستخدام memfd_create()، ويحدد حجماً للملف، ويُخططه في الذاكرة، ويضع اختيارياً بعض الأختام على الملف. يقبل البرنامج ما يصل إلى ثلاث وسائط لسطر الأوامر، أول اثنتين منها مطلوبتان. الوسيطة الأولى هي الاسم المرتبط بالملف، والوسيطة الثانية هي الحجم الذي سيُحدد للملف، والوسيطة الثالثة الاختيارية هي سلسلة من الأحرف تحدد الأختام التي ستوضع على الملف.

البرنامج الثاني، t_get_seals.c، يمكن استخدامه لفتح ملف موجود أُنشئ عبر memfd_create() وفحص مجموعة الأختام التي طُبقت على ذلك الملف.

توضح جلسة الصدفة التالية استخدام هذه البرامج. أولاً ننشئ ملف tmpfs(5) ونضع بعض الأختام عليه:

    $ ./t_memfd_create my_memfd_file 4096 sw &
    [1] 11775
    PID: 11775; fd: 3; /proc/11775/fd/3

في هذه المرحلة، يستمر برنامج t_memfd_create في العمل في الخلفية. من برنامج آخر، يمكننا الحصول على واصف ملف للملف الذي أنشأه memfd_create() بفتح ملف /proc/pid/fd الذي يتوافق مع واصف الملف الذي فتحه memfd_create(). باستخدام اسم المسار هذا، نفحص محتوى الرابط الرمزي /proc/pid/fd، ونستخدم برنامج t_get_seals لعرض الأختام التي وُضعت على الملف:

    $ readlink /proc/11775/fd/3;
    /memfd:my_memfd_file (deleted)
    $ ./t_get_seals /proc/11775/fd/3;
    Existing seals: WRITE SHRINK

  مصدر البرنامج: t_memfd_create.c
    #define _GNU_SOURCE
    #include <err.h>
    #include <fcntl.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <unistd.h>

    int
    main(int argc, char *argv[])
    {
        int           fd;
        char          *name, *seals_arg;
        ssize_t       size;
        unsigned int  seals;

        if (argc < 3) {
            fprintf(stderr, "%s name size [seals]\n", argv[0]);
            fprintf(stderr, "\t'seals' can contain any of the "
                    "following characters:\n");
            fprintf(stderr, "\t\tg - F_SEAL_GROW\n");
            fprintf(stderr, "\t\ts - F_SEAL_SHRINK\n");
            fprintf(stderr, "\t\tw - F_SEAL_WRITE\n");
            fprintf(stderr, "\t\tW - F_SEAL_FUTURE_WRITE\n");
            fprintf(stderr, "\t\tS - F_SEAL_SEAL\n");
            exit(EXIT_FAILURE);
        }

        name = argv[1];
        size = atoi(argv[2]);
        seals_arg = argv[3];

        /* Create an anonymous file in tmpfs; allow seals to be
           placed on the file.  */

        fd = memfd_create(name, MFD_ALLOW_SEALING);
        if (fd == -1)
            err(EXIT_FAILURE, "memfd_create");

        /* Size the file as specified on the command line.  */

        if (ftruncate(fd, size) == -1)
            err(EXIT_FAILURE, "truncate");

        printf("PID: %jd; fd: %d; /proc/%jd/fd/%d\n",
               (intmax_t) getpid(), fd, (intmax_t) getpid(), fd);

        /* Code to map the file and populate the mapping with data
           omitted.  */

        /* If a 'seals' command-line argument was supplied, set some
           seals on the file.  */

        if (seals_arg != NULL) {
            seals = 0;

            if (strchr(seals_arg, 'g') != NULL)
                seals |= F_SEAL_GROW;
            if (strchr(seals_arg, 's') != NULL)
                seals |= F_SEAL_SHRINK;
            if (strchr(seals_arg, 'w') != NULL)
                seals |= F_SEAL_WRITE;
            if (strchr(seals_arg, 'W') != NULL)
                seals |= F_SEAL_FUTURE_WRITE;
            if (strchr(seals_arg, 'S') != NULL)
                seals |= F_SEAL_SEAL;

            if (fcntl(fd, F_ADD_SEALS, seals) == -1)
                err(EXIT_FAILURE, "fcntl");
        }

        /* Keep running, so that the file created by memfd_create()
           continues to exist.  */

        pause();

        exit(EXIT_SUCCESS);
    }

  مصدر البرنامج: t_get_seals.c
    #define _GNU_SOURCE
    #include <err.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>

    int
    main(int argc, char *argv[])
    {
        int           fd;
        unsigned int  seals;

        if (argc != 2) {
            fprintf(stderr, "%s /proc/PID/fd/FD\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        fd = open(argv[1], O_RDWR);
        if (fd == -1)
            err(EXIT_FAILURE, "open");

        seals = fcntl(fd, F_GET_SEALS);
        if (seals == -1)
            err(EXIT_FAILURE, "fcntl");

        printf("Existing seals:");
        if (seals & F_SEAL_SEAL)
            printf(" SEAL");
        if (seals & F_SEAL_GROW)
            printf(" GROW");
        if (seals & F_SEAL_WRITE)
            printf(" WRITE");
        if (seals & F_SEAL_FUTURE_WRITE)
            printf(" FUTURE_WRITE");
        if (seals & F_SEAL_SHRINK)
            printf(" SHRINK");
        printf("\n");

        /* Code to map the file and access the contents of the
           resulting mapping omitted.  */

        exit(EXIT_SUCCESS);
    }

انظر أيضًا
fcntl(2), ftruncate(2), memfd_secret(2), mmap(2), shmget(2), shm_open(3)

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

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

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