select(2)

NAZWA
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO, fd_set - synchronicznie zwielokrotnia wejście/wyjście

BIBLIOTEKA
Standardowa biblioteka C (libc, -lc)

SKŁADNIA
#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);

    Wymagane ustawienia makr biblioteki glibc (patrz feature_test_macros(7)):

pselect():
    _POSIX_C_SOURCE >= 200112L

OPIS
OSTRZEŻENIE: select() potrafi monitorować deskryptory plików o numerach mniejszych niż FD_SETSIZE (1024) — limit nierozsądnie niewielki dla wielu współczesnych aplikacji — i ograniczenie to nie zmieni się. Wszystkie współczesne aplikacje powinny w zamian korzystać z poll(2) lub epoll(7), które nie mają tego ograniczenia.

select() umożliwia programowi monitorowanie wielu deskryptorów plików i oczekiwanie aż jeden lub więcej deskryptorów będzie „gotowy” na wykonanie pewnej klasy operacji wejścia/wyjścia (np. możliwy odczyt). Deskryptor pliku jest uważany za gotowy, jeżeli możliwe jest wykonanie odpowiadającej operacji wejścia/wyjścia (np. read(2) lub odpowiednio małe write(2)) bez blokowania.

  fd_set
Typ struktury mogącej reprezentować zbiór deskryptorów pliku. Zgodnie z POSIX, maksymalną liczbą deskryptorów pliku w strukturze fd_set jest wartość makra FD_SETSIZE.

  Zbiory deskryptorów pliku
Podstawowymi argumentami select() są trzy „zbiory” deskryptorów pliku (zadeklarowane jako typ fd_set), pozwalające wywołującemu na oczekiwanie na trzy klasy zdarzeń na zadanym zbiorze deskryptorów pliku. Każdy argument fd_set można podać jako NULL, jeśli dla odpowiadającej klasy zdarzeń nie mają być monitorowane deskryptory pliku.

Proszę zwrócić szczególną uwagę: Po powrocie, każdy ze zbiorów deskryptorów pliku jest modyfikowany w miejscu, aby wskazać, które z deskryptorów pliku są aktualnie „gotowe”. Z tego względu, jeśli select() jest używany w pętli, zbiory muszą być zainicjowane ponownie przed każdym wywołaniem.

Zawartością zbiorów deskryptorów pliku można manipulować za pomocą następujących makr:

FD_ZERO()
    Makro czyści (usuwa z niego wszystkie deskryptory pliku) set. Powinno być użyte jako pierwszy krok w inicjowaniu zbiorów deskryptorów pliku.

FD_SET()
    Makro dodaje deskryptor pliku fd do zbioru set. Dodanie deskryptora pliku, który jest już obecny w zbiorze, jest instrukcją pustą i nie powoduje błędu.

FD_CLR()
    Makro usuwa deskryptor pliku fd z zbioru set. Usunięcie deskryptora pliku, który nie jest obecny w zbiorze jest instrukcją pustą i nie powoduje błędu.

FD_ISSET()
    select() modyfikuje zawartość zbiorów, zgodnie z regułami opisanymi niżej. Po wywołaniu select(), można skorzystać z makra FD_ISSET(), aby sprawdzić, czy deskryptor pliku jest wciąż obecny w zbiorze. FD_ISSET() zwraca wartość niezerową, jeśli deskryptor pliku fd jest obecny w zbiorze set i zero, gdy nie jest.

  Argumenty
Argumenty select() są następujące:

readfds
    Deskryptory pliku w tym zbiorze są monitorowane, aby sprawdzić, czy są gotowe do odczytu. Deskryptor pliku jest gotowy do odczytu, jeśli operacja odczytu nie będzie blokowała; w szczególności, deskryptor pliku jest również gotowy na końcu pliku.

    Po powrocie select(), readfds zostanie wyczyszczone ze wszystkich deskryptorów pliku, poza gotowymi do odczytu.

writefds
    Deskryptory pliku w tym zbiorze są monitorowane, aby sprawdzić, czy są gotowe do zapisu. Deskryptor pliku jest gotowy do zapisu, jeśli wszystkie operacje zapisu nie będą blokowały. Jednak nawet gdy deskryptor pliku jest wskazywany jako zapisywalny, duży zapis może wciąż blokować.

    Po powrocie select(), writefds zostanie wyczyszczone ze wszystkich deskryptorów pliku, poza gotowymi do zapisu.

exceptfds
    Deskryptory pliku w tym zbiorze są monitorowane pod kątem „szczególnych okoliczności”. Przykłady takich szczególnych okoliczności opisano w podręczniku poll(2) odnośnie POLLPRI.

    Po powrocie select(), exceptfds zostanie wyczyszczone ze wszystkich deskryptorów pliku, poza tymi, dla których zaszły szczególne okoliczności.

nfds
    Argument ten powinien być ustawiony na najwyższy numer deskryptora z wszystkich trzech zbiorów plus 1. Sprawdzany jest wskazany deskryptor pliku w każdym zbiorze, do tego limitu (lecz zob. USTERKI).

timeout
    Argument timeout jest strukturą timeval (pokazaną niżej), określającą interwał, który powinien blokować select(), czekając na gotowość deskryptora plików. Wywołanie będzie skutkowało blokadą do momentu aż:

    •   deskryptor pliku stanie się dostępny

    •   lub wywołanie zostanie przerwane procedurą obsługi sygnału

    •   albo wywołanie przeterminuje się

    Proszę zauważyć, że interwał zostanie zaokrąglony w górę do dokładności zegara, a występowanie opóźnienia planisty jądra oznacza, że ten interwał może być nieznacznie przekroczony.

    Jeśli oba pola struktury timeval mają wartość zero, select() niezwłocznie zakończy pracę (jest to przydatne przy odpytywaniu).

    Jeśli timeout jest równe NULL, select() może blokować w nieskończoność, czekając na gotowość deskryptora pliku.

  pselect()
Wywołanie systemowe pselect() pozwala aplikacjom na bezpieczne oczekiwanie, do momentu uzyskania gotowości przez deskryptor plików lub do momentu przechwycenia sygnału.

Funkcjonalność funkcji select() i pselect() jest identyczna, jeśli pominąć trzy różnice:

•   Funkcja select() używa dla parametru timeout typu struct timeval (z sekundami i mikrosekundami), podczas gdy pselect() używa typu struct timespec (z sekundami i nanosekundami).

•   Funkcja select() może aktualizować parametr timeout, aby wskazać, jak dużo czasu minęło. Funkcja pselect() nie zmienia tego parametru.

•   Funkcja select() nie przyjmuje parametru sigmask i zachowuje się, jak pselect() wywołane z NULL-em przekazanym w sigmask.

sigmask jest wskaźnikiem do maski sygnałów (zobacz sigprocmask(2)). Jeśli nie jest równe NULL, to pselect() najpierw zastępuje bieżącą maskę sygnałów maską wskazywaną przez sigmask, a następnie wywołuje funkcję „select”, a po jej zakończeniu odtwarza oryginalną maskę sygnałów (jeśli sigmask wynosi NULL, to maska sygnałów nie jest modyfikowana podczas wywołania pselect()).

Poza różnicą w precyzji argumentu timeout, następujące wywołanie pselect():

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

jest odpowiednikiem niepodzielnego wykonania następujących funkcji:

    sigset_t origmask;

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

Idea pselect() polega na tym, że gdy chce się oczekiwać na zdarzenie będące sygnałem lub czymś na deskryptorze pliku, potrzebny jest atomowy test zapobiegający sytuacjom wyścigu. (Przypuśćmy, że procedura obsługi sygnału ustawia globalny znacznik i kończy działanie. Wówczas, test tego znacznika globalnego, po którym następuje wywołanie select() może wisieć w nieskończoność, gdyby sygnał przybył natychmiast po teście, ale przed wywołaniem. Inaczej mówiąc, pselect zezwala na, najpierw, zablokowanie sygnałów, następnie obsłużenie dostarczonych sygnałów, aby wreszcie wywołać pselect() z pożądanym sigmask, unikając wyścigu).

  Przeterminowanie
Argument timeout do select() jest strukturą następującego typu:

    struct timeval {
        time_t      tv_sec;         /* sekundy */
        suseconds_t tv_usec;        /* mikrosekundy */
    };

Odpowiadającym argumentem pselect() jest struktura timespec(3).

Pod Linuksem funkcja select() modyfikuje timeout, aby odzwierciedlić ilość nieprzespanego czasu; większość innych implementacji tego nie robi (POSIX.1 dopuszcza oba te zachowania). Powoduje to problemy, zarówno gdy kod linuksowy odczytujący timeout zostanie przeniesiony na inne systemy operacyjne, jak i gdy kod przeniesiony pod Linuksa z innych systemów używa ponownie struct timeval dla wielu wywołań select() w pętli, bez powtórnej inicjacji. Prosimy rozważyć traktowanie wartości timeout jako niezdefiniowanej po zakończeniu funkcji select().

WARTOŚĆ ZWRACANA
Po pomyślnym zakończeniu, select() i pselect() zwracają liczbę deskryptorów w zbiorach deskryptorów (to jest całkowitą liczbę bitów ustawioną w readfds, writefds, exceptfds). Wartość zwracana może wynosić zero, jeśli czas oczekiwania upłynął zanim jakiś deskryptor plików stał się gotowy.

Po błędzie zwracane jest -1 i ustawiane errno, wskazując błąd; zbiory deskryptorów nie są modyfikowane, a timeout staje się niezdefiniowane.

BŁĘDY

EBADF
    W jednym ze zbiorów przekazano niepoprawny deskryptor pliku (być może deskryptor ten został już zamknięty lub wystąpił na nim inny błąd). Zob. jednak BŁĘDY.

EINTR
    Przechwycono sygnał, patrz signal(7).

EINVAL
    nfds jest ujemne lub przekracza limit zasobów RLIMIT_NOFILE (zob. getrlimit(2)).

EINVAL
    Wartość timeout jest nieprawidłowa.

ENOMEM
    Nie można było przydzielić pamięci dla wewnętrznych tablic.

WERSJE
Na niektórych innych systemach Uniksowych select() może zwrócić błąd EAGAIN jeśli systemowi nie uda się przydzielić wewnątrzjądrowych zasobów, zamiast ENOMEM, tak jak robi to Linux. POSIX przewiduje ten błąd dla poll(2), lecz nie dla select(). Przenośne programy mogą chcieć sprawdzać EAGAIN w pętli, tak jak dla EINTR.

STANDARDY
POSIX.1-2024.

HISTORIA

select()
    POSIX.1-2001, 4.4BSD (pojawiło się pierwotnie w 4.2BSD).

    W ogólności jest przenośne do/z systemów nie-BSD wspierających sklonowaną warstwę gniazd BSD (włączając warianty Systemu V). Jednakże należy zauważyć, że warianty Systemu V zasadniczo ustawiają zmienną przeterminowania przed powrotem, ale wariant BSD tego nie robi.

pselect()
    Linux 2.6.16. POSIX.1g, POSIX.1-2001.

    Wcześniej był on emulowany w glibc (patrz również USTERKI).

fd_set
    POSIX.1-2001.

UWAGI
Następujący nagłówek zapewnia również typ fd_set: <sys/time.h>.

fd_set jest buforem o stałym rozmiarze. Wykonanie FD_CLR() lub FD_SET() z ujemną wartością fd albo z wartością większą lub równą FD_SETSIZE spowoduje zachowanie niezdefiniowane. Ponadto POSIX wymaga, by fd był prawidłowym deskryptorem pliku.

Na działanie select() i pselect() nie wpływa znacznik O_NONBLOCK.

  Sztuczka „potoku do siebie”
W systemach, które nie mają pselect() niezawodne (i bardziej przenośne) przechwytywanie sygnałów można osiągnąć, używając sztuczki w postaci „potoku do siebie”. W tej technice procedura obsługi sygnału zapisuje bajt do potoku, którego drugi koniec jest monitorowany przez select() w głównym programie. Aby uniknąć możliwego zablokowania przy pisaniu do potoku który może być pełny lub czytaniu z potoku który może być pusty, przy czytaniu z i pisaniu do potoku używane jest nieblokujące wejście/wyjście.

  Emulowanie usleep(3)
Przed powstaniem usleep(3) niektóre programy używały wywołania select() z wszystkimi trzema zbiorami pustymi, z nfds równym zeru i niezerowym timeout. Jest to całkiem przenośny sposób pauzowania z dokładnością subsekundową.

  Relacja pomiędzy powiadomieniami select() i poll()
W źródłach jądra Linux można znaleźć następujące definicje pokazujące relację pomiędzy powiadomieniami o warunkach: odczytu, zapisu i szczególnych okoliczności select(), a powiadomieniami zdarzeń zapewnianymi przez poll(2) i epoll(7):

    #define POLLIN_SET  (EPOLLRDNORM | EPOLLRDBAND | EPOLLIN |
                         EPOLLHUP | EPOLLERR)
                       /* Gotowość do odczytu */
    #define POLLOUT_SET (EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT |
                         EPOLLERR)
                       /* Gotowość do zapisu */
    #define POLLEX_SET  (EPOLLPRI)
                       /* Szczególne okoliczności */

  Aplikacje wielowątkowe
Jeśli deskryptor plików monitorowany przez select() zostanie zamknięty w innym wątku, to rezultat jest nieokreślony. Na niektórych systemach uniksowych select() odblokuje go i powróci wskazując, że dany deskryptor plików jest gotowy (kolejne operacje wyjścia/wyjścia prawdopodobnie zakończą się błędem, chyba że inny proces otworzy ponownie deskryptor plików w czasie pomiędzy powrotem select() a wykonanie operacji wejścia/wyjścia. W systemie Linux (i części innych) zamknięcie deskryptora pliku w innym wątku nie wpłynie na select(). Podsumowując, każda aplikacja polegająca na konkretnym zachowaniu w takim przypadku musi być uznana za błędną.

  Różnice biblioteki C/jądra
Jądro Linux pozwala deskryptorowi pliku ustawić dowolny rozmiar, na podstawie długości zbiorów do sprawdzenia z wartości nfds. Jednak w implementacji glibc, typ fd_set ma stały rozmiar. Zob. też USTERKI.

Interfejs pselect() opisany w niniejszym podręczniku jest zaimplementowany w glibc. Stojące za nim linuksowe wywołanie systemowe nazywa się pselect6(). Cechuje go nieco inne zachowanie od opisywanej funkcji opakowującej glibc.

Wywołanie systemowe pselect6() pod Linuksem modyfikuje argument timeout. Jednakże funkcja glibc ukrywa to zachowanie przez użycie dla argumentu timeout lokalnej zmiennej, która jest przekazywana do wywołania systemowego. Dlatego pselect() z glibc nie zmienia argumentu timeout, co jest zachowaniem wymaganym przez POSIX.1-2001.

Ostatnim argumentem wywołania systemowego pselect6() nie jest wskaźnik sigset_t * lecz struktura postaci:

    struct {
        const kernel_sigset_t *ss;   /* Wskaźnik do zestawu sygnałów */
        size_t ss_len;               /* Rozmiar (w bajtach) obiektu, na
                                        który wskazuje 'ss' */
    };

Pozwala to wywołaniu systemowemu pobrać oba wskaźniki do zestawu sygnałowego wraz z rozmiarem, biorąc pod uwagę, że większość architektur obsługuje maksymalnie 6 argumentów do wywołania systemowego. Opis różnic pomiędzy podejściem jądra i libc do zestawu sygnałów znajduje się w podręczniku sigprocmask(2).

  Historyczne detale glibc
glibc 2.0 dostarczała niepoprawną wersję pselect(), która nie przyjmowała argumentu sigmask.

Od glibc 2.1 do glibc 2.2.1 konieczne było zdefiniowanie _GNU_SOURCE, aby uzyskać deklarację pselect() z <sys/select.h>.

USTERKI
POSIX pozwala implementacji zdefiniować górny limit zakresu deskryptorów plików, które można podać w zbiorze deskryptora pliku, rozgłaszany stałą FD_SETSIZE. Jądro Linux nie wymusza stałego limitu, lecz implementacja glibc czyni fd_set typem o stałym rozmiarze, z FD_SETSIZE zdefiniowanym jako 1024 i makrami FD_*() działającymi zgodnie z tym limitem. Aby monitorować deskryptory plików większe niż 1023, należy w zamian użyć poll(2) lub epoll(7).

Implementacja argumentów fd_set jako argumentów wartość-wynik jest błędem projektowym, którego uniknięto w poll(2) i epoll(7).

Zgodnie z POSIX, select() powinien sprawdzać wszystkie podane deskryptory pliku w trzech zbiorach deskryptorów pliku, do limitu nfds-1. Jednak bieżąca implementacja ignoruje wszelkie deskryptory pliku w tych zbiorach, które są wyższe od maksymalnego numeru aktualnie otwartego przez proces deskryptora pliku. Zgodnie z POSIX, w przypadku wystąpienia takiego deskryptora pliku w jednym z zbiorów, powinien wystąpić błąd EBADF.

Od glibc 2.1, glibc dostarczała emulację pselect(), która była zaimplementowana przy użyciu sigprocmask(2) i select(). Implementacja ta pozostaje podatna na wiele błędów wyścigów (race conditions), których uniknięcie stanowiło ideę funkcji pselect(). Nowsze wersje glibc używają (wolnego od wyścigów) wywołania systemowego, jeśli tylko jądro dostarcza takiego wywołania.

W Linuksie select() może raportować deskryptory plików gniazd jako „dostępne do czytania”, podczas gdy kolejne czytania zostaną zablokowane. Może to się zdarzyć na przykład wtedy, gdy dane nadeszły, ale podczas sprawdzania okazało się, że mają złą sumę kontrolną i zostały odrzucone. Mogą wystąpić także inne sytuacje, w których deskryptor pliku jest błędnie raportowany jako gotowy. Dlatego używanie O_NONBLOCK na gniazdach, które nie powinny się blokować może być bezpieczniejsze.

Pod Linuksem wywołanie select() zmienia wartość timeout także wtedy, gdy zostanie przerwane przez procedurę obsługi sygnału (tj. zostanie zwrócony błąd EINTR). POSIX.1 nie pozwala na takie zachowanie. Wywołanie systemowe pselect() pod Linuksem zachowuje się tak samo, ale funkcja opakowująca biblioteki glibc ukrywa to zachowanie, kopiując wartość timeout do wewnętrznej lokalnej zmiennej i przekazując tę zmienną do wywołania systemowego.

PRZYKŁADY
    #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;

        /* Obserwacja stdin (fd 0) i sprawdzanie kiedy ma wejście.  */

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

        /* Czekanie do pięciu sekund.  */

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

        retval = select(1, &rfds, NULL, NULL, &tv);
        /* Nie należy już polegać na wartości tv! */

        if (retval == -1)
            perror("select()");
        else if (retval)
            printf("Dane są już dostępne.\n");
            /* FD_ISSET(0, &rfds) będzie prawdziwy.  */
        else
            printf("Brak danych w ciągu pięciu sekund.\n");

        exit(EXIT_SUCCESS);
    }

ZOBACZ TAKŻE
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)

Samouczek z dyskusją i przykładami znajduje się w select_tut(2).

TŁUMACZENIE
Tłumaczenie niniejszej strony podręcznika: Przemek Borys <pborys@dione.ids.pl>, Robert Luberda <robert@debian.org> i Michał Kułach <michal.kulach@gmail.com>

Niniejsze tłumaczenie jest wolną dokumentacją. Bliższe informacje o warunkach licencji można uzyskać zapoznając się z GNU General Public License w wersji 3 (https://www.gnu.org/licenses/gpl-3.0.html) lub nowszej. Nie przyjmuje się ŻADNEJ ODPOWIEDZIALNOŚCI.

Błędy w tłumaczeniu strony podręcznika prosimy zgłaszać na adres listy dyskusyjnej manpages-pl-list@lists.sourceforge.net.