mmap(2)

الاسم
mmap، munmap - يخطط أو يفصل الملفات أو الأجهزة في الذاكرة

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

موجز
#include <sys/mman.h>

void *mmap(size_t length;
           void addr[length], size_t length, int prot, int flags,
           int fd, off_t offset);
int munmap(size_t length;
           void addr[length], size_t length);

انظر VERSIONS للحصول على معلومات حول متطلبات ماكرو اختبار الميزات.

الوصف
تُنشئ mmap() تخطيطًا جديدًا في مساحة العناوين الافتراضية للعملية المستدعِية. يُحدد العنوان الابتدائي للتخطيط الجديد في addr. يُحدد المعامل length طول التخطيط (الذي يجب أن يكون أكبر من 0).

إذا كان addr هو NULL، فإن النواة تختار العنوان (المحاذي للصفحة) الذي ستُنشئ التخطيط عنده؛ وهذه هي الطريقة الأكثر قابلية للنقل لإنشاء تخطيط جديد. أما إذا لم يكن addr هو NULL، فإن النواة تعتبره تلميحًا حول مكان وضع التخطيط؛ وفي لينكس، ستختار النواة حدود صفحة قريبة (ولكن دائمًا أعلى من أو تساوي القيمة المحددة في /proc/sys/vm/mmap_min_addr) وتحاول إنشاء التخطيط هناك. وإذا وجد تخطيط آخر هناك بالفعل، تختار النواة عنوانًا جديدًا قد يعتمد أو لا يعتمد على التلميح. ويُعاد عنوان التخطيط الجديد كناتج للاستدعاء.

تُهيأ محتويات تخطيط الملف (على عكس التخطيط المجهول؛ انظر MAP_ANONYMOUS أدناه)، باستخدام بايتات بطول length بدءًا من الإزاحة offset في الملف (أو أي كائن آخر) المشار إليه بواسطة واصف الملف fd. يجب أن تكون offset مضاعفًا لحجم الصفحة كما يعيده sysconf(_SC_PAGE_SIZE).

بعد عودة استدعاء mmap()، يمكن إغلاق واصف الملف، fd، فورًا دون إبطال التخطيط.

يصف المعامل prot حماية الذاكرة المطلوبة للتخطيط (ويجب ألا يتعارض مع وضع فتح الملف). وهو إما PROT_NONE أو ناتج عملية OR الثنائية لعلامة أو أكثر من العلامات التالية:

PROT_EXEC
    يمكن تنفيذ الصفحات.

PROT_READ
    يمكن قراءة الصفحات.

PROT_WRITE
    يمكن الكتابة في الصفحات.

PROT_NONE
    لا يمكن الوصول إلى الصفحات.

  معامل العلامات (flags)
يحدد المعامل flags ما إذا كانت التحديثات على التخطيط مرئية للعمليات الأخرى التي تخطط نفس المنطقة، وما إذا كانت التحديثات تُنقل إلى الملف الأساسي. ويُحدد هذا السلوك بتضمين قيمة واحدة فقط من القيم التالية في flags:

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

MAP_SHARED_VALIDATE (منذ لينكس 4.15)
    توفر هذه العلامة نفس سلوك MAP_SHARED باستثناء أن تخطيطات MAP_SHARED تتجاهل العلامات غير المعروفة في flags. وبالمقابل، عند إنشاء تخطيط باستخدام MAP_SHARED_VALIDATE، تتحقق النواة من أن جميع العلامات الممرة معروفة وتُفشل التخطيط بالخطأ EOPNOTSUPP للعلامات غير المعروفة. كما أن هذا النوع من التخطيط مطلوب للتمكن من استخدام بعض علامات التخطيط (مثل MAP_SYNC).

MAP_PRIVATE
    إنشاء تخطيط خاص بنظام النسخ عند الكتابة (copy-on-write). لا تكون التحديثات على التخطيط مرئية للعمليات الأخرى التي تخطط نفس الملف، ولا تُنقل إلى الملف الأساسي. وغير محدد ما إذا كانت التغييرات التي أُجريت على الملف بعد استدعاء mmap() ستكون مرئية في المنطقة المخططة.

كلا العلامتين MAP_SHARED و MAP_PRIVATE موصوفتان في POSIX.1-2001 و POSIX.1-2008. أما MAP_SHARED_VALIDATE فهي امتداد للينكس.

بالإضافة إلى ذلك، يمكن إجراء عملية OR لصفر أو أكثر من القيم التالية في flags:

MAP_32BIT (منذ لينكس 2.4.20، 2.6)
    وضع التخطيط في أول 2 جيجابايت من مساحة عناوين العملية. تدعم هذه العلامة فقط على معمارية x86-64، للبرامج ذات 64 بت. أُضيفت للسماح بتخصيص كدسات الخيوط في مكان ما ضمن أول 2 جيجابايت من الذاكرة، لتحسين أداء تبديل السياق في بعض معالجات 64 بت المبكرة. لم تعد معالجات x86-64 الحديثة تعاني من مشكلة الأداء هذه، لذا لا يشترط استخدام هذه العلامة في تلك الأنظمة. تُتجاهل العلامة MAP_32BIT عند ضبط MAP_FIXED.

MAP_ANON
    مرادف لـ MAP_ANONYMOUS؛ توفر للتوافق مع التنفيذات الأخرى.

MAP_ANONYMOUS
    التخطيط غير مدعوم بأي ملف؛ وتُهيأ محتوياته إلى الصفر. يُتجاهل المعامل fd؛ ومع ذلك، تتطلب بعض التنفيذات أن يكون fd مساويًا لـ -1 إذا حُددت MAP_ANONYMOUS (أو MAP_ANON)، ويجب أن تضمن التطبيقات القابلة للنقل ذلك. يجب أن يكون المعامل offset صفرًا. أُضيفت دعم MAP_ANONYMOUS مع MAP_SHARED في لينكس 2.4.

MAP_DENYWRITE
    تُتجاهل هذه العلامة. (منذ زمن بعيد -لينكس 2.0 وما قبله- كانت تشير إلى أن محاولات الكتابة إلى الملف الأساسي يجب أن تفشل بالخطأ ETXTBSY. ولكن هذا كان مصدرًا لهجمات حجب الخدمة).

MAP_EXECUTABLE
    تُتجاهل هذه العلامة.

MAP_FILE
    علامة توافقية. تُتجاهل.

MAP_FIXED
    لا تفسر addr كتلميح: ضع التخطيط عند هذا العنوان تمامًا. يجب محاذاة addr بشكل مناسب: في معظم المعماريات يكفي أن يكون مضاعفًا لحجم الصفحة؛ ومع ذلك، قد تفرض بعض المعماريات قيودًا إضافية. إذا كانت منطقة الذاكرة المحددة بواسطة addr و length تتداخل مع صفحات أي تخطيط (أو تخطيطات) موجودة، فسيُهمل الجزء المتداخل من التخطيط (التخطيطات) الموجودة. إذا تعذر استخدام العنوان المحدد، فستفشل mmap().

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

MAP_FIXED_NOREPLACE (منذ لينكس 4.17)
    توفر هذه العلامة سلوكًا مشابهًا لـ MAP_FIXED فيما يتعلق بفرض العنوان addr، ولكنها تختلف في أن MAP_FIXED_NOREPLACE لا تمسح أبدًا أي نطاق مخطط مسبقًا. إذا اصطدم النطاق المطلوب مع تخطيط موجود، فإن هذا الاستدعاء يفشل بالخطأ EEXIST. لذا يمكن استخدام هذه العلامة كوسيلة لمحاولة تخطيط نطاق عناوين بشكل ذري (فيما يتعلق بالخيوط الأخرى): سينجح خيط واحد؛ وستبلغ جميع الخيوط الأخرى عن الفشل.

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

MAP_GROWSDOWN
    يُستخدم هذا العلامة في المكدسات. وهي تشير إلى نظام الذاكرة الافتراضية في النواة بأن التعيين يجب أن يمتد لأسفل في الذاكرة. وسيؤدي الوصول إلى عنوان في صفحة ”الحماية“ الموجودة أسفل التعيين إلى توسيع التعيين بمقدار صفحة واحدة. ويمكن تكرار هذا التوسيع حتى يصل التعيين إلى مسافة صفحة واحدة من الطرف الأعلى للتعيين الأدنى التالي، وعند هذه النقطة سيؤدي الوصول إلى صفحة ”الحماية“ إلى إصدار إشارة SIGSEGV.

MAP_HUGETLB (منذ لينكس 2.6.32)
    تخصيص التخطيط باستخدام الصفحات "الضخمة". انظر ملف مصدر نواة لينكس Documentation/admin-guide/mm/hugetlbpage.rst لمزيد من المعلومات، بالإضافة إلى قسم الملاحظات (NOTES) أدناه.

MAP_HUGE_2MB
MAP_HUGE_1GB (منذ لينكس 3.8)
    تُستخدم مع MAP_HUGETLB لاختيار أحجام بديلة لصفحات hugetlb (على التوالي، 2 ميجابايت و 1 جيجابايت) في الأنظمة التي تدعم أحجامًا متعددة لصفحات hugetlb.

    بشكل عام، يمكن ضبط حجم الصفحة الضخمة المطلوب بترميز اللوغاريتم للأساس 2 لحجم الصفحة المطلوب في البتات الستة عند الإزاحة MAP_HUGE_SHIFT. (قيمة الصفر في حقل البتات هذا توفر حجم الصفحة الضخمة المبدئي؛ يمكن اكتشاف حجم الصفحة الضخمة المبدئي عبر حقل Hugepagesize المعروض في /proc/meminfo). وبذلك، يُعرف الثابتان المذكوران أعلاه كالتالي:

        #define MAP_HUGE_2MB    (21 << MAP_HUGE_SHIFT)
        #define MAP_HUGE_1GB    (30 << MAP_HUGE_SHIFT)

    يمكن اكتشاف نطاق أحجام الصفحات الضخمة التي يدعمها النظام بسرد الأدلة الفرعية في /sys/kernel/mm/hugepages.

MAP_LOCKED (منذ لينكس 2.5.37)
    تعليم المنطقة المخططة لتُقفل بنفس طريقة mlock(2). سيحاول هذا التنفيذ ملء النطاق بالكامل مسبقًا ولكن لا يفشل استدعاء mmap() بالخطأ ENOMEM إذا فشل ذلك. لذا قد تحدث أخطاء جسيمة (major faults) لاحقًا. وبذلك فإن الدلالة ليست بقوة mlock(2). يجب استخدام mmap() بالإضافة لـ mlock(2) عندما لا تكون الأخطاء الجسيمة مقبولة بعد تهيئة التخطيط. تُتجاهل العلامة MAP_LOCKED في النوى القديمة.

MAP_NONBLOCK (منذ لينكس 2.5.46)
    هذه العلامة ذات معنى فقط عند استخدامها مع MAP_POPULATE. لا تقم بالقراءة المسبقة: أنشئ مدخلات جداول الصفحات فقط للصفحات الموجودة بالفعل في RAM. منذ لينكس 2.6.23، تجعل هذه العلامة MAP_POPULATE لا تفعل شيئًا. قد يُعاد تنفيذ الجمع بين MAP_POPULATE و MAP_NONBLOCK يومًا ما.

MAP_NORESERVE
    عدم حجز مساحة تبديل (swap) لهذا التخطيط. عند حجز مساحة التبديل، يكون هناك ضمان بإمكانية تعديل التخطيط. وعندما لا تُحجز مساحة التبديل، قد تحصل على إشارة SIGSEGV عند الكتابة إذا لم تتوفر ذاكرة فيزيائية. انظر أيضًا مناقشة الملف /proc/sys/vm/overcommit_memory في proc_sys_vm(5). قبل لينكس 2.6، كان لهذه العلامة تأثير فقط على التخطيطات الخاصة القابلة للكتابة.

MAP_POPULATE (منذ لينكس 2.5.46)
    ملء (خطأ مسبق) جداول الصفحات للتخطيط. بالنسبة لتخطيط الملفات، يؤدي هذا للقراءة المسبقة للملف. سيساعد هذا في تقليل الحجب بسبب أخطاء الصفحات لاحقًا. لا يفشل استدعاء mmap() إذا تعذر ملء التخطيط (على سبيل المثال، بسبب قيود على عدد الصفحات الضخمة المخططة عند استخدام MAP_HUGETLB). أُضيفت دعم MAP_POPULATE مع التخطيطات الخاصة في لينكس 2.6.23.

MAP_STACK (منذ لينكس 2.6.27)
    تخصيص التخطيط عند عنوان مناسب لكدسة عملية أو خيط.

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

MAP_SYNC (منذ لينكس 4.15)
    تتوفر هذه العلامة فقط مع نوع التخطيط MAP_SHARED_VALIDATE؛ وستتجاهل التخطيطات من نوع MAP_SHARED هذه العلامة بصمت. تُدعم هذه العلامة فقط للملفات التي تدعم DAX (التخطيط المباشر للذاكرة المستديمة). أما للملفات الأخرى، فإن إنشاء تخطيط بهذه العلامة يؤدي لخطأ EOPNOTSUPP.

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

MAP_UNINITIALIZED (منذ لينكس 2.6.33)
    عدم مسح الصفحات المجهولة. تهدف هذه العلامة لتحسين الأداء في الأجهزة المدمجة. لا تُحترم هذه العلامة إلا إذا ضُبطت النواة بخيار CONFIG_MMAP_ALLOW_UNINITIALIZED. وبسبب الآثار الأمنية، لا يُفعل هذا الخيار عادةً إلا في الأجهزة المدمجة (أي الأجهزة التي يمتلك فيها الشخص تحكمًا كاملاً في محتويات ذاكرة المستخدم).

من بين العلامات المذكورة أعلاه، حُددت MAP_FIXED فقط في POSIX.1-2001 و POSIX.1-2008. ومع ذلك، تدعم معظم الأنظمة أيضًا MAP_ANONYMOUS (أو مرادفها MAP_ANON).

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

يجب أن يكون العنوان addr مضاعفًا لحجم الصفحة (ولكن length لا يشترط ذلك). تُفصل جميع الصفحات التي تحتوي على جزء من النطاق المشار إليه، وستولد المراجع اللاحقة لهذه الصفحات إشارة SIGSEGV. ولا يعتبر خطأً إذا لم يحتوي النطاق المشار إليه على أي صفحات مخططة.

قيمة الإرجاع
عند النجاح، تعيد mmap() مؤشرًا إلى المنطقة المخططة. وعند حدوث خطأ، تُعاد القيمة MAP_FAILED (أي (void *) -1)، وتُضبط errno للإشارة إلى الخطأ.

عند النجاح، تعيد الدالة munmap() القيمة 0. وعند الفشل، تعيد -1، وتُضبط قيمة errno لتبين الخطأ (غالبًا إلى EINVAL).

الأخطاء

EACCES
    واصف الملف يشير إلى ملف غير منتظم. أو طُلب تخطيط ملف، ولكن fd ليس مفتوحًا للقراءة. أو طُلب MAP_SHARED وضُبطت PROT_WRITE، ولكن fd ليس مفتوحًا في وضع القراءة/الكتابة (O_RDWR). أو ضُبطت PROT_WRITE، ولكن الملف مخصص للإلحاق فقط.

EAGAIN
    قُفل الملف، أو قُفلت مساحة كبيرة جدًا من الذاكرة (انظر setrlimit(2)).

EBADF
    fd ليس واصف ملف صالحًا (ولم تُضبط MAP_ANONYMOUS).

EEXIST
    حُددت الراية MAP_FIXED_NOREPLACE في flags، ويتعارض النطاق الذي يغطيه addr و length مع تخطيط موجود.

EINVAL
    قيم addr أو length أو offset غير مقبولة (على سبيل المثال، هي كبيرة جدًا، أو غير محاذية لحدود الصفحة).

EINVAL
    (منذ لينكس 2.6.12) كانت قيمة length هي 0.

EINVAL
    لم تتضمن flags أيًا من MAP_PRIVATE أو MAP_SHARED أو MAP_SHARED_VALIDATE.

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

ENODEV
    نظام ملفات الملف المحدد لا يدعم تخطيط الذاكرة.

ENOMEM
    لا توجد ذاكرة متاحة.

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

ENOMEM
    (منذ لينكس 4.7) سيتم تجاوز حد RLIMIT_DATA للعملية، الموصوف في getrlimit(2).

ENOMEM
    رُفض العنوان addr لأنه يتجاوز فضاء العناوين الافتراضية للمعالج.

EOVERFLOW
    في معمارية 32 بت مع امتداد الملفات الكبيرة (أي باستخدام off_t ذي 64 بت): عدد الصفحات المستخدمة لـ length بالإضافة لعدد الصفحات المستخدمة لـ offset سيؤدي لفيض في unsigned long (32 بت).

EPERM
    تطلب المعطاة prot الصلاحية PROT_EXEC لكن المنطقة المخططة تنتمي إلى ملف في نظام ملفات وُصل بخيار منع التنفيذ no-exec.

EPERM
    مُنعت العملية بواسطة ختم ملف؛ انظر fcntl(2).

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

ETXTBSY
    ضُبطت MAP_DENYWRITE ولكن الكائن المحدد بواسطة fd مفتوح للكتابة.

يمكن أن يؤدي استخدام منطقة مخططة إلى هذه الإشارات:

SIGSEGV
    محاولة الكتابة في منطقة خُططت للقراءة فقط.

SIGBUS
    محاولة الوصول إلى صفحة في المخزن المؤقت تقع خارج نهاية الملف المخطط. للاطلاع على شرح لمعاملة البايتات في الصفحة التي تقابل نهاية ملف مخطط لا يمثل طوله مضاعفًا لحجم الصفحة، انظر قسم ملاحظات NOTES.

السمات
للاطلاع على شرح للمصطلحات المستخدمة في هذا القسم، انظر attributes(7).
الواجهة | السمة | القيمة
mmap(), munmap() | سلامة الخيوط | MT-Safe

الإصدارات
في بعض معماريات العتاد (مثل i386)، تتضمن الصلاحية PROT_WRITE ضمنيًا PROT_READ. ويعتمد الأمر على المعمارية فيما إذا كانت PROT_READ تتضمن PROT_EXEC أم لا. يجب على البرامج المنقولة تعيين PROT_EXEC دائمًا إذا كانت تنوي تنفيذ كود في التخطيط الجديد.

الطريقة المنقولة لإنشاء تخطيط هي تحديد addr بقيمة 0 (NULL)، وحذف MAP_FIXED من flags. في هذه الحالة، يختار النظام عنوان التخطيط؛ ويُختار العنوان بحيث لا يتعارض مع أي تخطيط موجود، ولن يكون 0. إذا حُددت الراية MAP_FIXED وكان addr هو 0 (NULL)، فسيكون العنوان المخطط 0 (NULL).

تُعرف ثوابت معينة في flags فقط إذا عُرفت وحدات ماكرو لاختبار الميزات المناسبة (ربما بشكل مبدئي آلي): _DEFAULT_SOURCE مع glibc 2.19 أو أحدث؛ أو _BSD_SOURCE أو _SVID_SOURCE في glibc 2.19 وما قبله. (استخدام _GNU_SOURCE يكفي أيضًا، وكان طلب هذا الماكرو تحديدًا أكثر منطقية، لأن هذه الرايات كلها خاصة بلينكس). الرايات ذات الصلة هي: MAP_32BIT، و MAP_ANONYMOUS (ومرادفها MAP_ANON)، و MAP_DENYWRITE، و MAP_EXECUTABLE، و MAP_FILE، و MAP_GROWSDOWN، و MAP_HUGETLB، و MAP_LOCKED، و MAP_NONBLOCK، و MAP_NORESERVE، و MAP_POPULATE، و MAP_STACK.

  الاختلافات بين مكتبة C والنواة
تصف هذه الصفحة الواجهة التي توفرها دالة الغلاف mmap() في glibc. في الأصل، كانت هذه الدالة تستدعي نداء نظام بنفس الاسم. ومنذ لينكس 2.4، حل نداء النظام mmap2(2) محل هذا النداء، وحاليًا تستدعي دالة غلاف mmap() في glibc نداء mmap2(2) مع قيمة معدلة لـ offset.

المعايير
POSIX.1-2024.

التاريخ
SVr4, 4.4BSD, SUSv1, POSIX.1-1996.

في أنظمة POSIX التي تتوفر فيها mmap() و msync(2) و munmap()، يُعرَّف _POSIX_MAPPED_FILES في <unistd.h> بقيمة أكبر من 0. (انظر أيضًا sysconf(3)).

ملاحظات
تُحفظ الذاكرة المخططة بواسطة mmap() عبر fork(2)، بنفس السمات.

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

يمكن للتطبيق تحديد الصفحات من التخطيط الموجودة حاليًا في خبيئة Cache الصفحة/المخزن المؤقت باستخدام mincore(2).

  استخدام MAP_FIXED بأمان
الاستخدام الآمن الوحيد لـ MAP_FIXED هو عندما يكون نطاق العناوين المحدد بواسطة addr و length قد حُجز مسبقًا باستخدام تخطيط آخر؛ وبخلاف ذلك، فإن استخدام MAP_FIXED يعد خطرًا لأنه يزيل التخطيطات الموجودة مسبقًا قسرًا، مما يسهل على العملية متعددة الخيوط إفساد فضاء عناوينها الخاص.

على سبيل المثال، افترض أن الخيط A يبحث في /proc/pid/maps لتحديد موقع نطاق عناوين غير مستخدم يمكنه تخطيطه باستخدام MAP_FIXED، بينما يحصل الخيط B في الوقت نفسه على جزء من نفس نطاق العناوين هذا أو كله. عندما يستخدم الخيط A لاحقًا mmap(MAP_FIXED)، فإنه سيمحو فعليًا التخطيط الذي أنشأه الخيط B. في هذا السيناريو، لا يحتاج الخيط B لإنشاء تخطيط مباشرة؛ إذ يكفي مجرد إجراء نداء لمكتبة تستخدم داخليًا dlopen(3) لتحميل مكتبة مشتركة أخرى. سيقوم نداء dlopen(3) بتخطيط المكتبة في فضاء عناوين العملية. علاوة على ذلك، يمكن تنفيذ أي نداء مكتبة تقريبًا بطريقة تضيف تخطيطات ذاكرة إلى فضاء العناوين، إما بهذه التقنية أو بمجرد تخصيص الذاكرة. تشمل الأمثلة brk(2)، و malloc(3)، و pthread_create(3)، ومكتبات PAM http://www.linux-pam.org.

منذ لينكس 4.17، يمكن لبرنامج متعدد الخيوط استخدام الراية MAP_FIXED_NOREPLACE لتجنب الخطر الموصوف أعلاه عند محاولة إنشاء تخطيط في عنوان ثابت لم يُحجز بواسطة تخطيط موجود مسبقًا.

  تغييرات الطوابع الزمنية للتخطيطات المدعومة بملفات
بالنسبة للتخطيطات المدعومة بملفات، قد يُحدث الحقل st_atime للملف المخطط في أي وقت بين mmap() وإلغاء التخطيط المقابل؛ وسيقوم أول وصول لصفحة مخططة بتحديث الحقل إذا لم يكن قد حُدث بالفعل.

سيُحدث الحقلان st_ctime و st_mtime لملف مخطط بـ PROT_WRITE و MAP_SHARED بعد الكتابة إلى المنطقة المخططة، وقبل نداء msync(2) لاحق باستخدام الراية MS_SYNC أو MS_ASYNC، إن وجد.

  تخطيطات الصفحات الضخمة (Huge TLB)
بالنسبة للتخطيطات التي تستخدم صفحات ضخمة، تختلف متطلبات معطيات mmap() و munmap() نوعًا ما عن متطلبات التخطيطات التي تستخدم حجم صفحة النظام الأصلي.

بالنسبة لـ mmap()، يجب أن يكون offset مضاعفًا لحجم الصفحة الضخمة الأساسي. يضبط النظام آليًا length ليكون مضاعفًا لحجم الصفحة الضخمة الأساسي.

بالنسبة لـ munmap()، يجب أن يكون كل من addr و length مضاعفًا لحجم الصفحة الضخمة الأساسي.

تحذيرات
على عكس تطبيقات malloc(3) النموذجية، لا يمنع mmap() إنشاء كائنات أكبر من PTRDIFF_MAX. الكائنات الأكبر من PTRDIFF_MAX تعمل بطرق محدودة فقط في لغة C (على وجه الخصوص، يؤدي طرح المؤشرات إلى سلوك غير محدد إذا كانت النتيجة أكبر من PTRDIFF_MAX). علاوة على ذلك، يفترض GCC أيضًا أنه لا يوجد كائن أكبر من PTRDIFF_MAX. عادة ما يكون PTRDIFF_MAX نصف حجم فضاء العناوين؛ لذا بالنسبة للعمليات ذات 32 بت، يكون عادةً 0x7fffffff (حوالي 2 جيجابايت).

العلل
في لينكس، لا توجد ضمانات مثل تلك المقترحة أعلاه تحت MAP_NORESERVE. مبدئيًا، يمكن قتل أي عملية في أي لحظة عندما تنفد ذاكرة النظام.

قبل لينكس 2.6.7، لم يكن للراية MAP_POPULATE تأثير إلا إذا حُددت prot بأنها PROT_NONE.

يحدد SUSv3 أن mmap() يجب أن يفشل إذا كانت length هي 0. ومع ذلك، قبل لينكس 2.6.12، كان mmap() ينجح في هذه الحالة: لم يُنشأ أي تخطيط وأعاد النداء القيمة addr. ومنذ لينكس 2.6.12، يفشل mmap() مع الخطأ EINVAL في هذه الحالة.

يحدد POSIX أن النظام سيقوم دائمًا بملء أي صفحة جزئية في نهاية الكائن بالأصفار وأن النظام لن يكتب أبدًا أي تعديل للكائن خارج نهايته. في لينكس، عند كتابة بيانات إلى مثل هذه الصفحة الجزئية بعد نهاية الكائن، تظل البيانات في خبيئة Cache الصفحة حتى بعد إغلاق الملف وإلغاء تخطيطه وبالرغم من أن البيانات لا تُكتب أبدًا في الملف نفسه، إلا أن التخطيطات اللاحقة قد ترى المحتوى المعدل. في بعض الحالات، يمكن إصلاح ذلك باستدعاء msync(2) قبل إلغاء التخطيط؛ ومع ذلك، لا يعمل هذا في tmpfs(5) (على سبيل المثال، عند استخدام واجهة ذاكرة POSIX المشتركة الموثقة في shm_overview(7)).

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

  مصدر البرنامج
    #include <err.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <unistd.h>

    int
    main(int argc, char *argv[])
    {
        int          fd;
        char         *addr;
        off_t        offset, pa_offset;
        size_t       length;
        ssize_t      s;
        struct stat  sb;

        if (argc < 3 || argc > 4) {
            fprintf(stderr, "%s file offset [length]\n", argv[0]);
            exit(EXIT_FAILURE);
        }

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

        if (fstat(fd, &sb) == -1)           /* To obtain file size */
            err(EXIT_FAILURE, "fstat");

        offset = atoi(argv[2]);
        pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
            /* offset for mmap() must be page aligned */

        if (offset >= sb.st_size) {
            fprintf(stderr, "offset is past end of file\n");
            exit(EXIT_FAILURE);
        }

        if (argc == 4) {
            length = atoi(argv[3]);
            if (offset + length > sb.st_size)
                length = sb.st_size - offset;
                    /* Can't display bytes past end of file */

        } else {    /* No length arg ==> display to end of file */
            length = sb.st_size - offset;
        }

        addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
                    MAP_PRIVATE, fd, pa_offset);
        if (addr == MAP_FAILED)
            err(EXIT_FAILURE, "mmap");

        s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
        if (s != length) {
            if (s == -1)
                err(EXIT_FAILURE, "write");

            fprintf(stderr, "partial write");
            exit(EXIT_FAILURE);
        }

        munmap(addr, length + offset - pa_offset);
        close(fd);

        exit(EXIT_SUCCESS);
    }

انظر أيضًا
ftruncate(2), getpagesize(2), memfd_create(2), mincore(2), mlock(2), mmap2(2), mprotect(2), mremap(2), msync(2), remap_file_pages(2), setrlimit(2), shmat(2), userfaultfd(2), shm_open(3), shm_overview(7)

أوصاف الملفات التالية في proc(5): /proc/pid/maps، و /proc/pid/map_files، و /proc/pid/smaps.

ب. أ. جالميستر، POSIX.4، دار أورايلي، ص. 128–129 و 389–391.

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

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

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