epoll(7)

الاسم
epoll - مرفق إعلام أحداث الإدخال/الإخراج

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

الوصف
تقوم واجهة برمجة التطبيقات epoll بمهمة مشابهة لـ poll(2): مراقبة واصفات ملفات متعددة لمعرفة ما إذا كان الإدخال/الإخراج ممكنًا على أي منها. يمكن استخدام واجهة برمجة التطبيقات epoll إما كواجهة محفزة بالحافة أو محفزة بالمستوى وتتوسع جيدًا لأعداد كبيرة من واصفات الملفات المراقبة.

المفهوم المركزي لواجهة برمجة التطبيقات epoll هو مثيل epoll، وهو بنية بيانات داخل النواة يمكن، من منظور مساحة المستخدم، اعتبارها حاوية لقائمتين:

•   قائمة الاهتمام (تسمى أحيانًا مجموعة epoll): مجموعة واصفات الملفات التي سجلت العملية اهتمامًا بمراقبتها.

•   قائمة الجاهزية: مجموعة واصفات الملفات "الجاهزة" للإدخال/الإخراج. قائمة الجاهزية هي مجموعة فرعية من (أو، بشكل أكثر دقة، مجموعة مراجع لـ) واصفات الملفات في قائمة الاهتمام. تُملئ قائمة الجاهزية ديناميكيًا بواسطة النواة نتيجة لنشاط الإدخال/الإخراج على واصفات الملفات تلك.

تُوفر استدعاءات النظام التالية لإنشاء وإدارة مثيل epoll:

•   يقوم epoll_create(2) بإنشاء مثيل epoll جديد ويعيد واصف ملف يشير إلى ذلك المثيل. (يقوم epoll_create1(2) الأحدث بتوسيع وظائف epoll_create(2).)

•   ثم يُسجل الاهتمام بواصفات ملفات معينة عبر epoll_ctl(2)، الذي يضيف عناصر إلى قائمة الاهتمام لمثيل epoll.

•   ينتظر epoll_wait(2) أحداث الإدخال/الإخراج، مما يحجب الخيط المستدعي إذا لم تكن أي أحداث متاحة حاليًا. (يمكن اعتبار استدعاء النظام هذا كجلب عناصر من قائمة الجاهزية لمثيل epoll.)

  محفز بالمستوى ومحفز بالحافة
واجهة توزيع الأحداث epoll قادرة على التصرف كمحفز بالحافة (ET) ومحفز بالمستوى (LT). يمكن وصف الفرق بين الآليتين على النحو التالي. افترض أن هذا السيناريو يحدث:

(1) يُسجل واصف الملف الذي يمثل جانب القراءة من أنبوب (rfd) على مثيل epoll.

(2) يكتب كاتب الأنبوب 2 كيلوبايت من البيانات على جانب الكتابة من الأنبوب.

(3) يُستدعى epoll_wait(2) الذي سيعيد rfd كواصف ملف جاهز.

(4) يقرأ قارئ الأنبوب 1 كيلوبايت من البيانات من rfd.

(5) يُستدعى epoll_wait(2).

إذا أُضيف واصف الملف rfd إلى واجهة epoll باستخدام العلم EPOLLET (محفز بالحافة)، فإن استدعاء epoll_wait(2) الذي تم في الخطوة 5 سيعلق على الأرجح على الرغم من البيانات المتاحة الموجودة في مخزن الإدخال المؤقت للملف؛ وفي الوقت نفسه، قد يتوقع النظير البعيد استجابة بناءً على البيانات التي أرسلها بالفعل. السبب في ذلك هو أن الوضع المحفز بالحافة يسلم الأحداث فقط عند حدوث تغييرات على واصف الملف المراقب، أي أنه يُولّد حدث عند كل استلام لكتلة من البيانات. لذا، في الخطوة 5 قد ينتهي الأمر بالمستدعي بانتظار بعض البيانات الموجودة بالفعل داخل مخزن الإدخال المؤقت. في المثال أعلاه، سيُولّد حدث على rfd بسبب الكتابة التي تمت في 2 ويُستهلك الحدث في 3. نظرًا لأن عملية القراءة التي تمت في 4 لا تستهلك بيانات المخزن المؤقت بالكامل، فإن استدعاء epoll_wait(2) الذي تم في الخطوة 5 قد يحظر إلى أجل غير مسمى.

يجب على تطبيق يستخدم العلم EPOLLET استخدام واصفات ملفات غير محظورة لتجنب تجويع مهمة تتعامل مع واصفات ملفات متعددة بسبب قراءة أو كتابة محظورة. الطريقة المقترحة لاستخدام epoll كواجهة محفزة بالحافة (EPOLLET) هي كما يلي:

(1) مع واصفات ملفات غير محظورة؛ و

(2) بانتظار حدث فقط بعد أن يعيد read(2) أو write(2) EAGAIN.

على النقيض، عند استخدامه كواجهة محفزة بالمستوى (المبدئي، عندما لا يُحدد EPOLLET)، فإن epoll هو ببساطة poll(2) أسرع، ويمكن استخدامه أينما اُستخدم الأخير لأنه يشارك نفس الدلالات.

نظرًا لأنه حتى مع epoll المحفز بالحافة، يمكن توليد أحداث متعددة عند استلام كتل متعددة من البيانات، فإن لدى المستدعي خيار تحديد العلم EPOLLONESHOT، لإخبار epoll بتعطيل واصف الملف المرتبط بعد استلام حدث مع epoll_wait(2). عند تحديد العلم EPOLLONESHOT، تقع على عاتق المستدعي مسؤولية إعادة تسليح واصف الملف باستخدام epoll_ctl(2) مع EPOLL_CTL_MOD.

إذا حُظر تعدد الخيوط (أو عمليات، إذا ورثت العمليات الفرعية واصف ملف epoll عبر fork(2)) في epoll_wait(2) منتظرة نفس واصف ملف epoll وأصبح واصف ملف في قائمة الاهتمام محدد للإعلام المحفز بالحافة (EPOLLET) جاهزًا، يُوقظ خيط واحد فقط (أو عملية) من epoll_wait(2). يوفر هذا تحسينًا مفيدًا لتجنب إيقاظ "القطيع المتدافع" في بعض السيناريوهات.

  التفاعل مع التعليق الآلي
إذا كان النظام في وضع autosleep عبر /sys/power/autosleep وحدث حدث يوقظ الجهاز من التعليق؛ فسيُبقي برنامج تشغيل الجهاز الجهاز مستيقظًا فقط حتى يُوضع ذلك الحدث في قائمة الانتظار. لإبقاء الجهاز مستيقظًا حتى يُعالج الحدث، من الضروري استخدام علامة epoll_ctl(2) EPOLLWAKEUP.

عند تعيين علامة EPOLLWAKEUP في حقل events لـ struct epoll_event، سيُبقي النظام مستيقظًا من لحظة وضع الحدث في قائمة الانتظار، عبر استدعاء epoll_wait(2) الذي يُرجع الحدث حتى استدعاء epoll_wait(2) التالي. إذا كان يجب أن يُبقي الحدث النظام مستيقظًا بعد ذلك الوقت، فيجب أخذ wake_lock منفصل قبل استدعاء epoll_wait(2) الثاني.

  واجهات /proc
يمكن استخدام الواجهات التالية للحد من مقدار ذاكرة النواة التي يستهلكها epoll:

/proc/sys/fs/epoll/max_user_watches (منذ Linux 2.6.28)
    يحدد هذا حدًا للعدد الإجمالي لوصفات الملفات التي يمكن لمستخدم تسجيلها عبر جميع مثيلات epoll على النظام. الحد هو لكل معرف مستخدم حقيقي. تكلف كل واصف ملف مسجل حوالي 90 بايت على نواة 32 بت، وحوالي 160 بايت على نواة 64 بت. حاليًا، القيمة المبدئية لـ max_user_watches هي 1/25 (4%) من الذاكرة المنخفضة المتاحة، مقسومة على تكلفة التسجيل بالبايت.

  مثال للاستخدام المقترح
بينما استخدام epoll عند توظيفه كواجهة محفزة بالمستوى له نفس دلالات poll(2)، فإن الاستخدام المحفز بالحافة يتطلب مزيدًا من التوضيح لتجنب التوقف في حلقة أحداث التطبيق. في هذا المثال، المستمع هو مقبس غير محظور اُستدعيت listen(2) عليه. تستخدم الدالة do_use_fd() واصف الملف الجديد الجاهز حتى يُرجع EAGAIN بواسطة إما read(2) أو write(2). يجب على تطبيق آلة الحالة الموجه بالأحداث، بعد استلام EAGAIN، تسجيل حالته الحالية بحيث في الاستدعاء التالي لـ do_use_fd() سيستمر في read(2) أو write(2) من حيث توقف سابقًا.

    #define MAX_EVENTS 10
    struct epoll_event ev, events[MAX_EVENTS];
    int listen_sock, conn_sock, nfds, epollfd;

    /* Code to set up listening socket, 'listen_sock',
       (socket(), bind(), listen()) omitted.  */

    epollfd = epoll_create1(0);
    if (epollfd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    ev.events = EPOLLIN;
    ev.data.fd = listen_sock;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
        perror("epoll_ctl: listen_sock");
        exit(EXIT_FAILURE);
    }

    for (;;) {
        nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for (n = 0; n < nfds; ++n) {
            if (events[n].data.fd == listen_sock) {
                conn_sock = accept(listen_sock,
                                   (struct sockaddr *) &addr, &addrlen);
                if (conn_sock == -1) {
                    perror("accept");
                    exit(EXIT_FAILURE);
                }
                setnonblocking(conn_sock);
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = conn_sock;
                if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                            &ev) == -1) {
                    perror("epoll_ctl: conn_sock");
                    exit(EXIT_FAILURE);
                }
            } else {
                do_use_fd(events[n].data.fd);
            }
        }
    }

عند استخدامه كواجهة محفزة بالحافة، لأسباب تتعلق بالأداء، من الممكن إضافة واصف الملف داخل واجهة epoll (EPOLL_CTL_ADD) مرة واحدة عن طريق تحديد (EPOLLIN|EPOLLOUT). يتيح لك هذا تجنب التبديل المستمر بين EPOLLIN و EPOLLOUT باستدعاء epoll_ctl(2) مع EPOLL_CTL_MOD.

  أسئلة وأجوبة

•   ما هو المفتاح المستخدم للتمييز بين واصفات الملفات المسجلة في قائمة الاهتمام؟

    المفتاح هو مزيج من رقم واصف الملف ووصف الملف المفتوح (المعروف أيضًا باسم "مقبض الملف المفتوح"، التمثيل الداخلي للنواة لملف مفتوح).

•   ماذا يحدث إذا سجلت نفس واصف الملف على مثيل epoll مرتين؟

    ستحصل على الأرجح على EEXIST. ومع ذلك، من الممكن إضافة واصف ملف مكرر (dup(2), dup2(2), fcntl(2) F_DUPFD) إلى نفس مثيل epoll. يمكن أن تكون هذه تقنية مفيدة لتصفية الأحداث، إذا سُجلت واصفات الملفات المكررة بأقنعة events مختلفة.

•   هل يمكن لمثيلين من epoll الانتظار لنفس واصف الملف؟ إذا كان الأمر كذلك، هل يُبلغ عن الأحداث لكلا واصفي ملف epoll؟

    نعم، وسيُبلغ عن الأحداث لكليهما. ومع ذلك، قد تكون هناك حاجة إلى برمجة دقيقة للقيام بذلك بشكل صحيح.

•   هل واصف ملف epoll نفسه قابل للاستقصاء/epoll/الاختيار؟

    نعم. إذا كان واصف ملف epoll يحتوي على أحداث في انتظار، فسيشير إلى أنه قابل للقراءة.

•   ماذا يحدث إذا حاول المرء وضع واصف ملف epoll في مجموعة واصفات الملفات الخاصة به؟

    يفشل استدعاء epoll_ctl(2) (EINVAL). ومع ذلك، يمكنك إضافة واصف ملف epoll داخل مجموعة واصفات ملف epoll أخرى.

•   هل يمكنني إرسال واصف ملف epoll عبر مقبس نطاق UNIX إلى عملية أخرى؟

    نعم، ولكن ليس من المنطقي القيام بذلك، لأن العملية المستقبلة لن يكون لديها نسخ من واصفات الملفات في قائمة الاهتمام.

•   هل سيؤدي إغلاق واصف ملف إلى إزالته من جميع قوائم اهتمام epoll؟

    نعم، ولكن كن على دراية بالنقطة التالية. واصف الملف هو مرجع لوصف ملف مفتوح (انظر open(2)). كلما كُرر واصف ملف عبر dup(2), dup2(2), fcntl(2) F_DUPFD, أو fork(2)، يُنشئ واصف ملف جديد يشير إلى نفس وصف الملف المفتوح. يستمر وصف الملف المفتوح في الوجود حتى تُغلق جميع واصفات الملفات التي تشير إليه.

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

•   إذا حدث أكثر من حدث واحد بين استدعاءات epoll_wait(2)، هل تُجمع أم يُبلغ عنها بشكل منفصل؟

    ستُجمع.

•   هل تؤثر عملية على واصف ملف على الأحداث المجموعة فعلًا ولكن لم يُبلغ عنها بعد؟

    يمكنك إجراء عمليتين على واصف ملف موجود. الإزالة ستكون غير ذات معنى لهذه الحالة. التعديل سيعيد قراءة الإدخال/الإخراج المتاح.

•   هل أحتاج إلى القراءة/الكتابة باستمرار من واصف ملف حتى EAGAIN عند استخدام علامة EPOLLET (السلوك المحفز بالحافة)؟

    استلام حدث من epoll_wait(2) يجب أن يشير لك أن واصف الملف هذا جاهز لعملية الإدخال/الإخراج المطلوبة. يجب أن تعتبره جاهزًا حتى تؤدي القراءة/الكتابة التالية (غير المحظورة) إلى EAGAIN. متى وكيف ستستخدم واصف الملف يعود إليك بالكامل.

    بالنسبة للملفات الموجهة للحزم/الرموز (مثل مقبس البيانات، الطرفية في الوضع القانوني)، الطريقة الوحيدة لاكتشاف نهاية مساحة الإدخال/الإخراج للقراءة/الكتابة هي الاستمرار في القراءة/الكتابة حتى EAGAIN.

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

  المزالق المحتملة وطرق تجنبها

•   التجويع (محفز بالحافة)

    إذا كانت هناك كمية كبيرة من مساحة الإدخال/الإخراج، فمن الممكن أن يؤدي محاولة تفريغها إلى عدم معالجة الملفات الأخرى مما يسبب التجويع. (هذه المشكلة ليست خاصة بـ epoll.)

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

•   إذا كنت تستخدم خبيئة أحداث...

    إذا كنت تستخدم خبيئة أحداث أو تخزن جميع واصفات الملفات المعادة من epoll_wait(2)، فتأكد من توفير طريقة لوضع علامة على إغلاقها ديناميكيًا (أي ناتج عن معالجة حدث سابق). افترض أنك تستلم 100 حدث من epoll_wait(2)، وفي الحدث #47 تسبب حالة في إغلاق الحدث #13. إذا أزلت البنية واستدعيت close(2) لواصف الملف للحدث #13، فقد تظل خبيئة الأحداث تقول أن هناك أحداثًا تنتظر واصف الملف هذا مما يسبب ارتباكًا.

    أحد الحلول لذلك هو استدعاء، أثناء معالجة الحدث 47، epoll_ctl(EPOLL_CTL_DEL) لحذف واصف الملف 13 و close(2)، ثم وضع علامة على بنية البيانات المرتبطة به كمزالة وربطها بقائمة تنظيف. إذا وجدت حدثًا آخر لواصف الملف 13 في معالجتك الدفعية، ستكتشف أن واصف الملف قد أزيل سابقًا ولن يكون هناك ارتباك.

الإصدارات
بعض الأنظمة الأخرى توفر آليات مماثلة؛ على سبيل المثال، FreeBSD لديها kqueue، وSolaris لديها /dev/poll.

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

التاريخ
لينكس 2.5.44. glibc 2.3.2.

ملاحظات
يمكن عرض مجموعة واصفات الملفات المُراقبة عبر واصف ملف epoll من خلال الإدخال الخاص بواصف ملف epoll في دليل /proc/pid/fdinfo للعملية. انظر proc(5) لمزيد من التفاصيل.

يمكن استخدام عملية kcmp(2) KCMP_EPOLL_TFD لاختبار ما إذا كان واصف ملف موجودًا في مثيل epoll.

انظر أيضًا
epoll_create(2)، epoll_create1(2)، epoll_ctl(2)، epoll_wait(2)، ioctl_eventpoll(2)، poll(2)، select(2)

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

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

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