The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

Интерактивная система просмотра системных руководств (man-ов)

 ТемаНаборКатегория 
 
 [Cписок руководств | Печать]

select_tut (2)
  • >> select_tut (2) ( Русские man: Системные вызовы )
  • select_tut (2) ( Linux man: Системные вызовы )
  •  

    НАЗВАНИЕ

    select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing  

    СИНТАКСИС

    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>

    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *utimeout);

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

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

    ОПИСАНИЕ

    Функция select (или pselect) является основной функцией большинства программ на языке C, эффективно обрабатывающих одновременно более одного файловового дескриптора (или сокета). Ее аргументами являются три массива файловых дескрипторов: readfds, writefds и exceptfds. Как правило, при использовании select программа ожидает "изменения состояния" одного или более файловых дескрипторов. Под "изменением состояния" понимается появление новых символов в потоке, с которым связан файловый дескриптор, или появление во внутренних буферах ядра места для записи в поток, или возникновение ошибки, связанной с файловым дескриптором (в случае сокета или канала это происходит, когда другая сторона закрывает соединение).

    Суммируя вышесказанное, select просто следит за несколькими файловыми дескрипторами и является стандартным вызовом Unix для этих целей.

    Массивы файловых дескрипторов называются наборами файловых дескрипторов. Каждый набор объявлен, как тип fd_set и его содержимое может быть изменено макросами FD_CLR, FD_ISSET, FD_SET и FD_ZERO. Обычно FD_ZERO является первой функцией, используемой со свежеобъявленным набором. После этого, отдельные файловые дескрипторы могут быть по-очереди добавлены с помощью FD_SET. select изменяет содержимое наборов в соответсвие с правилами, описанными ниже; после вызова select вы можете проверить, находится ли ваш файловый дескриптор все еще в наборе с помощью макроса FD_ISSET, возвращающей ненулевое значение, если дескриптор присутствует в наборе, и ноль, если не присутствует. FD_CLR удаляет файловый дескриптор из набора, хотя практическая ценность этого в хорошей программе сомнительна.

     

    АРГУМЕНТЫ

    readfds
    Этот набор служит для слежения за операциями чтения. После возврата из select readfds очищается от всех дескрипторов файлов, за исключением тех, для которых возможно немедленное чтение функциями recv() (для сокетов) или read() (для каналов, файлов и сокетов).
    writefds
    Этот набор служит для слежения за появлением места для записи данных в любой из файловых дескрипторов набора. После возврата из select writefds очищатся от всех файловых дескрипторов, за исключением тех, для которых возможна немедленная запись функциями send() (для сокетов) или write() (для каналов, файлов и сокетов).
    exceptfds
    Этот набор служит для слежения за исключениями или ошибками, связанными с любым из файловых дескрипторов набора. На самом деле слежение производится за появлением внепоточных (out-of-band - OOB) данных. Внепоточные данные посылаются через сокет с помощью флага MSG_OOB и, в действительности, exceptfds работает только для сокетов. Более подробно об этом написанов recv(2) и send(2). После возврата из select exceptfds очищается от всех файловых дескрипторов, кроме тех, для которых доступны внепоточные данные. Прочитать можно лишь один байт внепоточных данных (это делается с помощью recv()). Записать внепоточные данные можно в любой момент. Эта операция является неблокируемой. Поэтому нет необходимости в четвертом наборе, который мог бы служить для слежения за возможностью записи внепоточных данных в сокет.
    nfds
    Это целое число содержит значение, на единицу большеее максимального файлового дескриптора любого из наборов. Другими словами, при добавлении файловых дескрипторов в наборы необходимо подсчитывать максимальное целое значение любого из них, затем увеличить это значение на единицу и передать как аргумент nfds функции select.
    utimeout
    Этот аргумент задает наибольшее время, которое функция select будет ожидать изменения состояния дескрипторов. Если за это время ничего не произойдет, то функция возвратит управление вызвавшей программе. Если значение этого аргумента равно NULL, то select будет ожидать бесконечно. utimeout может быть установлен в ноль секунд; в этом случае select возвратит управление немедленно. Структура struct timeval определена как

    struct timeval {
     time_t tv_sec;    /* секунды */
     long tv_usec;     /* микросекунды */
    };
    
    ntimeout
    Этот аргумент имеет то же значение, что и utimeout, но структура struct timespec позволяет указывать время с точностью до наносекунд:

    struct timespec {
        long tv_sec;    /* секунды */
        long tv_nsec;   /* наносекунды */
    };
    
    sigmask
    Этот аргумент содержит набор сигналов, которые разрешены во время вызова pselect (см. sigaddset(3) и sigprocmask(2)). В качестве аргумента может быть передан NULL; в этом случае при входе в функцию и выходе из нее набор разрешенных сигналов не меняется. В этом случае функция ведет себя как select.

     

    КОМБИНИРОВАНИЕ СИГНАЛОВ И ДАННЫХ СОБЫТИЙ

    pselect должен использоваться как в случае если вы ожидаете сигнала, так и в случае, если вы ожидаете данных из файлового дескриптора. Программы, обрабатывающие сигналы, как правило лишь выставляют в обработчике сигнала глобальный флаг, который означает, что событие должно быть обработано в главно цикле программы. Появление сигнала заставит select (или pselect) вернуть управление вызвавшей программе; при этом errno будет установлен в EINTR. Это поведение продиктовано необходимостью обработки сигналов программой (ее главным циклом) во избежание бесконечной блокировки select. В главном цикле программы должно быть условие, проверяющее глобальный флаг. Возникает вопрос: а что если сигнал придет после проверки этого условия, но до вызова select? В этом случае select навсегда заблокируется, хотя и есть ожидающее событие. Для разрешения этой проблемы существует функция pselect. Эта функция может быть использована для маскировки сигналов, которые не должны быть приняты нигде, кроме как внутри pselect. Например, предположим что интересующее нас событие - это завершение дочернего процесса. Перед запуском главного цикла мы должны заблокировать SIGCHLD с помощью sigprocmask. Наш вызов pselect разрешит SIGCHLD указав изначальную маску сигналов. Программ будет выглядеть так:

    int child_events = 0;
    
    void child_sig_handler (int x) {
        child_events++;
        signal (SIGCHLD, child_sig_handler);
    }
    
    int main (int argc, char **argv) {
        sigset_t sigmask, orig_sigmask;
    
        sigemptyset (&sigmask);
        sigaddset (&sigmask, SIGCHLD);
        sigprocmask (SIG_BLOCK, &sigmask,
                                    &orig_sigmask);
    
        signal (SIGCHLD, child_sig_handler);
    
        for (;;) { /* главный цикл */
            for (; child_events > 0; child_events--) {
                /* здесь обработка событий */
            }
            r = pselect (nfds, &rd, &wr, &er, 0, &orig_sigmask);
    
            /* главная часть программы */
        }
    }
    

    Обратите внимание, что вышеуказанный вызов pselect может быть заменен:

            sigprocmask (SIG_BLOCK, &orig_sigmask, 0);
            r = select (nfds, &rd, &wr, &er, 0);
            sigprocmask (SIG_BLOCK, &sigmask, 0);
    

    но в этом случае все равно существует вероятность того, что сигнал будет получен после первого вызова sigprocmask, но до вызова select. Если вы все же решите сделать так, то разумно, как минимум, установить конечное время ожидания, чтобы процесс не блокировался. В настоящее время glibc работает таким образом. Ядро Linux не имеет встроенного вызова pselect.

     

    ПРАКТИКА

    Итак, какой прок от использования select? Разве нельзя просто считывать и записывать данные в файловые дескрипторы когда того захочется? Смысл использования select в том, что он следит за несколькими дескрипторами одновременно и корректно переводит процесс в режим ожидания, когда активности не наблюдается. Таким образом он позволяет вам одновременно обрабатывать несколько каналов и сокетов. Программисты Unix часто попадают в ситуацию, когда необходимо обработать ввод-вывод с более чем одного файловго дескриптора в то время как поток данных может быть неравномерным. Если вы создатите последовательность вызовов read и write, то вы можете попасть в ситуацию, когда один из вызовов будет ожидать данные из/в файлового дескриптора, в то время как другой будет простаивать, хотя данные для него уже появились. select позволяет эффективно справиться с такой ситуацией.

    Классический пример использования select приведен на странице man select:

    #include <stdio.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int
    main(void) {
        fd_set rfds;
        struct timeval tv;
        int retval;
    
        /* Следим ввели ли что-либо в stdin (fd 0). */
        FD_ZERO(&rfds);
        FD_SET(0, &rfds);
        /* Ждем до 5 секунд. */
        tv.tv_sec = 5;
        tv.tv_usec = 0;
    
        retval = select(1, &rfds, NULL, NULL, &tv);
        /* На значение tv в данный момент полагаться нельзя! */
    
        if (retval)
            printf("Данные доступны.\n");
            /* FD_ISSET(0, &rfds) will be true. */
        else
            printf("Нет данных в течение 5 секунд.\n");
    
        exit(0);
    }
    

     

    ПРИМЕР ПЕРЕНАПРАВЛЕНИЯ ПОРТА

    Пример ниже лучше демонстрирует возможности select. Программа осуществляет перенаправление одного порта TCP на другой.

    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <string.h>
    #include <signal.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <errno.h>
    
    static int forward_port;
    
    #undef max
    #define max(x,y) ((x) > (y) ? (x) : (y))
    
    static int listen_socket (int listen_port) {
        struct sockaddr_in a;
        int s;
        int yes;
        if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
            perror ("socket");
            return -1;
        }
        yes = 1;
        if (setsockopt
            (s, SOL_SOCKET, SO_REUSEADDR,
             (char *) &yes, sizeof (yes)) < 0) {
            perror ("setsockopt");
            close (s);
            return -1;
        }
        memset (&a, 0, sizeof (a));
        a.sin_port = htons (listen_port);
        a.sin_family = AF_INET;
        if (bind
            (s, (struct sockaddr *) &a, sizeof (a)) < 0) {
            perror ("bind");
            close (s);
            return -1;
        }
        printf ("ожидание соединений на порту %d\n",
                (int) listen_port);
        listen (s, 10);
        return s;
    }
    
    static int connect_socket (int connect_port,
                               char *address) {
        struct sockaddr_in a;
        int s;
        if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
            perror ("socket");
            close (s);
            return -1;
        }
    
        memset (&a, 0, sizeof (a));
        a.sin_port = htons (connect_port);
        a.sin_family = AF_INET;
    
        if (!inet_aton
            (address,
             (struct in_addr *) &a.sin_addr.s_addr)) {
            perror ("неправильный формат адрес IP");
            close (s);
            return -1;
        }
    
        if (connect
            (s, (struct sockaddr *) &a,
             sizeof (a)) < 0) {
            perror ("connect()");
            shutdown (s, SHUT_RDWR);
            close (s);
            return -1;
        }
        return s;
    }
    
    #define SHUT_FD1 {                      \
            if (fd1 >= 0) {                 \
                shutdown (fd1, SHUT_RDWR);  \
                close (fd1);                \
                fd1 = -1;                   \
            }                               \
        }
    
    #define SHUT_FD2 {                      \
            if (fd2 >= 0) {                 \
                shutdown (fd2, SHUT_RDWR);  \
                close (fd2);                \
                fd2 = -1;                   \
            }                               \
        }
    
    #define BUF_SIZE 1024
    
    int main (int argc, char **argv) {
        int h;
        int fd1 = -1, fd2 = -1;
        char buf1[BUF_SIZE], buf2[BUF_SIZE];
        int buf1_avail, buf1_written;
        int buf2_avail, buf2_written;
    
        if (argc != 4) {
            fprintf (stderr,
                     "Использование\n\tfwd <слушаемый-порт> \
    <порт-куда-перенаправлять> <IP-адрес-куда-перенаправлять>\n");
            exit (1);
        }
    
        signal (SIGPIPE, SIG_IGN);
    
        forward_port = atoi (argv[2]);
    
        h = listen_socket (atoi (argv[1]));
        if (h < 0)
            exit (1);
    
        for (;;) {
            int r, nfds = 0;
            fd_set rd, wr, er;
            FD_ZERO (&rd);
            FD_ZERO (&wr);
            FD_ZERO (&er);
            FD_SET (h, &rd);
            nfds = max (nfds, h);
            if (fd1 > 0 && buf1_avail < BUF_SIZE) {
                FD_SET (fd1, &rd);
                nfds = max (nfds, fd1);
            }
            if (fd2 > 0 && buf2_avail < BUF_SIZE) {
                FD_SET (fd2, &rd);
                nfds = max (nfds, fd2);
            }
            if (fd1 > 0
                && buf2_avail - buf2_written > 0) {
                FD_SET (fd1, &wr);
                nfds = max (nfds, fd1);
            }
            if (fd2 > 0
                && buf1_avail - buf1_written > 0) {
                FD_SET (fd2, &wr);
                nfds = max (nfds, fd2);
            }
            if (fd1 > 0) {
                FD_SET (fd1, &er);
                nfds = max (nfds, fd1);
            }
            if (fd2 > 0) {
                FD_SET (fd2, &er);
                nfds = max (nfds, fd2);
            }
    
            r = select (nfds + 1, &rd, &wr, &er, NULL);
    
            if (r == -1 && errno == EINTR)
                continue;
            if (r < 0) {
                perror ("select()");
                exit (1);
            }
            if (FD_ISSET (h, &rd)) {
                unsigned int l;
                struct sockaddr_in client_address;
                memset (&client_address, 0, l =
                        sizeof (client_address));
                r = accept (h, (struct sockaddr *)
                            &client_address, &l);
                if (r < 0) {
                    perror ("accept()");
                } else {
                    SHUT_FD1;
                    SHUT_FD2;
                    buf1_avail = buf1_written = 0;
                    buf2_avail = buf2_written = 0;
                    fd1 = r;
                    fd2 =
                        connect_socket (forward_port,
                                        argv[3]);
                    if (fd2 < 0) {
                        SHUT_FD1;
                    } else
                        printf ("connect from %s\n",
                                inet_ntoa
                                (client_address.sin_addr));
                }
            }
    /* NB: считать внепоточные данные перед обычными */
            if (fd1 > 0)
                if (FD_ISSET (fd1, &er)) {
                    char c;
                    errno = 0;
                    r = recv (fd1, &c, 1, MSG_OOB);
                    if (r < 1) {
                        SHUT_FD1;
                    } else
                        send (fd2, &c, 1, MSG_OOB);
                }
            if (fd2 > 0)
                if (FD_ISSET (fd2, &er)) {
                    char c;
                    errno = 0;
                    r = recv (fd2, &c, 1, MSG_OOB);
                    if (r < 1) {
                        SHUT_FD1;
                    } else
                        send (fd1, &c, 1, MSG_OOB);
                }
            if (fd1 > 0)
                if (FD_ISSET (fd1, &rd)) {
                    r =
                        read (fd1, buf1 + buf1_avail,
                              BUF_SIZE - buf1_avail);
                    if (r < 1) {
                        SHUT_FD1;
                    } else
                        buf1_avail += r;
                }
            if (fd2 > 0)
                if (FD_ISSET (fd2, &rd)) {
                    r =
                        read (fd2, buf2 + buf2_avail,
                              BUF_SIZE - buf2_avail);
                    if (r < 1) {
                        SHUT_FD2;
                    } else
                        buf2_avail += r;
                }
            if (fd1 > 0)
                if (FD_ISSET (fd1, &wr)) {
                    r =
                        write (fd1,
                               buf2 + buf2_written,
                               buf2_avail -
                               buf2_written);
                    if (r < 1) {
                        SHUT_FD1;
                    } else
                        buf2_written += r;
                }
            if (fd2 > 0)
                if (FD_ISSET (fd2, &wr)) {
                    r =
                        write (fd2,
                               buf1 + buf1_written,
                               buf1_avail -
                               buf1_written);
                    if (r < 1) {
                        SHUT_FD2;
                    } else
                        buf1_written += r;
                }
    /* проверить, что запись данных получила считанные данные */
            if (buf1_written == buf1_avail)
                buf1_written = buf1_avail = 0;
            if (buf2_written == buf2_avail)
                buf2_written = buf2_avail = 0;
    /* одна из сторон закрыла соединение, продолжать
       записывать, пока другая сторона не закончит */
            if (fd1 < 0
                && buf1_avail - buf1_written == 0) {
                SHUT_FD2;
            }
            if (fd2 < 0
                && buf2_avail - buf2_written == 0) {
                SHUT_FD1;
            }
        }
        return 0;
    }
    

    Вышеприведенная программа правильно перенаправляет большую чать соединений TCP, включая внепоточные данные, передаваемые серверами telnet. Она справляется со сложной проблемой поддержания одновременного двустороннего обмена данными. Возможно, вы решите, что эффективнее использовать fork() и выделить отдельный подпроцесс для каждого потока. На самом деле это сложнее, чем кажется. Другой идеей может быть использование неблокирующего ввода-вывода с помощью ioctl(). Это также может вызвать проблемы из за того, что придется использовать неэффективные таймауты.

    Программа не обрабатывает более одного соединения, однако она может быть легко добработана для этого с путем добавления связанного списка буферов - по одному на каждое соединение. В данный момент новые соединения приводят к закрытию текущего.

     

    ВЫБОР ПРАВИЛА

    Многие из тех, кто пытался использовать select, сталкивались с поведением, которое трудно понять, и которое приводила к непереносимым или просто плохим результатам. Например, вышеприведенная программа тщательно спланирована так, чтобы ни в каком случае не блокироваться, хотя для ее файловых дескрипторов не установлен неблокирующий режим (см. ioctl(2)). Несложно перечислить неочевидные ошибки, которые лишат всех преимуществ использования select, поэтому я приведу список основных моментов, на которые нужно обращать внимание при использовании select.

    1.
    Всегда старайтесь использовать select без указания времени ожидания. Ваша программа не должна ничего делать, если нет данных. Код, зависимый от времени ожидания, обычно плохо переносим и сложен для отладки.
    2.
    Для повышения эффективности значение ndfs должно быть правильно вычислено как указано выше.
    3.
    Файловые дескрипторы не должны добавляться в наборы, если вы не планируете после вызова select проверить результат и соответсвующим образом отреагировать.См. следующее правило.
    4.
    После возврата из select должны быть проверены все файловые дескрипторы во всех наборах. В каждый дескриптор, готовый к записи, должны быть записаны данные, и из каждого дескриптора, готового к чтению, данные должны быть прочитаны, и т.д.
    5.
    Функции read(), recv(), write() и send() не обязательно считывают/записывают данные в полном объеме. Такое, конечно, возможно при низком траффике или быстром потоке, однако происходит далеко не всегда. Вы должны рассчитывать, что ваши функции получают/отправляют только один байт за раз.
    6.
    Никогда не считывайте/записывайте побайтно, если только вы не асболютно уверены в том, что нужно обработать небольшой объем данных. Крайне неэффективно считывать/записывать меньшее количество байт, чем вы можете поместь в буфер за один раз. Буферы в вышеприведенном примере имеют размер 1024 байта, однако могут быть легко увеличены до максимального размера пакета в вашей локальной сети.
    7.
    Функции read(), recv(), write() и send(), также как и select() могут возвратить -1 с errno установленным в EINTR или EAGAIN (EWOULDBLOCK), что не является ошибкой. Такие ситуации должны быть правильно обработаны (в вышеприведенной программе этого не сделано). Если ваша программа не собирается принимать сигналы, то маловероятно, что вы получите EINTR. Если ваша программа не использует неблокирующий ввод-вывод, то вы не получите EAGAIN. В любом случае, вы должны обрабатывать эти ошибки для полноты.
    8.
    Никогда не вызывайте read(), recv(), write() или send() с размером буфера, равным нулю.
    9.
    Кроме случаев, описанных в 7., функции read(), recv(), write() и send() никогда не возвращают значение меньшее единицы, если не произошла ошибка. Например, read() при работе с каналом, на котором противоположная сторона завершила работу, возвращает ноль, но возвращает ноль только один раз (все последующие операции чтения или записи возвратят -1). Если хотя бы одна из этих функций вернула 0 или -1, то вы НЕ должны больше использовать этот дескриптор. В примере выше я немедленно закрываю дескриптор и устанавливаю его в -1 для предотвращения его включения в набор.
    10.
    Значение времени ожидания должно быть инициализировано при каждом новом вызове select, так как некоторые операционные системы изменяют структуру.
    11.
    Я слышал, что сокетный уровень в Windows не обрабатывает правильно внепоточные данные. Кроме того, он неправильно работает с select при отсутствии файловых дескрипторов. Отсутствие файловых дескрипторов - это полезный способ перевести процесс в режим ожидания на период времени меньше секунды.

     

    ЭМУЛЯЦИЯ USLEEP

    В системах, не имеющих функции usleep, вы можете использовать select с конечной задержкой и без файловых дескрипторов следующим образом:

        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 200000;  /* 0.2 секунды */
        select (0, NULL, NULL, NULL, &tv);
    

    Это гарантированно работает только на системах Unix.

     

    ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ

    При удачно завершении select возвращает общее число дескрипторов, которые еще присутствкют в наборах.

    При выходе из select по окончании времени ожидания все наборы файловых дескрипторов должны быть пусты (но могут быть не пусты на некторых системах). Возвращаемое значение при этом гарантировано равно нулю.

    Значение -1 сообщает об ошибке, при этом errno устанавливается соответствующим образом. В случае ошибки содержимое наборов и структуры времени ожидания не определено и не должно быть использовано. pselect никогда не изменяет ntimeout.

     

    НАЙДЕННЫЕ ОШИБКИ

    EBADF
    Набор содержит неправильный дескриптор файла. Эта ошибка возвращается, если вы включили в набор файловый дескриптор, уже закрытый функцией close, или если с файловым дескриптором произошла какая-либо ошибка. Вы не должны добавлять в наборы файловые дескрипторы, вернувшие ошибку при чтении или записи.
    EINTR
    Был получен сигнал, такой как SIGINT или SIGCHLD или другой. В этом случае необходимо пересоздать наборы и попробовать еще раз.
    EINVAL
    Значение ndfs отрицательно или в utimeout или в ntimeout указано некорректное значение.
    ENOMEM
    Внутренняя ошибка выделения памяти.

     

    ЗАМЕЧАНИЯ

    В общем случае, все операционные системы, поддерживающие сокеты, поддерживают также и select. Некоторые считают select экзотической и редко используемой функцией. На самом деле многие программы без нее становятся чрезвычайно сложными. select может быть использована для решения задач переносимым и эффективным способом, вместо которого многие программисты пытаются использовать подпроцессы, ветвления процессов, IPC, сигналы, разделение памяти и другие грязные методы. pselect - это более новая функция, используемая не так часто.

    Системный вызов poll(2) имеет такую же функциональность, как и select, но с более грубым поведением. Он менее портируем, чем select.

     

    СООТВЕТСТВИЕ СТАНДАРТАМ

    4.4BSD (функция select впервые появилась в 4.2BSD). В общем случае переносима на/с несовместимые с BSD системы, поддерживающие сокеты BSD (включая варианты System V). Однако стоит обратить внимание на то, что варианты в System V обычно меняют переменную времени ожидания перед выходом, а варианты в BSD этого не делают.

    Функция pselect описана в IEEE Std 1003.1g-2000 (POSIX.1g). Она есть в glibc2.1 и более поздних версиях. В glibc2.0 есть функция с таким именем, но она не имеет аргумента sigmask.

     

    СМ. ТАКЖЕ

    accept(2), connect(2), ioctl(2), poll(2), read(2), recv(2), select(2), send(2), sigaddset(3), sigdelset(3), sigemptyset(3), sigfillset(3), sigismember(3), sigprocmask(2), write(2)

     

    АВТОРЫ

    Эта страница руководства была написана Полом Широм (Paul Sheer).


     

    Index

    НАЗВАНИЕ
    СИНТАКСИС
    ОПИСАНИЕ
    АРГУМЕНТЫ
    КОМБИНИРОВАНИЕ СИГНАЛОВ И ДАННЫХ СОБЫТИЙ
    ПРАКТИКА
    ПРИМЕР ПЕРЕНАПРАВЛЕНИЯ ПОРТА
    ВЫБОР ПРАВИЛА
    ЭМУЛЯЦИЯ USLEEP
    ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ
    НАЙДЕННЫЕ ОШИБКИ
    ЗАМЕЧАНИЯ
    СООТВЕТСТВИЕ СТАНДАРТАМ
    СМ. ТАКЖЕ
    АВТОРЫ


    Поиск по тексту MAN-ов: 




    Партнёры:
    PostgresPro
    Inferno Solutions
    Hosting by Hoster.ru
    Хостинг:

    Закладки на сайте
    Проследить за страницей
    Created 1996-2024 by Maxim Chirkov
    Добавить, Поддержать, Вебмастеру