Scroll to navigation

futex(2) System Calls Manual futex(2)

الاسم

futex - قفل سريع في مساحة المستخدم

المكتبة

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

موجز

#include <linux/futex.h>      /* تعريف ثوابت FUTEX_* */
#include <sys/syscall.h>      /* تعريف ثوابت SYS_* */
#include <unistd.h>
long syscall(SYS_futex, uint32_t *uaddr, int futex_op, uint32_t val,
             const struct timespec *timeout,   /* أو: uint32_t val2 */
             uint32_t *uaddr2, uint32_t val3);

ملاحظة: لا توفر glibc أي غلاف لـ futex()، مما يستوجب استخدام syscall(2).

الوصف

يوفر استدعاء النظام futex() وسيلة للانتظار حتى يتحقق شرط معين. يُستخدم عادةً كبنية حابسة في سياق مزامنة الذاكرة المشتركة. عند استخدام الـ futexes، تُنفذ أغلبية عمليات المزامنة في مساحة المستخدم. يوظف برنامج مساحة المستخدم استدعاء النظام futex() فقط عندما يُرجح أن البرنامج سيُحبس لفترة طويلة حتى يتحقق الشرط. يمكن استخدام عمليات futex() الأخرى لإيقاظ أي عمليات أو خيوط تنتظر شرطًا محددًا.

الـ futex هو قيمة بحجم 32 بتًا —يُشار إليها أدناه باسم كلمة futex— يُزود استدعاء النظام futex() بعنوانها. (يبلغ حجم الـ futexes حوالي 32 بتًا في جميع المنصات، بما في ذلك الأنظمة بمعمارية 64 بتًا). تُحكم جميع عمليات الـ futex بهذه القيمة. ولمشاركة futex بين العمليات، يُوضع في منطقة من الذاكرة المشتركة، تُنشأ باستخدام (على سبيل المثال) mmap(2) أو shmat(2). (وبذلك، قد تملك كلمة الـ futex عناوين افتراضية مختلفة في عمليات مختلفة، ولكن هذه العناوين تشير جميعها إلى الموقع نفسه في الذاكرة الفيزيائية). في البرامج متعددة الخيوط، يكفي وضع كلمة الـ futex في متغير عام مشترك بين جميع الخيوط.

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

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

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

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

المعطيات

تُشير المعطاة uaddr إلى كلمة الفوتكس. في جميع المنصات، الفوتكسات هي أعداد صحيحة من أربعة بايتات يجب محاذاتها على حد أربعة بايتات. تُحدد العملية المطلوب تنفيذها على الفوتكس في المعطاة futex_op؛ و val هي قيمة يعتمد معناها والغرض منها على futex_op.

المعطيات المتبقية (timeout، و uaddr2، و val3) مطلوبة فقط لعمليات فوتكس معينة موصوفة أدناه. وعندما لا تكون إحدى هذه المعطيات مطلوبة، تُتجاهل.

بالنسبة لعدة عمليات حاصرة، تكون المعطاة timeout مؤشراً إلى هيكل timespec يحدد مهلة للعملية. ومع ذلك، وبالرغم من النموذج الموضح أعلاه، ففي بعض العمليات، تُستخدم البايتات الأربعة الأقل أهمية من هذه المعطاة بدلاً من ذلك كعدد صحيح يحدد معناه بواسطة العملية. لهذه العمليات، تحول النواة قيمة timeout أولاً إلى unsigned long، ثم إلى uint32_t، وفي بقية هذه الصفحة، يشار إلى هذه المعطاة باسم val2 عند تفسيرها بهذا الأسلوب.

عندما تكون مطلوبة، تكون المعطاة uaddr2 مؤشراً إلى كلمة فوتكس ثانية تستخدمها العملية.

تعتمد ترجمة المعطاة الصحيحة النهائية، val3، على العملية.

عمليات Futex

تتكون المعطاة futex_op من جزأين: أمر يحدد العملية المراد تنفيذها، معمول له OR بتياً مع خيار واحد أو أكثر من الخيارات التي تعدل سلوك العملية. الخيارات التي قد تُضمّن في futex_op هي كما يلي:

يمكن استخدام بت الخيار هذا مع جميع عمليات futex. يخبر النواة أن الـ futex خاص بالعملية وليس مشتركًا مع عملية أخرى (أي يُستخدم للمزامنة فقط بين خيوط العملية نفسها). يسمح هذا للنواة بإجراء بعض تحسينات الأداء الإضافية.
من باب التسهيل، يعرّف <linux/futex.h> مجموعة من الثوابت باللاحقة _PRIVATE التي تكافئ جميع العمليات المدرجة أدناه، ولكن مع دمج FUTEX_PRIVATE_FLAG في قيمة الثابت عبر عملية OR. وبذلك، توجد FUTEX_WAIT_PRIVATE وFUTEX_WAKE_PRIVATE، وهكذا.
لا يمكن استخدام بت الخيار هذا إلا مع عمليات FUTEX_WAIT_BITSET، و FUTEX_WAIT_REQUEUE_PI، و (منذ لينكس 4.5) FUTEX_WAIT، و (منذ لينكس 5.14) FUTEX_LOCK_PI2.
إذا ضُبط هذا الخيار، تقيس النواة مهلة الانتظار timeout مقابل ساعة الوقت الحقيقي CLOCK_REALTIME.
إذا لم يُضبط هذا الخيار، تقيس النواة مهلة الانتظار timeout مقابل ساعة الوقت الرتيب CLOCK_MONOTONIC.

العملية المحددة في futex_op هي واحدة مما يلي:

تختبر هذه العملية ما إذا كانت القيمة في كلمة الفوتكس التي يشير إليها العنوان uaddr لا تزال تحتوي على القيمة المتوقعة val، وإذا كان الأمر كذلك، فإنها تنام في انتظار عملية FUTEX_WAKE على كلمة الفوتكس. تحميل قيمة كلمة الفوتكس هو وصول ذري للذاكرة (أي باستخدام تعليمات الآلة الذرية للبنية المعنية). يُنفذ هذا التحميل، والمقارنة مع القيمة المتوقعة، وبدء النوم بشكل ذري وبترتيب كلي بالنسبة لعمليات فوتكس الأخرى على نفس كلمة الفوتكس. إذا بدأ الخيط في النوم، فيُعتبر منتظراً على كلمة الفوتكس هذه. إذا لم تتطابق قيمة الفوتكس مع val، فستفشل المكالمة فوراً مع الخطأ EAGAIN.
الغرض من المقارنة مع القيمة المتوقعة هو منع فقدان عمليات التنبيه. إذا قام خيط آخر بتغيير قيمة كلمة الفوتكس بعد أن قرر الخيط المستدعي الحصر بناءً على القيمة السابقة، وإذا قام الخيط الآخر بتنفيذ عملية FUTEX_WAKE (أو تنبيه مشابه) بعد تغيير القيمة وقبل عملية FUTEX_WAIT هذه، فسيلاحظ الخيط المستدعي تغيير القيمة ولن يبدأ في النوم.
إذا كانت timeout ليست NULL، فإن الهيكل الذي تشير إليه يحدد مهلة للانتظار. (سيتم تقريب هذه الفترة إلى دقة ساعة النظام، ومن المضمون عدم انتهاء صلاحيتها مبكراً.) تُقاس المهلة مبدئياً وفقاً لساعة CLOCK_MONOTONIC، ولكن منذ لينكس 4.5، يمكن اختيار ساعة CLOCK_REALTIME بتحديد FUTEX_CLOCK_REALTIME في futex_op. إذا كانت timeout هي NULL، فستُحصر المكالمة إلى أجل غير مسمى.
ملاحظة: بالنسبة لـ FUTEX_WAIT، تُفسر timeout كقيمة نسبية. وهذا يختلف عن عمليات فوتكس الأخرى، حيث تُفسر timeout كقيمة مطلقة. للحصول على ما يعادل FUTEX_WAIT بمهلة مطلقة، استخدم FUTEX_WAIT_BITSET مع تحديد val3 كـ FUTEX_BITSET_MATCH_ANY.
تُتجاهل المعطيات uaddr2 و val3.
توقظ هذه العملية على الأكثر val من المنتظرين الذين ينتظرون (على سبيل المثال، داخل FUTEX_WAIT) على كلمة الفوتكس في العنوان uaddr. الأكثر شيوعاً هو تحديد val كـ 1 (إيقاظ منتظر واحد) أو INT_MAX (إيقاظ جميع المنتظرين). لا يُقدم أي ضمان بشأن المنتظرين الذين سيتم إيقاظهم (على سبيل المثال، لا يضمن إيقاظ منتظر ذي أولوية جدولة أعلى قبل منتظر ذي أولوية أقل).
تُتجاهل المعطيات timeout، و uaddr2، و val3.
تنشئ هذه العملية واصف ملف مرتبطاً بالفوتكس عند uaddr. يجب على المستدعِي إغلاق واصف الملف المعاد بعد الاستخدام. عندما تقوم عملية أو خيط آخر بتنفيذ FUTEX_WAKE على كلمة الفوتكس، يشير واصف الملف إلى أنه قابل للقراءة باستخدام select(2)، و poll(2)، و epoll(7)
يمكن استخدام واصف الملف للحصول على إشعارات غير متزامنة: إذا كانت val غير صفرية، فعندما تقوم عملية أو خيط آخر بتنفيذ FUTEX_WAKE، سيتلقى المستدعِي رقم الإشارة الذي تم تمريره في val.
تُتجاهل المعطيات timeout، و uaddr2، و val3.
نظراً لأنه كان ينطوي بطبيعته على تنافس، فقد حُذف FUTEX_FD من لينكس 2.6.26 فصاعداً.
تؤدي هذه العملية نفس المهمة التي تؤديها FUTEX_CMP_REQUEUE (انظر أدناه)، باستثناء أنه لا يتم إجراء أي فحص باستخدام القيمة في val3. (تُتجاهل المعطاة val3.)
تتحقق هذه العملية أولاً مما إذا كان الموقع uaddr لا يزال يحتوي على القيمة val3. إذا لم يكن الأمر كذلك، تفشل العملية مع الخطأ EAGAIN. وإلا، فإن العملية توقظ حداً أقصى قدره val من المنتظرين الذين ينتظرون على الفوتكس عند uaddr. إذا كان هناك أكثر من val من المنتظرين، فسيتم حذف المنتظرين المتبقين من صف انتظار فوتكس المصدر عند uaddr وإضافتهم إلى صف انتظار فوتكس الهدف عند uaddr2. تحدد المعطاة val2 حداً أقصى لعدد المنتظرين الذين سيتم نقلهم إلى صف فوتكس آخر عند uaddr2.
التحميل من uaddr هو وصول ذري للذاكرة (أي باستخدام تعليمات الآلة الذرية للبنية المعنية). يُنفذ هذا التحميل، والمقارنة مع val3، وإعادة وضع أي منتظرين في صف آخر بشكل ذري وبترتيب كلي بالنسبة للعمليات الأخرى على نفس كلمة الفوتكس.
القيم النموذجية لتحديد val هي 0 أو 1. (تحديد INT_MAX غير مفيد، لأنه سيجعل عملية FUTEX_CMP_REQUEUE مكافئة لـ FUTEX_WAKE.) قيمة الحد المحددة عبر val2 هي عادةً إما 1 أو INT_MAX. (تحديد المعطاة كـ 0 غير مفيد، لأنه سيجعل عملية FUTEX_CMP_REQUEUE مكافئة لـ FUTEX_WAIT.)
أُضيفت عملية FUTEX_CMP_REQUEUE كبديل لعملية FUTEX_REQUEUE السابقة. والفرق هو أن التحقق من القيمة عند uaddr يمكن استخدامه لضمان حدوث إعادة الصف فقط في ظل ظروف معينة، مما يسمح بتجنب حالات التسابق في حالات استخدام معينة.
يمكن استخدام كل من FUTEX_REQUEUE و FUTEX_CMP_REQUEUE لتجنب تنبيهات "القطيع الهائج" التي قد تحدث عند استخدام FUTEX_WAKE في الحالات التي يحتاج فيها جميع المنتظرين الذين تم إيقاظهم إلى حيازة فوتكس آخر. تأمل السيناريو التالي، حيث تنتظر خيوط منتظرين متعددة على B، وهو صف انتظار نُفذ باستخدام فوتكس:

lock(A)
while (!check_value(V)) {

unlock(A);
block_on(B);
lock(A); }; unlock(A);

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

uint32_t oldval = *(uint32_t *) uaddr2;
*(uint32_t *) uaddr2 = oldval op oparg;
futex(uaddr, FUTEX_WAKE, val, 0, 0, 0);
if (oldval cmp cmparg)

futex(uaddr2, FUTEX_WAKE, val2, 0, 0, 0);

بعبارة أخرى، يقوم FUTEX_WAKE_OP بما يلي:
يحفظ القيمة الأصلية لكلمة الفوتكس عند uaddr2 وينفذ عملية لتعديل قيمة الفوتكس عند uaddr2؛ هذا هو وصول ذري للذاكرة من نوع قراءة-تعديل-كتابة (أي باستخدام تعليمات الآلة الذرية للبنية المعنية)
يوقظ بحد أقصى val من المنتظرين على الفوتكس لكلمة الفوتكس عند uaddr؛ و
اعتماداً على نتائج اختبار القيمة الأصلية لكلمة الفوتكس عند uaddr2، يوقظ بحد أقصى val2 من المنتظرين على الفوتكس لكلمة الفوتكس عند uaddr2.
تُرمز العملية والمقارنة المراد تنفيذهما في بتات المعطاة val3. بالرسم، التشفير هو:

+---+---+-----------+-----------+
|op |cmp|   oparg   |  cmparg   |
+---+---+-----------+-----------+

4 4 12 12 <== عدد البتات

معبراً عنه بالكود، التشفير هو:

#define FUTEX_OP(op, oparg, cmp, cmparg) \

(((op & 0xf) << 28) | \
((cmp & 0xf) << 24) | \
((oparg & 0xfff) << 12) | \
(cmparg & 0xfff))

فيما سبق، op و cmp كل منهما كود من الأكواد المدرجة أدناه. المكونان oparg و cmparg هما قيم عددية حرفية، إلا كما هو موضح أدناه.
يحتوي المكون op على إحدى القيم التالية:

FUTEX_OP_SET        0  /* uaddr2 = oparg; */
FUTEX_OP_ADD        1  /* uaddr2 += oparg; */
FUTEX_OP_OR         2  /* uaddr2 |= oparg; */
FUTEX_OP_ANDN       3  /* uaddr2 &= ~oparg; */
FUTEX_OP_XOR        4  /* uaddr2 ^= oparg; */
    

بالإضافة إلى ذلك، فإن إجراء OR بتياً للقيمة التالية في op يؤدي إلى استخدام (1 << oparg) كـ معامل:

FUTEX_OP_ARG_SHIFT  8  /* استخدم (1 << oparg) كمعامل */
    

حقل cmp هو واحد مما يلي:

FUTEX_OP_CMP_EQ     0  /* إذا كان (oldval == cmparg) استيقظ */
FUTEX_OP_CMP_NE     1  /* إذا كان (oldval != cmparg) استيقظ */
FUTEX_OP_CMP_LT     2  /* إذا كان (oldval < cmparg) استيقظ */
FUTEX_OP_CMP_LE     3  /* إذا كان (oldval <= cmparg) استيقظ */
FUTEX_OP_CMP_GT     4  /* إذا كان (oldval > cmparg) استيقظ */
FUTEX_OP_CMP_GE     5  /* إذا كان (oldval >= cmparg) استيقظ */
    

قيمة الإرجاع لـ FUTEX_WAKE_OP هي مجموع عدد المنتظرين الذين استيقظوا على فوتكس uaddr بالإضافة إلى عدد المنتظرين الذين استيقظوا على فوتكس uaddr2.
تشبه هذه العملية FUTEX_WAIT باستثناء أن val3 يُستخدم لتوفير قناع بتات 32 بت لـ النواة. يُخزن قناع البتات هذا، والذي يجب ضبط بت واحد على الأقل فيه، في الحالة الداخلية لـ النواة للمنتظر. انظر وصف FUTEX_WAKE_BITSET لمزيد من التفاصيل.
إذا كانت timeout ليست NULL، فإن الهيكل الذي تشير إليه يحدد مهلة مطلقة لعملية الانتظار. إذا كانت timeout هي NULL، يمكن حصر العملية إلى أجل غير مسمى.
تُتجاهل المعطاة uaddr2.
هذه العملية هي نفسها FUTEX_WAKE باستثناء أن المعطاة val3 تُستخدم لتوفير قناع بتات 32 بت لـ النواة. يُستخدم قناع البتات هذا، والذي يجب ضبط بت واحد على الأقل فيه، لتحديد المنتظرين الذين يجب إيقاظهم. يتم التحديد بواسطة AND بتياً لقناع بتات "التنبيه" (أي القيمة في val3) وقناع البتات المخزن في الحالة الداخلية لـ النواة للمنتظر (قناع بتات "الانتظار" الذي يتم ضبطه باستخدام FUTEX_WAIT_BITSET). يتم إيقاظ جميع المنتظرين الذين تكون نتيجة AND لهم غير صفرية؛ ويُترك المنتظرون المتبقون نائمين.
تأثير FUTEX_WAIT_BITSET و FUTEX_WAKE_BITSET هو السماح بعمليات تنبيه انتقائية بين عدة منتظرين محصورين على نفس الفوتكس. ومع ذلك، لاحظ أنه، اعتماداً على حالة الاستخدام، قد يكون استخدام ميزة تعدد الإرسال بقناع البتات هذه على فوتكس أقل كفاءة من مجرد استخدام فوتكسات متعددة، لأن استخدام تعدد الإرسال بقناع البتات يتطلب من النواة فحص جميع المنتظرين على الفوتكس، بما في ذلك أولئك غير المهتمين بالاستيقاظ (أي ليس لديهم البت المعني مضبوطاً في قناع بتات "الانتظار" الخاص بهم).
يمكن استخدام الثابت FUTEX_BITSET_MATCH_ANY، الذي يتوافق مع ضبط جميع البتات الـ 32 في قناع البتات، كـ معطاة val3 لـ FUTEX_WAIT_BITSET و FUTEX_WAKE_BITSET. وبخلاف الاختلافات في معالجة المعطاة timeout، فإن عملية FUTEX_WAIT تعادل FUTEX_WAIT_BITSET مع تحديد val3 كـ FUTEX_BITSET_MATCH_ANY؛ أي السماح بالتنبيه من قبل أي منبه. عملية FUTEX_WAKE تعادل FUTEX_WAKE_BITSET مع تحديد val3 كـ FUTEX_BITSET_MATCH_ANY؛ أي إيقاظ أي منتظر (أو منتظرين).
تُتجاهل المعطيات uaddr2 و timeout.

Futexes توريث الأولويات

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

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

من منظور مساحة المستخدم، ما يجعل الـ futex مدركًا لتوريث الأولويات (PI-aware) هو اتفاق سياسة (موصوف أدناه) بين مساحة المستخدم والنواة حول قيمة كلمة futex، مقترنًا باستخدام عمليات PI-futex الموضحة أدناه. (على عكس عمليات futex الأخرى الموضحة أعلاه، صُممت عمليات PI-futex لتنفيذ آليات اتصال بين العمليات (IPC) محددة للغاية).

تختلف عمليات PI-futex الموضحة أدناه عن عمليات futex الأخرى في أنها تفرض سياسة على استخدام قيمة كلمة futex:

إذا لم يُستحوذ على القفل، يجب أن تكون قيمة كلمة futex هي 0.
إذا استُحوذ على القفل، يجب أن تكون قيمة كلمة futex هي معرف الخيط (TID؛ انظر gettid(2)) للخيط المالك.
إذا كان القفل مملوكًا وهناك خيوط تتنازع عليه، يجب ضبط بت FUTEX_WAITERS في قيمة كلمة futex؛ بعبارة أخرى، هذه القيمة هي:

FUTEX_WAITERS | TID
    

(لاحظ أنه من غير الصالح أن تكون كلمة PI futex بلا مالك مع ضبط FUTEX_WAITERS).

مع تطبيق هذه السياسة، يمكن لتطبيق مساحة المستخدم الاستحواذ على قفل غير مستحوذ عليه أو تحرير قفل باستخدام تعليمات ذرية تُنفذ في وضع المستخدم (على سبيل المثال، عملية مقارنة وتبديل مثل cmpxchg في معمارية x86). يتكون الاستحواذ على قفل ببساطة من استخدام المقارنة والتبديل لضبط قيمة كلمة futex ذريًا إلى معرف خيط (TID) المستدعي إذا كانت قيمتها السابقة 0. يتطلب تحرير القفل استخدام المقارنة والتبديل لضبط قيمة كلمة futex إلى 0 إذا كانت القيمة السابقة هي TID المتوقع.

إذا تم حيازة فوتكس بالفعل (أي له قيمة غير صفرية)، فيجب على المنتظرين استخدام عمليات FUTEX_LOCK_PI أو FUTEX_LOCK_PI2 لحيازة القفل. إذا كانت هناك خيوط أخرى تنتظر القفل، فسيتم ضبط بت FUTEX_WAITERS في قيمة الفوتكس؛ في هذه الحالة، يجب على مالك القفل استخدام عملية FUTEX_UNLOCK_PI لتحرير القفل.

في الحالات التي يُجبر فيها المستدعون على الدخول إلى النواة (أي يُطلب منهم إجراء استدعاء futex())، فإنهم يتعاملون مباشرة مع ما يسمى RT-mutex، وهي آلية إقفال في النواة تنفذ دلالات توريث الأولويات المطلوبة. بعد الاستحواذ على RT-mutex، تُحدث قيمة الـ futex وفقًا لذلك، قبل أن يعود الخيط المستدعي إلى مساحة المستخدم.

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

إذا كان لـ futex ما يقابله من RT-mutex في النواة (أي يوجد منتظرون محبوسون) ومات مالك الـ futex/RT-mutex بشكل غير متوقع، تقوم النواة بتنظيف الـ RT-mutex وتسليمه للمنتظر التالي. وهذا بدوره يتطلب تحديث قيمة مساحة المستخدم وفقًا لذلك. وللإشارة إلى أن هذا مطلوب، تضبط النواة بت FUTEX_OWNER_DIED في كلمة futex مع معرف الخيط للمالك الجديد. يمكن لمساحة المستخدم اكتشاف هذا الموقف عبر وجود بت FUTEX_OWNER_DIED وهي المسؤولة حينئذٍ عن تنظيف الحالة القديمة التي تركها المالك الميت.

يتم تشغيل فوتكسات PI بتحديد إحدى القيم المدرجة أدناه في futex_op. لاحظ أنه يجب استخدام عمليات فوتكس PI كعمليات مزدوجة وهي تخضع لبعض المتطلبات الإضافية:

تتقابل العمليات FUTEX_LOCK_PI و FUTEX_LOCK_PI2 و FUTEX_TRYLOCK_PI مع العملية FUTEX_UNLOCK_PI. ويجب ألا تُستدعى FUTEX_UNLOCK_PI إلا على فيوتكس (futex) يمتلكه خيط الاستدعاء، كما هو محدد في سياسة القيمة، وإلا فسينتج الخطأ EPERM.
FUTEX_WAIT_REQUEUE_PI يقترن مع FUTEX_CMP_REQUEUE_PI. يجب أن يتم ذلك من فوتكس غير PI إلى فوتكس PI متميز (وإلا ستكون النتيجة الخطأ EINVAL). بالإضافة إلى ذلك، يجب أن يكون val (عدد المنتظرين الذين سيتم إيقاظهم) هو 1 (وإلا ستكون النتيجة الخطأ EINVAL).

عمليات PI futex هي كما يلي:

تُستخدم هذه العملية بعد فشل محاولة حيازة القفل عبر تعليمات ذرية في وضع المستخدم لأن كلمة الفوتكس لها قيمة غير صفرية—تحديداً، لأنها كانت تحتوي على TID (الخاص بنطاق أسماء PID) لمالك القفل.
تتحقق العملية من قيمة كلمة الفوتكس عند العنوان uaddr. إذا كانت القيمة 0، تحاول النواة ضبط قيمة الفوتكس ذرياً على TID الخاص بالمستدعي. إذا كانت قيمة كلمة الفوتكس غير صفرية، تقوم النواة بضبط بت FUTEX_WAITERS ذرياً، مما يعطي إشارة لمالك الفوتكس بأنه لا يمكنه إلغاء قفل الفوتكس في مساحة المستخدم ذرياً بضبط قيمة الفوتكس على 0. بعد ذلك، تقوم النواة بما يلي:
(1)
تحاول العثور على الخيط المرتبط بـ TID للمالك.
(2)
تنشئ حالة النواة أو تعيد استخدامها نيابة عن المالك. (إذا كان هذا هو المنتظر الأول، فلا توجد حالة نواة لهذا الفوتكس، لذا تُنشأ حالة النواة بقفل RT-mutex ويتم جعل مالك الفوتكس مالكاً لـ RT-mutex. إذا كان هناك منتظرون موجودون بالفعل، فيعاد استخدام الحالة الموجودة.)
(3)
تلحق المنتظر بالفوتكس (أي يتم إدراج المنتظر في صف انتظار RT-mutex).
إذا وجد أكثر من منتظر واحد، يكون إدراج المنتظر في الصف بترتيب تنازلي للأولويات. (للحصول على معلومات حول ترتيب الأولويات، راجع مناقشة سياسات الجدولة SCHED_DEADLINE، و SCHED_FIFO، و SCHED_RR في sched(7)). يرث المالك إما عرض النطاق الترددي للمعالج الخاص بالمنتظر (إذا كان المنتظر مجدولاً تحت سياسة SCHED_DEADLINE) أو أولوية المنتظر (إذا كان المنتظر مجدولاً تحت سياسة SCHED_RR أو SCHED_FIFO). يتبع هذا الميراث سلسلة القفل في حالة القفل المتداخل ويقوم باكتشاف الحصر المتبادل (Deadlock).
توفر المعطاة timeout مهلة لمحاولة القفل. إذا كانت timeout ليست NULL، فإن الهيكل الذي تشير إليه يحدد مهلة مطلقة، تُقاس مقابل ساعة CLOCK_REALTIME. إذا كانت timeout هي NULL، فستُحصر العملية إلى أجل غير مسمى.
تُتجاهل المعطيات uaddr2، و val، و val3.
هذه العملية هي نفسها FUTEX_LOCK_PI، باستثناء أن الساعة التي تُقاس بها timeout قابلة للاختيار. مبدئياً، تُقاس المهلة (المطلقة) المحددة في timeout مقابل ساعة CLOCK_MONOTONIC، ولكن إذا تم تحديد علم FUTEX_CLOCK_REALTIME في futex_op، فستُقاس المهلة مقابل ساعة CLOCK_REALTIME.
تحاول هذه العملية حيازة القفل عند uaddr. تُستدعى عندما لم تنجح الحيازة الذرية في مساحة المستخدم لأن كلمة الفوتكس لم تكن 0.
نظرًا لأن النواة لديها إمكانية الوصول إلى معلومات حالة أكثر من مساحة المستخدم، فقد تنجح حيازة القفل إذا قامت بها النواة في الحالات التي تحتوي فيها كلمة الفوتكس (أي معلومات الحالة التي يمكن لمساحة المستخدم الوصول إليها) على حالة قديمة (FUTEX_WAITERS و/أو FUTEX_OWNER_DIED). يمكن أن يحدث هذا عند وفاة صاحب الفوتكس. لا تستطيع مساحة المستخدم التعامل مع هذه الحالة بطريقة خالية من التنافس، ولكن يمكن للنواة إصلاح ذلك وحيازة الفوتكس.
تُتجاهل المعطيات uaddr2، و val، و timeout، و val3.
توقظ هذه العملية المنتظر صاحب الأولوية القصوى الذي ينتظر في FUTEX_LOCK_PI أو FUTEX_LOCK_PI2 على عنوان الفوتكس المقدم بواسطة معطاة uaddr.
يُستدعى هذا عندما لا يمكن تغيير القيمة في مساحة المستخدم عند uaddr ذرياً من TID (للمالك) إلى 0.
تُتجاهل المعطيات uaddr2، و val، و timeout، و val3.
هذه العملية هي متغير مدرك لـ PI من FUTEX_CMP_REQUEUE. تعيد وضع المنتظرين المحصورين عبر FUTEX_WAIT_REQUEUE_PI على uaddr من فوتكس مصدر غير PI‏ (uaddr) إلى فوتكس هدف PI‏ (uaddr2).
كما هو الحال مع FUTEX_CMP_REQUEUE، توقظ هذه العملية بحد أقصى val من المنتظرين الذين ينتظرون على الفوتكس عند uaddr. ومع ذلك، بالنسبة لـ FUTEX_CMP_REQUEUE_PI، يجب أن يكون val هو 1 (بما أن الهدف الرئيس هو تجنب القطيع الهائج). يُحذف المنتظرون المتبقون من صف انتظار فوتكس المصدر عند uaddr ويُضافون إلى صف انتظار فوتكس الهدف عند uaddr2.
تخدم المعطيات val2 و val3 نفس الأغراض كما في FUTEX_CMP_REQUEUE.
ينتظر على futex غير مرتبط بوراثة الأولوية (non-PI) عند uaddr ومن المحتمل إعادة وضعه في الطابور (عبر عملية FUTEX_CMP_REQUEUE_PI في مهمة أخرى) على futex مرتبط بوراثة الأولوية (PI) عند uaddr2. عملية الانتظار على uaddr هي نفسها في FUTEX_WAIT.
يمكن إزالة المنتظر من الانتظار على uaddr دون إعادة وضعه في الطابور على uaddr2 عبر عملية FUTEX_WAKE في مهمة أخرى. وفي هذه الحالة، تفشل عملية FUTEX_WAIT_REQUEUE_PI مع الخطأ EAGAIN.
إذا كانت timeout ليست NULL، فإن الهيكل الذي تشير إليه يحدد مهلة مطلقة لعملية الانتظار. إذا كانت timeout هي NULL، يمكن حصر العملية إلى أجل غير مسمى.
يُتجاهل المعطى val3.
أُضيفت العمليتان FUTEX_WAIT_REQUEUE_PI و FUTEX_CMP_REQUEUE_PI لدعم حالة استخدام محددة تمامًا: دعم متغيرات الحالة لخيوط POSIX المدركة لوراثة الأولوية. الفكرة هي وجوب إقران هاتين العمليتين دائمًا، لضمان بقاء مساحة المستخدم والنواة متزامنتين. وبذلك، يحدد تطبيق مساحة المستخدم مسبقًا في عملية FUTEX_WAIT_REQUEUE_PI هدف إعادة الاصطفاف الذي يحدث في عملية FUTEX_CMP_REQUEUE_PI.

قيمة الإرجاع

في حال حدوث خطأ (وبافتراض استدعاء futex عبر syscall(2))، تعيد جميع العمليات -1 وتضبط errno للإشارة إلى الخطأ.

تعتمد القيمة المعادة عند النجاح على العملية، كما هو موضح في القائمة التالية:

تعيد 0 إذا أُوقظ المستدعِي. لاحظ أن الاستيقاظ قد ينتج أيضًا عن أنماط استخدام futex الشائعة في كود غير ذي صلة تصادف استخدامه سابقًا لموقع ذاكرة كلمة futex (على سبيل المثال، يمكن لتطبيقات mutexes خيوط Pthreads التقليدية المعتمدة على futex أن تسبب هذا في ظل ظروف معينة). لذا، يجب على المستدعِين دائمًا افتراض أن القيمة المعادة 0 قد تعني استيقاظًا زائفًا، واستخدام قيمة كلمة futex (أي مخطط التزامن في مساحة المستخدم) لتقرير ما إذا كان سيستمر في الإعاقة أم لا.
تعيد عدد المنتظرين الذين أُوقظوا.
تعيد واصف الملف الجديد المرتبط بـ futex.
تعيد عدد المنتظرين الذين أُوقظوا.
تعيد إجمالي عدد المنتظرين الذين أُوقظوا أو أُعيد وضعهم في الطابور لـ futex عند كلمة futex في uaddr2. إذا كانت هذه القيمة أكبر من val، فإن الفرق هو عدد المنتظرين الذين أُعيد وضعهم في الطابور لـ futex عند كلمة futex في uaddr2.
تعيد إجمالي عدد المنتظرين الذين أُوقظوا. هذا هو مجموع المنتظرين المستيقظين على كلي الـ futexes لكلمتي futex عند uaddr و uaddr2.
تعيد 0 إذا أُوقظ المستدعِي. انظر FUTEX_WAIT لمعرفة كيفية تفسير ذلك بشكل صحيح في الممارسة العملية.
تعيد عدد المنتظرين الذين أُوقظوا.
تعيد 0 إذا وُضع قفل futex بنجاح.
تعيد 0 إذا وُضع قفل futex بنجاح.
تعيد 0 إذا وُضع قفل futex بنجاح.
تعيد 0 إذا فُك قفل futex بنجاح.
تعيد إجمالي عدد المنتظرين الذين أُوقظوا أو أُعيد وضعهم في الطابور لـ futex عند كلمة futex في uaddr2. إذا كانت هذه القيمة أكبر من val، فإن الفرق هو عدد المنتظرين الذين أُعيد وضعهم في الطابور لـ futex عند كلمة futex في uaddr2.
تعيد 0 إذا أُعيد المستدعِي بنجاح إلى طابور futex عند كلمة futex في uaddr2.

الأخطاء

لا يوجد وصول قراءة إلى ذاكرة كلمة futex.
(FUTEX_WAIT, FUTEX_WAIT_BITSET, FUTEX_WAIT_REQUEUE_PI) القيمة التي يشير إليها uaddr لم تكن مساوية للقيمة المتوقعة val في وقت الاستدعاء.
ملاحظة: في لينكس، الاسمان الرمزيان EAGAIN و EWOULDBLOCK (اللذان يظهران في أجزاء مختلفة من كود futex في النواة) لهما نفس القيمة.
(FUTEX_CMP_REQUEUE, FUTEX_CMP_REQUEUE_PI) القيمة التي يشير إليها uaddr لا تساوي القيمة المتوقعة val3.
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI, FUTEX_CMP_REQUEUE_PI) معرف خيط مالك futex لـ uaddr (بالنسبة لـ FUTEX_CMP_REQUEUE_PI: uaddr2) على وشك الخروج، لكنه لم يعالج بعد تنظيف الحالة الداخلية. حاول مرة أخرى.
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI, FUTEX_CMP_REQUEUE_PI) كلمة futex عند uaddr مقفلة بالفعل من قِبل المستدعِي.
(FUTEX_CMP_REQUEUE_PI) أثناء إعادة وضع منتظر في طابور PI futex لكلمة futex عند uaddr2، اكتشفت النواة حالة تعاجز (deadlock).
معطى مؤشر مطلوب (مثل uaddr أو uaddr2 أو timeout) لا يشير إلى عنوان صحيح في مساحة المستخدم.
قوطعت عملية FUTEX_WAIT أو FUTEX_WAIT_BITSET بواسطة إشارة (انظر signal(7)). قبل إصدار لينكس 2.6.22، كان من الممكن أيضًا إعادة هذا الخطأ عند حدوث استيقاظ زائف؛ ومنذ الإصدار 2.6.22، لم يعد هذا يحدث.
العملية في futex_op هي واحدة من تلك التي تستخدم مهلة زمنية، لكن معطى timeout المقدم كان غير صالح (tv_sec أقل من الصفر، أو tv_nsec ليس أقل من 1,000,000,000).
تستخدم العملية المحددة في futex_op أحد المؤشرين uaddr و uaddr2 أو كليهما، ولكن أحدهما لا يشير إلى كائن صالح؛ أي أن العنوان غير محاذٍ لأربعة بايتات.
(FUTEX_WAIT_BITSET, FUTEX_WAKE_BITSET) قناع البتات المزود في val3 هو صفر.
(FUTEX_CMP_REQUEUE_PI) uaddr يساوي uaddr2 (أي جرت محاولة لإعادة الاصطفاف في نفس الـ futex).
(FUTEX_FD) رقم الإشارة المزود في val غير صالح.
(FUTEX_WAKE, FUTEX_WAKE_OP, FUTEX_WAKE_BITSET, FUTEX_REQUEUE, FUTEX_CMP_REQUEUE) اكتشفت النواة تضاربًا بين حالة مساحة المستخدم عند uaddr وحالة النواة؛ أي أنها اكتشفت منتظرًا ينتظر في FUTEX_LOCK_PI أو FUTEX_LOCK_PI2 على uaddr.
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI, FUTEX_UNLOCK_PI) اكتشفت النواة تضاربًا بين حالة مساحة المستخدم عند uaddr وحالة النواة. يشير هذا إما إلى فساد في الحالة أو أن النواة وجدت منتظرًا على uaddr ينتظر عبر FUTEX_WAIT أو FUTEX_WAIT_BITSET.
(FUTEX_CMP_REQUEUE_PI) اكتشفت النواة تضاربًا بين حالة مساحة المستخدم عند uaddr2 وحالة النواة؛ أي أن النواة اكتشفت منتظرًا ينتظر عبر FUTEX_WAIT أو FUTEX_WAIT_BITSET على uaddr2.
(FUTEX_CMP_REQUEUE_PI) اكتشفت النواة تضاربًا بين حالة مساحة المستخدم عند uaddr وحالة النواة؛ أي أن النواة اكتشفت منتظرًا ينتظر عبر FUTEX_WAIT أو FUTEX_WAIT_BITSET على uaddr.
(FUTEX_CMP_REQUEUE_PI) اكتشفت النواة تضاربًا بين حالة مساحة المستخدم عند uaddr وحالة النواة؛ أي أن النواة اكتشفت منتظرًا ينتظر على uaddr عبر FUTEX_LOCK_PI أو FUTEX_LOCK_PI2 (بدلًا من FUTEX_WAIT_REQUEUE_PI).
(FUTEX_CMP_REQUEUE_PI) جرت محاولة لإعادة اصطفاف منتظر في futex غير ذلك المحدد بواسطة استدعاء FUTEX_WAIT_REQUEUE_PI المطابق لهذا المنتظر.
(FUTEX_CMP_REQUEUE_PI) المعطى val ليس 1.
معطى غير صالح.
(FUTEX_FD) وُصل إلى الحد الأقصى لعدد الملفات المفتوحة على مستوى النظام.
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI, FUTEX_CMP_REQUEUE_PI) لم تتمكن النواة من تخصيص ذاكرة للاحتفاظ بمعلومات الحالة.
حددت عملية غير صالحة في futex_op.
حُدد الخيار FUTEX_CLOCK_REALTIME في futex_op، ولكن العملية المرافقة لم تكن FUTEX_WAIT ولا FUTEX_WAIT_BITSET ولا FUTEX_WAIT_REQUEUE_PI ولا FUTEX_LOCK_PI2.
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI, FUTEX_UNLOCK_PI, FUTEX_CMP_REQUEUE_PI, FUTEX_WAIT_REQUEUE_PI) حدد فحص وقت التشغيل أن العملية غير متاحة. عمليات PI-futex غير مطبقة على جميع البنيات وغير مدعومة في بعض متغيرات وحدة المعالجة المركزية.
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI, FUTEX_CMP_REQUEUE_PI) لا يُسمح لـ المستدعِي بربط نفسه بـ futex عند uaddr (بالنسبة لـ FUTEX_CMP_REQUEUE_PI: الـ futex عند uaddr2). (قد يكون هذا ناتجًا عن فساد الحالة في مساحة المستخدم).
(FUTEX_UNLOCK_PI) المستدعِي لا يملك القفل الذي تمثله كلمة futex.
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI, FUTEX_CMP_REQUEUE_PI) معرف الخيط في كلمة futex عند uaddr غير موجود.
(FUTEX_CMP_REQUEUE_PI) معرف الخيط في كلمة futex عند uaddr2 غير موجود.
استخدمت العملية في futex_op المهلة الزمنية المحددة في timeout، وانتهت المهلة قبل اكتمال العملية.

المعايير

لينكس.

التاريخ

لينكس 2.6.0.

دُمج دعم futex الأولي في لينكس 2.5.7 ولكن بدلالات مختلفة عما وُصف أعلاه. قُدم استدعاء نظام بأربعة معطيات مع الدلالات الموضحة في هذه الصفحة في لينكس 2.5.40. أُضيف معطى خامس في لينكس 2.5.70، وأُضيف معطى سادس في لينكس 2.6.7.

أمثلة

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


$ ./futex_demo
Parent (18534) 0
Child  (18535) 0
Parent (18534) 1
Child  (18535) 1
Parent (18534) 2
Child  (18535) 2
Parent (18534) 3
Child  (18535) 3
Parent (18534) 4
Child  (18535) 4

مصدر البرنامج

/* futex_demo.c

Usage: futex_demo [nloops]
(Default: 5)
Demonstrate the use of futexes in a program where parent and child
use a pair of futexes located inside a shared anonymous mapping to
synchronize access to a shared resource: the terminal. The two
processes each write 'num-loops' messages to the terminal and employ
a synchronization protocol that ensures that they alternate in
writing messages. */ #define _GNU_SOURCE #include <err.h> #include <errno.h> #include <linux/futex.h> #include <stdatomic.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/syscall.h> #include <sys/time.h> #include <sys/wait.h> #include <unistd.h> static uint32_t *futex1, *futex2, *iaddr; static int futex(uint32_t *uaddr, int futex_op, uint32_t val,
const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3) {
return syscall(SYS_futex, uaddr, futex_op, val,
timeout, uaddr2, val3); } /* Acquire the futex pointed to by 'futexp': wait for its value to
become 1, and then set the value to 0. */ static void fwait(uint32_t *futexp) {
long s;
const uint32_t one = 1;
/* atomic_compare_exchange_strong(ptr, oldval, newval)
atomically performs the equivalent of:
if (*ptr == *oldval)
*ptr = newval;
It returns true if the test yielded true and *ptr was updated. */
while (1) {
/* Is the futex available? */
if (atomic_compare_exchange_strong(futexp, &one, 0))
break; /* Yes */
/* Futex is not available; wait. */
s = futex(futexp, FUTEX_WAIT, 0, NULL, NULL, 0);
if (s == -1 && errno != EAGAIN)
err(EXIT_FAILURE, "futex-FUTEX_WAIT");
} } /* Release the futex pointed to by 'futexp': if the futex currently
has the value 0, set its value to 1 and then wake any futex waiters,
so that if the peer is blocked in fwait(), it can proceed. */ static void fpost(uint32_t *futexp) {
long s;
const uint32_t zero = 0;
/* atomic_compare_exchange_strong() was described
in comments above. */
if (atomic_compare_exchange_strong(futexp, &zero, 1)) {
s = futex(futexp, FUTEX_WAKE, 1, NULL, NULL, 0);
if (s == -1)
err(EXIT_FAILURE, "futex-FUTEX_WAKE");
} } int main(int argc, char *argv[]) {
pid_t childPid;
unsigned int nloops;
setbuf(stdout, NULL);
nloops = (argc > 1) ? atoi(argv[1]) : 5;
/* Create a shared anonymous mapping that will hold the futexes.
Since the futexes are being shared between processes, we
subsequently use the "shared" futex operations (i.e., not the
ones suffixed "_PRIVATE"). */
iaddr = mmap(NULL, sizeof(*iaddr) * 2, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (iaddr == MAP_FAILED)
err(EXIT_FAILURE, "mmap");
futex1 = &iaddr[0];
futex2 = &iaddr[1];
*futex1 = 0; /* State: unavailable */
*futex2 = 1; /* State: available */
/* Create a child process that inherits the shared anonymous
mapping. */
childPid = fork();
if (childPid == -1)
err(EXIT_FAILURE, "fork");
if (childPid == 0) { /* Child */
for (unsigned int j = 0; j < nloops; j++) {
fwait(futex1);
printf("Child (%jd) %u\n", (intmax_t) getpid(), j);
fpost(futex2);
}
exit(EXIT_SUCCESS);
}
/* Parent falls through to here. */
for (unsigned int j = 0; j < nloops; j++) {
fwait(futex2);
printf("Parent (%jd) %u\n", (intmax_t) getpid(), j);
fpost(futex1);
}
wait(NULL);
exit(EXIT_SUCCESS); }

انظر أيضًا

get_robust_list(2), restart_syscall(2), pthread_mutexattr_getprotocol(3), futex(7), sched(7)

ملفات مصدر النواة التالية:

Documentation/pi-futex.txt
Documentation/futex-requeue-pi.txt
Documentation/locking/rt-mutex.txt
Documentation/locking/rt-mutex-design.txt
Documentation/robust-futex-ABI.txt

فرانك، هـ.، ورسل، ر.، وكيرود، م.، 2002. الضجيج، والـ Futexes والـ Furwocks: القفل السريع على مستوى المستخدم في لينكس (من وقائع ندوة أوتاوا للينكس 2002)،
http://kernel.org/doc/ols/2002/ols2002-pages-479-495.pdf

هارت، د.، 2009. نظرة عامة وتحديث حول futex، http://lwn.net/Articles/360699/

هارت، د. وغونيغونتالا، د.، 2009. Requeue-PI: جعل glibc Condvars مدركة لـ PI (من وقائع ورشة عمل لينكس للوقت الحقيقي 2009)، http://lwn.net/images/conf/rtlws11/papers/proc/p10.pdf

دريبر، أ.، 2011. الـ Futexes مخادعة، http://www.akkadia.org/drepper/futex.pdf

مكتبة أمثلة Futex،‏ futex-*.tar.bz2 عند
https://mirrors.kernel.org/pub/linux/kernel/people/rusty/

ترجمة

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

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

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

15 يونيو 2024 صفحات دليل لينكس 6.9.1