mremap(2)

الاسم
mremap - إعادة تعيين عنوان ذاكرة افتراضية

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

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

void *mremap(size_t old_size;
             void old_address[old_size], size_t old_size,
             size_t new_size, int flags, ...  /* void *new_address */);

الوصف
توسع mremap() (أو تقلص) تعيين ذاكرة موجود، مع إمكانية نقله في نفس الوقت (يُتحكم به بواسطة وسيط flags ومساحة العنوان الافتراضية المتاحة).

يمكن أيضًا نقل التعيينات ببساطة (بدون أي تغيير في الحجم) عن طريق تحديد old_size وnew_size متساويين واستخدام العلم MREMAP_FIXED (انظر أدناه). منذ Linux 6.17، بينما يجب أن يكون old_address معينًا، قد يمتد old_size عبر تعيينات متعددة بما في ذلك المناطق غير المعينة بينها عند إجراء نقل بسيط. يمكن أيضًا تحديد العلم MREMAP_DONTUNMAP.

بالمثل، إذا كانت العملية تقوم بتقليص، أي إذا كان old_size أكبر من new_size، فقد يمتد old_size أيضًا عبر تعيينات متعددة، والتي لا يجب أن تكون متجاورة مع بعضها البعض. إذا تم هذا التقليص في المكان، أي لم يُحدد MREMAP_FIXED ولا MREMAP_DONTUNMAP، فقد يمتد new_size أيضًا عبر VMAs متعددة. ومع ذلك، إذا نُقل النطاق، فيجب أن يمتد new_size عبر تعيين واحد فقط.

إذا لم تكن العملية نقلًا باستخدام MREMAP_FIXED ولا تقليصًا، فيجب أن يمتد old_size عبر تعيين واحد فقط.

old_address هو العنوان القديم لأول كتلة ذاكرة افتراضية تريد توسيعها أو تقليصها و/أو نقلها. لاحظ أن old_address يجب أن يكون محاذيًا للصفحة. old_size هو حجم النطاق الذي يحتوي على كتل الذاكرة الافتراضية المراد معالجتها. new_size هو الحجم المطلوب لكتل الذاكرة الافتراضية بعد تغيير الحجم. يمكن توفير وسيط خامس اختياري، new_address؛ انظر وصف MREMAP_FIXED أدناه.

إذا كانت قيمة old_size صفرًا، وكان old_address يشير إلى تعيين قابل للمشاركة (انظر وصف MAP_SHARED في mmap(2))، فستقوم mremap() بإنشاء تعيين جديد لنفس الصفحات. سيكون new_size هو حجم التعيين الجديد ويمكن تحديد موقع التعيين الجديد باستخدام new_address؛ انظر وصف MREMAP_FIXED أدناه. إذا طُلب تعيين جديد عبر هذه الطريقة، فيجب أيضًا تحديد العلم MREMAP_MAYMOVE.

قد يكون وسيط قناع البت flags 0، أو يتضمن الأعلام التالية:

MREMAP_MAYMOVE
    بشكل مبدئي، إذا لم تكن هناك مساحة كافية لتوسيع تعيين في موقعه الحالي، تفشل mremap(). إذا حُدد هذا العلم، يُسمح للنواة بنقل التعيين إلى عنوان افتراضي جديد، إذا لزم الأمر. إذا نُقل التعيين، تصبح المؤشرات المطلقة إلى موقع التعيين القديم غير صالحة (يجب استخدام الإزاحات النسبية لعنوان بداية التعيين).

MREMAP_FIXED (منذ Linux 2.3.31)
    يخدم هذا العلم غرضًا مشابهًا للعلم MAP_FIXED الخاص بـ mmap(2). إذا حُدد هذا العلم، تقبل mremap() وسيطًا خامسًا، void *new_address، والذي يحدد عنوانًا محاذيًا للصفحة يجب نقل التعيين إليه. يُلغى تعيين أي تعيين سابق في نطاق العنوان المحدد بواسطة new_address وnew_size.

    إذا حُدد MREMAP_FIXED، فيجب أيضًا تحديد MREMAP_MAYMOVE.

    منذ Linux 6.17، إذا كان old_size مساويًا لـ new_size وحُدد MREMAP_FIXED، فقد يمتد old_size إلى ما وراء التعيين الذي يوجد فيه old_address. في هذه الحالة، يُحافظ على الفجوات بين التعيينات في النطاق الأصلي في النطاق الجديد. تُنفذ العملية بأكملها بشكل ذري ما لم ينشأ خطأ، وفي هذه الحالة قد تكتمل العملية جزئيًا، أي قد تُنقل بعض التعيينات والبعض الآخر لا.

    لا يُسمح بنقل تعيينات متعددة إذا سُجل أي من تلك التعيينات مع userfaultfd(2)، أو تعيين برامج تشغيل تحدد منطق تعيين العناوين المخصص الخاص بها.

MREMAP_DONTUNMAP (منذ Linux 5.7)
    يعيد هذا العلم، الذي يجب استخدامه بالتزامن مع MREMAP_MAYMOVE، تعيين التعيينات إلى عنوان جديد ولكنه لا يلغي تعيينها من عنوانها الأصلي.

    يمكن استخدام العلم MREMAP_DONTUNMAP فقط مع التعيينات التي ليست VM_DONTEXPAND أو VM_PFNMAP. قبل Linux 5.13، كان يمكن استخدام العلم MREMAP_DONTUNMAP فقط مع التعيينات الخاصة المجهولة (انظر وصف MAP_PRIVATE وMAP_ANONYMOUS في mmap(2)).

    بعد الإكمال، أي وصول إلى النطاق المحدد بواسطة old_address وold_size سيؤدي إلى خطأ صفحة. سيُعالج خطأ الصفحة بواسطة معالج userfaultfd(2) إذا كان العنوان في نطاق سُجل مسبقًا مع userfaultfd(2). بخلاف ذلك، تخصص النواة صفحة مملوءة بالأصفار لمعالجة الخطأ.

    يمكن استخدام العلم MREMAP_DONTUNMAP لنقل تعيين بشكل ذري مع ترك المصدر معينًا. انظر الملاحظات لبعض التطبيقات الممكنة لـ MREMAP_DONTUNMAP.

إذا كانت مقاطع الذاكرة المحددة بواسطة old_address وold_size مقفلة (باستخدام mlock(2) أو ما شابه)، فسيُحافظ على هذا القفل عند تغيير حجم المقاطع و/أو نقلها. ونتيجة لذلك، قد تتغير كمية الذاكرة المقفلة بواسطة العملية.

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

الأخطاء

EAGAIN
    حاول المستدعي توسيع مقطع ذاكرة مقفل، لكن هذا لم يكن ممكنًا دون تجاوز حد المورد RLIMIT_MEMLOCK.

EFAULT
    بعض العناوين في النطاق من old_address إلى old_address+old_size هو عنوان ذاكرة افتراضية غير صالح لهذه العملية. يمكنك أيضًا الحصول على EFAULT حتى إذا كانت هناك تعيينات تغطي مساحة العنوان المطلوبة بالكامل، لكن تلك التعيينات من أنواع مختلفة، ولا تدعم عملية mremap() المُنفذة هذا.

EINVAL
    أُعطي وسيط غير صالح. الأسباب المحتملة هي:

    •   old_address لم يكن محاذيًا للصفحة؛

    •   حُدد قيمة غير MREMAP_MAYMOVE أو MREMAP_FIXED أو MREMAP_DONTUNMAP في flags؛

    •   new_size كان صفرًا؛

    •   new_size أو new_address كان غير صالح؛

    •   نطاق العنوان الجديد المحدد بواسطة new_address و new_size تداخل مع نطاق العنوان القديم المحدد بواسطة old_address و old_size؛

    •   حُدد MREMAP_FIXED أو MREMAP_DONTUNMAP دون تحديد MREMAP_MAYMOVE أيضًا؛

    •   حُدد MREMAP_DONTUNMAP، لكن صفحة واحدة أو أكثر في النطاق المحدد بواسطة old_address و old_size لم تكن خاصة مجهولة؛

    •   حُدد MREMAP_DONTUNMAP و old_size لم يكن مساويًا لـ new_size؛

    •   old_size كان صفرًا و old_address لا يشير إلى تعيين قابل للمشاركة (لكن انظر الأخطاء)؛

    •   old_size كان صفرًا ولم يُحدد العلم MREMAP_MAYMOVE.

ENOMEM
    لم تكن ذاكرة كافية متاحة لإكمال العملية. الأسباب المحتملة هي:

    •   لا يمكن توسيع منطقة الذاكرة في العنوان الافتراضي الحالي، والعلم MREMAP_MAYMOVE غير مضبوط في flags. أو، لا توجد ذاكرة (افتراضية) كافية متاحة.

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

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

التاريخ
قبل glibc 2.4، لم يكشف glibc عن تعريف MREMAP_FIXED، ولم يسمح النموذج الأولي لـ mremap() بالوسيطة new_address.

ملاحظات
تغير mremap() التعيين بين العناوين الافتراضية وصفحات الذاكرة. يمكن استخدام هذا لتنفيذ realloc(3) فعال جدًا.

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

إذا اُستخدمت mremap() لنقل أو توسيع منطقة مقفلة بـ mlock(2) أو ما يعادله، فستبذل استدعاء mremap() أقصى جهد لملء المنطقة الجديدة لكنها لن تفشل بـ ENOMEM إذا تعذر ملء المنطقة.

  حالات استخدام MREMAP_DONTUNMAP
التطبيقات الممكنة لـ MREMAP_DONTUNMAP تشمل:

•   userfaultfd(2) غير التعاوني: يمكن للتطبيق سحب نطاق عنوان افتراضي باستخدام MREMAP_DONTUNMAP ثم استخدام معالج userfaultfd(2) للتعامل مع أخطاء الصفحة التي تحدث لاحقًا عندما تلمس خيوط أخرى في العملية صفحات في النطاق المسحوب.

•   جمع القمامة: يمكن استخدام MREMAP_DONTUNMAP بالتزامن مع userfaultfd(2) لتنفيذ خوارزميات جمع القمامة (مثل، في آلة جافا الافتراضية). يمكن أن يكون هذا التنفيذ أرخص (وأبسط) من تقنيات جمع القمامة التقليدية التي تتضمن وضع علامات على الصفحات بحماية PROT_NONE بالتزامن مع استخدام معالج SIGSEGV لالتقاط الوصول إلى تلك الصفحات.

العلل
قبل لينكس 4.14، إذا كان old_size صفرًا وكان التعيين المشار إليه بـ old_address تعيينًا خاصًا (انظر وصف MAP_PRIVATE في mmap(2))، أنشأت mremap() تعيينًا خاصًا جديدًا غير مرتبط بالتعيين الأصلي. كان هذا السلوك غير مقصود وربما غير متوقع في تطبيقات مساحة المستخدم (لأن هدف mremap() هو إنشاء تعيين جديد بناءً على التعيين الأصلي). منذ لينكس 4.14، تفشل mremap() مع الخطأ EINVAL في هذا السيناريو.

انظر أيضًا
brk(2), getpagesize(2), getrlimit(2), mlock(2), mmap(2), sbrk(2), malloc(3), realloc(3)

كتابك المدرسي المفضل عن أنظمة التشغيل لمزيد من المعلومات حول الذاكرة المقسمة إلى صفحات (مثل، Modern Operating Systems بقلم Andrew S. Tanenbaum، Inside Linux بقلم Randolph Bentson، The Design of the UNIX Operating System بقلم Maurice J. Bach)

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

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

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