هل من الممكن تخزين المؤشرات في الذاكرة المشتركة دون استخدام الإزاحة؟

StackOverflow https://stackoverflow.com/questions/2489908

سؤال

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

  1. x = اقرأ p
  2. y = x + الإزاحة
  3. زيادة refcount في y

أثناء الخطوة 2 ، قد يتغير P وقد لم يعد X يشير إلى عقدة صالحة. الحل الوحيد الذي يمكنني التفكير فيه هو إجبار جميع العمليات بطريقة ما على الاتفاق على مكان تعيين الذاكرة المشتركة ، بحيث يمكن تخزين المؤشرات الحقيقية بدلاً من التعويضات في منطقة MMAP'd. هل هناك اي طريقة لعمل ذلك؟ أرى map_fixed في وثائق MMAP ، لكنني لا أعرف كيف يمكنني اختيار عنوان سيكون آمنًا.

تحرير: استخدام التجميع المضمن وبادئة "القفل" على x86 ربما يكون من الممكن إنشاء "زيادة ptr x مع الإزاحة y بواسطة القيمة z"؟ خيارات مكافئة في البنى الأخرى؟ لم تكتب الكثير من التجميع ، لا أعرف ما إذا كانت الإرشادات المطلوبة موجودة.

هل كانت مفيدة؟

المحلول

على المستوى المنخفض ، يمكن أن يقوم الحمر الذري X86 بالقيام بكل خطوات الشجرة هذه في وقت واحد:

  1. x = اقرأ p
  2. y = x + زيادة الإزاحة
  3. refcount في y
//
      mov  edi, Destination
      mov  edx, DataOffset
      mov  ecx, NewData
 @Repeat:
      mov  eax, [edi + edx]    //load OldData
//Here you can also increment eax and save to [edi + edx]          
      lock cmpxchg dword ptr [edi + edx], ecx
      jnz  @Repeat
//

نصائح أخرى

هذا تافهة على نظام UNIX. فقط استخدم وظائف الذاكرة المشتركة:

shgmet ، shmat ، shmctl ، shmdt

void *shmat (int shmid ، const void *shmaddr ، int shmflg) ؛

يرفع SHMAT () شريحة الذاكرة المشتركة التي حددها SHMID بمساحة عنوان عملية الاتصال. يتم تحديد عنوان الإرفاق بواسطة SHMADDR مع أحد المعايير التالية:

إذا كان shmaddr فارغًا ، فإن النظام يختار عنوانًا مناسبًا (غير مستخدم) لإرفاق الجزء.

فقط حدد عنوانك الخاص هنا ؛ على سبيل المثال 0x20000000000

إذا قمت باستخدام نفس المفتاح والحجم في كل عملية ، فستحصل على نفس قطاع الذاكرة المشتركة. إذا كنت shmat () في نفس العنوان ، فستكون العناوين الظاهرية هي نفسها في جميع العمليات. لا يهتم kernel بمدى العنوان الذي تستخدمه ، طالما أنه لا يتعارض مع أي مكان يعين فيه عادة الأشياء. (إذا تركت العنوان ، يمكنك رؤية المنطقة العامة التي تحب وضع الأشياء ؛ وأيضًا ، تحقق من العناوين على المكدس وعادت من Malloc () / new [].)

على Linux ، تأكد من مجموعات الجذر shmmax في/proc/sys/kernel/shmmax إلى رقم كبير بما يكفي لاستيعاب شرائح الذاكرة المشتركة الخاصة بك (الافتراضي هو 32 ميجابايت).

بالنسبة للعمليات الذرية ، يمكنك الحصول عليها جميعًا من مصدر Linux kernel ، على سبيل المثال

تشمل/ASM-X86/Atomic_64.h

/*
 * Make sure gcc doesn't try to be clever and move things around
 * on us. We need to use _exactly_ the address the user gave us,
 * not some alias that contains the same information.
 */
typedef struct {
        int counter;
} atomic_t;

/**
 * atomic_read - read atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically reads the value of @v.
 */
#define atomic_read(v)          ((v)->counter)

/**
 * atomic_set - set atomic variable
 * @v: pointer of type atomic_t
 * @i: required value
 *
 * Atomically sets the value of @v to @i.
 */
#define atomic_set(v, i)                (((v)->counter) = (i))


/**
 * atomic_add - add integer to atomic variable
 * @i: integer value to add
 * @v: pointer of type atomic_t
 *
 * Atomically adds @i to @v.
 */
static inline void atomic_add(int i, atomic_t *v)
{
        asm volatile(LOCK_PREFIX "addl %1,%0"
                     : "=m" (v->counter)
                     : "ir" (i), "m" (v->counter));
}

نسخة 64 بت:

typedef struct {
        long counter;
} atomic64_t;

/**
 * atomic64_add - add integer to atomic64 variable
 * @i: integer value to add
 * @v: pointer to type atomic64_t
 *
 * Atomically adds @i to @v.
 */
static inline void atomic64_add(long i, atomic64_t *v)
{
        asm volatile(LOCK_PREFIX "addq %1,%0"
                     : "=m" (v->counter)
                     : "er" (i), "m" (v->counter));
}

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

يجب ألا تخاف من تعويض عنوان عشوائيًا ، لأن النواة سترفض فقط العناوين التي لا تحبها (تلك التي تتعارض). انظر بلدي shmat() الإجابة أعلاه ، باستخدام 0x20000000000

مع MMAP:

void *mmap (void *addr ، size_t ، int prot ، int flags ، int fd ، Off_T Offset) ؛

إذا لم يكن Addr فارغًا ، فإن kernel يأخذها كتلميح حول مكان وضع التعيين ؛ على Linux ، سيتم إنشاء التعيين في حدود الصفحة الأعلى التالية. يتم إرجاع عنوان التعيين الجديد كنتيجة للمكالمة.

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

map_shared مشاركة هذا الخرائط. تحديثات التعيين واضحة للعمليات الأخرى التي تخطط لهذا الملف ، ويتم نقلها إلى الملف الأساسي. قد لا يتم تحديث الملف فعليًا حتى يتم استدعاء MSYNC (2) أو MUNMAP ().

الأخطاء

إينفال لا نحب ADDR أو الطول أو الإزاحة (على سبيل المثال ، فهي كبيرة جدًا ، أو غير محاذاة على حدود الصفحة).

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top