select(2)

الاسم
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO, fd_set - تعدد الإرسال المتزامن للإدخال/الإخراج

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

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

typedef /* ... */ fd_set;

int select(int nfds, fd_set *_Nullable restrict readfds,
           fd_set *_Nullable restrict writefds,
           fd_set *_Nullable restrict exceptfds,
           struct timeval *_Nullable restrict timeout);

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, const fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

int pselect(int nfds, fd_set *_Nullable restrict readfds,
           fd_set *_Nullable restrict writefds,
           fd_set *_Nullable restrict exceptfds,
           const struct timespec *_Nullable restrict timeout,
           const sigset_t *_Nullable restrict sigmask);

    متطلبات ماكروات اختبار الميزات لـ glibc (انظر feature_test_macros(7)):

pselect():
    _POSIX_C_SOURCE >= 200112L

الوصف
تحذير: select() يمكنه مراقبة أرقام واصفات الملفات الأقل من FD_SETSIZE (1024) فقط—حد منخفض بشكل غير معقول للعديد من التطبيقات الحديثة—وهذا القيد لن يتغير. يجب على جميع التطبيقات الحديثة استخدام poll(2) أو epoll(7) بدلاً من ذلك، والتي لا تعاني من هذا القيد.

select() يسمح للبرنامج بمراقبة واصفات ملفات متعددة، منتظراً حتى يصبح واحد أو أكثر من واصفات الملفات "جاهزاً" لفئة معينة من عمليات الإدخال/الإخراج (مثل، إدخال ممكن). يعتبر واصف الملف جاهزاً إذا كان من الممكن تنفيذ عملية إدخال/إخراج مقابلة (مثل، read(2)، أو write(2) صغير بما يكفي) دون حظر.

  fd_set
نوع بنية يمكنه تمثيل مجموعة من واصفات الملفات. وفقاً لـ POSIX، الحد الأقصى لعدد واصفات الملفات في بنية fd_set هو قيمة الماكرو FD_SETSIZE.

  مجموعات واصفات الملفات
الوسائط الرئيسية لـ select() هي ثلاث "مجموعات" من واصفات الملفات (مُعلنة بالنوع fd_set)، والتي تسمح للمستدعي بالانتظار لثلاث فئات من الأحداث على مجموعة واصفات الملفات المحددة. يمكن تحديد كل وسيطة من fd_set كـ NULL إذا لم تكن هناك واصفات ملفات يجب مراقبتها لفئة الأحداث المقابلة.

ملاحظة مهمة: عند العودة، يتم تعديل كل مجموعة من مجموعات واصفات الملفات في مكانها للإشارة إلى واصفات الملفات الجاهزة حالياً. وبالتالي، إذا تم استخدام select() داخل حلقة، يجب إعادة تهيئة المجموعات قبل كل استدعاء.

يمكن معالجة محتويات مجموعة واصفات الملفات باستخدام الماكرو التالية:

FD_ZERO()
    يقوم هذا الماكرو بمسح (إزالة جميع واصفات الملفات من) set. يجب استخدامه كخطوة أولى في تهيئة مجموعة واصفات الملفات.

FD_SET()
    يضيف هذا الماكرو واصف الملف fd إلى set. إضافة واصف ملف موجود بالفعل في المجموعة هو عملية عدم فعل، ولا ينتج عنه خطأ.

FD_CLR()
    يزيل هذا الماكرو واصف الملف fd من set. إزالة واصف ملف غير موجود في المجموعة هو عملية عدم فعل، ولا ينتج عنه خطأ.

FD_ISSET()
    يقوم select() بتعديل محتويات المجموعات وفقاً للقواعد الموصوفة أدناه. بعد استدعاء select()، يمكن استخدام الماكرو FD_ISSET() لاختبار ما إذا كان واصف الملف لا يزال موجوداً في مجموعة. يُرجع FD_ISSET() قيمة غير صفرية إذا كان واصف الملف fd موجوداً في set، وصفراً إذا لم يكن كذلك.

  المعطيات
وسائط select() هي كما يلي:

readfds
    يتم مراقبة واصفات الملفات في هذه المجموعة لمعرفة ما إذا كانت جاهزة للقراءة. واصف الملف جاهز للقراءة إذا كانت عملية القراءة لن تحظر؛ على وجه الخصوص، واصف الملف جاهز أيضًا عند نهاية الملف.

    بعد أن يعود select()، سيتم مسح readfds من جميع واصفات الملفات باستثناء تلك الجاهزة للقراءة.

writefds
    يتم مراقبة واصفات الملفات في هذه المجموعة لمعرفة ما إذا كانت جاهزة للكتابة. واصف الملف جاهز للكتابة إذا كانت عملية الكتابة لن تحظر. ومع ذلك، حتى إذا أشار واصف الملف إلى أنه قابل للكتابة، فقد تظل الكتابة الكبيرة تحظر.

    بعد أن يعود select()، سيتم مسح writefds من جميع واصفات الملفات باستثناء تلك الجاهزة للكتابة.

exceptfds
    يتم مراقبة واصفات الملفات في هذه المجموعة لـ "ظروف استثنائية". للحصول على أمثلة لبعض الظروف الاستثنائية، انظر مناقشة POLLPRI في poll(2).

    بعد أن يعود select()، سيتم مسح exceptfds من جميع واصفات الملفات باستثناء تلك التي حدثت لها حالة استثنائية.

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

timeout
    وسيطة timeout هي بنية timeval (موضحة أدناه) تحدد الفاصل الزمني الذي يجب أن يحظر فيه select() منتظراً حتى يصبح واصف الملف جاهزاً. سوف يحظر الاستدعاء حتى:

    •   يصبح واصف الملف جاهزًا؛

    •   الاستدعاء قوطع بواسطة معالج إشارة؛ أو

    •   انتهاء المهلة.

    لاحظ أن فاصل timeout سيتم تقريبه لأعلى إلى دقة ساعة النظام، وتأخيرات جدولة النواة تعني أن فاصل الحظر قد يتجاوز بمقدار صغير.

    إذا كان كلا حقلي بنية timeval صفراً، فإن select() يعود فوراً. (هذا مفيد للاستقصاء.)

    إذا تم تحديد timeout كـ NULL، فإن select() يحظر إلى أجل غير مسمى منتظراً حتى يصبح واصف الملف جاهزاً.

  pselect()
استدعاء النظام pselect() يسمح للتطبيق بالانتظار بأمان حتى يصبح واصف الملف جاهزاً أو حتى يتم التقاط إشارة.

عملية select() و pselect() متطابقة، باستثناء هذه الاختلافات الثلاثة:

•   تستخدم select() مهلة زمنية من النوع struct timeval (مع الثواني والميكروثواني)، بينما تستخدم pselect() النوع struct timespec (مع الثواني والنانوثواني).

•   قد تحدّث select() الوسيط timeout للإشارة إلى مقدار الوقت المتبقي. لا تغيّر pselect() هذه الوسيط.

•   لا تحتوي select() على وسيط sigmask، وتتصرف مثل pselect() التي استُدعيت مع sigmask بقيمة NULL.

sigmask هو مؤشر لقناع إشارة (انظر sigprocmask(2))؛ إذا لم يكن NULL، فإن pselect() تستبدل أولاً قناع الإشارة الحالي بالقناع المشار إليه بواسطة sigmask، ثم تؤدي وظيفة "select"، ثم تستعيد قناع الإشارة الأصلي. (إذا كان sigmask هو NULL، لا يُعدّل قناع الإشارة أثناء استدعاء pselect().)

بخلاف الاختلاف في دقة وسيط timeout، فإن استدعاء pselect() التالي:

    ready = pselect(nfds, &readfds, &writefds, &exceptfds,
                    timeout, &sigmask);

يعادل تنفيذ الاستدعاءات التالية بشكل ذري:

    sigset_t origmask;

    pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
    ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
    pthread_sigmask(SIG_SETMASK, &origmask, NULL);

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

  المهلة الزمنية
وسيط timeout لـ select() هو بنية من النوع التالي:

    struct timeval {
        time_t      tv_sec;         /* seconds */
        suseconds_t tv_usec;        /* microseconds */
    };

الوسيط المقابل لـ pselect() هو بنية timespec(3).

في لينكس، تعدل select() timeout لتعكس مقدار الوقت غير المُنام؛ معظم التطبيقات الأخرى لا تفعل هذا. (يسمح POSIX.1 بأي من السلوكين.) يسبب هذا مشاكل سواء عندما يُنقل كود لينكس الذي يقرأ timeout إلى أنظمة تشغيل أخرى، وعندما يُنقل كود إلى لينكس يعيد استخدام struct timeval لعدة select()s في حلقة دون إعادة تهيئته. اعتبر timeout غير معرف بعد عودة select().

قيمة الإرجاع
عند النجاح، تُرجع select() و pselect() عدد واصفات الملفات الموجودة في مجموعات الواصفات الثلاث المُعادة (أي العدد الإجمالي للبتات المُعيّنة في readfds و writefds و exceptfds). قد تكون قيمة الإرجاع صفرًا إذا انتهت المهلة الزمنية قبل أن يصبح أي واصف ملف جاهزًا.

عند الخطأ، تُعاد -1، ويُعيّن errno للإشارة إلى الخطأ؛ تبقى مجموعات واصفات الملفات دون تعديل، ويصبح timeout غير معرف.

الأخطاء

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

EINTR
    اُلتُقطت إشارة؛ انظر signal(7).

EINVAL
    nfds سالب أو يتجاوز حد المورد RLIMIT_NOFILE (انظر getrlimit(2)).

EINVAL
    القيمة الموجودة داخل timeout غير صالحة.

ENOMEM
    غير قادر على تخصيص ذاكرة للجداول الداخلية.

الإصدارات
في بعض أنظمة UNIX الأخرى، قد تفشل select() مع الخطأ EAGAIN إذا فشل النظام في تخصيص موارد داخلية للنواة، بدلاً من ENOMEM كما يفعل لينكس. يحدد POSIX هذا الخطأ لـ poll(2)، ولكن ليس لـ select(). قد ترغب البرامج المحمولة في التحقق من EAGAIN والتكرار، تمامًا كما هو الحال مع EINTR.

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

التاريخ

select()
    POSIX.1-2001،‏ 4.4BSD (ظهر أولاً في 4.2BSD).

    يمكن نقلها عمومًا من وإلى الأنظمة غير التابعة لنظام BSD التي تدعم نسخًا مطابقة لطبقة مآخذ BSD (بما في ذلك متغيرات System V). ومع ذلك، تجدر الإشارة إلى أن متغير System V عادةً ما يقوم بتعيين متغير المهلة قبل العودة، بينما لا يقوم متغير BSD بذلك.

pselect()
    لينكس 2.6.16. POSIX.1g, POSIX.1-2001.

    قبل ذلك، كان يُحاكى في glibc (ولكن انظر الأخطاء).

fd_set
    POSIX.1-2001.

ملاحظات
الملف الرأسي التالي يوفر أيضًا النوع fd_set: <sys/time.h>.

fd_set هو مخزن ثابت الحجم. تنفيذ FD_CLR() أو FD_SET() بقيمة fd سالبة أو تساوي أو أكبر من FD_SETSIZE سيؤدي إلى سلوك غير معرف. علاوة على ذلك، يتطلب POSIX أن يكون fd واصف ملف صالحًا.

لا يتأثر تشغيل select() و pselect() بالعلامة O_NONBLOCK.

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

  محاكاة usleep(3)
قبل ظهور usleep(3)، استخدم بعض الكود استدعاءً لـ select() مع جميع المجموعات الثلاث فارغة، nfds صفر، و timeout غير NULL كطريقة قابلة للنقل بشكل معقول للنوم بدقة دون الثانية.

  التوافق بين إشعارات select() و poll()
داخل مصدر نواة لينكس، نجد التعريفات التالية التي تُظهر التوافق بين إشعارات الحالة القابلة للقراءة والقابلة للكتابة والاستثنائية لـ select() وإشعارات الأحداث التي توفرها poll(2) و epoll(7):

    #define POLLIN_SET  (EPOLLRDNORM | EPOLLRDBAND | EPOLLIN |
                         EPOLLHUP | EPOLLERR)
                       /* Ready for reading */
    #define POLLOUT_SET (EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT |
                         EPOLLERR)
                       /* Ready for writing */
    #define POLLEX_SET  (EPOLLPRI)
                       /* Exceptional condition */

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

  الاختلافات بين مكتبة C والنواة
يسمح نواة لينكس بمجموعات واصفات ملفات ذات حجم عشوائي، محددًا طول المجموعات المراد فحصها من قيمة nfds. لكن، في تطبيق glibc، النوع fd_set ثابت الحجم. انظر أيضًا الأخطاء.

واجهة pselect() الموصوفة في هذه الصفحة منفذة بواسطة glibc. استدعاء نظام لينكس الأساسي يُسمى pselect6(). هذا الاستدعاء له سلوك مختلف قليلاً عن دالة الغلاف glibc.

يعدل استدعاء نظام لينكس pselect6() وسيطته timeout. لكن، دالة غلاف glibc تخفي هذا السلوك باستخدام متغير محلي لوسيطة timeout التي تُمرر لاستدعاء النظام. وبالتالي، دالة glibc pselect() لا تعدل وسيطتها timeout؛ هذا هو السلوك المطلوب بواسطة POSIX.1-2001.

الوسيطة الأخيرة لاستدعاء النظام pselect6() ليست مؤشر sigset_t *، بل هي بنية بالشكل:

    struct {
        const kernel_sigset_t *ss;   /* Pointer to signal set */
        size_t ss_len;               /* Size (in bytes) of object
                                        pointed to by 'ss' */
    };

يسمح هذا لاستدعاء النظام بالحصول على كل من مؤشر لمجموعة الإشارات وحجمها، مع مراعاة حقيقة أن معظم البنى تدعم حدًا أقصى 6 وسائط لاستدعاء النظام. انظر sigprocmask(2) لمناقشة الفرق بين مفهوم النواة وlibc لمجموعة الإشارات.

  تفاصيل glibc التاريخية
قدم glibc 2.0 نسخة غير صحيحة من pselect() لم تأخذ وسيطة sigmask.

من glibc 2.1 إلى glibc 2.2.1، يجب تعريف _GNU_SOURCE للحصول على تعريف pselect() من <sys/select.h>.

العلل
يسمح POSIX للتطبيق بتعريف حد أعلى، معلن عنه عبر الثابت FD_SETSIZE، على نطاق واصفات الملفات التي يمكن تحديدها في مجموعة واصفات ملفات. لا تفرض نواة لينكس حدًا ثابتًا، لكن تطبيق glibc يجعل fd_set نوعًا ثابت الحجم، مع تعريف FD_SETSIZE كـ 1024، وتعمل وحدات الماكرو FD_*() وفقًا لذلك الحد. لمراقبة واصفات ملفات أكبر من 1023، استخدم poll(2) أو epoll(7) بدلاً من ذلك.

تطبيق وسائط fd_set كوسائط قيمة-نتيجة هو خطأ تصميمي يُتجنب في poll(2) و epoll(7).

وفقًا لـ POSIX، يجب على select() فحص جميع واصفات الملفات المحددة في المجموعات الثلاث لواصفات الملفات، حتى الحد nfds-1. لكن، التطبيق الحالي يتجاهل أي واصف ملف في هذه المجموعات أكبر من رقم واصف الملف الأقصى الذي تمتلكه العملية مفتوحًا حاليًا. وفقًا لـ POSIX، أي واصف ملف كهذا محدد في إحدى المجموعات يجب أن يؤدي إلى الخطأ EBADF.

بدءًا من glibc 2.1، قدم glibc محاكاة لـ pselect() منفذة باستخدام sigprocmask(2) و select(). ظل هذا التطبيق عرضة لنفس حالة السباق التي صُممت pselect() لمنعها. تستخدم الإصدارات الحديثة من glibc استدعاء النظام (الخالي من السباق) pselect() على النوى حيث يُوفر.

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

في لينكس، تعدل select() أيضًا timeout إذا قاطع معالج إشارة الاستدعاء (أي، إرجاع الخطأ EINTR). هذا غير مسموح به بواسطة POSIX.1. استدعاء نظام لينكس pselect() له نفس السلوك، لكن غلاف glibc يخفي هذا السلوك بنسخ timeout داخليًا إلى متغير محلي وتمرير ذلك المتغير لاستدعاء النظام.

أمثلة
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/select.h>
    #include <sys/time.h>

    int
    main(void)
    {
        int             retval;
        fd_set          rfds;
        struct timeval  tv;

        /* Watch stdin (fd 0) to see when it has input.  */

        FD_ZERO(&rfds);
        FD_SET(0, &rfds);

        /* Wait up to five seconds.  */

        tv.tv_sec = 5;
        tv.tv_usec = 0;

        retval = select(1, &rfds, NULL, NULL, &tv);
        /* Don't rely on the value of tv now! */

        if (retval == -1)
            perror("select()");
        else if (retval)
            printf("Data is available now.\n");
            /* FD_ISSET(0, &rfds) will be true.  */
        else
            printf("No data within five seconds.\n");

        exit(EXIT_SUCCESS);
    }

انظر أيضًا
accept(2), connect(2), poll(2), read(2), recv(2), restart_syscall(2), send(2), sigprocmask(2), write(2), timespec(3), epoll(7), time(7)

لدروس تعليمية مع مناقشة وأمثلة، انظر select_tut(2).

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

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

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